Coverage for /usr/lib/python3/dist-packages/sympy/polys/matrices/dense.py: 10%

204 statements  

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

1""" 

2 

3Module for the ddm_* routines for operating on a matrix in list of lists 

4matrix representation. 

5 

6These routines are used internally by the DDM class which also provides a 

7friendlier interface for them. The idea here is to implement core matrix 

8routines in a way that can be applied to any simple list representation 

9without the need to use any particular matrix class. For example we can 

10compute the RREF of a matrix like: 

11 

12 >>> from sympy.polys.matrices.dense import ddm_irref 

13 >>> M = [[1, 2, 3], [4, 5, 6]] 

14 >>> pivots = ddm_irref(M) 

15 >>> M 

16 [[1.0, 0.0, -1.0], [0, 1.0, 2.0]] 

17 

18These are lower-level routines that work mostly in place.The routines at this 

19level should not need to know what the domain of the elements is but should 

20ideally document what operations they will use and what functions they need to 

21be provided with. 

22 

23The next-level up is the DDM class which uses these routines but wraps them up 

24with an interface that handles copying etc and keeps track of the Domain of 

25the elements of the matrix: 

26 

27 >>> from sympy.polys.domains import QQ 

28 >>> from sympy.polys.matrices.ddm import DDM 

29 >>> M = DDM([[QQ(1), QQ(2), QQ(3)], [QQ(4), QQ(5), QQ(6)]], (2, 3), QQ) 

30 >>> M 

31 [[1, 2, 3], [4, 5, 6]] 

32 >>> Mrref, pivots = M.rref() 

33 >>> Mrref 

34 [[1, 0, -1], [0, 1, 2]] 

35 

36""" 

37from __future__ import annotations 

38from operator import mul 

39from .exceptions import ( 

40 DMShapeError, 

41 DMNonInvertibleMatrixError, 

42 DMNonSquareMatrixError, 

43) 

44from typing import Sequence, TypeVar 

45from sympy.polys.matrices._typing import RingElement 

46 

47 

48T = TypeVar('T') 

49R = TypeVar('R', bound=RingElement) 

50 

51 

52def ddm_transpose(matrix: Sequence[Sequence[T]]) -> list[list[T]]: 

53 """matrix transpose""" 

54 return list(map(list, zip(*matrix))) 

55 

56 

57def ddm_iadd(a: list[list[R]], b: Sequence[Sequence[R]]) -> None: 

58 """a += b""" 

59 for ai, bi in zip(a, b): 

60 for j, bij in enumerate(bi): 

61 ai[j] += bij 

62 

63 

64def ddm_isub(a: list[list[R]], b: Sequence[Sequence[R]]) -> None: 

65 """a -= b""" 

66 for ai, bi in zip(a, b): 

67 for j, bij in enumerate(bi): 

68 ai[j] -= bij 

69 

70 

71def ddm_ineg(a: list[list[R]]) -> None: 

72 """a <-- -a""" 

73 for ai in a: 

74 for j, aij in enumerate(ai): 

75 ai[j] = -aij 

76 

77 

78def ddm_imul(a: list[list[R]], b: R) -> None: 

79 for ai in a: 

80 for j, aij in enumerate(ai): 

81 ai[j] = aij * b 

82 

83 

84def ddm_irmul(a: list[list[R]], b: R) -> None: 

85 for ai in a: 

86 for j, aij in enumerate(ai): 

87 ai[j] = b * aij 

88 

89 

90def ddm_imatmul( 

91 a: list[list[R]], b: Sequence[Sequence[R]], c: Sequence[Sequence[R]] 

92) -> None: 

93 """a += b @ c""" 

94 cT = list(zip(*c)) 

95 

96 for bi, ai in zip(b, a): 

97 for j, cTj in enumerate(cT): 

98 ai[j] = sum(map(mul, bi, cTj), ai[j]) 

99 

100 

101def ddm_irref(a, _partial_pivot=False): 

