Coverage for /usr/lib/python3/dist-packages/sympy/matrices/determinant.py: 11%

239 statements  

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

1from types import FunctionType 

2 

3from sympy.core.numbers import Float, Integer 

4from sympy.core.singleton import S 

5from sympy.core.symbol import uniquely_named_symbol 

6from sympy.core.mul import Mul 

7from sympy.polys import PurePoly, cancel 

8from sympy.functions.combinatorial.numbers import nC 

9from sympy.polys.matrices.domainmatrix import DomainMatrix 

10 

11from .common import NonSquareMatrixError 

12from .utilities import ( 

13 _get_intermediate_simp, _get_intermediate_simp_bool, 

14 _iszero, _is_zero_after_expand_mul, _dotprodsimp, _simplify) 

15 

16 

17def _find_reasonable_pivot(col, iszerofunc=_iszero, simpfunc=_simplify): 

18 """ Find the lowest index of an item in ``col`` that is 

19 suitable for a pivot. If ``col`` consists only of 

20 Floats, the pivot with the largest norm is returned. 

21 Otherwise, the first element where ``iszerofunc`` returns 

22 False is used. If ``iszerofunc`` does not return false, 

23 items are simplified and retested until a suitable 

24 pivot is found. 

25 

26 Returns a 4-tuple 

27 (pivot_offset, pivot_val, assumed_nonzero, newly_determined) 

28 where pivot_offset is the index of the pivot, pivot_val is 

29 the (possibly simplified) value of the pivot, assumed_nonzero 

30 is True if an assumption that the pivot was non-zero 

31 was made without being proved, and newly_determined are 

32 elements that were simplified during the process of pivot 

33 finding.""" 

34 

35 newly_determined = [] 

36 col = list(col) 

37 # a column that contains a mix of floats and integers 

38 # but at least one float is considered a numerical 

39 # column, and so we do partial pivoting 

40 if all(isinstance(x, (Float, Integer)) for x in col) and any( 

41 isinstance(x, Float) for x in col): 

42 col_abs = [abs(x) for x in col] 

43 max_value = max(col_abs) 

44 if iszerofunc(max_value): 

45 # just because iszerofunc returned True, doesn't 

46 # mean the value is numerically zero. Make sure 

47 # to replace all entries with numerical zeros 

48 if max_value != 0: 

49 newly_determined = [(i, 0) for i, x in enumerate(col) if x != 0] 

50 return (None, None, False, newly_determined) 

51 index = col_abs.index(max_value) 

52 return (index, col[index], False, newly_determined) 

53 

54 # PASS 1 (iszerofunc directly) 

55 possible_zeros = [] 

56 for i, x in enumerate(col): 

57 is_zero = iszerofunc(x) 

58 # is someone wrote a custom iszerofunc, it may return 

59 # BooleanFalse or BooleanTrue instead of True or False, 

60 # so use == for comparison instead of `is` 

61 if is_zero == False: 

62 # we found something that is definitely not zero 

63 return (i, x, False, newly_determined) 

64 possible_zeros.append(is_zero) 

65 

66 # by this point, we've found no certain non-zeros 

67 if all(possible_zeros): 

68 # if everything is definitely zero, we have 

69 # no pivot 

70 return (None, None, False, newly_determined) 

71 

72 # PASS 2 (iszerofunc after simplify) 

73 # we haven't found any for-sure non-zeros, so 

74 # go through the elements iszerofunc couldn't 

75 # make a determination about and opportunistically 

76 # simplify to see if we find something 

77 for i, x in enumerate(col): 

78 if possible_zeros[i] is not None: 

79 continue 

80 simped = simpfunc(x) 

81 is_zero = iszerofunc(simped) 

82 if is_zero in (True, False): 

83 newly_determined.append((i, simped)) 

84 if is_zero == False: 

85 return (i, simped, False, newly_determined) 

86 possible_zeros[i] = is_zero 

87 

88 # after simplifying, some things that were recognized 

89 # as zeros might be zeros 

90 if all(possible_zeros): 

91 # if everything is definitely zero, we have 

92 # no pivot 

93 return (None, None, False, newly_determined) 

94 

95 # PASS 3 (.equals(0)) 

96 # some expressions fail to simplify to zero, but 

97 # ``.equals(0)`` evaluates to True. As a last-ditch 

