Coverage for /usr/lib/python3/dist-packages/sympy/matrices/reductions.py: 9%

99 statements  

« prev     ^ index     » next       coverage.py v7.9.1, created at 2025-06-14 15:55 +0200

1from types import FunctionType 

2 

3from .utilities import _get_intermediate_simp, _iszero, _dotprodsimp, _simplify 

4from .determinant import _find_reasonable_pivot 

5 

6 

7def _row_reduce_list(mat, rows, cols, one, iszerofunc, simpfunc, 

8 normalize_last=True, normalize=True, zero_above=True): 

9 """Row reduce a flat list representation of a matrix and return a tuple 

10 (rref_matrix, pivot_cols, swaps) where ``rref_matrix`` is a flat list, 

11 ``pivot_cols`` are the pivot columns and ``swaps`` are any row swaps that 

12 were used in the process of row reduction. 

13 

14 Parameters 

15 ========== 

16 

17 mat : list 

18 list of matrix elements, must be ``rows`` * ``cols`` in length 

19 

20 rows, cols : integer 

21 number of rows and columns in flat list representation 

22 

23 one : SymPy object 

24 represents the value one, from ``Matrix.one`` 

25 

26 iszerofunc : determines if an entry can be used as a pivot 

27 

28 simpfunc : used to simplify elements and test if they are 

29 zero if ``iszerofunc`` returns `None` 

30 

31 normalize_last : indicates where all row reduction should 

32 happen in a fraction-free manner and then the rows are 

33 normalized (so that the pivots are 1), or whether 

34 rows should be normalized along the way (like the naive 

35 row reduction algorithm) 

36 

37 normalize : whether pivot rows should be normalized so that 

38 the pivot value is 1 

39 

40 zero_above : whether entries above the pivot should be zeroed. 

41 If ``zero_above=False``, an echelon matrix will be returned. 

42 """ 

43 

44 def get_col(i): 

45 return mat[i::cols] 

46 

47 def row_swap(i, j): 

48 mat[i*cols:(i + 1)*cols], mat[j*cols:(j + 1)*cols] = \ 

49 mat[j*cols:(j + 1)*cols], mat[i*cols:(i + 1)*cols] 

50 

51 def cross_cancel(a, i, b, j): 

52 """Does the row op row[i] = a*row[i] - b*row[j]""" 

53 q = (j - i)*cols 

54 for p in range(i*cols, (i + 1)*cols): 

55 mat[p] = isimp(a*mat[p] - b*mat[p + q]) 

56 

57 isimp = _get_intermediate_simp(_dotprodsimp) 

58 piv_row, piv_col = 0, 0 

59 pivot_cols = [] 

60 swaps = [] 

61 

62 # use a fraction free method to zero above and below each pivot 

63 while piv_col < cols and piv_row < rows: 

64 pivot_offset, pivot_val, \ 

65 assumed_nonzero, newly_determined = _find_reasonable_pivot( 

66 get_col(piv_col)[piv_row:], iszerofunc, simpfunc) 

67 

68 # _find_reasonable_pivot may have simplified some things 

69 # in the process. Let's not let them go to waste 

70 for (offset, val) in newly_determined: 

71 offset += piv_row 

72 mat[offset*cols + piv_col] = val 

73 

74 if pivot_offset is None: 

75 piv_col += 1 

76 continue 

77 

78 pivot_cols.append(piv_col) 

79 if pivot_offset != 0: 

80 row_swap(piv_row, pivot_offset + piv_row) 

81 swaps.append((piv_row, pivot_offset + piv_row)) 

82 

83 # if we aren't normalizing last, we normalize 

84 # before we zero the other rows 

85 if normalize_last is False: 

86 i, j = piv_row, piv_col 

87 mat[i*cols + j] = one 

88 for p in range(i*cols + j + 1, (i + 1)*cols): 

89 mat[p] = isimp(mat[p] / pivot_val) 

90 # after normalizing, the pivot value is 1 

91 pivot_val = one 

92 

93 # zero above and below the pivot 

94 for row in range(rows): 

95 # don't zero our current row 

96 if row == piv_row: 

97 continue 

98 # don't zero above the pivot unless we're told. 

99 if zero_above is False and row < piv_row: 

100 continue 

101 # if we're already a zero, don't do anything 

102 val = mat[row*cols + piv_col] 

103 if iszerofunc(val): 

104 continue 

105 

