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

File trac_9773-abelian-groups-draft-3.patch, 11.0 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 12932c3c301c4a36160baca0113c60aedb68fa05
    # Parent  aa148c3c0d7b06fd049b9e845eebeda2421d32bf
    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
    - +  
     1from sage.misc.cachefunc import cached_method
     2from sage.rings.all import ZZ
     3from sage.modules.fg_pid.fgp_module import FGP_Module_class, FGP_Element
     4from sage.structure.parent import Parent
     5
     6
     7
     8def _cover_and_relations_from_moduli(moduli):
     9    r"""
     10    Utility function to construct modules for a quotient construction.
     11
     12    INPUT:
     13
     14    A list of integers, designating the modulus of each term in
     15    a product of cyclic groups.  A zero implies the term is infinite,
     16    i.e. all of `\ZZ`.
     17
     18    OUTPUT:
     19
     20    Two additive FGP_Modules over `\ZZ`, such that their quotient
     21    is naturally isomorphic to the corresponding product of cyclic
     22    groups with given modulus.
     23
     24    This routine provides the necessary minimum input to create
     25    finitely-generated modules over `\ZZ`.  Classes in this module
     26    will augment this input to create objects tat behave as
     27    finitely-generated groups.  the FGP module class will perform
     28    optimizations on these moduli to achieve a minimal set of generators
     29    and an internal representation that is more economical for certain
     30    computations.
     31
     32    EXAMPLE::
     33
     34        sage: from sage.groups.fg_abelian.fg_abelian_group import _cover_and_relations_from_moduli
     35        sage: _cover_and_relations_from_moduli([0,2,3])
     36        (Ambient free module of rank 3 over the principal ideal domain Integer Ring, Free module of degree 3 and rank 2 over Integer Ring
     37        Echelon basis matrix:
     38        [0 2 0]
     39        [0 0 3])
     40
     41    TESTS::
     42
     43        sage: _cover_and_relations_from_moduli([5,2.3,0])
     44        Traceback (most recent call last):
     45        ...
     46        ValueError: List of moduli must be non-negative integers, not [5, 2.30000000000000, 0]
     47        sage: _cover_and_relations_from_moduli([0,4,-5,2])
     48        Traceback (most recent call last):
     49        ...
     50        ValueError: List of moduli must be non-negative integers, not [0, 4, -5, 2]
     51    """
     52    if moduli == []:
     53        raise ValueError('List of moduli cannot be empty')
     54    if not all([x in ZZ for x in moduli]) or not all([x >= 0 for x in moduli]):
     55        raise ValueError('List of moduli must be non-negative integers, not %s' % moduli)
     56    n = len(moduli)
     57    A = ZZ**n
     58    B = A.span([A.gen(i) * moduli[i] for i in range(n)])
     59    return A, B
     60
     61
     62
     63#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     64# Abstract Finitely-Generated Abelian Groups
     65#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     66
     67class AbelianFGGroupElement(FGP_Element):
     68
     69    def __init__(self, parent, x, check=None):
     70        FGP_Element.__init__(self, parent, x, check)
     71
     72    def _hermite_lift(self):
     73        r"""
     74        This gives a certain canonical lifting of elements of this group
     75        (represented as a quotient `G/H` of free abelian groups) to `G`, using
     76        the Hermite normal form of the matrix of relations.
     77
     78        Mainly used by the ``_repr_`` method.
     79
     80        EXAMPLES::
     81
     82            sage: A = AdditiveAbelianGroup([2, 3])
     83            sage: v = 3000001 * A.0
     84            sage: v.lift()
     85            (3000001, 0)
     86            sage: v._hermite_lift()
     87            (1, 0)
     88        """
     89        y = self.lift()
     90        H = self.parent().W().basis_matrix()
     91
     92        for i in xrange(H.nrows()):
     93            if i in H.pivot_rows():
     94                j = H.pivots()[i]
     95                N = H[i,j]
     96                a = (y[j] - (y[j] % N)) // N
     97                y = y - a*H.row(i)
     98        return y.change_ring(ZZ)
     99
     100
     101class AbelianFGGroup_class(FGP_Module_class):
     102
     103    Element = AbelianFGGroupElement
     104
     105    def _element_constructor_(self, x):
     106        print "in abstract element constructor"
     107        return self.element_class(x)
     108   
     109
     110    def __init__(self, cover, relations, category, gens=None, ambient=None):
     111        r"""
     112        (goes at class level, really)
     113        INPUT:
     114
     115        - ``cover`` - finitely-generated module - just copies of ZZ,
     116          used in quotient construction
     117
     118        - ``relations`` - finitely-generated module - used in quotient
     119          construction to create cyclic groups in direct product
     120
     121        - ``generators`` - arbitrary elements of some structure,
     122          defaults are provided in derived classes
     123
     124        - ``ambient`` - default: ``None`` - if not ``None``,
     125          value is a supergroup of this group (indicating ``self``
     126          is a subggroup of some other fg abelian group)
     127
     128        """
     129        #sage: S=TestGroup([3,3])
     130        #sage: TestSuite(S).run(verbose = True)
     131        #
     132        # same initial format as constructor of FGP class to allow subquotient, subclass, subgroup
     133        if gens != None and ambient != None:
     134            raise ValueError('cannot specify both generators and an ambient group when creating a finitely generated abelian group')
     135        # degree needs to be nonzero to ensure at least one generator
     136        self._degree = cover.degree()
     137        # ambient=None is a flag, group is "self-ambient", needs generators of some kind though
     138        # otherwise creating a subgroup, so get ambient group from construction, no gens required
     139        # non-None ambient values come from subgroup/quotient construction, so we don't test them here
     140        self._ambient = ambient
     141        if self._ambient is None:   # creating from scratch, store original gens, mods/orders (just in ambient group)
     142            self._ambient = self
     143            self._generators = gens
     144            print "intitalizing gens", gens
     145            if not gens is None:
     146                self._gen_parent = gens[0].parent()  # make more robust w/ Sequence?
     147            # original moduli for ambient (only???)
     148            m = relations.basis_matrix()
     149            mods = [0]*m.ncols()
     150            for row,col in zip(m.pivot_rows(), m.pivots()):
     151                mods[col] = m[row,col]
     152            self._mods = tuple(mods)
     153
     154
     155
     156
     157
     158
     159           
     160            ##if generators is None:
     161                ##self._orig_gens = tuple(self._default_gens(self._degree))
     162            ##else:
     163                ##if not isinstance(generators, (list,tuple)):
     164                    ##raise TypeError('generators of a finitely generated abelian group must be a list or tuple, not %s' % generators)
     165                ##if len(generators) != self._degree:
     166                    ##raise ValueError('the number of generators must equal the number of moduli')
     167                ##self._orig_gens = tuple(generators)
     168            # self._orig_gens will be non-empty now
     169            # the relations module has the orders passed to the cover/relation generator
     170            #   so we can recover/extract them here, to use for reporting on ambient's construction
     171        # parentage of generators comes from a cached method
     172        # could sanity check/examine generators versus apparent orders, common parent
     173        # test for integer multiples in additive groups, exponentiation in multiplicative for efficiency in discrete_exp
     174        #   belongs in derived classes?
     175        # Derived classes will set their category via Parent w/ gens initialization
     176        #   but the gens are the optimized ones from the module class, not the "original" ones here
     177        # We build the quotient module structure now
     178        FGP_Module_class.__init__(self, cover, relations)
     179        Parent.__init__(self, category=category)
     180
     181
     182#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     183# Additive Finitely-Generated Abelian Groups
     184#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     185
     186from sage.categories.commutative_additive_groups import CommutativeAdditiveGroups
     187
     188class AdditiveAbelianFGGroupElement(AbelianFGGroupElement):
     189
     190    def __init__(self, parent, x, check=None):
     191        print "in AAFG __init__"
     192        #if not parent._generators is None:
     193            #if x.parent() == parent._gen_parent:
     194                #x = parent._user_to_optimized(x)
     195        ##print "parent, x", parent, x
     196        AbelianFGGroupElement.__init__(self, parent, x, check)
     197
     198    def _optimized_to_user(self):
     199        # convert the optimized value (in self._x)
     200        # to what the user will eventually see
     201        # default is cover module elements, vectors over ZZ
     202        v = self._hermite_lift()
     203        gens = self.parent()._generators
     204        if gens is None:
     205            return v
     206        else:
     207            s = 0*gens[0]  # cheap zero element, can't have empty list of gens
     208            # add appropriate number of copies
     209            for i in range(len(gens)):
     210                s += v[i]*gens[i]
     211            return s
     212
     213    def _repr_(self):
     214        print "in repr"
     215        return repr(self._optimized_to_user())
     216
     217class AdditiveAbelianFGGroup_class(AbelianFGGroup_class):
     218
     219    Element = AdditiveAbelianFGGroupElement
     220
     221    def __init__(self, cover, relations, gens=None, ambient=None):
     222        AbelianFGGroup_class.__init__(self, cover, relations, CommutativeAdditiveGroups(), gens=gens, ambient=ambient)
     223
     224    # signature must match init?
     225    def _element_constructor_(self, x, check=True):
     226        import sage.modules.free_module_element
     227        print "in additive element constructor"
     228        try:
     229            if (not self._gen_parent is None) and (x.parent() == self._gen_parent):
     230                print "got a true element"
     231                # convert x to a cover module element
     232                # to be acceptable to FGP_element's init
     233                x = self._user_to_optimized(x)._x
     234                print "x", x
     235        except AttributeError:
     236            pass
     237        # no harm, and will allow an iterable (check length afterward)
     238        x = sage.modules.free_module_element.vector(ZZ, x)
     239        return self.element_class(self, x, check=check)
     240
     241    def _user_to_optimized(self, user):
     242        opt_iterator = self.__iter__()
     243        try:
     244            while True:
     245                opt = opt_iterator.next()
     246                if opt._optimized_to_user() == user:
     247                    return opt
     248        except StopIteration:
     249            raise ValueError('{0} is not an element of {1}'.format(user, self))
     250
     251    def _repr_(self):
     252        s = 'Additive abelian group isomorphic to '
     253        terms = []
     254        for x in self._mods:
     255            if x == 0:
     256                terms.append('Z')
     257            else:
     258                terms.append('Z_' + str(x))
     259        s = s + ' + '.join(terms)
     260        return s
     261 No newline at end of file
  • setup.py

    diff --git a/setup.py b/setup.py
    a b  
    911911                     'sage.groups',
    912912                     'sage.groups.abelian_gps',
    913913                     'sage.groups.additive_abelian',
     914                     'sage.groups.fg_abelian',
    914915                     'sage.groups.matrix_gps',
    915916                     'sage.groups.perm_gps',
    916917                     'sage.groups.perm_gps.partn_ref',