Spaces:
Sleeping
Sleeping
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 | |
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) | |