3851 lines
109 KiB
Python
3851 lines
109 KiB
Python
"""
|
||
|
||
Module for the DomainMatrix class.
|
||
|
||
A DomainMatrix represents a matrix with elements that are in a particular
|
||
Domain. Each DomainMatrix internally wraps a DDM which is used for the
|
||
lower-level operations. The idea is that the DomainMatrix class provides the
|
||
convenience routines for converting between Expr and the poly domains as well
|
||
as unifying matrices with different domains.
|
||
|
||
"""
|
||
from collections import Counter
|
||
from functools import reduce
|
||
from typing import Union as tUnion, Tuple as tTuple
|
||
|
||
from sympy.external.gmpy import GROUND_TYPES
|
||
from sympy.utilities.decorator import doctest_depends_on
|
||
|
||
from sympy.core.sympify import _sympify
|
||
|
||
from ..domains import Domain
|
||
|
||
from ..constructor import construct_domain
|
||
|
||
from .exceptions import (
|
||
DMFormatError,
|
||
DMBadInputError,
|
||
DMShapeError,
|
||
DMDomainError,
|
||
DMNotAField,
|
||
DMNonSquareMatrixError,
|
||
DMNonInvertibleMatrixError
|
||
)
|
||
|
||
from .domainscalar import DomainScalar
|
||
|
||
from sympy.polys.domains import ZZ, EXRAW, QQ
|
||
|
||
from sympy.polys.densearith import dup_mul
|
||
from sympy.polys.densebasic import dup_convert
|
||
from sympy.polys.densetools import (
|
||
dup_mul_ground,
|
||
dup_quo_ground,
|
||
dup_content,
|
||
dup_clear_denoms,
|
||
dup_primitive,
|
||
dup_transform,
|
||
)
|
||
from sympy.polys.factortools import dup_factor_list
|
||
from sympy.polys.polyutils import _sort_factors
|
||
|
||
from .ddm import DDM
|
||
|
||
from .sdm import SDM
|
||
|
||
from .dfm import DFM
|
||
|
||
from .rref import _dm_rref, _dm_rref_den
|
||
|
||
|
||
if GROUND_TYPES != 'flint':
|
||
__doctest_skip__ = ['DomainMatrix.to_dfm', 'DomainMatrix.to_dfm_or_ddm']
|
||
else:
|
||
__doctest_skip__ = ['DomainMatrix.from_list']
|
||
|
||
|
||
def DM(rows, domain):
|
||
"""Convenient alias for DomainMatrix.from_list
|
||
|
||
Examples
|
||
========
|
||
|
||
>>> from sympy import ZZ
|
||
>>> from sympy.polys.matrices import DM
|
||
>>> DM([[1, 2], [3, 4]], ZZ)
|
||
DomainMatrix([[1, 2], [3, 4]], (2, 2), ZZ)
|
||
|
||
See Also
|
||
========
|
||
|
||
DomainMatrix.from_list
|
||
"""
|
||
return DomainMatrix.from_list(rows, domain)
|
||
|
||
|
||
class DomainMatrix:
|
||
r"""
|
||
Associate Matrix with :py:class:`~.Domain`
|
||
|
||
Explanation
|
||
===========
|
||
|
||
DomainMatrix uses :py:class:`~.Domain` for its internal representation
|
||
which makes it faster than the SymPy Matrix class (currently) for many
|
||
common operations, but this advantage makes it not entirely compatible
|
||
with Matrix. DomainMatrix are analogous to numpy arrays with "dtype".
|
||
In the DomainMatrix, each element has a domain such as :ref:`ZZ`
|
||
or :ref:`QQ(a)`.
|
||
|
||
|
||
Examples
|
||
========
|
||
|
||
Creating a DomainMatrix from the existing Matrix class:
|
||
|
||
>>> from sympy import Matrix
|
||
>>> from sympy.polys.matrices import DomainMatrix
|
||
>>> Matrix1 = Matrix([
|
||
... [1, 2],
|
||
... [3, 4]])
|
||
>>> A = DomainMatrix.from_Matrix(Matrix1)
|
||
>>> A
|
||
DomainMatrix({0: {0: 1, 1: 2}, 1: {0: 3, 1: 4}}, (2, 2), ZZ)
|
||
|
||
Directly forming a DomainMatrix:
|
||
|
||
>>> from sympy import ZZ
|
||
>>> from sympy.polys.matrices import DomainMatrix
|
||
>>> A = DomainMatrix([
|
||
... [ZZ(1), ZZ(2)],
|
||
... [ZZ(3), ZZ(4)]], (2, 2), ZZ)
|
||
>>> A
|
||
DomainMatrix([[1, 2], [3, 4]], (2, 2), ZZ)
|
||
|
||
See Also
|
||
========
|
||
|
||
DDM
|
||
SDM
|
||
Domain
|
||
Poly
|
||
|
||
"""
|
||
rep: tUnion[SDM, DDM, DFM]
|
||
shape: tTuple[int, int]
|
||
domain: Domain
|
||
|
||
def __new__(cls, rows, shape, domain, *, fmt=None):
|
||
"""
|
||
Creates a :py:class:`~.DomainMatrix`.
|
||
|
||
Parameters
|
||
==========
|
||
|
||
rows : Represents elements of DomainMatrix as list of lists
|
||
shape : Represents dimension of DomainMatrix
|
||
domain : Represents :py:class:`~.Domain` of DomainMatrix
|
||
|
||
Raises
|
||
======
|
||
|
||
TypeError
|
||
If any of rows, shape and domain are not provided
|
||
|
||
"""
|
||
if isinstance(rows, (DDM, SDM, DFM)):
|
||
raise TypeError("Use from_rep to initialise from SDM/DDM")
|
||
elif isinstance(rows, list):
|
||
rep = DDM(rows, shape, domain)
|
||
elif isinstance(rows, dict):
|
||
rep = SDM(rows, shape, domain)
|
||
else:
|
||
msg = "Input should be list-of-lists or dict-of-dicts"
|
||
raise TypeError(msg)
|
||
|
||
if fmt is not None:
|
||
if fmt == 'sparse':
|
||
rep = rep.to_sdm()
|
||
elif fmt == 'dense':
|
||
rep = rep.to_ddm()
|
||
else:
|
||
raise ValueError("fmt should be 'sparse' or 'dense'")
|
||
|
||
# Use python-flint for dense matrices if possible
|
||
if rep.fmt == 'dense' and DFM._supports_domain(domain):
|
||
rep = rep.to_dfm()
|
||
|
||
return cls.from_rep(rep)
|
||
|
||
def __reduce__(self):
|
||
rep = self.rep
|
||
if rep.fmt == 'dense':
|
||
arg = self.to_list()
|
||
elif rep.fmt == 'sparse':
|
||
arg = dict(rep)
|
||
else:
|
||
raise RuntimeError # pragma: no cover
|
||
args = (arg, rep.shape, rep.domain)
|
||
return (self.__class__, args)
|
||
|
||
def __getitem__(self, key):
|
||
i, j = key
|
||
m, n = self.shape
|
||
if not (isinstance(i, slice) or isinstance(j, slice)):
|
||
return DomainScalar(self.rep.getitem(i, j), self.domain)
|
||
|
||
if not isinstance(i, slice):
|
||
if not -m <= i < m:
|
||
raise IndexError("Row index out of range")
|
||
i = i % m
|
||
i = slice(i, i+1)
|
||
if not isinstance(j, slice):
|
||
if not -n <= j < n:
|
||
raise IndexError("Column index out of range")
|
||
j = j % n
|
||
j = slice(j, j+1)
|
||
|
||
return self.from_rep(self.rep.extract_slice(i, j))
|
||
|
||
def getitem_sympy(self, i, j):
|
||
return self.domain.to_sympy(self.rep.getitem(i, j))
|
||
|
||
def extract(self, rowslist, colslist):
|
||
return self.from_rep(self.rep.extract(rowslist, colslist))
|
||
|
||
def __setitem__(self, key, value):
|
||
i, j = key
|
||
if not self.domain.of_type(value):
|
||
raise TypeError
|
||
if isinstance(i, int) and isinstance(j, int):
|
||
self.rep.setitem(i, j, value)
|
||
else:
|
||
raise NotImplementedError
|
||
|
||
@classmethod
|
||
def from_rep(cls, rep):
|
||
"""Create a new DomainMatrix efficiently from DDM/SDM.
|
||
|
||
Examples
|
||
========
|
||
|
||
Create a :py:class:`~.DomainMatrix` with an dense internal
|
||
representation as :py:class:`~.DDM`:
|
||
|
||
>>> from sympy.polys.domains import ZZ
|
||
>>> from sympy.polys.matrices import DomainMatrix
|
||
>>> from sympy.polys.matrices.ddm import DDM
|
||
>>> drep = DDM([[ZZ(1), ZZ(2)], [ZZ(3), ZZ(4)]], (2, 2), ZZ)
|
||
>>> dM = DomainMatrix.from_rep(drep)
|
||
>>> dM
|
||
DomainMatrix([[1, 2], [3, 4]], (2, 2), ZZ)
|
||
|
||
Create a :py:class:`~.DomainMatrix` with a sparse internal
|
||
representation as :py:class:`~.SDM`:
|
||
|
||
>>> from sympy.polys.matrices import DomainMatrix
|
||
>>> from sympy.polys.matrices.sdm import SDM
|
||
>>> from sympy import ZZ
|
||
>>> drep = SDM({0:{1:ZZ(1)},1:{0:ZZ(2)}}, (2, 2), ZZ)
|
||
>>> dM = DomainMatrix.from_rep(drep)
|
||
>>> dM
|
||
DomainMatrix({0: {1: 1}, 1: {0: 2}}, (2, 2), ZZ)
|
||
|
||
Parameters
|
||
==========
|
||
|
||
rep: SDM or DDM
|
||
The internal sparse or dense representation of the matrix.
|
||
|
||
Returns
|
||
=======
|
||
|
||
DomainMatrix
|
||
A :py:class:`~.DomainMatrix` wrapping *rep*.
|
||
|
||
Notes
|
||
=====
|
||
|
||
This takes ownership of rep as its internal representation. If rep is
|
||
being mutated elsewhere then a copy should be provided to
|
||
``from_rep``. Only minimal verification or checking is done on *rep*
|
||
as this is supposed to be an efficient internal routine.
|
||
|
||
"""
|
||
if not (isinstance(rep, (DDM, SDM)) or (DFM is not None and isinstance(rep, DFM))):
|
||
raise TypeError("rep should be of type DDM or SDM")
|
||
self = super().__new__(cls)
|
||
self.rep = rep
|
||
self.shape = rep.shape
|
||
self.domain = rep.domain
|
||
return self
|
||
|
||
@classmethod
|
||
@doctest_depends_on(ground_types=['python', 'gmpy'])
|
||
def from_list(cls, rows, domain):
|
||
r"""
|
||
Convert a list of lists into a DomainMatrix
|
||
|
||
Parameters
|
||
==========
|
||
|
||
rows: list of lists
|
||
Each element of the inner lists should be either the single arg,
|
||
or tuple of args, that would be passed to the domain constructor
|
||
in order to form an element of the domain. See examples.
|
||
|
||
Returns
|
||
=======
|
||
|
||
DomainMatrix containing elements defined in rows
|
||
|
||
Examples
|
||
========
|
||
|
||
>>> from sympy.polys.matrices import DomainMatrix
|
||
>>> from sympy import FF, QQ, ZZ
|
||
>>> A = DomainMatrix.from_list([[1, 0, 1], [0, 0, 1]], ZZ)
|
||
>>> A
|
||
DomainMatrix([[1, 0, 1], [0, 0, 1]], (2, 3), ZZ)
|
||
>>> B = DomainMatrix.from_list([[1, 0, 1], [0, 0, 1]], FF(7))
|
||
>>> B
|
||
DomainMatrix([[1 mod 7, 0 mod 7, 1 mod 7], [0 mod 7, 0 mod 7, 1 mod 7]], (2, 3), GF(7))
|
||
>>> C = DomainMatrix.from_list([[(1, 2), (3, 1)], [(1, 4), (5, 1)]], QQ)
|
||
>>> C
|
||
DomainMatrix([[1/2, 3], [1/4, 5]], (2, 2), QQ)
|
||
|
||
See Also
|
||
========
|
||
|
||
from_list_sympy
|
||
|
||
"""
|
||
nrows = len(rows)
|
||
ncols = 0 if not nrows else len(rows[0])
|
||
conv = lambda e: domain(*e) if isinstance(e, tuple) else domain(e)
|
||
domain_rows = [[conv(e) for e in row] for row in rows]
|
||
return DomainMatrix(domain_rows, (nrows, ncols), domain)
|
||
|
||
@classmethod
|
||
def from_list_sympy(cls, nrows, ncols, rows, **kwargs):
|
||
r"""
|
||
Convert a list of lists of Expr into a DomainMatrix using construct_domain
|
||
|
||
Parameters
|
||
==========
|
||
|
||
nrows: number of rows
|
||
ncols: number of columns
|
||
rows: list of lists
|
||
|
||
Returns
|
||
=======
|
||
|
||
DomainMatrix containing elements of rows
|
||
|
||
Examples
|
||
========
|
||
|
||
>>> from sympy.polys.matrices import DomainMatrix
|
||
>>> from sympy.abc import x, y, z
|
||
>>> A = DomainMatrix.from_list_sympy(1, 3, [[x, y, z]])
|
||
>>> A
|
||
DomainMatrix([[x, y, z]], (1, 3), ZZ[x,y,z])
|
||
|
||
See Also
|
||
========
|
||
|
||
sympy.polys.constructor.construct_domain, from_dict_sympy
|
||
|
||
"""
|
||
assert len(rows) == nrows
|
||
assert all(len(row) == ncols for row in rows)
|
||
|
||
items_sympy = [_sympify(item) for row in rows for item in row]
|
||
|
||
domain, items_domain = cls.get_domain(items_sympy, **kwargs)
|
||
|
||
domain_rows = [[items_domain[ncols*r + c] for c in range(ncols)] for r in range(nrows)]
|
||
|
||
return DomainMatrix(domain_rows, (nrows, ncols), domain)
|
||
|
||
@classmethod
|
||
def from_dict_sympy(cls, nrows, ncols, elemsdict, **kwargs):
|
||
"""
|
||
|
||
Parameters
|
||
==========
|
||
|
||
nrows: number of rows
|
||
ncols: number of cols
|
||
elemsdict: dict of dicts containing non-zero elements of the DomainMatrix
|
||
|
||
Returns
|
||
=======
|
||
|
||
DomainMatrix containing elements of elemsdict
|
||
|
||
Examples
|
||
========
|
||
|
||
>>> from sympy.polys.matrices import DomainMatrix
|
||
>>> from sympy.abc import x,y,z
|
||
>>> elemsdict = {0: {0:x}, 1:{1: y}, 2: {2: z}}
|
||
>>> A = DomainMatrix.from_dict_sympy(3, 3, elemsdict)
|
||
>>> A
|
||
DomainMatrix({0: {0: x}, 1: {1: y}, 2: {2: z}}, (3, 3), ZZ[x,y,z])
|
||
|
||
See Also
|
||
========
|
||
|
||
from_list_sympy
|
||
|
||
"""
|
||
if not all(0 <= r < nrows for r in elemsdict):
|
||
raise DMBadInputError("Row out of range")
|
||
if not all(0 <= c < ncols for row in elemsdict.values() for c in row):
|
||
raise DMBadInputError("Column out of range")
|
||
|
||
items_sympy = [_sympify(item) for row in elemsdict.values() for item in row.values()]
|
||
domain, items_domain = cls.get_domain(items_sympy, **kwargs)
|
||
|
||
idx = 0
|
||
items_dict = {}
|
||
for i, row in elemsdict.items():
|
||
items_dict[i] = {}
|
||
for j in row:
|
||
items_dict[i][j] = items_domain[idx]
|
||
idx += 1
|
||
|
||
return DomainMatrix(items_dict, (nrows, ncols), domain)
|
||
|
||
@classmethod
|
||
def from_Matrix(cls, M, fmt='sparse',**kwargs):
|
||
r"""
|
||
Convert Matrix to DomainMatrix
|
||
|
||
Parameters
|
||
==========
|
||
|
||
M: Matrix
|
||
|
||
Returns
|
||
=======
|
||
|
||
Returns DomainMatrix with identical elements as M
|
||
|
||
Examples
|
||
========
|
||
|
||
>>> from sympy import Matrix
|
||
>>> from sympy.polys.matrices import DomainMatrix
|
||
>>> M = Matrix([
|
||
... [1.0, 3.4],
|
||
... [2.4, 1]])
|
||
>>> A = DomainMatrix.from_Matrix(M)
|
||
>>> A
|
||
DomainMatrix({0: {0: 1.0, 1: 3.4}, 1: {0: 2.4, 1: 1.0}}, (2, 2), RR)
|
||
|
||
We can keep internal representation as ddm using fmt='dense'
|
||
>>> from sympy import Matrix, QQ
|
||
>>> from sympy.polys.matrices import DomainMatrix
|
||
>>> A = DomainMatrix.from_Matrix(Matrix([[QQ(1, 2), QQ(3, 4)], [QQ(0, 1), QQ(0, 1)]]), fmt='dense')
|
||
>>> A.rep
|
||
[[1/2, 3/4], [0, 0]]
|
||
|
||
See Also
|
||
========
|
||
|
||
Matrix
|
||
|
||
"""
|
||
if fmt == 'dense':
|
||
return cls.from_list_sympy(*M.shape, M.tolist(), **kwargs)
|
||
|
||
return cls.from_dict_sympy(*M.shape, M.todod(), **kwargs)
|
||
|
||
@classmethod
|
||
def get_domain(cls, items_sympy, **kwargs):
|
||
K, items_K = construct_domain(items_sympy, **kwargs)
|
||
return K, items_K
|
||
|
||
def choose_domain(self, **opts):
|
||
"""Convert to a domain found by :func:`~.construct_domain`.
|
||
|
||
Examples
|
||
========
|
||
|
||
>>> from sympy import ZZ
|
||
>>> from sympy.polys.matrices import DM
|
||
>>> M = DM([[1, 2], [3, 4]], ZZ)
|
||
>>> M
|
||
DomainMatrix([[1, 2], [3, 4]], (2, 2), ZZ)
|
||
>>> M.choose_domain(field=True)
|
||
DomainMatrix([[1, 2], [3, 4]], (2, 2), QQ)
|
||
|
||
>>> from sympy.abc import x
|
||
>>> M = DM([[1, x], [x**2, x**3]], ZZ[x])
|
||
>>> M.choose_domain(field=True).domain
|
||
ZZ(x)
|
||
|
||
Keyword arguments are passed to :func:`~.construct_domain`.
|
||
|
||
See Also
|
||
========
|
||
|
||
construct_domain
|
||
convert_to
|
||
"""
|
||
elements, data = self.to_sympy().to_flat_nz()
|
||
dom, elements_dom = construct_domain(elements, **opts)
|
||
return self.from_flat_nz(elements_dom, data, dom)
|
||
|
||
def copy(self):
|
||
return self.from_rep(self.rep.copy())
|
||
|
||
def convert_to(self, K):
|
||
r"""
|
||
Change the domain of DomainMatrix to desired domain or field
|
||
|
||
Parameters
|
||
==========
|
||
|
||
K : Represents the desired domain or field.
|
||
Alternatively, ``None`` may be passed, in which case this method
|
||
just returns a copy of this DomainMatrix.
|
||
|
||
Returns
|
||
=======
|
||
|
||
DomainMatrix
|
||
DomainMatrix with the desired domain or field
|
||
|
||
Examples
|
||
========
|
||
|
||
>>> from sympy import ZZ, ZZ_I
|
||
>>> from sympy.polys.matrices import DomainMatrix
|
||
>>> A = DomainMatrix([
|
||
... [ZZ(1), ZZ(2)],
|
||
... [ZZ(3), ZZ(4)]], (2, 2), ZZ)
|
||
|
||
>>> A.convert_to(ZZ_I)
|
||
DomainMatrix([[1, 2], [3, 4]], (2, 2), ZZ_I)
|
||
|
||
"""
|
||
if K == self.domain:
|
||
return self.copy()
|
||
|
||
rep = self.rep
|
||
|
||
# The DFM, DDM and SDM types do not do any implicit conversions so we
|
||
# manage switching between DDM and DFM here.
|
||
if rep.is_DFM and not DFM._supports_domain(K):
|
||
rep_K = rep.to_ddm().convert_to(K)
|
||
elif rep.is_DDM and DFM._supports_domain(K):
|
||
rep_K = rep.convert_to(K).to_dfm()
|
||
else:
|
||
rep_K = rep.convert_to(K)
|
||
|
||
return self.from_rep(rep_K)
|
||
|
||
def to_sympy(self):
|
||
return self.convert_to(EXRAW)
|
||
|
||
def to_field(self):
|
||
r"""
|
||
Returns a DomainMatrix with the appropriate field
|
||
|
||
Returns
|
||
=======
|
||
|
||
DomainMatrix
|
||
DomainMatrix with the appropriate field
|
||
|
||
Examples
|
||
========
|
||
|
||
>>> from sympy import ZZ
|
||
>>> from sympy.polys.matrices import DomainMatrix
|
||
>>> A = DomainMatrix([
|
||
... [ZZ(1), ZZ(2)],
|
||
... [ZZ(3), ZZ(4)]], (2, 2), ZZ)
|
||
|
||
>>> A.to_field()
|
||
DomainMatrix([[1, 2], [3, 4]], (2, 2), QQ)
|
||
|
||
"""
|
||
K = self.domain.get_field()
|
||
return self.convert_to(K)
|
||
|
||
def to_sparse(self):
|
||
"""
|
||
Return a sparse DomainMatrix representation of *self*.
|
||
|
||
Examples
|
||
========
|
||
|
||
>>> from sympy.polys.matrices import DomainMatrix
|
||
>>> from sympy import QQ
|
||
>>> A = DomainMatrix([[1, 0],[0, 2]], (2, 2), QQ)
|
||
>>> A.rep
|
||
[[1, 0], [0, 2]]
|
||
>>> B = A.to_sparse()
|
||
>>> B.rep
|
||
{0: {0: 1}, 1: {1: 2}}
|
||
"""
|
||
if self.rep.fmt == 'sparse':
|
||
return self
|
||
|
||
return self.from_rep(self.rep.to_sdm())
|
||
|
||
def to_dense(self):
|
||
"""
|
||
Return a dense DomainMatrix representation of *self*.
|
||
|
||
Examples
|
||
========
|
||
|
||
>>> from sympy.polys.matrices import DomainMatrix
|
||
>>> from sympy import QQ
|
||
>>> A = DomainMatrix({0: {0: 1}, 1: {1: 2}}, (2, 2), QQ)
|
||
>>> A.rep
|
||
{0: {0: 1}, 1: {1: 2}}
|
||
>>> B = A.to_dense()
|
||
>>> B.rep
|
||
[[1, 0], [0, 2]]
|
||
|
||
"""
|
||
rep = self.rep
|
||
|
||
if rep.fmt == 'dense':
|
||
return self
|
||
|
||
return self.from_rep(rep.to_dfm_or_ddm())
|
||
|
||
def to_ddm(self):
|
||
"""
|
||
Return a :class:`~.DDM` representation of *self*.
|
||
|
||
Examples
|
||
========
|
||
|
||
>>> from sympy.polys.matrices import DomainMatrix
|
||
>>> from sympy import QQ
|
||
>>> A = DomainMatrix({0: {0: 1}, 1: {1: 2}}, (2, 2), QQ)
|
||
>>> ddm = A.to_ddm()
|
||
>>> ddm
|
||
[[1, 0], [0, 2]]
|
||
>>> type(ddm)
|
||
<class 'sympy.polys.matrices.ddm.DDM'>
|
||
|
||
See Also
|
||
========
|
||
|
||
to_sdm
|
||
to_dense
|
||
sympy.polys.matrices.ddm.DDM.to_sdm
|
||
"""
|
||
return self.rep.to_ddm()
|
||
|
||
def to_sdm(self):
|
||
"""
|
||
Return a :class:`~.SDM` representation of *self*.
|
||
|
||
Examples
|
||
========
|
||
|
||
>>> from sympy.polys.matrices import DomainMatrix
|
||
>>> from sympy import QQ
|
||
>>> A = DomainMatrix([[1, 0],[0, 2]], (2, 2), QQ)
|
||
>>> sdm = A.to_sdm()
|
||
>>> sdm
|
||
{0: {0: 1}, 1: {1: 2}}
|
||
>>> type(sdm)
|
||
<class 'sympy.polys.matrices.sdm.SDM'>
|
||
|
||
See Also
|
||
========
|
||
|
||
to_ddm
|
||
to_sparse
|
||
sympy.polys.matrices.sdm.SDM.to_ddm
|
||
"""
|
||
return self.rep.to_sdm()
|
||
|
||
@doctest_depends_on(ground_types=['flint'])
|
||
def to_dfm(self):
|
||
"""
|
||
Return a :class:`~.DFM` representation of *self*.
|
||
|
||
Examples
|
||
========
|
||
|
||
>>> from sympy.polys.matrices import DomainMatrix
|
||
>>> from sympy import QQ
|
||
>>> A = DomainMatrix([[1, 0],[0, 2]], (2, 2), QQ)
|
||
>>> dfm = A.to_dfm()
|
||
>>> dfm
|
||
[[1, 0], [0, 2]]
|
||
>>> type(dfm)
|
||
<class 'sympy.polys.matrices._dfm.DFM'>
|
||
|
||
See Also
|
||
========
|
||
|
||
to_ddm
|
||
to_dense
|
||
DFM
|
||
"""
|
||
return self.rep.to_dfm()
|
||
|
||
@doctest_depends_on(ground_types=['flint'])
|
||
def to_dfm_or_ddm(self):
|
||
"""
|
||
Return a :class:`~.DFM` or :class:`~.DDM` representation of *self*.
|
||
|
||
Explanation
|
||
===========
|
||
|
||
The :class:`~.DFM` representation can only be used if the ground types
|
||
are ``flint`` and the ground domain is supported by ``python-flint``.
|
||
This method will return a :class:`~.DFM` representation if possible,
|
||
but will return a :class:`~.DDM` representation otherwise.
|
||
|
||
Examples
|
||
========
|
||
|
||
>>> from sympy.polys.matrices import DomainMatrix
|
||
>>> from sympy import QQ
|
||
>>> A = DomainMatrix([[1, 0],[0, 2]], (2, 2), QQ)
|
||
>>> dfm = A.to_dfm_or_ddm()
|
||
>>> dfm
|
||
[[1, 0], [0, 2]]
|
||
>>> type(dfm) # Depends on the ground domain and ground types
|
||
<class 'sympy.polys.matrices._dfm.DFM'>
|
||
|
||
See Also
|
||
========
|
||
|
||
to_ddm: Always return a :class:`~.DDM` representation.
|
||
to_dfm: Returns a :class:`~.DFM` representation or raise an error.
|
||
to_dense: Convert internally to a :class:`~.DFM` or :class:`~.DDM`
|
||
DFM: The :class:`~.DFM` dense FLINT matrix representation.
|
||
DDM: The Python :class:`~.DDM` dense domain matrix representation.
|
||
"""
|
||
return self.rep.to_dfm_or_ddm()
|
||
|
||
@classmethod
|
||
def _unify_domain(cls, *matrices):
|
||
"""Convert matrices to a common domain"""
|
||
domains = {matrix.domain for matrix in matrices}
|
||
if len(domains) == 1:
|
||
return matrices
|
||
domain = reduce(lambda x, y: x.unify(y), domains)
|
||
return tuple(matrix.convert_to(domain) for matrix in matrices)
|
||
|
||
@classmethod
|
||
def _unify_fmt(cls, *matrices, fmt=None):
|
||
"""Convert matrices to the same format.
|
||
|
||
If all matrices have the same format, then return unmodified.
|
||
Otherwise convert both to the preferred format given as *fmt* which
|
||
should be 'dense' or 'sparse'.
|
||
"""
|
||
formats = {matrix.rep.fmt for matrix in matrices}
|
||
if len(formats) == 1:
|
||
return matrices
|
||
if fmt == 'sparse':
|
||
return tuple(matrix.to_sparse() for matrix in matrices)
|
||
elif fmt == 'dense':
|
||
return tuple(matrix.to_dense() for matrix in matrices)
|
||
else:
|
||
raise ValueError("fmt should be 'sparse' or 'dense'")
|
||
|
||
def unify(self, *others, fmt=None):
|
||
"""
|
||
Unifies the domains and the format of self and other
|
||
matrices.
|
||
|
||
Parameters
|
||
==========
|
||
|
||
others : DomainMatrix
|
||
|
||
fmt: string 'dense', 'sparse' or `None` (default)
|
||
The preferred format to convert to if self and other are not
|
||
already in the same format. If `None` or not specified then no
|
||
conversion if performed.
|
||
|
||
Returns
|
||
=======
|
||
|
||
Tuple[DomainMatrix]
|
||
Matrices with unified domain and format
|
||
|
||
Examples
|
||
========
|
||
|
||
Unify the domain of DomainMatrix that have different domains:
|
||
|
||
>>> from sympy import ZZ, QQ
|
||
>>> from sympy.polys.matrices import DomainMatrix
|
||
>>> A = DomainMatrix([[ZZ(1), ZZ(2)]], (1, 2), ZZ)
|
||
>>> B = DomainMatrix([[QQ(1, 2), QQ(2)]], (1, 2), QQ)
|
||
>>> Aq, Bq = A.unify(B)
|
||
>>> Aq
|
||
DomainMatrix([[1, 2]], (1, 2), QQ)
|
||
>>> Bq
|
||
DomainMatrix([[1/2, 2]], (1, 2), QQ)
|
||
|
||
Unify the format (dense or sparse):
|
||
|
||
>>> A = DomainMatrix([[ZZ(1), ZZ(2)]], (1, 2), ZZ)
|
||
>>> B = DomainMatrix({0:{0: ZZ(1)}}, (2, 2), ZZ)
|
||
>>> B.rep
|
||
{0: {0: 1}}
|
||
|
||
>>> A2, B2 = A.unify(B, fmt='dense')
|
||
>>> B2.rep
|
||
[[1, 0], [0, 0]]
|
||
|
||
See Also
|
||
========
|
||
|
||
convert_to, to_dense, to_sparse
|
||
|
||
"""
|
||
matrices = (self,) + others
|
||
matrices = DomainMatrix._unify_domain(*matrices)
|
||
if fmt is not None:
|
||
matrices = DomainMatrix._unify_fmt(*matrices, fmt=fmt)
|
||
return matrices
|
||
|
||
def to_Matrix(self):
|
||
r"""
|
||
Convert DomainMatrix to Matrix
|
||
|
||
Returns
|
||
=======
|
||
|
||
Matrix
|
||
MutableDenseMatrix for the DomainMatrix
|
||
|
||
Examples
|
||
========
|
||
|
||
>>> from sympy import ZZ
|
||
>>> from sympy.polys.matrices import DomainMatrix
|
||
>>> A = DomainMatrix([
|
||
... [ZZ(1), ZZ(2)],
|
||
... [ZZ(3), ZZ(4)]], (2, 2), ZZ)
|
||
|
||
>>> A.to_Matrix()
|
||
Matrix([
|
||
[1, 2],
|
||
[3, 4]])
|
||
|
||
See Also
|
||
========
|
||
|
||
from_Matrix
|
||
|
||
"""
|
||
from sympy.matrices.dense import MutableDenseMatrix
|
||
|
||
# XXX: If the internal representation of RepMatrix changes then this
|
||
# might need to be changed also.
|
||
if self.domain in (ZZ, QQ, EXRAW):
|
||
if self.rep.fmt == "sparse":
|
||
rep = self.copy()
|
||
else:
|
||
rep = self.to_sparse()
|
||
else:
|
||
rep = self.convert_to(EXRAW).to_sparse()
|
||
|
||
return MutableDenseMatrix._fromrep(rep)
|
||
|
||
def to_list(self):
|
||
"""
|
||
Convert :class:`DomainMatrix` to list of lists.
|
||
|
||
See Also
|
||
========
|
||
|
||
from_list
|
||
to_list_flat
|
||
to_flat_nz
|
||
to_dok
|
||
"""
|
||
return self.rep.to_list()
|
||
|
||
def to_list_flat(self):
|
||
"""
|
||
Convert :class:`DomainMatrix` to flat list.
|
||
|
||
Examples
|
||
========
|
||
|
||
>>> from sympy import ZZ
|
||
>>> from sympy.polys.matrices import DomainMatrix
|
||
>>> A = DomainMatrix([[ZZ(1), ZZ(2)], [ZZ(3), ZZ(4)]], (2, 2), ZZ)
|
||
>>> A.to_list_flat()
|
||
[1, 2, 3, 4]
|
||
|
||
See Also
|
||
========
|
||
|
||
from_list_flat
|
||
to_list
|
||
to_flat_nz
|
||
to_dok
|
||
"""
|
||
return self.rep.to_list_flat()
|
||
|
||
@classmethod
|
||
def from_list_flat(cls, elements, shape, domain):
|
||
"""
|
||
Create :class:`DomainMatrix` from flat list.
|
||
|
||
Examples
|
||
========
|
||
|
||
>>> from sympy import ZZ
|
||
>>> from sympy.polys.matrices import DomainMatrix
|
||
>>> element_list = [ZZ(1), ZZ(2), ZZ(3), ZZ(4)]
|
||
>>> A = DomainMatrix.from_list_flat(element_list, (2, 2), ZZ)
|
||
>>> A
|
||
DomainMatrix([[1, 2], [3, 4]], (2, 2), ZZ)
|
||
>>> A == A.from_list_flat(A.to_list_flat(), A.shape, A.domain)
|
||
True
|
||
|
||
See Also
|
||
========
|
||
|
||
to_list_flat
|
||
"""
|
||
ddm = DDM.from_list_flat(elements, shape, domain)
|
||
return cls.from_rep(ddm.to_dfm_or_ddm())
|
||
|
||
def to_flat_nz(self):
|
||
"""
|
||
Convert :class:`DomainMatrix` to list of nonzero elements and data.
|
||
|
||
Explanation
|
||
===========
|
||
|
||
Returns a tuple ``(elements, data)`` where ``elements`` is a list of
|
||
elements of the matrix with zeros possibly excluded. The matrix can be
|
||
reconstructed by passing these to :meth:`from_flat_nz`. The idea is to
|
||
be able to modify a flat list of the elements and then create a new
|
||
matrix of the same shape with the modified elements in the same
|
||
positions.
|
||
|
||
The format of ``data`` differs depending on whether the underlying
|
||
representation is dense or sparse but either way it represents the
|
||
positions of the elements in the list in a way that
|
||
:meth:`from_flat_nz` can use to reconstruct the matrix. The
|
||
:meth:`from_flat_nz` method should be called on the same
|
||
:class:`DomainMatrix` that was used to call :meth:`to_flat_nz`.
|
||
|
||
Examples
|
||
========
|
||
|
||
>>> from sympy import ZZ
|
||
>>> from sympy.polys.matrices import DomainMatrix
|
||
>>> A = DomainMatrix([
|
||
... [ZZ(1), ZZ(2)],
|
||
... [ZZ(3), ZZ(4)]], (2, 2), ZZ)
|
||
>>> elements, data = A.to_flat_nz()
|
||
>>> elements
|
||
[1, 2, 3, 4]
|
||
>>> A == A.from_flat_nz(elements, data, A.domain)
|
||
True
|
||
|
||
Create a matrix with the elements doubled:
|
||
|
||
>>> elements_doubled = [2*x for x in elements]
|
||
>>> A2 = A.from_flat_nz(elements_doubled, data, A.domain)
|
||
>>> A2 == 2*A
|
||
True
|
||
|
||
See Also
|
||
========
|
||
|
||
from_flat_nz
|
||
"""
|
||
return self.rep.to_flat_nz()
|
||
|
||
def from_flat_nz(self, elements, data, domain):
|
||
"""
|
||
Reconstruct :class:`DomainMatrix` after calling :meth:`to_flat_nz`.
|
||
|
||
See :meth:`to_flat_nz` for explanation.
|
||
|
||
See Also
|
||
========
|
||
|
||
to_flat_nz
|
||
"""
|
||
rep = self.rep.from_flat_nz(elements, data, domain)
|
||
return self.from_rep(rep)
|
||
|
||
def to_dod(self):
|
||
"""
|
||
Convert :class:`DomainMatrix` to dictionary of dictionaries (dod) format.
|
||
|
||
Explanation
|
||
===========
|
||
|
||
Returns a dictionary of dictionaries representing the matrix.
|
||
|
||
Examples
|
||
========
|
||
|
||
>>> from sympy import ZZ
|
||
>>> from sympy.polys.matrices import DM
|
||
>>> A = DM([[ZZ(1), ZZ(2), ZZ(0)], [ZZ(3), ZZ(0), ZZ(4)]], ZZ)
|
||
>>> A.to_dod()
|
||
{0: {0: 1, 1: 2}, 1: {0: 3, 2: 4}}
|
||
>>> A.to_sparse() == A.from_dod(A.to_dod(), A.shape, A.domain)
|
||
True
|
||
>>> A == A.from_dod_like(A.to_dod())
|
||
True
|
||
|
||
See Also
|
||
========
|
||
|
||
from_dod
|
||
from_dod_like
|
||
to_dok
|
||
to_list
|
||
to_list_flat
|
||
to_flat_nz
|
||
sympy.matrices.matrixbase.MatrixBase.todod
|
||
"""
|
||
return self.rep.to_dod()
|
||
|
||
@classmethod
|
||
def from_dod(cls, dod, shape, domain):
|
||
"""
|
||
Create sparse :class:`DomainMatrix` from dict of dict (dod) format.
|
||
|
||
See :meth:`to_dod` for explanation.
|
||
|
||
See Also
|
||
========
|
||
|
||
to_dod
|
||
from_dod_like
|
||
"""
|
||
return cls.from_rep(SDM.from_dod(dod, shape, domain))
|
||
|
||
def from_dod_like(self, dod, domain=None):
|
||
"""
|
||
Create :class:`DomainMatrix` like ``self`` from dict of dict (dod) format.
|
||
|
||
See :meth:`to_dod` for explanation.
|
||
|
||
See Also
|
||
========
|
||
|
||
to_dod
|
||
from_dod
|
||
"""
|
||
if domain is None:
|
||
domain = self.domain
|
||
return self.from_rep(self.rep.from_dod(dod, self.shape, domain))
|
||
|
||
def to_dok(self):
|
||
"""
|
||
Convert :class:`DomainMatrix` to dictionary of keys (dok) format.
|
||
|
||
Examples
|
||
========
|
||
|
||
>>> from sympy import ZZ
|
||
>>> from sympy.polys.matrices import DomainMatrix
|
||
>>> A = DomainMatrix([
|
||
... [ZZ(1), ZZ(0)],
|
||
... [ZZ(0), ZZ(4)]], (2, 2), ZZ)
|
||
>>> A.to_dok()
|
||
{(0, 0): 1, (1, 1): 4}
|
||
|
||
The matrix can be reconstructed by calling :meth:`from_dok` although
|
||
the reconstructed matrix will always be in sparse format:
|
||
|
||
>>> A.to_sparse() == A.from_dok(A.to_dok(), A.shape, A.domain)
|
||
True
|
||
|
||
See Also
|
||
========
|
||
|
||
from_dok
|
||
to_list
|
||
to_list_flat
|
||
to_flat_nz
|
||
"""
|
||
return self.rep.to_dok()
|
||
|
||
@classmethod
|
||
def from_dok(cls, dok, shape, domain):
|
||
"""
|
||
Create :class:`DomainMatrix` from dictionary of keys (dok) format.
|
||
|
||
See :meth:`to_dok` for explanation.
|
||
|
||
See Also
|
||
========
|
||
|
||
to_dok
|
||
"""
|
||
return cls.from_rep(SDM.from_dok(dok, shape, domain))
|
||
|
||
def iter_values(self):
|
||
"""
|
||
Iterate over nonzero elements of the matrix.
|
||
|
||
Examples
|
||
========
|
||
|
||
>>> from sympy import ZZ
|
||
>>> from sympy.polys.matrices import DomainMatrix
|
||
>>> A = DomainMatrix([[ZZ(1), ZZ(0)], [ZZ(3), ZZ(4)]], (2, 2), ZZ)
|
||
>>> list(A.iter_values())
|
||
[1, 3, 4]
|
||
|
||
See Also
|
||
========
|
||
|
||
iter_items
|
||
to_list_flat
|
||
sympy.matrices.matrixbase.MatrixBase.iter_values
|
||
"""
|
||
return self.rep.iter_values()
|
||
|
||
def iter_items(self):
|
||
"""
|
||
Iterate over indices and values of nonzero elements of the matrix.
|
||
|
||
Examples
|
||
========
|
||
|
||
>>> from sympy import ZZ
|
||
>>> from sympy.polys.matrices import DomainMatrix
|
||
>>> A = DomainMatrix([[ZZ(1), ZZ(0)], [ZZ(3), ZZ(4)]], (2, 2), ZZ)
|
||
>>> list(A.iter_items())
|
||
[((0, 0), 1), ((1, 0), 3), ((1, 1), 4)]
|
||
|
||
See Also
|
||
========
|
||
|
||
iter_values
|
||
to_dok
|
||
sympy.matrices.matrixbase.MatrixBase.iter_items
|
||
"""
|
||
return self.rep.iter_items()
|
||
|
||
def nnz(self):
|
||
"""
|
||
Number of nonzero elements in the matrix.
|
||
|
||
Examples
|
||
========
|
||
|
||
>>> from sympy import ZZ
|
||
>>> from sympy.polys.matrices import DM
|
||
>>> A = DM([[1, 0], [0, 4]], ZZ)
|
||
>>> A.nnz()
|
||
2
|
||
"""
|
||
return self.rep.nnz()
|
||
|
||
def __repr__(self):
|
||
return 'DomainMatrix(%s, %r, %r)' % (str(self.rep), self.shape, self.domain)
|
||
|
||
def transpose(self):
|
||
"""Matrix transpose of ``self``"""
|
||
return self.from_rep(self.rep.transpose())
|
||
|
||
def flat(self):
|
||
rows, cols = self.shape
|
||
return [self[i,j].element for i in range(rows) for j in range(cols)]
|
||
|
||
@property
|
||
def is_zero_matrix(self):
|
||
return self.rep.is_zero_matrix()
|
||
|
||
@property
|
||
def is_upper(self):
|
||
"""
|
||
Says whether this matrix is upper-triangular. True can be returned
|
||
even if the matrix is not square.
|
||
"""
|
||
return self.rep.is_upper()
|
||
|
||
@property
|
||
def is_lower(self):
|
||
"""
|
||
Says whether this matrix is lower-triangular. True can be returned
|
||
even if the matrix is not square.
|
||
"""
|
||
return self.rep.is_lower()
|
||
|
||
@property
|
||
def is_diagonal(self):
|
||
"""
|
||
True if the matrix is diagonal.
|
||
|
||
Can return true for non-square matrices. A matrix is diagonal if
|
||
``M[i,j] == 0`` whenever ``i != j``.
|
||
|
||
Examples
|
||
========
|
||
|
||
>>> from sympy import ZZ
|
||
>>> from sympy.polys.matrices import DM
|
||
>>> M = DM([[ZZ(1), ZZ(0)], [ZZ(0), ZZ(1)]], ZZ)
|
||
>>> M.is_diagonal
|
||
True
|
||
|
||
See Also
|
||
========
|
||
|
||
is_upper
|
||
is_lower
|
||
is_square
|
||
diagonal
|
||
"""
|
||
return self.rep.is_diagonal()
|
||
|
||
def diagonal(self):
|
||
"""
|
||
Get the diagonal entries of the matrix as a list.
|
||
|
||
Examples
|
||
========
|
||
|
||
>>> from sympy import ZZ
|
||
>>> from sympy.polys.matrices import DM
|
||
>>> M = DM([[ZZ(1), ZZ(2)], [ZZ(3), ZZ(4)]], ZZ)
|
||
>>> M.diagonal()
|
||
[1, 4]
|
||
|
||
See Also
|
||
========
|
||
|
||
is_diagonal
|
||
diag
|
||
"""
|
||
return self.rep.diagonal()
|
||
|
||
@property
|
||
def is_square(self):
|
||
"""
|
||
True if the matrix is square.
|
||
"""
|
||
return self.shape[0] == self.shape[1]
|
||
|
||
def rank(self):
|
||
rref, pivots = self.rref()
|
||
return len(pivots)
|
||
|
||
def hstack(A, *B):
|
||
r"""Horizontally stack the given matrices.
|
||
|
||
Parameters
|
||
==========
|
||
|
||
B: DomainMatrix
|
||
Matrices to stack horizontally.
|
||
|
||
Returns
|
||
=======
|
||
|
||
DomainMatrix
|
||
DomainMatrix by stacking horizontally.
|
||
|
||
Examples
|
||
========
|
||
|
||
>>> from sympy import ZZ
|
||
>>> from sympy.polys.matrices import DomainMatrix
|
||
|
||
>>> A = DomainMatrix([[ZZ(1), ZZ(2)], [ZZ(3), ZZ(4)]], (2, 2), ZZ)
|
||
>>> B = DomainMatrix([[ZZ(5), ZZ(6)], [ZZ(7), ZZ(8)]], (2, 2), ZZ)
|
||
>>> A.hstack(B)
|
||
DomainMatrix([[1, 2, 5, 6], [3, 4, 7, 8]], (2, 4), ZZ)
|
||
|
||
>>> C = DomainMatrix([[ZZ(9), ZZ(10)], [ZZ(11), ZZ(12)]], (2, 2), ZZ)
|
||
>>> A.hstack(B, C)
|
||
DomainMatrix([[1, 2, 5, 6, 9, 10], [3, 4, 7, 8, 11, 12]], (2, 6), ZZ)
|
||
|
||
See Also
|
||
========
|
||
|
||
unify
|
||
"""
|
||
A, *B = A.unify(*B, fmt=A.rep.fmt)
|
||
return DomainMatrix.from_rep(A.rep.hstack(*(Bk.rep for Bk in B)))
|
||
|
||
def vstack(A, *B):
|
||
r"""Vertically stack the given matrices.
|
||
|
||
Parameters
|
||
==========
|
||
|
||
B: DomainMatrix
|
||
Matrices to stack vertically.
|
||
|
||
Returns
|
||
=======
|
||
|
||
DomainMatrix
|
||
DomainMatrix by stacking vertically.
|
||
|
||
Examples
|
||
========
|
||
|
||
>>> from sympy import ZZ
|
||
>>> from sympy.polys.matrices import DomainMatrix
|
||
|
||
>>> A = DomainMatrix([[ZZ(1), ZZ(2)], [ZZ(3), ZZ(4)]], (2, 2), ZZ)
|
||
>>> B = DomainMatrix([[ZZ(5), ZZ(6)], [ZZ(7), ZZ(8)]], (2, 2), ZZ)
|
||
>>> A.vstack(B)
|
||
DomainMatrix([[1, 2], [3, 4], [5, 6], [7, 8]], (4, 2), ZZ)
|
||
|
||
>>> C = DomainMatrix([[ZZ(9), ZZ(10)], [ZZ(11), ZZ(12)]], (2, 2), ZZ)
|
||
>>> A.vstack(B, C)
|
||
DomainMatrix([[1, 2], [3, 4], [5, 6], [7, 8], [9, 10], [11, 12]], (6, 2), ZZ)
|
||
|
||
See Also
|
||
========
|
||
|
||
unify
|
||
"""
|
||
A, *B = A.unify(*B, fmt='dense')
|
||
return DomainMatrix.from_rep(A.rep.vstack(*(Bk.rep for Bk in B)))
|
||
|
||
def applyfunc(self, func, domain=None):
|
||
if domain is None:
|
||
domain = self.domain
|
||
return self.from_rep(self.rep.applyfunc(func, domain))
|
||
|
||
def __add__(A, B):
|
||
if not isinstance(B, DomainMatrix):
|
||
return NotImplemented
|
||
A, B = A.unify(B, fmt='dense')
|
||
return A.add(B)
|
||
|
||
def __sub__(A, B):
|
||
if not isinstance(B, DomainMatrix):
|
||
return NotImplemented
|
||
A, B = A.unify(B, fmt='dense')
|
||
return A.sub(B)
|
||
|
||
def __neg__(A):
|
||
return A.neg()
|
||
|
||
def __mul__(A, B):
|
||
"""A * B"""
|
||
if isinstance(B, DomainMatrix):
|
||
A, B = A.unify(B, fmt='dense')
|
||
return A.matmul(B)
|
||
elif B in A.domain:
|
||
return A.scalarmul(B)
|
||
elif isinstance(B, DomainScalar):
|
||
A, B = A.unify(B)
|
||
return A.scalarmul(B.element)
|
||
else:
|
||
return NotImplemented
|
||
|
||
def __rmul__(A, B):
|
||
if B in A.domain:
|
||
return A.rscalarmul(B)
|
||
elif isinstance(B, DomainScalar):
|
||
A, B = A.unify(B)
|
||
return A.rscalarmul(B.element)
|
||
else:
|
||
return NotImplemented
|
||
|
||
def __pow__(A, n):
|
||
"""A ** n"""
|
||
if not isinstance(n, int):
|
||
return NotImplemented
|
||
return A.pow(n)
|
||
|
||
def _check(a, op, b, ashape, bshape):
|
||
if a.domain != b.domain:
|
||
msg = "Domain mismatch: %s %s %s" % (a.domain, op, b.domain)
|
||
raise DMDomainError(msg)
|
||
if ashape != bshape:
|
||
msg = "Shape mismatch: %s %s %s" % (a.shape, op, b.shape)
|
||
raise DMShapeError(msg)
|
||
if a.rep.fmt != b.rep.fmt:
|
||
msg = "Format mismatch: %s %s %s" % (a.rep.fmt, op, b.rep.fmt)
|
||
raise DMFormatError(msg)
|
||
if type(a.rep) != type(b.rep):
|
||
msg = "Type mismatch: %s %s %s" % (type(a.rep), op, type(b.rep))
|
||
raise DMFormatError(msg)
|
||
|
||
def add(A, B):
|
||
r"""
|
||
Adds two DomainMatrix matrices of the same Domain
|
||
|
||
Parameters
|
||
==========
|
||
|
||
A, B: DomainMatrix
|
||
matrices to add
|
||
|
||
Returns
|
||
=======
|
||
|
||
DomainMatrix
|
||
DomainMatrix after Addition
|
||
|
||
Raises
|
||
======
|
||
|
||
DMShapeError
|
||
If the dimensions of the two DomainMatrix are not equal
|
||
|
||
ValueError
|
||
If the domain of the two DomainMatrix are not same
|
||
|
||
Examples
|
||
========
|
||
|
||
>>> from sympy import ZZ
|
||
>>> from sympy.polys.matrices import DomainMatrix
|
||
>>> A = DomainMatrix([
|
||
... [ZZ(1), ZZ(2)],
|
||
... [ZZ(3), ZZ(4)]], (2, 2), ZZ)
|
||
>>> B = DomainMatrix([
|
||
... [ZZ(4), ZZ(3)],
|
||
... [ZZ(2), ZZ(1)]], (2, 2), ZZ)
|
||
|
||
>>> A.add(B)
|
||
DomainMatrix([[5, 5], [5, 5]], (2, 2), ZZ)
|
||
|
||
See Also
|
||
========
|
||
|
||
sub, matmul
|
||
|
||
"""
|
||
A._check('+', B, A.shape, B.shape)
|
||
return A.from_rep(A.rep.add(B.rep))
|
||
|
||
|
||
def sub(A, B):
|
||
r"""
|
||
Subtracts two DomainMatrix matrices of the same Domain
|
||
|
||
Parameters
|
||
==========
|
||
|
||
A, B: DomainMatrix
|
||
matrices to subtract
|
||
|
||
Returns
|
||
=======
|
||
|
||
DomainMatrix
|
||
DomainMatrix after Subtraction
|
||
|
||
Raises
|
||
======
|
||
|
||
DMShapeError
|
||
If the dimensions of the two DomainMatrix are not equal
|
||
|
||
ValueError
|
||
If the domain of the two DomainMatrix are not same
|
||
|
||
Examples
|
||
========
|
||
|
||
>>> from sympy import ZZ
|
||
>>> from sympy.polys.matrices import DomainMatrix
|
||
>>> A = DomainMatrix([
|
||
... [ZZ(1), ZZ(2)],
|
||
... [ZZ(3), ZZ(4)]], (2, 2), ZZ)
|
||
>>> B = DomainMatrix([
|
||
... [ZZ(4), ZZ(3)],
|
||
... [ZZ(2), ZZ(1)]], (2, 2), ZZ)
|
||
|
||
>>> A.sub(B)
|
||
DomainMatrix([[-3, -1], [1, 3]], (2, 2), ZZ)
|
||
|
||
See Also
|
||
========
|
||
|
||
add, matmul
|
||
|
||
"""
|
||
A._check('-', B, A.shape, B.shape)
|
||
return A.from_rep(A.rep.sub(B.rep))
|
||
|
||
def neg(A):
|
||
r"""
|
||
Returns the negative of DomainMatrix
|
||
|
||
Parameters
|
||
==========
|
||
|
||
A : Represents a DomainMatrix
|
||
|
||
Returns
|
||
=======
|
||
|
||
DomainMatrix
|
||
DomainMatrix after Negation
|
||
|
||
Examples
|
||
========
|
||
|
||
>>> from sympy import ZZ
|
||
>>> from sympy.polys.matrices import DomainMatrix
|
||
>>> A = DomainMatrix([
|
||
... [ZZ(1), ZZ(2)],
|
||
... [ZZ(3), ZZ(4)]], (2, 2), ZZ)
|
||
|
||
>>> A.neg()
|
||
DomainMatrix([[-1, -2], [-3, -4]], (2, 2), ZZ)
|
||
|
||
"""
|
||
return A.from_rep(A.rep.neg())
|
||
|
||
def mul(A, b):
|
||
r"""
|
||
Performs term by term multiplication for the second DomainMatrix
|
||
w.r.t first DomainMatrix. Returns a DomainMatrix whose rows are
|
||
list of DomainMatrix matrices created after term by term multiplication.
|
||
|
||
Parameters
|
||
==========
|
||
|
||
A, B: DomainMatrix
|
||
matrices to multiply term-wise
|
||
|
||
Returns
|
||
=======
|
||
|
||
DomainMatrix
|
||
DomainMatrix after term by term multiplication
|
||
|
||
Examples
|
||
========
|
||
|
||
>>> from sympy import ZZ
|
||
>>> from sympy.polys.matrices import DomainMatrix
|
||
>>> A = DomainMatrix([
|
||
... [ZZ(1), ZZ(2)],
|
||
... [ZZ(3), ZZ(4)]], (2, 2), ZZ)
|
||
>>> b = ZZ(2)
|
||
|
||
>>> A.mul(b)
|
||
DomainMatrix([[2, 4], [6, 8]], (2, 2), ZZ)
|
||
|
||
See Also
|
||
========
|
||
|
||
matmul
|
||
|
||
"""
|
||
return A.from_rep(A.rep.mul(b))
|
||
|
||
def rmul(A, b):
|
||
return A.from_rep(A.rep.rmul(b))
|
||
|
||
def matmul(A, B):
|
||
r"""
|
||
Performs matrix multiplication of two DomainMatrix matrices
|
||
|
||
Parameters
|
||
==========
|
||
|
||
A, B: DomainMatrix
|
||
to multiply
|
||
|
||
Returns
|
||
=======
|
||
|
||
DomainMatrix
|
||
DomainMatrix after multiplication
|
||
|
||
Examples
|
||
========
|
||
|
||
>>> from sympy import ZZ
|
||
>>> from sympy.polys.matrices import DomainMatrix
|
||
>>> A = DomainMatrix([
|
||
... [ZZ(1), ZZ(2)],
|
||
... [ZZ(3), ZZ(4)]], (2, 2), ZZ)
|
||
>>> B = DomainMatrix([
|
||
... [ZZ(1), ZZ(1)],
|
||
... [ZZ(0), ZZ(1)]], (2, 2), ZZ)
|
||
|
||
>>> A.matmul(B)
|
||
DomainMatrix([[1, 3], [3, 7]], (2, 2), ZZ)
|
||
|
||
See Also
|
||
========
|
||
|
||
mul, pow, add, sub
|
||
|
||
"""
|
||
|
||
A._check('*', B, A.shape[1], B.shape[0])
|
||
return A.from_rep(A.rep.matmul(B.rep))
|
||
|
||
def _scalarmul(A, lamda, reverse):
|
||
if lamda == A.domain.zero:
|
||
return DomainMatrix.zeros(A.shape, A.domain)
|
||
elif lamda == A.domain.one:
|
||
return A.copy()
|
||
elif reverse:
|
||
return A.rmul(lamda)
|
||
else:
|
||
return A.mul(lamda)
|
||
|
||
def scalarmul(A, lamda):
|
||
return A._scalarmul(lamda, reverse=False)
|
||
|
||
def rscalarmul(A, lamda):
|
||
return A._scalarmul(lamda, reverse=True)
|
||
|
||
def mul_elementwise(A, B):
|
||
assert A.domain == B.domain
|
||
return A.from_rep(A.rep.mul_elementwise(B.rep))
|
||
|
||
def __truediv__(A, lamda):
|
||
""" Method for Scalar Division"""
|
||
if isinstance(lamda, int) or ZZ.of_type(lamda):
|
||
lamda = DomainScalar(ZZ(lamda), ZZ)
|
||
elif A.domain.is_Field and lamda in A.domain:
|
||
K = A.domain
|
||
lamda = DomainScalar(K.convert(lamda), K)
|
||
|
||
if not isinstance(lamda, DomainScalar):
|
||
return NotImplemented
|
||
|
||
A, lamda = A.to_field().unify(lamda)
|
||
if lamda.element == lamda.domain.zero:
|
||
raise ZeroDivisionError
|
||
if lamda.element == lamda.domain.one:
|
||
return A
|
||
|
||
return A.mul(1 / lamda.element)
|
||
|
||
def pow(A, n):
|
||
r"""
|
||
Computes A**n
|
||
|
||
Parameters
|
||
==========
|
||
|
||
A : DomainMatrix
|
||
|
||
n : exponent for A
|
||
|
||
Returns
|
||
=======
|
||
|
||
DomainMatrix
|
||
DomainMatrix on computing A**n
|
||
|
||
Raises
|
||
======
|
||
|
||
NotImplementedError
|
||
if n is negative.
|
||
|
||
Examples
|
||
========
|
||
|
||
>>> from sympy import ZZ
|
||
>>> from sympy.polys.matrices import DomainMatrix
|
||
>>> A = DomainMatrix([
|
||
... [ZZ(1), ZZ(1)],
|
||
... [ZZ(0), ZZ(1)]], (2, 2), ZZ)
|
||
|
||
>>> A.pow(2)
|
||
DomainMatrix([[1, 2], [0, 1]], (2, 2), ZZ)
|
||
|
||
See Also
|
||
========
|
||
|
||
matmul
|
||
|
||
"""
|
||
nrows, ncols = A.shape
|
||
if nrows != ncols:
|
||
raise DMNonSquareMatrixError('Power of a nonsquare matrix')
|
||
if n < 0:
|
||
raise NotImplementedError('Negative powers')
|
||
elif n == 0:
|
||
return A.eye(nrows, A.domain)
|
||
elif n == 1:
|
||
return A
|
||
elif n % 2 == 1:
|
||
return A * A**(n - 1)
|
||
else:
|
||
sqrtAn = A ** (n // 2)
|
||
return sqrtAn * sqrtAn
|
||
|
||
def scc(self):
|
||
"""Compute the strongly connected components of a DomainMatrix
|
||
|
||
Explanation
|
||
===========
|
||
|
||
A square matrix can be considered as the adjacency matrix for a
|
||
directed graph where the row and column indices are the vertices. In
|
||
this graph if there is an edge from vertex ``i`` to vertex ``j`` if
|
||
``M[i, j]`` is nonzero. This routine computes the strongly connected
|
||
components of that graph which are subsets of the rows and columns that
|
||
are connected by some nonzero element of the matrix. The strongly
|
||
connected components are useful because many operations such as the
|
||
determinant can be computed by working with the submatrices
|
||
corresponding to each component.
|
||
|
||
Examples
|
||
========
|
||
|
||
Find the strongly connected components of a matrix:
|
||
|
||
>>> from sympy import ZZ
|
||
>>> from sympy.polys.matrices import DomainMatrix
|
||
>>> M = DomainMatrix([[ZZ(1), ZZ(0), ZZ(2)],
|
||
... [ZZ(0), ZZ(3), ZZ(0)],
|
||
... [ZZ(4), ZZ(6), ZZ(5)]], (3, 3), ZZ)
|
||
>>> M.scc()
|
||
[[1], [0, 2]]
|
||
|
||
Compute the determinant from the components:
|
||
|
||
>>> MM = M.to_Matrix()
|
||
>>> MM
|
||
Matrix([
|
||
[1, 0, 2],
|
||
[0, 3, 0],
|
||
[4, 6, 5]])
|
||
>>> MM[[1], [1]]
|
||
Matrix([[3]])
|
||
>>> MM[[0, 2], [0, 2]]
|
||
Matrix([
|
||
[1, 2],
|
||
[4, 5]])
|
||
>>> MM.det()
|
||
-9
|
||
>>> MM[[1], [1]].det() * MM[[0, 2], [0, 2]].det()
|
||
-9
|
||
|
||
The components are given in reverse topological order and represent a
|
||
permutation of the rows and columns that will bring the matrix into
|
||
block lower-triangular form:
|
||
|
||
>>> MM[[1, 0, 2], [1, 0, 2]]
|
||
Matrix([
|
||
[3, 0, 0],
|
||
[0, 1, 2],
|
||
[6, 4, 5]])
|
||
|
||
Returns
|
||
=======
|
||
|
||
List of lists of integers
|
||
Each list represents a strongly connected component.
|
||
|
||
See also
|
||
========
|
||
|
||
sympy.matrices.matrixbase.MatrixBase.strongly_connected_components
|
||
sympy.utilities.iterables.strongly_connected_components
|
||
|
||
"""
|
||
if not self.is_square:
|
||
raise DMNonSquareMatrixError('Matrix must be square for scc')
|
||
|
||
return self.rep.scc()
|
||
|
||
def clear_denoms(self, convert=False):
|
||
"""
|
||
Clear denominators, but keep the domain unchanged.
|
||
|
||
Examples
|
||
========
|
||
|
||
>>> from sympy import QQ
|
||
>>> from sympy.polys.matrices import DM
|
||
>>> A = DM([[(1,2), (1,3)], [(1,4), (1,5)]], QQ)
|
||
>>> den, Anum = A.clear_denoms()
|
||
>>> den.to_sympy()
|
||
60
|
||
>>> Anum.to_Matrix()
|
||
Matrix([
|
||
[30, 20],
|
||
[15, 12]])
|
||
>>> den * A == Anum
|
||
True
|
||
|
||
The numerator matrix will be in the same domain as the original matrix
|
||
unless ``convert`` is set to ``True``:
|
||
|
||
>>> A.clear_denoms()[1].domain
|
||
QQ
|
||
>>> A.clear_denoms(convert=True)[1].domain
|
||
ZZ
|
||
|
||
The denominator is always in the associated ring:
|
||
|
||
>>> A.clear_denoms()[0].domain
|
||
ZZ
|
||
>>> A.domain.get_ring()
|
||
ZZ
|
||
|
||
See Also
|
||
========
|
||
|
||
sympy.polys.polytools.Poly.clear_denoms
|
||
clear_denoms_rowwise
|
||
"""
|
||
elems0, data = self.to_flat_nz()
|
||
|
||
K0 = self.domain
|
||
K1 = K0.get_ring() if K0.has_assoc_Ring else K0
|
||
|
||
den, elems1 = dup_clear_denoms(elems0, K0, K1, convert=convert)
|
||
|
||
if convert:
|
||
Kden, Knum = K1, K1
|
||
else:
|
||
Kden, Knum = K1, K0
|
||
|
||
den = DomainScalar(den, Kden)
|
||
num = self.from_flat_nz(elems1, data, Knum)
|
||
|
||
return den, num
|
||
|
||
def clear_denoms_rowwise(self, convert=False):
|
||
"""
|
||
Clear denominators from each row of the matrix.
|
||
|
||
Examples
|
||
========
|
||
|
||
>>> from sympy import QQ
|
||
>>> from sympy.polys.matrices import DM
|
||
>>> A = DM([[(1,2), (1,3), (1,4)], [(1,5), (1,6), (1,7)]], QQ)
|
||
>>> den, Anum = A.clear_denoms_rowwise()
|
||
>>> den.to_Matrix()
|
||
Matrix([
|
||
[12, 0],
|
||
[ 0, 210]])
|
||
>>> Anum.to_Matrix()
|
||
Matrix([
|
||
[ 6, 4, 3],
|
||
[42, 35, 30]])
|
||
|
||
The denominator matrix is a diagonal matrix with the denominators of
|
||
each row on the diagonal. The invariants are:
|
||
|
||
>>> den * A == Anum
|
||
True
|
||
>>> A == den.to_field().inv() * Anum
|
||
True
|
||
|
||
The numerator matrix will be in the same domain as the original matrix
|
||
unless ``convert`` is set to ``True``:
|
||
|
||
>>> A.clear_denoms_rowwise()[1].domain
|
||
QQ
|
||
>>> A.clear_denoms_rowwise(convert=True)[1].domain
|
||
ZZ
|
||
|
||
The domain of the denominator matrix is the associated ring:
|
||
|
||
>>> A.clear_denoms_rowwise()[0].domain
|
||
ZZ
|
||
|
||
See Also
|
||
========
|
||
|
||
sympy.polys.polytools.Poly.clear_denoms
|
||
clear_denoms
|
||
"""
|
||
dod = self.to_dod()
|
||
|
||
K0 = self.domain
|
||
K1 = K0.get_ring() if K0.has_assoc_Ring else K0
|
||
|
||
diagonals = [K0.one] * self.shape[0]
|
||
dod_num = {}
|
||
for i, rowi in dod.items():
|
||
indices, elems = zip(*rowi.items())
|
||
den, elems_num = dup_clear_denoms(elems, K0, K1, convert=convert)
|
||
rowi_num = dict(zip(indices, elems_num))
|
||
diagonals[i] = den
|
||
dod_num[i] = rowi_num
|
||
|
||
if convert:
|
||
Kden, Knum = K1, K1
|
||
else:
|
||
Kden, Knum = K1, K0
|
||
|
||
den = self.diag(diagonals, Kden)
|
||
num = self.from_dod_like(dod_num, Knum)
|
||
|
||
return den, num
|
||
|
||
def cancel_denom(self, denom):
|
||
"""
|
||
Cancel factors between a matrix and a denominator.
|
||
|
||
Returns a matrix and denominator on lowest terms.
|
||
|
||
Requires ``gcd`` in the ground domain.
|
||
|
||
Methods like :meth:`solve_den`, :meth:`inv_den` and :meth:`rref_den`
|
||
return a matrix and denominator but not necessarily on lowest terms.
|
||
Reduction to lowest terms without fractions can be performed with
|
||
:meth:`cancel_denom`.
|
||
|
||
Examples
|
||
========
|
||
|
||
>>> from sympy.polys.matrices import DM
|
||
>>> from sympy import ZZ
|
||
>>> M = DM([[2, 2, 0],
|
||
... [0, 2, 2],
|
||
... [0, 0, 2]], ZZ)
|
||
>>> Minv, den = M.inv_den()
|
||
>>> Minv.to_Matrix()
|
||
Matrix([
|
||
[1, -1, 1],
|
||
[0, 1, -1],
|
||
[0, 0, 1]])
|
||
>>> den
|
||
2
|
||
>>> Minv_reduced, den_reduced = Minv.cancel_denom(den)
|
||
>>> Minv_reduced.to_Matrix()
|
||
Matrix([
|
||
[1, -1, 1],
|
||
[0, 1, -1],
|
||
[0, 0, 1]])
|
||
>>> den_reduced
|
||
2
|
||
>>> Minv_reduced.to_field() / den_reduced == Minv.to_field() / den
|
||
True
|
||
|
||
The denominator is made canonical with respect to units (e.g. a
|
||
negative denominator is made positive):
|
||
|
||
>>> M = DM([[2, 2, 0]], ZZ)
|
||
>>> den = ZZ(-4)
|
||
>>> M.cancel_denom(den)
|
||
(DomainMatrix([[-1, -1, 0]], (1, 3), ZZ), 2)
|
||
|
||
Any factor common to _all_ elements will be cancelled but there can
|
||
still be factors in common between _some_ elements of the matrix and
|
||
the denominator. To cancel factors between each element and the
|
||
denominator, use :meth:`cancel_denom_elementwise` or otherwise convert
|
||
to a field and use division:
|
||
|
||
>>> M = DM([[4, 6]], ZZ)
|
||
>>> den = ZZ(12)
|
||
>>> M.cancel_denom(den)
|
||
(DomainMatrix([[2, 3]], (1, 2), ZZ), 6)
|
||
>>> numers, denoms = M.cancel_denom_elementwise(den)
|
||
>>> numers
|
||
DomainMatrix([[1, 1]], (1, 2), ZZ)
|
||
>>> denoms
|
||
DomainMatrix([[3, 2]], (1, 2), ZZ)
|
||
>>> M.to_field() / den
|
||
DomainMatrix([[1/3, 1/2]], (1, 2), QQ)
|
||
|
||
See Also
|
||
========
|
||
|
||
solve_den
|
||
inv_den
|
||
rref_den
|
||
cancel_denom_elementwise
|
||
"""
|
||
M = self
|
||
K = self.domain
|
||
|
||
if K.is_zero(denom):
|
||
raise ZeroDivisionError('denominator is zero')
|
||
elif K.is_one(denom):
|
||
return (M.copy(), denom)
|
||
|
||
elements, data = M.to_flat_nz()
|
||
|
||
# First canonicalize the denominator (e.g. multiply by -1).
|
||
if K.is_negative(denom):
|
||
u = -K.one
|
||
else:
|
||
u = K.canonical_unit(denom)
|
||
|
||
# Often after e.g. solve_den the denominator will be much more
|
||
# complicated than the elements of the numerator. Hopefully it will be
|
||
# quicker to find the gcd of the numerator and if there is no content
|
||
# then we do not need to look at the denominator at all.
|
||
content = dup_content(elements, K)
|
||
common = K.gcd(content, denom)
|
||
|
||
if not K.is_one(content):
|
||
|
||
common = K.gcd(content, denom)
|
||
|
||
if not K.is_one(common):
|
||
elements = dup_quo_ground(elements, common, K)
|
||
denom = K.quo(denom, common)
|
||
|
||
if not K.is_one(u):
|
||
elements = dup_mul_ground(elements, u, K)
|
||
denom = u * denom
|
||
elif K.is_one(common):
|
||
return (M.copy(), denom)
|
||
|
||
M_cancelled = M.from_flat_nz(elements, data, K)
|
||
|
||
return M_cancelled, denom
|
||
|
||
def cancel_denom_elementwise(self, denom):
|
||
"""
|
||
Cancel factors between the elements of a matrix and a denominator.
|
||
|
||
Returns a matrix of numerators and matrix of denominators.
|
||
|
||
Requires ``gcd`` in the ground domain.
|
||
|
||
Examples
|
||
========
|
||
|
||
>>> from sympy.polys.matrices import DM
|
||
>>> from sympy import ZZ
|
||
>>> M = DM([[2, 3], [4, 12]], ZZ)
|
||
>>> denom = ZZ(6)
|
||
>>> numers, denoms = M.cancel_denom_elementwise(denom)
|
||
>>> numers.to_Matrix()
|
||
Matrix([
|
||
[1, 1],
|
||
[2, 2]])
|
||
>>> denoms.to_Matrix()
|
||
Matrix([
|
||
[3, 2],
|
||
[3, 1]])
|
||
>>> M_frac = (M.to_field() / denom).to_Matrix()
|
||
>>> M_frac
|
||
Matrix([
|
||
[1/3, 1/2],
|
||
[2/3, 2]])
|
||
>>> denoms_inverted = denoms.to_Matrix().applyfunc(lambda e: 1/e)
|
||
>>> numers.to_Matrix().multiply_elementwise(denoms_inverted) == M_frac
|
||
True
|
||
|
||
Use :meth:`cancel_denom` to cancel factors between the matrix and the
|
||
denominator while preserving the form of a matrix with a scalar
|
||
denominator.
|
||
|
||
See Also
|
||
========
|
||
|
||
cancel_denom
|
||
"""
|
||
K = self.domain
|
||
M = self
|
||
|
||
if K.is_zero(denom):
|
||
raise ZeroDivisionError('denominator is zero')
|
||
elif K.is_one(denom):
|
||
M_numers = M.copy()
|
||
M_denoms = M.ones(M.shape, M.domain)
|
||
return (M_numers, M_denoms)
|
||
|
||
elements, data = M.to_flat_nz()
|
||
|
||
cofactors = [K.cofactors(numer, denom) for numer in elements]
|
||
gcds, numers, denoms = zip(*cofactors)
|
||
|
||
M_numers = M.from_flat_nz(list(numers), data, K)
|
||
M_denoms = M.from_flat_nz(list(denoms), data, K)
|
||
|
||
return (M_numers, M_denoms)
|
||
|
||
def content(self):
|
||
"""
|
||
Return the gcd of the elements of the matrix.
|
||
|
||
Requires ``gcd`` in the ground domain.
|
||
|
||
Examples
|
||
========
|
||
|
||
>>> from sympy.polys.matrices import DM
|
||
>>> from sympy import ZZ
|
||
>>> M = DM([[2, 4], [4, 12]], ZZ)
|
||
>>> M.content()
|
||
2
|
||
|
||
See Also
|
||
========
|
||
|
||
primitive
|
||
cancel_denom
|
||
"""
|
||
K = self.domain
|
||
elements, _ = self.to_flat_nz()
|
||
return dup_content(elements, K)
|
||
|
||
def primitive(self):
|
||
"""
|
||
Factor out gcd of the elements of a matrix.
|
||
|
||
Requires ``gcd`` in the ground domain.
|
||
|
||
Examples
|
||
========
|
||
|
||
>>> from sympy.polys.matrices import DM
|
||
>>> from sympy import ZZ
|
||
>>> M = DM([[2, 4], [4, 12]], ZZ)
|
||
>>> content, M_primitive = M.primitive()
|
||
>>> content
|
||
2
|
||
>>> M_primitive
|
||
DomainMatrix([[1, 2], [2, 6]], (2, 2), ZZ)
|
||
>>> content * M_primitive == M
|
||
True
|
||
>>> M_primitive.content() == ZZ(1)
|
||
True
|
||
|
||
See Also
|
||
========
|
||
|
||
content
|
||
cancel_denom
|
||
"""
|
||
K = self.domain
|
||
elements, data = self.to_flat_nz()
|
||
content, prims = dup_primitive(elements, K)
|
||
M_primitive = self.from_flat_nz(prims, data, K)
|
||
return content, M_primitive
|
||
|
||
def rref(self, *, method='auto'):
|
||
r"""
|
||
Returns reduced-row echelon form (RREF) and list of pivots.
|
||
|
||
If the domain is not a field then it will be converted to a field. See
|
||
:meth:`rref_den` for the fraction-free version of this routine that
|
||
returns RREF with denominator instead.
|
||
|
||
The domain must either be a field or have an associated fraction field
|
||
(see :meth:`to_field`).
|
||
|
||
Examples
|
||
========
|
||
|
||
>>> from sympy import QQ
|
||
>>> from sympy.polys.matrices import DomainMatrix
|
||
>>> A = DomainMatrix([
|
||
... [QQ(2), QQ(-1), QQ(0)],
|
||
... [QQ(-1), QQ(2), QQ(-1)],
|
||
... [QQ(0), QQ(0), QQ(2)]], (3, 3), QQ)
|
||
|
||
>>> rref_matrix, rref_pivots = A.rref()
|
||
>>> rref_matrix
|
||
DomainMatrix([[1, 0, 0], [0, 1, 0], [0, 0, 1]], (3, 3), QQ)
|
||
>>> rref_pivots
|
||
(0, 1, 2)
|
||
|
||
Parameters
|
||
==========
|
||
|
||
method : str, optional (default: 'auto')
|
||
The method to use to compute the RREF. The default is ``'auto'``,
|
||
which will attempt to choose the fastest method. The other options
|
||
are:
|
||
|
||
- ``A.rref(method='GJ')`` uses Gauss-Jordan elimination with
|
||
division. If the domain is not a field then it will be converted
|
||
to a field with :meth:`to_field` first and RREF will be computed
|
||
by inverting the pivot elements in each row. This is most
|
||
efficient for very sparse matrices or for matrices whose elements
|
||
have complex denominators.
|
||
|
||
- ``A.rref(method='FF')`` uses fraction-free Gauss-Jordan
|
||
elimination. Elimination is performed using exact division
|
||
(``exquo``) to control the growth of the coefficients. In this
|
||
case the current domain is always used for elimination but if
|
||
the domain is not a field then it will be converted to a field
|
||
at the end and divided by the denominator. This is most efficient
|
||
for dense matrices or for matrices with simple denominators.
|
||
|
||
- ``A.rref(method='CD')`` clears the denominators before using
|
||
fraction-free Gauss-Jordan elimination in the assoicated ring.
|
||
This is most efficient for dense matrices with very simple
|
||
denominators.
|
||
|
||
- ``A.rref(method='GJ_dense')``, ``A.rref(method='FF_dense')``, and
|
||
``A.rref(method='CD_dense')`` are the same as the above methods
|
||
except that the dense implementations of the algorithms are used.
|
||
By default ``A.rref(method='auto')`` will usually choose the
|
||
sparse implementations for RREF.
|
||
|
||
Regardless of which algorithm is used the returned matrix will
|
||
always have the same format (sparse or dense) as the input and its
|
||
domain will always be the field of fractions of the input domain.
|
||
|
||
Returns
|
||
=======
|
||
|
||
(DomainMatrix, list)
|
||
reduced-row echelon form and list of pivots for the DomainMatrix
|
||
|
||
See Also
|
||
========
|
||
|
||
rref_den
|
||
RREF with denominator
|
||
sympy.polys.matrices.sdm.sdm_irref
|
||
Sparse implementation of ``method='GJ'``.
|
||
sympy.polys.matrices.sdm.sdm_rref_den
|
||
Sparse implementation of ``method='FF'`` and ``method='CD'``.
|
||
sympy.polys.matrices.dense.ddm_irref
|
||
Dense implementation of ``method='GJ'``.
|
||
sympy.polys.matrices.dense.ddm_irref_den
|
||
Dense implementation of ``method='FF'`` and ``method='CD'``.
|
||
clear_denoms
|
||
Clear denominators from a matrix, used by ``method='CD'`` and
|
||
by ``method='GJ'`` when the original domain is not a field.
|
||
|
||
"""
|
||
return _dm_rref(self, method=method)
|
||
|
||
def rref_den(self, *, method='auto', keep_domain=True):
|
||
r"""
|
||
Returns reduced-row echelon form with denominator and list of pivots.
|
||
|
||
Requires exact division in the ground domain (``exquo``).
|
||
|
||
Examples
|
||
========
|
||
|
||
>>> from sympy import ZZ, QQ
|
||
>>> from sympy.polys.matrices import DomainMatrix
|
||
>>> A = DomainMatrix([
|
||
... [ZZ(2), ZZ(-1), ZZ(0)],
|
||
... [ZZ(-1), ZZ(2), ZZ(-1)],
|
||
... [ZZ(0), ZZ(0), ZZ(2)]], (3, 3), ZZ)
|
||
|
||
>>> A_rref, denom, pivots = A.rref_den()
|
||
>>> A_rref
|
||
DomainMatrix([[6, 0, 0], [0, 6, 0], [0, 0, 6]], (3, 3), ZZ)
|
||
>>> denom
|
||
6
|
||
>>> pivots
|
||
(0, 1, 2)
|
||
>>> A_rref.to_field() / denom
|
||
DomainMatrix([[1, 0, 0], [0, 1, 0], [0, 0, 1]], (3, 3), QQ)
|
||
>>> A_rref.to_field() / denom == A.convert_to(QQ).rref()[0]
|
||
True
|
||
|
||
Parameters
|
||
==========
|
||
|
||
method : str, optional (default: 'auto')
|
||
The method to use to compute the RREF. The default is ``'auto'``,
|
||
which will attempt to choose the fastest method. The other options
|
||
are:
|
||
|
||
- ``A.rref(method='FF')`` uses fraction-free Gauss-Jordan
|
||
elimination. Elimination is performed using exact division
|
||
(``exquo``) to control the growth of the coefficients. In this
|
||
case the current domain is always used for elimination and the
|
||
result is always returned as a matrix over the current domain.
|
||
This is most efficient for dense matrices or for matrices with
|
||
simple denominators.
|
||
|
||
- ``A.rref(method='CD')`` clears denominators before using
|
||
fraction-free Gauss-Jordan elimination in the assoicated ring.
|
||
The result will be converted back to the original domain unless
|
||
``keep_domain=False`` is passed in which case the result will be
|
||
over the ring used for elimination. This is most efficient for
|
||
dense matrices with very simple denominators.
|
||
|
||
- ``A.rref(method='GJ')`` uses Gauss-Jordan elimination with
|
||
division. If the domain is not a field then it will be converted
|
||
to a field with :meth:`to_field` first and RREF will be computed
|
||
by inverting the pivot elements in each row. The result is
|
||
converted back to the original domain by clearing denominators
|
||
unless ``keep_domain=False`` is passed in which case the result
|
||
will be over the field used for elimination. This is most
|
||
efficient for very sparse matrices or for matrices whose elements
|
||
have complex denominators.
|
||
|
||
- ``A.rref(method='GJ_dense')``, ``A.rref(method='FF_dense')``, and
|
||
``A.rref(method='CD_dense')`` are the same as the above methods
|
||
except that the dense implementations of the algorithms are used.
|
||
By default ``A.rref(method='auto')`` will usually choose the
|
||
sparse implementations for RREF.
|
||
|
||
Regardless of which algorithm is used the returned matrix will
|
||
always have the same format (sparse or dense) as the input and if
|
||
``keep_domain=True`` its domain will always be the same as the
|
||
input.
|
||
|
||
keep_domain : bool, optional
|
||
If True (the default), the domain of the returned matrix and
|
||
denominator are the same as the domain of the input matrix. If
|
||
False, the domain of the returned matrix might be changed to an
|
||
associated ring or field if the algorithm used a different domain.
|
||
This is useful for efficiency if the caller does not need the
|
||
result to be in the original domain e.g. it avoids clearing
|
||
denominators in the case of ``A.rref(method='GJ')``.
|
||
|
||
Returns
|
||
=======
|
||
|
||
(DomainMatrix, scalar, list)
|
||
Reduced-row echelon form, denominator and list of pivot indices.
|
||
|
||
See Also
|
||
========
|
||
|
||
rref
|
||
RREF without denominator for field domains.
|
||
sympy.polys.matrices.sdm.sdm_irref
|
||
Sparse implementation of ``method='GJ'``.
|
||
sympy.polys.matrices.sdm.sdm_rref_den
|
||
Sparse implementation of ``method='FF'`` and ``method='CD'``.
|
||
sympy.polys.matrices.dense.ddm_irref
|
||
Dense implementation of ``method='GJ'``.
|
||
sympy.polys.matrices.dense.ddm_irref_den
|
||
Dense implementation of ``method='FF'`` and ``method='CD'``.
|
||
clear_denoms
|
||
Clear denominators from a matrix, used by ``method='CD'``.
|
||
|
||
"""
|
||
return _dm_rref_den(self, method=method, keep_domain=keep_domain)
|
||
|
||
def columnspace(self):
|
||
r"""
|
||
Returns the columnspace for the DomainMatrix
|
||
|
||
Returns
|
||
=======
|
||
|
||
DomainMatrix
|
||
The columns of this matrix form a basis for the columnspace.
|
||
|
||
Examples
|
||
========
|
||
|
||
>>> from sympy import QQ
|
||
>>> from sympy.polys.matrices import DomainMatrix
|
||
>>> A = DomainMatrix([
|
||
... [QQ(1), QQ(-1)],
|
||
... [QQ(2), QQ(-2)]], (2, 2), QQ)
|
||
>>> A.columnspace()
|
||
DomainMatrix([[1], [2]], (2, 1), QQ)
|
||
|
||
"""
|
||
if not self.domain.is_Field:
|
||
raise DMNotAField('Not a field')
|
||
rref, pivots = self.rref()
|
||
rows, cols = self.shape
|
||
return self.extract(range(rows), pivots)
|
||
|
||
def rowspace(self):
|
||
r"""
|
||
Returns the rowspace for the DomainMatrix
|
||
|
||
Returns
|
||
=======
|
||
|
||
DomainMatrix
|
||
The rows of this matrix form a basis for the rowspace.
|
||
|
||
Examples
|
||
========
|
||
|
||
>>> from sympy import QQ
|
||
>>> from sympy.polys.matrices import DomainMatrix
|
||
>>> A = DomainMatrix([
|
||
... [QQ(1), QQ(-1)],
|
||
... [QQ(2), QQ(-2)]], (2, 2), QQ)
|
||
>>> A.rowspace()
|
||
DomainMatrix([[1, -1]], (1, 2), QQ)
|
||
|
||
"""
|
||
if not self.domain.is_Field:
|
||
raise DMNotAField('Not a field')
|
||
rref, pivots = self.rref()
|
||
rows, cols = self.shape
|
||
return self.extract(range(len(pivots)), range(cols))
|
||
|
||
def nullspace(self, divide_last=False):
|
||
r"""
|
||
Returns the nullspace for the DomainMatrix
|
||
|
||
Returns
|
||
=======
|
||
|
||
DomainMatrix
|
||
The rows of this matrix form a basis for the nullspace.
|
||
|
||
Examples
|
||
========
|
||
|
||
>>> from sympy import QQ
|
||
>>> from sympy.polys.matrices import DM
|
||
>>> A = DM([
|
||
... [QQ(2), QQ(-2)],
|
||
... [QQ(4), QQ(-4)]], QQ)
|
||
>>> A.nullspace()
|
||
DomainMatrix([[1, 1]], (1, 2), QQ)
|
||
|
||
The returned matrix is a basis for the nullspace:
|
||
|
||
>>> A_null = A.nullspace().transpose()
|
||
>>> A * A_null
|
||
DomainMatrix([[0], [0]], (2, 1), QQ)
|
||
>>> rows, cols = A.shape
|
||
>>> nullity = rows - A.rank()
|
||
>>> A_null.shape == (cols, nullity)
|
||
True
|
||
|
||
Nullspace can also be computed for non-field rings. If the ring is not
|
||
a field then division is not used. Setting ``divide_last`` to True will
|
||
raise an error in this case:
|
||
|
||
>>> from sympy import ZZ
|
||
>>> B = DM([[6, -3],
|
||
... [4, -2]], ZZ)
|
||
>>> B.nullspace()
|
||
DomainMatrix([[3, 6]], (1, 2), ZZ)
|
||
>>> B.nullspace(divide_last=True)
|
||
Traceback (most recent call last):
|
||
...
|
||
DMNotAField: Cannot normalize vectors over a non-field
|
||
|
||
Over a ring with ``gcd`` defined the nullspace can potentially be
|
||
reduced with :meth:`primitive`:
|
||
|
||
>>> B.nullspace().primitive()
|
||
(3, DomainMatrix([[1, 2]], (1, 2), ZZ))
|
||
|
||
A matrix over a ring can often be normalized by converting it to a
|
||
field but it is often a bad idea to do so:
|
||
|
||
>>> from sympy.abc import a, b, c
|
||
>>> from sympy import Matrix
|
||
>>> M = Matrix([[ a*b, b + c, c],
|
||
... [ a - b, b*c, c**2],
|
||
... [a*b + a - b, b*c + b + c, c**2 + c]])
|
||
>>> M.to_DM().domain
|
||
ZZ[a,b,c]
|
||
>>> M.to_DM().nullspace().to_Matrix().transpose()
|
||
Matrix([
|
||
[ c**3],
|
||
[ -a*b*c**2 + a*c - b*c],
|
||
[a*b**2*c - a*b - a*c + b**2 + b*c]])
|
||
|
||
The unnormalized form here is nicer than the normalized form that
|
||
spreads a large denominator throughout the matrix:
|
||
|
||
>>> M.to_DM().to_field().nullspace(divide_last=True).to_Matrix().transpose()
|
||
Matrix([
|
||
[ c**3/(a*b**2*c - a*b - a*c + b**2 + b*c)],
|
||
[(-a*b*c**2 + a*c - b*c)/(a*b**2*c - a*b - a*c + b**2 + b*c)],
|
||
[ 1]])
|
||
|
||
Parameters
|
||
==========
|
||
|
||
divide_last : bool, optional
|
||
If False (the default), the vectors are not normalized and the RREF
|
||
is computed using :meth:`rref_den` and the denominator is
|
||
discarded. If True, then each row is divided by its final element;
|
||
the domain must be a field in this case.
|
||
|
||
See Also
|
||
========
|
||
|
||
nullspace_from_rref
|
||
rref
|
||
rref_den
|
||
rowspace
|
||
"""
|
||
A = self
|
||
K = A.domain
|
||
|
||
if divide_last and not K.is_Field:
|
||
raise DMNotAField("Cannot normalize vectors over a non-field")
|
||
|
||
if divide_last:
|
||
A_rref, pivots = A.rref()
|
||
else:
|
||
A_rref, den, pivots = A.rref_den()
|
||
|
||
# Ensure that the sign is canonical before discarding the
|
||
# denominator. Then M.nullspace().primitive() is canonical.
|
||
u = K.canonical_unit(den)
|
||
if u != K.one:
|
||
A_rref *= u
|
||
|
||
A_null = A_rref.nullspace_from_rref(pivots)
|
||
|
||
return A_null
|
||
|
||
def nullspace_from_rref(self, pivots=None):
|
||
"""
|
||
Compute nullspace from rref and pivots.
|
||
|
||
The domain of the matrix can be any domain.
|
||
|
||
The matrix must be in reduced row echelon form already. Otherwise the
|
||
result will be incorrect. Use :meth:`rref` or :meth:`rref_den` first
|
||
to get the reduced row echelon form or use :meth:`nullspace` instead.
|
||
|
||
See Also
|
||
========
|
||
|
||
nullspace
|
||
rref
|
||
rref_den
|
||
sympy.polys.matrices.sdm.SDM.nullspace_from_rref
|
||
sympy.polys.matrices.ddm.DDM.nullspace_from_rref
|
||
"""
|
||
null_rep, nonpivots = self.rep.nullspace_from_rref(pivots)
|
||
return self.from_rep(null_rep)
|
||
|
||
def inv(self):
|
||
r"""
|
||
Finds the inverse of the DomainMatrix if exists
|
||
|
||
Returns
|
||
=======
|
||
|
||
DomainMatrix
|
||
DomainMatrix after inverse
|
||
|
||
Raises
|
||
======
|
||
|
||
ValueError
|
||
If the domain of DomainMatrix not a Field
|
||
|
||
DMNonSquareMatrixError
|
||
If the DomainMatrix is not a not Square DomainMatrix
|
||
|
||
Examples
|
||
========
|
||
|
||
>>> from sympy import QQ
|
||
>>> from sympy.polys.matrices import DomainMatrix
|
||
>>> A = DomainMatrix([
|
||
... [QQ(2), QQ(-1), QQ(0)],
|
||
... [QQ(-1), QQ(2), QQ(-1)],
|
||
... [QQ(0), QQ(0), QQ(2)]], (3, 3), QQ)
|
||
>>> A.inv()
|
||
DomainMatrix([[2/3, 1/3, 1/6], [1/3, 2/3, 1/3], [0, 0, 1/2]], (3, 3), QQ)
|
||
|
||
See Also
|
||
========
|
||
|
||
neg
|
||
|
||
"""
|
||
if not self.domain.is_Field:
|
||
raise DMNotAField('Not a field')
|
||
m, n = self.shape
|
||
if m != n:
|
||
raise DMNonSquareMatrixError
|
||
inv = self.rep.inv()
|
||
return self.from_rep(inv)
|
||
|
||
def det(self):
|
||
r"""
|
||
Returns the determinant of a square :class:`DomainMatrix`.
|
||
|
||
Returns
|
||
=======
|
||
|
||
determinant: DomainElement
|
||
Determinant of the matrix.
|
||
|
||
Raises
|
||
======
|
||
|
||
ValueError
|
||
If the domain of DomainMatrix is not a Field
|
||
|
||
Examples
|
||
========
|
||
|
||
>>> from sympy import ZZ
|
||
>>> from sympy.polys.matrices import DomainMatrix
|
||
>>> A = DomainMatrix([
|
||
... [ZZ(1), ZZ(2)],
|
||
... [ZZ(3), ZZ(4)]], (2, 2), ZZ)
|
||
|
||
>>> A.det()
|
||
-2
|
||
|
||
"""
|
||
m, n = self.shape
|
||
if m != n:
|
||
raise DMNonSquareMatrixError
|
||
return self.rep.det()
|
||
|
||
def adj_det(self):
|
||
"""
|
||
Adjugate and determinant of a square :class:`DomainMatrix`.
|
||
|
||
Returns
|
||
=======
|
||
|
||
(adjugate, determinant) : (DomainMatrix, DomainScalar)
|
||
The adjugate matrix and determinant of this matrix.
|
||
|
||
Examples
|
||
========
|
||
|
||
>>> from sympy import ZZ
|
||
>>> from sympy.polys.matrices import DM
|
||
>>> A = DM([
|
||
... [ZZ(1), ZZ(2)],
|
||
... [ZZ(3), ZZ(4)]], ZZ)
|
||
>>> adjA, detA = A.adj_det()
|
||
>>> adjA
|
||
DomainMatrix([[4, -2], [-3, 1]], (2, 2), ZZ)
|
||
>>> detA
|
||
-2
|
||
|
||
See Also
|
||
========
|
||
|
||
adjugate
|
||
Returns only the adjugate matrix.
|
||
det
|
||
Returns only the determinant.
|
||
inv_den
|
||
Returns a matrix/denominator pair representing the inverse matrix
|
||
but perhaps differing from the adjugate and determinant by a common
|
||
factor.
|
||
"""
|
||
m, n = self.shape
|
||
I_m = self.eye((m, m), self.domain)
|
||
adjA, detA = self.solve_den_charpoly(I_m, check=False)
|
||
if self.rep.fmt == "dense":
|
||
adjA = adjA.to_dense()
|
||
return adjA, detA
|
||
|
||
def adjugate(self):
|
||
"""
|
||
Adjugate of a square :class:`DomainMatrix`.
|
||
|
||
The adjugate matrix is the transpose of the cofactor matrix and is
|
||
related to the inverse by::
|
||
|
||
adj(A) = det(A) * A.inv()
|
||
|
||
Unlike the inverse matrix the adjugate matrix can be computed and
|
||
expressed without division or fractions in the ground domain.
|
||
|
||
Examples
|
||
========
|
||
|
||
>>> from sympy import ZZ
|
||
>>> from sympy.polys.matrices import DM
|
||
>>> A = DM([[ZZ(1), ZZ(2)], [ZZ(3), ZZ(4)]], ZZ)
|
||
>>> A.adjugate()
|
||
DomainMatrix([[4, -2], [-3, 1]], (2, 2), ZZ)
|
||
|
||
Returns
|
||
=======
|
||
|
||
DomainMatrix
|
||
The adjugate matrix of this matrix with the same domain.
|
||
|
||
See Also
|
||
========
|
||
|
||
adj_det
|
||
"""
|
||
adjA, detA = self.adj_det()
|
||
return adjA
|
||
|
||
def inv_den(self, method=None):
|
||
"""
|
||
Return the inverse as a :class:`DomainMatrix` with denominator.
|
||
|
||
Returns
|
||
=======
|
||
|
||
(inv, den) : (:class:`DomainMatrix`, :class:`~.DomainElement`)
|
||
The inverse matrix and its denominator.
|
||
|
||
This is more or less equivalent to :meth:`adj_det` except that ``inv``
|
||
and ``den`` are not guaranteed to be the adjugate and inverse. The
|
||
ratio ``inv/den`` is equivalent to ``adj/det`` but some factors
|
||
might be cancelled between ``inv`` and ``den``. In simple cases this
|
||
might just be a minus sign so that ``(inv, den) == (-adj, -det)`` but
|
||
factors more complicated than ``-1`` can also be cancelled.
|
||
Cancellation is not guaranteed to be complete so ``inv`` and ``den``
|
||
may not be on lowest terms. The denominator ``den`` will be zero if and
|
||
only if the determinant is zero.
|
||
|
||
If the actual adjugate and determinant are needed, use :meth:`adj_det`
|
||
instead. If the intention is to compute the inverse matrix or solve a
|
||
system of equations then :meth:`inv_den` is more efficient.
|
||
|
||
Examples
|
||
========
|
||
|
||
>>> from sympy import ZZ
|
||
>>> from sympy.polys.matrices import DomainMatrix
|
||
>>> A = DomainMatrix([
|
||
... [ZZ(2), ZZ(-1), ZZ(0)],
|
||
... [ZZ(-1), ZZ(2), ZZ(-1)],
|
||
... [ZZ(0), ZZ(0), ZZ(2)]], (3, 3), ZZ)
|
||
>>> Ainv, den = A.inv_den()
|
||
>>> den
|
||
6
|
||
>>> Ainv
|
||
DomainMatrix([[4, 2, 1], [2, 4, 2], [0, 0, 3]], (3, 3), ZZ)
|
||
>>> A * Ainv == den * A.eye(A.shape, A.domain).to_dense()
|
||
True
|
||
|
||
Parameters
|
||
==========
|
||
|
||
method : str, optional
|
||
The method to use to compute the inverse. Can be one of ``None``,
|
||
``'rref'`` or ``'charpoly'``. If ``None`` then the method is
|
||
chosen automatically (see :meth:`solve_den` for details).
|
||
|
||
See Also
|
||
========
|
||
|
||
inv
|
||
det
|
||
adj_det
|
||
solve_den
|
||
"""
|
||
I = self.eye(self.shape, self.domain)
|
||
return self.solve_den(I, method=method)
|
||
|
||
def solve_den(self, b, method=None):
|
||
"""
|
||
Solve matrix equation $Ax = b$ without fractions in the ground domain.
|
||
|
||
Examples
|
||
========
|
||
|
||
Solve a matrix equation over the integers:
|
||
|
||
>>> from sympy import ZZ
|
||
>>> from sympy.polys.matrices import DM
|
||
>>> A = DM([[ZZ(1), ZZ(2)], [ZZ(3), ZZ(4)]], ZZ)
|
||
>>> b = DM([[ZZ(5)], [ZZ(6)]], ZZ)
|
||
>>> xnum, xden = A.solve_den(b)
|
||
>>> xden
|
||
-2
|
||
>>> xnum
|
||
DomainMatrix([[8], [-9]], (2, 1), ZZ)
|
||
>>> A * xnum == xden * b
|
||
True
|
||
|
||
Solve a matrix equation over a polynomial ring:
|
||
|
||
>>> from sympy import ZZ
|
||
>>> from sympy.abc import x, y, z, a, b
|
||
>>> R = ZZ[x, y, z, a, b]
|
||
>>> M = DM([[x*y, x*z], [y*z, x*z]], R)
|
||
>>> b = DM([[a], [b]], R)
|
||
>>> M.to_Matrix()
|
||
Matrix([
|
||
[x*y, x*z],
|
||
[y*z, x*z]])
|
||
>>> b.to_Matrix()
|
||
Matrix([
|
||
[a],
|
||
[b]])
|
||
>>> xnum, xden = M.solve_den(b)
|
||
>>> xden
|
||
x**2*y*z - x*y*z**2
|
||
>>> xnum.to_Matrix()
|
||
Matrix([
|
||
[ a*x*z - b*x*z],
|
||
[-a*y*z + b*x*y]])
|
||
>>> M * xnum == xden * b
|
||
True
|
||
|
||
The solution can be expressed over a fraction field which will cancel
|
||
gcds between the denominator and the elements of the numerator:
|
||
|
||
>>> xsol = xnum.to_field() / xden
|
||
>>> xsol.to_Matrix()
|
||
Matrix([
|
||
[ (a - b)/(x*y - y*z)],
|
||
[(-a*z + b*x)/(x**2*z - x*z**2)]])
|
||
>>> (M * xsol).to_Matrix() == b.to_Matrix()
|
||
True
|
||
|
||
When solving a large system of equations this cancellation step might
|
||
be a lot slower than :func:`solve_den` itself. The solution can also be
|
||
expressed as a ``Matrix`` without attempting any polynomial
|
||
cancellation between the numerator and denominator giving a less
|
||
simplified result more quickly:
|
||
|
||
>>> xsol_uncancelled = xnum.to_Matrix() / xnum.domain.to_sympy(xden)
|
||
>>> xsol_uncancelled
|
||
Matrix([
|
||
[ (a*x*z - b*x*z)/(x**2*y*z - x*y*z**2)],
|
||
[(-a*y*z + b*x*y)/(x**2*y*z - x*y*z**2)]])
|
||
>>> from sympy import cancel
|
||
>>> cancel(xsol_uncancelled) == xsol.to_Matrix()
|
||
True
|
||
|
||
Parameters
|
||
==========
|
||
|
||
self : :class:`DomainMatrix`
|
||
The ``m x n`` matrix $A$ in the equation $Ax = b$. Underdetermined
|
||
systems are not supported so ``m >= n``: $A$ should be square or
|
||
have more rows than columns.
|
||
b : :class:`DomainMatrix`
|
||
The ``n x m`` matrix $b$ for the rhs.
|
||
cp : list of :class:`~.DomainElement`, optional
|
||
The characteristic polynomial of the matrix $A$. If not given, it
|
||
will be computed using :meth:`charpoly`.
|
||
method: str, optional
|
||
The method to use for solving the system. Can be one of ``None``,
|
||
``'charpoly'`` or ``'rref'``. If ``None`` (the default) then the
|
||
method will be chosen automatically.
|
||
|
||
The ``charpoly`` method uses :meth:`solve_den_charpoly` and can
|
||
only be used if the matrix is square. This method is division free
|
||
and can be used with any domain.
|
||
|
||
The ``rref`` method is fraction free but requires exact division
|
||
in the ground domain (``exquo``). This is also suitable for most
|
||
domains. This method can be used with overdetermined systems (more
|
||
equations than unknowns) but not underdetermined systems as a
|
||
unique solution is sought.
|
||
|
||
Returns
|
||
=======
|
||
|
||
(xnum, xden) : (DomainMatrix, DomainElement)
|
||
The solution of the equation $Ax = b$ as a pair consisting of an
|
||
``n x m`` matrix numerator ``xnum`` and a scalar denominator
|
||
``xden``.
|
||
|
||
The solution $x$ is given by ``x = xnum / xden``. The division free
|
||
invariant is ``A * xnum == xden * b``. If $A$ is square then the
|
||
denominator ``xden`` will be a divisor of the determinant $det(A)$.
|
||
|
||
Raises
|
||
======
|
||
|
||
DMNonInvertibleMatrixError
|
||
If the system $Ax = b$ does not have a unique solution.
|
||
|
||
See Also
|
||
========
|
||
|
||
solve_den_charpoly
|
||
solve_den_rref
|
||
inv_den
|
||
"""
|
||
m, n = self.shape
|
||
bm, bn = b.shape
|
||
|
||
if m != bm:
|
||
raise DMShapeError("Matrix equation shape mismatch.")
|
||
|
||
if method is None:
|
||
method = 'rref'
|
||
elif method == 'charpoly' and m != n:
|
||
raise DMNonSquareMatrixError("method='charpoly' requires a square matrix.")
|
||
|
||
if method == 'charpoly':
|
||
xnum, xden = self.solve_den_charpoly(b)
|
||
elif method == 'rref':
|
||
xnum, xden = self.solve_den_rref(b)
|
||
else:
|
||
raise DMBadInputError("method should be 'rref' or 'charpoly'")
|
||
|
||
return xnum, xden
|
||
|
||
def solve_den_rref(self, b):
|
||
"""
|
||
Solve matrix equation $Ax = b$ using fraction-free RREF
|
||
|
||
Solves the matrix equation $Ax = b$ for $x$ and returns the solution
|
||
as a numerator/denominator pair.
|
||
|
||
Examples
|
||
========
|
||
|
||
>>> from sympy import ZZ
|
||
>>> from sympy.polys.matrices import DM
|
||
>>> A = DM([[ZZ(1), ZZ(2)], [ZZ(3), ZZ(4)]], ZZ)
|
||
>>> b = DM([[ZZ(5)], [ZZ(6)]], ZZ)
|
||
>>> xnum, xden = A.solve_den_rref(b)
|
||
>>> xden
|
||
-2
|
||
>>> xnum
|
||
DomainMatrix([[8], [-9]], (2, 1), ZZ)
|
||
>>> A * xnum == xden * b
|
||
True
|
||
|
||
See Also
|
||
========
|
||
|
||
solve_den
|
||
solve_den_charpoly
|
||
"""
|
||
A = self
|
||
m, n = A.shape
|
||
bm, bn = b.shape
|
||
|
||
if m != bm:
|
||
raise DMShapeError("Matrix equation shape mismatch.")
|
||
|
||
if m < n:
|
||
raise DMShapeError("Underdetermined matrix equation.")
|
||
|
||
Aaug = A.hstack(b)
|
||
Aaug_rref, denom, pivots = Aaug.rref_den()
|
||
|
||
# XXX: We check here if there are pivots after the last column. If
|
||
# there were than it possibly means that rref_den performed some
|
||
# unnecessary elimination. It would be better if rref methods had a
|
||
# parameter indicating how many columns should be used for elimination.
|
||
if len(pivots) != n or pivots and pivots[-1] >= n:
|
||
raise DMNonInvertibleMatrixError("Non-unique solution.")
|
||
|
||
xnum = Aaug_rref[:n, n:]
|
||
xden = denom
|
||
|
||
return xnum, xden
|
||
|
||
def solve_den_charpoly(self, b, cp=None, check=True):
|
||
"""
|
||
Solve matrix equation $Ax = b$ using the characteristic polynomial.
|
||
|
||
This method solves the square matrix equation $Ax = b$ for $x$ using
|
||
the characteristic polynomial without any division or fractions in the
|
||
ground domain.
|
||
|
||
Examples
|
||
========
|
||
|
||
Solve a matrix equation over the integers:
|
||
|
||
>>> from sympy import ZZ
|
||
>>> from sympy.polys.matrices import DM
|
||
>>> A = DM([[ZZ(1), ZZ(2)], [ZZ(3), ZZ(4)]], ZZ)
|
||
>>> b = DM([[ZZ(5)], [ZZ(6)]], ZZ)
|
||
>>> xnum, detA = A.solve_den_charpoly(b)
|
||
>>> detA
|
||
-2
|
||
>>> xnum
|
||
DomainMatrix([[8], [-9]], (2, 1), ZZ)
|
||
>>> A * xnum == detA * b
|
||
True
|
||
|
||
Parameters
|
||
==========
|
||
|
||
self : DomainMatrix
|
||
The ``n x n`` matrix `A` in the equation `Ax = b`. Must be square
|
||
and invertible.
|
||
b : DomainMatrix
|
||
The ``n x m`` matrix `b` for the rhs.
|
||
cp : list, optional
|
||
The characteristic polynomial of the matrix `A` if known. If not
|
||
given, it will be computed using :meth:`charpoly`.
|
||
check : bool, optional
|
||
If ``True`` (the default) check that the determinant is not zero
|
||
and raise an error if it is. If ``False`` then if the determinant
|
||
is zero the return value will be equal to ``(A.adjugate()*b, 0)``.
|
||
|
||
Returns
|
||
=======
|
||
|
||
(xnum, detA) : (DomainMatrix, DomainElement)
|
||
The solution of the equation `Ax = b` as a matrix numerator and
|
||
scalar denominator pair. The denominator is equal to the
|
||
determinant of `A` and the numerator is ``adj(A)*b``.
|
||
|
||
The solution $x$ is given by ``x = xnum / detA``. The division free
|
||
invariant is ``A * xnum == detA * b``.
|
||
|
||
If ``b`` is the identity matrix, then ``xnum`` is the adjugate matrix
|
||
and we have ``A * adj(A) == detA * I``.
|
||
|
||
See Also
|
||
========
|
||
|
||
solve_den
|
||
Main frontend for solving matrix equations with denominator.
|
||
solve_den_rref
|
||
Solve matrix equations using fraction-free RREF.
|
||
inv_den
|
||
Invert a matrix using the characteristic polynomial.
|
||
"""
|
||
A, b = self.unify(b)
|
||
m, n = self.shape
|
||
mb, nb = b.shape
|
||
|
||
if m != n:
|
||
raise DMNonSquareMatrixError("Matrix must be square")
|
||
|
||
if mb != m:
|
||
raise DMShapeError("Matrix and vector must have the same number of rows")
|
||
|
||
f, detA = self.adj_poly_det(cp=cp)
|
||
|
||
if check and not detA:
|
||
raise DMNonInvertibleMatrixError("Matrix is not invertible")
|
||
|
||
# Compute adj(A)*b = det(A)*inv(A)*b using Horner's method without
|
||
# constructing inv(A) explicitly.
|
||
adjA_b = self.eval_poly_mul(f, b)
|
||
|
||
return (adjA_b, detA)
|
||
|
||
def adj_poly_det(self, cp=None):
|
||
"""
|
||
Return the polynomial $p$ such that $p(A) = adj(A)$ and also the
|
||
determinant of $A$.
|
||
|
||
Examples
|
||
========
|
||
|
||
>>> from sympy import QQ
|
||
>>> from sympy.polys.matrices import DM
|
||
>>> A = DM([[QQ(1), QQ(2)], [QQ(3), QQ(4)]], QQ)
|
||
>>> p, detA = A.adj_poly_det()
|
||
>>> p
|
||
[-1, 5]
|
||
>>> p_A = A.eval_poly(p)
|
||
>>> p_A
|
||
DomainMatrix([[4, -2], [-3, 1]], (2, 2), QQ)
|
||
>>> p[0]*A**1 + p[1]*A**0 == p_A
|
||
True
|
||
>>> p_A == A.adjugate()
|
||
True
|
||
>>> A * A.adjugate() == detA * A.eye(A.shape, A.domain).to_dense()
|
||
True
|
||
|
||
See Also
|
||
========
|
||
|
||
adjugate
|
||
eval_poly
|
||
adj_det
|
||
"""
|
||
|
||
# Cayley-Hamilton says that a matrix satisfies its own minimal
|
||
# polynomial
|
||
#
|
||
# p[0]*A^n + p[1]*A^(n-1) + ... + p[n]*I = 0
|
||
#
|
||
# with p[0]=1 and p[n]=(-1)^n*det(A) or
|
||
#
|
||
# det(A)*I = -(-1)^n*(p[0]*A^(n-1) + p[1]*A^(n-2) + ... + p[n-1]*A).
|
||
#
|
||
# Define a new polynomial f with f[i] = -(-1)^n*p[i] for i=0..n-1. Then
|
||
#
|
||
# det(A)*I = f[0]*A^n + f[1]*A^(n-1) + ... + f[n-1]*A.
|
||
#
|
||
# Multiplying on the right by inv(A) gives
|
||
#
|
||
# det(A)*inv(A) = f[0]*A^(n-1) + f[1]*A^(n-2) + ... + f[n-1].
|
||
#
|
||
# So adj(A) = det(A)*inv(A) = f(A)
|
||
|
||
A = self
|
||
m, n = self.shape
|
||
|
||
if m != n:
|
||
raise DMNonSquareMatrixError("Matrix must be square")
|
||
|
||
if cp is None:
|
||
cp = A.charpoly()
|
||
|
||
if len(cp) % 2:
|
||
# n is even
|
||
detA = cp[-1]
|
||
f = [-cpi for cpi in cp[:-1]]
|
||
else:
|
||
# n is odd
|
||
detA = -cp[-1]
|
||
f = cp[:-1]
|
||
|
||
return f, detA
|
||
|
||
def eval_poly(self, p):
|
||
"""
|
||
Evaluate polynomial function of a matrix $p(A)$.
|
||
|
||
Examples
|
||
========
|
||
|
||
>>> from sympy import QQ
|
||
>>> from sympy.polys.matrices import DM
|
||
>>> A = DM([[QQ(1), QQ(2)], [QQ(3), QQ(4)]], QQ)
|
||
>>> p = [QQ(1), QQ(2), QQ(3)]
|
||
>>> p_A = A.eval_poly(p)
|
||
>>> p_A
|
||
DomainMatrix([[12, 14], [21, 33]], (2, 2), QQ)
|
||
>>> p_A == p[0]*A**2 + p[1]*A + p[2]*A**0
|
||
True
|
||
|
||
See Also
|
||
========
|
||
|
||
eval_poly_mul
|
||
"""
|
||
A = self
|
||
m, n = A.shape
|
||
|
||
if m != n:
|
||
raise DMNonSquareMatrixError("Matrix must be square")
|
||
|
||
if not p:
|
||
return self.zeros(self.shape, self.domain)
|
||
elif len(p) == 1:
|
||
return p[0] * self.eye(self.shape, self.domain)
|
||
|
||
# Evaluate p(A) using Horner's method:
|
||
# XXX: Use Paterson-Stockmeyer method?
|
||
I = A.eye(A.shape, A.domain)
|
||
p_A = p[0] * I
|
||
for pi in p[1:]:
|
||
p_A = A*p_A + pi*I
|
||
|
||
return p_A
|
||
|
||
def eval_poly_mul(self, p, B):
|
||
r"""
|
||
Evaluate polynomial matrix product $p(A) \times B$.
|
||
|
||
Evaluate the polynomial matrix product $p(A) \times B$ using Horner's
|
||
method without creating the matrix $p(A)$ explicitly. If $B$ is a
|
||
column matrix then this method will only use matrix-vector multiplies
|
||
and no matrix-matrix multiplies are needed.
|
||
|
||
If $B$ is square or wide or if $A$ can be represented in a simpler
|
||
domain than $B$ then it might be faster to evaluate $p(A)$ explicitly
|
||
(see :func:`eval_poly`) and then multiply with $B$.
|
||
|
||
Examples
|
||
========
|
||
|
||
>>> from sympy import QQ
|
||
>>> from sympy.polys.matrices import DM
|
||
>>> A = DM([[QQ(1), QQ(2)], [QQ(3), QQ(4)]], QQ)
|
||
>>> b = DM([[QQ(5)], [QQ(6)]], QQ)
|
||
>>> p = [QQ(1), QQ(2), QQ(3)]
|
||
>>> p_A_b = A.eval_poly_mul(p, b)
|
||
>>> p_A_b
|
||
DomainMatrix([[144], [303]], (2, 1), QQ)
|
||
>>> p_A_b == p[0]*A**2*b + p[1]*A*b + p[2]*b
|
||
True
|
||
>>> A.eval_poly_mul(p, b) == A.eval_poly(p)*b
|
||
True
|
||
|
||
See Also
|
||
========
|
||
|
||
eval_poly
|
||
solve_den_charpoly
|
||
"""
|
||
A = self
|
||
m, n = A.shape
|
||
mb, nb = B.shape
|
||
|
||
if m != n:
|
||
raise DMNonSquareMatrixError("Matrix must be square")
|
||
|
||
if mb != n:
|
||
raise DMShapeError("Matrices are not aligned")
|
||
|
||
if A.domain != B.domain:
|
||
raise DMDomainError("Matrices must have the same domain")
|
||
|
||
# Given a polynomial p(x) = p[0]*x^n + p[1]*x^(n-1) + ... + p[n-1]
|
||
# and matrices A and B we want to find
|
||
#
|
||
# p(A)*B = p[0]*A^n*B + p[1]*A^(n-1)*B + ... + p[n-1]*B
|
||
#
|
||
# Factoring out A term by term we get
|
||
#
|
||
# p(A)*B = A*(...A*(A*(A*(p[0]*B) + p[1]*B) + p[2]*B) + ...) + p[n-1]*B
|
||
#
|
||
# where each pair of brackets represents one iteration of the loop
|
||
# below starting from the innermost p[0]*B. If B is a column matrix
|
||
# then products like A*(...) are matrix-vector multiplies and products
|
||
# like p[i]*B are scalar-vector multiplies so there are no
|
||
# matrix-matrix multiplies.
|
||
|
||
if not p:
|
||
return B.zeros(B.shape, B.domain, fmt=B.rep.fmt)
|
||
|
||
p_A_B = p[0]*B
|
||
|
||
for p_i in p[1:]:
|
||
p_A_B = A*p_A_B + p_i*B
|
||
|
||
return p_A_B
|
||
|
||
def lu(self):
|
||
r"""
|
||
Returns Lower and Upper decomposition of the DomainMatrix
|
||
|
||
Returns
|
||
=======
|
||
|
||
(L, U, exchange)
|
||
L, U are Lower and Upper decomposition of the DomainMatrix,
|
||
exchange is the list of indices of rows exchanged in the
|
||
decomposition.
|
||
|
||
Raises
|
||
======
|
||
|
||
ValueError
|
||
If the domain of DomainMatrix not a Field
|
||
|
||
Examples
|
||
========
|
||
|
||
>>> from sympy import QQ
|
||
>>> from sympy.polys.matrices import DomainMatrix
|
||
>>> A = DomainMatrix([
|
||
... [QQ(1), QQ(-1)],
|
||
... [QQ(2), QQ(-2)]], (2, 2), QQ)
|
||
>>> L, U, exchange = A.lu()
|
||
>>> L
|
||
DomainMatrix([[1, 0], [2, 1]], (2, 2), QQ)
|
||
>>> U
|
||
DomainMatrix([[1, -1], [0, 0]], (2, 2), QQ)
|
||
>>> exchange
|
||
[]
|
||
|
||
See Also
|
||
========
|
||
|
||
lu_solve
|
||
|
||
"""
|
||
if not self.domain.is_Field:
|
||
raise DMNotAField('Not a field')
|
||
L, U, swaps = self.rep.lu()
|
||
return self.from_rep(L), self.from_rep(U), swaps
|
||
|
||
def lu_solve(self, rhs):
|
||
r"""
|
||
Solver for DomainMatrix x in the A*x = B
|
||
|
||
Parameters
|
||
==========
|
||
|
||
rhs : DomainMatrix B
|
||
|
||
Returns
|
||
=======
|
||
|
||
DomainMatrix
|
||
x in A*x = B
|
||
|
||
Raises
|
||
======
|
||
|
||
DMShapeError
|
||
If the DomainMatrix A and rhs have different number of rows
|
||
|
||
ValueError
|
||
If the domain of DomainMatrix A not a Field
|
||
|
||
Examples
|
||
========
|
||
|
||
>>> from sympy import QQ
|
||
>>> from sympy.polys.matrices import DomainMatrix
|
||
>>> A = DomainMatrix([
|
||
... [QQ(1), QQ(2)],
|
||
... [QQ(3), QQ(4)]], (2, 2), QQ)
|
||
>>> B = DomainMatrix([
|
||
... [QQ(1), QQ(1)],
|
||
... [QQ(0), QQ(1)]], (2, 2), QQ)
|
||
|
||
>>> A.lu_solve(B)
|
||
DomainMatrix([[-2, -1], [3/2, 1]], (2, 2), QQ)
|
||
|
||
See Also
|
||
========
|
||
|
||
lu
|
||
|
||
"""
|
||
if self.shape[0] != rhs.shape[0]:
|
||
raise DMShapeError("Shape")
|
||
if not self.domain.is_Field:
|
||
raise DMNotAField('Not a field')
|
||
sol = self.rep.lu_solve(rhs.rep)
|
||
return self.from_rep(sol)
|
||
|
||
def _solve(A, b):
|
||
# XXX: Not sure about this method or its signature. It is just created
|
||
# because it is needed by the holonomic module.
|
||
if A.shape[0] != b.shape[0]:
|
||
raise DMShapeError("Shape")
|
||
if A.domain != b.domain or not A.domain.is_Field:
|
||
raise DMNotAField('Not a field')
|
||
Aaug = A.hstack(b)
|
||
Arref, pivots = Aaug.rref()
|
||
particular = Arref.from_rep(Arref.rep.particular())
|
||
nullspace_rep, nonpivots = Arref[:,:-1].rep.nullspace()
|
||
nullspace = Arref.from_rep(nullspace_rep)
|
||
return particular, nullspace
|
||
|
||
def charpoly(self):
|
||
r"""
|
||
Characteristic polynomial of a square matrix.
|
||
|
||
Computes the characteristic polynomial in a fully expanded form using
|
||
division free arithmetic. If a factorization of the characteristic
|
||
polynomial is needed then it is more efficient to call
|
||
:meth:`charpoly_factor_list` than calling :meth:`charpoly` and then
|
||
factorizing the result.
|
||
|
||
Returns
|
||
=======
|
||
|
||
list: list of DomainElement
|
||
coefficients of the characteristic polynomial
|
||
|
||
Examples
|
||
========
|
||
|
||
>>> from sympy import ZZ
|
||
>>> from sympy.polys.matrices import DomainMatrix
|
||
>>> A = DomainMatrix([
|
||
... [ZZ(1), ZZ(2)],
|
||
... [ZZ(3), ZZ(4)]], (2, 2), ZZ)
|
||
|
||
>>> A.charpoly()
|
||
[1, -5, -2]
|
||
|
||
See Also
|
||
========
|
||
|
||
charpoly_factor_list
|
||
Compute the factorisation of the characteristic polynomial.
|
||
charpoly_factor_blocks
|
||
A partial factorisation of the characteristic polynomial that can
|
||
be computed more efficiently than either the full factorisation or
|
||
the fully expanded polynomial.
|
||
"""
|
||
M = self
|
||
K = M.domain
|
||
|
||
factors = M.charpoly_factor_blocks()
|
||
|
||
cp = [K.one]
|
||
|
||
for f, mult in factors:
|
||
for _ in range(mult):
|
||
cp = dup_mul(cp, f, K)
|
||
|
||
return cp
|
||
|
||
def charpoly_factor_list(self):
|
||
"""
|
||
Full factorization of the characteristic polynomial.
|
||
|
||
Examples
|
||
========
|
||
|
||
>>> from sympy.polys.matrices import DM
|
||
>>> from sympy import ZZ
|
||
>>> M = DM([[6, -1, 0, 0],
|
||
... [9, 12, 0, 0],
|
||
... [0, 0, 1, 2],
|
||
... [0, 0, 5, 6]], ZZ)
|
||
|
||
Compute the factorization of the characteristic polynomial:
|
||
|
||
>>> M.charpoly_factor_list()
|
||
[([1, -9], 2), ([1, -7, -4], 1)]
|
||
|
||
Use :meth:`charpoly` to get the unfactorized characteristic polynomial:
|
||
|
||
>>> M.charpoly()
|
||
[1, -25, 203, -495, -324]
|
||
|
||
The same calculations with ``Matrix``:
|
||
|
||
>>> M.to_Matrix().charpoly().as_expr()
|
||
lambda**4 - 25*lambda**3 + 203*lambda**2 - 495*lambda - 324
|
||
>>> M.to_Matrix().charpoly().as_expr().factor()
|
||
(lambda - 9)**2*(lambda**2 - 7*lambda - 4)
|
||
|
||
Returns
|
||
=======
|
||
|
||
list: list of pairs (factor, multiplicity)
|
||
A full factorization of the characteristic polynomial.
|
||
|
||
See Also
|
||
========
|
||
|
||
charpoly
|
||
Expanded form of the characteristic polynomial.
|
||
charpoly_factor_blocks
|
||
A partial factorisation of the characteristic polynomial that can
|
||
be computed more efficiently.
|
||
"""
|
||
M = self
|
||
K = M.domain
|
||
|
||
# It is more efficient to start from the partial factorization provided
|
||
# for free by M.charpoly_factor_blocks than the expanded M.charpoly.
|
||
factors = M.charpoly_factor_blocks()
|
||
|
||
factors_irreducible = []
|
||
|
||
for factor_i, mult_i in factors:
|
||
|
||
_, factors_list = dup_factor_list(factor_i, K)
|
||
|
||
for factor_j, mult_j in factors_list:
|
||
factors_irreducible.append((factor_j, mult_i * mult_j))
|
||
|
||
return _collect_factors(factors_irreducible)
|
||
|
||
def charpoly_factor_blocks(self):
|
||
"""
|
||
Partial factorisation of the characteristic polynomial.
|
||
|
||
This factorisation arises from a block structure of the matrix (if any)
|
||
and so the factors are not guaranteed to be irreducible. The
|
||
:meth:`charpoly_factor_blocks` method is the most efficient way to get
|
||
a representation of the characteristic polynomial but the result is
|
||
neither fully expanded nor fully factored.
|
||
|
||
Examples
|
||
========
|
||
|
||
>>> from sympy.polys.matrices import DM
|
||
>>> from sympy import ZZ
|
||
>>> M = DM([[6, -1, 0, 0],
|
||
... [9, 12, 0, 0],
|
||
... [0, 0, 1, 2],
|
||
... [0, 0, 5, 6]], ZZ)
|
||
|
||
This computes a partial factorization using only the block structure of
|
||
the matrix to reveal factors:
|
||
|
||
>>> M.charpoly_factor_blocks()
|
||
[([1, -18, 81], 1), ([1, -7, -4], 1)]
|
||
|
||
These factors correspond to the two diagonal blocks in the matrix:
|
||
|
||
>>> DM([[6, -1], [9, 12]], ZZ).charpoly()
|
||
[1, -18, 81]
|
||
>>> DM([[1, 2], [5, 6]], ZZ).charpoly()
|
||
[1, -7, -4]
|
||
|
||
Use :meth:`charpoly_factor_list` to get a complete factorization into
|
||
irreducibles:
|
||
|
||
>>> M.charpoly_factor_list()
|
||
[([1, -9], 2), ([1, -7, -4], 1)]
|
||
|
||
Use :meth:`charpoly` to get the expanded characteristic polynomial:
|
||
|
||
>>> M.charpoly()
|
||
[1, -25, 203, -495, -324]
|
||
|
||
Returns
|
||
=======
|
||
|
||
list: list of pairs (factor, multiplicity)
|
||
A partial factorization of the characteristic polynomial.
|
||
|
||
See Also
|
||
========
|
||
|
||
charpoly
|
||
Compute the fully expanded characteristic polynomial.
|
||
charpoly_factor_list
|
||
Compute a full factorization of the characteristic polynomial.
|
||
"""
|
||
M = self
|
||
|
||
if not M.is_square:
|
||
raise DMNonSquareMatrixError("not square")
|
||
|
||
# scc returns indices that permute the matrix into block triangular
|
||
# form and can extract the diagonal blocks. M.charpoly() is equal to
|
||
# the product of the diagonal block charpolys.
|
||
components = M.scc()
|
||
|
||
block_factors = []
|
||
|
||
for indices in components:
|
||
block = M.extract(indices, indices)
|
||
block_factors.append((block.charpoly_base(), 1))
|
||
|
||
return _collect_factors(block_factors)
|
||
|
||
def charpoly_base(self):
|
||
"""
|
||
Base case for :meth:`charpoly_factor_blocks` after block decomposition.
|
||
|
||
This method is used internally by :meth:`charpoly_factor_blocks` as the
|
||
base case for computing the characteristic polynomial of a block. It is
|
||
more efficient to call :meth:`charpoly_factor_blocks`, :meth:`charpoly`
|
||
or :meth:`charpoly_factor_list` rather than call this method directly.
|
||
|
||
This will use either the dense or the sparse implementation depending
|
||
on the sparsity of the matrix and will clear denominators if possible
|
||
before calling :meth:`charpoly_berk` to compute the characteristic
|
||
polynomial using the Berkowitz algorithm.
|
||
|
||
See Also
|
||
========
|
||
|
||
charpoly
|
||
charpoly_factor_list
|
||
charpoly_factor_blocks
|
||
charpoly_berk
|
||
"""
|
||
M = self
|
||
K = M.domain
|
||
|
||
# It seems that the sparse implementation is always faster for random
|
||
# matrices with fewer than 50% non-zero entries. This does not seem to
|
||
# depend on domain, size, bit count etc.
|
||
density = self.nnz() / self.shape[0]**2
|
||
if density < 0.5:
|
||
M = M.to_sparse()
|
||
else:
|
||
M = M.to_dense()
|
||
|
||
# Clearing denominators is always more efficient if it can be done.
|
||
# Doing it here after block decomposition is good because each block
|
||
# might have a smaller denominator. However it might be better for
|
||
# charpoly and charpoly_factor_list to restore the denominators only at
|
||
# the very end so that they can call e.g. dup_factor_list before
|
||
# restoring the denominators. The methods would need to be changed to
|
||
# return (poly, denom) pairs to make that work though.
|
||
clear_denoms = K.is_Field and K.has_assoc_Ring
|
||
|
||
if clear_denoms:
|
||
clear_denoms = True
|
||
d, M = M.clear_denoms(convert=True)
|
||
d = d.element
|
||
K_f = K
|
||
K_r = M.domain
|
||
|
||
# Berkowitz algorithm over K_r.
|
||
cp = M.charpoly_berk()
|
||
|
||
if clear_denoms:
|
||
# Restore the denominator in the charpoly over K_f.
|
||
#
|
||
# If M = N/d then p_M(x) = p_N(x*d)/d^n.
|
||
cp = dup_convert(cp, K_r, K_f)
|
||
p = [K_f.one, K_f.zero]
|
||
q = [K_f.one/d]
|
||
cp = dup_transform(cp, p, q, K_f)
|
||
|
||
return cp
|
||
|
||
def charpoly_berk(self):
|
||
"""Compute the characteristic polynomial using the Berkowitz algorithm.
|
||
|
||
This method directly calls the underlying implementation of the
|
||
Berkowitz algorithm (:meth:`sympy.polys.matrices.dense.ddm_berk` or
|
||
:meth:`sympy.polys.matrices.sdm.sdm_berk`).
|
||
|
||
This is used by :meth:`charpoly` and other methods as the base case for
|
||
for computing the characteristic polynomial. However those methods will
|
||
apply other optimizations such as block decomposition, clearing
|
||
denominators and converting between dense and sparse representations
|
||
before calling this method. It is more efficient to call those methods
|
||
instead of this one but this method is provided for direct access to
|
||
the Berkowitz algorithm.
|
||
|
||
Examples
|
||
========
|
||
|
||
>>> from sympy.polys.matrices import DM
|
||
>>> from sympy import QQ
|
||
>>> M = DM([[6, -1, 0, 0],
|
||
... [9, 12, 0, 0],
|
||
... [0, 0, 1, 2],
|
||
... [0, 0, 5, 6]], QQ)
|
||
>>> M.charpoly_berk()
|
||
[1, -25, 203, -495, -324]
|
||
|
||
See Also
|
||
========
|
||
|
||
charpoly
|
||
charpoly_base
|
||
charpoly_factor_list
|
||
charpoly_factor_blocks
|
||
sympy.polys.matrices.dense.ddm_berk
|
||
sympy.polys.matrices.sdm.sdm_berk
|
||
"""
|
||
return self.rep.charpoly()
|
||
|
||
@classmethod
|
||
def eye(cls, shape, domain):
|
||
r"""
|
||
Return identity matrix of size n or shape (m, n).
|
||
|
||
Examples
|
||
========
|
||
|
||
>>> from sympy.polys.matrices import DomainMatrix
|
||
>>> from sympy import QQ
|
||
>>> DomainMatrix.eye(3, QQ)
|
||
DomainMatrix({0: {0: 1}, 1: {1: 1}, 2: {2: 1}}, (3, 3), QQ)
|
||
|
||
"""
|
||
if isinstance(shape, int):
|
||
shape = (shape, shape)
|
||
return cls.from_rep(SDM.eye(shape, domain))
|
||
|
||
@classmethod
|
||
def diag(cls, diagonal, domain, shape=None):
|
||
r"""
|
||
Return diagonal matrix with entries from ``diagonal``.
|
||
|
||
Examples
|
||
========
|
||
|
||
>>> from sympy.polys.matrices import DomainMatrix
|
||
>>> from sympy import ZZ
|
||
>>> DomainMatrix.diag([ZZ(5), ZZ(6)], ZZ)
|
||
DomainMatrix({0: {0: 5}, 1: {1: 6}}, (2, 2), ZZ)
|
||
|
||
"""
|
||
if shape is None:
|
||
N = len(diagonal)
|
||
shape = (N, N)
|
||
return cls.from_rep(SDM.diag(diagonal, domain, shape))
|
||
|
||
@classmethod
|
||
def zeros(cls, shape, domain, *, fmt='sparse'):
|
||
"""Returns a zero DomainMatrix of size shape, belonging to the specified domain
|
||
|
||
Examples
|
||
========
|
||
|
||
>>> from sympy.polys.matrices import DomainMatrix
|
||
>>> from sympy import QQ
|
||
>>> DomainMatrix.zeros((2, 3), QQ)
|
||
DomainMatrix({}, (2, 3), QQ)
|
||
|
||
"""
|
||
return cls.from_rep(SDM.zeros(shape, domain))
|
||
|
||
@classmethod
|
||
def ones(cls, shape, domain):
|
||
"""Returns a DomainMatrix of 1s, of size shape, belonging to the specified domain
|
||
|
||
Examples
|
||
========
|
||
|
||
>>> from sympy.polys.matrices import DomainMatrix
|
||
>>> from sympy import QQ
|
||
>>> DomainMatrix.ones((2,3), QQ)
|
||
DomainMatrix([[1, 1, 1], [1, 1, 1]], (2, 3), QQ)
|
||
|
||
"""
|
||
return cls.from_rep(DDM.ones(shape, domain).to_dfm_or_ddm())
|
||
|
||
def __eq__(A, B):
|
||
r"""
|
||
Checks for two DomainMatrix matrices to be equal or not
|
||
|
||
Parameters
|
||
==========
|
||
|
||
A, B: DomainMatrix
|
||
to check equality
|
||
|
||
Returns
|
||
=======
|
||
|
||
Boolean
|
||
True for equal, else False
|
||
|
||
Raises
|
||
======
|
||
|
||
NotImplementedError
|
||
If B is not a DomainMatrix
|
||
|
||
Examples
|
||
========
|
||
|
||
>>> from sympy import ZZ
|
||
>>> from sympy.polys.matrices import DomainMatrix
|
||
>>> A = DomainMatrix([
|
||
... [ZZ(1), ZZ(2)],
|
||
... [ZZ(3), ZZ(4)]], (2, 2), ZZ)
|
||
>>> B = DomainMatrix([
|
||
... [ZZ(1), ZZ(1)],
|
||
... [ZZ(0), ZZ(1)]], (2, 2), ZZ)
|
||
>>> A.__eq__(A)
|
||
True
|
||
>>> A.__eq__(B)
|
||
False
|
||
|
||
"""
|
||
if not isinstance(A, type(B)):
|
||
return NotImplemented
|
||
return A.domain == B.domain and A.rep == B.rep
|
||
|
||
def unify_eq(A, B):
|
||
if A.shape != B.shape:
|
||
return False
|
||
if A.domain != B.domain:
|
||
A, B = A.unify(B)
|
||
return A == B
|
||
|
||
def lll(A, delta=QQ(3, 4)):
|
||
"""
|
||
Performs the Lenstra–Lenstra–Lovász (LLL) basis reduction algorithm.
|
||
See [1]_ and [2]_.
|
||
|
||
Parameters
|
||
==========
|
||
|
||
delta : QQ, optional
|
||
The Lovász parameter. Must be in the interval (0.25, 1), with larger
|
||
values producing a more reduced basis. The default is 0.75 for
|
||
historical reasons.
|
||
|
||
Returns
|
||
=======
|
||
|
||
The reduced basis as a DomainMatrix over ZZ.
|
||
|
||
Throws
|
||
======
|
||
|
||
DMValueError: if delta is not in the range (0.25, 1)
|
||
DMShapeError: if the matrix is not of shape (m, n) with m <= n
|
||
DMDomainError: if the matrix domain is not ZZ
|
||
DMRankError: if the matrix contains linearly dependent rows
|
||
|
||
Examples
|
||
========
|
||
|
||
>>> from sympy.polys.domains import ZZ, QQ
|
||
>>> from sympy.polys.matrices import DM
|
||
>>> x = DM([[1, 0, 0, 0, -20160],
|
||
... [0, 1, 0, 0, 33768],
|
||
... [0, 0, 1, 0, 39578],
|
||
... [0, 0, 0, 1, 47757]], ZZ)
|
||
>>> y = DM([[10, -3, -2, 8, -4],
|
||
... [3, -9, 8, 1, -11],
|
||
... [-3, 13, -9, -3, -9],
|
||
... [-12, -7, -11, 9, -1]], ZZ)
|
||
>>> assert x.lll(delta=QQ(5, 6)) == y
|
||
|
||
Notes
|
||
=====
|
||
|
||
The implementation is derived from the Maple code given in Figures 4.3
|
||
and 4.4 of [3]_ (pp.68-69). It uses the efficient method of only calculating
|
||
state updates as they are required.
|
||
|
||
See also
|
||
========
|
||
|
||
lll_transform
|
||
|
||
References
|
||
==========
|
||
|
||
.. [1] https://en.wikipedia.org/wiki/Lenstra%E2%80%93Lenstra%E2%80%93Lov%C3%A1sz_lattice_basis_reduction_algorithm
|
||
.. [2] https://web.archive.org/web/20221029115428/https://web.cs.elte.hu/~lovasz/scans/lll.pdf
|
||
.. [3] Murray R. Bremner, "Lattice Basis Reduction: An Introduction to the LLL Algorithm and Its Applications"
|
||
|
||
"""
|
||
return DomainMatrix.from_rep(A.rep.lll(delta=delta))
|
||
|
||
def lll_transform(A, delta=QQ(3, 4)):
|
||
"""
|
||
Performs the Lenstra–Lenstra–Lovász (LLL) basis reduction algorithm
|
||
and returns the reduced basis and transformation matrix.
|
||
|
||
Explanation
|
||
===========
|
||
|
||
Parameters, algorithm and basis are the same as for :meth:`lll` except that
|
||
the return value is a tuple `(B, T)` with `B` the reduced basis and
|
||
`T` a transformation matrix. The original basis `A` is transformed to
|
||
`B` with `T*A == B`. If only `B` is needed then :meth:`lll` should be
|
||
used as it is a little faster.
|
||
|
||
Examples
|
||
========
|
||
|
||
>>> from sympy.polys.domains import ZZ, QQ
|
||
>>> from sympy.polys.matrices import DM
|
||
>>> X = DM([[1, 0, 0, 0, -20160],
|
||
... [0, 1, 0, 0, 33768],
|
||
... [0, 0, 1, 0, 39578],
|
||
... [0, 0, 0, 1, 47757]], ZZ)
|
||
>>> B, T = X.lll_transform(delta=QQ(5, 6))
|
||
>>> T * X == B
|
||
True
|
||
|
||
See also
|
||
========
|
||
|
||
lll
|
||
|
||
"""
|
||
reduced, transform = A.rep.lll_transform(delta=delta)
|
||
return DomainMatrix.from_rep(reduced), DomainMatrix.from_rep(transform)
|
||
|
||
|
||
def _collect_factors(factors_list):
|
||
"""
|
||
Collect repeating factors and sort.
|
||
|
||
>>> from sympy.polys.matrices.domainmatrix import _collect_factors
|
||
>>> _collect_factors([([1, 2], 2), ([1, 4], 3), ([1, 2], 5)])
|
||
[([1, 4], 3), ([1, 2], 7)]
|
||
"""
|
||
factors = Counter()
|
||
for factor, exponent in factors_list:
|
||
factors[tuple(factor)] += exponent
|
||
|
||
factors_list = [(list(f), e) for f, e in factors.items()]
|
||
|
||
return _sort_factors(factors_list)
|