98 # attempt, apply ``.equals`` to these expressions 

99 for i, x in enumerate(col): 

100 if possible_zeros[i] is not None: 

101 continue 

102 if x.equals(S.Zero): 

103 # ``.iszero`` may return False with 

104 # an implicit assumption (e.g., ``x.equals(0)`` 

105 # when ``x`` is a symbol), so only treat it 

106 # as proved when ``.equals(0)`` returns True 

107 possible_zeros[i] = True 

108 newly_determined.append((i, S.Zero)) 

109 

110 if all(possible_zeros): 

111 return (None, None, False, newly_determined) 

112 

113 # at this point there is nothing that could definitely 

114 # be a pivot. To maintain compatibility with existing 

115 # behavior, we'll assume that an illdetermined thing is 

116 # non-zero. We should probably raise a warning in this case 

117 i = possible_zeros.index(None) 

118 return (i, col[i], True, newly_determined) 

119 

120 

121def _find_reasonable_pivot_naive(col, iszerofunc=_iszero, simpfunc=None): 

122 """ 

123 Helper that computes the pivot value and location from a 

124 sequence of contiguous matrix column elements. As a side effect 

125 of the pivot search, this function may simplify some of the elements 

126 of the input column. A list of these simplified entries and their 

127 indices are also returned. 

128 This function mimics the behavior of _find_reasonable_pivot(), 

129 but does less work trying to determine if an indeterminate candidate 

130 pivot simplifies to zero. This more naive approach can be much faster, 

131 with the trade-off that it may erroneously return a pivot that is zero. 

132 

133 ``col`` is a sequence of contiguous column entries to be searched for 

134 a suitable pivot. 

135 ``iszerofunc`` is a callable that returns a Boolean that indicates 

136 if its input is zero, or None if no such determination can be made. 

137 ``simpfunc`` is a callable that simplifies its input. It must return 

138 its input if it does not simplify its input. Passing in 

139 ``simpfunc=None`` indicates that the pivot search should not attempt 

140 to simplify any candidate pivots. 

141 

142 Returns a 4-tuple: 

143 (pivot_offset, pivot_val, assumed_nonzero, newly_determined) 

144 ``pivot_offset`` is the sequence index of the pivot. 

145 ``pivot_val`` is the value of the pivot. 

146 pivot_val and col[pivot_index] are equivalent, but will be different 

147 when col[pivot_index] was simplified during the pivot search. 

148 ``assumed_nonzero`` is a boolean indicating if the pivot cannot be 

149 guaranteed to be zero. If assumed_nonzero is true, then the pivot 

150 may or may not be non-zero. If assumed_nonzero is false, then 

151 the pivot is non-zero. 

152 ``newly_determined`` is a list of index-value pairs of pivot candidates 

153 that were simplified during the pivot search. 

154 """ 

155 

156 # indeterminates holds the index-value pairs of each pivot candidate 

157 # that is neither zero or non-zero, as determined by iszerofunc(). 

158 # If iszerofunc() indicates that a candidate pivot is guaranteed 

159 # non-zero, or that every candidate pivot is zero then the contents 

160 # of indeterminates are unused. 

161 # Otherwise, the only viable candidate pivots are symbolic. 

162 # In this case, indeterminates will have at least one entry, 

163 # and all but the first entry are ignored when simpfunc is None. 

164 indeterminates = [] 

165 for i, col_val in enumerate(col): 

166 col_val_is_zero = iszerofunc(col_val) 

167 if col_val_is_zero == False: 

168 # This pivot candidate is non-zero. 

169 return i, col_val, False, [] 

170 elif col_val_is_zero is None: 

171 # The candidate pivot's comparison with zero 

172 # is indeterminate. 

173 indeterminates.append((i, col_val)) 

174 

175 if len(indeterminates) == 0: 

176 # All candidate pivots are guaranteed to be zero, i.e. there is 

177 # no pivot. 

178 return None, None, False, [] 

179 

180 if simpfunc is None: 

181 # Caller did not pass in a simplification function that might 

182 # determine if an indeterminate pivot candidate is guaranteed 

183 # to be nonzero, so assume the first indeterminate candidate 

184 # is non-zero. 

185 return indeterminates[0][0], indeterminates[0][1], True, [] 

186 

187 # newly_determined holds index-value pairs of candidate pivots 

