Ticket #9773: trac_9773-abelian-groups-draft-2.patch

File trac_9773-abelian-groups-draft-2.patch, 30.5 KB (added by rbeezer, 11 years ago)
  • sage/groups/all.py

    # HG changeset patch
    # User Rob Beezer <beezer@ups.edu>
    # Date 1283400941 25200
    # Node ID b8b209f74df25e4f730c7bf2c0fab68367fe74e6
    # Parent  5b338f2e484f2065d3d30d47bc204d6e9ed13d12
    #9773: Implement abelian group classes
    
    diff -r 5b338f2e484f -r b8b209f74df2 sage/groups/all.py
    a b  
    1212from class_function import ClassFunction
    1313
    1414from additive_abelian.all import *
     15
     16from fg_abelian.all import *
     17
  • new file sage/groups/fg_abelian/__init__.py

    diff -r 5b338f2e484f -r b8b209f74df2 sage/groups/fg_abelian/__init__.py
    - +  
     1#
     2 No newline at end of file
  • new file sage/groups/fg_abelian/all.py

    diff -r 5b338f2e484f -r b8b209f74df2 sage/groups/fg_abelian/all.py
    - +  
     1from fg_abelian_group import AdditiveAbelianFGGroup, MultiplicativeAbelianFGGroup
     2from units_modn import UnitsModnGroup
     3 No newline at end of file
  • new file sage/groups/fg_abelian/fg_abelian_group.py

    diff -r 5b338f2e484f -r b8b209f74df2 sage/groups/fg_abelian/fg_abelian_group.py
    - +  
     1r"""
     2Finitely-Generated Abelian Groups
     3---------------------------------
     4
     5Abstract class, built on FG modules, in both additive and multiplicative versions.
     6
     7"""
     8from sage.modules.fg_pid.fgp_module import FGP_Module_class, FGP_Element
     9from sage.structure.parent_gens import ParentWithGens
     10from sage.rings.all import ZZ
     11from sage.misc.cachefunc import cached_method
     12
     13def _cover_and_relations_from_moduli(mods):
     14    r"""
     15    Utility function to construct modules for a quotient construction.
     16
     17    INPUT:
     18
     19    A list of integers, designating the modulus of each term in
     20    a product of cyclic groups.  A zero implies the term is infinite,
     21    i.e. all of `\ZZ`.
     22
     23    OUTPUT:
     24
     25    Two additive FGP_Modules over `\ZZ`, such that their quotient
     26    is naturally isomorphic to the corresponding product of cyclic
     27    groups with given modulus.
     28
     29    This routine provides the necessary minimum input to create
     30    finitely-generated modules over `\ZZ`.  Classes in this module
     31    will augment this input to create objects tat behave as
     32    finitely-generated groups.  the FGP module class will perform
     33    optimizations on these moduli to achieve a minimal set of generators
     34    and an internal representation that is more economical for certain
     35    computations.
     36
     37    EXAMPLE::
     38
     39        sage: from sage.groups.fg_abelian.fg_abelian_group import _cover_and_relations_from_moduli
     40        sage: _cover_and_relations_from_moduli([0,2,3])
     41        (Ambient free module of rank 3 over the principal ideal domain Integer Ring, Free module of degree 3 and rank 2 over Integer Ring
     42        Echelon basis matrix:
     43        [0 2 0]
     44        [0 0 3])
     45
     46    TESTS::
     47
     48        sage: _cover_and_relations_from_moduli([5,2.3,0])
     49        Traceback (most recent call last):
     50        ...
     51        ValueError: List of moduli must be non-negative integers, not [5, 2.30000000000000, 0]
     52        sage: _cover_and_relations_from_moduli([0,4,-5,2])
     53        Traceback (most recent call last):
     54        ...
     55        ValueError: List of moduli must be non-negative integers, not [0, 4, -5, 2]
     56    """
     57    # We need some generators so the generator-parent routine returns
     58    #   something, and we need at least one modulus to get default generators
     59    #   but in the case of trivial groups, the optimized modules will do the right thing
     60    if mods == []:
     61        mods = [1]
     62    if not all([x in ZZ for x in mods]) or not all([x >= 0 for x in mods]):
     63        raise ValueError('List of moduli must be non-negative integers, not %s' % mods)
     64    n = len(mods)
     65    A = ZZ**n
     66    B = A.span([A.gen(i) * mods[i] for i in range(n)])
     67    return A, B
     68
     69class AbelianFGGroup_class(FGP_Module_class):
     70    r"""
     71    This is the abstract base class, has desired functionality, as much as we can push up here.
     72
     73    Lots of abstract methods to get info from additive/multiplicative behavior
     74    """
     75
     76    def __init__(self, cover, relations, generators=None, ambient=None):
     77        r"""
     78        (goes at class level, really)
     79        INPUT:
     80
     81        - cover - module - copies of ZZ, from cover/relations
     82
     83        - relations - module - mod out by moduli to create cyclic groups
     84
     85        - generators - arbitrary elements of some structure, defaults come from derived classes
     86
     87        - ambient - if non-None, indicates a subggroup of some other fg abelian group
     88
     89        """
     90        #sage: S=TestGroup([3,3])
     91        #sage: TestSuite(S).run(verbose = True)
     92        #
     93        # same initial format as constructor of FGP class to allow subquotient, subclass, subgroup
     94        if generators != None and ambient != None:
     95            raise ValueError('cannot specify both generators and an ambient group when creating a finitely generated abelian group')
     96        # degree needs to be nonzero to ensure at least one generator
     97        self._degree = cover.degree()
     98        # ambient=None is a flag, group is "self-ambient", needs generators of some kind though
     99        # otherwise creating a subgroup, so get ambient group from construction, no gens required
     100        # non-None ambient values come from subgroup/quotient construction, so we don't test them here
     101        self._ambient = ambient
     102        if self._ambient is None:   # creating from scratch, store original genes, mods/orders (just in ambient group)
     103            self._ambient = self
     104            if generators is None:
     105                self._orig_gens = tuple(self._default_gens(self._degree))
     106            else:
     107                if not isinstance(generators, (list,tuple)):
     108                    raise TypeError('generators of a finitely generated abelian group must be a list or tuple, not %s' % generators)
     109                if len(generators) != self._degree:
     110                    raise ValueError('the number of generators must equal the number of moduli')
     111                self._orig_gens = tuple(generators) 
     112            # self._orig_gens will be non-empty now
     113            # the relations module has the orders passed to the cover/relation generator
     114            #   so we can recover/extract them here, to use for reporting on ambient's construction
     115            m = relations.basis_matrix()
     116            mods = [0]*m.ncols()
     117            for row,col in zip(m.pivot_rows(), m.pivots()):
     118                mods[col]=m[row,col]
     119            self._orig_mods = tuple(mods)
     120        # parentage of generators comes from a cached method
     121        # could sanity check/examine generators versus apparent orders, common parent
     122        # test for integer multiples in additive groups, exponentiation in multiplicative for efficiency in discrete_exp
     123        #   belongs in derived classes?
     124        # Derived classes will set their category via Parent w/ gens initialization
     125        #   but the gens are the optimized ones from the module class, not the "original" ones here
     126        # We build the quotient module structure now
     127        FGP_Module_class.__init__(self, cover, relations)
     128
     129    def __call__(self, x, check=None):
     130        from sage.structure.element import is_Vector
     131        from sage.rings.all import QQ
     132        # check for lists posing as module elements, else complain | delegate
     133        # send some things up to FGP module
     134        # trap others and test
     135        #
     136        # if parent is this group, then return it
     137        if hasattr(x,'parent') and x.parent() is self:
     138            # print "returning own element"
     139            return x
     140        sanitized = None
     141        # naked lists are scalars for minimal generators, not module elements
     142        if isinstance(x, (list,tuple)):
     143            # print "handling a list"
     144            if len(x) == len(self.invariants()):
     145                sanitized = x
     146            else:
     147                raise ValueError('%s is the wrong length to provide a linear combination of minimal generators' % x)
     148        # an element of the right class gets passed up to be treated as an FGP_Element
     149        elif isinstance(x, self._element_class()):
     150            # print "handling own class"
     151            sanitized = x
     152            ###  CHECK type of elements here?
     153        # vectors are linear combos of original generators
     154        elif is_Vector(x):
     155            # print "Handling a vector"
     156            if len(x) == self._degree:
     157                sanitized = x
     158            else:
     159                raise ValueError('%s is the wrong length to provide a linear combination of original generators' % x)
     160        if sanitized != None:
     161            # print "calling super __call__"
     162            return FGP_Module_class.__call__(self, sanitized, check=check)
     163        else:
     164            # print "Coercing into generator parent"
     165            # first coerce into parent, raiase Type error
     166            # then see if we can log it
     167            try:
     168                x = self.generator_parent()(x)
     169                return self._discrete_log(x)
     170            except:
     171                raise ValueError('cannot interpret %s as an element of %s' % (x, self))
     172
     173
     174    def __contains__(self, x):
     175        r"""
     176        Return true if x is contained in self.
     177        """
     178        if not self.is_finite():
     179            raise TypeError('Unable to decide if %s is in an infinite group: %s' % (x, self))
     180        if not isinstance(x, self._element_class()):
     181            try:
     182                x = self.generator_parent()(x)
     183            except TypeError:
     184                print "gen parent problem"
     185                return False
     186        # have something of the right element class or right generator type
     187        try:
     188            self(x)
     189            return True
     190        except:
     191            return False
     192
     193
     194    # comparison, see module class for __eq__
     195
     196
     197    def _subquotient_class(self):
     198        return self.__class__
     199
     200    def _repr_(self):
     201        return self._ascii_name()
     202
     203
     204    @cached_method
     205    def generator_parent(self):
     206        # Mostly to get 0 or 1 for empty sum/product in discrete exp
     207        return self.ambient_generators()[0].parent()
     208
     209    def _discrete_log(self, element):
     210        # Generic  method
     211        # element is posing as a possible element of the group generated by the generators
     212        # We look to see if it is a (linear) combination, and return the combination as vector over ZZ
     213        # This will be computationally expensive usually, it is a decomposition
     214        # Input: anything that __call__ does not recognize (based on class types)
     215        # Output: a specific element with the "right" discrete exponential
     216        #
     217        if not self.is_finite():
     218            raise TypeError("naive brute-force discrete log is not possible for infinite group")
     219        # self.list() is cached automatically, and discrete_exp is cached purposely
     220        #   subsequent calls become more like lookups
     221        #   first call will be slow, implement speed-ups in discrete_exp
     222        #   or overide this method in a derived class
     223        matches = filter(lambda x: x.discrete_exp() == element, self.list())
     224        if not matches:
     225            raise ValueError("%s is not an element of %s" % (element, self))
     226        if len(matches) > 1:
     227            print "Warning: %s has several representations in %s" % (element, self)
     228        return matches[0]
     229
     230
     231    def ambient_group(self):
     232        # always returns a group with some _orig_gens and _orig_mods
     233        return self._ambient
     234
     235    def ambient_generators(self):
     236        return self.ambient_group()._orig_gens
     237
     238    def ambient_generator_orders(self):
     239        return self.ambient_group()._orig_mods
     240
     241    def is_abelian(self):
     242        return True
     243
     244    def is_ambient(self):
     245        return self.ambient_group() == self
     246
     247    def is_subgroup(self, other):
     248        # self subgroup of other?
     249        # .is_submodule() for FGP requires common relations, tests submodule for covers
     250        # We add check on ambient group too (ensuring common relations)
     251        # modules could be equal, while built with different generators,
     252        #   thus strictness about ambient group check
     253        return self.ambient_group() == other.ambient_group() and self.is_submodule(other)
     254
     255    def is_isomorphic(self, other):
     256        # if other is not abelian class, convert to permutation group
     257        # if other is permutation group, promote self to a permutation group and then test
     258        # as is, just for abelian groups
     259        return self.invariants() == other.invariants()
     260
     261    def is_cyclic(self):
     262        return len(self.smith_form_gens()) < 2
     263
     264    def order(self):
     265        r"""
     266        Return the order of this group (an integer or infinity)
     267
     268        EXAMPLES::
     269
     270            sage: AdditiveAbelianGroup([2,4]).order()
     271            8
     272            sage: AdditiveAbelianGroup([0, 2,4]).order()
     273            +Infinity
     274            sage: AdditiveAbelianGroup([]).order()
     275            1
     276        """
     277        return self.cardinality()
     278
     279    def exponent(self):
     280        r"""
     281        Return the exponent of this group (the smallest positive integer `N`
     282        such that `Nx = 0` for all `x` in the group). If there is no such
     283        integer, return 0.
     284
     285        EXAMPLES::
     286
     287            sage: AdditiveAbelianGroup([2,4]).exponent()
     288            4
     289            sage: AdditiveAbelianGroup([0, 2,4]).exponent()
     290            0
     291            sage: AdditiveAbelianGroup([]).exponent()
     292            1
     293        """
     294        if not self.invariants():
     295            return 1
     296        else:
     297            ann =  self.annihilator().gen()
     298            if ann:
     299                return ann
     300            return ZZ(0)
     301
     302    def cyclic_generator(self):
     303        gens = self.smith_form_gens()
     304        if self.is_cyclic():
     305            if len(gens) == 1:
     306                return(gens[0])
     307            else:
     308                return self.identity()
     309        else:
     310            raise ValueError('No cyclic generator for the non-cyclic %s' % self)
     311
     312    def identity(self):
     313        # Build 0 and 1 from this in derived classes
     314        from sage.modules.free_module_element import vector
     315        trivial = vector(ZZ, [0]*self._degree)
     316        return self(trivial, check=True)
     317
     318    def subgroup(self, gens):
     319        if not isinstance(gens, (list, tuple)):
     320            raise TypeError, "Generators of a subgroup of an abelian group must be in a list or tuple"
     321        gen_vectors = [self(v).lift() for v in gens]
     322        relations = self.relations()  # preserved across subgroups
     323        newcover = self.cover().submodule(gen_vectors) + relations
     324        return self._subquotient_class()(newcover, relations, ambient=self.ambient_group())
     325
     326    def intersection(self, other):
     327        if self.ambient_group() != other.ambient_group():
     328            raise ValueError('cannot intersect subgroups of different groups: %s and %s' % (self, other))
     329        # subgroups preserve relations, so just intersect covers, also preserve ambient group
     330        newcover = self.cover().intersection(other.cover())
     331        return self._subquotient_class()(newcover, self.relations(), ambient=self.ambient_group())
     332
     333
     334    def _cyclic_product_name(self, orders):
     335        r"""
     336        Helper formatting function, direct sum of cyclic groups
     337        INPUT: orders = list of positive integers
     338        OUTPUT: ASCII string
     339        """
     340        # Optimized trivial group can have no generators, so no orders
     341        if orders==() or orders==[]:
     342            orders = [1]
     343        terms = []
     344        for order in orders:
     345            if order:
     346                terms.append('Z_' + str(order))
     347            else:
     348                terms.append('Z')
     349        return ' + '.join(terms)
     350
     351    def _ascii_name(self):
     352        # uses isomorphism class no matter what
     353        # build an ambient name class, direct product, etc.
     354        # finite|infinite
     355        # multiplicative|additive
     356        # group|subgroup(w/ generators)
     357        # {isomorphism class}
     358        rep=[]
     359        if self.is_multiplicative():
     360            rep.append('Multiplicative')
     361        else:
     362            rep.append('Additive')
     363        rep.append('abelian group')
     364        if self.is_finite():
     365            rep.extend(['of order', repr(self.cardinality())+','])
     366        else:
     367            rep.append('of infinite order,')
     368        rep.append('isomorphic to')
     369        rep.append(self._cyclic_product_name(self.invariants()))
     370        rep.append("with generator(s):")
     371        rep.append(', '.join([repr(x) for x in self.gens()]))
     372        return " ".join(rep)
     373
     374
     375    def ambient_name(self):
     376        rep =[]
     377        if self.is_multiplicative():
     378            rep.append('Multiplicative')
     379        else:
     380            rep.append('Additive')
     381        rep.append('abelian group isomorphic to')
     382        rep.append(self._cyclic_product_name(self.ambient_generator_orders()))
     383        rep.append("with generator(s):")
     384        rep.append(', '.join([repr(x) for x in self.ambient_generators()]))
     385        return " ".join(rep)
     386
     387
     388
     389
     390    def info(self):
     391        print "Finite:", self.is_finite()
     392        print "Multiplicative:", self.is_multiplicative()
     393        print "Ambient:", self.is_ambient()
     394        print "Generators:", self.gens()
     395        print "Ambient Gens:", self.ambient_group()._orig_gens
     396        print "Ambient Mods:", self.ambient_group()._orig_mods
     397
     398class AbelianFGGroupElement(FGP_Element):
     399
     400    def __init__(self, parent, x, check=None):
     401        FGP_Element.__init__(self, parent, x, check)
     402
     403    def _discrete_exp(self):
     404        # Generic method
     405        # Assumes just sum or product possible
     406        # Used by the _repr_ and _latex_ and ... routines
     407        # Convert from internal to human representation
     408        # Override in derived classes for better performance
     409        # Input:  an (optimized, enhanced) group element represenation
     410        # Output: an element of the real group being represented, store as side efffect
     411        parent = self.parent()
     412        terms=[]
     413        # replace blunt copies by multiple or power, if that corresponding operation is available
     414        for gen, copies in zip(parent.ambient_group()._orig_gens, self._hermite_lift()):
     415            terms.extend([gen]*copies)
     416        # n.b. terms is empty for identity element of group
     417        return parent._listop()(terms, parent._identity_gen())
     418
     419    @cached_method
     420    def discrete_exp(self):
     421        # Do not override, instead override _discrete_exp
     422        # result is cached with element, so not recomputed
     423        return self._discrete_exp()
     424
     425    # make ascii versions with operators, generators (?)
     426    # define term-format-function and operator/seperator
     427    #    functions in derived additive/multiplicative classes (?)
     428    def representation(self, generators='minimal'):
     429        # Result can differ for an ambient group
     430        #   where minimal generators could be just a re-ordering
     431        if generators=='minimal':
     432            # from module routines, cleaned-up ('reduced')
     433            return self.parent().coordinate_vector(self, reduce=True)
     434        elif generators=='ambient':
     435            # up in the ambient group
     436            return self._hermite_lift()
     437        else:
     438            raise ValueError('"generators" keyword must be "minimal" or "ambient", not %s' % generators)
     439
     440
     441    def _hermite_lift(self):
     442        r"""
     443        This gives a certain canonical lifting of elements of this group
     444        (represented as a quotient `G/H` of free abelian groups) to `G`, using
     445        the Hermite normal form of the matrix of relations.
     446
     447        Mainly used by the ``_repr_`` method.
     448
     449        EXAMPLES::
     450
     451            sage: A = AdditiveAbelianGroup([2, 3])
     452            sage: v = 3000001 * A.0
     453            sage: v.lift()
     454            (3000001, 0)
     455            sage: v._hermite_lift()
     456            (1, 0)
     457        """
     458        y = self.lift()
     459        H = self.parent().W().basis_matrix()
     460
     461        for i in xrange(H.nrows()):
     462            if i in H.pivot_rows():
     463                j = H.pivots()[i]
     464                N = H[i,j]
     465                a = (y[j] - (y[j] % N)) // N
     466                y = y - a*H.row(i)
     467        return y.change_ring(ZZ)
     468
     469    def _repr_(self):
     470        return self.discrete_exp()._repr_()
     471
     472
     473#~~~~~~~~~~~~~~~~~~~~~~  Additive  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     474from sage.categories.commutative_additive_groups import CommutativeAdditiveGroups
     475
     476class AdditiveAbelianFGGroup_class(AbelianFGGroup_class):
     477
     478    def __init__(self, cover, relations, generators=None, ambient=None):
     479        ParentWithGens.__init__(self, self, category=CommutativeAdditiveGroups())
     480        AbelianFGGroup_class.__init__(self, cover, relations, generators=generators, ambient=ambient)
     481
     482    def _element_class(self):
     483        return AdditiveAbelianFGGroupElement
     484
     485    def _listop(self):
     486        from sage.all import sum
     487        return sum
     488
     489    def _identity_gen(self):
     490        #return self.generator_parent().zero()
     491        return self.generator_parent()(0)
     492
     493    def _default_gens(self, deg):
     494        # returns a tuple
     495        from sage.modules.free_module_element import vector
     496        basis = []
     497        for i in range(deg):
     498            zero = vector(ZZ, [0]*deg)
     499            zero[i] = 1
     500            basis.append(zero)
     501        return tuple(basis)
     502
     503    def is_multiplicative(self):
     504        return False
     505
     506
     507class AdditiveAbelianFGGroupElement(AbelianFGGroupElement):
     508
     509    def __init__(self, parent, x, check=None):
     510        AbelianFGGroupElement.__init__(self, parent, x, check)
     511
     512#~~~~~~~~~~~~~~~~~~~~~~  Multiplicative  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     513from sage.categories.groups import Groups
     514
     515class MultiplicativeAbelianFGGroup_class(AbelianFGGroup_class):
     516
     517    def __init__(self, cover, relations, generators=None, ambient=None):
     518        # nee commutative?
     519        ParentWithGens.__init__(self, self, category=Groups())
     520        AbelianFGGroup_class.__init__(self, cover, relations, generators=generators, ambient=ambient)
     521
     522    def _element_class(self):
     523        return MultiplicativeAbelianFGGroupElement
     524
     525    def _listop(self):
     526        from sage.all import prod
     527        return prod
     528
     529    def _identity_gen(self):
     530        # return self.generator_parent().one()
     531        return self.generator_parent()(1)
     532
     533    def _default_gens(self, deg):
     534        # returns a tuple
     535        from sage.symbolic.ring import SymbolicRing
     536        SR=SymbolicRing()
     537        return tuple([SR('f'+str(i+1)) for i in range(deg)])
     538
     539    def is_multiplicative(self):
     540        return True
     541
     542
     543class MultiplicativeAbelianFGGroupElement(AbelianFGGroupElement):
     544
     545    def __init__(self, parent, elt, check=None):
     546        AbelianFGGroupElement.__init__(self, parent, elt, check)
     547
     548    def __mul__(self, other):
     549        return self+other
     550
     551    def __pow__(self, other):
     552        return other*self
     553
     554    # make sum return not implemented
     555    # Also integer multiples
     556
     557#~~~~~~~~~~~~~~~~~~~~~~  Builders  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     558
     559def AdditiveAbelianFGGroup(orders, generators=None):
     560    r"""
     561    Creates a finitely-generated additive abelian group.
     562
     563    INPUT:
     564
     565    - ``orders`` - a list of integers giving the orders of the
     566      generators (in the same order).  If no generators are
     567      given these are theorders of the default generators.
     568      Use a zero to specify an infinite order generator.
     569
     570    - ``generators`` - a list of elements with a common parent.
     571      The only other requirement is that it must be possible to
     572      add two elements.  When no generators are given, the defaults
     573      provided are dense vectors (module elements) over the integers.
     574      We do not presume the generators are independent, but performance
     575      and results will be better if they are.
     576
     577    OUTPUT:
     578
     579    An element of the XXX class that is an additive abelian group.
     580
     581    EXAMPLES:
     582
     583    A finite group with default generators. ::
     584
     585        sage: G = AdditiveAbelianFGGroup([2,4]); G
     586        Additive abelian group of order 8, isomorphic to Z_2 + Z_4 with generator(s): (1, 0), (0, 1)
     587        sage: G.list()
     588        [(0, 0), (0, 1), (0, 2), (0, 3), (1, 0), (1, 1), (1, 2), (1, 3)]
     589
     590    Notice that generators generally get replaced by a smaller set.  The
     591    generators used to create the group (or if a subgroup, the generators
     592    used to create the universal, or ambient, group) can be requested. ::
     593
     594        sage: G = AdditiveAbelianFGGroup([2,3]); G
     595        Additive abelian group of order 6, isomorphic to Z_6 with generator(s): (1, 2)
     596        sage: G.gens()
     597        ((1, 2),)
     598        sage: G.ambient_generators()
     599        ((1, 0), (0, 1))
     600        sage: G.generator_parent()
     601        Ambient free module of rank 2 over the principal ideal domain Integer Ring
     602
     603    Generators can be any element with an addition defined.
     604
     605        sage: E = EllipticCurve('30a2')
     606        sage: points = [E(4,-7,1), E(7/4, -11/8, 1), E(3, -2, 1)]
     607        sage: M = AdditiveAbelianFGGroup([3, 2, 2], points)
     608        sage: M
     609        Additive abelian group of order 12, isomorphic to Z_2 + Z_6 with generator(s): (7/4 : -11/8 : 1), (13 : -52 : 1)
     610        sage: M.ambient_generators()
     611        ((4 : -7 : 1), (7/4 : -11/8 : 1), (3 : -2 : 1))
     612        sage: M.list()
     613        [(0 : 1 : 0), (13 : -52 : 1), (4 : -7 : 1), (3 : -2 : 1), (4 : 2 : 1), (13 : 38 : 1), (7/4 : -11/8 : 1), (1 : -4 : 1), (-2 : -7 : 1), (-5 : 2 : 1), (-2 : 8 : 1), (1 : 2 : 1)]
     614    """
     615    cover, relations = _cover_and_relations_from_moduli(orders)
     616    return AdditiveAbelianFGGroup_class(cover, relations, generators=generators)
     617
     618
     619def MultiplicativeAbelianFGGroup(orders, generators=None):
     620    cover, relations = _cover_and_relations_from_moduli(orders)
     621    return MultiplicativeAbelianFGGroup_class(cover, relations, generators=generators)
  • new file sage/groups/fg_abelian/units_modn.py

    diff -r 5b338f2e484f -r b8b209f74df2 sage/groups/fg_abelian/units_modn.py
    - +  
     1def UnitsModnGroup(n):
     2    r"""
     3    Creates a group with the units (invertible elements) under multiplication mod n.
     4
     5    INPUT:
     6
     7    - ``n`` - a positive non-zero integer
     8
     9    OUTPUT:
     10
     11    A multiplicative abelian group containing all the invertible elements (units)
     12    when the operation is multiplication mod n.
     13
     14    EXAMPLES:
     15
     16    A nontrivial example, illustrating the possible operations. ::
     17
     18        sage: n = 2^3 * 3^2 * 7^2
     19        sage: U = UnitsModnGroup(n); U
     20        Multiplicative abelian group of order 1008, isomorphic to Z_2 + Z_2 + Z_6 + Z_42 with generator(s): 2647, 685, 785, 2053
     21        sage: U.order() == euler_phi(n)
     22        True
     23
     24    The original generators are all elements of prime-power order,
     25    while the computed generators have orders equal to the
     26    invariants of the abelian group.  ::
     27
     28        sage: U.ambient_generators()
     29        (2647, 1765, 1961, 2353, 2449, 3313, 1513)
     30        sage: U.ambient_generator_orders()
     31        (2, 2, 2, 3, 2, 3, 7)
     32        sage: U.gens()
     33        (2647, 685, 785, 2053)
     34        sage: [x.order() for x in U.gens()]
     35        [2, 2, 6, 42]
     36        sage: U.invariants()
     37        (2, 2, 6, 42)
     38
     39    Subgroups, intersections and quotients are all possible.  ::
     40
     41        sage: a = U(3331); b = U(757); c = U(785); d = U(2377);
     42        sage: X=U.subgroup([a,b])
     43        sage: X
     44        Multiplicative abelian group of order 28, isomorphic to Z_2 + Z_14 with generator(s): 1567, 757
     45        sage: Y=U.subgroup([c,d])
     46        sage: Y
     47        Multiplicative abelian group of order 126, isomorphic to Z_3 + Z_42 with generator(s): 2353, 2249
     48        sage: A=X.intersection(Y)
     49        sage: A
     50        Multiplicative abelian group of order 7, isomorphic to Z_7 with generator(s): 1513
     51        sage: A.is_cyclic()
     52        True
     53        sage: A.list()
     54        [1, 1513, 3025, 1009, 2521, 505, 2017]
     55        sage: A.0 in X and A.0 in Y
     56        True
     57        sage: b^2
     58        1513
     59        sage: d^3
     60        1513
     61        sage: X.is_subgroup(U)
     62        True
     63        sage: U.is_subgroup(Y)
     64        False
     65        sage: U/X
     66        Multiplicative abelian group of order 36, isomorphic to Z_6 + Z_6 with generator(s): f3*f6^2, f4^2*f5
     67        sage: Y/A
     68        Multiplicative abelian group of order 18, isomorphic to Z_3 + Z_6 with generator(s): f6, f3*f4^2
     69
     70    A Cayley table for a group of units. ::
     71
     72        sage: U = UnitsModnGroup(28); U
     73        Multiplicative abelian group of order 12, isomorphic to Z_2 + Z_6 with generator(s): 15, 17
     74        sage: U.cayley_table(names='elements')
     75         *   1 17  9 13 25  5 15  3 23 27 11 19
     76          +------------------------------------
     77         1|  1 17  9 13 25  5 15  3 23 27 11 19
     78        17| 17  9 13 25  5  1  3 23 27 11 19 15
     79         9|  9 13 25  5  1 17 23 27 11 19 15  3
     80        13| 13 25  5  1 17  9 27 11 19 15  3 23
     81        25| 25  5  1 17  9 13 11 19 15  3 23 27
     82         5|  5  1 17  9 13 25 19 15  3 23 27 11
     83        15| 15  3 23 27 11 19  1 17  9 13 25  5
     84         3|  3 23 27 11 19 15 17  9 13 25  5  1
     85        23| 23 27 11 19 15  3  9 13 25  5  1 17
     86        27| 27 11 19 15  3 23 13 25  5  1 17  9
     87        11| 11 19 15  3 23 27 25  5  1 17  9 13
     88        19| 19 15  3 23 27 11  5  1 17  9 13 25
     89    """
     90    from sage.rings.finite_rings.integer_mod_ring import IntegerModRing
     91    from sage.groups.fg_abelian.fg_abelian_group import _cover_and_relations_from_moduli, MultiplicativeAbelianFGGroup_class
     92    # sanity check n
     93    R=IntegerModRing(n)
     94    # Build with elements strictly of prime-power order
     95    # Optimized module code will consolidate to minimal generators
     96    newgens = []
     97    for gen in R.unit_gens():
     98        order = gen.multiplicative_order()
     99        prime_power = [gen**(order/(base**exponent)) for base,exponent in gen.multiplicative_order().factor()]
     100        newgens.extend(prime_power)
     101    #gens = R.unit_gens()
     102    #newgens = []
     103    #for gen in gens:
     104        #order = gen.multiplicative_order()
     105        #for base, exponent in order.factor():
     106            #newgen = gen**(order/(base**exponent))
     107            #newgens.append(newgen)
     108    orders = [x.multiplicative_order() for x in newgens]
     109    cover, relations = _cover_and_relations_from_moduli(orders)
     110    return MultiplicativeAbelianFGGroup_class(cover, relations, generators=newgens)
     111
  • setup.py

    diff -r 5b338f2e484f -r b8b209f74df2 setup.py
    a b  
    829829                     'sage.groups',
    830830                     'sage.groups.abelian_gps',
    831831                     'sage.groups.additive_abelian',
     832                     'sage.groups.fg_abelian',
    832833                     'sage.groups.matrix_gps',
    833834                     'sage.groups.perm_gps',
    834835                     'sage.groups.perm_gps.partn_ref',