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

File trac_9773-abelian-groups-draft-1.patch, 18.6 KB (added by rbeezer, 11 years ago)
  • new file sage/groups/abelian_gps/abstract_abelian_group.py

    # HG changeset patch
    # User Rob Beezer <beezer@ups.edu>
    # Date 1282345368 25200
    # Node ID 4e3edeb0a1a6e5118e99e51b5758ef57368dd4dd
    # Parent  5b338f2e484f2065d3d30d47bc204d6e9ed13d12
    #9773: Implement abelian group classes
    
    diff -r 5b338f2e484f -r 4e3edeb0a1a6 sage/groups/abelian_gps/abstract_abelian_group.py
    - +  
     1# from abc import ABCMeta  conflict once we add a category
     2from sage.modules.fg_pid.fgp_module import FGP_Module_class, FGP_Element
     3from sage.structure.parent_gens import ParentWithGens
     4
     5
     6def _cover_and_relations_from_invariants(invs):
     7    r"""
     8    Utility function: given a list of integers, construct the obvious pair of
     9    free modules such that the quotient is naturally isomorphic to the
     10    corresponding product of cyclic modules.
     11
     12    EXAMPLES::
     13
     14        sage: from sage.groups.additive_abelian.additive_abelian_group import cover_and_relations_from_invariants as cr
     15        sage: cr([0,2,3])
     16        (Ambient free module of rank 3 over the principal ideal domain Integer Ring, Free module of degree 3 and rank 2 over Integer Ring
     17        Echelon basis matrix:
     18        [0 2 0]
     19        [0 0 3])
     20    """
     21    from sage.rings.all import ZZ
     22    n = len(invs)
     23    A = ZZ**n
     24    B = A.span([A.gen(i) * invs[i] for i in xrange(n)])
     25    return (A, B)
     26
     27class AbstractAbelianGroup_class(FGP_Module_class):
     28
     29    def __init__(self, cover, relations, generators=None, ambient=None, category=None):
     30        r"""
     31
     32        ::
     33
     34            sage: S=TestGroup([3,3])
     35            sage: TestSuite(S).run(verbose = True)
     36        """
     37        # same format as constructor of FGP class to allow subquotient, subclass, subgroup
     38        # the FGPmodule init does not chain any higher up the class hierarchy,
     39        #   so the ParentWithGens (higher up) is independent and allows setting the category right
     40        self._degree = cover.degree()  # check for zero here?
     41        if generators != None and ambient != None:
     42            raise ValueError('cannot specify both generators and an ambient group when creating an abelian group')
     43        # ambient=None is a flag, group is "self-ambient", needs generators to exist
     44        #       otherwise creating a subgroup, so get ambient group from construction, no gens required
     45        self._ambient = ambient
     46        if ambient == None:   # creating from scratch, grab gens or get defaults (like for quotient groups)
     47            if generators:
     48                self._orig_gens = generators  # sanity check as a list, and non-empty
     49            else:
     50                self._orig_gens = self._default_gens(self._degree)
     51        # sanity check/examine generators versus apparent orders
     52        # test integer multiples, exponentiation for efficiency in discrete_exp
     53        ParentWithGens.__init__(self, self, category=category)
     54        FGP_Module_class.__init__(self, cover, relations)
     55
     56    def ambient_group(self):
     57        # always returns a group with some _orig_gens
     58        if self._ambient == None:
     59            return self
     60        else:
     61            return self._ambient
     62
     63    from sage.misc.cachefunc import cached_method
     64    @cached_method
     65    def _generator_parent(self):
     66        # Mostly to get 0 or 1 for empty sum/product in discrete exp
     67        return self.ambient_group()._orig_gens[0].parent()
     68
     69    def __call__(self, x, check=None):
     70        from sage.structure.element import is_Vector
     71        from sage.rings.all import ZZ, QQ
     72        # check for lists posing as module elements, else complain | delegate
     73        # send some things up to FGP module
     74        # trap others and test
     75        #
     76        # if parent is this group, then return it
     77        if hasattr(x,'parent') and x.parent() is self:
     78            # print "returning own element"
     79            return x
     80        sanitized = None
     81        # naked lists are scalars for minimal generators, not module elements
     82        if isinstance(x, (list,tuple)):
     83            # print "handling a list"
     84            if len(x) == len(self.invariants()):
     85                sanitized = x
     86            else:
     87                raise ValueError('%s is the wrong length to provide a linear combination of minimal generators' % x)
     88        # an element of the right class gets passed up to be treated as an FGP_Element
     89        elif isinstance(x, self._element_class()):
     90            # print "handling own class"
     91            sanitized = x
     92        # vectors are linear combos of original generators
     93        elif is_Vector(x):
     94            # print "Handling a vector"
     95            if len(x) == self._degree:
     96                sanitized = x
     97            else:
     98                raise ValueError('%s is the wrong length to provide a linear combination of original generators' % x)
     99        if sanitized != None:
     100            # print "calling super __call__"
     101            return FGP_Module_class.__call__(self, sanitized, check=check)
     102        else:
     103            # print "Coercing into generator parent"
     104            try:
     105                x = self._generator_parent()(x)
     106                return self._discrete_log(x)
     107            except:
     108                raise ValueError('cannot interpret %s as an element of %s' % (x, self))
     109
     110    def is_subgroup(self):
     111        return self.ambient_group() != self
     112
     113    def order(self):
     114        r"""
     115        Return the order of this group (an integer or infinity)
     116
     117        EXAMPLES::
     118
     119            sage: AdditiveAbelianGroup([2,4]).order()
     120            8
     121            sage: AdditiveAbelianGroup([0, 2,4]).order()
     122            +Infinity
     123            sage: AdditiveAbelianGroup([]).order()
     124            1
     125        """
     126        return self.cardinality()
     127
     128    def exponent(self):
     129        r"""
     130        Return the exponent of this group (the smallest positive integer `N`
     131        such that `Nx = 0` for all `x` in the group). If there is no such
     132        integer, return 0.
     133
     134        EXAMPLES::
     135
     136            sage: AdditiveAbelianGroup([2,4]).exponent()
     137            4
     138            sage: AdditiveAbelianGroup([0, 2,4]).exponent()
     139            0
     140            sage: AdditiveAbelianGroup([]).exponent()
     141            1
     142        """
     143        if not self.invariants():
     144            return 1
     145        else:
     146            ann =  self.annihilator().gen()
     147            if ann:
     148                return ann
     149            return ZZ(0)
     150
     151    def _subquotient_class(self):
     152        return self.__class__
     153
     154    def is_cyclic(self):
     155        return len(self.smith_form_gens()) < 2
     156
     157    def cyclic_generator(self):
     158        gens = self.smith_form_gens()
     159        if self.is_cyclic():
     160            if len(gens) == 1:
     161                return(gens[0])
     162            else:
     163                return self.identity()
     164        else:
     165            raise ValueError('No cyclic generator for the non-cyclic %s' % self)
     166
     167    def identity(self):
     168        # Build 0 and 1 from this in derived classes
     169        from sage.modules.free_module_element import vector
     170        from sage.rings.all import ZZ
     171        trivial = vector(ZZ, [0]*self._degree)
     172        return self(trivial, check=True)
     173
     174    def is_abelian(self):
     175        return True
     176
     177    def _repr_(self):
     178        return self._full_name(with_isomorphism=True, subgroup_generators='minimal')
     179
     180    def _cyclic_product_name(self, orders):
     181        r"""
     182        Helper formatting function, direct sum of cyclic groups
     183        INPUT: orders = list of positive integers
     184        OUTPUT: ASCII string
     185        """
     186        terms = []
     187        for order in orders:
     188            if order: terms.append('Z_' + str(order))
     189            else:     terms.append('Z')
     190        if terms: return ' + '.join(terms)
     191        else:     return 'the trivial group'
     192
     193    def isomorphism_class_name(self):
     194        r"""
     195        ASCII string describing isomorphism class.
     196        """
     197        return self._cyclic_product_name(self.invariants())
     198
     199    def _full_name(self, with_isomorphism=True, subgroup_generators=None):
     200        # finite|infinite
     201        # multiplicative|additive
     202        # group|subgroup(w/ generators)
     203        # {isomorphism class}
     204        if self.is_finite():
     205            rep = ['Finite']
     206        else:
     207            rep = ['Infinite']
     208        if self.is_multiplicative():
     209            rep.append('multiplicative')
     210        else:
     211            rep.append('additive')
     212        rep.append('abelian group')
     213        if with_isomorphism:
     214            rep.append('isomorphic to')
     215            rep.append(self.isomorphism_class_name())
     216        rep.append("with generator(s):")
     217        rep.append(', '.join([repr(x) for x in self.gens()]))
     218        return " ".join(rep)
     219
     220
     221    def is_isomorphic(self, other):
     222        # if other is not abelian class, convert to permutation group
     223        # if other is permutation group, promote self to a permutation group and then test
     224        # as is, just for abelian groups
     225        return self.invariants() == other.invariants()
     226
     227    def subgroup(self, gens):
     228        if not isinstance(gens, (list, tuple)):
     229            raise TypeError, "Generators of a subgroup of an abelian group must be in a list or tuple"
     230        gen_vectors = [self(v).lift() for v in gens]
     231        relations = self.relations()  # preserved across subgroups
     232        newcover = self.cover().submodule(gen_vectors) + relations
     233        if newcover == self.cover():
     234            return self  # nothing happened
     235        else:
     236            return self._subquotient_class()(newcover, relations, ambient=self.ambient_group())
     237
     238    def intersection(self, other):
     239        if self.ambient_group() != other.ambient_group():
     240            raise ValueError('cannot form intersection of %s and %s' % (self, other))
     241        # subgroups preserve relations, so just intersect covers
     242        newcover = self.cover().intersection(other.cover())
     243        return self._subquotient_class()(newcover, self.relations(), ambient=self.ambient_group())
     244
     245
     246    def _discrete_log(self, element):
     247        # Generic dumb  method
     248        # element is posing as a possible element of the group generated by the generators
     249        # We look to see if it is a (linear) combination, and return the combination as vector over ZZ
     250        # This will be computationally expensive usually, it is a decomposition
     251        # Input: anything that __call__ does not recognize (based on class types)
     252        # Output: a specific element with the "right" discrete exponential
     253        #
     254        if not self.is_finite():
     255            raise TypeError("naive brute-force discrete log is not possible for infinite group")
     256        # .list() is cached,
     257        # elements' discrete exponentials get computed and cached as searched here
     258        # so subsequent calls become more like lookups, more often
     259        matches = filter(lambda x: x.discrete_exp() == element, self.list())
     260        if not matches:
     261            raise ValueError("%s is not an element of %s" % (element, self))
     262        if len(matches) > 1:
     263            print "Warning: %s has several representations in %s" % (element, self)
     264        return matches[0]
     265
     266
     267    def info(self):
     268        print "Finite:", self.is_finite()
     269        print "Multiplicative:", self.is_multiplicative()
     270        print "Subgroup:", self.is_subgroup()
     271        print "Generators:", self.gens()
     272        print "Isomorphism:", self.isomorphism_class_name()
     273        print "Ambient Gens:", self.ambient_group()._orig_gens
     274
     275class AbstractAbelianGroupElement(FGP_Element):
     276
     277    from sage.misc.cachefunc import cached_method
     278
     279    def __init__(self, parent, x, check=None):
     280        FGP_Element.__init__(self, parent, x, check)
     281
     282    def _discrete_exp(self):
     283        # Generic method
     284        # Assumes sum or product possible
     285        # Used by the _repr_ and _latex_ and ... routines
     286        # Convert from internal to human representation
     287        # Override in derived classes for better performance
     288        # Input:  an (optimized, enhanced) group element represenation
     289        # Output: an element of the real group being represented, store as side efffect
     290        parent = self.parent()
     291        terms=[]
     292        # replace copies by multiple or power, if that corresponding operation is available
     293        for gen, copies in zip(parent.ambient_group()._orig_gens, self._hermite_lift()):
     294            terms.extend([gen]*copies)
     295        # n.b. terms is empty for identity element of group
     296        return (parent._listop())(terms, parent._identity_gen())
     297
     298    @cached_method
     299    def discrete_exp(self):
     300        # Do not override, instead override _discrete_exp
     301        # result is cached with element, so not recomputed
     302        return self._discrete_exp()
     303
     304    # rewrite/rename these
     305    # make ascii versions with operators, generators
     306    # define term-format-function and operator/seperator
     307    #    functions in derived additive/multiplicative classes
     308    def representation_original(self):
     309        return self._hermite_lift()
     310
     311    def representation_minimal(self):
     312        # check this for possible reordering of _orig_gens
     313        return self.parent().coordinate_vector(self, reduce=True)
     314
     315    def _hermite_lift(self):
     316        r"""
     317        This gives a certain canonical lifting of elements of this group
     318        (represented as a quotient `G/H` of free abelian groups) to `G`, using
     319        the Hermite normal form of the matrix of relations.
     320
     321        Mainly used by the ``_repr_`` method.
     322
     323        EXAMPLES::
     324
     325            sage: A = AdditiveAbelianGroup([2, 3])
     326            sage: v = 3000001 * A.0
     327            sage: v.lift()
     328            (3000001, 0)
     329            sage: v._hermite_lift()
     330            (1, 0)
     331        """
     332        from sage.rings.all import ZZ
     333        y = self.lift()
     334        H = self.parent().W().basis_matrix()
     335
     336        for i in xrange(H.nrows()):
     337            if i in H.pivot_rows():
     338                j = H.pivots()[i]
     339                N = H[i,j]
     340                a = (y[j] - (y[j] % N)) // N
     341                y = y - a*H.row(i)
     342        return y.change_ring(ZZ)
     343
     344    def _repr_(self):
     345        return self.discrete_exp()._repr_()
     346
     347
     348#~~~~~~~~~~~~~~~~~~~~~~  Additive  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     349from sage.categories.commutative_additive_groups import CommutativeAdditiveGroups
     350
     351class AdditiveAbelianGroup_class(AbstractAbelianGroup_class):
     352
     353    def __init__(self, cover, relations, generators=None, ambient=None):
     354        AbstractAbelianGroup_class.__init__(self, cover, relations, generators=generators, ambient=ambient, category=CommutativeAdditiveGroups())
     355
     356    def _element_class(self):
     357        return AdditiveAbelianGroupElement
     358
     359    def _listop(self):
     360        from sage.all import sum
     361        return sum
     362
     363    def _identity_gen(self):
     364        #return self._generator_parent().zero()
     365        return self._generator_parent()(0)
     366
     367    def _default_gens(self, deg):
     368        from sage.rings.all import ZZ
     369        from sage.modules.free_module_element import vector
     370        basis = []
     371        for i in range(deg):
     372            zero = vector(ZZ, [0]*deg)
     373            zero[i] = 1
     374            basis.append(zero)
     375        return basis
     376
     377    def is_multiplicative(self):
     378        return False
     379
     380
     381class AdditiveAbelianGroupElement(AbstractAbelianGroupElement):
     382
     383    def __init__(self, parent, x, check=None):
     384        AbstractAbelianGroupElement.__init__(self, parent, x, check)
     385
     386#~~~~~~~~~~~~~~~~~~~~~~  Multiplicative  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     387from sage.categories.groups import Groups
     388
     389class MultiplicativeAbelianGroup_class(AbstractAbelianGroup_class):
     390
     391    def __init__(self, cover, relations, generators=None, ambient=None):
     392        # nee commutative?
     393        AbstractAbelianGroup_class.__init__(self, cover, relations, generators=generators, ambient=ambient, category=Groups())
     394
     395    def _element_class(self):
     396        return MultiplicativeAbelianGroupElement
     397
     398    def _listop(self):
     399        from sage.all import prod
     400        return prod
     401
     402    def _identity_gen(self):
     403        # return self._generator_parent().one()
     404        return self._generator_parent()(1)
     405
     406    def _default_gens(self, deg):
     407        from sage.symbolic.ring import SymbolicRing
     408        SR=SymbolicRing()
     409        return [SR('f'+str(i+1)) for i in range(deg)]
     410
     411    def is_multiplicative(self):
     412        return True
     413
     414
     415class MultiplicativeAbelianGroupElement(AbstractAbelianGroupElement):
     416
     417    def __init__(self, parent, elt, check=None):
     418        AbstractAbelianGroupElement.__init__(self, parent, elt, check)
     419
     420    def __mul__(self, other):
     421        return self+other
     422
     423    def __pow__(self, other):
     424        return other*self
     425
     426    # obsoloete others, both r eversed versions, i versions
     427
     428#~~~~~~~~~~~~~~~~~~~~~~  Builders  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     429
     430# rename later
     431
     432def AAG(invs, generators=None):
     433    cover, relations = _cover_and_relations_from_invariants(invs)
     434    return AdditiveAbelianGroup_class(cover, relations, generators=generators)
     435
     436
     437def MAG(invs, generators=None):
     438    cover, relations = _cover_and_relations_from_invariants(invs)
     439    return MultiplicativeAbelianGroup_class(cover, relations, generators=generators)
     440
     441def CGP(invs):
     442    cover, relations = _cover_and_relations_from_invariants(invs)
     443    return CyclicGroupProduct_class(cover, relations)
     444
     445def GUN(n):
     446    # Group of units mod n
     447    # sanity check n
     448    # Build with elements of prime-power order
     449    #   Module code will consolidate to minimal generators
     450    from sage.rings.finite_rings.integer_mod_ring import IntegerModRing
     451    R=IntegerModRing(n)
     452    gens = R.unit_gens()
     453    newgens = []
     454    for gen in gens:
     455        order = gen.multiplicative_order()
     456        for base, exponent in order.factor():
     457            newgen = gen**(order/(base**exponent))
     458            newgens.append(newgen)
     459    orders = [x.multiplicative_order() for x in newgens]
     460    cover, relations = _cover_and_relations_from_invariants(orders)
     461    return MultiplicativeAbelianGroup_class(cover, relations, generators=newgens)
     462
     463#~~~~~~~~~~~~~~~~~~~~~~  Applications  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     464
     465# Product of cyclic groups for students
     466# Simplify group's repr, also simplify (eventual) generators
     467class CyclicGroupProduct_class(AdditiveAbelianGroup_class):
     468
     469    def _repr_(self):
     470        return self._full_name(with_isomorphism=False)
     471 No newline at end of file
  • sage/groups/abelian_gps/all.py

    diff -r 5b338f2e484f -r 4e3edeb0a1a6 sage/groups/abelian_gps/all.py
    a b  
    2929
    3030from abelian_group_morphism import is_AbelianGroupMorphism,AbelianGroupMap,AbelianGroupMorphism_id,AbelianGroupMorphism,AbelianGroupMorphism
    3131
     32
     33
     34from abstract_abelian_group import AbstractAbelianGroup_class, AdditiveAbelianGroup_class, MultiplicativeAbelianGroup_class, AAG, MAG, CGP, GUN
     35