188 # that were simplified during the search for a non-zero pivot. 

189 newly_determined = [] 

190 for i, col_val in indeterminates: 

191 tmp_col_val = simpfunc(col_val) 

192 if id(col_val) != id(tmp_col_val): 

193 # simpfunc() simplified this candidate pivot. 

194 newly_determined.append((i, tmp_col_val)) 

195 if iszerofunc(tmp_col_val) == False: 

196 # Candidate pivot simplified to a guaranteed non-zero value. 

197 return i, tmp_col_val, False, newly_determined 

198 

199 return indeterminates[0][0], indeterminates[0][1], True, newly_determined 

200 

201 

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

203def _berkowitz_toeplitz_matrix(M): 

204 """Return (A,T) where T the Toeplitz matrix used in the Berkowitz algorithm 

205 corresponding to ``M`` and A is the first principal submatrix. 

206 """ 

207 

208 # the 0 x 0 case is trivial 

209 if M.rows == 0 and M.cols == 0: 

210 return M._new(1,1, [M.one]) 

211 

212 # 

213 # Partition M = [ a_11 R ] 

214 # [ C A ] 

215 # 

216 

217 a, R = M[0,0], M[0, 1:] 

218 C, A = M[1:, 0], M[1:,1:] 

219 

220 # 

221 # The Toeplitz matrix looks like 

222 # 

223 # [ 1 ] 

224 # [ -a 1 ] 

225 # [ -RC -a 1 ] 

226 # [ -RAC -RC -a 1 ] 

227 # [ -RA**2C -RAC -RC -a 1 ] 

228 # etc. 

229 

230 # Compute the diagonal entries. 

231 # Because multiplying matrix times vector is so much 

232 # more efficient than matrix times matrix, recursively 

233 # compute -R * A**n * C. 

234 diags = [C] 

235 for i in range(M.rows - 2): 

236 diags.append(A.multiply(diags[i], dotprodsimp=None)) 

237 diags = [(-R).multiply(d, dotprodsimp=None)[0, 0] for d in diags] 

238 diags = [M.one, -a] + diags 

239 

240 def entry(i,j): 

241 if j > i: 

242 return M.zero 

243 return diags[i - j] 

244 

245 toeplitz = M._new(M.cols + 1, M.rows, entry) 

246 return (A, toeplitz) 

247 

248 

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

250def _berkowitz_vector(M): 

251 """ Run the Berkowitz algorithm and return a vector whose entries 

252 are the coefficients of the characteristic polynomial of ``M``. 

253 

254 Given N x N matrix, efficiently compute 

255 coefficients of characteristic polynomials of ``M`` 

256 without division in the ground domain. 

257 

258 This method is particularly useful for computing determinant, 

259 principal minors and characteristic polynomial when ``M`` 

260 has complicated coefficients e.g. polynomials. Semi-direct 

261 usage of this algorithm is also important in computing 

262 efficiently sub-resultant PRS. 

263 

264 Assuming that M is a square matrix of dimension N x N and 

265 I is N x N identity matrix, then the Berkowitz vector is 

266 an N x 1 vector whose entries are coefficients of the 

267 polynomial 

268 

269 charpoly(M) = det(t*I - M) 

270 

271 As a consequence, all polynomials generated by Berkowitz 

272 algorithm are monic. 

273 

274 For more information on the implemented algorithm refer to: 

275 

276 [1] S.J. Berkowitz, On computing the determinant in small 

277 parallel time using a small number of processors, ACM, 

278 Information Processing Letters 18, 1984, pp. 147-150 

279 

280 [2] M. Keber, Division-Free computation of sub-resultants 

281 using Bezout matrices, Tech. Report MPI-I-2006-1-006, 

282 Saarbrucken, 2006 

283 """ 

284 

285 # handle the trivial cases 

286 if M.rows == 0 and M.cols == 0: 

287 return M._new(1, 1, [M.one]) 

288 elif M.rows == 1 and M.cols == 1: 

289 return M._new(2, 1, [M.one, -M[0,0]]) 

290 

291 submat, toeplitz = _berkowitz_toeplitz_matrix(M) 

292 

293 return toeplitz.multiply(_berkowitz_vector(submat), dotprodsimp=None) 

294 

295 

296def _adjugate(M, method="berkowitz"): 

