Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sagemath
GitHub Repository: sagemath/sagesmc
Path: blob/master/src/sage/homology/matrix_utils.py
8818 views
1
"""
2
Utility Functions for Matrices
3
4
The actual computation of homology groups ends up being linear algebra
5
with the differentials thought of as matrices. This module contains
6
some utility functions for this purpose.
7
"""
8
9
########################################################################
10
# Copyright (C) 2013 John H. Palmieri <[email protected]>
11
#
12
# Distributed under the terms of the GNU General Public License (GPL)
13
# as published by the Free Software Foundation; either version 2 of
14
# the License, or (at your option) any later version.
15
#
16
# http://www.gnu.org/licenses/
17
########################################################################
18
19
20
# TODO: this module is a clear candidate for cythonizing. Need to
21
# evaluate speed benefits.
22
23
from sage.matrix.constructor import matrix
24
25
26
def dhsw_snf(mat, verbose=False):
27
"""
28
Preprocess a matrix using the "Elimination algorithm" described by
29
Dumas et al. [DHSW]_, and then call ``elementary_divisors`` on the
30
resulting (smaller) matrix.
31
32
.. NOTE::
33
34
'snf' stands for 'Smith Normal Form'.
35
36
INPUT:
37
38
- ``mat`` -- an integer matrix, either sparse or dense.
39
40
(They use the transpose of the matrix considered here, so they use
41
rows instead of columns.)
42
43
ALGORITHM:
44
45
Go through ``mat`` one column at a time. For each
46
column, add multiples of previous columns to it until either
47
48
- it's zero, in which case it should be deleted.
49
- its first nonzero entry is 1 or -1, in which case it should be kept.
50
- its first nonzero entry is something else, in which case it is
51
deferred until the second pass.
52
53
Then do a second pass on the deferred columns.
54
55
At this point, the columns with 1 or -1 in the first entry
56
contribute to the rank of the matrix, and these can be counted and
57
then deleted (after using the 1 or -1 entry to clear out its row).
58
Suppose that there were `N` of these.
59
60
The resulting matrix should be much smaller; we then feed it
61
to Sage's ``elementary_divisors`` function, and prepend `N` 1's to
62
account for the rows deleted in the previous step.
63
64
EXAMPLES::
65
66
sage: from sage.homology.matrix_utils import dhsw_snf
67
sage: mat = matrix(ZZ, 3, 4, range(12))
68
sage: dhsw_snf(mat)
69
[1, 4, 0]
70
sage: mat = random_matrix(ZZ, 20, 20, x=-1, y=2)
71
sage: mat.elementary_divisors() == dhsw_snf(mat)
72
True
73
74
REFERENCES:
75
76
.. [DHSW] Dumas, Heckenbach, Saunders, and Welker. *Computing simplicial
77
homology based on efficient Smith normal form algorithms*.
78
Algebra, geometry, and software systems. (2003) 177-206.
79
"""
80
ring = mat.base_ring()
81
rows = mat.nrows()
82
cols = mat.ncols()
83
new_data = {}
84
new_mat = matrix(ring, rows, cols, new_data)
85
add_to_rank = 0
86
zero_cols = 0
87
if verbose:
88
print "old matrix: %s by %s" % (rows, cols)
89
# leading_positions: dictionary of lists indexed by row: if first
90
# nonzero entry in column c is in row r, then leading_positions[r]
91
# should contain c
92
leading_positions = {}
93
# pass 1:
94
if verbose:
95
print "starting pass 1"
96
for j in range(cols):
97
# new_col is a matrix with one column: sparse matrices seem to
98
# be less buggy than sparse vectors (#5184, #5185), and
99
# perhaps also faster.
100
new_col = mat.matrix_from_columns([j])
101
if new_col.is_zero():
102
zero_cols += 1
103
else:
104
check_leading = True
105
while check_leading:
106
i = new_col.nonzero_positions_in_column(0)[0]
107
entry = new_col[i,0]
108
check_leading = False
109
if i in leading_positions:
110
for c in leading_positions[i]:
111
earlier = new_mat[i,c]
112
# right now we don't check to see if entry divides
113
# earlier, because we don't want to modify the
114
# earlier columns of the matrix. Deal with this
115
# in pass 2.
116
if entry and earlier.divides(entry):
117
quo = entry.divide_knowing_divisible_by(earlier)
118
new_col = new_col - quo * new_mat.matrix_from_columns([c])
119
entry = 0
120
if not new_col.is_zero():
121
check_leading = True
122
if not new_col.is_zero():
123
new_mat.set_column(j-zero_cols, new_col.column(0))
124
i = new_col.nonzero_positions_in_column(0)[0]
125
if i in leading_positions:
126
leading_positions[i].append(j-zero_cols)
127
else:
128
leading_positions[i] = [j-zero_cols]
129
else:
130
zero_cols += 1
131
# pass 2:
132
# first eliminate the zero columns at the end
133
cols = cols - zero_cols
134
zero_cols = 0
135
new_mat = new_mat.matrix_from_columns(range(cols))
136
if verbose:
137
print "starting pass 2"
138
keep_columns = range(cols)
139
check_leading = True
140
while check_leading:
141
check_leading = False
142
new_leading = leading_positions.copy()
143
for i in leading_positions:
144
if len(leading_positions[i]) > 1:
145
j = leading_positions[i][0]
146
jth = new_mat[i, j]
147
for n in leading_positions[i][1:]:
148
nth = new_mat[i,n]
149
if jth.divides(nth):
150
quo = nth.divide_knowing_divisible_by(jth)
151
new_mat.add_multiple_of_column(n, j, -quo)
152
elif nth.divides(jth):
153
quo = jth.divide_knowing_divisible_by(nth)
154
jth = nth
155
new_mat.swap_columns(n, j)
156
new_mat.add_multiple_of_column(n, j, -quo)
157
else:
158
(g,r,s) = jth.xgcd(nth)
159
(unit,A,B) = r.xgcd(-s) # unit ought to be 1 here
160
jth_col = new_mat.column(j)
161
nth_col = new_mat.column(n)
162
new_mat.set_column(j, r*jth_col + s*nth_col)
163
new_mat.set_column(n, B*jth_col + A*nth_col)
164
nth = B*jth + A*nth
165
jth = g
166
# at this point, jth should divide nth
167
quo = nth.divide_knowing_divisible_by(jth)
168
new_mat.add_multiple_of_column(n, j, -quo)
169
new_leading[i].remove(n)
170
if new_mat.column(n).is_zero():
171
keep_columns.remove(n)
172
zero_cols += 1
173
else:
174
new_r = new_mat.column(n).nonzero_positions()[0]
175
if new_r in new_leading:
176
new_leading[new_r].append(n)
177
else:
178
new_leading[new_r] = [n]
179
check_leading = True
180
leading_positions = new_leading
181
# pass 3: get rid of columns which start with 1 or -1
182
if verbose:
183
print "starting pass 3"
184
max_leading = 1
185
for i in leading_positions:
186
j = leading_positions[i][0]
187
entry = new_mat[i,j]
188
if entry.abs() == 1:
189
add_to_rank += 1
190
keep_columns.remove(j)
191
for c in new_mat.nonzero_positions_in_row(i):
192
if c in keep_columns:
193
new_mat.add_multiple_of_column(c, j, -entry * new_mat[i,c])
194
else:
195
max_leading = max(max_leading, new_mat[i,j].abs())
196
# form the new matrix
197
if max_leading != 1:
198
new_mat = new_mat.matrix_from_columns(keep_columns)
199
if verbose:
200
print "new matrix: %s by %s" % (new_mat.nrows(), new_mat.ncols())
201
if new_mat.is_sparse():
202
ed = [1]*add_to_rank + new_mat.dense_matrix().elementary_divisors()
203
else:
204
ed = [1]*add_to_rank + new_mat.elementary_divisors()
205
else:
206
if verbose:
207
print "new matrix: all pivots are 1 or -1"
208
ed = [1]*add_to_rank
209
210
if len(ed) < rows:
211
return ed + [0]*(rows - len(ed))
212
return ed[:rows]
213
214
215