Traktor/myenv/Lib/site-packages/sympy/matrices/determinant.py

899 lines
29 KiB
Python
Raw Normal View History

2024-05-26 05:12:46 +02:00
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.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, _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" 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_%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.
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 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)
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
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)