from __future__ import division, print_function
from types import FunctionType
from mpmath.libmp.libmpf import prec_to_dps
from sympy.core.add import Add
from sympy.core.basic import Basic
from sympy.core.compatibility import (
Callable, NotIterable, as_int, default_sort_key, is_sequence, range,
reduce, string_types)
from sympy.core.decorators import deprecated
from sympy.core.expr import Expr
from sympy.core.function import expand_mul
from sympy.core.numbers import Float, Integer, mod_inverse
from sympy.core.power import Pow
from sympy.core.singleton import S
from sympy.core.symbol import Dummy, Symbol, _uniquely_named_symbol, symbols
from sympy.core.sympify import sympify
from sympy.functions import exp, factorial
from sympy.functions.elementary.miscellaneous import Max, Min, sqrt
from sympy.polys import PurePoly, cancel, roots
from sympy.printing import sstr
from sympy.simplify import nsimplify
from sympy.simplify import simplify as _simplify
from sympy.utilities.exceptions import SymPyDeprecationWarning
from sympy.utilities.iterables import flatten, numbered_symbols
from .common import (
MatrixCommon, MatrixError, NonSquareMatrixError, ShapeError)
def _iszero(x):
"""Returns True if x is zero."""
return getattr(x, 'is_zero', None)
def _is_zero_after_expand_mul(x):
"""Tests by expand_mul only, suitable for polynomials and rational
functions."""
return expand_mul(x) == 0
class DeferredVector(Symbol, NotIterable):
"""A vector whose components are deferred (e.g. for use with lambdify)
Examples
========
>>> from sympy import DeferredVector, lambdify
>>> X = DeferredVector( 'X' )
>>> X
X
>>> expr = (X[0] + 2, X[2] + 3)
>>> func = lambdify( X, expr)
>>> func( [1, 2, 3] )
(3, 6)
"""
def __getitem__(self, i):
if i == -0:
i = 0
if i < 0:
raise IndexError('DeferredVector index out of range')
component_name = '%s[%d]' % (self.name, i)
return Symbol(component_name)
def __str__(self):
return sstr(self)
def __repr__(self):
return "DeferredVector('%s')" % self.name
[docs]class MatrixDeterminant(MatrixCommon):
"""Provides basic matrix determinant operations.
Should not be instantiated directly."""
def _eval_berkowitz_toeplitz_matrix(self):
"""Return (A,T) where T the Toeplitz matrix used in the Berkowitz algorithm
corresponding to ``self`` and A is the first principal submatrix."""
# the 0 x 0 case is trivial
if self.rows == 0 and self.cols == 0:
return self._new(1,1, [S.One])
#
# Partition self = [ a_11 R ]
# [ C A ]
#
a, R = self[0,0], self[0, 1:]
C, A = self[1:, 0], self[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(self.rows - 2):
diags.append(A * diags[i])
diags = [(-R*d)[0, 0] for d in diags]
diags = [S.One, -a] + diags
def entry(i,j):
if j > i:
return S.Zero
return diags[i - j]
toeplitz = self._new(self.cols + 1, self.rows, entry)
return (A, toeplitz)
def _eval_berkowitz_vector(self):
""" Run the Berkowitz algorithm and return a vector whose entries
are the coefficients of the characteristic polynomial of ``self``.
Given N x N matrix, efficiently compute
coefficients of characteristic polynomials of ``self``
without division in the ground domain.
This method is particularly useful for computing determinant,
principal minors and characteristic polynomial when ``self``
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 self.rows == 0 and self.cols == 0:
return self._new(1, 1, [S.One])
elif self.rows == 1 and self.cols == 1:
return self._new(2, 1, [S.One, -self[0,0]])
submat, toeplitz = self._eval_berkowitz_toeplitz_matrix()
return toeplitz * submat._eval_berkowitz_vector()
def _eval_det_bareiss(self, 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.
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 S.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 S.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 not ret.is_Atom:
return cancel(ret)
return ret
return sign*bareiss(self._new(mat.rows - 1, mat.cols - 1, entry), pivot_val)
return cancel(bareiss(self))
def _eval_det_berkowitz(self):
""" Use the Berkowitz algorithm to compute the determinant."""
berk_vector = self._eval_berkowitz_vector()
return (-1)**(len(berk_vector) - 1) * berk_vector[-1]
def _eval_det_lu(self, 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."""
if self.rows == 0:
return S.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 = self.LUdecomposition_Simple(iszerofunc=iszerofunc, simpfunc=None)
# 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 S.Zero
# Compute det(P)
det = -S.One if len(row_swaps)%2 else S.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 _eval_determinant(self):
"""Assumed to exist by matrix expressions; If we subclass
MatrixDeterminant, we can fully evaluate determinants."""
return self.det()
[docs] def adjugate(self, 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
See Also
========
cofactor_matrix
transpose
"""
return self.cofactor_matrix(method).transpose()
[docs] def charpoly(self, x='lambda', simplify=_simplify):
"""Computes characteristic polynomial det(x*I - self) where I is
the identity matrix.
A PurePoly is returned, so using different variables for ``x`` does
not affect the comparison or the polynomials:
Examples
========
>>> from sympy import Matrix
>>> from sympy.abc import x, y
>>> A = Matrix([[1, 3], [2, 0]])
>>> A.charpoly(x) == A.charpoly(y)
True
Specifying ``x`` is optional; a symbol named ``lambda`` is used by
default (which looks good when pretty-printed in unicode):
>>> A.charpoly().as_expr()
lambda**2 - lambda - 6
And if ``x`` clashes with an existing symbol, underscores will
be preppended to the name to make it unique:
>>> A = Matrix([[1, 2], [x, 0]])
>>> A.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:
>>> A.charpoly(x).gen
_x
>>> A.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.
See Also
========
det
"""
if not self.is_square:
raise NonSquareMatrixError()
berk_vector = self._eval_berkowitz_vector()
x = _uniquely_named_symbol(x, berk_vector)
return PurePoly([simplify(a) for a in berk_vector], x)
[docs] def cofactor(self, i, j, method="berkowitz"):
"""Calculate the cofactor of an element.
See Also
========
cofactor_matrix
minor
minor_submatrix
"""
if not self.is_square or self.rows < 1:
raise NonSquareMatrixError()
return (-1)**((i + j) % 2) * self.minor(i, j, method)
[docs] def cofactor_matrix(self, method="berkowitz"):
"""Return a matrix containing the cofactor of each element.
See Also
========
cofactor
minor
minor_submatrix
adjugate
"""
if not self.is_square or self.rows < 1:
raise NonSquareMatrixError()
return self._new(self.rows, self.cols,
lambda i, j: self.cofactor(i, j, method))
[docs] def det(self, method="bareiss", iszerofunc=None):
"""Computes the determinant of a matrix.
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'``.
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.
"""
# sanitize `method`
method = method.lower()
if method == "bareis":
method = "bareiss"
if method == "det_lu":
method = "lu"
if method not in ("bareiss", "berkowitz", "lu"):
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)
# if methods were made internal and all determinant calculations
# passed through here, then these lines could be factored out of
# the method routines
if not self.is_square:
raise NonSquareMatrixError()
n = self.rows
if n == 0:
return S.One
elif n == 1:
return self[0,0]
elif n == 2:
return self[0, 0] * self[1, 1] - self[0, 1] * self[1, 0]
elif n == 3:
return (self[0, 0] * self[1, 1] * self[2, 2]
+ self[0, 1] * self[1, 2] * self[2, 0]
+ self[0, 2] * self[1, 0] * self[2, 1]
- self[0, 2] * self[1, 1] * self[2, 0]
- self[0, 0] * self[1, 2] * self[2, 1]
- self[0, 1] * self[1, 0] * self[2, 2])
if method == "bareiss":
return self._eval_det_bareiss(iszerofunc=iszerofunc)
elif method == "berkowitz":
return self._eval_det_berkowitz()
elif method == "lu":
return self._eval_det_lu(iszerofunc=iszerofunc)
[docs] def minor(self, i, j, method="berkowitz"):
"""Return the (i,j) minor of ``self``. That is,
return the determinant of the matrix obtained by deleting
the `i`th row and `j`th column from ``self``.
See Also
========
minor_submatrix
cofactor
det
"""
if not self.is_square or self.rows < 1:
raise NonSquareMatrixError()
return self.minor_submatrix(i, j).det(method=method)
[docs] def minor_submatrix(self, i, j):
"""Return the submatrix obtained by removing the `i`th row
and `j`th column from ``self``.
See Also
========
minor
cofactor
"""
if i < 0:
i += self.rows
if j < 0:
j += self.cols
if not 0 <= i < self.rows or not 0 <= j < self.cols:
raise ValueError("`i` and `j` must satisfy 0 <= i < ``self.rows`` "
"(%d)" % self.rows + "and 0 <= j < ``self.cols`` (%d)." % self.cols)
rows = [a for a in range(self.rows) if a != i]
cols = [a for a in range(self.cols) if a != j]
return self.extract(rows, cols)
[docs]class MatrixReductions(MatrixDeterminant):
"""Provides basic matrix row/column operations.
Should not be instantiated directly."""
def _eval_col_op_swap(self, col1, col2):
def entry(i, j):
if j == col1:
return self[i, col2]
elif j == col2:
return self[i, col1]
return self[i, j]
return self._new(self.rows, self.cols, entry)
def _eval_col_op_multiply_col_by_const(self, col, k):
def entry(i, j):
if j == col:
return k * self[i, j]
return self[i, j]
return self._new(self.rows, self.cols, entry)
def _eval_col_op_add_multiple_to_other_col(self, col, k, col2):
def entry(i, j):
if j == col:
return self[i, j] + k * self[i, col2]
return self[i, j]
return self._new(self.rows, self.cols, entry)
def _eval_row_op_swap(self, row1, row2):
def entry(i, j):
if i == row1:
return self[row2, j]
elif i == row2:
return self[row1, j]
return self[i, j]
return self._new(self.rows, self.cols, entry)
def _eval_row_op_multiply_row_by_const(self, row, k):
def entry(i, j):
if i == row:
return k * self[i, j]
return self[i, j]
return self._new(self.rows, self.cols, entry)
def _eval_row_op_add_multiple_to_other_row(self, row, k, row2):
def entry(i, j):
if i == row:
return self[i, j] + k * self[row2, j]
return self[i, j]
return self._new(self.rows, self.cols, entry)
def _eval_echelon_form(self, iszerofunc, simpfunc):
"""Returns (mat, swaps) where ``mat`` is a row-equivalent matrix
in echelon form and ``swaps`` is a list of row-swaps performed."""
reduced, pivot_cols, swaps = self._row_reduce(iszerofunc, simpfunc,
normalize_last=True,
normalize=False,
zero_above=False)
return reduced, pivot_cols, swaps
def _eval_is_echelon(self, iszerofunc):
if self.rows <= 0 or self.cols <= 0:
return True
zeros_below = all(iszerofunc(t) for t in self[1:, 0])
if iszerofunc(self[0, 0]):
return zeros_below and self[:, 1:]._eval_is_echelon(iszerofunc)
return zeros_below and self[1:, 1:]._eval_is_echelon(iszerofunc)
def _eval_rref(self, iszerofunc, simpfunc, normalize_last=True):
reduced, pivot_cols, swaps = self._row_reduce(iszerofunc, simpfunc,
normalize_last, normalize=True,
zero_above=True)
return reduced, pivot_cols
def _normalize_op_args(self, op, col, k, col1, col2, error_str="col"):
"""Validate the arguments for a row/column operation. ``error_str``
can be one of "row" or "col" depending on the arguments being parsed."""
if op not in ["n->kn", "n<->m", "n->n+km"]:
raise ValueError("Unknown {} operation '{}'. Valid col operations "
"are 'n->kn', 'n<->m', 'n->n+km'".format(error_str, op))
# normalize and validate the arguments
if op == "n->kn":
col = col if col is not None else col1
if col is None or k is None:
raise ValueError("For a {0} operation 'n->kn' you must provide the "
"kwargs `{0}` and `k`".format(error_str))
if not 0 <= col <= self.cols:
raise ValueError("This matrix doesn't have a {} '{}'".format(error_str, col))
if op == "n<->m":
# we need two cols to swap. It doesn't matter
# how they were specified, so gather them together and
# remove `None`
cols = set((col, k, col1, col2)).difference([None])
if len(cols) > 2:
# maybe the user left `k` by mistake?
cols = set((col, col1, col2)).difference([None])
if len(cols) != 2:
raise ValueError("For a {0} operation 'n<->m' you must provide the "
"kwargs `{0}1` and `{0}2`".format(error_str))
col1, col2 = cols
if not 0 <= col1 <= self.cols:
raise ValueError("This matrix doesn't have a {} '{}'".format(error_str, col1))
if not 0 <= col2 <= self.cols:
raise ValueError("This matrix doesn't have a {} '{}'".format(error_str, col2))
if op == "n->n+km":
col = col1 if col is None else col
col2 = col1 if col2 is None else col2
if col is None or col2 is None or k is None:
raise ValueError("For a {0} operation 'n->n+km' you must provide the "
"kwargs `{0}`, `k`, and `{0}2`".format(error_str))
if col == col2:
raise ValueError("For a {0} operation 'n->n+km' `{0}` and `{0}2` must "
"be different.".format(error_str))
if not 0 <= col <= self.cols:
raise ValueError("This matrix doesn't have a {} '{}'".format(error_str, col))
if not 0 <= col2 <= self.cols:
raise ValueError("This matrix doesn't have a {} '{}'".format(error_str, col2))
return op, col, k, col1, col2
def _permute_complexity_right(self, iszerofunc):
"""Permute columns with complicated elements as
far right as they can go. Since the ``sympy`` row reduction
algorithms start on the left, having complexity right-shifted
speeds things up.
Returns a tuple (mat, perm) where perm is a permutation
of the columns to perform to shift the complex columns right, and mat
is the permuted matrix."""
def complexity(i):
# the complexity of a column will be judged by how many
# element's zero-ness cannot be determined
return sum(1 if iszerofunc(e) is None else 0 for e in self[:, i])
complex = [(complexity(i), i) for i in range(self.cols)]
perm = [j for (i, j) in sorted(complex)]
return (self.permute(perm, orientation='cols'), perm)
def _row_reduce(self, iszerofunc, simpfunc, normalize_last=True,
normalize=True, zero_above=True):
"""Row reduce ``self`` and return a tuple (rref_matrix,
pivot_cols, swaps) where pivot_cols are the pivot columns
and swaps are any row swaps that were used in the process
of row reduction.
Parameters
==========
iszerofunc : determines if an entry can be used as a pivot
simpfunc : used to simplify elements and test if they are
zero if ``iszerofunc`` returns `None`
normalize_last : indicates where all row reduction should
happen in a fraction-free manner and then the rows are
normalized (so that the pivots are 1), or whether
rows should be normalized along the way (like the naive
row reduction algorithm)
normalize : whether pivot rows should be normalized so that
the pivot value is 1
zero_above : whether entries above the pivot should be zeroed.
If ``zero_above=False``, an echelon matrix will be returned.
"""
rows, cols = self.rows, self.cols
mat = list(self)
def get_col(i):
return mat[i::cols]
def row_swap(i, j):
mat[i*cols:(i + 1)*cols], mat[j*cols:(j + 1)*cols] = \
mat[j*cols:(j + 1)*cols], mat[i*cols:(i + 1)*cols]
def cross_cancel(a, i, b, j):
"""Does the row op row[i] = a*row[i] - b*row[j]"""
q = (j - i)*cols
for p in range(i*cols, (i + 1)*cols):
mat[p] = a*mat[p] - b*mat[p + q]
piv_row, piv_col = 0, 0
pivot_cols = []
swaps = []
# use a fraction free method to zero above and below each pivot
while piv_col < cols and piv_row < rows:
pivot_offset, pivot_val, \
assumed_nonzero, newly_determined = _find_reasonable_pivot(
get_col(piv_col)[piv_row:], iszerofunc, simpfunc)
# _find_reasonable_pivot may have simplified some things
# in the process. Let's not let them go to waste
for (offset, val) in newly_determined:
offset += piv_row
mat[offset*cols + piv_col] = val
if pivot_offset is None:
piv_col += 1
continue
pivot_cols.append(piv_col)
if pivot_offset != 0:
row_swap(piv_row, pivot_offset + piv_row)
swaps.append((piv_row, pivot_offset + piv_row))
# if we aren't normalizing last, we normalize
# before we zero the other rows
if normalize_last is False:
i, j = piv_row, piv_col
mat[i*cols + j] = S.One
for p in range(i*cols + j + 1, (i + 1)*cols):
mat[p] = mat[p] / pivot_val
# after normalizing, the pivot value is 1
pivot_val = S.One
# zero above and below the pivot
for row in range(rows):
# don't zero our current row
if row == piv_row:
continue
# don't zero above the pivot unless we're told.
if zero_above is False and row < piv_row:
continue
# if we're already a zero, don't do anything
val = mat[row*cols + piv_col]
if iszerofunc(val):
continue
cross_cancel(pivot_val, row, val, piv_row)
piv_row += 1
# normalize each row
if normalize_last is True and normalize is True:
for piv_i, piv_j in enumerate(pivot_cols):
pivot_val = mat[piv_i*cols + piv_j]
mat[piv_i*cols + piv_j] = S.One
for p in range(piv_i*cols + piv_j + 1, (piv_i + 1)*cols):
mat[p] = mat[p] / pivot_val
return self._new(self.rows, self.cols, mat), tuple(pivot_cols), tuple(swaps)
[docs] def elementary_col_op(self, op="n->kn", col=None, k=None, col1=None, col2=None):
"""Performs the elementary column operation `op`.
`op` may be one of
* "n->kn" (column n goes to k*n)
* "n<->m" (swap column n and column m)
* "n->n+km" (column n goes to column n + k*column m)
Parameters
==========
op : string; the elementary row operation
col : the column to apply the column operation
k : the multiple to apply in the column operation
col1 : one column of a column swap
col2 : second column of a column swap or column "m" in the column operation
"n->n+km"
"""
op, col, k, col1, col2 = self._normalize_op_args(op, col, k, col1, col2, "col")
# now that we've validated, we're all good to dispatch
if op == "n->kn":
return self._eval_col_op_multiply_col_by_const(col, k)
if op == "n<->m":
return self._eval_col_op_swap(col1, col2)
if op == "n->n+km":
return self._eval_col_op_add_multiple_to_other_col(col, k, col2)
[docs] def elementary_row_op(self, op="n->kn", row=None, k=None, row1=None, row2=None):
"""Performs the elementary row operation `op`.
`op` may be one of
* "n->kn" (row n goes to k*n)
* "n<->m" (swap row n and row m)
* "n->n+km" (row n goes to row n + k*row m)
Parameters
==========
op : string; the elementary row operation
row : the row to apply the row operation
k : the multiple to apply in the row operation
row1 : one row of a row swap
row2 : second row of a row swap or row "m" in the row operation
"n->n+km"
"""
op, row, k, row1, row2 = self._normalize_op_args(op, row, k, row1, row2, "row")
# now that we've validated, we're all good to dispatch
if op == "n->kn":
return self._eval_row_op_multiply_row_by_const(row, k)
if op == "n<->m":
return self._eval_row_op_swap(row1, row2)
if op == "n->n+km":
return self._eval_row_op_add_multiple_to_other_row(row, k, row2)
@property
def is_echelon(self, iszerofunc=_iszero):
"""Returns `True` if the matrix is in echelon form.
That is, all rows of zeros are at the bottom, and below
each leading non-zero in a row are exclusively zeros."""
return self._eval_is_echelon(iszerofunc)
[docs] def rank(self, iszerofunc=_iszero, simplify=False):
"""
Returns the rank of a matrix
>>> from sympy import Matrix
>>> from sympy.abc import x
>>> m = Matrix([[1, 2], [x, 1 - 1/x]])
>>> m.rank()
2
>>> n = Matrix(3, 3, range(1, 10))
>>> n.rank()
2
"""
simpfunc = simplify if isinstance(
simplify, FunctionType) else _simplify
# for small matrices, we compute the rank explicitly
# if is_zero on elements doesn't answer the question
# for small matrices, we fall back to the full routine.
if self.rows <= 0 or self.cols <= 0:
return 0
if self.rows <= 1 or self.cols <= 1:
zeros = [iszerofunc(x) for x in self]
if False in zeros:
return 1
if self.rows == 2 and self.cols == 2:
zeros = [iszerofunc(x) for x in self]
if not False in zeros and not None in zeros:
return 0
det = self.det()
if iszerofunc(det) and False in zeros:
return 1
if iszerofunc(det) is False:
return 2
mat, _ = self._permute_complexity_right(iszerofunc=iszerofunc)
echelon_form, pivots, swaps = mat._eval_echelon_form(iszerofunc=iszerofunc, simpfunc=simpfunc)
return len(pivots)
[docs] def rref(self, iszerofunc=_iszero, simplify=False, pivots=True, normalize_last=True):
"""Return reduced row-echelon form of matrix and indices of pivot vars.
Parameters
==========
iszerofunc : Function
A function used for detecting whether an element can
act as a pivot. ``lambda x: x.is_zero`` is used by default.
simplify : Function
A function used to simplify elements when looking for a pivot.
By default SymPy's ``simplify`` is used.
pivots : True or False
If ``True``, a tuple containing the row-reduced matrix and a tuple
of pivot columns is returned. If ``False`` just the row-reduced
matrix is returned.
normalize_last : True or False
If ``True``, no pivots are normalized to `1` until after all
entries above and below each pivot are zeroed. This means the row
reduction algorithm is fraction free until the very last step.
If ``False``, the naive row reduction procedure is used where
each pivot is normalized to be `1` before row operations are
used to zero above and below the pivot.
Notes
=====
The default value of ``normalize_last=True`` can provide significant
speedup to row reduction, especially on matrices with symbols. However,
if you depend on the form row reduction algorithm leaves entries
of the matrix, set ``noramlize_last=False``
Examples
========
>>> from sympy import Matrix
>>> from sympy.abc import x
>>> m = Matrix([[1, 2], [x, 1 - 1/x]])
>>> m.rref()
(Matrix([
[1, 0],
[0, 1]]), (0, 1))
>>> rref_matrix, rref_pivots = m.rref()
>>> rref_matrix
Matrix([
[1, 0],
[0, 1]])
>>> rref_pivots
(0, 1)
"""
simpfunc = simplify if isinstance(
simplify, FunctionType) else _simplify
ret, pivot_cols = self._eval_rref(iszerofunc=iszerofunc,
simpfunc=simpfunc,
normalize_last=normalize_last)
if pivots:
ret = (ret, pivot_cols)
return ret
[docs]class MatrixSubspaces(MatrixReductions):
"""Provides methods relating to the fundamental subspaces
of a matrix. Should not be instantiated directly."""
[docs] def columnspace(self, simplify=False):
"""Returns a list of vectors (Matrix objects) that span columnspace of ``self``
Examples
========
>>> from sympy.matrices import Matrix
>>> m = Matrix(3, 3, [1, 3, 0, -2, -6, 0, 3, 9, 6])
>>> m
Matrix([
[ 1, 3, 0],
[-2, -6, 0],
[ 3, 9, 6]])
>>> m.columnspace()
[Matrix([
[ 1],
[-2],
[ 3]]), Matrix([
[0],
[0],
[6]])]
See Also
========
nullspace
rowspace
"""
reduced, pivots = self.echelon_form(simplify=simplify, with_pivots=True)
return [self.col(i) for i in pivots]
[docs] def nullspace(self, simplify=False, iszerofunc=_iszero):
"""Returns list of vectors (Matrix objects) that span nullspace of ``self``
Examples
========
>>> from sympy.matrices import Matrix
>>> m = Matrix(3, 3, [1, 3, 0, -2, -6, 0, 3, 9, 6])
>>> m
Matrix([
[ 1, 3, 0],
[-2, -6, 0],
[ 3, 9, 6]])
>>> m.nullspace()
[Matrix([
[-3],
[ 1],
[ 0]])]
See Also
========
columnspace
rowspace
"""
reduced, pivots = self.rref(iszerofunc=iszerofunc, simplify=simplify)
free_vars = [i for i in range(self.cols) if i not in pivots]
basis = []
for free_var in free_vars:
# for each free variable, we will set it to 1 and all others
# to 0. Then, we will use back substitution to solve the system
vec = [S.Zero]*self.cols
vec[free_var] = S.One
for piv_row, piv_col in enumerate(pivots):
vec[piv_col] -= reduced[piv_row, free_var]
basis.append(vec)
return [self._new(self.cols, 1, b) for b in basis]
[docs] def rowspace(self, simplify=False):
"""Returns a list of vectors that span the row space of ``self``."""
reduced, pivots = self.echelon_form(simplify=simplify, with_pivots=True)
return [reduced.row(i) for i in range(len(pivots))]
[docs] @classmethod
def orthogonalize(cls, *vecs, **kwargs):
"""Apply the Gram-Schmidt orthogonalization procedure
to vectors supplied in ``vecs``.
Parameters
==========
vecs
vectors to be made orthogonal
normalize : bool
If true, return an orthonormal basis.
"""
normalize = kwargs.get('normalize', False)
def project(a, b):
return b * (a.dot(b) / b.dot(b))
def perp_to_subspace(vec, basis):
"""projects vec onto the subspace given
by the orthogonal basis ``basis``"""
components = [project(vec, b) for b in basis]
if len(basis) == 0:
return vec
return vec - reduce(lambda a, b: a + b, components)
ret = []
# make sure we start with a non-zero vector
vecs = list(vecs)
while len(vecs) > 0 and vecs[0].is_zero:
del vecs[0]
for vec in vecs:
perp = perp_to_subspace(vec, ret)
if not perp.is_zero:
ret.append(perp)
if normalize:
ret = [vec / vec.norm() for vec in ret]
return ret
[docs]class MatrixEigen(MatrixSubspaces):
"""Provides basic matrix eigenvalue/vector operations.
Should not be instantiated directly."""
@property
def _cache_is_diagonalizable(self):
SymPyDeprecationWarning(
feature='_cache_is_diagonalizable',
deprecated_since_version="1.4",
issue=15887
).warn()
return None
@property
def _cache_eigenvects(self):
SymPyDeprecationWarning(
feature='_cache_eigenvects',
deprecated_since_version="1.4",
issue=15887
).warn()
return None
[docs] def diagonalize(self, reals_only=False, sort=False, normalize=False):
"""
Return (P, D), where D is diagonal and
D = P^-1 * M * P
where M is current matrix.
Parameters
==========
reals_only : bool. Whether to throw an error if complex numbers are need
to diagonalize. (Default: False)
sort : bool. Sort the eigenvalues along the diagonal. (Default: False)
normalize : bool. If True, normalize the columns of P. (Default: False)
Examples
========
>>> from sympy import Matrix
>>> m = Matrix(3, 3, [1, 2, 0, 0, 3, 0, 2, -4, 2])
>>> m
Matrix([
[1, 2, 0],
[0, 3, 0],
[2, -4, 2]])
>>> (P, D) = m.diagonalize()
>>> D
Matrix([
[1, 0, 0],
[0, 2, 0],
[0, 0, 3]])
>>> P
Matrix([
[-1, 0, -1],
[ 0, 0, -1],
[ 2, 1, 2]])
>>> P.inv() * m * P
Matrix([
[1, 0, 0],
[0, 2, 0],
[0, 0, 3]])
See Also
========
is_diagonal
is_diagonalizable
"""
if not self.is_square:
raise NonSquareMatrixError()
if not self.is_diagonalizable(reals_only=reals_only):
raise MatrixError("Matrix is not diagonalizable")
eigenvecs = self.eigenvects(simplify=True)
if sort:
eigenvecs = sorted(eigenvecs, key=default_sort_key)
p_cols, diag = [], []
for val, mult, basis in eigenvecs:
diag += [val] * mult
p_cols += basis
if normalize:
p_cols = [v / v.norm() for v in p_cols]
return self.hstack(*p_cols), self.diag(*diag)
[docs] def eigenvals(self, error_when_incomplete=True, **flags):
r"""Return eigenvalues using the Berkowitz agorithm to compute
the characteristic polynomial.
Parameters
==========
error_when_incomplete : bool, optional
If it is set to ``True``, it will raise an error if not all
eigenvalues are computed. This is caused by ``roots`` not returning
a full list of eigenvalues.
simplify : bool or function, optional
If it is set to ``True``, it attempts to return the most
simplified form of expressions returned by applying default
simplification method in every routine.
If it is set to ``False``, it will skip simplification in this
particular routine to save computation resources.
If a function is passed to, it will attempt to apply
the particular function as simplification method.
rational : bool, optional
If it is set to ``True``, every floating point numbers would be
replaced with rationals before computation. It can solve some
issues of ``roots`` routine not working well with floats.
multiple : bool, optional
If it is set to ``True``, the result will be in the form of a
list.
If it is set to ``False``, the result will be in the form of a
dictionary.
Returns
=======
eigs : list or dict
Eigenvalues of a matrix. The return format would be specified by
the key ``multiple``.
Raises
======
MatrixError
If not enough roots had got computed.
NonSquareMatrixError
If attempted to compute eigenvalues from a non-square matrix.
See Also
========
MatrixDeterminant.charpoly
eigenvects
Notes
=====
Eigenvalues of a matrix `A` can be computed by solving a matrix
equation `\det(A - \lambda I) = 0`
"""
simplify = flags.get('simplify', False) # Collect simplify flag before popped up, to reuse later in the routine.
multiple = flags.get('multiple', False) # Collect multiple flag to decide whether return as a dict or list.
rational = flags.pop('rational', True)
mat = self
if not mat:
return {}
if rational:
mat = mat.applyfunc(
lambda x: nsimplify(x, rational=True) if x.has(Float) else x)
if mat.is_upper or mat.is_lower:
if not self.is_square:
raise NonSquareMatrixError()
diagonal_entries = [mat[i, i] for i in range(mat.rows)]
if multiple:
eigs = diagonal_entries
else:
eigs = {}
for diagonal_entry in diagonal_entries:
if diagonal_entry not in eigs:
eigs[diagonal_entry] = 0
eigs[diagonal_entry] += 1
else:
flags.pop('simplify', None) # pop unsupported flag
if isinstance(simplify, FunctionType):
eigs = roots(mat.charpoly(x=Dummy('x'), simplify=simplify), **flags)
else:
eigs = roots(mat.charpoly(x=Dummy('x')), **flags)
# make sure the algebraic multiplicty sums to the
# size of the matrix
if error_when_incomplete and (sum(eigs.values()) if
isinstance(eigs, dict) else len(eigs)) != self.cols:
raise MatrixError("Could not compute eigenvalues for {}".format(self))
# Since 'simplify' flag is unsupported in roots()
# simplify() function will be applied once at the end of the routine.
if not simplify:
return eigs
if not isinstance(simplify, FunctionType):
simplify = _simplify
# With 'multiple' flag set true, simplify() will be mapped for the list
# Otherwise, simplify() will be mapped for the keys of the dictionary
if not multiple:
return {simplify(key): value for key, value in eigs.items()}
else:
return [simplify(value) for value in eigs]
[docs] def eigenvects(self, error_when_incomplete=True, iszerofunc=_iszero, **flags):
"""Return list of triples (eigenval, multiplicity, eigenspace).
Parameters
==========
error_when_incomplete : bool, optional
Raise an error when not all eigenvalues are computed. This is
caused by ``roots`` not returning a full list of eigenvalues.
iszerofunc : function, optional
Specifies a zero testing function to be used in ``rref``.
Default value is ``_iszero``, which uses SymPy's naive and fast
default assumption handler.
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
is tested as non-zero, and ``None`` if it is undecidable.
simplify : bool or function, optional
If ``True``, ``as_content_primitive()`` will be used to tidy up
normalization artifacts.
It will also be used by the ``nullspace`` routine.
chop : bool or positive number, optional
If the matrix contains any Floats, they will be changed to Rationals
for computation purposes, but the answers will be returned after
being evaluated with evalf. The ``chop`` flag is passed to ``evalf``.
When ``chop=True`` a default precision will be used; a number will
be interpreted as the desired level of precision.
Returns
=======
ret : [(eigenval, multiplicity, eigenspace), ...]
A ragged list containing tuples of data obtained by ``eigenvals``
and ``nullspace``.
``eigenspace`` is a list containing the ``eigenvector`` for each
eigenvalue.
``eigenvector`` is a vector in the form of a ``Matrix``. e.g.
a vector of length 3 is returned as ``Matrix([a_1, a_2, a_3])``.
Raises
======
NotImplementedError
If failed to compute nullspace.
See Also
========
eigenvals
MatrixSubspaces.nullspace
"""
from sympy.matrices import eye
simplify = flags.get('simplify', True)
if not isinstance(simplify, FunctionType):
simpfunc = _simplify if simplify else lambda x: x
primitive = flags.get('simplify', False)
chop = flags.pop('chop', False)
flags.pop('multiple', None) # remove this if it's there
mat = self
# roots doesn't like Floats, so replace them with Rationals
has_floats = self.has(Float)
if has_floats:
mat = mat.applyfunc(lambda x: nsimplify(x, rational=True))
def eigenspace(eigenval):
"""Get a basis for the eigenspace for a particular eigenvalue"""
m = mat - self.eye(mat.rows) * eigenval
ret = m.nullspace(iszerofunc=iszerofunc)
# the nullspace for a real eigenvalue should be
# non-trivial. If we didn't find an eigenvector, try once
# more a little harder
if len(ret) == 0 and simplify:
ret = m.nullspace(iszerofunc=iszerofunc, simplify=True)
if len(ret) == 0:
raise NotImplementedError(
"Can't evaluate eigenvector for eigenvalue %s" % eigenval)
return ret
eigenvals = mat.eigenvals(rational=False,
error_when_incomplete=error_when_incomplete,
**flags)
ret = [(val, mult, eigenspace(val)) for val, mult in
sorted(eigenvals.items(), key=default_sort_key)]
if primitive:
# if the primitive flag is set, get rid of any common
# integer denominators
def denom_clean(l):
from sympy import gcd
return [(v / gcd(list(v))).applyfunc(simpfunc) for v in l]
ret = [(val, mult, denom_clean(es)) for val, mult, es in ret]
if has_floats:
# if we had floats to start with, turn the eigenvectors to floats
ret = [(val.evalf(chop=chop), mult, [v.evalf(chop=chop) for v in es]) for val, mult, es in ret]
return ret
[docs] def is_diagonalizable(self, reals_only=False, **kwargs):
"""Returns true if a matrix is diagonalizable.
Parameters
==========
reals_only : bool. If reals_only=True, determine whether the matrix can be
diagonalized without complex numbers. (Default: False)
kwargs
======
clear_cache : bool. If True, clear the result of any computations when finished.
(Default: True)
Examples
========
>>> from sympy import Matrix
>>> m = Matrix(3, 3, [1, 2, 0, 0, 3, 0, 2, -4, 2])
>>> m
Matrix([
[1, 2, 0],
[0, 3, 0],
[2, -4, 2]])
>>> m.is_diagonalizable()
True
>>> m = Matrix(2, 2, [0, 1, 0, 0])
>>> m
Matrix([
[0, 1],
[0, 0]])
>>> m.is_diagonalizable()
False
>>> m = Matrix(2, 2, [0, 1, -1, 0])
>>> m
Matrix([
[ 0, 1],
[-1, 0]])
>>> m.is_diagonalizable()
True
>>> m.is_diagonalizable(reals_only=True)
False
See Also
========
is_diagonal
diagonalize
"""
if 'clear_cache' in kwargs:
SymPyDeprecationWarning(
feature='clear_cache',
deprecated_since_version=1.4,
issue=15887
).warn()
if 'clear_subproducts' in kwargs:
SymPyDeprecationWarning(
feature='clear_subproducts',
deprecated_since_version=1.4,
issue=15887
).warn()
if not self.is_square:
return False
if all(e.is_real for e in self) and self.is_symmetric():
# every real symmetric matrix is real diagonalizable
return True
eigenvecs = self.eigenvects(simplify=True)
ret = True
for val, mult, basis in eigenvecs:
# if we have a complex eigenvalue
if reals_only and not val.is_real:
ret = False
# if the geometric multiplicity doesn't equal the algebraic
if mult != len(basis):
ret = False
return ret
[docs] def left_eigenvects(self, **flags):
"""Returns left eigenvectors and eigenvalues.
This function returns the list of triples (eigenval, multiplicity,
basis) for the left eigenvectors. Options are the same as for
eigenvects(), i.e. the ``**flags`` arguments gets passed directly to
eigenvects().
Examples
========
>>> from sympy import Matrix
>>> M = Matrix([[0, 1, 1], [1, 0, 0], [1, 1, 1]])
>>> M.eigenvects()
[(-1, 1, [Matrix([
[-1],
[ 1],
[ 0]])]), (0, 1, [Matrix([
[ 0],
[-1],
[ 1]])]), (2, 1, [Matrix([
[2/3],
[1/3],
[ 1]])])]
>>> M.left_eigenvects()
[(-1, 1, [Matrix([[-2, 1, 1]])]), (0, 1, [Matrix([[-1, -1, 1]])]), (2,
1, [Matrix([[1, 1, 1]])])]
"""
eigs = self.transpose().eigenvects(**flags)
return [(val, mult, [l.transpose() for l in basis]) for val, mult, basis in eigs]
[docs] def singular_values(self):
"""Compute the singular values of a Matrix
Examples
========
>>> from sympy import Matrix, Symbol
>>> x = Symbol('x', real=True)
>>> A = Matrix([[0, 1, 0], [0, x, 0], [-1, 0, 0]])
>>> A.singular_values()
[sqrt(x**2 + 1), 1, 0]
See Also
========
condition_number
"""
mat = self
if self.rows >= self.cols:
valmultpairs = (mat.H * mat).eigenvals()
else:
valmultpairs = (mat * mat.H).eigenvals()
# Expands result from eigenvals into a simple list
vals = []
for k, v in valmultpairs.items():
vals += [sqrt(k)] * v # dangerous! same k in several spots!
# Pad with zeros if singular values are computed in reverse way,
# to give consistent format.
if len(vals) < self.cols:
vals += [S.Zero] * (self.cols - len(vals))
# sort them in descending order
vals.sort(reverse=True, key=default_sort_key)
return vals
[docs]class MatrixCalculus(MatrixCommon):
"""Provides calculus-related matrix operations."""
[docs] def diff(self, *args, **kwargs):
"""Calculate the derivative of each element in the matrix.
``args`` will be passed to the ``integrate`` function.
Examples
========
>>> from sympy.matrices import Matrix
>>> from sympy.abc import x, y
>>> M = Matrix([[x, y], [1, 0]])
>>> M.diff(x)
Matrix([
[1, 0],
[0, 0]])
See Also
========
integrate
limit
"""
# XXX this should be handled here rather than in Derivative
from sympy import Derivative
kwargs.setdefault('evaluate', True)
deriv = Derivative(self, *args, evaluate=True)
if not isinstance(self, Basic):
return deriv.as_mutable()
else:
return deriv
def _eval_derivative(self, arg):
return self.applyfunc(lambda x: x.diff(arg))
def _accept_eval_derivative(self, s):
return s._visit_eval_derivative_array(self)
def _visit_eval_derivative_scalar(self, base):
# Types are (base: scalar, self: matrix)
return self.applyfunc(lambda x: base.diff(x))
def _visit_eval_derivative_array(self, base):
# Types are (base: array/matrix, self: matrix)
from sympy import derive_by_array
return derive_by_array(base, self)
[docs] def integrate(self, *args):
"""Integrate each element of the matrix. ``args`` will
be passed to the ``integrate`` function.
Examples
========
>>> from sympy.matrices import Matrix
>>> from sympy.abc import x, y
>>> M = Matrix([[x, y], [1, 0]])
>>> M.integrate((x, ))
Matrix([
[x**2/2, x*y],
[ x, 0]])
>>> M.integrate((x, 0, 2))
Matrix([
[2, 2*y],
[2, 0]])
See Also
========
limit
diff
"""
return self.applyfunc(lambda x: x.integrate(*args))
[docs] def jacobian(self, X):
"""Calculates the Jacobian matrix (derivative of a vector-valued function).
Parameters
==========
``self`` : vector of expressions representing functions f_i(x_1, ..., x_n).
X : set of x_i's in order, it can be a list or a Matrix
Both ``self`` and X can be a row or a column matrix in any order
(i.e., jacobian() should always work).
Examples
========
>>> from sympy import sin, cos, Matrix
>>> from sympy.abc import rho, phi
>>> X = Matrix([rho*cos(phi), rho*sin(phi), rho**2])
>>> Y = Matrix([rho, phi])
>>> X.jacobian(Y)
Matrix([
[cos(phi), -rho*sin(phi)],
[sin(phi), rho*cos(phi)],
[ 2*rho, 0]])
>>> X = Matrix([rho*cos(phi), rho*sin(phi)])
>>> X.jacobian(Y)
Matrix([
[cos(phi), -rho*sin(phi)],
[sin(phi), rho*cos(phi)]])
See Also
========
hessian
wronskian
"""
if not isinstance(X, MatrixBase):
X = self._new(X)
# Both X and ``self`` can be a row or a column matrix, so we need to make
# sure all valid combinations work, but everything else fails:
if self.shape[0] == 1:
m = self.shape[1]
elif self.shape[1] == 1:
m = self.shape[0]
else:
raise TypeError("``self`` must be a row or a column matrix")
if X.shape[0] == 1:
n = X.shape[1]
elif X.shape[1] == 1:
n = X.shape[0]
else:
raise TypeError("X must be a row or a column matrix")
# m is the number of functions and n is the number of variables
# computing the Jacobian is now easy:
return self._new(m, n, lambda j, i: self[j].diff(X[i]))
[docs] def limit(self, *args):
"""Calculate the limit of each element in the matrix.
``args`` will be passed to the ``limit`` function.
Examples
========
>>> from sympy.matrices import Matrix
>>> from sympy.abc import x, y
>>> M = Matrix([[x, y], [1, 0]])
>>> M.limit(x, 2)
Matrix([
[2, y],
[1, 0]])
See Also
========
integrate
diff
"""
return self.applyfunc(lambda x: x.limit(*args))
# https://github.com/sympy/sympy/pull/12854
class MatrixDeprecated(MatrixCommon):
"""A class to house deprecated matrix methods."""
def _legacy_array_dot(self, b):
"""Compatibility function for deprecated behavior of ``matrix.dot(vector)``
"""
from .dense import Matrix
if not isinstance(b, MatrixBase):
if is_sequence(b):
if len(b) != self.cols and len(b) != self.rows:
raise ShapeError(
"Dimensions incorrect for dot product: %s, %s" % (
self.shape, len(b)))
return self.dot(Matrix(b))
else:
raise TypeError(
"`b` must be an ordered iterable or Matrix, not %s." %
type(b))
mat = self
if mat.cols == b.rows:
if b.cols != 1:
mat = mat.T
b = b.T
prod = flatten((mat * b).tolist())
return prod
if mat.cols == b.cols:
return mat.dot(b.T)
elif mat.rows == b.rows:
return mat.T.dot(b)
else:
raise ShapeError("Dimensions incorrect for dot product: %s, %s" % (
self.shape, b.shape))
def berkowitz_charpoly(self, x=Dummy('lambda'), simplify=_simplify):
return self.charpoly(x=x)
def berkowitz_det(self):
"""Computes determinant using Berkowitz method.
See Also
========
det
berkowitz
"""
return self.det(method='berkowitz')
def berkowitz_eigenvals(self, **flags):
"""Computes eigenvalues of a Matrix using Berkowitz method.
See Also
========
berkowitz
"""
return self.eigenvals(**flags)
def berkowitz_minors(self):
"""Computes principal minors using Berkowitz method.
See Also
========
berkowitz
"""
sign, minors = S.One, []
for poly in self.berkowitz():
minors.append(sign * poly[-1])
sign = -sign
return tuple(minors)
def berkowitz(self):
from sympy.matrices import zeros
berk = ((1,),)
if not self:
return berk
if not self.is_square:
raise NonSquareMatrixError()
A, N = self, self.rows
transforms = [0] * (N - 1)
for n in range(N, 1, -1):
T, k = zeros(n + 1, n), n - 1
R, C = -A[k, :k], A[:k, k]
A, a = A[:k, :k], -A[k, k]
items = [C]
for i in range(0, n - 2):
items.append(A * items[i])
for i, B in enumerate(items):
items[i] = (R * B)[0, 0]
items = [S.One, a] + items
for i in range(n):
T[i:, i] = items[:n - i + 1]
transforms[k - 1] = T
polys = [self._new([S.One, -A[0, 0]])]
for i, T in enumerate(transforms):
polys.append(T * polys[i])
return berk + tuple(map(tuple, polys))
def cofactorMatrix(self, method="berkowitz"):
return self.cofactor_matrix(method=method)
def det_bareis(self):
return self.det(method='bareiss')
def det_bareiss(self):
"""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.
TODO: Implement algorithm for sparse matrices (SFF),
http://www.eecis.udel.edu/~saunders/papers/sffge/it5.ps.
See Also
========
det
berkowitz_det
"""
return self.det(method='bareiss')
def det_LU_decomposition(self):
"""Compute matrix determinant using LU decomposition
Note that this method fails if the LU decomposition itself
fails. In particular, if the matrix has no inverse this method
will fail.
TODO: Implement algorithm for sparse matrices (SFF),
http://www.eecis.udel.edu/~saunders/papers/sffge/it5.ps.
See Also
========
det
det_bareiss
berkowitz_det
"""
return self.det(method='lu')
def jordan_cell(self, eigenval, n):
return self.jordan_block(size=n, eigenvalue=eigenval)
def jordan_cells(self, calc_transformation=True):
P, J = self.jordan_form()
return P, J.get_diag_blocks()
def minorEntry(self, i, j, method="berkowitz"):
return self.minor(i, j, method=method)
def minorMatrix(self, i, j):
return self.minor_submatrix(i, j)
def permuteBkwd(self, perm):
"""Permute the rows of the matrix with the given permutation in reverse."""
return self.permute_rows(perm, direction='backward')
def permuteFwd(self, perm):
"""Permute the rows of the matrix with the given permutation."""
return self.permute_rows(perm, direction='forward')
[docs]class MatrixBase(MatrixDeprecated,
MatrixCalculus,
MatrixEigen,
MatrixCommon):
"""Base class for matrix objects."""
# Added just for numpy compatibility
__array_priority__ = 11
is_Matrix = True
_class_priority = 3
_sympify = staticmethod(sympify)
__hash__ = None # Mutable
# Defined here the same as on Basic.
# We don't define _repr_png_ here because it would add a large amount of
# data to any notebook containing SymPy expressions, without adding
# anything useful to the notebook. It can still enabled manually, e.g.,
# for the qtconsole, with init_printing().
def _repr_latex_(self):
"""
IPython/Jupyter LaTeX printing
To change the behavior of this (e.g., pass in some settings to LaTeX),
use init_printing(). init_printing() will also enable LaTeX printing
for built in numeric types like ints and container types that contain
SymPy objects, like lists and dictionaries of expressions.
"""
from sympy.printing.latex import latex
s = latex(self, mode='plain')
return "$\\displaystyle %s$" % s
_repr_latex_orig = _repr_latex_
def __array__(self, dtype=object):
from .dense import matrix2numpy
return matrix2numpy(self, dtype=dtype)
def __getattr__(self, attr):
if attr in ('diff', 'integrate', 'limit'):
def doit(*args):
item_doit = lambda item: getattr(item, attr)(*args)
return self.applyfunc(item_doit)
return doit
else:
raise AttributeError(
"%s has no attribute %s." % (self.__class__.__name__, attr))
def __len__(self):
"""Return the number of elements of ``self``.
Implemented mainly so bool(Matrix()) == False.
"""
return self.rows * self.cols
def __mathml__(self):
mml = ""
for i in range(self.rows):
mml += "<matrixrow>"
for j in range(self.cols):
mml += self[i, j].__mathml__()
mml += "</matrixrow>"
return "<matrix>" + mml + "</matrix>"
# needed for python 2 compatibility
def __ne__(self, other):
return not self == other
def _matrix_pow_by_jordan_blocks(self, num):
from sympy.matrices import diag, MutableMatrix
from sympy import binomial
def jordan_cell_power(jc, n):
N = jc.shape[0]
l = jc[0, 0]
if l == 0 and (n < N - 1) != False:
raise ValueError("Matrix det == 0; not invertible")
elif l == 0 and N > 1 and n % 1 != 0:
raise ValueError("Non-integer power cannot be evaluated")
for i in range(N):
for j in range(N-i):
bn = binomial(n, i)
if isinstance(bn, binomial):
bn = bn._eval_expand_func()
jc[j, i+j] = l**(n-i)*bn
P, J = self.jordan_form()
jordan_cells = J.get_diag_blocks()
# Make sure jordan_cells matrices are mutable:
jordan_cells = [MutableMatrix(j) for j in jordan_cells]
for j in jordan_cells:
jordan_cell_power(j, num)
return self._new(P*diag(*jordan_cells)*P.inv())
def __repr__(self):
return sstr(self)
def __str__(self):
if self.rows == 0 or self.cols == 0:
return 'Matrix(%s, %s, [])' % (self.rows, self.cols)
return "Matrix(%s)" % str(self.tolist())
def _diagonalize_clear_subproducts(self):
del self._is_symbolic
del self._is_symmetric
del self._eigenvects
def _format_str(self, printer=None):
if not printer:
from sympy.printing.str import StrPrinter
printer = StrPrinter()
# Handle zero dimensions:
if self.rows == 0 or self.cols == 0:
return 'Matrix(%s, %s, [])' % (self.rows, self.cols)
if self.rows == 1:
return "Matrix([%s])" % self.table(printer, rowsep=',\n')
return "Matrix([\n%s])" % self.table(printer, rowsep=',\n')
@classmethod
def _handle_creation_inputs(cls, *args, **kwargs):
"""Return the number of rows, cols and flat matrix elements.
Examples
========
>>> from sympy import Matrix, I
Matrix can be constructed as follows:
* from a nested list of iterables
>>> Matrix( ((1, 2+I), (3, 4)) )
Matrix([
[1, 2 + I],
[3, 4]])
* from un-nested iterable (interpreted as a column)
>>> Matrix( [1, 2] )
Matrix([
[1],
[2]])
* from un-nested iterable with dimensions
>>> Matrix(1, 2, [1, 2] )
Matrix([[1, 2]])
* from no arguments (a 0 x 0 matrix)
>>> Matrix()
Matrix(0, 0, [])
* from a rule
>>> Matrix(2, 2, lambda i, j: i/(j + 1) )
Matrix([
[0, 0],
[1, 1/2]])
"""
from sympy.matrices.sparse import SparseMatrix
flat_list = None
if len(args) == 1:
# Matrix(SparseMatrix(...))
if isinstance(args[0], SparseMatrix):
return args[0].rows, args[0].cols, flatten(args[0].tolist())
# Matrix(Matrix(...))
elif isinstance(args[0], MatrixBase):
return args[0].rows, args[0].cols, args[0]._mat
# Matrix(MatrixSymbol('X', 2, 2))
elif isinstance(args[0], Basic) and args[0].is_Matrix:
return args[0].rows, args[0].cols, args[0].as_explicit()._mat
# Matrix(numpy.ones((2, 2)))
elif hasattr(args[0], "__array__"):
# NumPy array or matrix or some other object that implements
# __array__. So let's first use this method to get a
# numpy.array() and then make a python list out of it.
arr = args[0].__array__()
if len(arr.shape) == 2:
rows, cols = arr.shape[0], arr.shape[1]
flat_list = [cls._sympify(i) for i in arr.ravel()]
return rows, cols, flat_list
elif len(arr.shape) == 1:
rows, cols = arr.shape[0], 1
flat_list = [S.Zero] * rows
for i in range(len(arr)):
flat_list[i] = cls._sympify(arr[i])
return rows, cols, flat_list
else:
raise NotImplementedError(
"SymPy supports just 1D and 2D matrices")
# Matrix([1, 2, 3]) or Matrix([[1, 2], [3, 4]])
elif is_sequence(args[0]) \
and not isinstance(args[0], DeferredVector):
in_mat = []
ncol = set()
for row in args[0]:
if isinstance(row, MatrixBase):
in_mat.extend(row.tolist())
if row.cols or row.rows: # only pay attention if it's not 0x0
ncol.add(row.cols)
else:
in_mat.append(row)
try:
ncol.add(len(row))
except TypeError:
ncol.add(1)
if len(ncol) > 1:
raise ValueError("Got rows of variable lengths: %s" %
sorted(list(ncol)))
cols = ncol.pop() if ncol else 0
rows = len(in_mat) if cols else 0
if rows:
if not is_sequence(in_mat[0]):
cols = 1
flat_list = [cls._sympify(i) for i in in_mat]
return rows, cols, flat_list
flat_list = []
for j in range(rows):
for i in range(cols):
flat_list.append(cls._sympify(in_mat[j][i]))
elif len(args) == 3:
rows = as_int(args[0])
cols = as_int(args[1])
if rows < 0 or cols < 0:
raise ValueError("Cannot create a {} x {} matrix. "
"Both dimensions must be positive".format(rows, cols))
# Matrix(2, 2, lambda i, j: i+j)
if len(args) == 3 and isinstance(args[2], Callable):
op = args[2]
flat_list = []
for i in range(rows):
flat_list.extend(
[cls._sympify(op(cls._sympify(i), cls._sympify(j)))
for j in range(cols)])
# Matrix(2, 2, [1, 2, 3, 4])
elif len(args) == 3 and is_sequence(args[2]):
flat_list = args[2]
if len(flat_list) != rows * cols:
raise ValueError(
'List length should be equal to rows*columns')
flat_list = [cls._sympify(i) for i in flat_list]
# Matrix()
elif len(args) == 0:
# Empty Matrix
rows = cols = 0
flat_list = []
if flat_list is None:
raise TypeError("Data type not understood")
return rows, cols, flat_list
def _setitem(self, key, value):
"""Helper to set value at location given by key.
Examples
========
>>> from sympy import Matrix, I, zeros, ones
>>> m = Matrix(((1, 2+I), (3, 4)))
>>> m
Matrix([
[1, 2 + I],
[3, 4]])
>>> m[1, 0] = 9
>>> m
Matrix([
[1, 2 + I],
[9, 4]])
>>> m[1, 0] = [[0, 1]]
To replace row r you assign to position r*m where m
is the number of columns:
>>> M = zeros(4)
>>> m = M.cols
>>> M[3*m] = ones(1, m)*2; M
Matrix([
[0, 0, 0, 0],
[0, 0, 0, 0],
[0, 0, 0, 0],
[2, 2, 2, 2]])
And to replace column c you can assign to position c:
>>> M[2] = ones(m, 1)*4; M
Matrix([
[0, 0, 4, 0],
[0, 0, 4, 0],
[0, 0, 4, 0],
[2, 2, 4, 2]])
"""
from .dense import Matrix
is_slice = isinstance(key, slice)
i, j = key = self.key2ij(key)
is_mat = isinstance(value, MatrixBase)
if type(i) is slice or type(j) is slice:
if is_mat:
self.copyin_matrix(key, value)
return
if not isinstance(value, Expr) and is_sequence(value):
self.copyin_list(key, value)
return
raise ValueError('unexpected value: %s' % value)
else:
if (not is_mat and
not isinstance(value, Basic) and is_sequence(value)):
value = Matrix(value)
is_mat = True
if is_mat:
if is_slice:
key = (slice(*divmod(i, self.cols)),
slice(*divmod(j, self.cols)))
else:
key = (slice(i, i + value.rows),
slice(j, j + value.cols))
self.copyin_matrix(key, value)
else:
return i, j, self._sympify(value)
return
[docs] def add(self, b):
"""Return self + b """
return self + b
[docs] def cholesky_solve(self, rhs):
"""Solves ``Ax = B`` using Cholesky decomposition,
for a general square non-singular matrix.
For a non-square matrix with rows > cols,
the least squares solution is returned.
See Also
========
lower_triangular_solve
upper_triangular_solve
gauss_jordan_solve
diagonal_solve
LDLsolve
LUsolve
QRsolve
pinv_solve
"""
hermitian = True
if self.is_symmetric():
hermitian = False
L = self._cholesky(hermitian=hermitian)
elif self.is_hermitian:
L = self._cholesky(hermitian=hermitian)
elif self.rows >= self.cols:
L = (self.H * self)._cholesky(hermitian=hermitian)
rhs = self.H * rhs
else:
raise NotImplementedError('Under-determined System. '
'Try M.gauss_jordan_solve(rhs)')
Y = L._lower_triangular_solve(rhs)
if hermitian:
return (L.H)._upper_triangular_solve(Y)
else:
return (L.T)._upper_triangular_solve(Y)
[docs] def cholesky(self, hermitian=True):
"""Returns the Cholesky-type decomposition L of a matrix A
such that L * L.H == A if hermitian flag is True,
or L * L.T == A if hermitian is False.
A must be a Hermitian positive-definite matrix if hermitian is True,
or a symmetric matrix if it is False.
Examples
========
>>> from sympy.matrices import Matrix
>>> A = Matrix(((25, 15, -5), (15, 18, 0), (-5, 0, 11)))
>>> A.cholesky()
Matrix([
[ 5, 0, 0],
[ 3, 3, 0],
[-1, 1, 3]])
>>> A.cholesky() * A.cholesky().T
Matrix([
[25, 15, -5],
[15, 18, 0],
[-5, 0, 11]])
The matrix can have complex entries:
>>> from sympy import I
>>> A = Matrix(((9, 3*I), (-3*I, 5)))
>>> A.cholesky()
Matrix([
[ 3, 0],
[-I, 2]])
>>> A.cholesky() * A.cholesky().H
Matrix([
[ 9, 3*I],
[-3*I, 5]])
Non-hermitian Cholesky-type decomposition may be useful when the
matrix is not positive-definite.
>>> A = Matrix([[1, 2], [2, 1]])
>>> L = A.cholesky(hermitian=False)
>>> L
Matrix([
[1, 0],
[2, sqrt(3)*I]])
>>> L*L.T == A
True
See Also
========
LDLdecomposition
LUdecomposition
QRdecomposition
"""
if not self.is_square:
raise NonSquareMatrixError("Matrix must be square.")
if hermitian and not self.is_hermitian:
raise ValueError("Matrix must be Hermitian.")
if not hermitian and not self.is_symmetric():
raise ValueError("Matrix must be symmetric.")
return self._cholesky(hermitian=hermitian)
[docs] def condition_number(self):
"""Returns the condition number of a matrix.
This is the maximum singular value divided by the minimum singular value
Examples
========
>>> from sympy import Matrix, S
>>> A = Matrix([[1, 0, 0], [0, 10, 0], [0, 0, S.One/10]])
>>> A.condition_number()
100
See Also
========
singular_values
"""
if not self:
return S.Zero
singularvalues = self.singular_values()
return Max(*singularvalues) / Min(*singularvalues)
[docs] def copy(self):
"""
Returns the copy of a matrix.
Examples
========
>>> from sympy import Matrix
>>> A = Matrix(2, 2, [1, 2, 3, 4])
>>> A.copy()
Matrix([
[1, 2],
[3, 4]])
"""
return self._new(self.rows, self.cols, self._mat)
[docs] def cross(self, b):
r"""
Return the cross product of ``self`` and ``b`` relaxing the condition
of compatible dimensions: if each has 3 elements, a matrix of the
same type and shape as ``self`` will be returned. If ``b`` has the same
shape as ``self`` then common identities for the cross product (like
`a \times b = - b \times a`) will hold.
Parameters
==========
b : 3x1 or 1x3 Matrix
See Also
========
dot
multiply
multiply_elementwise
"""
if not is_sequence(b):
raise TypeError(
"`b` must be an ordered iterable or Matrix, not %s." %
type(b))
if not (self.rows * self.cols == b.rows * b.cols == 3):
raise ShapeError("Dimensions incorrect for cross product: %s x %s" %
((self.rows, self.cols), (b.rows, b.cols)))
else:
return self._new(self.rows, self.cols, (
(self[1] * b[2] - self[2] * b[1]),
(self[2] * b[0] - self[0] * b[2]),
(self[0] * b[1] - self[1] * b[0])))
@property
def D(self):
"""Return Dirac conjugate (if ``self.rows == 4``).
Examples
========
>>> from sympy import Matrix, I, eye
>>> m = Matrix((0, 1 + I, 2, 3))
>>> m.D
Matrix([[0, 1 - I, -2, -3]])
>>> m = (eye(4) + I*eye(4))
>>> m[0, 3] = 2
>>> m.D
Matrix([
[1 - I, 0, 0, 0],
[ 0, 1 - I, 0, 0],
[ 0, 0, -1 + I, 0],
[ 2, 0, 0, -1 + I]])
If the matrix does not have 4 rows an AttributeError will be raised
because this property is only defined for matrices with 4 rows.
>>> Matrix(eye(2)).D
Traceback (most recent call last):
...
AttributeError: Matrix has no attribute D.
See Also
========
conjugate: By-element conjugation
H: Hermite conjugation
"""
from sympy.physics.matrices import mgamma
if self.rows != 4:
# In Python 3.2, properties can only return an AttributeError
# so we can't raise a ShapeError -- see commit which added the
# first line of this inline comment. Also, there is no need
# for a message since MatrixBase will raise the AttributeError
raise AttributeError
return self.H * mgamma(0)
[docs] def diagonal_solve(self, rhs):
"""Solves ``Ax = B`` efficiently, where A is a diagonal Matrix,
with non-zero diagonal entries.
Examples
========
>>> from sympy.matrices import Matrix, eye
>>> A = eye(2)*2
>>> B = Matrix([[1, 2], [3, 4]])
>>> A.diagonal_solve(B) == B/2
True
See Also
========
lower_triangular_solve
upper_triangular_solve
gauss_jordan_solve
cholesky_solve
LDLsolve
LUsolve
QRsolve
pinv_solve
"""
if not self.is_diagonal():
raise TypeError("Matrix should be diagonal")
if rhs.rows != self.rows:
raise TypeError("Size mis-match")
return self._diagonal_solve(rhs)
[docs] def dot(self, b, hermitian=None, conjugate_convention=None):
"""Return the dot or inner product of two vectors of equal length.
Here ``self`` must be a ``Matrix`` of size 1 x n or n x 1, and ``b``
must be either a matrix of size 1 x n, n x 1, or a list/tuple of length n.
A scalar is returned.
By default, ``dot`` does not conjugate ``self`` or ``b``, even if there are
complex entries. Set ``hermitian=True`` (and optionally a ``conjugate_convention``)
to compute the hermitian inner product.
Possible kwargs are ``hermitian`` and ``conjugate_convention``.
If ``conjugate_convention`` is ``"left"``, ``"math"`` or ``"maths"``,
the conjugate of the first vector (``self``) is used. If ``"right"``
or ``"physics"`` is specified, the conjugate of the second vector ``b`` is used.
Examples
========
>>> from sympy import Matrix
>>> M = Matrix([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
>>> v = Matrix([1, 1, 1])
>>> M.row(0).dot(v)
6
>>> M.col(0).dot(v)
12
>>> v = [3, 2, 1]
>>> M.row(0).dot(v)
10
>>> from sympy import I
>>> q = Matrix([1*I, 1*I, 1*I])
>>> q.dot(q, hermitian=False)
-3
>>> q.dot(q, hermitian=True)
3
>>> q1 = Matrix([1, 1, 1*I])
>>> q.dot(q1, hermitian=True, conjugate_convention="maths")
1 - 2*I
>>> q.dot(q1, hermitian=True, conjugate_convention="physics")
1 + 2*I
See Also
========
cross
multiply
multiply_elementwise
"""
from .dense import Matrix
if not isinstance(b, MatrixBase):
if is_sequence(b):
if len(b) != self.cols and len(b) != self.rows:
raise ShapeError(
"Dimensions incorrect for dot product: %s, %s" % (
self.shape, len(b)))
return self.dot(Matrix(b))
else:
raise TypeError(
"`b` must be an ordered iterable or Matrix, not %s." %
type(b))
mat = self
if (1 not in mat.shape) or (1 not in b.shape) :
SymPyDeprecationWarning(
feature="Dot product of non row/column vectors",
issue=13815,
deprecated_since_version="1.2",
useinstead="* to take matrix products").warn()
return mat._legacy_array_dot(b)
if len(mat) != len(b):
raise ShapeError("Dimensions incorrect for dot product: %s, %s" % (self.shape, b.shape))
n = len(mat)
if mat.shape != (1, n):
mat = mat.reshape(1, n)
if b.shape != (n, 1):
b = b.reshape(n, 1)
# Now ``mat`` is a row vector and ``b`` is a column vector.
# If it so happens that only conjugate_convention is passed
# then automatically set hermitian to True. If only hermitian
# is true but no conjugate_convention is not passed then
# automatically set it to ``"maths"``
if conjugate_convention is not None and hermitian is None:
hermitian = True
if hermitian and conjugate_convention is None:
conjugate_convention = "maths"
if hermitian == True:
if conjugate_convention in ("maths", "left", "math"):
mat = mat.conjugate()
elif conjugate_convention in ("physics", "right"):
b = b.conjugate()
else:
raise ValueError("Unknown conjugate_convention was entered."
" conjugate_convention must be one of the"
" following: math, maths, left, physics or right.")
return (mat * b)[0]
[docs] def dual(self):
"""Returns the dual of a matrix, which is:
``(1/2)*levicivita(i, j, k, l)*M(k, l)`` summed over indices `k` and `l`
Since the levicivita method is anti_symmetric for any pairwise
exchange of indices, the dual of a symmetric matrix is the zero
matrix. Strictly speaking the dual defined here assumes that the
'matrix' `M` is a contravariant anti_symmetric second rank tensor,
so that the dual is a covariant second rank tensor.
"""
from sympy import LeviCivita
from sympy.matrices import zeros
M, n = self[:, :], self.rows
work = zeros(n)
if self.is_symmetric():
return work
for i in range(1, n):
for j in range(1, n):
acum = 0
for k in range(1, n):
acum += LeviCivita(i, j, 0, k) * M[0, k]
work[i, j] = acum
work[j, i] = -acum
for l in range(1, n):
acum = 0
for a in range(1, n):
for b in range(1, n):
acum += LeviCivita(0, l, a, b) * M[a, b]
acum /= 2
work[0, l] = -acum
work[l, 0] = acum
return work
[docs] def exp(self):
"""Return the exponentiation of a square matrix."""
if not self.is_square:
raise NonSquareMatrixError(
"Exponentiation is valid only for square matrices")
try:
P, J = self.jordan_form()
cells = J.get_diag_blocks()
except MatrixError:
raise NotImplementedError(
"Exponentiation is implemented only for matrices for which the Jordan normal form can be computed")
def _jblock_exponential(b):
# This function computes the matrix exponential for one single Jordan block
nr = b.rows
l = b[0, 0]
if nr == 1:
res = exp(l)
else:
from sympy import eye
# extract the diagonal part
d = b[0, 0] * eye(nr)
# and the nilpotent part
n = b - d
# compute its exponential
nex = eye(nr)
for i in range(1, nr):
nex = nex + n ** i / factorial(i)
# combine the two parts
res = exp(b[0, 0]) * nex
return (res)
blocks = list(map(_jblock_exponential, cells))
from sympy.matrices import diag
from sympy import re
eJ = diag(*blocks)
# n = self.rows
ret = P * eJ * P.inv()
if all(value.is_real for value in self.values()):
return type(self)(re(ret))
else:
return type(self)(ret)
[docs] def gauss_jordan_solve(self, B, freevar=False):
"""
Solves ``Ax = B`` using Gauss Jordan elimination.
There may be zero, one, or infinite solutions. If one solution
exists, it will be returned. If infinite solutions exist, it will
be returned parametrically. If no solutions exist, It will throw
ValueError.
Parameters
==========
B : Matrix
The right hand side of the equation to be solved for. Must have
the same number of rows as matrix A.
freevar : List
If the system is underdetermined (e.g. A has more columns than
rows), infinite solutions are possible, in terms of arbitrary
values of free variables. Then the index of the free variables
in the solutions (column Matrix) will be returned by freevar, if
the flag `freevar` is set to `True`.
Returns
=======
x : Matrix
The matrix that will satisfy ``Ax = B``. Will have as many rows as
matrix A has columns, and as many columns as matrix B.
params : Matrix
If the system is underdetermined (e.g. A has more columns than
rows), infinite solutions are possible, in terms of arbitrary
parameters. These arbitrary parameters are returned as params
Matrix.
Examples
========
>>> from sympy import Matrix
>>> A = Matrix([[1, 2, 1, 1], [1, 2, 2, -1], [2, 4, 0, 6]])
>>> B = Matrix([7, 12, 4])
>>> sol, params = A.gauss_jordan_solve(B)
>>> sol
Matrix([
[-2*tau0 - 3*tau1 + 2],
[ tau0],
[ 2*tau1 + 5],
[ tau1]])
>>> params
Matrix([
[tau0],
[tau1]])
>>> taus_zeroes = { tau:0 for tau in params }
>>> sol_unique = sol.xreplace(taus_zeroes)
>>> sol_unique
Matrix([
[2],
[0],
[5],
[0]])
>>> A = Matrix([[1, 2, 3], [4, 5, 6], [7, 8, 10]])
>>> B = Matrix([3, 6, 9])
>>> sol, params = A.gauss_jordan_solve(B)
>>> sol
Matrix([
[-1],
[ 2],
[ 0]])
>>> params
Matrix(0, 1, [])
>>> A = Matrix([[2, -7], [-1, 4]])
>>> B = Matrix([[-21, 3], [12, -2]])
>>> sol, params = A.gauss_jordan_solve(B)
>>> sol
Matrix([
[0, -2],
[3, -1]])
>>> params
Matrix(0, 2, [])
See Also
========
lower_triangular_solve
upper_triangular_solve
cholesky_solve
diagonal_solve
LDLsolve
LUsolve
QRsolve
pinv
References
==========
.. [1] https://en.wikipedia.org/wiki/Gaussian_elimination
"""
from sympy.matrices import Matrix, zeros
aug = self.hstack(self.copy(), B.copy())
B_cols = B.cols
row, col = aug[:, :-B_cols].shape
# solve by reduced row echelon form
A, pivots = aug.rref(simplify=True)
A, v = A[:, :-B_cols], A[:, -B_cols:]
pivots = list(filter(lambda p: p < col, pivots))
rank = len(pivots)
# Bring to block form
permutation = Matrix(range(col)).T
for i, c in enumerate(pivots):
permutation.col_swap(i, c)
# check for existence of solutions
# rank of aug Matrix should be equal to rank of coefficient matrix
if not v[rank:, :].is_zero:
raise ValueError("Linear system has no solution")
# Get index of free symbols (free parameters)
free_var_index = permutation[
len(pivots):] # non-pivots columns are free variables
# Free parameters
# what are current unnumbered free symbol names?
name = _uniquely_named_symbol('tau', aug,
compare=lambda i: str(i).rstrip('1234567890')).name
gen = numbered_symbols(name)
tau = Matrix([next(gen) for k in range((col - rank)*B_cols)]).reshape(
col - rank, B_cols)
# Full parametric solution
V = A[:rank,:]
for c in reversed(pivots):
V.col_del(c)
vt = v[:rank, :]
free_sol = tau.vstack(vt - V * tau, tau)
# Undo permutation
sol = zeros(col, B_cols)
for k in range(col):
sol[permutation[k], :] = free_sol[k,:]
if freevar:
return sol, tau, free_var_index
else:
return sol, tau
[docs] def inv_mod(self, m):
r"""
Returns the inverse of the matrix `K` (mod `m`), if it exists.
Method to find the matrix inverse of `K` (mod `m`) implemented in this function:
* Compute `\mathrm{adj}(K) = \mathrm{cof}(K)^t`, the adjoint matrix of `K`.
* Compute `r = 1/\mathrm{det}(K) \pmod m`.
* `K^{-1} = r\cdot \mathrm{adj}(K) \pmod m`.
Examples
========
>>> from sympy import Matrix
>>> A = Matrix(2, 2, [1, 2, 3, 4])
>>> A.inv_mod(5)
Matrix([
[3, 1],
[4, 2]])
>>> A.inv_mod(3)
Matrix([
[1, 1],
[0, 1]])
"""
if not self.is_square:
raise NonSquareMatrixError()
N = self.cols
det_K = self.det()
det_inv = None
try:
det_inv = mod_inverse(det_K, m)
except ValueError:
raise ValueError('Matrix is not invertible (mod %d)' % m)
K_adj = self.adjugate()
K_inv = self.__class__(N, N,
[det_inv * K_adj[i, j] % m for i in range(N) for
j in range(N)])
return K_inv
[docs] def inverse_ADJ(self, iszerofunc=_iszero):
"""Calculates the inverse using the adjugate matrix and a determinant.
See Also
========
inv
inverse_LU
inverse_GE
"""
if not self.is_square:
raise NonSquareMatrixError("A Matrix must be square to invert.")
d = self.det(method='berkowitz')
zero = d.equals(0)
if zero is None:
# if equals() can't decide, will rref be able to?
ok = self.rref(simplify=True)[0]
zero = any(iszerofunc(ok[j, j]) for j in range(ok.rows))
if zero:
raise ValueError("Matrix det == 0; not invertible.")
return self.adjugate() / d
[docs] def inverse_GE(self, iszerofunc=_iszero):
"""Calculates the inverse using Gaussian elimination.
See Also
========
inv
inverse_LU
inverse_ADJ
"""
from .dense import Matrix
if not self.is_square:
raise NonSquareMatrixError("A Matrix must be square to invert.")
big = Matrix.hstack(self.as_mutable(), Matrix.eye(self.rows))
red = big.rref(iszerofunc=iszerofunc, simplify=True)[0]
if any(iszerofunc(red[j, j]) for j in range(red.rows)):
raise ValueError("Matrix det == 0; not invertible.")
return self._new(red[:, big.rows:])
[docs] def inverse_LU(self, iszerofunc=_iszero):
"""Calculates the inverse using LU decomposition.
See Also
========
inv
inverse_GE
inverse_ADJ
"""
if not self.is_square:
raise NonSquareMatrixError()
ok = self.rref(simplify=True)[0]
if any(iszerofunc(ok[j, j]) for j in range(ok.rows)):
raise ValueError("Matrix det == 0; not invertible.")
return self.LUsolve(self.eye(self.rows), iszerofunc=_iszero)
[docs] def inv(self, method=None, **kwargs):
"""
Return the inverse of a matrix.
CASE 1: If the matrix is a dense matrix.
Return the matrix inverse using the method indicated (default
is Gauss elimination).
Parameters
==========
method : ('GE', 'LU', or 'ADJ')
Notes
=====
According to the ``method`` keyword, it calls the appropriate method:
GE .... inverse_GE(); default
LU .... inverse_LU()
ADJ ... inverse_ADJ()
See Also
========
inverse_LU
inverse_GE
inverse_ADJ
Raises
------
ValueError
If the determinant of the matrix is zero.
CASE 2: If the matrix is a sparse matrix.
Return the matrix inverse using Cholesky or LDL (default).
kwargs
======
method : ('CH', 'LDL')
Notes
=====
According to the ``method`` keyword, it calls the appropriate method:
LDL ... inverse_LDL(); default
CH .... inverse_CH()
Raises
------
ValueError
If the determinant of the matrix is zero.
"""
if not self.is_square:
raise NonSquareMatrixError()
if method is not None:
kwargs['method'] = method
return self._eval_inverse(**kwargs)
[docs] def is_nilpotent(self):
"""Checks if a matrix is nilpotent.
A matrix B is nilpotent if for some integer k, B**k is
a zero matrix.
Examples
========
>>> from sympy import Matrix
>>> a = Matrix([[0, 0, 0], [1, 0, 0], [1, 1, 0]])
>>> a.is_nilpotent()
True
>>> a = Matrix([[1, 0, 1], [1, 0, 0], [1, 1, 0]])
>>> a.is_nilpotent()
False
"""
if not self:
return True
if not self.is_square:
raise NonSquareMatrixError(
"Nilpotency is valid only for square matrices")
x = _uniquely_named_symbol('x', self)
p = self.charpoly(x)
if p.args[0] == x ** self.rows:
return True
return False
[docs] def key2bounds(self, keys):
"""Converts a key with potentially mixed types of keys (integer and slice)
into a tuple of ranges and raises an error if any index is out of ``self``'s
range.
See Also
========
key2ij
"""
from sympy.matrices.common import a2idx as a2idx_ # Remove this line after deprecation of a2idx from matrices.py
islice, jslice = [isinstance(k, slice) for k in keys]
if islice:
if not self.rows:
rlo = rhi = 0
else:
rlo, rhi = keys[0].indices(self.rows)[:2]
else:
rlo = a2idx_(keys[0], self.rows)
rhi = rlo + 1
if jslice:
if not self.cols:
clo = chi = 0
else:
clo, chi = keys[1].indices(self.cols)[:2]
else:
clo = a2idx_(keys[1], self.cols)
chi = clo + 1
return rlo, rhi, clo, chi
[docs] def key2ij(self, key):
"""Converts key into canonical form, converting integers or indexable
items into valid integers for ``self``'s range or returning slices
unchanged.
See Also
========
key2bounds
"""
from sympy.matrices.common import a2idx as a2idx_ # Remove this line after deprecation of a2idx from matrices.py
if is_sequence(key):
if not len(key) == 2:
raise TypeError('key must be a sequence of length 2')
return [a2idx_(i, n) if not isinstance(i, slice) else i
for i, n in zip(key, self.shape)]
elif isinstance(key, slice):
return key.indices(len(self))[:2]
else:
return divmod(a2idx_(key, len(self)), self.cols)
[docs] def LDLdecomposition(self, hermitian=True):
"""Returns the LDL Decomposition (L, D) of matrix A,
such that L * D * L.H == A if hermitian flag is True, or
L * D * L.T == A if hermitian is False.
This method eliminates the use of square root.
Further this ensures that all the diagonal entries of L are 1.
A must be a Hermitian positive-definite matrix if hermitian is True,
or a symmetric matrix otherwise.
Examples
========
>>> from sympy.matrices import Matrix, eye
>>> A = Matrix(((25, 15, -5), (15, 18, 0), (-5, 0, 11)))
>>> L, D = A.LDLdecomposition()
>>> L
Matrix([
[ 1, 0, 0],
[ 3/5, 1, 0],
[-1/5, 1/3, 1]])
>>> D
Matrix([
[25, 0, 0],
[ 0, 9, 0],
[ 0, 0, 9]])
>>> L * D * L.T * A.inv() == eye(A.rows)
True
The matrix can have complex entries:
>>> from sympy import I
>>> A = Matrix(((9, 3*I), (-3*I, 5)))
>>> L, D = A.LDLdecomposition()
>>> L
Matrix([
[ 1, 0],
[-I/3, 1]])
>>> D
Matrix([
[9, 0],
[0, 4]])
>>> L*D*L.H == A
True
See Also
========
cholesky
LUdecomposition
QRdecomposition
"""
if not self.is_square:
raise NonSquareMatrixError("Matrix must be square.")
if hermitian and not self.is_hermitian:
raise ValueError("Matrix must be Hermitian.")
if not hermitian and not self.is_symmetric():
raise ValueError("Matrix must be symmetric.")
return self._LDLdecomposition(hermitian=hermitian)
[docs] def LDLsolve(self, rhs):
"""Solves ``Ax = B`` using LDL decomposition,
for a general square and non-singular matrix.
For a non-square matrix with rows > cols,
the least squares solution is returned.
Examples
========
>>> from sympy.matrices import Matrix, eye
>>> A = eye(2)*2
>>> B = Matrix([[1, 2], [3, 4]])
>>> A.LDLsolve(B) == B/2
True
See Also
========
LDLdecomposition
lower_triangular_solve
upper_triangular_solve
gauss_jordan_solve
cholesky_solve
diagonal_solve
LUsolve
QRsolve
pinv_solve
"""
hermitian = True
if self.is_symmetric():
hermitian = False
L, D = self.LDLdecomposition(hermitian=hermitian)
elif self.is_hermitian:
L, D = self.LDLdecomposition(hermitian=hermitian)
elif self.rows >= self.cols:
L, D = (self.H * self).LDLdecomposition(hermitian=hermitian)
rhs = self.H * rhs
else:
raise NotImplementedError('Under-determined System. '
'Try M.gauss_jordan_solve(rhs)')
Y = L._lower_triangular_solve(rhs)
Z = D._diagonal_solve(Y)
if hermitian:
return (L.H)._upper_triangular_solve(Z)
else:
return (L.T)._upper_triangular_solve(Z)
[docs] def lower_triangular_solve(self, rhs):
"""Solves ``Ax = B``, where A is a lower triangular matrix.
See Also
========
upper_triangular_solve
gauss_jordan_solve
cholesky_solve
diagonal_solve
LDLsolve
LUsolve
QRsolve
pinv_solve
"""
if not self.is_square:
raise NonSquareMatrixError("Matrix must be square.")
if rhs.rows != self.rows:
raise ShapeError("Matrices size mismatch.")
if not self.is_lower:
raise ValueError("Matrix must be lower triangular.")
return self._lower_triangular_solve(rhs)
[docs] def LUdecomposition(self,
iszerofunc=_iszero,
simpfunc=None,
rankcheck=False):
"""Returns (L, U, perm) where L is a lower triangular matrix with unit
diagonal, U is an upper triangular matrix, and perm is a list of row
swap index pairs. If A is the original matrix, then
A = (L*U).permuteBkwd(perm), and the row permutation matrix P such
that P*A = L*U can be computed by P=eye(A.row).permuteFwd(perm).
See documentation for LUCombined for details about the keyword argument
rankcheck, iszerofunc, and simpfunc.
Examples
========
>>> from sympy import Matrix
>>> a = Matrix([[4, 3], [6, 3]])
>>> L, U, _ = a.LUdecomposition()
>>> L
Matrix([
[ 1, 0],
[3/2, 1]])
>>> U
Matrix([
[4, 3],
[0, -3/2]])
See Also
========
cholesky
LDLdecomposition
QRdecomposition
LUdecomposition_Simple
LUdecompositionFF
LUsolve
"""
combined, p = self.LUdecomposition_Simple(iszerofunc=iszerofunc,
simpfunc=simpfunc,
rankcheck=rankcheck)
# L is lower triangular ``self.rows x self.rows``
# U is upper triangular ``self.rows x self.cols``
# L has unit diagonal. For each column in combined, the subcolumn
# below the diagonal of combined is shared by L.
# If L has more columns than combined, then the remaining subcolumns
# below the diagonal of L are zero.
# The upper triangular portion of L and combined are equal.
def entry_L(i, j):
if i < j:
# Super diagonal entry
return S.Zero
elif i == j:
return S.One
elif j < combined.cols:
return combined[i, j]
# Subdiagonal entry of L with no corresponding
# entry in combined
return S.Zero
def entry_U(i, j):
return S.Zero if i > j else combined[i, j]
L = self._new(combined.rows, combined.rows, entry_L)
U = self._new(combined.rows, combined.cols, entry_U)
return L, U, p
[docs] def LUdecomposition_Simple(self,
iszerofunc=_iszero,
simpfunc=None,
rankcheck=False):
"""Compute an lu decomposition of m x n matrix A, where P*A = L*U
* L is m x m lower triangular with unit diagonal
* U is m x n upper triangular
* P is an m x m permutation matrix
Returns an m x n matrix lu, and an m element list perm where each
element of perm is a pair of row exchange indices.
The factors L and U are stored in lu as follows:
The subdiagonal elements of L are stored in the subdiagonal elements
of lu, that is lu[i, j] = L[i, j] whenever i > j.
The elements on the diagonal of L are all 1, and are not explicitly
stored.
U is stored in the upper triangular portion of lu, that is
lu[i ,j] = U[i, j] whenever i <= j.
The output matrix can be visualized as:
Matrix([
[u, u, u, u],
[l, u, u, u],
[l, l, u, u],
[l, l, l, u]])
where l represents a subdiagonal entry of the L factor, and u
represents an entry from the upper triangular entry of the U
factor.
perm is a list row swap index pairs such that if A is the original
matrix, then A = (L*U).permuteBkwd(perm), and the row permutation
matrix P such that ``P*A = L*U`` can be computed by
``P=eye(A.row).permuteFwd(perm)``.
The keyword argument rankcheck determines if this function raises a
ValueError when passed a matrix whose rank is strictly less than
min(num rows, num cols). The default behavior is to decompose a rank
deficient matrix. Pass rankcheck=True to raise a
ValueError instead. (This mimics the previous behavior of this function).
The keyword arguments iszerofunc and simpfunc are used by the pivot
search algorithm.
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.
When a matrix contains symbolic entries, the pivot search algorithm
differs from the case where every entry can be categorized as zero or
nonzero.
The algorithm searches column by column through the submatrix whose
top left entry coincides with the pivot position.
If it exists, the pivot is the first entry in the current search
column that iszerofunc guarantees is nonzero.
If no such candidate exists, then each candidate pivot is simplified
if simpfunc is not None.
The search is repeated, with the difference that a candidate may be
the pivot if ``iszerofunc()`` cannot guarantee that it is nonzero.
In the second search the pivot is the first candidate that
iszerofunc can guarantee is nonzero.
If no such candidate exists, then the pivot is the first candidate
for which iszerofunc returns None.
If no such candidate exists, then the search is repeated in the next
column to the right.
The pivot search algorithm differs from the one in ``rref()``, which
relies on ``_find_reasonable_pivot()``.
Future versions of ``LUdecomposition_simple()`` may use
``_find_reasonable_pivot()``.
See Also
========
LUdecomposition
LUdecompositionFF
LUsolve
"""
if rankcheck:
# https://github.com/sympy/sympy/issues/9796
pass
if self.rows == 0 or self.cols == 0:
# Define LU decomposition of a matrix with no entries as a matrix
# of the same dimensions with all zero entries.
return self.zeros(self.rows, self.cols), []
lu = self.as_mutable()
row_swaps = []
pivot_col = 0
for pivot_row in range(0, lu.rows - 1):
# Search for pivot. Prefer entry that iszeropivot determines
# is nonzero, over entry that iszeropivot cannot guarantee
# is zero.
# XXX ``_find_reasonable_pivot`` uses slow zero testing. Blocked by bug #10279
# Future versions of LUdecomposition_simple can pass iszerofunc and simpfunc
# to _find_reasonable_pivot().
# In pass 3 of _find_reasonable_pivot(), the predicate in ``if x.equals(S.Zero):``
# calls sympy.simplify(), and not the simplification function passed in via
# the keyword argument simpfunc.
iszeropivot = True
while pivot_col != self.cols and iszeropivot:
sub_col = (lu[r, pivot_col] for r in range(pivot_row, self.rows))
pivot_row_offset, pivot_value, is_assumed_non_zero, ind_simplified_pairs =\
_find_reasonable_pivot_naive(sub_col, iszerofunc, simpfunc)
iszeropivot = pivot_value is None
if iszeropivot:
# All candidate pivots in this column are zero.
# Proceed to next column.
pivot_col += 1
if rankcheck and pivot_col != pivot_row:
# All entries including and below the pivot position are
# zero, which indicates that the rank of the matrix is
# strictly less than min(num rows, num cols)
# Mimic behavior of previous implementation, by throwing a
# ValueError.
raise ValueError("Rank of matrix is strictly less than"
" number of rows or columns."
" Pass keyword argument"
" rankcheck=False to compute"
" the LU decomposition of this matrix.")
candidate_pivot_row = None if pivot_row_offset is None else pivot_row + pivot_row_offset
if candidate_pivot_row is None and iszeropivot:
# If candidate_pivot_row is None and iszeropivot is True
# after pivot search has completed, then the submatrix
# below and to the right of (pivot_row, pivot_col) is
# all zeros, indicating that Gaussian elimination is
# complete.
return lu, row_swaps
# Update entries simplified during pivot search.
for offset, val in ind_simplified_pairs:
lu[pivot_row + offset, pivot_col] = val
if pivot_row != candidate_pivot_row:
# Row swap book keeping:
# Record which rows were swapped.
# Update stored portion of L factor by multiplying L on the
# left and right with the current permutation.
# Swap rows of U.
row_swaps.append([pivot_row, candidate_pivot_row])
# Update L.
lu[pivot_row, 0:pivot_row], lu[candidate_pivot_row, 0:pivot_row] = \
lu[candidate_pivot_row, 0:pivot_row], lu[pivot_row, 0:pivot_row]
# Swap pivot row of U with candidate pivot row.
lu[pivot_row, pivot_col:lu.cols], lu[candidate_pivot_row, pivot_col:lu.cols] = \
lu[candidate_pivot_row, pivot_col:lu.cols], lu[pivot_row, pivot_col:lu.cols]
# Introduce zeros below the pivot by adding a multiple of the
# pivot row to a row under it, and store the result in the
# row under it.
# Only entries in the target row whose index is greater than
# start_col may be nonzero.
start_col = pivot_col + 1
for row in range(pivot_row + 1, lu.rows):
# Store factors of L in the subcolumn below
# (pivot_row, pivot_row).
lu[row, pivot_row] =\
lu[row, pivot_col]/lu[pivot_row, pivot_col]
# Form the linear combination of the pivot row and the current
# row below the pivot row that zeros the entries below the pivot.
# Employing slicing instead of a loop here raises
# NotImplementedError: Cannot add Zero to MutableSparseMatrix
# in sympy/matrices/tests/test_sparse.py.
# c = pivot_row + 1 if pivot_row == pivot_col else pivot_col
for c in range(start_col, lu.cols):
lu[row, c] = lu[row, c] - lu[row, pivot_row]*lu[pivot_row, c]
if pivot_row != pivot_col:
# matrix rank < min(num rows, num cols),
# so factors of L are not stored directly below the pivot.
# These entries are zero by construction, so don't bother
# computing them.
for row in range(pivot_row + 1, lu.rows):
lu[row, pivot_col] = S.Zero
pivot_col += 1
if pivot_col == lu.cols:
# All candidate pivots are zero implies that Gaussian
# elimination is complete.
return lu, row_swaps
if rankcheck:
if iszerofunc(
lu[Min(lu.rows, lu.cols) - 1, Min(lu.rows, lu.cols) - 1]):
raise ValueError("Rank of matrix is strictly less than"
" number of rows or columns."
" Pass keyword argument"
" rankcheck=False to compute"
" the LU decomposition of this matrix.")
return lu, row_swaps
[docs] def LUdecompositionFF(self):
"""Compute a fraction-free LU decomposition.
Returns 4 matrices P, L, D, U such that PA = L D**-1 U.
If the elements of the matrix belong to some integral domain I, then all
elements of L, D and U are guaranteed to belong to I.
**Reference**
- W. Zhou & D.J. Jeffrey, "Fraction-free matrix factors: new forms
for LU and QR factors". Frontiers in Computer Science in China,
Vol 2, no. 1, pp. 67-80, 2008.
See Also
========
LUdecomposition
LUdecomposition_Simple
LUsolve
"""
from sympy.matrices import SparseMatrix
zeros = SparseMatrix.zeros
eye = SparseMatrix.eye
n, m = self.rows, self.cols
U, L, P = self.as_mutable(), eye(n), eye(n)
DD = zeros(n, n)
oldpivot = 1
for k in range(n - 1):
if U[k, k] == 0:
for kpivot in range(k + 1, n):
if U[kpivot, k]:
break
else:
raise ValueError("Matrix is not full rank")
U[k, k:], U[kpivot, k:] = U[kpivot, k:], U[k, k:]
L[k, :k], L[kpivot, :k] = L[kpivot, :k], L[k, :k]
P[k, :], P[kpivot, :] = P[kpivot, :], P[k, :]
L[k, k] = Ukk = U[k, k]
DD[k, k] = oldpivot * Ukk
for i in range(k + 1, n):
L[i, k] = Uik = U[i, k]
for j in range(k + 1, m):
U[i, j] = (Ukk * U[i, j] - U[k, j] * Uik) / oldpivot
U[i, k] = 0
oldpivot = Ukk
DD[n - 1, n - 1] = oldpivot
return P, L, DD, U
[docs] def LUsolve(self, rhs, iszerofunc=_iszero):
"""Solve the linear system ``Ax = rhs`` for ``x`` where ``A = self``.
This is for symbolic matrices, for real or complex ones use
mpmath.lu_solve or mpmath.qr_solve.
See Also
========
lower_triangular_solve
upper_triangular_solve
gauss_jordan_solve
cholesky_solve
diagonal_solve
LDLsolve
QRsolve
pinv_solve
LUdecomposition
"""
if rhs.rows != self.rows:
raise ShapeError(
"``self`` and ``rhs`` must have the same number of rows.")
m = self.rows
n = self.cols
if m < n:
raise NotImplementedError("Underdetermined systems not supported.")
try:
A, perm = self.LUdecomposition_Simple(
iszerofunc=_iszero, rankcheck=True)
except ValueError:
raise NotImplementedError("Underdetermined systems not supported.")
b = rhs.permute_rows(perm).as_mutable()
# forward substitution, all diag entries are scaled to 1
for i in range(m):
for j in range(min(i, n)):
scale = A[i, j]
b.zip_row_op(i, j, lambda x, y: x - y * scale)
# consistency check for overdetermined systems
if m > n:
for i in range(n, m):
for j in range(b.cols):
if not iszerofunc(b[i, j]):
raise ValueError("The system is inconsistent.")
b = b[0:n, :] # truncate zero rows if consistent
# backward substitution
for i in range(n - 1, -1, -1):
for j in range(i + 1, n):
scale = A[i, j]
b.zip_row_op(i, j, lambda x, y: x - y * scale)
scale = A[i, i]
b.row_op(i, lambda x, _: x / scale)
return rhs.__class__(b)
[docs] def multiply(self, b):
"""Returns ``self*b``
See Also
========
dot
cross
multiply_elementwise
"""
return self * b
[docs] def normalized(self, iszerofunc=_iszero):
"""Return the normalized version of ``self``.
Parameters
==========
iszerofunc : Function, optional
A function to determine whether ``self`` is a zero vector.
The default ``_iszero`` tests to see if each element is
exactly zero.
Returns
=======
Matrix
Normalized vector form of ``self``.
It has the same length as a unit vector. However, a zero vector
will be returned for a vector with norm 0.
Raises
======
ShapeError
If the matrix is not in a vector form.
See Also
========
norm
"""
if self.rows != 1 and self.cols != 1:
raise ShapeError("A Matrix must be a vector to normalize.")
norm = self.norm()
if iszerofunc(norm):
out = self.zeros(self.rows, self.cols)
else:
out = self.applyfunc(lambda i: i / norm)
return out
[docs] def norm(self, ord=None):
"""Return the Norm of a Matrix or Vector.
In the simplest case this is the geometric size of the vector
Other norms can be specified by the ord parameter
===== ============================ ==========================
ord norm for matrices norm for vectors
===== ============================ ==========================
None Frobenius norm 2-norm
'fro' Frobenius norm - does not exist
inf maximum row sum max(abs(x))
-inf -- min(abs(x))
1 maximum column sum as below
-1 -- as below
2 2-norm (largest sing. value) as below
-2 smallest singular value as below
other - does not exist sum(abs(x)**ord)**(1./ord)
===== ============================ ==========================
Examples
========
>>> from sympy import Matrix, Symbol, trigsimp, cos, sin, oo
>>> x = Symbol('x', real=True)
>>> v = Matrix([cos(x), sin(x)])
>>> trigsimp( v.norm() )
1
>>> v.norm(10)
(sin(x)**10 + cos(x)**10)**(1/10)
>>> A = Matrix([[1, 1], [1, 1]])
>>> A.norm(1) # maximum sum of absolute values of A is 2
2
>>> A.norm(2) # Spectral norm (max of |Ax|/|x| under 2-vector-norm)
2
>>> A.norm(-2) # Inverse spectral norm (smallest singular value)
0
>>> A.norm() # Frobenius Norm
2
>>> A.norm(oo) # Infinity Norm
2
>>> Matrix([1, -2]).norm(oo)
2
>>> Matrix([-1, 2]).norm(-oo)
1
See Also
========
normalized
"""
# Row or Column Vector Norms
vals = list(self.values()) or [0]
if self.rows == 1 or self.cols == 1:
if ord == 2 or ord is None: # Common case sqrt(<x, x>)
return sqrt(Add(*(abs(i) ** 2 for i in vals)))
elif ord == 1: # sum(abs(x))
return Add(*(abs(i) for i in vals))
elif ord == S.Infinity: # max(abs(x))
return Max(*[abs(i) for i in vals])
elif ord == S.NegativeInfinity: # min(abs(x))
return Min(*[abs(i) for i in vals])
# Otherwise generalize the 2-norm, Sum(x_i**ord)**(1/ord)
# Note that while useful this is not mathematically a norm
try:
return Pow(Add(*(abs(i) ** ord for i in vals)), S(1) / ord)
except (NotImplementedError, TypeError):
raise ValueError("Expected order to be Number, Symbol, oo")
# Matrix Norms
else:
if ord == 1: # Maximum column sum
m = self.applyfunc(abs)
return Max(*[sum(m.col(i)) for i in range(m.cols)])
elif ord == 2: # Spectral Norm
# Maximum singular value
return Max(*self.singular_values())
elif ord == -2:
# Minimum singular value
return Min(*self.singular_values())
elif ord == S.Infinity: # Infinity Norm - Maximum row sum
m = self.applyfunc(abs)
return Max(*[sum(m.row(i)) for i in range(m.rows)])
elif (ord is None or isinstance(ord,
string_types) and ord.lower() in
['f', 'fro', 'frobenius', 'vector']):
# Reshape as vector and send back to norm function
return self.vec().norm(ord=2)
else:
raise NotImplementedError("Matrix Norms under development")
[docs] def pinv_solve(self, B, arbitrary_matrix=None):
"""Solve ``Ax = B`` using the Moore-Penrose pseudoinverse.
There may be zero, one, or infinite solutions. If one solution
exists, it will be returned. If infinite solutions exist, one will
be returned based on the value of arbitrary_matrix. If no solutions
exist, the least-squares solution is returned.
Parameters
==========
B : Matrix
The right hand side of the equation to be solved for. Must have
the same number of rows as matrix A.
arbitrary_matrix : Matrix
If the system is underdetermined (e.g. A has more columns than
rows), infinite solutions are possible, in terms of an arbitrary
matrix. This parameter may be set to a specific matrix to use
for that purpose; if so, it must be the same shape as x, with as
many rows as matrix A has columns, and as many columns as matrix
B. If left as None, an appropriate matrix containing dummy
symbols in the form of ``wn_m`` will be used, with n and m being
row and column position of each symbol.
Returns
=======
x : Matrix
The matrix that will satisfy ``Ax = B``. Will have as many rows as
matrix A has columns, and as many columns as matrix B.
Examples
========
>>> from sympy import Matrix
>>> A = Matrix([[1, 2, 3], [4, 5, 6]])
>>> B = Matrix([7, 8])
>>> A.pinv_solve(B)
Matrix([
[ _w0_0/6 - _w1_0/3 + _w2_0/6 - 55/18],
[-_w0_0/3 + 2*_w1_0/3 - _w2_0/3 + 1/9],
[ _w0_0/6 - _w1_0/3 + _w2_0/6 + 59/18]])
>>> A.pinv_solve(B, arbitrary_matrix=Matrix([0, 0, 0]))
Matrix([
[-55/18],
[ 1/9],
[ 59/18]])
See Also
========
lower_triangular_solve
upper_triangular_solve
gauss_jordan_solve
cholesky_solve
diagonal_solve
LDLsolve
LUsolve
QRsolve
pinv
Notes
=====
This may return either exact solutions or least squares solutions.
To determine which, check ``A * A.pinv() * B == B``. It will be
True if exact solutions exist, and False if only a least-squares
solution exists. Be aware that the left hand side of that equation
may need to be simplified to correctly compare to the right hand
side.
References
==========
.. [1] https://en.wikipedia.org/wiki/Moore-Penrose_pseudoinverse#Obtaining_all_solutions_of_a_linear_system
"""
from sympy.matrices import eye
A = self
A_pinv = self.pinv()
if arbitrary_matrix is None:
rows, cols = A.cols, B.cols
w = symbols('w:{0}_:{1}'.format(rows, cols), cls=Dummy)
arbitrary_matrix = self.__class__(cols, rows, w).T
return A_pinv * B + (eye(A.cols) - A_pinv * A) * arbitrary_matrix
[docs] def pinv(self):
"""Calculate the Moore-Penrose pseudoinverse of the matrix.
The Moore-Penrose pseudoinverse exists and is unique for any matrix.
If the matrix is invertible, the pseudoinverse is the same as the
inverse.
Examples
========
>>> from sympy import Matrix
>>> Matrix([[1, 2, 3], [4, 5, 6]]).pinv()
Matrix([
[-17/18, 4/9],
[ -1/9, 1/9],
[ 13/18, -2/9]])
See Also
========
inv
pinv_solve
References
==========
.. [1] https://en.wikipedia.org/wiki/Moore-Penrose_pseudoinverse
"""
A = self
AH = self.H
# Trivial case: pseudoinverse of all-zero matrix is its transpose.
if A.is_zero:
return AH
try:
if self.rows >= self.cols:
return (AH * A).inv() * AH
else:
return AH * (A * AH).inv()
except ValueError:
# Matrix is not full rank, so A*AH cannot be inverted.
pass
try:
# However, A*AH is Hermitian, so we can diagonalize it.
if self.rows >= self.cols:
P, D = (AH * A).diagonalize(normalize=True)
D_pinv = D.applyfunc(lambda x: 0 if _iszero(x) else 1 / x)
return P * D_pinv * P.H * AH
else:
P, D = (A * AH).diagonalize(normalize=True)
D_pinv = D.applyfunc(lambda x: 0 if _iszero(x) else 1 / x)
return AH * P * D_pinv * P.H
except MatrixError:
raise NotImplementedError('pinv for rank-deficient matrices where diagonalization '
'of A.H*A fails is not supported yet.')
[docs] def print_nonzero(self, symb="X"):
"""Shows location of non-zero entries for fast shape lookup.
Examples
========
>>> from sympy.matrices import Matrix, eye
>>> m = Matrix(2, 3, lambda i, j: i*3+j)
>>> m
Matrix([
[0, 1, 2],
[3, 4, 5]])
>>> m.print_nonzero()
[ XX]
[XXX]
>>> m = eye(4)
>>> m.print_nonzero("x")
[x ]
[ x ]
[ x ]
[ x]
"""
s = []
for i in range(self.rows):
line = []
for j in range(self.cols):
if self[i, j] == 0:
line.append(" ")
else:
line.append(str(symb))
s.append("[%s]" % ''.join(line))
print('\n'.join(s))
[docs] def project(self, v):
"""Return the projection of ``self`` onto the line containing ``v``.
Examples
========
>>> from sympy import Matrix, S, sqrt
>>> V = Matrix([sqrt(3)/2, S.Half])
>>> x = Matrix([[1, 0]])
>>> V.project(x)
Matrix([[sqrt(3)/2, 0]])
>>> V.project(-x)
Matrix([[sqrt(3)/2, 0]])
"""
return v * (self.dot(v) / v.dot(v))
[docs] def QRdecomposition(self):
"""Return Q, R where A = Q*R, Q is orthogonal and R is upper triangular.
Examples
========
This is the example from wikipedia:
>>> from sympy import Matrix
>>> A = Matrix([[12, -51, 4], [6, 167, -68], [-4, 24, -41]])
>>> Q, R = A.QRdecomposition()
>>> Q
Matrix([
[ 6/7, -69/175, -58/175],
[ 3/7, 158/175, 6/175],
[-2/7, 6/35, -33/35]])
>>> R
Matrix([
[14, 21, -14],
[ 0, 175, -70],
[ 0, 0, 35]])
>>> A == Q*R
True
QR factorization of an identity matrix:
>>> A = Matrix([[1, 0, 0], [0, 1, 0], [0, 0, 1]])
>>> Q, R = A.QRdecomposition()
>>> Q
Matrix([
[1, 0, 0],
[0, 1, 0],
[0, 0, 1]])
>>> R
Matrix([
[1, 0, 0],
[0, 1, 0],
[0, 0, 1]])
See Also
========
cholesky
LDLdecomposition
LUdecomposition
QRsolve
"""
cls = self.__class__
mat = self.as_mutable()
n = mat.rows
m = mat.cols
ranked = list()
# Pad with additional rows to make wide matrices square
# nOrig keeps track of original size so zeros can be trimmed from Q
if n < m:
nOrig = n
n = m
mat = mat.col_join(mat.zeros(n - nOrig, m))
else:
nOrig = n
Q, R = mat.zeros(n, m), mat.zeros(m)
for j in range(m): # for each column vector
tmp = mat[:, j] # take original v
for i in range(j):
# subtract the project of mat on new vector
R[i, j] = Q[:, i].dot(mat[:, j])
tmp -= Q[:, i] * R[i, j]
tmp.expand()
# normalize it
R[j, j] = tmp.norm()
if not R[j, j].is_zero:
ranked.append(j)
Q[:, j] = tmp / R[j, j]
if len(ranked) != 0:
return (
cls(Q.extract(range(nOrig), ranked)),
cls(R.extract(ranked, range(R.cols)))
)
else:
# Trivial case handling for zero-rank matrix
# Force Q as matrix containing standard basis vectors
for i in range(Min(nOrig, m)):
Q[i, i] = 1
return (
cls(Q.extract(range(nOrig), range(Min(nOrig, m)))),
cls(R.extract(range(Min(nOrig, m)), range(R.cols)))
)
[docs] def QRsolve(self, b):
"""Solve the linear system ``Ax = b``.
``self`` is the matrix ``A``, the method argument is the vector
``b``. The method returns the solution vector ``x``. If ``b`` is a
matrix, the system is solved for each column of ``b`` and the
return value is a matrix of the same shape as ``b``.
This method is slower (approximately by a factor of 2) but
more stable for floating-point arithmetic than the LUsolve method.
However, LUsolve usually uses an exact arithmetic, so you don't need
to use QRsolve.
This is mainly for educational purposes and symbolic matrices, for real
(or complex) matrices use mpmath.qr_solve.
See Also
========
lower_triangular_solve
upper_triangular_solve
gauss_jordan_solve
cholesky_solve
diagonal_solve
LDLsolve
LUsolve
pinv_solve
QRdecomposition
"""
Q, R = self.as_mutable().QRdecomposition()
y = Q.T * b
# back substitution to solve R*x = y:
# We build up the result "backwards" in the vector 'x' and reverse it
# only in the end.
x = []
n = R.rows
for j in range(n - 1, -1, -1):
tmp = y[j, :]
for k in range(j + 1, n):
tmp -= R[j, k] * x[n - 1 - k]
x.append(tmp / R[j, j])
return self._new([row._mat for row in reversed(x)])
[docs] def rank_decomposition(self, iszerofunc=_iszero, simplify=False):
r"""Returns a pair of matrices (`C`, `F`) with matching rank
such that `A = C F`.
Parameters
==========
iszerofunc : Function, optional
A function used for detecting whether an element can
act as a pivot. ``lambda x: x.is_zero`` is used by default.
simplify : Bool or Function, optional
A function used to simplify elements when looking for a
pivot. By default SymPy's ``simplify`` is used.
Returns
=======
(C, F) : Matrices
`C` and `F` are full-rank matrices with rank as same as `A`,
whose product gives `A`.
See Notes for additional mathematical details.
Examples
========
>>> from sympy.matrices import Matrix
>>> A = Matrix([
... [1, 3, 1, 4],
... [2, 7, 3, 9],
... [1, 5, 3, 1],
... [1, 2, 0, 8]
... ])
>>> C, F = A.rank_decomposition()
>>> C
Matrix([
[1, 3, 4],
[2, 7, 9],
[1, 5, 1],
[1, 2, 8]])
>>> F
Matrix([
[1, 0, -2, 0],
[0, 1, 1, 0],
[0, 0, 0, 1]])
>>> C * F == A
True
Notes
=====
Obtaining `F`, an RREF of `A`, is equivalent to creating a
product
.. math::
E_n E_{n-1} ... E_1 A = F
where `E_n, E_{n-1}, ... , E_1` are the elimination matrices or
permutation matrices equivalent to each row-reduction step.
The inverse of the same product of elimination matrices gives
`C`:
.. math::
C = (E_n E_{n-1} ... E_1)^{-1}
It is not necessary, however, to actually compute the inverse:
the columns of `C` are those from the original matrix with the
same column indices as the indices of the pivot columns of `F`.
References
==========
.. [1] https://en.wikipedia.org/wiki/Rank_factorization
.. [2] Piziak, R.; Odell, P. L. (1 June 1999).
"Full Rank Factorization of Matrices".
Mathematics Magazine. 72 (3): 193. doi:10.2307/2690882
See Also
========
rref
"""
(F, pivot_cols) = self.rref(
simplify=simplify, iszerofunc=iszerofunc, pivots=True)
rank = len(pivot_cols)
C = self.extract(range(self.rows), pivot_cols)
F = F[:rank, :]
return (C, F)
[docs] def solve_least_squares(self, rhs, method='CH'):
"""Return the least-square fit to the data.
Parameters
==========
rhs : Matrix
Vector representing the right hand side of the linear equation.
method : string or boolean, optional
If set to ``'CH'``, ``cholesky_solve`` routine will be used.
If set to ``'LDL'``, ``LDLsolve`` routine will be used.
If set to ``'QR'``, ``QRsolve`` routine will be used.
If set to ``'PINV'``, ``pinv_solve`` routine will be used.
Otherwise, the conjugate of ``self`` will be used to create a system
of equations that is passed to ``solve`` along with the hint
defined by ``method``.
Returns
=======
solutions : Matrix
Vector representing the solution.
Examples
========
>>> from sympy.matrices import Matrix, ones
>>> A = Matrix([1, 2, 3])
>>> B = Matrix([2, 3, 4])
>>> S = Matrix(A.row_join(B))
>>> S
Matrix([
[1, 2],
[2, 3],
[3, 4]])
If each line of S represent coefficients of Ax + By
and x and y are [2, 3] then S*xy is:
>>> r = S*Matrix([2, 3]); r
Matrix([
[ 8],
[13],
[18]])
But let's add 1 to the middle value and then solve for the
least-squares value of xy:
>>> xy = S.solve_least_squares(Matrix([8, 14, 18])); xy
Matrix([
[ 5/3],
[10/3]])
The error is given by S*xy - r:
>>> S*xy - r
Matrix([
[1/3],
[1/3],
[1/3]])
>>> _.norm().n(2)
0.58
If a different xy is used, the norm will be higher:
>>> xy += ones(2, 1)/10
>>> (S*xy - r).norm().n(2)
1.5
"""
if method == 'CH':
return self.cholesky_solve(rhs)
elif method == 'QR':
return self.QRsolve(rhs)
elif method == 'LDL':
return self.LDLsolve(rhs)
elif method == 'PINV':
return self.pinv_solve(rhs)
else:
t = self.H
return (t * self).solve(t * rhs, method=method)
[docs] def solve(self, rhs, method='GJ'):
"""Solves linear equation where the unique solution exists.
Parameters
==========
rhs : Matrix
Vector representing the right hand side of the linear equation.
method : string, optional
If set to ``'GJ'``, the Gauss-Jordan elimination will be used, which
is implemented in the routine ``gauss_jordan_solve``.
If set to ``'LU'``, ``LUsolve`` routine will be used.
If set to ``'QR'``, ``QRsolve`` routine will be used.
If set to ``'PINV'``, ``pinv_solve`` routine will be used.
It also supports the methods available for special linear systems
For positive definite systems:
If set to ``'CH'``, ``cholesky_solve`` routine will be used.
If set to ``'LDL'``, ``LDLsolve`` routine will be used.
To use a different method and to compute the solution via the
inverse, use a method defined in the .inv() docstring.
Returns
=======
solutions : Matrix
Vector representing the solution.
Raises
======
ValueError
If there is not a unique solution then a ``ValueError`` will be
raised.
If ``self`` is not square, a ``ValueError`` and a different routine
for solving the system will be suggested.
"""
if method == 'GJ':
try:
soln, param = self.gauss_jordan_solve(rhs)
if param:
raise ValueError("Matrix det == 0; not invertible. "
"Try ``self.gauss_jordan_solve(rhs)`` to obtain a parametric solution.")
except ValueError:
# raise same error as in inv:
self.zeros(1).inv()
return soln
elif method == 'LU':
return self.LUsolve(rhs)
elif method == 'CH':
return self.cholesky_solve(rhs)
elif method == 'QR':
return self.QRsolve(rhs)
elif method == 'LDL':
return self.LDLsolve(rhs)
elif method == 'PINV':
return self.pinv_solve(rhs)
else:
return self.inv(method=method)*rhs
[docs] def table(self, printer, rowstart='[', rowend=']', rowsep='\n',
colsep=', ', align='right'):
r"""
String form of Matrix as a table.
``printer`` is the printer to use for on the elements (generally
something like StrPrinter())
``rowstart`` is the string used to start each row (by default '[').
``rowend`` is the string used to end each row (by default ']').
``rowsep`` is the string used to separate rows (by default a newline).
``colsep`` is the string used to separate columns (by default ', ').
``align`` defines how the elements are aligned. Must be one of 'left',
'right', or 'center'. You can also use '<', '>', and '^' to mean the
same thing, respectively.
This is used by the string printer for Matrix.
Examples
========
>>> from sympy import Matrix
>>> from sympy.printing.str import StrPrinter
>>> M = Matrix([[1, 2], [-33, 4]])
>>> printer = StrPrinter()
>>> M.table(printer)
'[ 1, 2]\n[-33, 4]'
>>> print(M.table(printer))
[ 1, 2]
[-33, 4]
>>> print(M.table(printer, rowsep=',\n'))
[ 1, 2],
[-33, 4]
>>> print('[%s]' % M.table(printer, rowsep=',\n'))
[[ 1, 2],
[-33, 4]]
>>> print(M.table(printer, colsep=' '))
[ 1 2]
[-33 4]
>>> print(M.table(printer, align='center'))
[ 1 , 2]
[-33, 4]
>>> print(M.table(printer, rowstart='{', rowend='}'))
{ 1, 2}
{-33, 4}
"""
# Handle zero dimensions:
if self.rows == 0 or self.cols == 0:
return '[]'
# Build table of string representations of the elements
res = []
# Track per-column max lengths for pretty alignment
maxlen = [0] * self.cols
for i in range(self.rows):
res.append([])
for j in range(self.cols):
s = printer._print(self[i, j])
res[-1].append(s)
maxlen[j] = max(len(s), maxlen[j])
# Patch strings together
align = {
'left': 'ljust',
'right': 'rjust',
'center': 'center',
'<': 'ljust',
'>': 'rjust',
'^': 'center',
}[align]
for i, row in enumerate(res):
for j, elem in enumerate(row):
row[j] = getattr(elem, align)(maxlen[j])
res[i] = rowstart + colsep.join(row) + rowend
return rowsep.join(res)
[docs] def upper_triangular_solve(self, rhs):
"""Solves ``Ax = B``, where A is an upper triangular matrix.
See Also
========
lower_triangular_solve
gauss_jordan_solve
cholesky_solve
diagonal_solve
LDLsolve
LUsolve
QRsolve
pinv_solve
"""
if not self.is_square:
raise NonSquareMatrixError("Matrix must be square.")
if rhs.rows != self.rows:
raise TypeError("Matrix size mismatch.")
if not self.is_upper:
raise TypeError("Matrix is not upper triangular.")
return self._upper_triangular_solve(rhs)
[docs] def vech(self, diagonal=True, check_symmetry=True):
"""Return the unique elements of a symmetric Matrix as a one column matrix
by stacking the elements in the lower triangle.
Arguments:
diagonal -- include the diagonal cells of ``self`` or not
check_symmetry -- checks symmetry of ``self`` but not completely reliably
Examples
========
>>> from sympy import Matrix
>>> m=Matrix([[1, 2], [2, 3]])
>>> m
Matrix([
[1, 2],
[2, 3]])
>>> m.vech()
Matrix([
[1],
[2],
[3]])
>>> m.vech(diagonal=False)
Matrix([[2]])
See Also
========
vec
"""
from sympy.matrices import zeros
c = self.cols
if c != self.rows:
raise ShapeError("Matrix must be square")
if check_symmetry:
self.simplify()
if self != self.transpose():
raise ValueError(
"Matrix appears to be asymmetric; consider check_symmetry=False")
count = 0
if diagonal:
v = zeros(c * (c + 1) // 2, 1)
for j in range(c):
for i in range(j, c):
v[count] = self[i, j]
count += 1
else:
v = zeros(c * (c - 1) // 2, 1)
for j in range(c):
for i in range(j + 1, c):
v[count] = self[i, j]
count += 1
return v
@deprecated(
issue=15109,
useinstead="from sympy.matrices.common import classof",
deprecated_since_version="1.3")
def classof(A, B):
from sympy.matrices.common import classof as classof_
return classof_(A, B)
[docs]@deprecated(
issue=15109,
deprecated_since_version="1.3",
useinstead="from sympy.matrices.common import a2idx")
def a2idx(j, n=None):
from sympy.matrices.common import a2idx as a2idx_
return a2idx_(j, n)
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 == True or is_zero == 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