You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
863 lines
22 KiB
863 lines
22 KiB
5 months ago
|
from sympy.core.function import expand_mul
|
||
|
from sympy.core.symbol import Dummy, uniquely_named_symbol, symbols
|
||
|
from sympy.utilities.iterables import numbered_symbols
|
||
|
|
||
|
from .common import ShapeError, NonSquareMatrixError, NonInvertibleMatrixError
|
||
|
from .eigen import _fuzzy_positive_definite
|
||
|
from .utilities import _get_intermediate_simp, _iszero
|
||
|
|
||
|
|
||
|
def _diagonal_solve(M, rhs):
|
||
|
"""Solves ``Ax = B`` efficiently, where A is a diagonal Matrix,
|
||
|
with non-zero diagonal entries.
|
||
|
|
||
|
Examples
|
||
|
========
|
||
|
|
||
|
>>> from sympy import Matrix, eye
|
||
|
>>> A = eye(2)*2
|
||
|
>>> B = Matrix([[1, 2], [3, 4]])
|
||
|
>>> A.diagonal_solve(B) == B/2
|
||
|
True
|
||
|
|
||
|
See Also
|
||
|
========
|
||
|
|
||
|
sympy.matrices.dense.DenseMatrix.lower_triangular_solve
|
||
|
sympy.matrices.dense.DenseMatrix.upper_triangular_solve
|
||
|
gauss_jordan_solve
|
||
|
cholesky_solve
|
||
|
LDLsolve
|
||
|
LUsolve
|
||
|
QRsolve
|
||
|
pinv_solve
|
||
|
"""
|
||
|
|
||
|
if not M.is_diagonal():
|
||
|
raise TypeError("Matrix should be diagonal")
|
||
|
if rhs.rows != M.rows:
|
||
|
raise TypeError("Size mismatch")
|
||
|
|
||
|
return M._new(
|
||
|
rhs.rows, rhs.cols, lambda i, j: rhs[i, j] / M[i, i])
|
||
|
|
||
|
|
||
|
def _lower_triangular_solve(M, 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
|
||
|
"""
|
||
|
|
||
|
from .dense import MutableDenseMatrix
|
||
|
|
||
|
if not M.is_square:
|
||
|
raise NonSquareMatrixError("Matrix must be square.")
|
||
|
if rhs.rows != M.rows:
|
||
|
raise ShapeError("Matrices size mismatch.")
|
||
|
if not M.is_lower:
|
||
|
raise ValueError("Matrix must be lower triangular.")
|
||
|
|
||
|
dps = _get_intermediate_simp()
|
||
|
X = MutableDenseMatrix.zeros(M.rows, rhs.cols)
|
||
|
|
||
|
for j in range(rhs.cols):
|
||
|
for i in range(M.rows):
|
||
|
if M[i, i] == 0:
|
||
|
raise TypeError("Matrix must be non-singular.")
|
||
|
|
||
|
X[i, j] = dps((rhs[i, j] - sum(M[i, k]*X[k, j]
|
||
|
for k in range(i))) / M[i, i])
|
||
|
|
||
|
return M._new(X)
|
||
|
|
||
|
def _lower_triangular_solve_sparse(M, 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 M.is_square:
|
||
|
raise NonSquareMatrixError("Matrix must be square.")
|
||
|
if rhs.rows != M.rows:
|
||
|
raise ShapeError("Matrices size mismatch.")
|
||
|
if not M.is_lower:
|
||
|
raise ValueError("Matrix must be lower triangular.")
|
||
|
|
||
|
dps = _get_intermediate_simp()
|
||
|
rows = [[] for i in range(M.rows)]
|
||
|
|
||
|
for i, j, v in M.row_list():
|
||
|
if i > j:
|
||
|
rows[i].append((j, v))
|
||
|
|
||
|
X = rhs.as_mutable()
|
||
|
|
||
|
for j in range(rhs.cols):
|
||
|
for i in range(rhs.rows):
|
||
|
for u, v in rows[i]:
|
||
|
X[i, j] -= v*X[u, j]
|
||
|
|
||
|
X[i, j] = dps(X[i, j] / M[i, i])
|
||
|
|
||
|
return M._new(X)
|
||
|
|
||
|
|
||
|
def _upper_triangular_solve(M, 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
|
||
|
"""
|
||
|
|
||
|
from .dense import MutableDenseMatrix
|
||
|
|
||
|
if not M.is_square:
|
||
|
raise NonSquareMatrixError("Matrix must be square.")
|
||
|
if rhs.rows != M.rows:
|
||
|
raise ShapeError("Matrix size mismatch.")
|
||
|
if not M.is_upper:
|
||
|
raise TypeError("Matrix is not upper triangular.")
|
||
|
|
||
|
dps = _get_intermediate_simp()
|
||
|
X = MutableDenseMatrix.zeros(M.rows, rhs.cols)
|
||
|
|
||
|
for j in range(rhs.cols):
|
||
|
for i in reversed(range(M.rows)):
|
||
|
if M[i, i] == 0:
|
||
|
raise ValueError("Matrix must be non-singular.")
|
||
|
|
||
|
X[i, j] = dps((rhs[i, j] - sum(M[i, k]*X[k, j]
|
||
|
for k in range(i + 1, M.rows))) / M[i, i])
|
||
|
|
||
|
return M._new(X)
|
||
|
|
||
|
def _upper_triangular_solve_sparse(M, 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 M.is_square:
|
||
|
raise NonSquareMatrixError("Matrix must be square.")
|
||
|
if rhs.rows != M.rows:
|
||
|
raise ShapeError("Matrix size mismatch.")
|
||
|
if not M.is_upper:
|
||
|
raise TypeError("Matrix is not upper triangular.")
|
||
|
|
||
|
dps = _get_intermediate_simp()
|
||
|
rows = [[] for i in range(M.rows)]
|
||
|
|
||
|
for i, j, v in M.row_list():
|
||
|
if i < j:
|
||
|
rows[i].append((j, v))
|
||
|
|
||
|
X = rhs.as_mutable()
|
||
|
|
||
|
for j in range(rhs.cols):
|
||
|
for i in reversed(range(rhs.rows)):
|
||
|
for u, v in reversed(rows[i]):
|
||
|
X[i, j] -= v*X[u, j]
|
||
|
|
||
|
X[i, j] = dps(X[i, j] / M[i, i])
|
||
|
|
||
|
return M._new(X)
|
||
|
|
||
|
|
||
|
def _cholesky_solve(M, 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
|
||
|
========
|
||
|
|
||
|
sympy.matrices.dense.DenseMatrix.lower_triangular_solve
|
||
|
sympy.matrices.dense.DenseMatrix.upper_triangular_solve
|
||
|
gauss_jordan_solve
|
||
|
diagonal_solve
|
||
|
LDLsolve
|
||
|
LUsolve
|
||
|
QRsolve
|
||
|
pinv_solve
|
||
|
"""
|
||
|
|
||
|
if M.rows < M.cols:
|
||
|
raise NotImplementedError(
|
||
|
'Under-determined System. Try M.gauss_jordan_solve(rhs)')
|
||
|
|
||
|
hermitian = True
|
||
|
reform = False
|
||
|
|
||
|
if M.is_symmetric():
|
||
|
hermitian = False
|
||
|
elif not M.is_hermitian:
|
||
|
reform = True
|
||
|
|
||
|
if reform or _fuzzy_positive_definite(M) is False:
|
||
|
H = M.H
|
||
|
M = H.multiply(M)
|
||
|
rhs = H.multiply(rhs)
|
||
|
hermitian = not M.is_symmetric()
|
||
|
|
||
|
L = M.cholesky(hermitian=hermitian)
|
||
|
Y = L.lower_triangular_solve(rhs)
|
||
|
|
||
|
if hermitian:
|
||
|
return (L.H).upper_triangular_solve(Y)
|
||
|
else:
|
||
|
return (L.T).upper_triangular_solve(Y)
|
||
|
|
||
|
|
||
|
def _LDLsolve(M, 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 import Matrix, eye
|
||
|
>>> A = eye(2)*2
|
||
|
>>> B = Matrix([[1, 2], [3, 4]])
|
||
|
>>> A.LDLsolve(B) == B/2
|
||
|
True
|
||
|
|
||
|
See Also
|
||
|
========
|
||
|
|
||
|
sympy.matrices.dense.DenseMatrix.LDLdecomposition
|
||
|
sympy.matrices.dense.DenseMatrix.lower_triangular_solve
|
||
|
sympy.matrices.dense.DenseMatrix.upper_triangular_solve
|
||
|
gauss_jordan_solve
|
||
|
cholesky_solve
|
||
|
diagonal_solve
|
||
|
LUsolve
|
||
|
QRsolve
|
||
|
pinv_solve
|
||
|
"""
|
||
|
|
||
|
if M.rows < M.cols:
|
||
|
raise NotImplementedError(
|
||
|
'Under-determined System. Try M.gauss_jordan_solve(rhs)')
|
||
|
|
||
|
hermitian = True
|
||
|
reform = False
|
||
|
|
||
|
if M.is_symmetric():
|
||
|
hermitian = False
|
||
|
elif not M.is_hermitian:
|
||
|
reform = True
|
||
|
|
||
|
if reform or _fuzzy_positive_definite(M) is False:
|
||
|
H = M.H
|
||
|
M = H.multiply(M)
|
||
|
rhs = H.multiply(rhs)
|
||
|
hermitian = not M.is_symmetric()
|
||
|
|
||
|
L, D = M.LDLdecomposition(hermitian=hermitian)
|
||
|
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)
|
||
|
|
||
|
|
||
|
def _LUsolve(M, rhs, iszerofunc=_iszero):
|
||
|
"""Solve the linear system ``Ax = rhs`` for ``x`` where ``A = M``.
|
||
|
|
||
|
This is for symbolic matrices, for real or complex ones use
|
||
|
mpmath.lu_solve or mpmath.qr_solve.
|
||
|
|
||
|
See Also
|
||
|
========
|
||
|
|
||
|
sympy.matrices.dense.DenseMatrix.lower_triangular_solve
|
||
|
sympy.matrices.dense.DenseMatrix.upper_triangular_solve
|
||
|
gauss_jordan_solve
|
||
|
cholesky_solve
|
||
|
diagonal_solve
|
||
|
LDLsolve
|
||
|
QRsolve
|
||
|
pinv_solve
|
||
|
LUdecomposition
|
||
|
"""
|
||
|
|
||
|
if rhs.rows != M.rows:
|
||
|
raise ShapeError(
|
||
|
"``M`` and ``rhs`` must have the same number of rows.")
|
||
|
|
||
|
m = M.rows
|
||
|
n = M.cols
|
||
|
|
||
|
if m < n:
|
||
|
raise NotImplementedError("Underdetermined systems not supported.")
|
||
|
|
||
|
try:
|
||
|
A, perm = M.LUdecomposition_Simple(
|
||
|
iszerofunc=_iszero, rankcheck=True)
|
||
|
except ValueError:
|
||
|
raise NonInvertibleMatrixError("Matrix det == 0; not invertible.")
|
||
|
|
||
|
dps = _get_intermediate_simp()
|
||
|
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: dps(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: dps(x - y * scale))
|
||
|
|
||
|
scale = A[i, i]
|
||
|
b.row_op(i, lambda x, _: dps(x / scale))
|
||
|
|
||
|
return rhs.__class__(b)
|
||
|
|
||
|
|
||
|
def _QRsolve(M, b):
|
||
|
"""Solve the linear system ``Ax = b``.
|
||
|
|
||
|
``M`` 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 do not need
|
||
|
to use QRsolve.
|
||
|
|
||
|
This is mainly for educational purposes and symbolic matrices, for real
|
||
|
(or complex) matrices use mpmath.qr_solve.
|
||
|
|
||
|
See Also
|
||
|
========
|
||
|
|
||
|
sympy.matrices.dense.DenseMatrix.lower_triangular_solve
|
||
|
sympy.matrices.dense.DenseMatrix.upper_triangular_solve
|
||
|
gauss_jordan_solve
|
||
|
cholesky_solve
|
||
|
diagonal_solve
|
||
|
LDLsolve
|
||
|
LUsolve
|
||
|
pinv_solve
|
||
|
QRdecomposition
|
||
|
"""
|
||
|
|
||
|
dps = _get_intermediate_simp(expand_mul, expand_mul)
|
||
|
Q, R = M.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]
|
||
|
|
||
|
tmp = dps(tmp)
|
||
|
|
||
|
x.append(tmp / R[j, j])
|
||
|
|
||
|
return M.vstack(*x[::-1])
|
||
|
|
||
|
|
||
|
def _gauss_jordan_solve(M, 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 : boolean, optional
|
||
|
Flag, when set to `True` will return the indices of the free
|
||
|
variables in the solutions (column Matrix), for a system that is
|
||
|
undetermined (e.g. A has more columns than rows), for which
|
||
|
infinite solutions are possible, in terms of arbitrary
|
||
|
values of free variables. Default `False`.
|
||
|
|
||
|
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.
|
||
|
|
||
|
free_var_index : List, optional
|
||
|
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 indices of the free variables
|
||
|
in the solutions (column Matrix) are returned by free_var_index,
|
||
|
if the flag `freevar` is set to `True`.
|
||
|
|
||
|
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, [])
|
||
|
|
||
|
|
||
|
>>> 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, freevars = A.gauss_jordan_solve(B, freevar=True)
|
||
|
>>> sol
|
||
|
Matrix([
|
||
|
[-2*tau0 - 3*tau1 + 2],
|
||
|
[ tau0],
|
||
|
[ 2*tau1 + 5],
|
||
|
[ tau1]])
|
||
|
>>> params
|
||
|
Matrix([
|
||
|
[tau0],
|
||
|
[tau1]])
|
||
|
>>> freevars
|
||
|
[1, 3]
|
||
|
|
||
|
|
||
|
See Also
|
||
|
========
|
||
|
|
||
|
sympy.matrices.dense.DenseMatrix.lower_triangular_solve
|
||
|
sympy.matrices.dense.DenseMatrix.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
|
||
|
|
||
|
cls = M.__class__
|
||
|
aug = M.hstack(M.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)
|
||
|
|
||
|
# Get index of free symbols (free parameters)
|
||
|
# non-pivots columns are free variables
|
||
|
free_var_index = [c for c in range(A.cols) if c not in pivots]
|
||
|
|
||
|
# Bring to block form
|
||
|
permutation = Matrix(pivots + free_var_index).T
|
||
|
|
||
|
# check for existence of solutions
|
||
|
# rank of aug Matrix should be equal to rank of coefficient matrix
|
||
|
if not v[rank:, :].is_zero_matrix:
|
||
|
raise ValueError("Linear system has no solution")
|
||
|
|
||
|
# Free parameters
|
||
|
# what are current unnumbered free symbol names?
|
||
|
name = uniquely_named_symbol('tau', aug,
|
||
|
compare=lambda i: str(i).rstrip('1234567890'),
|
||
|
modify=lambda s: '_' + s).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, free_var_index]
|
||
|
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,:]
|
||
|
|
||
|
sol, tau = cls(sol), cls(tau)
|
||
|
|
||
|
if freevar:
|
||
|
return sol, tau, free_var_index
|
||
|
else:
|
||
|
return sol, tau
|
||
|
|
||
|
|
||
|
def _pinv_solve(M, 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
|
||
|
========
|
||
|
|
||
|
sympy.matrices.dense.DenseMatrix.lower_triangular_solve
|
||
|
sympy.matrices.dense.DenseMatrix.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 = M
|
||
|
A_pinv = M.pinv()
|
||
|
|
||
|
if arbitrary_matrix is None:
|
||
|
rows, cols = A.cols, B.cols
|
||
|
w = symbols('w:{}_:{}'.format(rows, cols), cls=Dummy)
|
||
|
arbitrary_matrix = M.__class__(cols, rows, w).T
|
||
|
|
||
|
return A_pinv.multiply(B) + (eye(A.cols) -
|
||
|
A_pinv.multiply(A)).multiply(arbitrary_matrix)
|
||
|
|
||
|
|
||
|
def _solve(M, 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'`` or ``'GE'``, 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 ``M`` is not square, a ``ValueError`` and a different routine
|
||
|
for solving the system will be suggested.
|
||
|
"""
|
||
|
|
||
|
if method in ('GJ', 'GE'):
|
||
|
try:
|
||
|
soln, param = M.gauss_jordan_solve(rhs)
|
||
|
|
||
|
if param:
|
||
|
raise NonInvertibleMatrixError("Matrix det == 0; not invertible. "
|
||
|
"Try ``M.gauss_jordan_solve(rhs)`` to obtain a parametric solution.")
|
||
|
|
||
|
except ValueError:
|
||
|
raise NonInvertibleMatrixError("Matrix det == 0; not invertible.")
|
||
|
|
||
|
return soln
|
||
|
|
||
|
elif method == 'LU':
|
||
|
return M.LUsolve(rhs)
|
||
|
elif method == 'CH':
|
||
|
return M.cholesky_solve(rhs)
|
||
|
elif method == 'QR':
|
||
|
return M.QRsolve(rhs)
|
||
|
elif method == 'LDL':
|
||
|
return M.LDLsolve(rhs)
|
||
|
elif method == 'PINV':
|
||
|
return M.pinv_solve(rhs)
|
||
|
else:
|
||
|
return M.inv(method=method).multiply(rhs)
|
||
|
|
||
|
|
||
|
def _solve_least_squares(M, 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 ``M`` 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 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 M.cholesky_solve(rhs)
|
||
|
elif method == 'QR':
|
||
|
return M.QRsolve(rhs)
|
||
|
elif method == 'LDL':
|
||
|
return M.LDLsolve(rhs)
|
||
|
elif method == 'PINV':
|
||
|
return M.pinv_solve(rhs)
|
||
|
else:
|
||
|
t = M.H
|
||
|
return (t * M).solve(t * rhs, method=method)
|