Spaces:
Running
Running
""" | |
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 | |
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 | |
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) | |
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) | |
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) | |
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) | |
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() | |
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() | |
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() | |
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) | |
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() | |
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() | |
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() | |
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)] | |
def is_zero_matrix(self): | |
return self.rep.is_zero_matrix() | |
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() | |
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() | |
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() | |
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 | |
>>> 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 | |
>>> 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() | |
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)) | |
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)) | |
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)) | |
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) | |