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

File trac_9773-abelian-groups-draft-5.patch, 30.2 KB (added by rbeezer, 9 years ago)
  • new file sage/groups/fg_abelian/fg_abelian_group.py

    # HG changeset patch
    # User Rob Beezer <beezer@ups.edu>
    # Date 1337294569 25200
    # Node ID 8709c50fe3570f5ee36e6539b4190a5fc7a8d44f
    # Parent  7a2a36b9da0fc4bfc82fbd11ca7d48f741f74bf7
    9773: finitely-generated abelian groups
    
    diff --git a/sage/groups/fg_abelian/__init__.py b/sage/groups/fg_abelian/__init__.py
    new file mode 100644
    diff --git a/sage/groups/fg_abelian/fg_abelian_group.py b/sage/groups/fg_abelian/fg_abelian_group.py
    new file mode 100644
    - +  
     1r"""
     2Fix me and remove later (ie get correct names into global namespace) ::
     3
     4    sage: from sage.groups.fg_abelian.fg_abelian_group import *
     5
     6Direct Product of Cyclic Groups, Additive ::
     7
     8    sage: H = CyclicProductGroup([3,5,5])
     9    sage: H
     10    Product of cyclic groups: C_3 x C_5 x C_5
     11    sage: H.order()
     12    75
     13    sage: H.cardinality()
     14    75
     15    sage: H.invariants()
     16    (5, 15)
     17   
     18    sage: elts = list(H)
     19    sage: c = elts[22]; c
     20    (1, 4, 1)
     21    sage: c.order()
     22    15
     23    sage: d = elts[39]; d
     24    (0, 3, 2)
     25    sage: d.order()
     26    5
     27    sage: e = c + d; e
     28    (1, 2, 3)
     29    sage: e.order()
     30    15
     31   
     32    sage: TestSuite(H).run()
     33
     34    sage: x = elts[22]
     35    sage: y = elts[50]
     36    sage: x, y
     37    ((1, 4, 1), (2, 0, 3))
     38    sage: x.order(), y.order()
     39    (15, 15)
     40    sage: L = H.subgroup([x,y])
     41    sage: L.order()
     42    75
     43    sage: L
     44    Subgroup of (Product of cyclic groups: C_3 x C_5 x C_5) generated by ((0, 0, 1), (1, 2, 0))
     45    sage: L.is_subgroup(H)
     46    True
     47    sage: TestSuite(L).run()
     48   
     49    sage: K = CyclicProductGroup([2,2,4])
     50    sage: sorted(list(K))
     51    [(0, 0, 0), (0, 0, 1), (0, 0, 2), (0, 0, 3),
     52     (1, 0, 0), (1, 0, 1), (1, 0, 2), (1, 0, 3),
     53     (0, 1, 0), (0, 1, 1), (0, 1, 2), (0, 1, 3),
     54     (1, 1, 0), (1, 1, 1), (1, 1, 2), (1, 1, 3)]
     55    sage: TestSuite(K).run()
     56
     57Infinite orders are fine, but not rigorously tested yet. ::
     58
     59    sage: C = CyclicProductGroup([4, 0, 5])
     60    sage: C
     61    Product of cyclic groups: C_4 x C x C_5
     62    sage: C.order()
     63    +Infinity
     64    sage: C.invariants()
     65    (20, 0)
     66    sage: C.gens()
     67    ((3, 0, 4), (0, 1, 0))
     68
     69    sage: TestSuite(C).run()
     70
     71    sage: a = C(vector(ZZ, (0,4,0))) # might need to change
     72    sage: a
     73    (0, 4, 0)
     74    sage: a.order()
     75    +Infinity
     76    sage: D = C.subgroup([a])
     77    sage: D
     78    Subgroup of (Product of cyclic groups: C_4 x C x C_5) generated by (0, 4, 0)
     79    sage: D.order()
     80    +Infinity
     81   
     82
     83Group of Units, mod m ::
     84
     85    sage: U = UnitsModmGroup(40)
     86    sage: U
     87    Group of units mod 40
     88    sage: els = list(U)
     89    sage: els
     90    [1, 17, 9, 33, 31, 7, 39, 23, 21, 37, 29, 13, 11, 27, 19, 3]
     91    sage: a = els[1]
     92    sage: b = els[4]
     93    sage: a, b, a*b
     94    (17, 31, 7)
     95    sage: a.order()
     96    4
     97    sage: b.order()
     98    2
     99    sage: (a*b).order()
     100    4
     101    sage: TestSuite(U).run()
     102
     103Constructing, testing a subgroup;  mathematically,
     104U = U(40)
     105B = <17> = {1, 17, 9, 33}
     106C = <11> = {1, 11}
     107D = <9>  = {1, 9}
     108E = D, but formed from B ::
     109
     110    sage: B = U.subgroup([a])
     111    sage: B
     112    Subgroup of (Group of units mod 40) generated by 17
     113    sage: Blist = list(B); Blist
     114    [1, 17, 9, 33]
     115    sage: B.invariants()
     116    (4,)
     117    sage: # TestSuite(B).run()
     118
     119    sage: U.is_ambient()
     120    True
     121    sage: B.is_ambient()
     122    False
     123    sage: B.ambient() is U
     124    True
     125    sage: B.is_subgroup(U)
     126    True
     127    sage: c = els[12]; c
     128    11
     129    sage: C = U.subgroup([c])
     130    sage: C
     131    Subgroup of (Group of units mod 40) generated by 11
     132    sage: list(C)
     133    [1, 11]
     134    sage: C.is_subgroup(U)
     135    True
     136    sage: B.is_subgroup(C)
     137    False
     138    sage: C.is_subgroup(B)
     139    False
     140    sage: d = Blist[2]; d
     141    9
     142    sage: D = U.subgroup([d])
     143    sage: D
     144    Subgroup of (Group of units mod 40) generated by 9
     145    sage: D.is_subgroup(D)
     146    True
     147    sage: D.is_subgroup(B)
     148    True
     149    sage: E = B.subgroup([d])
     150    sage: E
     151    Subgroup of (Group of units mod 40) generated by 9
     152    sage: E.is_subgroup(B)
     153    True
     154    sage: E.is_subgroup(U)
     155    True
     156    sage: E.is_subgroup(C)
     157    False
     158
     159No effort made to implement equality tests, which seem to work based on FGP module code.  Should probably check ambients first, or ambient's generators.
     160
     161    sage: D == E
     162    True
     163    sage: C == E
     164    False
     165
     166Wrapper functionality, test copied from "Abelian group" class, amended as needed.
     167We create a toy example based on the Mordell-Weil group of an elliptic curve over `\QQ`
     168(_test_elements fails if the test suite is enabled).
     169
     170::
     171
     172    sage: E = EllipticCurve('30a2')
     173    sage: pts = [E(4,-7,1), E(7/4, -11/8, 1), E(3, -2, 1)]
     174    sage: M = AdditiveAbelianFGGroup( pts, [3, 2, 2])
     175    sage: M
     176    Additive abelian group isomorphic to Z/2 + Z/6 embedded in Abelian group of
     177    points on Elliptic Curve defined by y^2 + x*y + y = x^3 - 19*x + 26 over
     178    Rational Field
     179    sage: M.gens()
     180    ((7/4 : -11/8 : 1), (13 : -52 : 1))
     181    sage: pts[0]+ pts[0]+ pts[2]
     182    (13 : -52 : 1)
     183    sage: 6*M.1
     184    (0 : 1 : 0)
     185    sage: 6000000000000001 * M.1
     186    (13 : -52 : 1)
     187    sage: # TestSuite(E).run()
     188
     189Abstract Cyclic Groups ::
     190
     191    sage: C = CyclicGroup(12, 'b')
     192    sage: C
     193    Cyclic group of order 12 generated by b
     194    sage: list(C)
     195    [1, b, b^2, b^3, b^4, b^5, b^6, b^7, b^8, b^9, b^10, b^11]
     196    sage: C.order()
     197    12
     198    sage: C.invariants()
     199    (12,)
     200    sage: C.gens()
     201    (b,)
     202    sage: dd = list(C)[3]
     203    sage: dd
     204    b^3
     205    sage: dd * dd * dd * dd * dd * dd
     206    b^6
     207    sage: D = C.subgroup([dd])
     208    sage: D
     209    Subgroup of (Cyclic group of order 12 generated by b) generated by b^3
     210    sage: D.order()
     211    4
     212    sage: D.invariants()
     213    (4,)
     214    sage: D.gens()
     215    (b^3,)
     216    sage: # TestSuite(C).run()
     217
     218A generator syntax may be used to create cyclic groups ::
     219
     220    sage: B.<c> = CyclicGroup(10)
     221    sage: B
     222    Cyclic group of order 10 generated by c
     223    sage: list(B)
     224    [1, c, c^2, c^3, c^4, c^5, c^6, c^7, c^8, c^9]
     225    sage: B.gens()
     226    (c,)
     227    sage: B._first_ngens(1)
     228    (c,)
     229
     230And the infinite cyclic group.  ::
     231
     232    sage: G.<y> = CyclicGroup(0)
     233    sage: G
     234    Cyclic group of order +Infinity generated by y
     235    sage: G.gens()
     236    (y,)
     237
     238Permutation group representations allow the use of an expanded
     239repertoire of methods (until implemented natively) for finite
     240abelian groups. ::
     241
     242    sage: C.<d> = CyclicGroup(2^4*3^2*5)
     243    sage: C
     244    Cyclic group of order 720 generated by d
     245    sage: C.as_permutation_group()
     246    Permutation Group with generators [(26,27,28,29,30),
     247    (17,18,19,20,21,22,23,24,25), (1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16)]
     248    sage: P=C.as_permutation_group()
     249    sage: P.order()
     250    720
     251    sage: P.is_cyclic()
     252    True
     253    sage: [K.order() for K in P.subgroups()]
     254    [1, 2, 3, 4, 5, 6, 8, 9, 10, 12, 15, 16, 18, 20, 24, 30,
     255     36, 40, 45, 48, 60, 72, 80, 90, 120, 144, 180, 240, 360, 720]
     256
     257    sage: Q = CyclicProductGroup([3,3,3])
     258    sage: Q
     259    Product of cyclic groups: C_3 x C_3 x C_3
     260    sage: P = Q.as_permutation_group()
     261    sage: P
     262    Permutation Group with generators [(7,8,9), (4,5,6), (1,2,3)]
     263    sage: [K.order() for K in P.subgroups()]
     264    [1, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 27]
     265
     266"""
     267
     268# *PROBLEM*: Muliplicative examples in TestSuite() are commented out above
     269# Failures blame:  _test_additive_associativity, _test_category, _test_prod, _test_zero
     270# The "additive associtivity" and "zero" tests are unexplained
     271
     272
     273from sage.misc.cachefunc import cached_method
     274from sage.rings.all import ZZ
     275from sage.modules.fg_pid.fgp_module import FGP_Module_class, FGP_Element
     276from sage.structure.parent import Parent
     277
     278from sage.categories.commutative_additive_groups import CommutativeAdditiveGroups
     279from sage.categories.groups import Groups
     280
     281# Helper method to build appropriate quotient modules
     282def _cover_and_relations_from_moduli(moduli):
     283    r"""
     284    Utility function to construct modules for a quotient construction.
     285
     286    (Existing code, from sage.groups.additive_abelian.additive_abelian_wrapper)
     287
     288    INPUT:
     289
     290    A list of integers, designating the modulus of each term in
     291    a product of cyclic groups.  A zero implies the term is infinite,
     292    i.e. all of `\ZZ`.
     293
     294    OUTPUT:
     295
     296    Two additive FGP_Modules over `\ZZ`, such that their quotient
     297    is naturally isomorphic to the corresponding product of cyclic
     298    groups with given modulus.
     299
     300    This routine provides the necessary minimum input to create
     301    finitely-generated modules over `\ZZ`.  Classes in this module
     302    will augment this input to create objects tat behave as
     303    finitely-generated groups.  the FGP module class will perform
     304    optimizations on these moduli to achieve a minimal set of generators
     305    and an internal representation that is more economical for certain
     306    computations.
     307
     308    EXAMPLE::
     309
     310        sage: from sage.groups.fg_abelian.fg_abelian_group import _cover_and_relations_from_moduli
     311        sage: _cover_and_relations_from_moduli([0,2,3])
     312        (Ambient free module of rank 3 over the principal ideal domain Integer Ring, Free module of degree 3 and rank 2 over Integer Ring
     313        Echelon basis matrix:
     314        [0 2 0]
     315        [0 0 3])
     316
     317    TESTS::
     318
     319        sage: _cover_and_relations_from_moduli([5,2.3,0])
     320        Traceback (most recent call last):
     321        ...
     322        ValueError: List of moduli must be non-negative integers, not [5, 2.30000000000000, 0]
     323        sage: _cover_and_relations_from_moduli([0,4,-5,2])
     324        Traceback (most recent call last):
     325        ...
     326        ValueError: List of moduli must be non-negative integers, not [0, 4, -5, 2]
     327    """
     328    if moduli == []:
     329        raise ValueError('List of moduli cannot be empty')
     330    if not all([x in ZZ for x in moduli]) or not all([x >= 0 for x in moduli]):
     331        raise ValueError('List of moduli must be non-negative integers, not %s' % moduli)
     332    n = len(moduli)
     333    A = ZZ**n
     334    B = A.span([A.gen(i) * moduli[i] for i in range(n)])
     335    return A, B
     336
     337
     338# Class Hierarchy
     339# ~~~~~~~~~~~~~~~
     340#
     341# AbelianFGGroup_class
     342#     - AdditiveAbelianFGGroup_class
     343#         - CyclicProductGroup_class
     344#     - MultiplicativeAbelianFGGroup_class
     345#         - UnitsModmGroup_class
     346#         - CyclicGroup_class
     347#
     348# User-level creation functions
     349#     - AdditiveAbelianFGGroup (generic)
     350#     - CyclicProductGroup
     351#     - MultiplicativeAbelianFGGroup (generic)
     352#     - UnitsModmGroup
     353#     - CyclicGroup
     354
     355
     356#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     357# Abstract Finitely-Generated Abelian Groups
     358#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     359
     360class AbelianFGGroupElement(FGP_Element):
     361    r"""
     362    The main abstract element class:
     363        (a) trade on as much of the FGP Module code as possible
     364        (b) otherwise make general enough to put lots of code here
     365    """
     366
     367    def __init__(self, parent, x, check=None):
     368        FGP_Element.__init__(self, parent, x, check)
     369
     370    def _hermite_lift(self):
     371        r"""
     372        This gives a certain canonical lifting of elements of this group
     373        (represented as a quotient `G/H` of free abelian groups) to `G`, using
     374        the Hermite normal form of the matrix of relations.
     375
     376        Mainly used by the ``_repr_`` method.
     377
     378        (From sage.groups.additive_abelian.additive_abelian_group.AdditiveAbelianGroupElement)
     379
     380        Returns an element of the covering free module with positive
     381        entries, each as small as possible.
     382
     383        EXAMPLES::
     384
     385            sage: A = AdditiveAbelianGroup([2, 3])
     386            sage: v = 3000001 * A.0
     387            sage: v.lift()
     388            (3000001, 0)
     389            sage: v._hermite_lift()
     390            (1, 0)
     391        """
     392        y = self.lift()
     393        H = self.parent().W().basis_matrix()
     394
     395        for i in xrange(H.nrows()):
     396            if i in H.pivot_rows():
     397                j = H.pivots()[i]
     398                N = H[i,j]
     399                a = (y[j] - (y[j] % N)) // N
     400                y = y - a*H.row(i)
     401        return y.change_ring(ZZ)
     402
     403    def _repr_(self):
     404        return repr(self._optimized_to_user())
     405
     406class AbelianFGGroup_class(FGP_Module_class):
     407    r"""
     408    The main abstract parent class:
     409        (a) trade on as much of the FGP Module code as possible
     410        (b) otherwise make general enough to put lots of code here
     411    """
     412    Element = AbelianFGGroupElement
     413
     414    def __init__(self, cover, relations, category, ambient, gens):
     415        r"""
     416        (a) to create a new (super)group, pass in "ambient=None" and then
     417        also include a list of generators from the structure being emulated.
     418        Ambient will be set to the group being created.
     419        (b) For a new (sub)group pass in the relevant (super)group as "ambient"
     420        and do not give generators (the complete list is part of ambient).
     421        """
     422        if ambient is None:
     423            self._ambient = self
     424            if gens is None:
     425                print "ambient group needs generators"
     426            else:
     427                self._generators = gens
     428                # for convenience we record the parent of the generators
     429                self._generators_parent = gens[0].parent()
     430        else:
     431            self._ambient = ambient
     432            if gens:
     433                print "don't pass any generators when there is ambient group"
     434        #
     435        # We build the quotient module structure now
     436        # NB: calling the FGP_Module class sets some
     437        # category information incorrectly, and as a consequence
     438        # the multiplicative descendants still try additive
     439        # associativity in the test suites
     440        #
     441        # So we instead mimic the functionality of
     442        # FGP_Module_class.__init__()
     443        #
     444        # FGP_Module_class.__init__(self, cover, relations)
     445        self._V = cover
     446        self._W = relations
     447        Parent.__init__(self, category=category)
     448
     449    def _user_to_optimized(self, user):
     450        r"""
     451        A naive brute-force discrete log:
     452        Find a module element that converts to a given group element.
     453        Overide if this can be improved in a given class.
     454        """
     455        opt_iterator = self.__iter__()
     456        try:
     457            while True:
     458                opt = opt_iterator.next()
     459                if opt._optimized_to_user() == user:
     460                    return opt
     461        except StopIteration:
     462            raise ValueError('{0} is not an element of {1}'.format(user, self))
     463
     464    def _element_constructor_(self, x, check=True):
     465        r"""
     466        This is a *PROBLEM*.
     467
     468        Code is verbatim from FGP_Module's element constructor.
     469
     470        A ZZ-vector (free module element, really) is a natural element of
     471        the supporting quotient of modules and takes no manipulations.
     472        (See example.)
     473
     474        A list or tuple is interpreted as a linear combination of the
     475        optimized representation in the FGP module code and converted to
     476        a vector.  (See example.)
     477
     478        An actual FGP element is lifted to a vector, then suitably interpreted.
     479
     480        Two examples:
     481       
     482        C = CyclicProductGroup([4, 0, 5])
     483        a = C(vector(ZZ, (0,4,0)))
     484        a
     485        (0, 4, 0)
     486       
     487        C.gens()
     488        ((3, 0, 4), (0, 1, 0))
     489        b = C([1,2])
     490        b
     491        (3, 2, 4)
     492
     493        How to pass in elements of the parent of the generators?
     494        The _user_to_optimized() routine should be able to creat an FGP element.
     495        But every time I try, all the FGP Module routines break.
     496
     497        Also, conceivably a group could be built from ZZ-vectors in
     498        some non-standard way and this routine would need to recognize them as not
     499        being ZZ-vectors of the underlying module.  Maybe the type
     500        (additive, multiplicative elements?) would catch this possibility?
     501
     502        Uncomment the print statement below and create the subgroup B above
     503        as a subgroup of the group of units, then print it (which creates
     504        generators).  Restart (or remove caching of the smith_form_generators
     505        method) and repeat the creation of B and then ask for the .smith_form_generators()
     506        of B.  It seems that in the smith_form_generators routine the rows of Z get
     507        passed into "self()" which might be circular?
     508
     509        Adding the following code to the beginning of this routine
     510        seems to make creating some elements possible (with units
     511        mod 40 from tests).  Totally hangs up creating smith_gens
     512        of a subgroup though.
     513
     514        try:
     515            y = self._gens_parent()(x)
     516            in_parent= True
     517        except:
     518            in_parent = False
     519        if in_parent:
     520            x = self._user_to_optimized(y)
     521       
     522        """
     523        # This is an interesting diagnostic statement
     524        # print type(x), x
     525        if isinstance(x, (list,tuple)):
     526            try:
     527                x = self.optimized()[0].V().linear_combination_of_basis(x)
     528            except ValueError, msg:
     529                raise TypeError, msg
     530        elif isinstance(x, FGP_Element):
     531            x = x.lift()
     532        return self.element_class(self, self._V(x))
     533       
     534    def _repr_(self):
     535        if self._ambient is self:
     536            return self._ambient_string()
     537        else:
     538            gens = self.gens()
     539            if len(gens) == 1:
     540                gens = gens[0]
     541            msg = "Subgroup of ({}) generated by {}"
     542            return msg.format(self._ambient_string(), gens)       
     543       
     544    def _identity(self):
     545        r"""
     546        Zero vector of module,
     547        will be zero element additively,
     548        the one element multiplicatively
     549        """
     550        from sage.modules.free_module_element import zero_vector
     551        zero = zero_vector(ZZ, self.cover().degree())
     552        return self(zero)
     553
     554    def _ambient_gens(self):
     555        return self._ambient._generators
     556
     557    def _gens_parent(self):
     558        return self._ambient._generators_parent
     559
     560    def is_ambient(self):
     561        return self._ambient is self
     562
     563    def ambient(self):
     564        return self._ambient
     565       
     566    def subgroup(self, gens):
     567        r"""
     568        """
     569        import sage.modules.module
     570        S = self.submodule(gens)
     571        return (self.classtype())(S.cover(), S.relations(), ambient=self.ambient())
     572
     573    def is_subgroup(self, other):
     574        if not self.ambient() is other.ambient():
     575            return False
     576        return self.is_submodule(other)
     577       
     578    def order(self):
     579        r"""
     580        Duplicates cardinality from FG PID modules.
     581        """
     582        return self.cardinality()
     583
     584    def as_permutation_group(self):
     585        r"""
     586        Allows full range of properties from permutation group implementation
     587        Prime powers lessen overall degree, using invariants instead
     588        might allow natural isomorphism to Smith form generators
     589        """
     590        from sage.groups.perm_gps.permgroup import  PermutationGroup
     591        # descending list of prime powers characterizing group
     592        pp = [f[0]**f[1] for i in self.invariants() for f in i.factor()]
     593        pp.sort(reverse=True)
     594        gens = []
     595        start = 1
     596        for cycle_length in pp:
     597            gens.append(tuple(range(start, start + cycle_length)))
     598            start = start + cycle_length
     599        return PermutationGroup(gens=gens)
     600       
     601    def is_isomorphic(self, other):
     602        r"""
     603        Improvement:  Allow "other" to be perm group,
     604        then in this case convert self to perm group representation
     605        """
     606        # error check other as fg abelian
     607        return self.invariants() == other.invariants()
     608       
     609#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     610# Additive Finitely-Generated Abelian Groups
     611#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     612
     613class AdditiveAbelianFGGroupElement(AbelianFGGroupElement):
     614
     615    def __init__(self, parent, x, check=None):
     616        AbelianFGGroupElement.__init__(self, parent, x, check)
     617
     618    def _optimized_to_user(self):
     619        # convert the optimized value (in self._x via hermite lift)
     620        # to what the user will eventually see
     621        # default is cover module elements, vectors over ZZ
     622        parent = self.parent()
     623        gens = parent._ambient_gens()
     624        v = self._hermite_lift()
     625        # add appropriate number of copies
     626        s = parent._gens_parent()(0)  # initialize with zero element of gens' parent
     627        for i in range(len(gens)):
     628            s += gens[i]*v[i]
     629        return s
     630
     631class AdditiveAbelianFGGroup_class(AbelianFGGroup_class):
     632
     633    Element = AdditiveAbelianFGGroupElement
     634
     635    def __init__(self, cover, relations, ambient=None, gens=None):
     636        AbelianFGGroup_class.__init__(self, cover, relations, CommutativeAdditiveGroups(), ambient, gens)
     637
     638    def _ambient_string(self):
     639        r"""
     640        Override in specialized classes, otherwise
     641        this is generic when wrapper classes employed
     642        """
     643        group_names = ["Z/{}".format(x) for x in self.invariants()]
     644        msg = "Additive abelian group isomorphic to {} embedded in {}"
     645        return msg.format(' + '.join(group_names), self._gens_parent())
     646
     647    def zero(self):
     648        r"""
     649        """
     650        return self._identity()
     651
     652#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     653# Multiplicative Finitely-Generated Abelian Groups
     654#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     655
     656class MultiplicativeAbelianFGGroupElement(AbelianFGGroupElement):
     657
     658    def __init__(self, parent, x, check=None):
     659        AbelianFGGroupElement.__init__(self, parent, x, check)
     660
     661    def _optimized_to_user(self):
     662        # convert the optimized value (in self._x via hermite lift)
     663        # to what the user will eventually see
     664        # default is cover module elements, vectors over ZZ
     665        parent = self.parent()
     666        gens = parent._ambient_gens()
     667        v = self._hermite_lift()
     668        p = parent._gens_parent()(1)   # initialize with one element of gens' parent
     669        for i in range(len(gens)):
     670            p *= gens[i]**v[i]
     671        return p
     672
     673
     674    # TestSuite fails on multiplicative examples
     675    # while complaining about addtive tests
     676    # *PROBLEM*: is the module code (additive) confusing the issue?
     677    # *PROBLEM*: the module code had a double-underscore addition,
     678    # so the definitions below are attempts to provide a single-underscore
     679    # version and clobber the double-underscore versions.  Maybe.
     680
     681    def _mul_(self, other):
     682        return AbelianFGGroupElement._add_(self, other)
     683
     684    def __mul__(self, other):
     685        return self._mul_(other)
     686
     687    def _invert_(self):
     688        return AbelianFGGroupElement._neg_(self)
     689
     690    def __invert__(self):
     691        return self._invert_()
     692
     693    def _div_(self, other):
     694        return AbelianFGGroupElement._sub_(self, other)
     695
     696    def __div__(self, other):
     697        return self._div_(self, other)
     698
     699    def _imul_(self, other):
     700        return NotImplementedError
     701
     702    def __imul__(self, other):
     703        return NotImplementedError
     704
     705    def _add_(self, other):
     706        return NotImplementedError
     707       
     708    def __add__(self, other):
     709        return NotImplementedError
     710
     711class MultiplicativeAbelianFGGroup_class(AbelianFGGroup_class):
     712
     713    Element = MultiplicativeAbelianFGGroupElement
     714
     715    def __init__(self, cover, relations, ambient=None, gens=None):
     716        AbelianFGGroup_class.__init__(self, cover, relations, Groups(), ambient, gens)
     717
     718    def _ambient_string(self):
     719        r"""
     720        Override in specialized classes, otherwise
     721        this is generic when wrapper cl;asses employed
     722        """
     723        group_names = ["Z/{}".format(x) for x in self.invariants()]
     724        msg = "Multiplicative abelian group isomorphic to {} embedded in {}"
     725        return msg.format(' + '.join(group_names), self._gens_parent())
     726
     727    def one(self):
     728        r"""
     729        """
     730        return self._identity()
     731
     732    # as above, an attempt to override additive functionality of module code
     733    def zero(self):
     734        r"""
     735        """
     736        return NotImplementedError
     737
     738#~~~~~~~~~~~~~~~~~
     739# Wrappers
     740#~~~~~~~~~~~~~~~~~
     741#
     742# These are the generic user-level routines
     743# Input:
     744#    - a list of elements of an algebraic structure, such that
     745#        - addition (or respectively) multiplication is defined
     746#        - the elements commute pairwise
     747#        - all possible products create a set with cardinality
     748#          equal to the product of the element's orders
     749#        - the orders of the generators, 0 denoting infinite order
     750
     751
     752# tested in elliptic curve doctest
     753def AdditiveAbelianFGGroup(generators, factors):
     754    # optionally deduce factors from generators
     755    cover, relations = _cover_and_relations_from_moduli(factors)
     756    ambient_group = AdditiveAbelianFGGroup_class(cover, relations, gens=generators)
     757    return ambient_group
     758
     759# untested, but mirrors previous
     760def MultiplicativeAbelianFGGroup(generators, factors):
     761    # optionally deduce factors from generators
     762    cover, relations = _cover_and_relations_from_moduli(factors)
     763    ambient_group = MultiplicativeAbelianFGGroup_class(cover, relations, gens=generators)
     764    return ambient_group
     765
     766#~~~~~~~~~~~~~~~~~~~~
     767#~~~~~~~~~~~~~~~~~~~~
     768# Specialized Classes
     769#~~~~~~~~~~~~~~~~~~~~
     770#~~~~~~~~~~~~~~~~~~~~
     771#
     772# Three examples of creating specialized fg abelian groups follow:
     773#    - Direct product of cyclic groups, additive
     774#    - Group of units mod m, additively
     775#    - Totally abstract cyclic group
     776#
     777# To implement similar:
     778#     - Create a "factory" function to accept defining input and,
     779#       - create generators
     780#       - compute orders of generators, 0 denoting infinite order
     781#       - add any of the defining input as an attribute of the group
     782#         (likely to be used in text representations)
     783#       - call the class init with "ambient=None" and with the generators
     784#     - create derived element class
     785#       - override _optimized_to_user for efficiency if possible
     786#     - create derived parent class
     787#       - provide "ambient string" for both the group and subgroups text representation
     788#       - implement "classtype" method for use in subgroup creation
     789#
     790
     791
     792#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     793# Direct Product of Cyclic Groups
     794#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     795
     796# Factory function for ambient, classes follow
     797def CyclicProductGroup(factors):
     798    # error-check factors here
     799    cover, relations = _cover_and_relations_from_moduli(factors)
     800    ambient_group = CyclicProductGroup_class(cover, relations, gens=cover.gens())
     801    ambient_group._factors = factors
     802    return ambient_group
     803
     804class CyclicProductGroupElement(AdditiveAbelianFGGroupElement):
     805    def __init__(self, parent, x, check=None):
     806        AdditiveAbelianFGGroupElement.__init__(self, parent, x, check)
     807
     808class CyclicProductGroup_class(AdditiveAbelianFGGroup_class):
     809    r"""
     810    """
     811    Element = CyclicProductGroupElement
     812
     813    def __init__(self, cover, relations, ambient=None, gens=None):
     814        AdditiveAbelianFGGroup_class.__init__(self, cover, relations, ambient=ambient, gens=gens)
     815
     816    def _ambient_string(self):
     817        group_names = ["C_{}".format(x) for x in self.ambient()._factors]
     818        name = ' x '.join(group_names).replace('C_0', 'C')
     819        return "Product of cyclic groups: {}".format(name)
     820
     821    def classtype(self):
     822        return CyclicProductGroup_class
     823
     824#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     825# Group of Units mod m
     826#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     827
     828# Factory function for ambient, classes follow
     829def UnitsModmGroup(m):
     830    from sage.rings.all import Integers
     831    U = Integers(m)
     832    generators = U.unit_gens()
     833    factors = [u.multiplicative_order() for u in generators]
     834    cover, relations = _cover_and_relations_from_moduli(factors)
     835    ambient_group = UnitsModmGroup_class(cover, relations, gens=generators)
     836    ambient_group._modulus = m
     837    return ambient_group
     838
     839class UnitsModmElement(MultiplicativeAbelianFGGroupElement):
     840    def __init__(self, parent, x, check=None):
     841        MultiplicativeAbelianFGGroupElement.__init__(self, parent, x, check)
     842
     843class UnitsModmGroup_class(MultiplicativeAbelianFGGroup_class):
     844
     845    Element = UnitsModmElement
     846
     847    def __init__(self, cover, relations, ambient=None, gens=None):
     848        MultiplicativeAbelianFGGroup_class.__init__(self, cover, relations, ambient=ambient, gens=gens)
     849
     850    def _ambient_string(self):
     851        return "Group of units mod {}".format(self.ambient()._modulus)
     852
     853    def classtype(self):
     854        return UnitsModmGroup_class
     855
     856#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     857# Abstract Cyclic Group, with symbolic elements
     858#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     859
     860# Factory function for ambient, classes follow
     861def CyclicGroup(n, names='a'):
     862    r"""
     863    Makes the following syntax possible
     864    In:     preparse("C.<b> = CyclicGroup(10)")
     865    Actual: "C = CyclicGroup(Integer(10), names=('b',)); (b,) = C._first_ngens(1)"
     866    """
     867    from sage.symbolic.ring import var
     868    cover, relations = _cover_and_relations_from_moduli([n])
     869    if not isinstance(names, (list, tuple)):
     870        names = (names,)
     871    ambient_group = CyclicGroup_class(cover, relations, gens=[var(names[0])])
     872    return ambient_group
     873
     874class CyclicGroupElement(MultiplicativeAbelianFGGroupElement):
     875    def __init__(self, parent, x, check=None):
     876        MultiplicativeAbelianFGGroupElement.__init__(self, parent, x, check)
     877
     878class CyclicGroup_class(MultiplicativeAbelianFGGroup_class):
     879    r"""
     880    """
     881    Element = CyclicGroupElement
     882
     883    def __init__(self, cover, relations, ambient=None, gens=None):
     884        MultiplicativeAbelianFGGroup_class.__init__(self, cover, relations, ambient=ambient, gens=gens)
     885
     886    def _first_ngens(self, n):
     887        # only n = 1 makes sense here
     888        # needs adjustment for subgroups
     889        # only implemented enough to support C.<> syntax
     890        return (self._ambient_gens()[0],)
     891       
     892    def _ambient_string(self):
     893        msg = "Cyclic group of order {} generated by {}"
     894        return msg.format(self.ambient().order(), self.ambient().gen(0))
     895       
     896    def classtype(self):
     897        return CyclicGroup_class
     898
     899
     900
     901       
     902 No newline at end of file
  • setup.py

    diff --git a/setup.py b/setup.py
    a b  
    910910                     'sage.groups',
    911911                     'sage.groups.abelian_gps',
    912912                     'sage.groups.additive_abelian',
     913                     'sage.groups.fg_abelian',
    913914                     'sage.groups.matrix_gps',
    914915                     'sage.groups.perm_gps',
    915916                     'sage.groups.perm_gps.partn_ref',