106 cross_cancel(pivot_val, row, val, piv_row) 

107 piv_row += 1 

108 

109 # normalize each row 

110 if normalize_last is True and normalize is True: 

111 for piv_i, piv_j in enumerate(pivot_cols): 

112 pivot_val = mat[piv_i*cols + piv_j] 

113 mat[piv_i*cols + piv_j] = one 

114 for p in range(piv_i*cols + piv_j + 1, (piv_i + 1)*cols): 

115 mat[p] = isimp(mat[p] / pivot_val) 

116 

117 return mat, tuple(pivot_cols), tuple(swaps) 

118 

119 

120# This functions is a candidate for caching if it gets implemented for matrices. 

121def _row_reduce(M, iszerofunc, simpfunc, normalize_last=True, 

122 normalize=True, zero_above=True): 

123 

124 mat, pivot_cols, swaps = _row_reduce_list(list(M), M.rows, M.cols, M.one, 

125 iszerofunc, simpfunc, normalize_last=normalize_last, 

126 normalize=normalize, zero_above=zero_above) 

127 

128 return M._new(M.rows, M.cols, mat), pivot_cols, swaps 

129 

130 

131def _is_echelon(M, iszerofunc=_iszero): 

132 """Returns `True` if the matrix is in echelon form. That is, all rows of 

133 zeros are at the bottom, and below each leading non-zero in a row are 

134 exclusively zeros.""" 

135 

136 if M.rows <= 0 or M.cols <= 0: 

137 return True 

138 

139 zeros_below = all(iszerofunc(t) for t in M[1:, 0]) 

140 

141 if iszerofunc(M[0, 0]): 

142 return zeros_below and _is_echelon(M[:, 1:], iszerofunc) 

143 

144 return zeros_below and _is_echelon(M[1:, 1:], iszerofunc) 

145 

146 

147def _echelon_form(M, iszerofunc=_iszero, simplify=False, with_pivots=False): 

148 """Returns a matrix row-equivalent to ``M`` that is in echelon form. Note 

149 that echelon form of a matrix is *not* unique, however, properties like the 

150 row space and the null space are preserved. 

151 

152 Examples 

153 ======== 

154 

155 >>> from sympy import Matrix 

156 >>> M = Matrix([[1, 2], [3, 4]]) 

157 >>> M.echelon_form() 

158 Matrix([ 

159 [1, 2], 

160 [0, -2]]) 

161 """ 

162 

163 simpfunc = simplify if isinstance(simplify, FunctionType) else _simplify 

164 

165 mat, pivots, _ = _row_reduce(M, iszerofunc, simpfunc, 

166 normalize_last=True, normalize=False, zero_above=False) 

167 

168 if with_pivots: 

169 return mat, pivots 

170 

171 return mat 

172 

173 

174# This functions is a candidate for caching if it gets implemented for matrices. 

175def _rank(M, iszerofunc=_iszero, simplify=False): 

176 """Returns the rank of a matrix. 

177 

178 Examples 

179 ======== 

180 

181 >>> from sympy import Matrix 

182 >>> from sympy.abc import x 

183 >>> m = Matrix([[1, 2], [x, 1 - 1/x]]) 

184 >>> m.rank() 

185 2 

186 >>> n = Matrix(3, 3, range(1, 10)) 

187 >>> n.rank() 

188 2 

189 """ 

190 

191 def _permute_complexity_right(M, iszerofunc): 

192 """Permute columns with complicated elements as 

193 far right as they can go. Since the ``sympy`` row reduction 

194 algorithms start on the left, having complexity right-shifted 

195 speeds things up. 

196 

197 Returns a tuple (mat, perm) where perm is a permutation 

198 of the columns to perform to shift the complex columns right, and mat 

199 is the permuted matrix.""" 

200 

201 def complexity(i): 

202 # the complexity of a column will be judged by how many 

203 # element's zero-ness cannot be determined 

204 return sum(1 if iszerofunc(e) is None else 0 for e in M[:, i]) 

205 

206 complex = [(complexity(i), i) for i in range(M.cols)] 

207 perm = [j for (i, j) in sorted(complex)] 

208 

209 return (M.permute(perm, orientation='cols'), perm) 

210 

211 simpfunc = simplify if isinstance(simplify, FunctionType) else _simplify 

212 

213 # for small matrices, we compute the rank explicitly 

214 # if is_zero on elements doesn't answer the question 

