| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021 |
- from types import FunctionType
- from sympy.core.cache import cacheit
- from sympy.core.numbers import Float, Integer
- from sympy.core.singleton import S
- from sympy.core.symbol import uniquely_named_symbol
- from sympy.core.mul import Mul
- from sympy.polys import PurePoly, cancel
- from sympy.functions.combinatorial.numbers import nC
- from sympy.polys.matrices.domainmatrix import DomainMatrix
- from sympy.polys.matrices.ddm import DDM
- from .exceptions import NonSquareMatrixError
- from .utilities import (
- _get_intermediate_simp, _get_intermediate_simp_bool,
- _iszero, _is_zero_after_expand_mul, _dotprodsimp, _simplify)
- def _find_reasonable_pivot(col, iszerofunc=_iszero, simpfunc=_simplify):
- """ Find the lowest index of an item in ``col`` that is
- suitable for a pivot. If ``col`` consists only of
- Floats, the pivot with the largest norm is returned.
- Otherwise, the first element where ``iszerofunc`` returns
- False is used. If ``iszerofunc`` does not return false,
- items are simplified and retested until a suitable
- pivot is found.
- Returns a 4-tuple
- (pivot_offset, pivot_val, assumed_nonzero, newly_determined)
- where pivot_offset is the index of the pivot, pivot_val is
- the (possibly simplified) value of the pivot, assumed_nonzero
- is True if an assumption that the pivot was non-zero
- was made without being proved, and newly_determined are
- elements that were simplified during the process of pivot
- finding."""
- newly_determined = []
- col = list(col)
- # a column that contains a mix of floats and integers
- # but at least one float is considered a numerical
- # column, and so we do partial pivoting
- if all(isinstance(x, (Float, Integer)) for x in col) and any(
- isinstance(x, Float) for x in col):
- col_abs = [abs(x) for x in col]
- max_value = max(col_abs)
- if iszerofunc(max_value):
- # just because iszerofunc returned True, doesn't
- # mean the value is numerically zero. Make sure
- # to replace all entries with numerical zeros
- if max_value != 0:
- newly_determined = [(i, 0) for i, x in enumerate(col) if x != 0]
- return (None, None, False, newly_determined)
- index = col_abs.index(max_value)
- return (index, col[index], False, newly_determined)
- # PASS 1 (iszerofunc directly)
- possible_zeros = []
- for i, x in enumerate(col):
- is_zero = iszerofunc(x)
- # is someone wrote a custom iszerofunc, it may return
- # BooleanFalse or BooleanTrue instead of True or False,
- # so use == for comparison instead of `is`
- if is_zero == False:
- # we found something that is definitely not zero
- return (i, x, False, newly_determined)
- possible_zeros.append(is_zero)
- # by this point, we've found no certain non-zeros
- if all(possible_zeros):
- # if everything is definitely zero, we have
- # no pivot
- return (None, None, False, newly_determined)
- # PASS 2 (iszerofunc after simplify)
- # we haven't found any for-sure non-zeros, so
- # go through the elements iszerofunc couldn't
- # make a determination about and opportunistically
- # simplify to see if we find something
- for i, x in enumerate(col):
- if possible_zeros[i] is not None:
- continue
- simped = simpfunc(x)
- is_zero = iszerofunc(simped)
- if is_zero in (True, False):
- newly_determined.append((i, simped))
- if is_zero == False:
- return (i, simped, False, newly_determined)
- possible_zeros[i] = is_zero
- # after simplifying, some things that were recognized
- # as zeros might be zeros
- if all(possible_zeros):
- # if everything is definitely zero, we have
- # no pivot
- return (None, None, False, newly_determined)
- # PASS 3 (.equals(0))
- # some expressions fail to simplify to zero, but
- # ``.equals(0)`` evaluates to True. As a last-ditch
- # attempt, apply ``.equals`` to these expressions
- for i, x in enumerate(col):
- if possible_zeros[i] is not None:
- continue
- if x.equals(S.Zero):
- # ``.iszero`` may return False with
- # an implicit assumption (e.g., ``x.equals(0)``
- # when ``x`` is a symbol), so only treat it
- # as proved when ``.equals(0)`` returns True
- possible_zeros[i] = True
- newly_determined.append((i, S.Zero))
- if all(possible_zeros):
- return (None, None, False, newly_determined)
- # at this point there is nothing that could definitely
- # be a pivot. To maintain compatibility with existing
- # behavior, we'll assume that an illdetermined thing is
- # non-zero. We should probably raise a warning in this case
- i = possible_zeros.index(None)
- return (i, col[i], True, newly_determined)
- def _find_reasonable_pivot_naive(col, iszerofunc=_iszero, simpfunc=None):
- """
- Helper that computes the pivot value and location from a
- sequence of contiguous matrix column elements. As a side effect
- of the pivot search, this function may simplify some of the elements
- of the input column. A list of these simplified entries and their
- indices are also returned.
- This function mimics the behavior of _find_reasonable_pivot(),
- but does less work trying to determine if an indeterminate candidate
- pivot simplifies to zero. This more naive approach can be much faster,
- with the trade-off that it may erroneously return a pivot that is zero.
- ``col`` is a sequence of contiguous column entries to be searched for
- a suitable pivot.
- ``iszerofunc`` is a callable that returns a Boolean that indicates
- if its input is zero, or None if no such determination can be made.
- ``simpfunc`` is a callable that simplifies its input. It must return
- its input if it does not simplify its input. Passing in
- ``simpfunc=None`` indicates that the pivot search should not attempt
- to simplify any candidate pivots.
- Returns a 4-tuple:
- (pivot_offset, pivot_val, assumed_nonzero, newly_determined)
- ``pivot_offset`` is the sequence index of the pivot.
- ``pivot_val`` is the value of the pivot.
- pivot_val and col[pivot_index] are equivalent, but will be different
- when col[pivot_index] was simplified during the pivot search.
- ``assumed_nonzero`` is a boolean indicating if the pivot cannot be
- guaranteed to be zero. If assumed_nonzero is true, then the pivot
- may or may not be non-zero. If assumed_nonzero is false, then
- the pivot is non-zero.
- ``newly_determined`` is a list of index-value pairs of pivot candidates
- that were simplified during the pivot search.
- """
- # indeterminates holds the index-value pairs of each pivot candidate
- # that is neither zero or non-zero, as determined by iszerofunc().
- # If iszerofunc() indicates that a candidate pivot is guaranteed
- # non-zero, or that every candidate pivot is zero then the contents
- # of indeterminates are unused.
- # Otherwise, the only viable candidate pivots are symbolic.
- # In this case, indeterminates will have at least one entry,
- # and all but the first entry are ignored when simpfunc is None.
- indeterminates = []
- for i, col_val in enumerate(col):
- col_val_is_zero = iszerofunc(col_val)
- if col_val_is_zero == False:
- # This pivot candidate is non-zero.
- return i, col_val, False, []
- elif col_val_is_zero is None:
- # The candidate pivot's comparison with zero
- # is indeterminate.
- indeterminates.append((i, col_val))
- if len(indeterminates) == 0:
- # All candidate pivots are guaranteed to be zero, i.e. there is
- # no pivot.
- return None, None, False, []
- if simpfunc is None:
- # Caller did not pass in a simplification function that might
- # determine if an indeterminate pivot candidate is guaranteed
- # to be nonzero, so assume the first indeterminate candidate
- # is non-zero.
- return indeterminates[0][0], indeterminates[0][1], True, []
- # newly_determined holds index-value pairs of candidate pivots
- # that were simplified during the search for a non-zero pivot.
- newly_determined = []
- for i, col_val in indeterminates:
- tmp_col_val = simpfunc(col_val)
- if id(col_val) != id(tmp_col_val):
- # simpfunc() simplified this candidate pivot.
- newly_determined.append((i, tmp_col_val))
- if iszerofunc(tmp_col_val) == False:
- # Candidate pivot simplified to a guaranteed non-zero value.
- return i, tmp_col_val, False, newly_determined
- return indeterminates[0][0], indeterminates[0][1], True, newly_determined
- # This functions is a candidate for caching if it gets implemented for matrices.
- def _berkowitz_toeplitz_matrix(M):
- """Return (A,T) where T the Toeplitz matrix used in the Berkowitz algorithm
- corresponding to ``M`` and A is the first principal submatrix.
- """
- # the 0 x 0 case is trivial
- if M.rows == 0 and M.cols == 0:
- return M._new(1,1, [M.one])
- #
- # Partition M = [ a_11 R ]
- # [ C A ]
- #
- a, R = M[0,0], M[0, 1:]
- C, A = M[1:, 0], M[1:,1:]
- #
- # The Toeplitz matrix looks like
- #
- # [ 1 ]
- # [ -a 1 ]
- # [ -RC -a 1 ]
- # [ -RAC -RC -a 1 ]
- # [ -RA**2C -RAC -RC -a 1 ]
- # etc.
- # Compute the diagonal entries.
- # Because multiplying matrix times vector is so much
- # more efficient than matrix times matrix, recursively
- # compute -R * A**n * C.
- diags = [C]
- for i in range(M.rows - 2):
- diags.append(A.multiply(diags[i], dotprodsimp=None))
- diags = [(-R).multiply(d, dotprodsimp=None)[0, 0] for d in diags]
- diags = [M.one, -a] + diags
- def entry(i,j):
- if j > i:
- return M.zero
- return diags[i - j]
- toeplitz = M._new(M.cols + 1, M.rows, entry)
- return (A, toeplitz)
- # This functions is a candidate for caching if it gets implemented for matrices.
- def _berkowitz_vector(M):
- """ Run the Berkowitz algorithm and return a vector whose entries
- are the coefficients of the characteristic polynomial of ``M``.
- Given N x N matrix, efficiently compute
- coefficients of characteristic polynomials of ``M``
- without division in the ground domain.
- This method is particularly useful for computing determinant,
- principal minors and characteristic polynomial when ``M``
- has complicated coefficients e.g. polynomials. Semi-direct
- usage of this algorithm is also important in computing
- efficiently sub-resultant PRS.
- Assuming that M is a square matrix of dimension N x N and
- I is N x N identity matrix, then the Berkowitz vector is
- an N x 1 vector whose entries are coefficients of the
- polynomial
- charpoly(M) = det(t*I - M)
- As a consequence, all polynomials generated by Berkowitz
- algorithm are monic.
- For more information on the implemented algorithm refer to:
- [1] S.J. Berkowitz, On computing the determinant in small
- parallel time using a small number of processors, ACM,
- Information Processing Letters 18, 1984, pp. 147-150
- [2] M. Keber, Division-Free computation of sub-resultants
- using Bezout matrices, Tech. Report MPI-I-2006-1-006,
- Saarbrucken, 2006
- """
- # handle the trivial cases
- if M.rows == 0 and M.cols == 0:
- return M._new(1, 1, [M.one])
- elif M.rows == 1 and M.cols == 1:
- return M._new(2, 1, [M.one, -M[0,0]])
- submat, toeplitz = _berkowitz_toeplitz_matrix(M)
- return toeplitz.multiply(_berkowitz_vector(submat), dotprodsimp=None)
- def _adjugate(M, method="berkowitz"):
- """Returns the adjugate, or classical adjoint, of
- a matrix. That is, the transpose of the matrix of cofactors.
- https://en.wikipedia.org/wiki/Adjugate
- Parameters
- ==========
- method : string, optional
- Method to use to find the cofactors, can be "bareiss", "berkowitz",
- "bird", "laplace" or "lu".
- Examples
- ========
- >>> from sympy import Matrix
- >>> M = Matrix([[1, 2], [3, 4]])
- >>> M.adjugate()
- Matrix([
- [ 4, -2],
- [-3, 1]])
- See Also
- ========
- cofactor_matrix
- sympy.matrices.matrixbase.MatrixBase.transpose
- """
- return M.cofactor_matrix(method=method).transpose()
- # This functions is a candidate for caching if it gets implemented for matrices.
- def _charpoly(M, x='lambda', simplify=_simplify):
- """Computes characteristic polynomial det(x*I - M) where I is
- the identity matrix.
- A PurePoly is returned, so using different variables for ``x`` does
- not affect the comparison or the polynomials:
- Parameters
- ==========
- x : string, optional
- Name for the "lambda" variable, defaults to "lambda".
- simplify : function, optional
- Simplification function to use on the characteristic polynomial
- calculated. Defaults to ``simplify``.
- Examples
- ========
- >>> from sympy import Matrix
- >>> from sympy.abc import x, y
- >>> M = Matrix([[1, 3], [2, 0]])
- >>> M.charpoly()
- PurePoly(lambda**2 - lambda - 6, lambda, domain='ZZ')
- >>> M.charpoly(x) == M.charpoly(y)
- True
- >>> M.charpoly(x) == M.charpoly(y)
- True
- Specifying ``x`` is optional; a symbol named ``lambda`` is used by
- default (which looks good when pretty-printed in unicode):
- >>> M.charpoly().as_expr()
- lambda**2 - lambda - 6
- And if ``x`` clashes with an existing symbol, underscores will
- be prepended to the name to make it unique:
- >>> M = Matrix([[1, 2], [x, 0]])
- >>> M.charpoly(x).as_expr()
- _x**2 - _x - 2*x
- Whether you pass a symbol or not, the generator can be obtained
- with the gen attribute since it may not be the same as the symbol
- that was passed:
- >>> M.charpoly(x).gen
- _x
- >>> M.charpoly(x).gen == x
- False
- Notes
- =====
- The Samuelson-Berkowitz algorithm is used to compute
- the characteristic polynomial efficiently and without any
- division operations. Thus the characteristic polynomial over any
- commutative ring without zero divisors can be computed.
- If the determinant det(x*I - M) can be found out easily as
- in the case of an upper or a lower triangular matrix, then
- instead of Samuelson-Berkowitz algorithm, eigenvalues are computed
- and the characteristic polynomial with their help.
- See Also
- ========
- det
- """
- if not M.is_square:
- raise NonSquareMatrixError()
- # Use DomainMatrix. We are already going to convert this to a Poly so there
- # is no need to worry about expanding powers etc. Also since this algorithm
- # does not require division or zero detection it is fine to use EX.
- #
- # M.to_DM() will fall back on EXRAW rather than EX. EXRAW is a lot faster
- # for elementary arithmetic because it does not call cancel for each
- # operation but it generates large unsimplified results that are slow in
- # the subsequent call to simplify. Using EX instead is faster overall
- # but at least in some cases EXRAW+simplify gives a simpler result so we
- # preserve that existing behaviour of charpoly for now...
- dM = M.to_DM()
- K = dM.domain
- cp = dM.charpoly()
- x = uniquely_named_symbol(x, [M], modify=lambda s: '_' + s)
- if K.is_EXRAW or simplify is not _simplify:
- # XXX: Converting back to Expr is expensive. We only do it if the
- # caller supplied a custom simplify function for backwards
- # compatibility or otherwise if the domain was EX. For any other domain
- # there should be no benefit in simplifying at this stage because Poly
- # will put everything into canonical form anyway.
- berk_vector = [K.to_sympy(c) for c in cp]
- berk_vector = [simplify(a) for a in berk_vector]
- p = PurePoly(berk_vector, x)
- else:
- # Convert from the list of domain elements directly to Poly.
- p = PurePoly(cp, x, domain=K)
- return p
- def _cofactor(M, i, j, method="berkowitz"):
- """Calculate the cofactor of an element.
- Parameters
- ==========
- method : string, optional
- Method to use to find the cofactors, can be "bareiss", "berkowitz",
- "bird", "laplace" or "lu".
- Examples
- ========
- >>> from sympy import Matrix
- >>> M = Matrix([[1, 2], [3, 4]])
- >>> M.cofactor(0, 1)
- -3
- See Also
- ========
- cofactor_matrix
- minor
- minor_submatrix
- """
- if not M.is_square or M.rows < 1:
- raise NonSquareMatrixError()
- return S.NegativeOne**((i + j) % 2) * M.minor(i, j, method)
- def _cofactor_matrix(M, method="berkowitz"):
- """Return a matrix containing the cofactor of each element.
- Parameters
- ==========
- method : string, optional
- Method to use to find the cofactors, can be "bareiss", "berkowitz",
- "bird", "laplace" or "lu".
- Examples
- ========
- >>> from sympy import Matrix
- >>> M = Matrix([[1, 2], [3, 4]])
- >>> M.cofactor_matrix()
- Matrix([
- [ 4, -3],
- [-2, 1]])
- See Also
- ========
- cofactor
- minor
- minor_submatrix
- """
- if not M.is_square:
- raise NonSquareMatrixError()
- return M._new(M.rows, M.cols,
- lambda i, j: M.cofactor(i, j, method))
- def _per(M):
- """Returns the permanent of a matrix. Unlike determinant,
- permanent is defined for both square and non-square matrices.
- For an m x n matrix, with m less than or equal to n,
- it is given as the sum over the permutations s of size
- less than or equal to m on [1, 2, . . . n] of the product
- from i = 1 to m of M[i, s[i]]. Taking the transpose will
- not affect the value of the permanent.
- In the case of a square matrix, this is the same as the permutation
- definition of the determinant, but it does not take the sign of the
- permutation into account. Computing the permanent with this definition
- is quite inefficient, so here the Ryser formula is used.
- Examples
- ========
- >>> from sympy import Matrix
- >>> M = Matrix([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
- >>> M.per()
- 450
- >>> M = Matrix([1, 5, 7])
- >>> M.per()
- 13
- References
- ==========
- .. [1] Prof. Frank Ben's notes: https://math.berkeley.edu/~bernd/ban275.pdf
- .. [2] Wikipedia article on Permanent: https://en.wikipedia.org/wiki/Permanent_%28mathematics%29
- .. [3] https://reference.wolfram.com/language/ref/Permanent.html
- .. [4] Permanent of a rectangular matrix : https://arxiv.org/pdf/0904.3251.pdf
- """
- import itertools
- m, n = M.shape
- if m > n:
- M = M.T
- m, n = n, m
- s = list(range(n))
- subsets = []
- for i in range(1, m + 1):
- subsets += list(map(list, itertools.combinations(s, i)))
- perm = 0
- for subset in subsets:
- prod = 1
- sub_len = len(subset)
- for i in range(m):
- prod *= sum(M[i, j] for j in subset)
- perm += prod * S.NegativeOne**sub_len * nC(n - sub_len, m - sub_len)
- perm *= S.NegativeOne**m
- return perm.simplify()
- def _det_DOM(M):
- DOM = DomainMatrix.from_Matrix(M, field=True, extension=True)
- K = DOM.domain
- return K.to_sympy(DOM.det())
- # This functions is a candidate for caching if it gets implemented for matrices.
- def _det(M, method="bareiss", iszerofunc=None):
- """Computes the determinant of a matrix if ``M`` is a concrete matrix object
- otherwise return an expressions ``Determinant(M)`` if ``M`` is a
- ``MatrixSymbol`` or other expression.
- Parameters
- ==========
- method : string, optional
- Specifies the algorithm used for computing the matrix determinant.
- If the matrix is at most 3x3, a hard-coded formula is used and the
- specified method is ignored. Otherwise, it defaults to
- ``'bareiss'``.
- Also, if the matrix is an upper or a lower triangular matrix, determinant
- is computed by simple multiplication of diagonal elements, and the
- specified method is ignored.
- If it is set to ``'domain-ge'``, then Gaussian elimination method will
- be used via using DomainMatrix.
- If it is set to ``'bareiss'``, Bareiss' fraction-free algorithm will
- be used.
- If it is set to ``'berkowitz'``, Berkowitz' algorithm will be used.
- If it is set to ``'bird'``, Bird's algorithm will be used [1]_.
- If it is set to ``'laplace'``, Laplace's algorithm will be used.
- Otherwise, if it is set to ``'lu'``, LU decomposition will be used.
- .. note::
- For backward compatibility, legacy keys like "bareis" and
- "det_lu" can still be used to indicate the corresponding
- methods.
- And the keys are also case-insensitive for now. However, it is
- suggested to use the precise keys for specifying the method.
- iszerofunc : FunctionType or None, optional
- If it is set to ``None``, it will be defaulted to ``_iszero`` if the
- method is set to ``'bareiss'``, and ``_is_zero_after_expand_mul`` if
- the method is set to ``'lu'``.
- It can also accept any user-specified zero testing function, if it
- is formatted as a function which accepts a single symbolic argument
- and returns ``True`` if it is tested as zero and ``False`` if it
- tested as non-zero, and also ``None`` if it is undecidable.
- Returns
- =======
- det : Basic
- Result of determinant.
- Raises
- ======
- ValueError
- If unrecognized keys are given for ``method`` or ``iszerofunc``.
- NonSquareMatrixError
- If attempted to calculate determinant from a non-square matrix.
- Examples
- ========
- >>> from sympy import Matrix, eye, det
- >>> I3 = eye(3)
- >>> det(I3)
- 1
- >>> M = Matrix([[1, 2], [3, 4]])
- >>> det(M)
- -2
- >>> det(M) == M.det()
- True
- >>> M.det(method="domain-ge")
- -2
- References
- ==========
- .. [1] Bird, R. S. (2011). A simple division-free algorithm for computing
- determinants. Inf. Process. Lett., 111(21), 1072-1074. doi:
- 10.1016/j.ipl.2011.08.006
- """
- # sanitize `method`
- method = method.lower()
- if method == "bareis":
- method = "bareiss"
- elif method == "det_lu":
- method = "lu"
- if method not in ("bareiss", "berkowitz", "lu", "domain-ge", "bird",
- "laplace"):
- raise ValueError("Determinant method '%s' unrecognized" % method)
- if iszerofunc is None:
- if method == "bareiss":
- iszerofunc = _is_zero_after_expand_mul
- elif method == "lu":
- iszerofunc = _iszero
- elif not isinstance(iszerofunc, FunctionType):
- raise ValueError("Zero testing method '%s' unrecognized" % iszerofunc)
- n = M.rows
- if n == M.cols: # square check is done in individual method functions
- if n == 0:
- return M.one
- elif n == 1:
- return M[0, 0]
- elif n == 2:
- m = M[0, 0] * M[1, 1] - M[0, 1] * M[1, 0]
- return _get_intermediate_simp(_dotprodsimp)(m)
- elif n == 3:
- m = (M[0, 0] * M[1, 1] * M[2, 2]
- + M[0, 1] * M[1, 2] * M[2, 0]
- + M[0, 2] * M[1, 0] * M[2, 1]
- - M[0, 2] * M[1, 1] * M[2, 0]
- - M[0, 0] * M[1, 2] * M[2, 1]
- - M[0, 1] * M[1, 0] * M[2, 2])
- return _get_intermediate_simp(_dotprodsimp)(m)
- dets = []
- for b in M.strongly_connected_components():
- if method == "domain-ge": # uses DomainMatrix to evaluate determinant
- det = _det_DOM(M[b, b])
- elif method == "bareiss":
- det = M[b, b]._eval_det_bareiss(iszerofunc=iszerofunc)
- elif method == "berkowitz":
- det = M[b, b]._eval_det_berkowitz()
- elif method == "lu":
- det = M[b, b]._eval_det_lu(iszerofunc=iszerofunc)
- elif method == "bird":
- det = M[b, b]._eval_det_bird()
- elif method == "laplace":
- det = M[b, b]._eval_det_laplace()
- dets.append(det)
- return Mul(*dets)
- # This functions is a candidate for caching if it gets implemented for matrices.
- def _det_bareiss(M, iszerofunc=_is_zero_after_expand_mul):
- """Compute matrix determinant using Bareiss' fraction-free
- algorithm which is an extension of the well known Gaussian
- elimination method. This approach is best suited for dense
- symbolic matrices and will result in a determinant with
- minimal number of fractions. It means that less term
- rewriting is needed on resulting formulae.
- Parameters
- ==========
- iszerofunc : function, optional
- The function to use to determine zeros when doing an LU decomposition.
- Defaults to ``lambda x: x.is_zero``.
- TODO: Implement algorithm for sparse matrices (SFF),
- http://www.eecis.udel.edu/~saunders/papers/sffge/it5.ps.
- """
- # Recursively implemented Bareiss' algorithm as per Deanna Richelle Leggett's
- # thesis http://www.math.usm.edu/perry/Research/Thesis_DRL.pdf
- def bareiss(mat, cumm=1):
- if mat.rows == 0:
- return mat.one
- elif mat.rows == 1:
- return mat[0, 0]
- # find a pivot and extract the remaining matrix
- # With the default iszerofunc, _find_reasonable_pivot slows down
- # the computation by the factor of 2.5 in one test.
- # Relevant issues: #10279 and #13877.
- pivot_pos, pivot_val, _, _ = _find_reasonable_pivot(mat[:, 0], iszerofunc=iszerofunc)
- if pivot_pos is None:
- return mat.zero
- # if we have a valid pivot, we'll do a "row swap", so keep the
- # sign of the det
- sign = (-1) ** (pivot_pos % 2)
- # we want every row but the pivot row and every column
- rows = [i for i in range(mat.rows) if i != pivot_pos]
- cols = list(range(mat.cols))
- tmp_mat = mat.extract(rows, cols)
- def entry(i, j):
- ret = (pivot_val*tmp_mat[i, j + 1] - mat[pivot_pos, j + 1]*tmp_mat[i, 0]) / cumm
- if _get_intermediate_simp_bool(True):
- return _dotprodsimp(ret)
- elif not ret.is_Atom:
- return cancel(ret)
- return ret
- return sign*bareiss(M._new(mat.rows - 1, mat.cols - 1, entry), pivot_val)
- if not M.is_square:
- raise NonSquareMatrixError()
- if M.rows == 0:
- return M.one
- # sympy/matrices/tests/test_matrices.py contains a test that
- # suggests that the determinant of a 0 x 0 matrix is one, by
- # convention.
- return bareiss(M)
- def _det_berkowitz(M):
- """ Use the Berkowitz algorithm to compute the determinant."""
- if not M.is_square:
- raise NonSquareMatrixError()
- if M.rows == 0:
- return M.one
- # sympy/matrices/tests/test_matrices.py contains a test that
- # suggests that the determinant of a 0 x 0 matrix is one, by
- # convention.
- berk_vector = _berkowitz_vector(M)
- return (-1)**(len(berk_vector) - 1) * berk_vector[-1]
- # This functions is a candidate for caching if it gets implemented for matrices.
- def _det_LU(M, iszerofunc=_iszero, simpfunc=None):
- """ Computes the determinant of a matrix from its LU decomposition.
- This function uses the LU decomposition computed by
- LUDecomposition_Simple().
- The keyword arguments iszerofunc and simpfunc are passed to
- LUDecomposition_Simple().
- iszerofunc is a callable that returns a boolean indicating if its
- input is zero, or None if it cannot make the determination.
- simpfunc is a callable that simplifies its input.
- The default is simpfunc=None, which indicate that the pivot search
- algorithm should not attempt to simplify any candidate pivots.
- If simpfunc fails to simplify its input, then it must return its input
- instead of a copy.
- Parameters
- ==========
- iszerofunc : function, optional
- The function to use to determine zeros when doing an LU decomposition.
- Defaults to ``lambda x: x.is_zero``.
- simpfunc : function, optional
- The simplification function to use when looking for zeros for pivots.
- """
- if not M.is_square:
- raise NonSquareMatrixError()
- if M.rows == 0:
- return M.one
- # sympy/matrices/tests/test_matrices.py contains a test that
- # suggests that the determinant of a 0 x 0 matrix is one, by
- # convention.
- lu, row_swaps = M.LUdecomposition_Simple(iszerofunc=iszerofunc,
- simpfunc=simpfunc)
- # P*A = L*U => det(A) = det(L)*det(U)/det(P) = det(P)*det(U).
- # Lower triangular factor L encoded in lu has unit diagonal => det(L) = 1.
- # P is a permutation matrix => det(P) in {-1, 1} => 1/det(P) = det(P).
- # LUdecomposition_Simple() returns a list of row exchange index pairs, rather
- # than a permutation matrix, but det(P) = (-1)**len(row_swaps).
- # Avoid forming the potentially time consuming product of U's diagonal entries
- # if the product is zero.
- # Bottom right entry of U is 0 => det(A) = 0.
- # It may be impossible to determine if this entry of U is zero when it is symbolic.
- if iszerofunc(lu[lu.rows-1, lu.rows-1]):
- return M.zero
- # Compute det(P)
- det = -M.one if len(row_swaps)%2 else M.one
- # Compute det(U) by calculating the product of U's diagonal entries.
- # The upper triangular portion of lu is the upper triangular portion of the
- # U factor in the LU decomposition.
- for k in range(lu.rows):
- det *= lu[k, k]
- # return det(P)*det(U)
- return det
- @cacheit
- def __det_laplace(M):
- """Compute the determinant of a matrix using Laplace expansion.
- This is a recursive function, and it should not be called directly.
- Use _det_laplace() instead. The reason for splitting this function
- into two is to allow caching of determinants of submatrices. While
- one could also define this function inside _det_laplace(), that
- would remove the advantage of using caching in Cramer Solve.
- """
- n = M.shape[0]
- if n == 1:
- return M[0]
- elif n == 2:
- return M[0, 0] * M[1, 1] - M[0, 1] * M[1, 0]
- else:
- return sum((-1) ** i * M[0, i] *
- __det_laplace(M.minor_submatrix(0, i)) for i in range(n))
- def _det_laplace(M):
- """Compute the determinant of a matrix using Laplace expansion.
- While Laplace expansion is not the most efficient method of computing
- a determinant, it is a simple one, and it has the advantage of
- being division free. To improve efficiency, this function uses
- caching to avoid recomputing determinants of submatrices.
- """
- if not M.is_square:
- raise NonSquareMatrixError()
- if M.shape[0] == 0:
- return M.one
- # sympy/matrices/tests/test_matrices.py contains a test that
- # suggests that the determinant of a 0 x 0 matrix is one, by
- # convention.
- return __det_laplace(M.as_immutable())
- def _det_bird(M):
- r"""Compute the determinant of a matrix using Bird's algorithm.
- Bird's algorithm is a simple division-free algorithm for computing, which
- is of lower order than the Laplace's algorithm. It is described in [1]_.
- References
- ==========
- .. [1] Bird, R. S. (2011). A simple division-free algorithm for computing
- determinants. Inf. Process. Lett., 111(21), 1072-1074. doi:
- 10.1016/j.ipl.2011.08.006
- """
- def mu(X):
- n = X.shape[0]
- zero = X.domain.zero
- total = zero
- diag_sums = [zero]
- for i in reversed(range(1, n)):
- total -= X[i][i]
- diag_sums.append(total)
- diag_sums = diag_sums[::-1]
- elems = [[zero] * i + [diag_sums[i]] + X_i[i + 1:] for i, X_i in
- enumerate(X)]
- return DDM(elems, X.shape, X.domain)
- Mddm = M._rep.to_ddm()
- n = M.shape[0]
- if n == 0:
- return M.one
- # sympy/matrices/tests/test_matrices.py contains a test that
- # suggests that the determinant of a 0 x 0 matrix is one, by
- # convention.
- Fn1 = Mddm
- for _ in range(n - 1):
- Fn1 = mu(Fn1).matmul(Mddm)
- detA = Fn1[0][0]
- if n % 2 == 0:
- detA = -detA
- return Mddm.domain.to_sympy(detA)
- def _minor(M, i, j, method="berkowitz"):
- """Return the (i,j) minor of ``M``. That is,
- return the determinant of the matrix obtained by deleting
- the `i`th row and `j`th column from ``M``.
- Parameters
- ==========
- i, j : int
- The row and column to exclude to obtain the submatrix.
- method : string, optional
- Method to use to find the determinant of the submatrix, can be
- "bareiss", "berkowitz", "bird", "laplace" or "lu".
- Examples
- ========
- >>> from sympy import Matrix
- >>> M = Matrix([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
- >>> M.minor(1, 1)
- -12
- See Also
- ========
- minor_submatrix
- cofactor
- det
- """
- if not M.is_square:
- raise NonSquareMatrixError()
- return M.minor_submatrix(i, j).det(method=method)
- def _minor_submatrix(M, i, j):
- """Return the submatrix obtained by removing the `i`th row
- and `j`th column from ``M`` (works with Pythonic negative indices).
- Parameters
- ==========
- i, j : int
- The row and column to exclude to obtain the submatrix.
- Examples
- ========
- >>> from sympy import Matrix
- >>> M = Matrix([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
- >>> M.minor_submatrix(1, 1)
- Matrix([
- [1, 3],
- [7, 9]])
- See Also
- ========
- minor
- cofactor
- """
- if i < 0:
- i += M.rows
- if j < 0:
- j += M.cols
- if not 0 <= i < M.rows or not 0 <= j < M.cols:
- raise ValueError("`i` and `j` must satisfy 0 <= i < ``M.rows`` "
- "(%d)" % M.rows + "and 0 <= j < ``M.cols`` (%d)." % M.cols)
- rows = [a for a in range(M.rows) if a != i]
- cols = [a for a in range(M.cols) if a != j]
- return M.extract(rows, cols)
|