297 """Returns the adjugate, or classical adjoint, of 

298 a matrix. That is, the transpose of the matrix of cofactors. 

299 

300 https://en.wikipedia.org/wiki/Adjugate 

301 

302 Parameters 

303 ========== 

304 

305 method : string, optional 

306 Method to use to find the cofactors, can be "bareiss", "berkowitz" or 

307 "lu". 

308 

309 Examples 

310 ======== 

311 

312 >>> from sympy import Matrix 

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

314 >>> M.adjugate() 

315 Matrix([ 

316 [ 4, -2], 

317 [-3, 1]]) 

318 

319 See Also 

320 ======== 

321 

322 cofactor_matrix 

323 sympy.matrices.common.MatrixCommon.transpose 

324 """ 

325 

326 return M.cofactor_matrix(method=method).transpose() 

327 

328 

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

330def _charpoly(M, x='lambda', simplify=_simplify): 

331 """Computes characteristic polynomial det(x*I - M) where I is 

332 the identity matrix. 

333 

334 A PurePoly is returned, so using different variables for ``x`` does 

335 not affect the comparison or the polynomials: 

336 

337 Parameters 

338 ========== 

339 

340 x : string, optional 

341 Name for the "lambda" variable, defaults to "lambda". 

342 

343 simplify : function, optional 

344 Simplification function to use on the characteristic polynomial 

345 calculated. Defaults to ``simplify``. 

346 

347 Examples 

348 ======== 

349 

350 >>> from sympy import Matrix 

351 >>> from sympy.abc import x, y 

352 >>> M = Matrix([[1, 3], [2, 0]]) 

353 >>> M.charpoly() 

354 PurePoly(lambda**2 - lambda - 6, lambda, domain='ZZ') 

355 >>> M.charpoly(x) == M.charpoly(y) 

356 True 

357 >>> M.charpoly(x) == M.charpoly(y) 

358 True 

359 

360 Specifying ``x`` is optional; a symbol named ``lambda`` is used by 

361 default (which looks good when pretty-printed in unicode): 

362 

363 >>> M.charpoly().as_expr() 

364 lambda**2 - lambda - 6 

365 

366 And if ``x`` clashes with an existing symbol, underscores will 

367 be prepended to the name to make it unique: 

368 

369 >>> M = Matrix([[1, 2], [x, 0]]) 

370 >>> M.charpoly(x).as_expr() 

371 _x**2 - _x - 2*x 

372 

373 Whether you pass a symbol or not, the generator can be obtained 

374 with the gen attribute since it may not be the same as the symbol 

375 that was passed: 

376 

377 >>> M.charpoly(x).gen 

378 _x 

379 >>> M.charpoly(x).gen == x 

380 False 

381 

382 Notes 

383 ===== 

384 

385 The Samuelson-Berkowitz algorithm is used to compute 

386 the characteristic polynomial efficiently and without any 

387 division operations. Thus the characteristic polynomial over any 

388 commutative ring without zero divisors can be computed. 

389 

390 If the determinant det(x*I - M) can be found out easily as 

391 in the case of an upper or a lower triangular matrix, then 

392 instead of Samuelson-Berkowitz algorithm, eigenvalues are computed 

393 and the characteristic polynomial with their help. 

394 

395 See Also 

396 ======== 

397 

398 det 

399 """ 

400 

401 if not M.is_square: 

402 raise NonSquareMatrixError() 

403 if M.is_lower or M.is_upper: 

404 diagonal_elements = M.diagonal() 

405 x = uniquely_named_symbol(x, diagonal_elements, modify=lambda s: '_' + s) 

406 m = 1 

407 for i in diagonal_elements: 

408 m = m * (x - simplify(i)) 

409 return PurePoly(m, x) 

410 

411 berk_vector = _berkowitz_vector(M) 

412 x = uniquely_named_symbol(x, berk_vector, modify=lambda s: '_' + s) 

413 

414 return PurePoly([simplify(a) for a in berk_vector], x) 

415 

416 

417def _cofactor(M, i, j, method="berkowitz"): 

