123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902 |
- from types import FunctionType
- 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.simplify.simplify import (simplify as _simplify,
- dotprodsimp as _dotprodsimp)
- from sympy.core.sympify import sympify
- from sympy.functions.combinatorial.numbers import nC
- from sympy.polys.matrices.domainmatrix import DomainMatrix
- from .common import NonSquareMatrixError
- from .utilities import (
- _get_intermediate_simp, _get_intermediate_simp_bool,
- _iszero, _is_zero_after_expand_mul)
- 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`` doesn't 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" 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.common.MatrixCommon.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()
- if M.is_lower or M.is_upper:
- diagonal_elements = M.diagonal()
- x = uniquely_named_symbol(x, diagonal_elements, modify=lambda s: '_' + s)
- m = 1
- for i in diagonal_elements:
- m = m * (x - simplify(i))
- return PurePoly(m, x)
- berk_vector = _berkowitz_vector(M)
- x = uniquely_named_symbol(x, berk_vector, modify=lambda s: '_' + s)
- return PurePoly([simplify(a) for a in berk_vector], x)
- 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" 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" 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 or M.rows < 1:
- 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_(mathematics)
- .. [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
- perm = sympify(perm)
- 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.
- 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
- """
- # 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"):
- 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 evalute 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)
- 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 = list(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
- 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" 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)
|