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

File trac_9773-abelian-groups-draft-4.patch, 29.7 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 17f443b7cf9f73beb4a4c708d7f95ed68d65f798
    # Parent  7a2a36b9da0fc4bfc82fbd11ca7d48f741f74bf7
    9773: finitely-generated abelian groups
    
    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        # We build the quotient module structure now
     435        FGP_Module_class.__init__(self, cover, relations)
     436        self._init_category_(category)
     437
     438    def _user_to_optimized(self, user):
     439        r"""
     440        A naive brute-force discrete log:
     441        Find a module element that converts to a given group element.
     442        Overide if this can be improved in a given class.
     443        """
     444        opt_iterator = self.__iter__()
     445        try:
     446            while True:
     447                opt = opt_iterator.next()
     448                if opt._optimized_to_user() == user:
     449                    return opt
     450        except StopIteration:
     451            raise ValueError('{0} is not an element of {1}'.format(user, self))
     452
     453    def _element_constructor_(self, x, check=True):
     454        r"""
     455        This is a *PROBLEM*.
     456
     457        Code is verbatim from FGP_Module's element constructor.
     458
     459        A ZZ-vector (free module element, really) is a natural element of
     460        the supporting quotient of modules and takes no manipulations.
     461        (See example.)
     462
     463        A list or tuple is interpreted as a linear combination of the
     464        optimized representation in the FGP module code and converted to
     465        a vector.  (See example.)
     466
     467        An actual FGP element is lifted to a vector, then suitably interpreted.
     468
     469        Two examples:
     470       
     471        C = CyclicProductGroup([4, 0, 5])
     472        a = C(vector(ZZ, (0,4,0)))
     473        a
     474        (0, 4, 0)
     475       
     476        C.gens()
     477        ((3, 0, 4), (0, 1, 0))
     478        b = C([1,2])
     479        b
     480        (3, 2, 4)
     481
     482        How to pass in elements of the parent of the generators?
     483        The _user_to_optimized() routine should be able to creat an FGP element.
     484        But every time I try, all the FGP Module routines break.
     485
     486        Also, conceivably a group could be built from ZZ-vectors in
     487        some non-standard way and this routine would need to recognize them as not
     488        being ZZ-vectors of the underlying module.  Maybe the type
     489        (additive, multiplicative elements?) would catch this possibility?
     490
     491        Uncomment the print statement below and create the subgroup B above
     492        as a subgroup of the group of units, then print it (which creates
     493        generators).  Restart (or remove caching of the smith_form_generators
     494        method) and repeat the creation of B and then ask for the .smith_form_generators()
     495        of B.  It seems that in the smith_form_generators routine the rows of Z get
     496        passed into "self()" which might be circular?
     497
     498        Adding the following code to the beginning of this routine
     499        seems to make creating some elements possible (with units
     500        mod 40 from tests).  Totally hangs up creating smith_gens
     501        of a subgroup though.
     502
     503        try:
     504            y = self._gens_parent()(x)
     505            in_parent= True
     506        except:
     507            in_parent = False
     508        if in_parent:
     509            x = self._user_to_optimized(y)
     510       
     511        """
     512        # This is an interesting diagnostic statement
     513        # print type(x), x
     514        if isinstance(x, (list,tuple)):
     515            try:
     516                x = self.optimized()[0].V().linear_combination_of_basis(x)
     517            except ValueError, msg:
     518                raise TypeError, msg
     519        elif isinstance(x, FGP_Element):
     520            x = x.lift()
     521        return self.element_class(self, self._V(x))
     522       
     523    def _repr_(self):
     524        if self._ambient is self:
     525            return self._ambient_string()
     526        else:
     527            gens = self.gens()
     528            if len(gens) == 1:
     529                gens = gens[0]
     530            msg = "Subgroup of ({}) generated by {}"
     531            return msg.format(self._ambient_string(), gens)       
     532       
     533    def _identity(self):
     534        r"""
     535        Zero vector of module,
     536        will be zero element additively,
     537        the one element multiplicatively
     538        """
     539        from sage.modules.free_module_element import zero_vector
     540        zero = zero_vector(ZZ, self.cover().degree())
     541        return self(zero)
     542
     543    def _ambient_gens(self):
     544        return self._ambient._generators
     545
     546    def _gens_parent(self):
     547        return self._ambient._generators_parent
     548
     549    def is_ambient(self):
     550        return self._ambient is self
     551
     552    def ambient(self):
     553        return self._ambient
     554       
     555    def subgroup(self, gens):
     556        r"""
     557        """
     558        import sage.modules.module
     559        S = self.submodule(gens)
     560        return (self.classtype())(S.cover(), S.relations(), ambient=self.ambient())
     561
     562    def is_subgroup(self, other):
     563        if not self.ambient() is other.ambient():
     564            return False
     565        return self.is_submodule(other)
     566       
     567    def order(self):
     568        r"""
     569        Duplicates cardinality from FG PID modules.
     570        """
     571        return self.cardinality()
     572
     573    def as_permutation_group(self):
     574        r"""
     575        Allows full range of properties from permutation group implementation
     576        Prime powers lessen overall degree, using invariants instead
     577        might allow natural isomorphism to Smith form generators
     578        """
     579        from sage.groups.perm_gps.permgroup import  PermutationGroup
     580        # descending list of prime powers characterizing group
     581        pp = [f[0]**f[1] for i in self.invariants() for f in i.factor()]
     582        pp.sort(reverse=True)
     583        gens = []
     584        start = 1
     585        for cycle_length in pp:
     586            gens.append(tuple(range(start, start + cycle_length)))
     587            start = start + cycle_length
     588        return PermutationGroup(gens=gens)
     589       
     590    def is_isomorphic(self, other):
     591        r"""
     592        Improvement:  Allow "other" to be perm group,
     593        then in this case convert self to perm group representation
     594        """
     595        # error check other as fg abelian
     596        return self.invariants() == other.invariants()
     597       
     598#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     599# Additive Finitely-Generated Abelian Groups
     600#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     601
     602class AdditiveAbelianFGGroupElement(AbelianFGGroupElement):
     603
     604    def __init__(self, parent, x, check=None):
     605        AbelianFGGroupElement.__init__(self, parent, x, check)
     606
     607    def _optimized_to_user(self):
     608        # convert the optimized value (in self._x via hermite lift)
     609        # to what the user will eventually see
     610        # default is cover module elements, vectors over ZZ
     611        parent = self.parent()
     612        gens = parent._ambient_gens()
     613        v = self._hermite_lift()
     614        # add appropriate number of copies
     615        s = parent._gens_parent()(0)  # initialize with zero element of gens' parent
     616        for i in range(len(gens)):
     617            s += gens[i]*v[i]
     618        return s
     619
     620class AdditiveAbelianFGGroup_class(AbelianFGGroup_class):
     621
     622    Element = AdditiveAbelianFGGroupElement
     623
     624    def __init__(self, cover, relations, ambient=None, gens=None):
     625        AbelianFGGroup_class.__init__(self, cover, relations, CommutativeAdditiveGroups(), ambient, gens)
     626
     627    def _ambient_string(self):
     628        r"""
     629        Override in specialized classes, otherwise
     630        this is generic when wrapper classes employed
     631        """
     632        group_names = ["Z/{}".format(x) for x in self.invariants()]
     633        msg = "Additive abelian group isomorphic to {} embedded in {}"
     634        return msg.format(' + '.join(group_names), self._gens_parent())
     635
     636    def zero(self):
     637        r"""
     638        """
     639        return self._identity()
     640
     641#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     642# Multiplicative Finitely-Generated Abelian Groups
     643#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     644
     645class MultiplicativeAbelianFGGroupElement(AbelianFGGroupElement):
     646
     647    def __init__(self, parent, x, check=None):
     648        AbelianFGGroupElement.__init__(self, parent, x, check)
     649
     650    def _optimized_to_user(self):
     651        # convert the optimized value (in self._x via hermite lift)
     652        # to what the user will eventually see
     653        # default is cover module elements, vectors over ZZ
     654        parent = self.parent()
     655        gens = parent._ambient_gens()
     656        v = self._hermite_lift()
     657        p = parent._gens_parent()(1)   # initialize with one element of gens' parent
     658        for i in range(len(gens)):
     659            p *= gens[i]**v[i]
     660        return p
     661
     662
     663    # TestSuite fails on multiplicative examples
     664    # while complaining about addtive tests
     665    # *PROBLEM*: is the module code (additive) confusing the issue?
     666    # *PROBLEM*: the module code had a double-underscore addition,
     667    # so the definitions below are attempts to provide a single-underscore
     668    # version and clobber the double-underscore versions.  Maybe.
     669
     670    def _mul_(self, other):
     671        return AbelianFGGroupElement._add_(self, other)
     672
     673    def __mul__(self, other):
     674        return self._mul_(other)
     675
     676    def _invert_(self):
     677        return AbelianFGGroupElement._neg_(self)
     678
     679    def __invert__(self):
     680        return self._invert_()
     681
     682    def _div_(self, other):
     683        return AbelianFGGroupElement._sub_(self, other)
     684
     685    def __div__(self, other):
     686        return self._div_(self, other)
     687
     688    def _imul_(self, other):
     689        return NotImplementedError
     690
     691    def __imul__(self, other):
     692        return NotImplementedError
     693
     694    def _add_(self, other):
     695        return NotImplementedError
     696       
     697    def __add__(self, other):
     698        return NotImplementedError
     699
     700class MultiplicativeAbelianFGGroup_class(AbelianFGGroup_class):
     701
     702    Element = MultiplicativeAbelianFGGroupElement
     703
     704    def __init__(self, cover, relations, ambient=None, gens=None):
     705        AbelianFGGroup_class.__init__(self, cover, relations, Groups(), ambient, gens)
     706
     707    def _ambient_string(self):
     708        r"""
     709        Override in specialized classes, otherwise
     710        this is generic when wrapper cl;asses employed
     711        """
     712        group_names = ["Z/{}".format(x) for x in self.invariants()]
     713        msg = "Multiplicative abelian group isomorphic to {} embedded in {}"
     714        return msg.format(' + '.join(group_names), self._gens_parent())
     715
     716    def one(self):
     717        r"""
     718        """
     719        return self._identity()
     720
     721    # as above, an attempt to override additive functionality of module code
     722    def zero(self):
     723        r"""
     724        """
     725        return NotImplementedError
     726
     727#~~~~~~~~~~~~~~~~~
     728# Wrappers
     729#~~~~~~~~~~~~~~~~~
     730#
     731# These are the generic user-level routines
     732# Input:
     733#    - a list of elements of an algebraic structure, such that
     734#        - addition (or respectively) multiplication is defined
     735#        - the elements commute pairwise
     736#        - all possible products create a set with cardinality
     737#          equal to the product of the element's orders
     738#        - the orders of the generators, 0 denoting infinite order
     739
     740
     741# tested in elliptic curve doctest
     742def AdditiveAbelianFGGroup(generators, factors):
     743    # optionally deduce factors from generators
     744    cover, relations = _cover_and_relations_from_moduli(factors)
     745    ambient_group = AdditiveAbelianFGGroup_class(cover, relations, gens=generators)
     746    return ambient_group
     747
     748# untested, but mirrors previous
     749def MultiplicativeAbelianFGGroup(generators, factors):
     750    # optionally deduce factors from generators
     751    cover, relations = _cover_and_relations_from_moduli(factors)
     752    ambient_group = MultiplicativeAbelianFGGroup_class(cover, relations, gens=generators)
     753    return ambient_group
     754
     755#~~~~~~~~~~~~~~~~~~~~
     756#~~~~~~~~~~~~~~~~~~~~
     757# Specialized Classes
     758#~~~~~~~~~~~~~~~~~~~~
     759#~~~~~~~~~~~~~~~~~~~~
     760#
     761# Three examples of creating specialized fg abelian groups follow:
     762#    - Direct product of cyclic groups, additive
     763#    - Group of units mod m, additively
     764#    - Totally abstract cyclic group
     765#
     766# To implement similar:
     767#     - Create a "factory" function to accept defining input and,
     768#       - create generators
     769#       - compute orders of generators, 0 denoting infinite order
     770#       - add any of the defining input as an attribute of the group
     771#         (likely to be used in text representations)
     772#       - call the class init with "ambient=None" and with the generators
     773#     - create derived element class
     774#       - override _optimized_to_user for efficiency if possible
     775#     - create derived parent class
     776#       - provide "ambient string" for both the group and subgroups text representation
     777#       - implement "classtype" method for use in subgroup creation
     778#
     779
     780
     781#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     782# Direct Product of Cyclic Groups
     783#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     784
     785# Factory function for ambient, classes follow
     786def CyclicProductGroup(factors):
     787    # error-check factors here
     788    cover, relations = _cover_and_relations_from_moduli(factors)
     789    ambient_group = CyclicProductGroup_class(cover, relations, gens=cover.gens())
     790    ambient_group._factors = factors
     791    return ambient_group
     792
     793class CyclicProductGroupElement(AdditiveAbelianFGGroupElement):
     794    def __init__(self, parent, x, check=None):
     795        AdditiveAbelianFGGroupElement.__init__(self, parent, x, check)
     796
     797class CyclicProductGroup_class(AdditiveAbelianFGGroup_class):
     798    r"""
     799    """
     800    Element = CyclicProductGroupElement
     801
     802    def __init__(self, cover, relations, ambient=None, gens=None):
     803        AdditiveAbelianFGGroup_class.__init__(self, cover, relations, ambient=ambient, gens=gens)
     804
     805    def _ambient_string(self):
     806        group_names = ["C_{}".format(x) for x in self.ambient()._factors]
     807        name = ' x '.join(group_names).replace('C_0', 'C')
     808        return "Product of cyclic groups: {}".format(name)
     809
     810    def classtype(self):
     811        return CyclicProductGroup_class
     812
     813#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     814# Group of Units mod m
     815#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     816
     817# Factory function for ambient, classes follow
     818def UnitsModmGroup(m):
     819    from sage.rings.all import Integers
     820    U = Integers(m)
     821    generators = U.unit_gens()
     822    factors = [u.multiplicative_order() for u in generators]
     823    cover, relations = _cover_and_relations_from_moduli(factors)
     824    ambient_group = UnitsModmGroup_class(cover, relations, gens=generators)
     825    ambient_group._modulus = m
     826    return ambient_group
     827
     828class UnitsModmElement(MultiplicativeAbelianFGGroupElement):
     829    def __init__(self, parent, x, check=None):
     830        MultiplicativeAbelianFGGroupElement.__init__(self, parent, x, check)
     831
     832class UnitsModmGroup_class(MultiplicativeAbelianFGGroup_class):
     833
     834    Element = UnitsModmElement
     835
     836    def __init__(self, cover, relations, ambient=None, gens=None):
     837        MultiplicativeAbelianFGGroup_class.__init__(self, cover, relations, ambient=ambient, gens=gens)
     838
     839    def _ambient_string(self):
     840        return "Group of units mod {}".format(self.ambient()._modulus)
     841
     842    def classtype(self):
     843        return UnitsModmGroup_class
     844
     845#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     846# Abstract Cyclic Group, with symbolic elements
     847#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     848
     849# Factory function for ambient, classes follow
     850def CyclicGroup(n, names='a'):
     851    r"""
     852    Makes the following syntax possible
     853    In:     preparse("C.<b> = CyclicGroup(10)")
     854    Actual: "C = CyclicGroup(Integer(10), names=('b',)); (b,) = C._first_ngens(1)"
     855    """
     856    from sage.symbolic.ring import var
     857    cover, relations = _cover_and_relations_from_moduli([n])
     858    if not isinstance(names, (list, tuple)):
     859        names = (names,)
     860    ambient_group = CyclicGroup_class(cover, relations, gens=[var(names[0])])
     861    return ambient_group
     862
     863class CyclicGroupElement(MultiplicativeAbelianFGGroupElement):
     864    def __init__(self, parent, x, check=None):
     865        MultiplicativeAbelianFGGroupElement.__init__(self, parent, x, check)
     866
     867class CyclicGroup_class(MultiplicativeAbelianFGGroup_class):
     868    r"""
     869    """
     870    Element = CyclicGroupElement
     871
     872    def __init__(self, cover, relations, ambient=None, gens=None):
     873        MultiplicativeAbelianFGGroup_class.__init__(self, cover, relations, ambient=ambient, gens=gens)
     874
     875    def _first_ngens(self, n):
     876        # only n = 1 makes sense here
     877        # needs adjustment for subgroups
     878        # only implemented enough to support C.<> syntax
     879        return (self._ambient_gens()[0],)
     880       
     881    def _ambient_string(self):
     882        msg = "Cyclic group of order {} generated by {}"
     883        return msg.format(self.ambient().order(), self.ambient().gen(0))
     884       
     885    def classtype(self):
     886        return CyclicGroup_class
     887
     888
     889
     890       
     891 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',