418 """Calculate the cofactor of an element. 

419 

420 Parameters 

421 ========== 

422 

423 method : string, optional 

424 Method to use to find the cofactors, can be "bareiss", "berkowitz" or 

425 "lu". 

426 

427 Examples 

428 ======== 

429 

430 >>> from sympy import Matrix 

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

432 >>> M.cofactor(0, 1) 

433 -3 

434 

435 See Also 

436 ======== 

437 

438 cofactor_matrix 

439 minor 

440 minor_submatrix 

441 """ 

442 

443 if not M.is_square or M.rows < 1: 

444 raise NonSquareMatrixError() 

445 

446 return S.NegativeOne**((i + j) % 2) * M.minor(i, j, method) 

447 

448 

449def _cofactor_matrix(M, method="berkowitz"): 

450 """Return a matrix containing the cofactor of each element. 

451 

452 Parameters 

453 ========== 

454 

455 method : string, optional 

456 Method to use to find the cofactors, can be "bareiss", "berkowitz" or 

457 "lu". 

458 

459 Examples 

460 ======== 

461 

462 >>> from sympy import Matrix 

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

464 >>> M.cofactor_matrix() 

465 Matrix([ 

466 [ 4, -3], 

467 [-2, 1]]) 

468 

469 See Also 

470 ======== 

471 

472 cofactor 

473 minor 

474 minor_submatrix 

475 """ 

476 

477 if not M.is_square or M.rows < 1: 

478 raise NonSquareMatrixError() 

479 

480 return M._new(M.rows, M.cols, 

481 lambda i, j: M.cofactor(i, j, method)) 

482 

483def _per(M): 

484 """Returns the permanent of a matrix. Unlike determinant, 

485 permanent is defined for both square and non-square matrices. 

486 

487 For an m x n matrix, with m less than or equal to n, 

488 it is given as the sum over the permutations s of size 

489 less than or equal to m on [1, 2, . . . n] of the product 

490 from i = 1 to m of M[i, s[i]]. Taking the transpose will 

491 not affect the value of the permanent. 

492 

493 In the case of a square matrix, this is the same as the permutation 

494 definition of the determinant, but it does not take the sign of the 

495 permutation into account. Computing the permanent with this definition 

496 is quite inefficient, so here the Ryser formula is used. 

497 

498 Examples 

499 ======== 

500 

501 >>> from sympy import Matrix 

502 >>> M = Matrix([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) 

503 >>> M.per() 

504 450 

505 >>> M = Matrix([1, 5, 7]) 

506 >>> M.per() 

507 13 

508 

509 References 

510 ========== 

511 

512 .. [1] Prof. Frank Ben's notes: https://math.berkeley.edu/~bernd/ban275.pdf 

513 .. [2] Wikipedia article on Permanent: https://en.wikipedia.org/wiki/Permanent_%28mathematics%29 

514 .. [3] https://reference.wolfram.com/language/ref/Permanent.html 

515 .. [4] Permanent of a rectangular matrix : https://arxiv.org/pdf/0904.3251.pdf 

516 """ 

517 import itertools 

518 

519 m, n = M.shape 

520 if m > n: 

521 M = M.T 

522 m, n = n, m 

523 s = list(range(n)) 

524 

525 subsets = [] 

526 for i in range(1, m + 1): 

527 subsets += list(map(list, itertools.combinations(s, i))) 

528 

529 perm = 0 

530 for subset in subsets: 

531 prod = 1 

532 sub_len = len(subset) 

533 for i in range(m): 

534 prod *= sum([M[i, j] for j in subset]) 

535 perm += prod * S.NegativeOne**sub_len * nC(n - sub_len, m - sub_len) 

536 perm *= S.NegativeOne**m 

537 return perm.simplify() 

538 

539def _det_DOM(M): 

540 DOM = DomainMatrix.from_Matrix(M, field=True, extension=True) 

541 K = DOM.domain 

542 return K.to_sympy(DOM.det()) 

543 

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

545def _det(M, method="bareiss", iszerofunc=None): 