102 """a <-- rref(a)""" 

103 # a is (m x n) 

104 m = len(a) 

105 if not m: 

106 return [] 

107 n = len(a[0]) 

108 

109 i = 0 

110 pivots = [] 

111 

112 for j in range(n): 

113 # Proper pivoting should be used for all domains for performance 

114 # reasons but it is only strictly needed for RR and CC (and possibly 

115 # other domains like RR(x)). This path is used by DDM.rref() if the 

116 # domain is RR or CC. It uses partial (row) pivoting based on the 

117 # absolute value of the pivot candidates. 

118 if _partial_pivot: 

119 ip = max(range(i, m), key=lambda ip: abs(a[ip][j])) 

120 a[i], a[ip] = a[ip], a[i] 

121 

122 # pivot 

123 aij = a[i][j] 

124 

125 # zero-pivot 

126 if not aij: 

127 for ip in range(i+1, m): 

128 aij = a[ip][j] 

129 # row-swap 

130 if aij: 

131 a[i], a[ip] = a[ip], a[i] 

132 break 

133 else: 

134 # next column 

135 continue 

136 

137 # normalise row 

138 ai = a[i] 

139 aijinv = aij**-1 

140 for l in range(j, n): 

141 ai[l] *= aijinv # ai[j] = one 

142 

143 # eliminate above and below to the right 

144 for k, ak in enumerate(a): 

145 if k == i or not ak[j]: 

146 continue 

147 akj = ak[j] 

148 ak[j] -= akj # ak[j] = zero 

149 for l in range(j+1, n): 

150 ak[l] -= akj * ai[l] 

151 

152 # next row 

153 pivots.append(j) 

154 i += 1 

155 

156 # no more rows? 

157 if i >= m: 

158 break 

159 

160 return pivots 

161 

162 

163def ddm_idet(a, K): 

164 """a <-- echelon(a); return det""" 

165 # Bareiss algorithm 

166 # https://www.math.usm.edu/perry/Research/Thesis_DRL.pdf 

167 

168 # a is (m x n) 

169 m = len(a) 

170 if not m: 

171 return K.one 

172 n = len(a[0]) 

173 

174 exquo = K.exquo 

175 # uf keeps track of the sign change from row swaps 

176 uf = K.one 

177 

178 for k in range(n-1): 

179 if not a[k][k]: 

180 for i in range(k+1, n): 

181 if a[i][k]: 

182 a[k], a[i] = a[i], a[k] 

183 uf = -uf 

184 break 

185 else: 

186 return K.zero 

187 

188 akkm1 = a[k-1][k-1] if k else K.one 

189 

190 for i in range(k+1, n): 

191 for j in range(k+1, n): 

192 a[i][j] = exquo(a[i][j]*a[k][k] - a[i][k]*a[k][j], akkm1) 

193 

194 return uf * a[-1][-1] 

195 

196 

197def ddm_iinv(ainv, a, K): 

198 if not K.is_Field: 

199 raise ValueError('Not a field') 

200 

201 # a is (m x n) 

202 m = len(a) 

203 if not m: 

204 return 

205 n = len(a[0]) 

206 if m != n: 

207 raise DMNonSquareMatrixError 

208 

209 eye = [[K.one if i==j else K.zero for j in range(n)] for i in range(n)] 

210 Aaug = [row + eyerow for row, eyerow in zip(a, eye)] 

211 pivots = ddm_irref(Aaug) 

212 if pivots != list(range(n)): 

213 raise DMNonInvertibleMatrixError('Matrix det == 0; not invertible.') 

214 ainv[:] = [row[n:] for row in Aaug] 

215 

216 

217def ddm_ilu_split(L, U, K): 

218 """L, U <-- LU(U)""" 

219 m = len(U) 

220 if not m: 

221 return [] 

222 n = len(U[0]) 

223 

224 swaps = ddm_ilu(U) 

225 

226 zeros = [K.zero] * min(m, n) 

227 for i in range(1, m): 

