Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sagemath
GitHub Repository: sagemath/sagelib
Path: blob/master/sage/homology/chain_complex.py
4086 views
1
r"""
2
Chain complexes
3
4
AUTHORS:
5
6
- John H. Palmieri (2009-04)
7
8
This module implements chain complexes of free `R`-modules, for any
9
commutative ring `R` (although the interesting things, like homology,
10
only work if `R` is the integers or a field).
11
12
Fix a ring `R`. A chain complex over `R` is a collection of
13
`R`-modules `\{C_n\}` indexed by the integers, with `R`-module maps
14
`d_n : C_n \rightarrow C_{n+1}` such that `d_{n+1} \circ d_n = 0` for
15
all `n`. The maps `d_n` are called *differentials*.
16
17
One can vary this somewhat: the differentials may decrease degree by
18
one instead of increasing it: sometimes a chain complex is defined
19
with `d_n : C_n \rightarrow C_{n-1}` for each `n`. Indeed, the
20
differentials may change dimension by any fixed integer.
21
22
Also, the modules may be indexed over an abelian group other than the
23
integers, e.g., `\ZZ^{m}` for some integer `m \geq 1`, in which
24
case the differentials may change the grading by any element of that
25
grading group.
26
27
In this implementation, the ring `R` must be commutative and the
28
modules `C_n` must be free `R`-modules. As noted above, homology
29
calculations will only work if the ring `R` is either `\ZZ` or a field.
30
The modules may be indexed by any free abelian group. The
31
differentials may increase degree by 1 or decrease it, or indeed
32
change it by any fixed amount: this is controlled by the ``degree``
33
parameter used in defining the chain complex.
34
"""
35
36
from sage.structure.sage_object import SageObject
37
from sage.rings.integer_ring import ZZ
38
from sage.rings.rational_field import QQ
39
from sage.modules.free_module import FreeModule, VectorSpace
40
from sage.matrix.matrix0 import Matrix
41
from sage.matrix.constructor import matrix, prepare_dict
42
from sage.misc.latex import latex
43
from sage.groups.additive_abelian.additive_abelian_group import AdditiveAbelianGroup_fixed_gens
44
from sage.modules.free_module import FreeModule
45
#from sage.groups.abelian_gps.abelian_group import AbelianGroup_class
46
from sage.rings.all import GF, prime_range
47
48
def dhsw_snf(mat, verbose=False):
49
"""
50
Preprocess a matrix using the "Elimination algorithm" described by
51
Dumas et al., and then call ``elementary_divisors`` on the
52
resulting (smaller) matrix.
53
54
'dhsw' stands for 'Dumas, Heckenbach, Saunders, Welker,' and 'snf'
55
stands for 'Smith Normal Form.'
56
57
INPUT:
58
59
- ``mat`` - an integer matrix, either sparse or dense.
60
61
(They use the transpose of the matrix considered here, so they use
62
rows instead of columns.)
63
64
Algorithm: go through ``mat`` one column at a time. For each
65
column, add multiples of previous columns to it until either
66
67
- it's zero, in which case it should be deleted.
68
69
- its first nonzero entry is 1 or -1, in which case it should be kept.
70
71
- its first nonzero entry is something else, in which case it is
72
deferred until the second pass.
73
74
Then do a second pass on the deferred columns.
75
76
At this point, the columns with 1 or -1 in the first entry
77
contribute to the rank of the matrix, and these can be counted and
78
then deleted (after using the 1 or -1 entry to clear out its row).
79
Suppose that there were `N` of these.
80
81
The resulting matrix should be much smaller; we then feed it
82
to Sage's ``elementary_divisors`` function, and prepend `N` 1's to
83
account for the rows deleted in the previous step.
84
85
EXAMPLES::
86
87
sage: from sage.homology.chain_complex import dhsw_snf
88
sage: mat = matrix(ZZ, 3, 4, range(12))
89
sage: dhsw_snf(mat)
90
[1, 4, 0]
91
sage: mat = random_matrix(ZZ, 20, 20, x=-1, y=2)
92
sage: mat.elementary_divisors() == dhsw_snf(mat)
93
True
94
95
REFERENCES:
96
97
- Dumas, Heckenbach, Saunders, Welker, "Computing simplicial
98
homology based on efficient Smith normal form algorithms," in
99
"Algebra, geometry, and software systems" (2003), 177-206.
100
"""
101
ring = mat.base_ring()
102
rows = mat.nrows()
103
cols = mat.ncols()
104
new_data = {}
105
new_mat = matrix(ring, rows, cols, new_data)
106
add_to_rank = 0
107
zero_cols = 0
108
if verbose:
109
print "old matrix: %s by %s" % (rows, cols)
110
# leading_positions: dictionary of lists indexed by row: if first
111
# nonzero entry in column c is in row r, then leading_positions[r]
112
# should contain c
113
leading_positions = {}
114
# pass 1:
115
if verbose:
116
print "starting pass 1"
117
for j in range(cols):
118
# new_col is a matrix with one column: sparse matrices seem to
119
# be less buggy than sparse vectors (#5184, #5185), and
120
# perhaps also faster.
121
new_col = mat.matrix_from_columns([j])
122
if new_col.is_zero():
123
zero_cols += 1
124
else:
125
check_leading = True
126
while check_leading:
127
i = new_col.nonzero_positions_in_column(0)[0]
128
entry = new_col[i,0]
129
check_leading = False
130
if i in leading_positions:
131
for c in leading_positions[i]:
132
earlier = new_mat[i,c]
133
# right now we don't check to see if entry divides
134
# earlier, because we don't want to modify the
135
# earlier columns of the matrix. Deal with this
136
# in pass 2.
137
if entry and earlier.divides(entry):
138
quo = entry.divide_knowing_divisible_by(earlier)
139
new_col = new_col - quo * new_mat.matrix_from_columns([c])
140
entry = 0
141
if not new_col.is_zero():
142
check_leading = True
143
if not new_col.is_zero():
144
new_mat.set_column(j-zero_cols, new_col.column(0))
145
i = new_col.nonzero_positions_in_column(0)[0]
146
if i in leading_positions:
147
leading_positions[i].append(j-zero_cols)
148
else:
149
leading_positions[i] = [j-zero_cols]
150
else:
151
zero_cols += 1
152
# pass 2:
153
# first eliminate the zero columns at the end
154
cols = cols - zero_cols
155
zero_cols = 0
156
new_mat = new_mat.matrix_from_columns(range(cols))
157
if verbose:
158
print "starting pass 2"
159
keep_columns = range(cols)
160
check_leading = True
161
while check_leading:
162
check_leading = False
163
new_leading = leading_positions.copy()
164
for i in leading_positions:
165
if len(leading_positions[i]) > 1:
166
j = leading_positions[i][0]
167
jth = new_mat[i, j]
168
for n in leading_positions[i][1:]:
169
nth = new_mat[i,n]
170
if jth.divides(nth):
171
quo = nth.divide_knowing_divisible_by(jth)
172
new_mat.add_multiple_of_column(n, j, -quo)
173
elif nth.divides(jth):
174
quo = jth.divide_knowing_divisible_by(nth)
175
jth = nth
176
new_mat.swap_columns(n, j)
177
new_mat.add_multiple_of_column(n, j, -quo)
178
else:
179
(g,r,s) = jth.xgcd(nth)
180
(unit,A,B) = r.xgcd(-s) # unit ought to be 1 here
181
jth_col = new_mat.column(j)
182
nth_col = new_mat.column(n)
183
new_mat.set_column(j, r*jth_col + s*nth_col)
184
new_mat.set_column(n, B*jth_col + A*nth_col)
185
nth = B*jth + A*nth
186
jth = g
187
# at this point, jth should divide nth
188
quo = nth.divide_knowing_divisible_by(jth)
189
new_mat.add_multiple_of_column(n, j, -quo)
190
new_leading[i].remove(n)
191
if new_mat.column(n).is_zero():
192
keep_columns.remove(n)
193
zero_cols += 1
194
else:
195
new_r = new_mat.column(n).nonzero_positions()[0]
196
if new_r in new_leading:
197
new_leading[new_r].append(n)
198
else:
199
new_leading[new_r] = [n]
200
check_leading = True
201
leading_positions = new_leading
202
# pass 3: get rid of columns which start with 1 or -1
203
if verbose:
204
print "starting pass 3"
205
max_leading = 1
206
for i in leading_positions:
207
j = leading_positions[i][0]
208
entry = new_mat[i,j]
209
if entry.abs() == 1:
210
add_to_rank += 1
211
keep_columns.remove(j)
212
for c in new_mat.nonzero_positions_in_row(i):
213
if c in keep_columns:
214
new_mat.add_multiple_of_column(c, j, -entry * new_mat[i,c])
215
else:
216
max_leading = max(max_leading, new_mat[i,j].abs())
217
# form the new matrix
218
if max_leading != 1:
219
new_mat = new_mat.matrix_from_columns(keep_columns)
220
if verbose:
221
print "new matrix: %s by %s" % (new_mat.nrows(), new_mat.ncols())
222
if new_mat.is_sparse():
223
ed = [1]*add_to_rank + new_mat.dense_matrix().elementary_divisors()
224
else:
225
ed = [1]*add_to_rank + new_mat.elementary_divisors()
226
else:
227
if verbose:
228
print "new matrix: all pivots are 1 or -1"
229
ed = [1]*add_to_rank
230
231
if len(ed) < rows:
232
return ed + [0]*(rows - len(ed))
233
else:
234
return ed[:rows]
235
236
def _latex_module(R, m):
237
"""
238
LaTeX string representing a free module over ``R`` of rank ``m``.
239
240
INPUT:
241
242
- ``R`` - a commutative ring
243
- ``m`` - non-negative integer
244
245
This is used by the ``_latex_`` method for chain complexes.
246
247
EXAMPLES::
248
249
sage: from sage.homology.chain_complex import _latex_module
250
sage: _latex_module(ZZ, 3)
251
'\\Bold{Z}^{3}'
252
sage: _latex_module(ZZ, 0)
253
'0'
254
sage: _latex_module(GF(3), 1)
255
'\\Bold{F}_{3}^{1}'
256
"""
257
if m == 0:
258
return str(latex(0))
259
else:
260
return str(latex(FreeModule(R, m)))
261
262
class ChainComplex(SageObject):
263
"""
264
Define a chain complex.
265
266
INPUT:
267
268
- ``data`` - the data defining the chain complex; see below for
269
more details.
270
271
- ``base_ring`` - a commutative ring (optional), the ring over
272
which the chain complex is defined. If this is not specified,
273
it is determined by the data defining the chain complex.
274
275
- ``grading_group`` - a free abelian group (optional, default
276
ZZ), the group over which the chain complex is indexed.
277
278
- ``degree`` - element of grading_group (optional, default 1),
279
the degree of the differential.
280
281
- ``check_products`` - boolean (optional, default True). If True,
282
check that each consecutive pair of differentials are
283
composable and have composite equal to zero.
284
285
OUTPUT: a chain complex
286
287
.. warning::
288
289
Right now, homology calculations will only work if the base
290
ring is either ZZ or a field, so please take this into account
291
when defining a chain complex.
292
293
Use data to define the chain complex. This may be in any of the
294
following forms.
295
296
1. a dictionary with integers (or more generally, elements of
297
grading_group) for keys, and with data[n] a matrix representing
298
(via left multiplication) the differential coming from degree
299
`n`. (Note that the shape of the matrix then determines the
300
rank of the free modules `C_n` and `C_{n+d}`.)
301
302
2. a list or tuple of the form `[C_0, d_0, C_1, d_1, C_2, d_2,
303
...]`, where each `C_i` is a free module and each `d_i` is a
304
matrix, as above. This only makes sense if ``grading_group``
305
is `\\ZZ` and ``degree`` is 1.
306
307
3. a list or tuple of the form `[r_0, d_0, r_1, d_1, r_2, d_2,
308
...]`, where `r_i` is the rank of the free module `C_i` and
309
each `d_i` is a matrix, as above. This only makes sense if
310
``grading_group`` is `\\ZZ` and ``degree`` is 1.
311
312
4. a list or tuple of the form `[d_0, d_1, d_2, ...]` where each
313
`d_i` is a matrix, as above. This only makes sense if
314
``grading_group`` is `\\ZZ` and ``degree`` is 1.
315
316
.. note::
317
318
In fact, the free modules `C_i` in case 2 and the ranks `r_i`
319
in case 3 are ignored: only the matrices are kept, and from
320
their shapes, the ranks of the modules are determined.
321
(Indeed, if ``data`` is a list or tuple, then any element which
322
is not a matrix is discarded; thus the list may have any number
323
of different things in it, and all of the non-matrices will be
324
ignored.) No error checking is done to make sure, for
325
instance, that the given modules have the appropriate ranks for
326
the given matrices. However, as long as ``check_products`` is
327
True, the code checks to see if the matrices are composable and
328
that each appropriate composite is zero.
329
330
If the base ring is not specified, then the matrices are examined
331
to determine a ring over which they are all naturally defined, and
332
this becomes the base ring for the complex. If no such ring can
333
be found, an error is raised. If the base ring is specified, then
334
the matrices are converted automatically to this ring when
335
defining the chain complex. If some matrix cannot be converted,
336
then an error is raised.
337
338
EXAMPLES::
339
340
sage: ChainComplex()
341
Chain complex with at most 0 nonzero terms over Integer Ring
342
sage: C = ChainComplex({0: matrix(ZZ, 2, 3, [3, 0, 0, 0, 0, 0])})
343
sage: C
344
Chain complex with at most 2 nonzero terms over Integer Ring
345
sage: D = ChainComplex([matrix(ZZ, 2, 2, [0, 1, 0, 0]), matrix(ZZ, 2, 2, [0, 1, 0, 0])], base_ring=GF(2)); D
346
Chain complex with at most 3 nonzero terms over Finite Field of size 2
347
sage: D == loads(dumps(D))
348
True
349
350
Note that when a chain complex is defined in Sage, new
351
differentials may be created: every nonzero module in the chain
352
complex must have a differential coming from it, even if that
353
differential is zero::
354
355
sage: IZ = ChainComplex({0: identity_matrix(ZZ, 1)})
356
sage: IZ.differential() # the differentials in the chain complex
357
{0: [1], 1: []}
358
sage: IZ.differential(1).parent()
359
Full MatrixSpace of 0 by 1 dense matrices over Integer Ring
360
sage: mat = ChainComplex({0: matrix(ZZ, 3, 4)}).differential(1)
361
sage: mat.nrows(), mat.ncols()
362
(0, 3)
363
364
Defining the base ring implicitly::
365
366
sage: ChainComplex([matrix(QQ, 3, 1), matrix(ZZ, 4, 3)])
367
Chain complex with at most 3 nonzero terms over Rational Field
368
sage: ChainComplex([matrix(GF(125, 'a'), 3, 1), matrix(ZZ, 4, 3)])
369
Chain complex with at most 3 nonzero terms over Finite Field in a of size 5^3
370
371
If the matrices are defined over incompatible rings, an error results::
372
373
sage: ChainComplex([matrix(GF(125, 'a'), 3, 1), matrix(QQ, 4, 3)])
374
Traceback (most recent call last):
375
...
376
TypeError: unable to find a common ring for all elements
377
378
If the base ring is given explicitly but is not compatible with
379
the matrices, an error results::
380
381
sage: ChainComplex([matrix(GF(125, 'a'), 3, 1)], base_ring=QQ)
382
Traceback (most recent call last):
383
...
384
TypeError: Unable to coerce 0 (<type 'sage.rings.finite_rings.element_givaro.FiniteField_givaroElement'>) to Rational
385
"""
386
387
def __init__(self, data=None, **kwds):
388
"""
389
See ``ChainComplex`` for full documentation.
390
391
EXAMPLES::
392
393
sage: C = ChainComplex(); C
394
Chain complex with at most 0 nonzero terms over Integer Ring
395
sage: D = ChainComplex({0: matrix(ZZ, 2, 3, [3, 0, 0, 0, 0, 0])})
396
sage: D
397
Chain complex with at most 2 nonzero terms over Integer Ring
398
"""
399
check_products = kwds.get('check_products', True) or kwds.get('check_diffs')
400
base_ring = kwds.get('base_ring', None)
401
grading_group = kwds.get('grading_group', ZZ)
402
degree = kwds.get('degree', 1)
403
404
try:
405
deg = grading_group(degree)
406
except:
407
raise ValueError, "The 'degree' does not appear to be an element of the grading group."
408
# check form of data
409
new_data = {}
410
if data is None or (isinstance(data, (list, tuple)) and len(data) == 0):
411
# the zero chain complex
412
try:
413
zero = grading_group.zero_vector()
414
except AttributeError:
415
zero = ZZ.zero_element()
416
if base_ring is None:
417
base_ring = ZZ
418
new_data = {zero: matrix(base_ring, 0, 0, [])}
419
else:
420
if isinstance(data, dict): # case 1, dictionary
421
temp_dict = data
422
elif isinstance(data, (list, tuple)): # cases 2, 3, 4: list or tuple
423
if degree != 1:
424
raise ValueError, "The degree must be +1 if the data argument is a list or tuple."
425
if grading_group != ZZ:
426
raise ValueError, "The grading_group must be ZZ if the data argument is a list or tuple."
427
new_list = filter(lambda x: isinstance(x, Matrix), data)
428
temp_dict = dict(zip(range(len(new_list)), new_list))
429
else:
430
raise TypeError, "The data for a chain complex must be a dictionary, list, or tuple."
431
432
if base_ring is None:
433
junk, ring = prepare_dict(dict([n, temp_dict[n].base_ring()(0)] for n in temp_dict))
434
base_ring = ring
435
# 'dict' is the dictionary of matrices. check to see that
436
# each entry is in fact a matrix, and that the codomain of
437
# one matches up with the domain of the next. if the
438
# number of rows in the differential in degree n is
439
# nonzero, then make sure that the differential in degree
440
# n+degree is present (although it of course may be zero).
441
# If it's not present, add a zero matrix of the
442
# appropriate shape. This way, if self._data does not
443
# have a key for n, then the complex is zero in degree n.
444
for n in temp_dict.keys():
445
if not n in grading_group:
446
raise ValueError, "One of the dictionary keys does not appear to be an element of the grading group."
447
mat = temp_dict[n]
448
if not isinstance(mat, Matrix):
449
raise TypeError, "One of the differentials in the data dictionary indexed by does not appear to be a matrix."
450
if mat.base_ring() == base_ring:
451
new_data[n] = mat
452
else:
453
new_data[n] = mat.change_ring(base_ring)
454
for n in temp_dict.keys():
455
mat = temp_dict[n]
456
if n+degree in temp_dict:
457
mat2 = temp_dict[n+degree]
458
if check_products:
459
try:
460
prod = mat2 * mat
461
except TypeError:
462
raise TypeError, "The differentials d_{%s} and d_{%s} are not compatible: their product is not defined." % (n, n+degree)
463
if not prod.is_zero():
464
raise ValueError, "The differentials d_{%s} and d_{%s} are not compatible: their composition is not zero." % (n, n+degree)
465
else:
466
if not mat.nrows() == 0:
467
if n+2*degree in temp_dict:
468
new_data[n+degree] = matrix(base_ring, temp_dict[n+2*degree].ncols(), mat.nrows())
469
else:
470
new_data[n+degree] = matrix(base_ring, 0, mat.nrows())
471
# here ends the initialization/error-checking of the data
472
self._grading_group = grading_group
473
self._degree = deg
474
self._base_ring = base_ring
475
self._diff = new_data
476
# self._ranks: dictionary for caching the ranks of the
477
# differentials: keys are pairs (dim, base_ring)
478
self._ranks = {}
479
480
def base_ring(self):
481
"""
482
The base ring for this simplicial complex.
483
484
EXAMPLES::
485
486
sage: ChainComplex().base_ring()
487
Integer Ring
488
"""
489
return self._base_ring
490
491
def differential(self, dim=None):
492
"""
493
The differentials which make up the chain complex.
494
495
INPUT:
496
497
- ``dim`` - element of the grading group (optional, default
498
None). If this is None, return a dictionary of all of the
499
differentials. If this is a single element, return the
500
differential starting in that dimension.
501
502
OUTPUT: either a dictionary of all of the differentials or a single
503
differential (i.e., a matrix)
504
505
EXAMPLES::
506
507
sage: D = ChainComplex({0: matrix(ZZ, 2, 2, [1,0,0,2])})
508
sage: D.differential()
509
{0: [1 0]
510
[0 2], 1: []}
511
sage: D.differential(0)
512
[1 0]
513
[0 2]
514
sage: C = ChainComplex({0: identity_matrix(ZZ, 40)})
515
sage: C.differential()
516
{0: 40 x 40 dense matrix over Integer Ring, 1: []}
517
"""
518
if dim is None:
519
return self._diff
520
deg = self._degree
521
if dim in self._diff:
522
return self._diff[dim]
523
if dim+deg in self._diff:
524
return matrix(self._base_ring, self._diff[dim+deg].ncols(), 0)
525
return matrix(self._base_ring, 0, 0)
526
527
def dual(self):
528
"""
529
The dual chain complex to ``self``.
530
531
Since all modules in ``self`` are free of finite rank, the
532
dual in dimension `n` is isomorphic to the original chain
533
complex in dimension `n`, and the corresponding boundary
534
matrix is the transpose of the matrix in the original complex.
535
This converts a chain complex to a cochain complex and vice
536
versa.
537
538
EXAMPLES::
539
540
sage: C = ChainComplex({2: matrix(ZZ, 2, 3, [3, 0, 0, 0, 0, 0])})
541
sage: C._degree
542
1
543
sage: C.differential(2)
544
[3 0 0]
545
[0 0 0]
546
sage: C.dual()._degree
547
-1
548
sage: C.dual().differential(3)
549
[3 0]
550
[0 0]
551
[0 0]
552
"""
553
data = {}
554
deg = self._degree
555
for d in self.differential():
556
data[(d+deg)] = self.differential()[d].transpose()
557
558
return ChainComplex(data, degree=-deg)
559
560
def free_module(self):
561
"""
562
The free module underlying this chain complex.
563
564
EXAMPLES::
565
566
sage: C = ChainComplex({0: matrix(ZZ, 2, 3, [3, 0, 0, 0, 0, 0]), 1: matrix(ZZ, 0, 2)})
567
sage: C.free_module()
568
Ambient free module of rank 5 over the principal ideal domain Integer Ring
569
570
This defines the forgetful functor from the category of chain
571
complexes to the category of free modules::
572
573
sage: FreeModules(ZZ)(C)
574
Ambient free module of rank 5 over the principal ideal domain Integer Ring
575
"""
576
rank = sum([mat.ncols() for mat in self.differential().values()])
577
return FreeModule(self._base_ring, rank)
578
579
def __cmp__(self, other):
580
"""
581
Return True iff this chain complex is the same as other: that
582
is, if the base rings and the matrices of the two are the
583
same.
584
585
EXAMPLES::
586
587
sage: C = ChainComplex({0: matrix(ZZ, 2, 3, [3, 0, 0, 0, 0, 0])}, base_ring=GF(2))
588
sage: D = ChainComplex({0: matrix(GF(2), 2, 3, [1, 0, 0, 0, 0, 0]), 1: matrix(ZZ, 0, 2), 3: matrix(ZZ, 0, 0)}) # base_ring determined from the matrices
589
sage: C == D
590
True
591
"""
592
if self._base_ring != other._base_ring:
593
return -1
594
R = self._base_ring
595
equal = True
596
for d,mat in self.differential().iteritems():
597
if d not in other.differential():
598
equal = equal and mat.ncols() == 0 and mat.nrows() == 0
599
else:
600
equal = (equal and
601
other.differential()[d].change_ring(R) == mat.change_ring(R))
602
for d,mat in other.differential().iteritems():
603
if d not in self.differential():
604
equal = equal and mat.ncols() == 0 and mat.nrows() == 0
605
if equal:
606
return 0
607
return -1
608
609
def homology(self, dim=None, **kwds):
610
"""
611
The homology of the chain complex in the given dimension.
612
613
INPUT:
614
615
- ``dim`` - an element of the grading group for the chain
616
complex (optional, default None): the degree in which to
617
compute homology. If this is None, return the homology in
618
every dimension in which the chain complex is possibly
619
nonzero.
620
621
- ``base_ring`` - a commutative ring (optional, default is the
622
base ring for the chain complex). Must be either the
623
integers `\\ZZ` or a field.
624
625
- ``generators`` - boolean (optional, default False). If
626
True, return generators for the homology groups along with
627
the groups. NOTE: this is only implemented if the CHomP
628
package is available.
629
630
- ``verbose`` - boolean (optional, default False). If True,
631
print some messages as the homology is computed.
632
633
- ``algorithm`` - string (optional, default 'auto'). The
634
options are 'auto', 'dhsw', 'pari' or 'no_chomp'. See
635
below for descriptions.
636
637
OUTPUT: if dim is specified, the homology in dimension dim.
638
Otherwise, the homology in every dimension as a dictionary
639
indexed by dimension.
640
641
ALGORITHM:
642
643
If ``algorithm`` is set to 'auto' (the default), then use
644
CHomP if available. (CHomP is available at the web page
645
http://chomp.rutgers.edu/. It is also an experimental package
646
for Sage.)
647
648
CHomP computes homology, not cohomology, and only works over
649
the integers or finite prime fields. Therefore if any of
650
these conditions fails, or if CHomP is not present, or if
651
``algorithm`` is set to 'no_chomp', go to plan B: if ``self``
652
has a ``_homology`` method -- each simplicial complex has
653
this, for example -- then call that. Such a method implements
654
specialized algorithms for the particular type of cell
655
complex.
656
657
Otherwise, move on to plan C: compute the chain complex of
658
``self`` and compute its homology groups. To do this: over a
659
field, just compute ranks and nullities, thus obtaining
660
dimensions of the homology groups as vector spaces. Over the
661
integers, compute Smith normal form of the boundary matrices
662
defining the chain complex according to the value of
663
``algorithm``. If ``algorithm`` is 'auto' or 'no_chomp', then
664
for each relatively small matrix, use the standard Sage
665
method, which calls the Pari package. For any large matrix,
666
reduce it using the Dumas, Heckenbach, Saunders, and Welker
667
elimination algorithm: see
668
:func:`sage.homology.chain_complex.dhsw_snf` for details.
669
670
Finally, ``algorithm`` may also be 'pari' or 'dhsw', which
671
forces the named algorithm to be used regardless of the size
672
of the matrices and regardless of whether CHomP is available.
673
674
As of this writing, CHomP is by far the fastest option,
675
followed by the 'auto' or 'no_chomp' setting of using the
676
Dumas, Heckenbach, Saunders, and Welker elimination algorithm
677
for large matrices and Pari for small ones.
678
679
.. warning::
680
681
This only works if the base ring is the integers or a
682
field. Other values will return an error.
683
684
EXAMPLES::
685
686
sage: C = ChainComplex({0: matrix(ZZ, 2, 3, [3, 0, 0, 0, 0, 0])})
687
sage: C.homology()
688
{0: Z x Z, 1: Z x C3}
689
sage: C.homology(dim=1, base_ring = GF(3))
690
Vector space of dimension 2 over Finite Field of size 3
691
sage: D = ChainComplex({0: identity_matrix(ZZ, 4), 4: identity_matrix(ZZ, 30)})
692
sage: D.homology()
693
{0: 0, 1: 0, 4: 0, 5: 0}
694
695
Generators (only available via CHomP): generators are given as
696
a list of cycles, each of which is an element in the
697
appropriate free module, and hence is represented as a vector::
698
699
sage: C.homology(1, generators=True) # optional - CHomP
700
(Z x C3, [(1, 0), (0, 1)])
701
"""
702
from sage.interfaces.chomp import have_chomp, homchain
703
704
algorithm = kwds.get('algorithm', 'auto')
705
verbose = kwds.get('verbose', False)
706
base_ring = kwds.get('base_ring', None)
707
if base_ring is None or base_ring == self._base_ring:
708
change_ring = False
709
base_ring = self._base_ring
710
else:
711
change_ring = True
712
if (not base_ring.is_field()) and (base_ring != ZZ):
713
raise NotImplementedError, "Can't compute homology if the base ring is not the integers or a field."
714
715
# try to use CHomP if working over Z or F_p, p a prime.
716
if (algorithm == 'auto' and (base_ring == ZZ or
717
(base_ring.is_prime_field() and base_ring != QQ))):
718
# compute all of homology, then pick off requested dimensions
719
H = None
720
if have_chomp('homchain'):
721
H = homchain(self, **kwds)
722
# now pick off the requested dimensions
723
if H:
724
if dim is not None:
725
field = base_ring.is_prime_field()
726
answer = {}
727
if isinstance(dim, (list, tuple)):
728
for d in dim:
729
if d in H:
730
answer[d] = H[d]
731
else:
732
if field:
733
answer[d] = VectorSpace(base_ring, 0)
734
else:
735
answer[d] = HomologyGroup(0)
736
else:
737
if dim in H:
738
answer = H[dim]
739
else:
740
if field:
741
answer = VectorSpace(base_ring, 0)
742
else:
743
answer = HomologyGroup(0)
744
else:
745
answer = H
746
return answer
747
else:
748
if verbose:
749
print "ran CHomP, but no output."
750
751
if algorithm not in ['dhsw', 'pari', 'auto', 'no_chomp']:
752
raise NotImplementedError, "algorithm not recognized"
753
# if dim is None, return all of the homology groups
754
if dim is None:
755
answer = {}
756
for n in self._diff.keys():
757
if verbose:
758
print "Computing homology of the chain complex in dimension %s..." % n
759
if base_ring == self._base_ring:
760
answer[n] = self.homology(n, verbose=verbose,
761
algorithm=algorithm)
762
else:
763
answer[n] = self.homology(n, base_ring=base_ring,
764
verbose=verbose,
765
algorithm=algorithm)
766
return answer
767
# now compute the homology in the given dimension
768
degree = self._degree
769
# check that dim is in grading_group
770
if not dim in self._grading_group:
771
raise ValueError, "The dimension does not appear to be an element of the grading group."
772
if dim in self._diff:
773
# d_out is the differential going out of degree dim,
774
# d_in is the differential entering degree dim
775
d_out_cols = self._diff[dim].ncols()
776
d_out_rows = self._diff[dim].nrows()
777
# avoid bugs in rank of sparse mod n matrices (#5099):
778
if min(d_out_cols, d_out_rows) == 0:
779
d_out_rank = 0
780
if base_ring == ZZ:
781
temp_ring = QQ
782
else:
783
temp_ring = base_ring
784
if (dim, temp_ring) in self._ranks:
785
d_out_rank = self._ranks[(dim, temp_ring)]
786
else:
787
if min(d_out_cols, d_out_rows) == 0:
788
d_out_rank = 0
789
elif change_ring or base_ring == ZZ:
790
d_out_rank = self._diff[dim].change_ring(temp_ring).rank()
791
else:
792
d_out_rank = self._diff[dim].rank()
793
self._ranks[(dim, temp_ring)] = d_out_rank
794
795
if dim-degree in self._diff:
796
if change_ring:
797
d_in = self._diff[dim-degree].change_ring(base_ring)
798
else:
799
d_in = self._diff[dim-degree]
800
if base_ring.is_field():
801
# avoid bugs in rank of sparse mod n matrices (#5099):
802
if d_out_cols == 0:
803
null = 0
804
elif d_out_rows == 0:
805
null = d_out_cols
806
else:
807
null = d_out_cols - d_out_rank
808
if d_in.nrows() == 0 or d_in.ncols() == 0:
809
rk = 0
810
else:
811
if (dim-degree, temp_ring) in self._ranks:
812
rk = self._ranks[(dim-degree, temp_ring)]
813
else:
814
rk = d_in.rank()
815
self._ranks[(dim-degree, temp_ring)] = rk
816
answer = VectorSpace(base_ring, null - rk)
817
elif base_ring == ZZ:
818
nullity = d_out_cols - d_out_rank
819
if d_in.ncols() == 0:
820
all_divs = [0] * nullity
821
else:
822
if algorithm == 'auto' or algorithm == 'no_chomp':
823
if ((d_in.ncols() > 300 and d_in.nrows() > 300)
824
or (min(d_in.ncols(), d_in.nrows()) > 100 and
825
d_in.ncols() + d_in.nrows() > 600)):
826
algorithm = 'dhsw'
827
else:
828
algorithm = 'pari'
829
if algorithm == 'dhsw':
830
all_divs = dhsw_snf(d_in, verbose=verbose)
831
else:
832
if d_in.is_sparse():
833
all_divs = d_in.dense_matrix().elementary_divisors(algorithm)
834
else:
835
all_divs = d_in.elementary_divisors(algorithm)
836
all_divs = all_divs[:nullity]
837
# divisors equal to 1 produce trivial
838
# summands, so filter them out
839
divisors = filter(lambda x: x != 1, all_divs)
840
answer = HomologyGroup(len(divisors), divisors)
841
else:
842
# This code is not in use: base ring isn't a field
843
# or ZZ
844
pass
845
else: # no incoming differential: it's zero
846
if base_ring.is_field():
847
answer = VectorSpace(base_ring, d_out_cols - d_out_rank)
848
elif base_ring == ZZ:
849
nullity = d_out_cols - d_out_rank
850
answer = HomologyGroup(nullity)
851
else:
852
# This code is not in use: base ring isn't a field
853
# or ZZ
854
pass
855
else: # chain complex is zero here, so return the zero module
856
if base_ring.is_field():
857
answer = VectorSpace(base_ring, 0)
858
elif base_ring == ZZ:
859
answer = HomologyGroup(0)
860
else:
861
# This code is not in use: base ring isn't a field
862
# or ZZ
863
answer = FreeModule(self._base_ring, rank=0)
864
if verbose:
865
print " Homology is %s" % answer
866
return answer
867
868
def betti(self, dim=None, **kwds):
869
"""
870
The Betti number of the homology of the chain complex in this
871
dimension.
872
873
That is, write the homology in this dimension as a direct sum
874
of a free module and a torsion module; the Betti number is the
875
rank of the free summand.
876
877
INPUT:
878
879
- ``dim`` - an element of the grading group for the chain
880
complex or None (optional, default None). If None, then
881
return every Betti number, as a dictionary indexed by
882
degree. If an element of the grading group, then return
883
the Betti number in that dimension.
884
885
- ``base_ring`` - a commutative ring (optional, default is the
886
base ring for the chain complex). Compute homology with
887
these coefficients. Must be either the integers or a
888
field.
889
890
OUTPUT: the Betti number in dimension ``dim`` - the rank of
891
the free part of the homology module in this dimension.
892
893
EXAMPLES::
894
895
sage: C = ChainComplex({0: matrix(ZZ, 2, 3, [3, 0, 0, 0, 0, 0])})
896
sage: C.betti(0)
897
2
898
sage: [C.betti(n) for n in range(5)]
899
[2, 1, 0, 0, 0]
900
sage: C.betti()
901
{0: 2, 1: 1}
902
"""
903
base_ring = kwds.get('base_ring', None)
904
905
if base_ring is None:
906
base_ring = self._base_ring
907
if base_ring == ZZ:
908
base_ring = QQ
909
if base_ring.is_field():
910
kwds['base_ring'] = base_ring
911
H = self.homology(dim=dim, **kwds)
912
if isinstance(H, dict):
913
return dict([i, H[i].dimension()] for i in H)
914
else:
915
return H.dimension()
916
else:
917
raise NotImplementedError, "Not implemented: unable to compute Betti numbers if the base ring is not ZZ or a field."
918
919
def torsion_list(self, max_prime, min_prime=2):
920
"""
921
Look for torsion in this chain complex by computing its mod `p`
922
homology for a range of primes `p`.
923
924
INPUT:
925
926
- ``max_prime`` - prime number: search for torsion mod `p` for
927
all `p` strictly less than this number.
928
929
- ``min_prime`` - prime (optional, default 2): search for
930
torsion mod `p` for primes at least as big as this.
931
932
Return a list of pairs (`p`, ``dims``) where `p` is a prime at
933
which there is torsion and ``dims`` is a list of dimensions in
934
which this torsion occurs.
935
936
The base ring for the chain complex must be the integers; if
937
not, an error is raised.
938
939
Algorithm: let `C` denote the chain complex. Let `P` equal
940
``max_prime``. Compute the mod `P` homology of `C`, and use
941
this as the base-line computation: the assumption is that this
942
is isomorphic to the integral homology tensored with
943
`\\GF{P}`. Then compute the mod `p` homology for a range of
944
primes `p`, and record whenever the answer differs from the
945
base-line answer.
946
947
EXAMPLES::
948
949
sage: C = ChainComplex({0: matrix(ZZ, 2, 3, [3, 0, 0, 0, 0, 0])})
950
sage: C.homology()
951
{0: Z x Z, 1: Z x C3}
952
sage: C.torsion_list(11)
953
[(3, [1])]
954
sage: C = ChainComplex([matrix(ZZ, 1, 1, [2]), matrix(ZZ, 1, 1), matrix(1, 1, [3])])
955
sage: C.homology(1)
956
C2
957
sage: C.homology(3)
958
C3
959
sage: C.torsion_list(5)
960
[(2, [1]), (3, [3])]
961
"""
962
if self._base_ring != ZZ:
963
raise ValueError, "This only works if the base ring of the chain complex is the integers"
964
else:
965
answer = []
966
torsion_free = self.betti(base_ring=GF(max_prime))
967
for p in prime_range(min_prime, max_prime):
968
mod_p_betti = self.betti(base_ring=GF(p))
969
if mod_p_betti != torsion_free:
970
diff_dict = {}
971
temp_diff = {}
972
D = self._degree
973
for i in torsion_free:
974
temp_diff[i] = mod_p_betti.get(i, 0) - torsion_free[i]
975
for i in temp_diff:
976
if temp_diff[i] > 0:
977
if i+D in diff_dict:
978
lower = diff_dict[i+D]
979
else:
980
lower = 0
981
current = temp_diff[i]
982
if current > lower:
983
diff_dict[i] = current - lower
984
if i-D in diff_dict:
985
diff_dict[i-D] -= current - lower
986
differences = []
987
for i in diff_dict:
988
if diff_dict[i] != 0:
989
differences.append(i)
990
answer.append((p,differences))
991
return answer
992
993
def category(self):
994
"""
995
Return the category to which this chain complex belongs: the
996
category of all chain complexes over the base ring.
997
998
EXAMPLES::
999
1000
sage: C = ChainComplex({0: matrix(ZZ, 2, 3, [3, 0, 0, 0, 0, 0])}, base_ring=GF(7))
1001
sage: C.category()
1002
Category of chain complexes over Finite Field of size 7
1003
"""
1004
import sage.categories.all
1005
return sage.categories.all.ChainComplexes(self.base_ring())
1006
1007
def _Hom_(self, other, category=None):
1008
"""
1009
Return the set of chain maps between chain complexes ``self``
1010
and ``other``.
1011
1012
EXAMPLES::
1013
1014
sage: S = simplicial_complexes.Sphere(2)
1015
sage: T = simplicial_complexes.Torus()
1016
sage: C = S.chain_complex(augmented=True,cochain=True)
1017
sage: D = T.chain_complex(augmented=True,cochain=True)
1018
sage: Hom(C,D) # indirect doctest
1019
Set of Morphisms from Chain complex with at most 4 nonzero terms over Integer Ring to Chain complex with at most 4 nonzero terms over Integer Ring in Category of chain complexes over Integer Ring
1020
"""
1021
from sage.homology.chain_complex_homspace import ChainComplexHomspace
1022
return ChainComplexHomspace(self, other)
1023
1024
def _flip_(self):
1025
"""
1026
Flip chain complex upside down (degree `n` gets changed to
1027
degree `-n`), thus turning a chain complex into a cochain
1028
complex without changing the homology (except for flipping it,
1029
too).
1030
1031
EXAMPLES::
1032
1033
sage: C = ChainComplex({2: matrix(ZZ, 2, 3, [3, 0, 0, 0, 0, 0])})
1034
sage: C._degree
1035
1
1036
sage: C.differential(2)
1037
[3 0 0]
1038
[0 0 0]
1039
sage: C._flip_()._degree
1040
-1
1041
sage: C._flip_().differential(-2)
1042
[3 0 0]
1043
[0 0 0]
1044
"""
1045
data = {}
1046
deg = self._degree
1047
for d in self.differential():
1048
data[-d] = self.differential()[d]
1049
return ChainComplex(data, degree=-deg)
1050
1051
def _chomp_repr_(self):
1052
r"""
1053
String representation of self suitable for use by the CHomP
1054
program.
1055
1056
Since CHomP can only handle chain complexes, not cochain
1057
complexes, and since it likes its complexes to start in degree
1058
0, flip the complex over if necessary, and shift it to start
1059
in degree 0. Note also that CHomP only works over the
1060
integers or a finite prime field.
1061
1062
EXAMPLES::
1063
1064
sage: C = ChainComplex({-2: matrix(ZZ, 1, 3, [3, 0, 0])}, degree=-1)
1065
sage: C._chomp_repr_()
1066
'chain complex\n\nmax dimension = 1\n\ndimension 0\n boundary a1 = 0\n\ndimension 1\n boundary a1 = + 3 * a1 \n boundary a2 = 0\n boundary a3 = 0\n\n'
1067
sage: C = ChainComplex({-2: matrix(ZZ, 1, 3, [3, 0, 0])}, degree=1)
1068
sage: C._chomp_repr_()
1069
'chain complex\n\nmax dimension = 1\n\ndimension 0\n boundary a1 = 0\n\ndimension 1\n boundary a1 = + 3 * a1 \n boundary a2 = 0\n boundary a3 = 0\n\n'
1070
"""
1071
if (self._grading_group != ZZ or
1072
(self._degree != 1 and self._degree != -1)):
1073
raise ValueError, "CHomP only works on Z-graded chain complexes with differential of degree 1 or -1."
1074
base_ring = self._base_ring
1075
if (base_ring == QQ) or (base_ring != ZZ and not (base_ring.is_prime_field())):
1076
raise ValueError, "CHomP doesn't compute over the rationals, only over Z or F_p."
1077
if self._degree == -1:
1078
diffs = self.differential()
1079
else:
1080
diffs = self._flip_().differential()
1081
1082
maxdim = max(diffs)
1083
mindim = min(diffs)
1084
# will shift chain complex by subtracting mindim from
1085
# dimensions, so its bottom dimension is zero.
1086
s = "chain complex\n\nmax dimension = %s\n\n" % (maxdim - mindim,)
1087
1088
for i in range(0, maxdim - mindim + 1):
1089
s += "dimension %s\n" % i
1090
mat = diffs.get(i + mindim, matrix(base_ring, 0, 0))
1091
for idx in range(mat.ncols()):
1092
s += " boundary a%s = " % (idx + 1)
1093
# construct list of bdries
1094
col = mat.column(idx)
1095
if col.nonzero_positions():
1096
for j in col.nonzero_positions():
1097
entry = col[j]
1098
if entry > 0:
1099
sgn = "+"
1100
else:
1101
sgn = "-"
1102
entry = -entry
1103
s += "%s %s * a%s " % (sgn, entry, j+1)
1104
else:
1105
s += "0"
1106
s += "\n"
1107
s += "\n"
1108
1109
return s
1110
1111
def _repr_(self):
1112
"""
1113
Print representation.
1114
1115
EXAMPLES::
1116
1117
sage: C = ChainComplex({0: matrix(ZZ, 2, 3, [3, 0, 0, 0, 0, 0])})
1118
sage: C._repr_()
1119
'Chain complex with at most 2 nonzero terms over Integer Ring'
1120
"""
1121
diffs = filter(lambda mat: mat.nrows() + mat.ncols() > 0,
1122
self._diff.values())
1123
string1 = "Chain complex with at most"
1124
string2 = " %s nonzero terms over %s" % (len(diffs),
1125
self._base_ring)
1126
return string1 + string2
1127
1128
def _latex_(self):
1129
"""
1130
LaTeX print representation.
1131
1132
EXAMPLES::
1133
1134
sage: C = ChainComplex({0: matrix(ZZ, 2, 3, [3, 0, 0, 0, 0, 0])})
1135
sage: C._latex_()
1136
'\\Bold{Z}^{3} \\xrightarrow{d_{0}} \\Bold{Z}^{2}'
1137
"""
1138
# Warning: this is likely to screw up if, for example, the
1139
# degree of the differential is 2 and there are nonzero terms
1140
# in consecutive dimensions (e.g., in dimensions 0 and 1). In
1141
# such cases, the representation might show a differential
1142
# connecting these terms, although the differential goes from
1143
# dimension 0 to dimension 2, and from dimension 1 to
1144
# dimension 3, etc. I don't know how much effort should be
1145
# put into trying to fix this.
1146
string = ""
1147
dict = self._diff
1148
deg = self._degree
1149
ring = self._base_ring
1150
if self._grading_group != ZZ:
1151
guess = dict.keys()[0]
1152
if guess-deg in dict:
1153
string += "\\dots \\xrightarrow{d_{%s}} " % latex(guess-deg)
1154
string += _latex_module(ring, mat.ncols())
1155
string += " \\xrightarrow{d_{%s}} \\dots" % latex(guess)
1156
else:
1157
backwards = (deg < 0)
1158
sorted_list = sorted(dict.keys(), reverse=backwards)
1159
if len(dict) <= 6:
1160
for n in sorted_list[:-1]:
1161
mat = dict[n]
1162
string += _latex_module(ring, mat.ncols())
1163
string += " \\xrightarrow{d_{%s}} " % latex(n)
1164
mat = dict[sorted_list[-1]]
1165
string += _latex_module(ring, mat.ncols())
1166
else:
1167
for n in sorted_list[:2]:
1168
mat = dict[n]
1169
string += _latex_module(ring, mat.ncols())
1170
string += " \\xrightarrow{d_{%s}} " % latex(n)
1171
string += "\\dots "
1172
n = sorted_list[-2]
1173
string += "\\xrightarrow{d_{%s}} " % latex(n)
1174
mat = dict[sorted_list[-1]]
1175
string += _latex_module(ring, mat.ncols())
1176
return string
1177
1178
class HomologyGroup_class(AdditiveAbelianGroup_fixed_gens):
1179
"""
1180
Abelian group on `n` generators. This class inherits from
1181
``AdditiveAbelianGroup``; see that for more documentation. The main
1182
difference between the classes is in the print representation.
1183
1184
EXAMPLES::
1185
1186
sage: from sage.homology.chain_complex import HomologyGroup
1187
sage: G = AbelianGroup(5,[5,5,7,8,9]); G
1188
Multiplicative Abelian Group isomorphic to C5 x C5 x C7 x C8 x C9
1189
sage: H = HomologyGroup(5,[5,5,7,8,9]); H
1190
C5 x C5 x C7 x C8 x C9
1191
sage: G == loads(dumps(G))
1192
True
1193
sage: AbelianGroup(4)
1194
Multiplicative Abelian Group isomorphic to Z x Z x Z x Z
1195
sage: HomologyGroup(4)
1196
Z x Z x Z x Z
1197
sage: HomologyGroup(100)
1198
Z^100
1199
"""
1200
def __init__(self, n, invfac):
1201
"""
1202
See ``HomologyGroup`` for full documentation.
1203
1204
EXAMPLES::
1205
1206
sage: from sage.homology.chain_complex import HomologyGroup
1207
sage: H = HomologyGroup(5,[5,5,7,8,9]); H
1208
C5 x C5 x C7 x C8 x C9
1209
"""
1210
n = len(invfac)
1211
A = ZZ**n
1212
B = A.span([A.gen(i) * invfac[i] for i in xrange(n)])
1213
1214
AdditiveAbelianGroup_fixed_gens.__init__(self, A, B, A.gens())
1215
self._original_invts = invfac
1216
1217
def _repr_(self):
1218
"""
1219
Print representation
1220
1221
EXAMPLES::
1222
1223
sage: from sage.homology.chain_complex import HomologyGroup
1224
sage: H = HomologyGroup(7,[4,4,4,4,4,7,7])
1225
sage: H._repr_()
1226
'C4^5 x C7 x C7'
1227
sage: HomologyGroup(6)
1228
Z^6
1229
"""
1230
eldv = self._original_invts
1231
if len(eldv) == 0:
1232
return "0"
1233
rank = len(filter(lambda x: x == 0, eldv))
1234
torsion = sorted(filter(lambda x: x, eldv))
1235
if rank > 4:
1236
g = ["Z^%s" % rank]
1237
else:
1238
g = ["Z"] * rank
1239
if len(torsion) != 0:
1240
printed = []
1241
for t in torsion:
1242
numfac = torsion.count(t)
1243
too_many = (numfac > 4)
1244
if too_many:
1245
if t not in printed:
1246
g.append("C%s^%s" % (t, numfac))
1247
printed.append(t)
1248
else:
1249
g.append("C%s" % t)
1250
times = " x "
1251
return times.join(g)
1252
1253
def _latex_(self):
1254
"""
1255
LaTeX representation
1256
1257
EXAMPLES::
1258
1259
sage: from sage.homology.chain_complex import HomologyGroup
1260
sage: H = HomologyGroup(7,[4,4,4,4,4,7,7])
1261
sage: H._latex_()
1262
'C_{4}^{5} \\times C_{7} \\times C_{7}'
1263
sage: latex(HomologyGroup(6))
1264
\ZZ^{6}
1265
"""
1266
eldv = self._original_invts
1267
if len(eldv) == 0:
1268
return "0"
1269
rank = len(filter(lambda x: x == 0, eldv))
1270
torsion = sorted(filter(lambda x: x, eldv))
1271
if rank > 4:
1272
g = ["\\ZZ^{%s}" % rank]
1273
else:
1274
g = ["\\ZZ"] * rank
1275
if len(torsion) != 0:
1276
printed = []
1277
for t in torsion:
1278
numfac = torsion.count(t)
1279
too_many = (numfac > 4)
1280
if too_many:
1281
if t not in printed:
1282
g.append("C_{%s}^{%s}" % (t, numfac))
1283
printed.append(t)
1284
else:
1285
g.append("C_{%s}" % t)
1286
times = " \\times "
1287
return times.join(g)
1288
1289
def HomologyGroup(n, invfac=None):
1290
"""
1291
Abelian group on `n` generators. This class inherits from
1292
``AdditiveAbelianGroup``; see that for more documentation. The main
1293
difference between the classes is in the print representation.
1294
1295
EXAMPLES::
1296
1297
sage: from sage.homology.chain_complex import HomologyGroup
1298
sage: G = AbelianGroup(5,[5,5,7,8,9]); G
1299
Multiplicative Abelian Group isomorphic to C5 x C5 x C7 x C8 x C9
1300
sage: H = HomologyGroup(5,[5,5,7,8,9]); H
1301
C5 x C5 x C7 x C8 x C9
1302
sage: AbelianGroup(4)
1303
Multiplicative Abelian Group isomorphic to Z x Z x Z x Z
1304
sage: HomologyGroup(4)
1305
Z x Z x Z x Z
1306
sage: HomologyGroup(100)
1307
Z^100
1308
"""
1309
# copied from AbelianGroup:
1310
if invfac is None:
1311
if isinstance(n, (list, tuple)):
1312
invfac = n
1313
n = len(n)
1314
else:
1315
invfac = []
1316
if len(invfac) < n:
1317
invfac = [0] * (n - len(invfac)) + invfac
1318
elif len(invfac) > n:
1319
raise ValueError, "invfac (=%s) must have length n (=%s)"%(invfac, n)
1320
M = HomologyGroup_class(n, invfac)
1321
return M
1322
1323