546 """Computes the determinant of a matrix if ``M`` is a concrete matrix object 

547 otherwise return an expressions ``Determinant(M)`` if ``M`` is a 

548 ``MatrixSymbol`` or other expression. 

549 

550 Parameters 

551 ========== 

552 

553 method : string, optional 

554 Specifies the algorithm used for computing the matrix determinant. 

555 

556 If the matrix is at most 3x3, a hard-coded formula is used and the 

557 specified method is ignored. Otherwise, it defaults to 

558 ``'bareiss'``. 

559 

560 Also, if the matrix is an upper or a lower triangular matrix, determinant 

561 is computed by simple multiplication of diagonal elements, and the 

562 specified method is ignored. 

563 

564 If it is set to ``'domain-ge'``, then Gaussian elimination method will 

565 be used via using DomainMatrix. 

566 

567 If it is set to ``'bareiss'``, Bareiss' fraction-free algorithm will 

568 be used. 

569 

570 If it is set to ``'berkowitz'``, Berkowitz' algorithm will be used. 

571 

572 Otherwise, if it is set to ``'lu'``, LU decomposition will be used. 

573 

574 .. note:: 

575 For backward compatibility, legacy keys like "bareis" and 

576 "det_lu" can still be used to indicate the corresponding 

577 methods. 

578 And the keys are also case-insensitive for now. However, it is 

579 suggested to use the precise keys for specifying the method. 

580 

581 iszerofunc : FunctionType or None, optional 

582 If it is set to ``None``, it will be defaulted to ``_iszero`` if the 

583 method is set to ``'bareiss'``, and ``_is_zero_after_expand_mul`` if 

584 the method is set to ``'lu'``. 

585 

586 It can also accept any user-specified zero testing function, if it 

587 is formatted as a function which accepts a single symbolic argument 

588 and returns ``True`` if it is tested as zero and ``False`` if it 

589 tested as non-zero, and also ``None`` if it is undecidable. 

590 

591 Returns 

592 ======= 

593 

594 det : Basic 

595 Result of determinant. 

596 

597 Raises 

598 ====== 

599 

600 ValueError 

601 If unrecognized keys are given for ``method`` or ``iszerofunc``. 

602 

603 NonSquareMatrixError 

604 If attempted to calculate determinant from a non-square matrix. 

605 

606 Examples 

607 ======== 

608 

609 >>> from sympy import Matrix, eye, det 

610 >>> I3 = eye(3) 

611 >>> det(I3) 

612 1 

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

614 >>> det(M) 

615 -2 

616 >>> det(M) == M.det() 

617 True 

618 >>> M.det(method="domain-ge") 

619 -2 

620 """ 

621 

622 # sanitize `method` 

623 method = method.lower() 

624 

625 if method == "bareis": 

626 method = "bareiss" 

627 elif method == "det_lu": 

628 method = "lu" 

629 

630 if method not in ("bareiss", "berkowitz", "lu", "domain-ge"): 

631 raise ValueError("Determinant method '%s' unrecognized" % method) 

632 

633 if iszerofunc is None: 

634 if method == "bareiss": 

635 iszerofunc = _is_zero_after_expand_mul 

636 elif method == "lu": 

637 iszerofunc = _iszero 

638 

639 elif not isinstance(iszerofunc, FunctionType): 

640 raise ValueError("Zero testing method '%s' unrecognized" % iszerofunc) 

641 

642 n = M.rows 

643 

644 if n == M.cols: # square check is done in individual method functions 

645 if n == 0: 

646 return M.one 

647 elif n == 1: 

648 return M[0, 0] 

649 elif n == 2: 

650 m = M[0, 0] * M[1, 1] - M[0, 1] * M[1, 0] 

651 return _get_intermediate_simp(_dotprodsimp)(m) 

652 elif n == 3: 

653 m = (M[0, 0] * M[1, 1] * M[2, 2] 

654 + M[0, 1] * M[1, 2] * M[2, 0] 

655 + M[0, 2] * M[1, 0] * M[2, 1] 

656 - M[0, 2] * M[1, 1] * M[2, 0] 

657 - M[0, 0] * M[1, 2] * M[2, 1] 

658 - M[0, 1] * M[1, 0] * M[2, 2]) 

659 return _get_intermediate_simp(_dotprodsimp)(m) 

660 

661 dets = [] 

662 for b in M.strongly_connected_components(): 

663 if method == "domain-ge": # uses DomainMatrix to evaluate determinant 

664 det = _det_DOM(M[b, b]) 

665 elif method == "bareiss": 

666 det = M[b, b]._eval_det_bareiss(iszerofunc=iszerofunc) 

667 elif method == "berkowitz": 

668 det = M[b, b]._eval_det_berkowitz() 

669 elif method == "lu": 

