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.

474 lines
14 KiB

from collections.abc import Callable
from sympy.core.containers import Dict
from sympy.utilities.exceptions import sympy_deprecation_warning
from sympy.utilities.iterables import is_sequence
from sympy.utilities.misc import as_int
from .matrices import MatrixBase
from .repmatrix import MutableRepMatrix, RepMatrix
from .utilities import _iszero
from .decompositions import (
_liupc, _row_structure_symbolic_cholesky, _cholesky_sparse,
_LDLdecomposition_sparse)
from .solvers import (
_lower_triangular_solve_sparse, _upper_triangular_solve_sparse)
class SparseRepMatrix(RepMatrix):
"""
A sparse matrix (a matrix with a large number of zero elements).
Examples
========
>>> from sympy import SparseMatrix, ones
>>> SparseMatrix(2, 2, range(4))
Matrix([
[0, 1],
[2, 3]])
>>> SparseMatrix(2, 2, {(1, 1): 2})
Matrix([
[0, 0],
[0, 2]])
A SparseMatrix can be instantiated from a ragged list of lists:
>>> SparseMatrix([[1, 2, 3], [1, 2], [1]])
Matrix([
[1, 2, 3],
[1, 2, 0],
[1, 0, 0]])
For safety, one may include the expected size and then an error
will be raised if the indices of any element are out of range or
(for a flat list) if the total number of elements does not match
the expected shape:
>>> SparseMatrix(2, 2, [1, 2])
Traceback (most recent call last):
...
ValueError: List length (2) != rows*columns (4)
Here, an error is not raised because the list is not flat and no
element is out of range:
>>> SparseMatrix(2, 2, [[1, 2]])
Matrix([
[1, 2],
[0, 0]])
But adding another element to the first (and only) row will cause
an error to be raised:
>>> SparseMatrix(2, 2, [[1, 2, 3]])
Traceback (most recent call last):
...
ValueError: The location (0, 2) is out of designated range: (1, 1)
To autosize the matrix, pass None for rows:
>>> SparseMatrix(None, [[1, 2, 3]])
Matrix([[1, 2, 3]])
>>> SparseMatrix(None, {(1, 1): 1, (3, 3): 3})
Matrix([
[0, 0, 0, 0],
[0, 1, 0, 0],
[0, 0, 0, 0],
[0, 0, 0, 3]])
Values that are themselves a Matrix are automatically expanded:
>>> SparseMatrix(4, 4, {(1, 1): ones(2)})
Matrix([
[0, 0, 0, 0],
[0, 1, 1, 0],
[0, 1, 1, 0],
[0, 0, 0, 0]])
A ValueError is raised if the expanding matrix tries to overwrite
a different element already present:
>>> SparseMatrix(3, 3, {(0, 0): ones(2), (1, 1): 2})
Traceback (most recent call last):
...
ValueError: collision at (1, 1)
See Also
========
DenseMatrix
MutableSparseMatrix
ImmutableSparseMatrix
"""
@classmethod
def _handle_creation_inputs(cls, *args, **kwargs):
if len(args) == 1 and isinstance(args[0], MatrixBase):
rows = args[0].rows
cols = args[0].cols
smat = args[0].todok()
return rows, cols, smat
smat = {}
# autosizing
if len(args) == 2 and args[0] is None:
args = [None, None, args[1]]
if len(args) == 3:
r, c = args[:2]
if r is c is None:
rows = cols = None
elif None in (r, c):
raise ValueError(
'Pass rows=None and no cols for autosizing.')
else:
rows, cols = as_int(args[0]), as_int(args[1])
if isinstance(args[2], Callable):
op = args[2]
if None in (rows, cols):
raise ValueError(
"{} and {} must be integers for this "
"specification.".format(rows, cols))
row_indices = [cls._sympify(i) for i in range(rows)]
col_indices = [cls._sympify(j) for j in range(cols)]
for i in row_indices:
for j in col_indices:
value = cls._sympify(op(i, j))
if value != cls.zero:
smat[i, j] = value
return rows, cols, smat
elif isinstance(args[2], (dict, Dict)):
def update(i, j, v):
# update smat and make sure there are no collisions
if v:
if (i, j) in smat and v != smat[i, j]:
raise ValueError(
"There is a collision at {} for {} and {}."
.format((i, j), v, smat[i, j])
)
smat[i, j] = v
# manual copy, copy.deepcopy() doesn't work
for (r, c), v in args[2].items():
if isinstance(v, MatrixBase):
for (i, j), vv in v.todok().items():
update(r + i, c + j, vv)
elif isinstance(v, (list, tuple)):
_, _, smat = cls._handle_creation_inputs(v, **kwargs)
for i, j in smat:
update(r + i, c + j, smat[i, j])
else:
v = cls._sympify(v)
update(r, c, cls._sympify(v))
elif is_sequence(args[2]):
flat = not any(is_sequence(i) for i in args[2])
if not flat:
_, _, smat = \
cls._handle_creation_inputs(args[2], **kwargs)
else:
flat_list = args[2]
if len(flat_list) != rows * cols:
raise ValueError(
"The length of the flat list ({}) does not "
"match the specified size ({} * {})."
.format(len(flat_list), rows, cols)
)
for i in range(rows):
for j in range(cols):
value = flat_list[i*cols + j]
value = cls._sympify(value)
if value != cls.zero:
smat[i, j] = value
if rows is None: # autosizing
keys = smat.keys()
rows = max([r for r, _ in keys]) + 1 if keys else 0
cols = max([c for _, c in keys]) + 1 if keys else 0
else:
for i, j in smat.keys():
if i and i >= rows or j and j >= cols:
raise ValueError(
"The location {} is out of the designated range"
"[{}, {}]x[{}, {}]"
.format((i, j), 0, rows - 1, 0, cols - 1)
)
return rows, cols, smat
elif len(args) == 1 and isinstance(args[0], (list, tuple)):
# list of values or lists
v = args[0]
c = 0
for i, row in enumerate(v):
if not isinstance(row, (list, tuple)):
row = [row]
for j, vv in enumerate(row):
if vv != cls.zero:
smat[i, j] = cls._sympify(vv)
c = max(c, len(row))
rows = len(v) if c else 0
cols = c
return rows, cols, smat
else:
# handle full matrix forms with _handle_creation_inputs
rows, cols, mat = super()._handle_creation_inputs(*args)
for i in range(rows):
for j in range(cols):
value = mat[cols*i + j]
if value != cls.zero:
smat[i, j] = value
return rows, cols, smat
@property
def _smat(self):
sympy_deprecation_warning(
"""
The private _smat attribute of SparseMatrix is deprecated. Use the
.todok() method instead.
""",
deprecated_since_version="1.9",
active_deprecations_target="deprecated-private-matrix-attributes"
)
return self.todok()
def _eval_inverse(self, **kwargs):
return self.inv(method=kwargs.get('method', 'LDL'),
iszerofunc=kwargs.get('iszerofunc', _iszero),
try_block_diag=kwargs.get('try_block_diag', False))
def applyfunc(self, f):
"""Apply a function to each element of the matrix.
Examples
========
>>> from sympy import SparseMatrix
>>> m = SparseMatrix(2, 2, lambda i, j: i*2+j)
>>> m
Matrix([
[0, 1],
[2, 3]])
>>> m.applyfunc(lambda i: 2*i)
Matrix([
[0, 2],
[4, 6]])
"""
if not callable(f):
raise TypeError("`f` must be callable.")
# XXX: This only applies the function to the nonzero elements of the
# matrix so is inconsistent with DenseMatrix.applyfunc e.g.
# zeros(2, 2).applyfunc(lambda x: x + 1)
dok = {}
for k, v in self.todok().items():
fv = f(v)
if fv != 0:
dok[k] = fv
return self._new(self.rows, self.cols, dok)
def as_immutable(self):
"""Returns an Immutable version of this Matrix."""
from .immutable import ImmutableSparseMatrix
return ImmutableSparseMatrix(self)
def as_mutable(self):
"""Returns a mutable version of this matrix.
Examples
========
>>> from sympy import ImmutableMatrix
>>> X = ImmutableMatrix([[1, 2], [3, 4]])
>>> Y = X.as_mutable()
>>> Y[1, 1] = 5 # Can set values in Y
>>> Y
Matrix([
[1, 2],
[3, 5]])
"""
return MutableSparseMatrix(self)
def col_list(self):
"""Returns a column-sorted list of non-zero elements of the matrix.
Examples
========
>>> from sympy import SparseMatrix
>>> a=SparseMatrix(((1, 2), (3, 4)))
>>> a
Matrix([
[1, 2],
[3, 4]])
>>> a.CL
[(0, 0, 1), (1, 0, 3), (0, 1, 2), (1, 1, 4)]
See Also
========
sympy.matrices.sparse.SparseMatrix.row_list
"""
return [tuple(k + (self[k],)) for k in sorted(self.todok().keys(), key=lambda k: list(reversed(k)))]
def nnz(self):
"""Returns the number of non-zero elements in Matrix."""
return len(self.todok())
def row_list(self):
"""Returns a row-sorted list of non-zero elements of the matrix.
Examples
========
>>> from sympy import SparseMatrix
>>> a = SparseMatrix(((1, 2), (3, 4)))
>>> a
Matrix([
[1, 2],
[3, 4]])
>>> a.RL
[(0, 0, 1), (0, 1, 2), (1, 0, 3), (1, 1, 4)]
See Also
========
sympy.matrices.sparse.SparseMatrix.col_list
"""
return [tuple(k + (self[k],)) for k in
sorted(self.todok().keys(), key=list)]
def scalar_multiply(self, scalar):
"Scalar element-wise multiplication"
return scalar * self
def solve_least_squares(self, rhs, method='LDL'):
"""Return the least-square fit to the data.
By default the cholesky_solve routine is used (method='CH'); other
methods of matrix inversion can be used. To find out which are
available, see the docstring of the .inv() method.
Examples
========
>>> from sympy import SparseMatrix, Matrix, ones
>>> A = Matrix([1, 2, 3])
>>> B = Matrix([2, 3, 4])
>>> S = SparseMatrix(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
"""
t = self.T
return (t*self).inv(method=method)*t*rhs
def solve(self, rhs, method='LDL'):
"""Return solution to self*soln = rhs using given inversion method.
For a list of possible inversion methods, see the .inv() docstring.
"""
if not self.is_square:
if self.rows < self.cols:
raise ValueError('Under-determined system.')
elif self.rows > self.cols:
raise ValueError('For over-determined system, M, having '
'more rows than columns, try M.solve_least_squares(rhs).')
else:
return self.inv(method=method).multiply(rhs)
RL = property(row_list, None, None, "Alternate faster representation")
CL = property(col_list, None, None, "Alternate faster representation")
def liupc(self):
return _liupc(self)
def row_structure_symbolic_cholesky(self):
return _row_structure_symbolic_cholesky(self)
def cholesky(self, hermitian=True):
return _cholesky_sparse(self, hermitian=hermitian)
def LDLdecomposition(self, hermitian=True):
return _LDLdecomposition_sparse(self, hermitian=hermitian)
def lower_triangular_solve(self, rhs):
return _lower_triangular_solve_sparse(self, rhs)
def upper_triangular_solve(self, rhs):
return _upper_triangular_solve_sparse(self, rhs)
liupc.__doc__ = _liupc.__doc__
row_structure_symbolic_cholesky.__doc__ = _row_structure_symbolic_cholesky.__doc__
cholesky.__doc__ = _cholesky_sparse.__doc__
LDLdecomposition.__doc__ = _LDLdecomposition_sparse.__doc__
lower_triangular_solve.__doc__ = lower_triangular_solve.__doc__
upper_triangular_solve.__doc__ = upper_triangular_solve.__doc__
class MutableSparseMatrix(SparseRepMatrix, MutableRepMatrix):
@classmethod
def _new(cls, *args, **kwargs):
rows, cols, smat = cls._handle_creation_inputs(*args, **kwargs)
rep = cls._smat_to_DomainMatrix(rows, cols, smat)
return cls._fromrep(rep)
SparseMatrix = MutableSparseMatrix