| 1 | # from abc import ABCMeta conflict once we add a category |
| 2 | from sage.modules.fg_pid.fgp_module import FGP_Module_class, FGP_Element |
| 3 | from sage.structure.parent_gens import ParentWithGens |
| 4 | |
| 5 | |
| 6 | def _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 | |
| 27 | class 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 | |
| 275 | class 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 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
| 349 | from sage.categories.commutative_additive_groups import CommutativeAdditiveGroups |
| 350 | |
| 351 | class 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 | |
| 381 | class AdditiveAbelianGroupElement(AbstractAbelianGroupElement): |
| 382 | |
| 383 | def __init__(self, parent, x, check=None): |
| 384 | AbstractAbelianGroupElement.__init__(self, parent, x, check) |
| 385 | |
| 386 | #~~~~~~~~~~~~~~~~~~~~~~ Multiplicative ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
| 387 | from sage.categories.groups import Groups |
| 388 | |
| 389 | class 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 | |
| 415 | class 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 | |
| 432 | def AAG(invs, generators=None): |
| 433 | cover, relations = _cover_and_relations_from_invariants(invs) |
| 434 | return AdditiveAbelianGroup_class(cover, relations, generators=generators) |
| 435 | |
| 436 | |
| 437 | def MAG(invs, generators=None): |
| 438 | cover, relations = _cover_and_relations_from_invariants(invs) |
| 439 | return MultiplicativeAbelianGroup_class(cover, relations, generators=generators) |
| 440 | |
| 441 | def CGP(invs): |
| 442 | cover, relations = _cover_and_relations_from_invariants(invs) |
| 443 | return CyclicGroupProduct_class(cover, relations) |
| 444 | |
| 445 | def 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 |
| 467 | class CyclicGroupProduct_class(AdditiveAbelianGroup_class): |
| 468 | |
| 469 | def _repr_(self): |
| 470 | return self._full_name(with_isomorphism=False) |
| 471 | No newline at end of file |