670 det = M[b, b]._eval_det_lu(iszerofunc=iszerofunc) 

671 dets.append(det) 

672 return Mul(*dets) 

673 

674 

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

676def _det_bareiss(M, iszerofunc=_is_zero_after_expand_mul): 

677 """Compute matrix determinant using Bareiss' fraction-free 

678 algorithm which is an extension of the well known Gaussian 

679 elimination method. This approach is best suited for dense 

680 symbolic matrices and will result in a determinant with 

681 minimal number of fractions. It means that less term 

682 rewriting is needed on resulting formulae. 

683 

684 Parameters 

685 ========== 

686 

687 iszerofunc : function, optional 

688 The function to use to determine zeros when doing an LU decomposition. 

689 Defaults to ``lambda x: x.is_zero``. 

690 

691 TODO: Implement algorithm for sparse matrices (SFF), 

692 http://www.eecis.udel.edu/~saunders/papers/sffge/it5.ps. 

693 """ 

694 

695 # Recursively implemented Bareiss' algorithm as per Deanna Richelle Leggett's 

696 # thesis http://www.math.usm.edu/perry/Research/Thesis_DRL.pdf 

697 def bareiss(mat, cumm=1): 

698 if mat.rows == 0: 

699 return mat.one 

700 elif mat.rows == 1: 

701 return mat[0, 0] 

702 

703 # find a pivot and extract the remaining matrix 

704 # With the default iszerofunc, _find_reasonable_pivot slows down 

705 # the computation by the factor of 2.5 in one test. 

706 # Relevant issues: #10279 and #13877. 

707 pivot_pos, pivot_val, _, _ = _find_reasonable_pivot(mat[:, 0], iszerofunc=iszerofunc) 

708 if pivot_pos is None: 

709 return mat.zero 

710 

711 # if we have a valid pivot, we'll do a "row swap", so keep the 

712 # sign of the det 

713 sign = (-1) ** (pivot_pos % 2) 

714 

715 # we want every row but the pivot row and every column 

716 rows = [i for i in range(mat.rows) if i != pivot_pos] 

717 cols = list(range(mat.cols)) 

718 tmp_mat = mat.extract(rows, cols) 

719 

720 def entry(i, j): 

721 ret = (pivot_val*tmp_mat[i, j + 1] - mat[pivot_pos, j + 1]*tmp_mat[i, 0]) / cumm 

722 if _get_intermediate_simp_bool(True): 

723 return _dotprodsimp(ret) 

724 elif not ret.is_Atom: 

725 return cancel(ret) 

726 return ret 

727 

728 return sign*bareiss(M._new(mat.rows - 1, mat.cols - 1, entry), pivot_val) 

729 

730 if not M.is_square: 

731 raise NonSquareMatrixError() 

732 

733 if M.rows == 0: 

734 return M.one 

735 # sympy/matrices/tests/test_matrices.py contains a test that 

736 # suggests that the determinant of a 0 x 0 matrix is one, by 

737 # convention. 

738 

739 return bareiss(M) 

740 

741 

742def _det_berkowitz(M): 

743 """ Use the Berkowitz algorithm to compute the determinant.""" 

744 

745 if not M.is_square: 

746 raise NonSquareMatrixError() 

747 

748 if M.rows == 0: 

749 return M.one 

750 # sympy/matrices/tests/test_matrices.py contains a test that 

751 # suggests that the determinant of a 0 x 0 matrix is one, by 

752 # convention. 

753 

754 berk_vector = _berkowitz_vector(M) 

755 return (-1)**(len(berk_vector) - 1) * berk_vector[-1] 

756 

757 

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

759def _det_LU(M, iszerofunc=_iszero, simpfunc=None): 

760 """ Computes the determinant of a matrix from its LU decomposition. 

761 This function uses the LU decomposition computed by 

762 LUDecomposition_Simple(). 

763 

764 The keyword arguments iszerofunc and simpfunc are passed to 

765 LUDecomposition_Simple(). 

766 iszerofunc is a callable that returns a boolean indicating if its 

767 input is zero, or None if it cannot make the determination. 

768 simpfunc is a callable that simplifies its input. 

769 The default is simpfunc=None, which indicate that the pivot search 

770 algorithm should not attempt to simplify any candidate pivots. 

771 If simpfunc fails to simplify its input, then it must return its input 

772 instead of a copy. 

773 

774 Parameters 

775 ========== 

776 

777 iszerofunc : function, optional 

778 The function to use to determine zeros when doing an LU decomposition. 

779 Defaults to ``lambda x: x.is_zero``. 

780 

781 simpfunc : function, optional 

782 The simplification function to use when looking for zeros for pivots. 

783 """ 