228 j = min(i, n) 

229 L[i][:j] = U[i][:j] 

230 U[i][:j] = zeros[:j] 

231 

232 return swaps 

233 

234 

235def ddm_ilu(a): 

236 """a <-- LU(a)""" 

237 m = len(a) 

238 if not m: 

239 return [] 

240 n = len(a[0]) 

241 

242 swaps = [] 

243 

244 for i in range(min(m, n)): 

245 if not a[i][i]: 

246 for ip in range(i+1, m): 

247 if a[ip][i]: 

248 swaps.append((i, ip)) 

249 a[i], a[ip] = a[ip], a[i] 

250 break 

251 else: 

252 # M = Matrix([[1, 0, 0, 0], [0, 0, 0, 0], [0, 0, 1, 1], [0, 0, 1, 2]]) 

253 continue 

254 for j in range(i+1, m): 

255 l_ji = a[j][i] / a[i][i] 

256 a[j][i] = l_ji 

257 for k in range(i+1, n): 

258 a[j][k] -= l_ji * a[i][k] 

259 

260 return swaps 

261 

262 

263def ddm_ilu_solve(x, L, U, swaps, b): 

264 """x <-- solve(L*U*x = swaps(b))""" 

265 m = len(U) 

266 if not m: 

267 return 

268 n = len(U[0]) 

269 

270 m2 = len(b) 

271 if not m2: 

272 raise DMShapeError("Shape mismtch") 

273 o = len(b[0]) 

274 

275 if m != m2: 

276 raise DMShapeError("Shape mismtch") 

277 if m < n: 

278 raise NotImplementedError("Underdetermined") 

279 

280 if swaps: 

281 b = [row[:] for row in b] 

282 for i1, i2 in swaps: 

283 b[i1], b[i2] = b[i2], b[i1] 

284 

285 # solve Ly = b 

286 y = [[None] * o for _ in range(m)] 

287 for k in range(o): 

288 for i in range(m): 

289 rhs = b[i][k] 

290 for j in range(i): 

291 rhs -= L[i][j] * y[j][k] 

292 y[i][k] = rhs 

293 

294 if m > n: 

295 for i in range(n, m): 

296 for j in range(o): 

297 if y[i][j]: 

298 raise DMNonInvertibleMatrixError 

299 

300 # Solve Ux = y 

301 for k in range(o): 

302 for i in reversed(range(n)): 

303 if not U[i][i]: 

304 raise DMNonInvertibleMatrixError 

305 rhs = y[i][k] 

306 for j in range(i+1, n): 

307 rhs -= U[i][j] * x[j][k] 

308 x[i][k] = rhs / U[i][i] 

309 

310 

311def ddm_berk(M, K): 

312 m = len(M) 

313 if not m: 

314 return [[K.one]] 

315 n = len(M[0]) 

316 

317 if m != n: 

318 raise DMShapeError("Not square") 

319 

320 if n == 1: 

321 return [[K.one], [-M[0][0]]] 

322 

323 a = M[0][0] 

324 R = [M[0][1:]] 

325 C = [[row[0]] for row in M[1:]] 

326 A = [row[1:] for row in M[1:]] 

327 

328 q = ddm_berk(A, K) 

329 

330 T = [[K.zero] * n for _ in range(n+1)] 

331 for i in range(n): 

332 T[i][i] = K.one 

333 T[i+1][i] = -a 

334 for i in range(2, n+1): 

335 if i == 2: 

336 AnC = C 

337 else: 

338 C = AnC 

339 AnC = [[K.zero] for row in C] 

340 ddm_imatmul(AnC, A, C) 

341 RAnC = [[K.zero]] 

342 ddm_imatmul(RAnC, R, AnC) 

343 for j in range(0, n+1-i): 

344 T[i+j][j] = -RAnC[0][0] 

345 

346 qout = [[K.zero] for _ in range(n+1)] 

347 ddm_imatmul(qout, T, q) 

348 return qout