215 # for small matrices, we fall back to the full routine. 

216 if M.rows <= 0 or M.cols <= 0: 

217 return 0 

218 

219 if M.rows <= 1 or M.cols <= 1: 

220 zeros = [iszerofunc(x) for x in M] 

221 

222 if False in zeros: 

223 return 1 

224 

225 if M.rows == 2 and M.cols == 2: 

226 zeros = [iszerofunc(x) for x in M] 

227 

228 if False not in zeros and None not in zeros: 

229 return 0 

230 

231 d = M.det() 

232 

233 if iszerofunc(d) and False in zeros: 

234 return 1 

235 if iszerofunc(d) is False: 

236 return 2 

237 

238 mat, _ = _permute_complexity_right(M, iszerofunc=iszerofunc) 

239 _, pivots, _ = _row_reduce(mat, iszerofunc, simpfunc, normalize_last=True, 

240 normalize=False, zero_above=False) 

241 

242 return len(pivots) 

243 

244 

245def _rref(M, iszerofunc=_iszero, simplify=False, pivots=True, 

246 normalize_last=True): 

247 """Return reduced row-echelon form of matrix and indices of pivot vars. 

248 

249 Parameters 

250 ========== 

251 

252 iszerofunc : Function 

253 A function used for detecting whether an element can 

254 act as a pivot. ``lambda x: x.is_zero`` is used by default. 

255 

256 simplify : Function 

257 A function used to simplify elements when looking for a pivot. 

258 By default SymPy's ``simplify`` is used. 

259 

260 pivots : True or False 

261 If ``True``, a tuple containing the row-reduced matrix and a tuple 

262 of pivot columns is returned. If ``False`` just the row-reduced 

263 matrix is returned. 

264 

265 normalize_last : True or False 

266 If ``True``, no pivots are normalized to `1` until after all 

267 entries above and below each pivot are zeroed. This means the row 

268 reduction algorithm is fraction free until the very last step. 

269 If ``False``, the naive row reduction procedure is used where 

270 each pivot is normalized to be `1` before row operations are 

271 used to zero above and below the pivot. 

272 

273 Examples 

274 ======== 

275 

276 >>> from sympy import Matrix 

277 >>> from sympy.abc import x 

278 >>> m = Matrix([[1, 2], [x, 1 - 1/x]]) 

279 >>> m.rref() 

280 (Matrix([ 

281 [1, 0], 

282 [0, 1]]), (0, 1)) 

283 >>> rref_matrix, rref_pivots = m.rref() 

284 >>> rref_matrix 

285 Matrix([ 

286 [1, 0], 

287 [0, 1]]) 

288 >>> rref_pivots 

289 (0, 1) 

290 

291 ``iszerofunc`` can correct rounding errors in matrices with float 

292 values. In the following example, calling ``rref()`` leads to 

293 floating point errors, incorrectly row reducing the matrix. 

294 ``iszerofunc= lambda x: abs(x)<1e-9`` sets sufficiently small numbers 

295 to zero, avoiding this error. 

296 

297 >>> m = Matrix([[0.9, -0.1, -0.2, 0], [-0.8, 0.9, -0.4, 0], [-0.1, -0.8, 0.6, 0]]) 

298 >>> m.rref() 

299 (Matrix([ 

300 [1, 0, 0, 0], 

301 [0, 1, 0, 0], 

302 [0, 0, 1, 0]]), (0, 1, 2)) 

303 >>> m.rref(iszerofunc=lambda x:abs(x)<1e-9) 

304 (Matrix([ 

305 [1, 0, -0.301369863013699, 0], 

306 [0, 1, -0.712328767123288, 0], 

307 [0, 0, 0, 0]]), (0, 1)) 

308 

309 Notes 

310 ===== 

311 

312 The default value of ``normalize_last=True`` can provide significant 

313 speedup to row reduction, especially on matrices with symbols. However, 

314 if you depend on the form row reduction algorithm leaves entries 

315 of the matrix, set ``noramlize_last=False`` 

316 """ 

317 

318 simpfunc = simplify if isinstance(simplify, FunctionType) else _simplify 

319 

320 mat, pivot_cols, _ = _row_reduce(M, iszerofunc, simpfunc, 

321 normalize_last, normalize=True, zero_above=True) 

322 

323 if pivots: 

324 mat = (mat, pivot_cols) 

325 

326 return mat