784 

785 if not M.is_square: 

786 raise NonSquareMatrixError() 

787 

788 if M.rows == 0: 

789 return M.one 

790 # sympy/matrices/tests/test_matrices.py contains a test that 

791 # suggests that the determinant of a 0 x 0 matrix is one, by 

792 # convention. 

793 

794 lu, row_swaps = M.LUdecomposition_Simple(iszerofunc=iszerofunc, 

795 simpfunc=simpfunc) 

796 # P*A = L*U => det(A) = det(L)*det(U)/det(P) = det(P)*det(U). 

797 # Lower triangular factor L encoded in lu has unit diagonal => det(L) = 1. 

798 # P is a permutation matrix => det(P) in {-1, 1} => 1/det(P) = det(P). 

799 # LUdecomposition_Simple() returns a list of row exchange index pairs, rather 

800 # than a permutation matrix, but det(P) = (-1)**len(row_swaps). 

801 

802 # Avoid forming the potentially time consuming product of U's diagonal entries 

803 # if the product is zero. 

804 # Bottom right entry of U is 0 => det(A) = 0. 

805 # It may be impossible to determine if this entry of U is zero when it is symbolic. 

806 if iszerofunc(lu[lu.rows-1, lu.rows-1]): 

807 return M.zero 

808 

809 # Compute det(P) 

810 det = -M.one if len(row_swaps)%2 else M.one 

811 

812 # Compute det(U) by calculating the product of U's diagonal entries. 

813 # The upper triangular portion of lu is the upper triangular portion of the 

814 # U factor in the LU decomposition. 

815 for k in range(lu.rows): 

816 det *= lu[k, k] 

817 

818 # return det(P)*det(U) 

819 return det 

820 

821 

822def _minor(M, i, j, method="berkowitz"): 

823 """Return the (i,j) minor of ``M``. That is, 

824 return the determinant of the matrix obtained by deleting 

825 the `i`th row and `j`th column from ``M``. 

826 

827 Parameters 

828 ========== 

829 

830 i, j : int 

831 The row and column to exclude to obtain the submatrix. 

832 

833 method : string, optional 

834 Method to use to find the determinant of the submatrix, can be 

835 "bareiss", "berkowitz" or "lu". 

836 

837 Examples 

838 ======== 

839 

840 >>> from sympy import Matrix 

841 >>> M = Matrix([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) 

842 >>> M.minor(1, 1) 

843 -12 

844 

845 See Also 

846 ======== 

847 

848 minor_submatrix 

849 cofactor 

850 det 

851 """ 

852 

853 if not M.is_square: 

854 raise NonSquareMatrixError() 

855 

856 return M.minor_submatrix(i, j).det(method=method) 

857 

858 

859def _minor_submatrix(M, i, j): 

860 """Return the submatrix obtained by removing the `i`th row 

861 and `j`th column from ``M`` (works with Pythonic negative indices). 

862 

863 Parameters 

864 ========== 

865 

866 i, j : int 

867 The row and column to exclude to obtain the submatrix. 

868 

869 Examples 

870 ======== 

871 

872 >>> from sympy import Matrix 

873 >>> M = Matrix([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) 

874 >>> M.minor_submatrix(1, 1) 

875 Matrix([ 

876 [1, 3], 

877 [7, 9]]) 

878 

879 See Also 

880 ======== 

881 

882 minor 

883 cofactor 

884 """ 

885 

886 if i < 0: 

887 i += M.rows 

888 if j < 0: 

889 j += M.cols 

890 

891 if not 0 <= i < M.rows or not 0 <= j < M.cols: 

892 raise ValueError("`i` and `j` must satisfy 0 <= i < ``M.rows`` " 

893 "(%d)" % M.rows + "and 0 <= j < ``M.cols`` (%d)." % M.cols) 

894 

895 rows = [a for a in range(M.rows) if a != i] 

896 cols = [a for a in range(M.cols) if a != j] 

897 

898 return M.extract(rows, cols)