add read me
This commit is contained in:
@@ -0,0 +1,15 @@
|
||||
"""
|
||||
|
||||
sympy.polys.matrices package.
|
||||
|
||||
The main export from this package is the DomainMatrix class which is a
|
||||
lower-level implementation of matrices based on the polys Domains. This
|
||||
implementation is typically a lot faster than SymPy's standard Matrix class
|
||||
but is a work in progress and is still experimental.
|
||||
|
||||
"""
|
||||
from .domainmatrix import DomainMatrix, DM
|
||||
|
||||
__all__ = [
|
||||
'DomainMatrix', 'DM',
|
||||
]
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
951
venv/lib/python3.12/site-packages/sympy/polys/matrices/_dfm.py
Normal file
951
venv/lib/python3.12/site-packages/sympy/polys/matrices/_dfm.py
Normal file
@@ -0,0 +1,951 @@
|
||||
#
|
||||
# sympy.polys.matrices.dfm
|
||||
#
|
||||
# This modules defines the DFM class which is a wrapper for dense flint
|
||||
# matrices as found in python-flint.
|
||||
#
|
||||
# As of python-flint 0.4.1 matrices over the following domains can be supported
|
||||
# by python-flint:
|
||||
#
|
||||
# ZZ: flint.fmpz_mat
|
||||
# QQ: flint.fmpq_mat
|
||||
# GF(p): flint.nmod_mat (p prime and p < ~2**62)
|
||||
#
|
||||
# The underlying flint library has many more domains, but these are not yet
|
||||
# supported by python-flint.
|
||||
#
|
||||
# The DFM class is a wrapper for the flint matrices and provides a common
|
||||
# interface for all supported domains that is interchangeable with the DDM
|
||||
# and SDM classes so that DomainMatrix can be used with any as its internal
|
||||
# matrix representation.
|
||||
#
|
||||
|
||||
# TODO:
|
||||
#
|
||||
# Implement the following methods that are provided by python-flint:
|
||||
#
|
||||
# - hnf (Hermite normal form)
|
||||
# - snf (Smith normal form)
|
||||
# - minpoly
|
||||
# - is_hnf
|
||||
# - is_snf
|
||||
# - rank
|
||||
#
|
||||
# The other types DDM and SDM do not have these methods and the algorithms
|
||||
# for hnf, snf and rank are already implemented. Algorithms for minpoly,
|
||||
# is_hnf and is_snf would need to be added.
|
||||
#
|
||||
# Add more methods to python-flint to expose more of Flint's functionality
|
||||
# and also to make some of the above methods simpler or more efficient e.g.
|
||||
# slicing, fancy indexing etc.
|
||||
|
||||
from sympy.external.gmpy import GROUND_TYPES
|
||||
from sympy.external.importtools import import_module
|
||||
from sympy.utilities.decorator import doctest_depends_on
|
||||
|
||||
from sympy.polys.domains import ZZ, QQ
|
||||
|
||||
from .exceptions import (
|
||||
DMBadInputError,
|
||||
DMDomainError,
|
||||
DMNonSquareMatrixError,
|
||||
DMNonInvertibleMatrixError,
|
||||
DMRankError,
|
||||
DMShapeError,
|
||||
DMValueError,
|
||||
)
|
||||
|
||||
|
||||
if GROUND_TYPES != 'flint':
|
||||
__doctest_skip__ = ['*']
|
||||
|
||||
|
||||
flint = import_module('flint')
|
||||
|
||||
|
||||
__all__ = ['DFM']
|
||||
|
||||
|
||||
@doctest_depends_on(ground_types=['flint'])
|
||||
class DFM:
|
||||
"""
|
||||
Dense FLINT matrix. This class is a wrapper for matrices from python-flint.
|
||||
|
||||
>>> from sympy.polys.domains import ZZ
|
||||
>>> from sympy.polys.matrices.dfm import DFM
|
||||
>>> dfm = DFM([[ZZ(1), ZZ(2)], [ZZ(3), ZZ(4)]], (2, 2), ZZ)
|
||||
>>> dfm
|
||||
[[1, 2], [3, 4]]
|
||||
>>> dfm.rep
|
||||
[1, 2]
|
||||
[3, 4]
|
||||
>>> type(dfm.rep) # doctest: +SKIP
|
||||
<class 'flint._flint.fmpz_mat'>
|
||||
|
||||
Usually, the DFM class is not instantiated directly, but is created as the
|
||||
internal representation of :class:`~.DomainMatrix`. When
|
||||
`SYMPY_GROUND_TYPES` is set to `flint` and `python-flint` is installed, the
|
||||
:class:`DFM` class is used automatically as the internal representation of
|
||||
:class:`~.DomainMatrix` in dense format if the domain is supported by
|
||||
python-flint.
|
||||
|
||||
>>> from sympy.polys.matrices.domainmatrix import DM
|
||||
>>> dM = DM([[1, 2], [3, 4]], ZZ)
|
||||
>>> dM.rep
|
||||
[[1, 2], [3, 4]]
|
||||
|
||||
A :class:`~.DomainMatrix` can be converted to :class:`DFM` by calling the
|
||||
:meth:`to_dfm` method:
|
||||
|
||||
>>> dM.to_dfm()
|
||||
[[1, 2], [3, 4]]
|
||||
|
||||
"""
|
||||
|
||||
fmt = 'dense'
|
||||
is_DFM = True
|
||||
is_DDM = False
|
||||
|
||||
def __new__(cls, rowslist, shape, domain):
|
||||
"""Construct from a nested list."""
|
||||
flint_mat = cls._get_flint_func(domain)
|
||||
|
||||
if 0 not in shape:
|
||||
try:
|
||||
rep = flint_mat(rowslist)
|
||||
except (ValueError, TypeError):
|
||||
raise DMBadInputError(f"Input should be a list of list of {domain}")
|
||||
else:
|
||||
rep = flint_mat(*shape)
|
||||
|
||||
return cls._new(rep, shape, domain)
|
||||
|
||||
@classmethod
|
||||
def _new(cls, rep, shape, domain):
|
||||
"""Internal constructor from a flint matrix."""
|
||||
cls._check(rep, shape, domain)
|
||||
obj = object.__new__(cls)
|
||||
obj.rep = rep
|
||||
obj.shape = obj.rows, obj.cols = shape
|
||||
obj.domain = domain
|
||||
return obj
|
||||
|
||||
def _new_rep(self, rep):
|
||||
"""Create a new DFM with the same shape and domain but a new rep."""
|
||||
return self._new(rep, self.shape, self.domain)
|
||||
|
||||
@classmethod
|
||||
def _check(cls, rep, shape, domain):
|
||||
repshape = (rep.nrows(), rep.ncols())
|
||||
if repshape != shape:
|
||||
raise DMBadInputError("Shape of rep does not match shape of DFM")
|
||||
if domain == ZZ and not isinstance(rep, flint.fmpz_mat):
|
||||
raise RuntimeError("Rep is not a flint.fmpz_mat")
|
||||
elif domain == QQ and not isinstance(rep, flint.fmpq_mat):
|
||||
raise RuntimeError("Rep is not a flint.fmpq_mat")
|
||||
elif domain.is_FF and not isinstance(rep, (flint.fmpz_mod_mat, flint.nmod_mat)):
|
||||
raise RuntimeError("Rep is not a flint.fmpz_mod_mat or flint.nmod_mat")
|
||||
elif domain not in (ZZ, QQ) and not domain.is_FF:
|
||||
raise NotImplementedError("Only ZZ and QQ are supported by DFM")
|
||||
|
||||
@classmethod
|
||||
def _supports_domain(cls, domain):
|
||||
"""Return True if the given domain is supported by DFM."""
|
||||
return domain in (ZZ, QQ) or domain.is_FF and domain._is_flint
|
||||
|
||||
@classmethod
|
||||
def _get_flint_func(cls, domain):
|
||||
"""Return the flint matrix class for the given domain."""
|
||||
if domain == ZZ:
|
||||
return flint.fmpz_mat
|
||||
elif domain == QQ:
|
||||
return flint.fmpq_mat
|
||||
elif domain.is_FF:
|
||||
c = domain.characteristic()
|
||||
if isinstance(domain.one, flint.nmod):
|
||||
_cls = flint.nmod_mat
|
||||
def _func(*e):
|
||||
if len(e) == 1 and isinstance(e[0], flint.nmod_mat):
|
||||
return _cls(e[0])
|
||||
else:
|
||||
return _cls(*e, c)
|
||||
else:
|
||||
m = flint.fmpz_mod_ctx(c)
|
||||
_func = lambda *e: flint.fmpz_mod_mat(*e, m)
|
||||
return _func
|
||||
else:
|
||||
raise NotImplementedError("Only ZZ and QQ are supported by DFM")
|
||||
|
||||
@property
|
||||
def _func(self):
|
||||
"""Callable to create a flint matrix of the same domain."""
|
||||
return self._get_flint_func(self.domain)
|
||||
|
||||
def __str__(self):
|
||||
"""Return ``str(self)``."""
|
||||
return str(self.to_ddm())
|
||||
|
||||
def __repr__(self):
|
||||
"""Return ``repr(self)``."""
|
||||
return f'DFM{repr(self.to_ddm())[3:]}'
|
||||
|
||||
def __eq__(self, other):
|
||||
"""Return ``self == other``."""
|
||||
if not isinstance(other, DFM):
|
||||
return NotImplemented
|
||||
# Compare domains first because we do *not* want matrices with
|
||||
# different domains to be equal but e.g. a flint fmpz_mat and fmpq_mat
|
||||
# with the same entries will compare equal.
|
||||
return self.domain == other.domain and self.rep == other.rep
|
||||
|
||||
@classmethod
|
||||
def from_list(cls, rowslist, shape, domain):
|
||||
"""Construct from a nested list."""
|
||||
return cls(rowslist, shape, domain)
|
||||
|
||||
def to_list(self):
|
||||
"""Convert to a nested list."""
|
||||
return self.rep.tolist()
|
||||
|
||||
def copy(self):
|
||||
"""Return a copy of self."""
|
||||
return self._new_rep(self._func(self.rep))
|
||||
|
||||
def to_ddm(self):
|
||||
"""Convert to a DDM."""
|
||||
return DDM.from_list(self.to_list(), self.shape, self.domain)
|
||||
|
||||
def to_sdm(self):
|
||||
"""Convert to a SDM."""
|
||||
return SDM.from_list(self.to_list(), self.shape, self.domain)
|
||||
|
||||
def to_dfm(self):
|
||||
"""Return self."""
|
||||
return self
|
||||
|
||||
def to_dfm_or_ddm(self):
|
||||
"""
|
||||
Convert to a :class:`DFM`.
|
||||
|
||||
This :class:`DFM` method exists to parallel the :class:`~.DDM` and
|
||||
:class:`~.SDM` methods. For :class:`DFM` it will always return self.
|
||||
|
||||
See Also
|
||||
========
|
||||
|
||||
to_ddm
|
||||
to_sdm
|
||||
sympy.polys.matrices.domainmatrix.DomainMatrix.to_dfm_or_ddm
|
||||
"""
|
||||
return self
|
||||
|
||||
@classmethod
|
||||
def from_ddm(cls, ddm):
|
||||
"""Convert from a DDM."""
|
||||
return cls.from_list(ddm.to_list(), ddm.shape, ddm.domain)
|
||||
|
||||
@classmethod
|
||||
def from_list_flat(cls, elements, shape, domain):
|
||||
"""Inverse of :meth:`to_list_flat`."""
|
||||
func = cls._get_flint_func(domain)
|
||||
try:
|
||||
rep = func(*shape, elements)
|
||||
except ValueError:
|
||||
raise DMBadInputError(f"Incorrect number of elements for shape {shape}")
|
||||
except TypeError:
|
||||
raise DMBadInputError(f"Input should be a list of {domain}")
|
||||
return cls(rep, shape, domain)
|
||||
|
||||
def to_list_flat(self):
|
||||
"""Convert to a flat list."""
|
||||
return self.rep.entries()
|
||||
|
||||
def to_flat_nz(self):
|
||||
"""Convert to a flat list of non-zeros."""
|
||||
return self.to_ddm().to_flat_nz()
|
||||
|
||||
@classmethod
|
||||
def from_flat_nz(cls, elements, data, domain):
|
||||
"""Inverse of :meth:`to_flat_nz`."""
|
||||
return DDM.from_flat_nz(elements, data, domain).to_dfm()
|
||||
|
||||
def to_dod(self):
|
||||
"""Convert to a DOD."""
|
||||
return self.to_ddm().to_dod()
|
||||
|
||||
@classmethod
|
||||
def from_dod(cls, dod, shape, domain):
|
||||
"""Inverse of :meth:`to_dod`."""
|
||||
return DDM.from_dod(dod, shape, domain).to_dfm()
|
||||
|
||||
def to_dok(self):
|
||||
"""Convert to a DOK."""
|
||||
return self.to_ddm().to_dok()
|
||||
|
||||
@classmethod
|
||||
def from_dok(cls, dok, shape, domain):
|
||||
"""Inverse of :math:`to_dod`."""
|
||||
return DDM.from_dok(dok, shape, domain).to_dfm()
|
||||
|
||||
def iter_values(self):
|
||||
"""Iterate over the non-zero values of the matrix."""
|
||||
m, n = self.shape
|
||||
rep = self.rep
|
||||
for i in range(m):
|
||||
for j in range(n):
|
||||
repij = rep[i, j]
|
||||
if repij:
|
||||
yield rep[i, j]
|
||||
|
||||
def iter_items(self):
|
||||
"""Iterate over indices and values of nonzero elements of the matrix."""
|
||||
m, n = self.shape
|
||||
rep = self.rep
|
||||
for i in range(m):
|
||||
for j in range(n):
|
||||
repij = rep[i, j]
|
||||
if repij:
|
||||
yield ((i, j), repij)
|
||||
|
||||
def convert_to(self, domain):
|
||||
"""Convert to a new domain."""
|
||||
if domain == self.domain:
|
||||
return self.copy()
|
||||
elif domain == QQ and self.domain == ZZ:
|
||||
return self._new(flint.fmpq_mat(self.rep), self.shape, domain)
|
||||
elif self._supports_domain(domain):
|
||||
# XXX: Use more efficient conversions when possible.
|
||||
return self.to_ddm().convert_to(domain).to_dfm()
|
||||
else:
|
||||
# It is the callers responsibility to convert to DDM before calling
|
||||
# this method if the domain is not supported by DFM.
|
||||
raise NotImplementedError("Only ZZ and QQ are supported by DFM")
|
||||
|
||||
def getitem(self, i, j):
|
||||
"""Get the ``(i, j)``-th entry."""
|
||||
# XXX: flint matrices do not support negative indices
|
||||
# XXX: They also raise ValueError instead of IndexError
|
||||
m, n = self.shape
|
||||
if i < 0:
|
||||
i += m
|
||||
if j < 0:
|
||||
j += n
|
||||
try:
|
||||
return self.rep[i, j]
|
||||
except ValueError:
|
||||
raise IndexError(f"Invalid indices ({i}, {j}) for Matrix of shape {self.shape}")
|
||||
|
||||
def setitem(self, i, j, value):
|
||||
"""Set the ``(i, j)``-th entry."""
|
||||
# XXX: flint matrices do not support negative indices
|
||||
# XXX: They also raise ValueError instead of IndexError
|
||||
m, n = self.shape
|
||||
if i < 0:
|
||||
i += m
|
||||
if j < 0:
|
||||
j += n
|
||||
try:
|
||||
self.rep[i, j] = value
|
||||
except ValueError:
|
||||
raise IndexError(f"Invalid indices ({i}, {j}) for Matrix of shape {self.shape}")
|
||||
|
||||
def _extract(self, i_indices, j_indices):
|
||||
"""Extract a submatrix with no checking."""
|
||||
# Indices must be positive and in range.
|
||||
M = self.rep
|
||||
lol = [[M[i, j] for j in j_indices] for i in i_indices]
|
||||
shape = (len(i_indices), len(j_indices))
|
||||
return self.from_list(lol, shape, self.domain)
|
||||
|
||||
def extract(self, rowslist, colslist):
|
||||
"""Extract a submatrix."""
|
||||
# XXX: flint matrices do not support fancy indexing or negative indices
|
||||
#
|
||||
# Check and convert negative indices before calling _extract.
|
||||
m, n = self.shape
|
||||
|
||||
new_rows = []
|
||||
new_cols = []
|
||||
|
||||
for i in rowslist:
|
||||
if i < 0:
|
||||
i_pos = i + m
|
||||
else:
|
||||
i_pos = i
|
||||
if not 0 <= i_pos < m:
|
||||
raise IndexError(f"Invalid row index {i} for Matrix of shape {self.shape}")
|
||||
new_rows.append(i_pos)
|
||||
|
||||
for j in colslist:
|
||||
if j < 0:
|
||||
j_pos = j + n
|
||||
else:
|
||||
j_pos = j
|
||||
if not 0 <= j_pos < n:
|
||||
raise IndexError(f"Invalid column index {j} for Matrix of shape {self.shape}")
|
||||
new_cols.append(j_pos)
|
||||
|
||||
return self._extract(new_rows, new_cols)
|
||||
|
||||
def extract_slice(self, rowslice, colslice):
|
||||
"""Slice a DFM."""
|
||||
# XXX: flint matrices do not support slicing
|
||||
m, n = self.shape
|
||||
i_indices = range(m)[rowslice]
|
||||
j_indices = range(n)[colslice]
|
||||
return self._extract(i_indices, j_indices)
|
||||
|
||||
def neg(self):
|
||||
"""Negate a DFM matrix."""
|
||||
return self._new_rep(-self.rep)
|
||||
|
||||
def add(self, other):
|
||||
"""Add two DFM matrices."""
|
||||
return self._new_rep(self.rep + other.rep)
|
||||
|
||||
def sub(self, other):
|
||||
"""Subtract two DFM matrices."""
|
||||
return self._new_rep(self.rep - other.rep)
|
||||
|
||||
def mul(self, other):
|
||||
"""Multiply a DFM matrix from the right by a scalar."""
|
||||
return self._new_rep(self.rep * other)
|
||||
|
||||
def rmul(self, other):
|
||||
"""Multiply a DFM matrix from the left by a scalar."""
|
||||
return self._new_rep(other * self.rep)
|
||||
|
||||
def mul_elementwise(self, other):
|
||||
"""Elementwise multiplication of two DFM matrices."""
|
||||
# XXX: flint matrices do not support elementwise multiplication
|
||||
return self.to_ddm().mul_elementwise(other.to_ddm()).to_dfm()
|
||||
|
||||
def matmul(self, other):
|
||||
"""Multiply two DFM matrices."""
|
||||
shape = (self.rows, other.cols)
|
||||
return self._new(self.rep * other.rep, shape, self.domain)
|
||||
|
||||
# XXX: For the most part DomainMatrix does not expect DDM, SDM, or DFM to
|
||||
# have arithmetic operators defined. The only exception is negation.
|
||||
# Perhaps that should be removed.
|
||||
|
||||
def __neg__(self):
|
||||
"""Negate a DFM matrix."""
|
||||
return self.neg()
|
||||
|
||||
@classmethod
|
||||
def zeros(cls, shape, domain):
|
||||
"""Return a zero DFM matrix."""
|
||||
func = cls._get_flint_func(domain)
|
||||
return cls._new(func(*shape), shape, domain)
|
||||
|
||||
# XXX: flint matrices do not have anything like ones or eye
|
||||
# In the methods below we convert to DDM and then back to DFM which is
|
||||
# probably about as efficient as implementing these methods directly.
|
||||
|
||||
@classmethod
|
||||
def ones(cls, shape, domain):
|
||||
"""Return a one DFM matrix."""
|
||||
# XXX: flint matrices do not have anything like ones
|
||||
return DDM.ones(shape, domain).to_dfm()
|
||||
|
||||
@classmethod
|
||||
def eye(cls, n, domain):
|
||||
"""Return the identity matrix of size n."""
|
||||
# XXX: flint matrices do not have anything like eye
|
||||
return DDM.eye(n, domain).to_dfm()
|
||||
|
||||
@classmethod
|
||||
def diag(cls, elements, domain):
|
||||
"""Return a diagonal matrix."""
|
||||
return DDM.diag(elements, domain).to_dfm()
|
||||
|
||||
def applyfunc(self, func, domain):
|
||||
"""Apply a function to each entry of a DFM matrix."""
|
||||
return self.to_ddm().applyfunc(func, domain).to_dfm()
|
||||
|
||||
def transpose(self):
|
||||
"""Transpose a DFM matrix."""
|
||||
return self._new(self.rep.transpose(), (self.cols, self.rows), self.domain)
|
||||
|
||||
def hstack(self, *others):
|
||||
"""Horizontally stack matrices."""
|
||||
return self.to_ddm().hstack(*[o.to_ddm() for o in others]).to_dfm()
|
||||
|
||||
def vstack(self, *others):
|
||||
"""Vertically stack matrices."""
|
||||
return self.to_ddm().vstack(*[o.to_ddm() for o in others]).to_dfm()
|
||||
|
||||
def diagonal(self):
|
||||
"""Return the diagonal of a DFM matrix."""
|
||||
M = self.rep
|
||||
m, n = self.shape
|
||||
return [M[i, i] for i in range(min(m, n))]
|
||||
|
||||
def is_upper(self):
|
||||
"""Return ``True`` if the matrix is upper triangular."""
|
||||
M = self.rep
|
||||
for i in range(self.rows):
|
||||
for j in range(min(i, self.cols)):
|
||||
if M[i, j]:
|
||||
return False
|
||||
return True
|
||||
|
||||
def is_lower(self):
|
||||
"""Return ``True`` if the matrix is lower triangular."""
|
||||
M = self.rep
|
||||
for i in range(self.rows):
|
||||
for j in range(i + 1, self.cols):
|
||||
if M[i, j]:
|
||||
return False
|
||||
return True
|
||||
|
||||
def is_diagonal(self):
|
||||
"""Return ``True`` if the matrix is diagonal."""
|
||||
return self.is_upper() and self.is_lower()
|
||||
|
||||
def is_zero_matrix(self):
|
||||
"""Return ``True`` if the matrix is the zero matrix."""
|
||||
M = self.rep
|
||||
for i in range(self.rows):
|
||||
for j in range(self.cols):
|
||||
if M[i, j]:
|
||||
return False
|
||||
return True
|
||||
|
||||
def nnz(self):
|
||||
"""Return the number of non-zero elements in the matrix."""
|
||||
return self.to_ddm().nnz()
|
||||
|
||||
def scc(self):
|
||||
"""Return the strongly connected components of the matrix."""
|
||||
return self.to_ddm().scc()
|
||||
|
||||
@doctest_depends_on(ground_types='flint')
|
||||
def det(self):
|
||||
"""
|
||||
Compute the determinant of the matrix using FLINT.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy import Matrix
|
||||
>>> M = Matrix([[1, 2], [3, 4]])
|
||||
>>> dfm = M.to_DM().to_dfm()
|
||||
>>> dfm
|
||||
[[1, 2], [3, 4]]
|
||||
>>> dfm.det()
|
||||
-2
|
||||
|
||||
Notes
|
||||
=====
|
||||
|
||||
Calls the ``.det()`` method of the underlying FLINT matrix.
|
||||
|
||||
For :ref:`ZZ` or :ref:`QQ` this calls ``fmpz_mat_det`` or
|
||||
``fmpq_mat_det`` respectively.
|
||||
|
||||
At the time of writing the implementation of ``fmpz_mat_det`` uses one
|
||||
of several algorithms depending on the size of the matrix and bit size
|
||||
of the entries. The algorithms used are:
|
||||
|
||||
- Cofactor for very small (up to 4x4) matrices.
|
||||
- Bareiss for small (up to 25x25) matrices.
|
||||
- Modular algorithms for larger matrices (up to 60x60) or for larger
|
||||
matrices with large bit sizes.
|
||||
- Modular "accelerated" for larger matrices (60x60 upwards) if the bit
|
||||
size is smaller than the dimensions of the matrix.
|
||||
|
||||
The implementation of ``fmpq_mat_det`` clears denominators from each
|
||||
row (not the whole matrix) and then calls ``fmpz_mat_det`` and divides
|
||||
by the product of the denominators.
|
||||
|
||||
See Also
|
||||
========
|
||||
|
||||
sympy.polys.matrices.domainmatrix.DomainMatrix.det
|
||||
Higher level interface to compute the determinant of a matrix.
|
||||
"""
|
||||
# XXX: At least the first three algorithms described above should also
|
||||
# be implemented in the pure Python DDM and SDM classes which at the
|
||||
# time of writng just use Bareiss for all matrices and domains.
|
||||
# Probably in Python the thresholds would be different though.
|
||||
return self.rep.det()
|
||||
|
||||
@doctest_depends_on(ground_types='flint')
|
||||
def charpoly(self):
|
||||
"""
|
||||
Compute the characteristic polynomial of the matrix using FLINT.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy import Matrix
|
||||
>>> M = Matrix([[1, 2], [3, 4]])
|
||||
>>> dfm = M.to_DM().to_dfm() # need ground types = 'flint'
|
||||
>>> dfm
|
||||
[[1, 2], [3, 4]]
|
||||
>>> dfm.charpoly()
|
||||
[1, -5, -2]
|
||||
|
||||
Notes
|
||||
=====
|
||||
|
||||
Calls the ``.charpoly()`` method of the underlying FLINT matrix.
|
||||
|
||||
For :ref:`ZZ` or :ref:`QQ` this calls ``fmpz_mat_charpoly`` or
|
||||
``fmpq_mat_charpoly`` respectively.
|
||||
|
||||
At the time of writing the implementation of ``fmpq_mat_charpoly``
|
||||
clears a denominator from the whole matrix and then calls
|
||||
``fmpz_mat_charpoly``. The coefficients of the characteristic
|
||||
polynomial are then multiplied by powers of the denominator.
|
||||
|
||||
The ``fmpz_mat_charpoly`` method uses a modular algorithm with CRT
|
||||
reconstruction. The modular algorithm uses ``nmod_mat_charpoly`` which
|
||||
uses Berkowitz for small matrices and non-prime moduli or otherwise
|
||||
the Danilevsky method.
|
||||
|
||||
See Also
|
||||
========
|
||||
|
||||
sympy.polys.matrices.domainmatrix.DomainMatrix.charpoly
|
||||
Higher level interface to compute the characteristic polynomial of
|
||||
a matrix.
|
||||
"""
|
||||
# FLINT polynomial coefficients are in reverse order compared to SymPy.
|
||||
return self.rep.charpoly().coeffs()[::-1]
|
||||
|
||||
@doctest_depends_on(ground_types='flint')
|
||||
def inv(self):
|
||||
"""
|
||||
Compute the inverse of a matrix using FLINT.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy import Matrix, QQ
|
||||
>>> M = Matrix([[1, 2], [3, 4]])
|
||||
>>> dfm = M.to_DM().to_dfm().convert_to(QQ)
|
||||
>>> dfm
|
||||
[[1, 2], [3, 4]]
|
||||
>>> dfm.inv()
|
||||
[[-2, 1], [3/2, -1/2]]
|
||||
>>> dfm.matmul(dfm.inv())
|
||||
[[1, 0], [0, 1]]
|
||||
|
||||
Notes
|
||||
=====
|
||||
|
||||
Calls the ``.inv()`` method of the underlying FLINT matrix.
|
||||
|
||||
For now this will raise an error if the domain is :ref:`ZZ` but will
|
||||
use the FLINT method for :ref:`QQ`.
|
||||
|
||||
The FLINT methods for :ref:`ZZ` and :ref:`QQ` are ``fmpz_mat_inv`` and
|
||||
``fmpq_mat_inv`` respectively. The ``fmpz_mat_inv`` method computes an
|
||||
inverse with denominator. This is implemented by calling
|
||||
``fmpz_mat_solve`` (see notes in :meth:`lu_solve` about the algorithm).
|
||||
|
||||
The ``fmpq_mat_inv`` method clears denominators from each row and then
|
||||
multiplies those into the rhs identity matrix before calling
|
||||
``fmpz_mat_solve``.
|
||||
|
||||
See Also
|
||||
========
|
||||
|
||||
sympy.polys.matrices.domainmatrix.DomainMatrix.inv
|
||||
Higher level method for computing the inverse of a matrix.
|
||||
"""
|
||||
# TODO: Implement similar algorithms for DDM and SDM.
|
||||
#
|
||||
# XXX: The flint fmpz_mat and fmpq_mat inv methods both return fmpq_mat
|
||||
# by default. The fmpz_mat method has an optional argument to return
|
||||
# fmpz_mat instead for unimodular matrices.
|
||||
#
|
||||
# The convention in DomainMatrix is to raise an error if the matrix is
|
||||
# not over a field regardless of whether the matrix is invertible over
|
||||
# its domain or over any associated field. Maybe DomainMatrix.inv
|
||||
# should be changed to always return a matrix over an associated field
|
||||
# except with a unimodular argument for returning an inverse over a
|
||||
# ring if possible.
|
||||
#
|
||||
# For now we follow the existing DomainMatrix convention...
|
||||
K = self.domain
|
||||
m, n = self.shape
|
||||
|
||||
if m != n:
|
||||
raise DMNonSquareMatrixError("cannot invert a non-square matrix")
|
||||
|
||||
if K == ZZ:
|
||||
raise DMDomainError("field expected, got %s" % K)
|
||||
elif K == QQ or K.is_FF:
|
||||
try:
|
||||
return self._new_rep(self.rep.inv())
|
||||
except ZeroDivisionError:
|
||||
raise DMNonInvertibleMatrixError("matrix is not invertible")
|
||||
else:
|
||||
# If more domains are added for DFM then we will need to consider
|
||||
# what happens here.
|
||||
raise NotImplementedError("DFM.inv() is not implemented for %s" % K)
|
||||
|
||||
def lu(self):
|
||||
"""Return the LU decomposition of the matrix."""
|
||||
L, U, swaps = self.to_ddm().lu()
|
||||
return L.to_dfm(), U.to_dfm(), swaps
|
||||
|
||||
def qr(self):
|
||||
"""Return the QR decomposition of the matrix."""
|
||||
Q, R = self.to_ddm().qr()
|
||||
return Q.to_dfm(), R.to_dfm()
|
||||
|
||||
# XXX: The lu_solve function should be renamed to solve. Whether or not it
|
||||
# uses an LU decomposition is an implementation detail. A method called
|
||||
# lu_solve would make sense for a situation in which an LU decomposition is
|
||||
# reused several times to solve with different rhs but that would imply a
|
||||
# different call signature.
|
||||
#
|
||||
# The underlying python-flint method has an algorithm= argument so we could
|
||||
# use that and have e.g. solve_lu and solve_modular or perhaps also a
|
||||
# method= argument to choose between the two. Flint itself has more
|
||||
# possible algorithms to choose from than are exposed by python-flint.
|
||||
|
||||
@doctest_depends_on(ground_types='flint')
|
||||
def lu_solve(self, rhs):
|
||||
"""
|
||||
Solve a matrix equation using FLINT.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy import Matrix, QQ
|
||||
>>> M = Matrix([[1, 2], [3, 4]])
|
||||
>>> dfm = M.to_DM().to_dfm().convert_to(QQ)
|
||||
>>> dfm
|
||||
[[1, 2], [3, 4]]
|
||||
>>> rhs = Matrix([1, 2]).to_DM().to_dfm().convert_to(QQ)
|
||||
>>> dfm.lu_solve(rhs)
|
||||
[[0], [1/2]]
|
||||
|
||||
Notes
|
||||
=====
|
||||
|
||||
Calls the ``.solve()`` method of the underlying FLINT matrix.
|
||||
|
||||
For now this will raise an error if the domain is :ref:`ZZ` but will
|
||||
use the FLINT method for :ref:`QQ`.
|
||||
|
||||
The FLINT methods for :ref:`ZZ` and :ref:`QQ` are ``fmpz_mat_solve``
|
||||
and ``fmpq_mat_solve`` respectively. The ``fmpq_mat_solve`` method
|
||||
uses one of two algorithms:
|
||||
|
||||
- For small matrices (<25 rows) it clears denominators between the
|
||||
matrix and rhs and uses ``fmpz_mat_solve``.
|
||||
- For larger matrices it uses ``fmpq_mat_solve_dixon`` which is a
|
||||
modular approach with CRT reconstruction over :ref:`QQ`.
|
||||
|
||||
The ``fmpz_mat_solve`` method uses one of four algorithms:
|
||||
|
||||
- For very small (<= 3x3) matrices it uses a Cramer's rule.
|
||||
- For small (<= 15x15) matrices it uses a fraction-free LU solve.
|
||||
- Otherwise it uses either Dixon or another multimodular approach.
|
||||
|
||||
See Also
|
||||
========
|
||||
|
||||
sympy.polys.matrices.domainmatrix.DomainMatrix.lu_solve
|
||||
Higher level interface to solve a matrix equation.
|
||||
"""
|
||||
if not self.domain == rhs.domain:
|
||||
raise DMDomainError("Domains must match: %s != %s" % (self.domain, rhs.domain))
|
||||
|
||||
# XXX: As for inv we should consider whether to return a matrix over
|
||||
# over an associated field or attempt to find a solution in the ring.
|
||||
# For now we follow the existing DomainMatrix convention...
|
||||
if not self.domain.is_Field:
|
||||
raise DMDomainError("Field expected, got %s" % self.domain)
|
||||
|
||||
m, n = self.shape
|
||||
j, k = rhs.shape
|
||||
if m != j:
|
||||
raise DMShapeError("Matrix size mismatch: %s * %s vs %s * %s" % (m, n, j, k))
|
||||
sol_shape = (n, k)
|
||||
|
||||
# XXX: The Flint solve method only handles square matrices. Probably
|
||||
# Flint has functions that could be used to solve non-square systems
|
||||
# but they are not exposed in python-flint yet. Alternatively we could
|
||||
# put something here using the features that are available like rref.
|
||||
if m != n:
|
||||
return self.to_ddm().lu_solve(rhs.to_ddm()).to_dfm()
|
||||
|
||||
try:
|
||||
sol = self.rep.solve(rhs.rep)
|
||||
except ZeroDivisionError:
|
||||
raise DMNonInvertibleMatrixError("Matrix det == 0; not invertible.")
|
||||
|
||||
return self._new(sol, sol_shape, self.domain)
|
||||
|
||||
def fflu(self):
|
||||
"""
|
||||
Fraction-free LU decomposition of DFM.
|
||||
|
||||
Explanation
|
||||
===========
|
||||
|
||||
Uses `python-flint` if possible for a matrix of
|
||||
integers otherwise uses the DDM method.
|
||||
|
||||
See Also
|
||||
========
|
||||
|
||||
sympy.polys.matrices.ddm.DDM.fflu
|
||||
"""
|
||||
if self.domain == ZZ:
|
||||
fflu = getattr(self.rep, 'fflu', None)
|
||||
if fflu is not None:
|
||||
P, L, D, U = self.rep.fflu()
|
||||
m, n = self.shape
|
||||
return (
|
||||
self._new(P, (m, m), self.domain),
|
||||
self._new(L, (m, m), self.domain),
|
||||
self._new(D, (m, m), self.domain),
|
||||
self._new(U, self.shape, self.domain)
|
||||
)
|
||||
ddm_p, ddm_l, ddm_d, ddm_u = self.to_ddm().fflu()
|
||||
P = ddm_p.to_dfm()
|
||||
L = ddm_l.to_dfm()
|
||||
D = ddm_d.to_dfm()
|
||||
U = ddm_u.to_dfm()
|
||||
return P, L, D, U
|
||||
|
||||
def nullspace(self):
|
||||
"""Return a basis for the nullspace of the matrix."""
|
||||
# Code to compute nullspace using flint:
|
||||
#
|
||||
# V, nullity = self.rep.nullspace()
|
||||
# V_dfm = self._new_rep(V)._extract(range(self.rows), range(nullity))
|
||||
#
|
||||
# XXX: That gives the nullspace but does not give us nonpivots. So we
|
||||
# use the slower DDM method anyway. It would be better to change the
|
||||
# signature of the nullspace method to not return nonpivots.
|
||||
#
|
||||
# XXX: Also python-flint exposes a nullspace method for fmpz_mat but
|
||||
# not for fmpq_mat. This is the reverse of the situation for DDM etc
|
||||
# which only allow nullspace over a field. The nullspace method for
|
||||
# DDM, SDM etc should be changed to allow nullspace over ZZ as well.
|
||||
# The DomainMatrix nullspace method does allow the domain to be a ring
|
||||
# but does not directly call the lower-level nullspace methods and uses
|
||||
# rref_den instead. Nullspace methods should also be added to all
|
||||
# matrix types in python-flint.
|
||||
ddm, nonpivots = self.to_ddm().nullspace()
|
||||
return ddm.to_dfm(), nonpivots
|
||||
|
||||
def nullspace_from_rref(self, pivots=None):
|
||||
"""Return a basis for the nullspace of the matrix."""
|
||||
# XXX: Use the flint nullspace method!!!
|
||||
sdm, nonpivots = self.to_sdm().nullspace_from_rref(pivots=pivots)
|
||||
return sdm.to_dfm(), nonpivots
|
||||
|
||||
def particular(self):
|
||||
"""Return a particular solution to the system."""
|
||||
return self.to_ddm().particular().to_dfm()
|
||||
|
||||
def _lll(self, transform=False, delta=0.99, eta=0.51, rep='zbasis', gram='approx'):
|
||||
"""Call the fmpz_mat.lll() method but check rank to avoid segfaults."""
|
||||
|
||||
# XXX: There are tests that pass e.g. QQ(5,6) for delta. That fails
|
||||
# with a TypeError in flint because if QQ is fmpq then conversion with
|
||||
# float fails. We handle that here but there are two better fixes:
|
||||
#
|
||||
# - Make python-flint's fmpq convert with float(x)
|
||||
# - Change the tests because delta should just be a float.
|
||||
|
||||
def to_float(x):
|
||||
if QQ.of_type(x):
|
||||
return float(x.numerator) / float(x.denominator)
|
||||
else:
|
||||
return float(x)
|
||||
|
||||
delta = to_float(delta)
|
||||
eta = to_float(eta)
|
||||
|
||||
if not 0.25 < delta < 1:
|
||||
raise DMValueError("delta must be between 0.25 and 1")
|
||||
|
||||
# XXX: The flint lll method segfaults if the matrix is not full rank.
|
||||
m, n = self.shape
|
||||
if self.rep.rank() != m:
|
||||
raise DMRankError("Matrix must have full row rank for Flint LLL.")
|
||||
|
||||
# Actually call the flint method.
|
||||
return self.rep.lll(transform=transform, delta=delta, eta=eta, rep=rep, gram=gram)
|
||||
|
||||
@doctest_depends_on(ground_types='flint')
|
||||
def lll(self, delta=0.75):
|
||||
"""Compute LLL-reduced basis using FLINT.
|
||||
|
||||
See :meth:`lll_transform` for more information.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy import Matrix
|
||||
>>> M = Matrix([[1, 2, 3], [4, 5, 6]])
|
||||
>>> M.to_DM().to_dfm().lll()
|
||||
[[2, 1, 0], [-1, 1, 3]]
|
||||
|
||||
See Also
|
||||
========
|
||||
|
||||
sympy.polys.matrices.domainmatrix.DomainMatrix.lll
|
||||
Higher level interface to compute LLL-reduced basis.
|
||||
lll_transform
|
||||
Compute LLL-reduced basis and transform matrix.
|
||||
"""
|
||||
if self.domain != ZZ:
|
||||
raise DMDomainError("ZZ expected, got %s" % self.domain)
|
||||
elif self.rows > self.cols:
|
||||
raise DMShapeError("Matrix must not have more rows than columns.")
|
||||
|
||||
rep = self._lll(delta=delta)
|
||||
return self._new_rep(rep)
|
||||
|
||||
@doctest_depends_on(ground_types='flint')
|
||||
def lll_transform(self, delta=0.75):
|
||||
"""Compute LLL-reduced basis and transform using FLINT.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy import Matrix
|
||||
>>> M = Matrix([[1, 2, 3], [4, 5, 6]]).to_DM().to_dfm()
|
||||
>>> M_lll, T = M.lll_transform()
|
||||
>>> M_lll
|
||||
[[2, 1, 0], [-1, 1, 3]]
|
||||
>>> T
|
||||
[[-2, 1], [3, -1]]
|
||||
>>> T.matmul(M) == M_lll
|
||||
True
|
||||
|
||||
See Also
|
||||
========
|
||||
|
||||
sympy.polys.matrices.domainmatrix.DomainMatrix.lll
|
||||
Higher level interface to compute LLL-reduced basis.
|
||||
lll
|
||||
Compute LLL-reduced basis without transform matrix.
|
||||
"""
|
||||
if self.domain != ZZ:
|
||||
raise DMDomainError("ZZ expected, got %s" % self.domain)
|
||||
elif self.rows > self.cols:
|
||||
raise DMShapeError("Matrix must not have more rows than columns.")
|
||||
|
||||
rep, T = self._lll(transform=True, delta=delta)
|
||||
basis = self._new_rep(rep)
|
||||
T_dfm = self._new(T, (self.rows, self.rows), self.domain)
|
||||
return basis, T_dfm
|
||||
|
||||
|
||||
# Avoid circular imports
|
||||
from sympy.polys.matrices.ddm import DDM
|
||||
from sympy.polys.matrices.ddm import SDM
|
||||
@@ -0,0 +1,16 @@
|
||||
from typing import TypeVar, Protocol
|
||||
|
||||
|
||||
T = TypeVar('T')
|
||||
|
||||
|
||||
class RingElement(Protocol):
|
||||
"""A ring element.
|
||||
|
||||
Must support ``+``, ``-``, ``*``, ``**`` and ``-``.
|
||||
"""
|
||||
def __add__(self: T, other: T, /) -> T: ...
|
||||
def __sub__(self: T, other: T, /) -> T: ...
|
||||
def __mul__(self: T, other: T, /) -> T: ...
|
||||
def __pow__(self: T, other: int, /) -> T: ...
|
||||
def __neg__(self: T, /) -> T: ...
|
||||
1176
venv/lib/python3.12/site-packages/sympy/polys/matrices/ddm.py
Normal file
1176
venv/lib/python3.12/site-packages/sympy/polys/matrices/ddm.py
Normal file
File diff suppressed because it is too large
Load Diff
824
venv/lib/python3.12/site-packages/sympy/polys/matrices/dense.py
Normal file
824
venv/lib/python3.12/site-packages/sympy/polys/matrices/dense.py
Normal file
@@ -0,0 +1,824 @@
|
||||
"""
|
||||
|
||||
Module for the ddm_* routines for operating on a matrix in list of lists
|
||||
matrix representation.
|
||||
|
||||
These routines are used internally by the DDM class which also provides a
|
||||
friendlier interface for them. The idea here is to implement core matrix
|
||||
routines in a way that can be applied to any simple list representation
|
||||
without the need to use any particular matrix class. For example we can
|
||||
compute the RREF of a matrix like:
|
||||
|
||||
>>> from sympy.polys.matrices.dense import ddm_irref
|
||||
>>> M = [[1, 2, 3], [4, 5, 6]]
|
||||
>>> pivots = ddm_irref(M)
|
||||
>>> M
|
||||
[[1.0, 0.0, -1.0], [0, 1.0, 2.0]]
|
||||
|
||||
These are lower-level routines that work mostly in place.The routines at this
|
||||
level should not need to know what the domain of the elements is but should
|
||||
ideally document what operations they will use and what functions they need to
|
||||
be provided with.
|
||||
|
||||
The next-level up is the DDM class which uses these routines but wraps them up
|
||||
with an interface that handles copying etc and keeps track of the Domain of
|
||||
the elements of the matrix:
|
||||
|
||||
>>> from sympy.polys.domains import QQ
|
||||
>>> from sympy.polys.matrices.ddm import DDM
|
||||
>>> M = DDM([[QQ(1), QQ(2), QQ(3)], [QQ(4), QQ(5), QQ(6)]], (2, 3), QQ)
|
||||
>>> M
|
||||
[[1, 2, 3], [4, 5, 6]]
|
||||
>>> Mrref, pivots = M.rref()
|
||||
>>> Mrref
|
||||
[[1, 0, -1], [0, 1, 2]]
|
||||
|
||||
"""
|
||||
from __future__ import annotations
|
||||
from operator import mul
|
||||
from .exceptions import (
|
||||
DMShapeError,
|
||||
DMDomainError,
|
||||
DMNonInvertibleMatrixError,
|
||||
DMNonSquareMatrixError,
|
||||
)
|
||||
from typing import Sequence, TypeVar
|
||||
from sympy.polys.matrices._typing import RingElement
|
||||
|
||||
|
||||
#: Type variable for the elements of the matrix
|
||||
T = TypeVar('T')
|
||||
|
||||
#: Type variable for the elements of the matrix that are in a ring
|
||||
R = TypeVar('R', bound=RingElement)
|
||||
|
||||
|
||||
def ddm_transpose(matrix: Sequence[Sequence[T]]) -> list[list[T]]:
|
||||
"""matrix transpose"""
|
||||
return list(map(list, zip(*matrix)))
|
||||
|
||||
|
||||
def ddm_iadd(a: list[list[R]], b: Sequence[Sequence[R]]) -> None:
|
||||
"""a += b"""
|
||||
for ai, bi in zip(a, b):
|
||||
for j, bij in enumerate(bi):
|
||||
ai[j] += bij
|
||||
|
||||
|
||||
def ddm_isub(a: list[list[R]], b: Sequence[Sequence[R]]) -> None:
|
||||
"""a -= b"""
|
||||
for ai, bi in zip(a, b):
|
||||
for j, bij in enumerate(bi):
|
||||
ai[j] -= bij
|
||||
|
||||
|
||||
def ddm_ineg(a: list[list[R]]) -> None:
|
||||
"""a <-- -a"""
|
||||
for ai in a:
|
||||
for j, aij in enumerate(ai):
|
||||
ai[j] = -aij
|
||||
|
||||
|
||||
def ddm_imul(a: list[list[R]], b: R) -> None:
|
||||
"""a <-- a*b"""
|
||||
for ai in a:
|
||||
for j, aij in enumerate(ai):
|
||||
ai[j] = aij * b
|
||||
|
||||
|
||||
def ddm_irmul(a: list[list[R]], b: R) -> None:
|
||||
"""a <-- b*a"""
|
||||
for ai in a:
|
||||
for j, aij in enumerate(ai):
|
||||
ai[j] = b * aij
|
||||
|
||||
|
||||
def ddm_imatmul(
|
||||
a: list[list[R]], b: Sequence[Sequence[R]], c: Sequence[Sequence[R]]
|
||||
) -> None:
|
||||
"""a += b @ c"""
|
||||
cT = list(zip(*c))
|
||||
|
||||
for bi, ai in zip(b, a):
|
||||
for j, cTj in enumerate(cT):
|
||||
ai[j] = sum(map(mul, bi, cTj), ai[j])
|
||||
|
||||
|
||||
def ddm_irref(a, _partial_pivot=False):
|
||||
"""In-place reduced row echelon form of a matrix.
|
||||
|
||||
Compute the reduced row echelon form of $a$. Modifies $a$ in place and
|
||||
returns a list of the pivot columns.
|
||||
|
||||
Uses naive Gauss-Jordan elimination in the ground domain which must be a
|
||||
field.
|
||||
|
||||
This routine is only really suitable for use with simple field domains like
|
||||
:ref:`GF(p)`, :ref:`QQ` and :ref:`QQ(a)` although even for :ref:`QQ` with
|
||||
larger matrices it is possibly more efficient to use fraction free
|
||||
approaches.
|
||||
|
||||
This method is not suitable for use with rational function fields
|
||||
(:ref:`K(x)`) because the elements will blowup leading to costly gcd
|
||||
operations. In this case clearing denominators and using fraction free
|
||||
approaches is likely to be more efficient.
|
||||
|
||||
For inexact numeric domains like :ref:`RR` and :ref:`CC` pass
|
||||
``_partial_pivot=True`` to use partial pivoting to control rounding errors.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.polys.matrices.dense import ddm_irref
|
||||
>>> from sympy import QQ
|
||||
>>> M = [[QQ(1), QQ(2), QQ(3)], [QQ(4), QQ(5), QQ(6)]]
|
||||
>>> pivots = ddm_irref(M)
|
||||
>>> M
|
||||
[[1, 0, -1], [0, 1, 2]]
|
||||
>>> pivots
|
||||
[0, 1]
|
||||
|
||||
See Also
|
||||
========
|
||||
|
||||
sympy.polys.matrices.domainmatrix.DomainMatrix.rref
|
||||
Higher level interface to this routine.
|
||||
ddm_irref_den
|
||||
The fraction free version of this routine.
|
||||
sdm_irref
|
||||
A sparse version of this routine.
|
||||
|
||||
References
|
||||
==========
|
||||
|
||||
.. [1] https://en.wikipedia.org/wiki/Row_echelon_form#Reduced_row_echelon_form
|
||||
"""
|
||||
# We compute aij**-1 below and then use multiplication instead of division
|
||||
# in the innermost loop. The domain here is a field so either operation is
|
||||
# defined. There are significant performance differences for some domains
|
||||
# though. In the case of e.g. QQ or QQ(x) inversion is free but
|
||||
# multiplication and division have the same cost so it makes no difference.
|
||||
# In cases like GF(p), QQ<sqrt(2)>, RR or CC though multiplication is
|
||||
# faster than division so reusing a precomputed inverse for many
|
||||
# multiplications can be a lot faster. The biggest win is QQ<a> when
|
||||
# deg(minpoly(a)) is large.
|
||||
#
|
||||
# With domains like QQ(x) this can perform badly for other reasons.
|
||||
# Typically the initial matrix has simple denominators and the
|
||||
# fraction-free approach with exquo (ddm_irref_den) will preserve that
|
||||
# property throughout. The method here causes denominator blowup leading to
|
||||
# expensive gcd reductions in the intermediate expressions. With many
|
||||
# generators like QQ(x,y,z,...) this is extremely bad.
|
||||
#
|
||||
# TODO: Use a nontrivial pivoting strategy to control intermediate
|
||||
# expression growth. Rearranging rows and/or columns could defer the most
|
||||
# complicated elements until the end. If the first pivot is a
|
||||
# complicated/large element then the first round of reduction will
|
||||
# immediately introduce expression blowup across the whole matrix.
|
||||
|
||||
# a is (m x n)
|
||||
m = len(a)
|
||||
if not m:
|
||||
return []
|
||||
n = len(a[0])
|
||||
|
||||
i = 0
|
||||
pivots = []
|
||||
|
||||
for j in range(n):
|
||||
# Proper pivoting should be used for all domains for performance
|
||||
# reasons but it is only strictly needed for RR and CC (and possibly
|
||||
# other domains like RR(x)). This path is used by DDM.rref() if the
|
||||
# domain is RR or CC. It uses partial (row) pivoting based on the
|
||||
# absolute value of the pivot candidates.
|
||||
if _partial_pivot:
|
||||
ip = max(range(i, m), key=lambda ip: abs(a[ip][j]))
|
||||
a[i], a[ip] = a[ip], a[i]
|
||||
|
||||
# pivot
|
||||
aij = a[i][j]
|
||||
|
||||
# zero-pivot
|
||||
if not aij:
|
||||
for ip in range(i+1, m):
|
||||
aij = a[ip][j]
|
||||
# row-swap
|
||||
if aij:
|
||||
a[i], a[ip] = a[ip], a[i]
|
||||
break
|
||||
else:
|
||||
# next column
|
||||
continue
|
||||
|
||||
# normalise row
|
||||
ai = a[i]
|
||||
aijinv = aij**-1
|
||||
for l in range(j, n):
|
||||
ai[l] *= aijinv # ai[j] = one
|
||||
|
||||
# eliminate above and below to the right
|
||||
for k, ak in enumerate(a):
|
||||
if k == i or not ak[j]:
|
||||
continue
|
||||
akj = ak[j]
|
||||
ak[j] -= akj # ak[j] = zero
|
||||
for l in range(j+1, n):
|
||||
ak[l] -= akj * ai[l]
|
||||
|
||||
# next row
|
||||
pivots.append(j)
|
||||
i += 1
|
||||
|
||||
# no more rows?
|
||||
if i >= m:
|
||||
break
|
||||
|
||||
return pivots
|
||||
|
||||
|
||||
def ddm_irref_den(a, K):
|
||||
"""a <-- rref(a); return (den, pivots)
|
||||
|
||||
Compute the fraction-free reduced row echelon form (RREF) of $a$. Modifies
|
||||
$a$ in place and returns a tuple containing the denominator of the RREF and
|
||||
a list of the pivot columns.
|
||||
|
||||
Explanation
|
||||
===========
|
||||
|
||||
The algorithm used is the fraction-free version of Gauss-Jordan elimination
|
||||
described as FFGJ in [1]_. Here it is modified to handle zero or missing
|
||||
pivots and to avoid redundant arithmetic.
|
||||
|
||||
The domain $K$ must support exact division (``K.exquo``) but does not need
|
||||
to be a field. This method is suitable for most exact rings and fields like
|
||||
:ref:`ZZ`, :ref:`QQ` and :ref:`QQ(a)`. In the case of :ref:`QQ` or
|
||||
:ref:`K(x)` it might be more efficient to clear denominators and use
|
||||
:ref:`ZZ` or :ref:`K[x]` instead.
|
||||
|
||||
For inexact domains like :ref:`RR` and :ref:`CC` use ``ddm_irref`` instead.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.polys.matrices.dense import ddm_irref_den
|
||||
>>> from sympy import ZZ, Matrix
|
||||
>>> M = [[ZZ(1), ZZ(2), ZZ(3)], [ZZ(4), ZZ(5), ZZ(6)]]
|
||||
>>> den, pivots = ddm_irref_den(M, ZZ)
|
||||
>>> M
|
||||
[[-3, 0, 3], [0, -3, -6]]
|
||||
>>> den
|
||||
-3
|
||||
>>> pivots
|
||||
[0, 1]
|
||||
>>> Matrix(M).rref()[0]
|
||||
Matrix([
|
||||
[1, 0, -1],
|
||||
[0, 1, 2]])
|
||||
|
||||
See Also
|
||||
========
|
||||
|
||||
ddm_irref
|
||||
A version of this routine that uses field division.
|
||||
sdm_irref
|
||||
A sparse version of :func:`ddm_irref`.
|
||||
sdm_rref_den
|
||||
A sparse version of :func:`ddm_irref_den`.
|
||||
sympy.polys.matrices.domainmatrix.DomainMatrix.rref_den
|
||||
Higher level interface.
|
||||
|
||||
References
|
||||
==========
|
||||
|
||||
.. [1] Fraction-free algorithms for linear and polynomial equations.
|
||||
George C. Nakos , Peter R. Turner , Robert M. Williams.
|
||||
https://dl.acm.org/doi/10.1145/271130.271133
|
||||
"""
|
||||
#
|
||||
# A simpler presentation of this algorithm is given in [1]:
|
||||
#
|
||||
# Given an n x n matrix A and n x 1 matrix b:
|
||||
#
|
||||
# for i in range(n):
|
||||
# if i != 0:
|
||||
# d = a[i-1][i-1]
|
||||
# for j in range(n):
|
||||
# if j == i:
|
||||
# continue
|
||||
# b[j] = a[i][i]*b[j] - a[j][i]*b[i]
|
||||
# for k in range(n):
|
||||
# a[j][k] = a[i][i]*a[j][k] - a[j][i]*a[i][k]
|
||||
# if i != 0:
|
||||
# a[j][k] /= d
|
||||
#
|
||||
# Our version here is a bit more complicated because:
|
||||
#
|
||||
# 1. We use row-swaps to avoid zero pivots.
|
||||
# 2. We allow for some columns to be missing pivots.
|
||||
# 3. We avoid a lot of redundant arithmetic.
|
||||
#
|
||||
# TODO: Use a non-trivial pivoting strategy. Even just row swapping makes a
|
||||
# big difference to performance if e.g. the upper-left entry of the matrix
|
||||
# is a huge polynomial.
|
||||
|
||||
# a is (m x n)
|
||||
m = len(a)
|
||||
if not m:
|
||||
return K.one, []
|
||||
n = len(a[0])
|
||||
|
||||
d = None
|
||||
pivots = []
|
||||
no_pivots = []
|
||||
|
||||
# i, j will be the row and column indices of the current pivot
|
||||
i = 0
|
||||
for j in range(n):
|
||||
# next pivot?
|
||||
aij = a[i][j]
|
||||
|
||||
# swap rows if zero
|
||||
if not aij:
|
||||
for ip in range(i+1, m):
|
||||
aij = a[ip][j]
|
||||
# row-swap
|
||||
if aij:
|
||||
a[i], a[ip] = a[ip], a[i]
|
||||
break
|
||||
else:
|
||||
# go to next column
|
||||
no_pivots.append(j)
|
||||
continue
|
||||
|
||||
# Now aij is the pivot and i,j are the row and column. We need to clear
|
||||
# the column above and below but we also need to keep track of the
|
||||
# denominator of the RREF which means also multiplying everything above
|
||||
# and to the left by the current pivot aij and dividing by d (which we
|
||||
# multiplied everything by in the previous iteration so this is an
|
||||
# exact division).
|
||||
#
|
||||
# First handle the upper left corner which is usually already diagonal
|
||||
# with all diagonal entries equal to the current denominator but there
|
||||
# can be other non-zero entries in any column that has no pivot.
|
||||
|
||||
# Update previous pivots in the matrix
|
||||
if pivots:
|
||||
pivot_val = aij * a[0][pivots[0]]
|
||||
# Divide out the common factor
|
||||
if d is not None:
|
||||
pivot_val = K.exquo(pivot_val, d)
|
||||
|
||||
# Could defer this until the end but it is pretty cheap and
|
||||
# helps when debugging.
|
||||
for ip, jp in enumerate(pivots):
|
||||
a[ip][jp] = pivot_val
|
||||
|
||||
# Update columns without pivots
|
||||
for jnp in no_pivots:
|
||||
for ip in range(i):
|
||||
aijp = a[ip][jnp]
|
||||
if aijp:
|
||||
aijp *= aij
|
||||
if d is not None:
|
||||
aijp = K.exquo(aijp, d)
|
||||
a[ip][jnp] = aijp
|
||||
|
||||
# Eliminate above, below and to the right as in ordinary division free
|
||||
# Gauss-Jordan elmination except also dividing out d from every entry.
|
||||
|
||||
for jp, aj in enumerate(a):
|
||||
|
||||
# Skip the current row
|
||||
if jp == i:
|
||||
continue
|
||||
|
||||
# Eliminate to the right in all rows
|
||||
for kp in range(j+1, n):
|
||||
ajk = aij * aj[kp] - aj[j] * a[i][kp]
|
||||
if d is not None:
|
||||
ajk = K.exquo(ajk, d)
|
||||
aj[kp] = ajk
|
||||
|
||||
# Set to zero above and below the pivot
|
||||
aj[j] = K.zero
|
||||
|
||||
# next row
|
||||
pivots.append(j)
|
||||
i += 1
|
||||
|
||||
# no more rows left?
|
||||
if i >= m:
|
||||
break
|
||||
|
||||
if not K.is_one(aij):
|
||||
d = aij
|
||||
else:
|
||||
d = None
|
||||
|
||||
if not pivots:
|
||||
denom = K.one
|
||||
else:
|
||||
denom = a[0][pivots[0]]
|
||||
|
||||
return denom, pivots
|
||||
|
||||
|
||||
def ddm_idet(a, K):
|
||||
"""a <-- echelon(a); return det
|
||||
|
||||
Explanation
|
||||
===========
|
||||
|
||||
Compute the determinant of $a$ using the Bareiss fraction-free algorithm.
|
||||
The matrix $a$ is modified in place. Its diagonal elements are the
|
||||
determinants of the leading principal minors. The determinant of $a$ is
|
||||
returned.
|
||||
|
||||
The domain $K$ must support exact division (``K.exquo``). This method is
|
||||
suitable for most exact rings and fields like :ref:`ZZ`, :ref:`QQ` and
|
||||
:ref:`QQ(a)` but not for inexact domains like :ref:`RR` and :ref:`CC`.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy import ZZ
|
||||
>>> from sympy.polys.matrices.ddm import ddm_idet
|
||||
>>> a = [[ZZ(1), ZZ(2), ZZ(3)], [ZZ(4), ZZ(5), ZZ(6)], [ZZ(7), ZZ(8), ZZ(9)]]
|
||||
>>> a
|
||||
[[1, 2, 3], [4, 5, 6], [7, 8, 9]]
|
||||
>>> ddm_idet(a, ZZ)
|
||||
0
|
||||
>>> a
|
||||
[[1, 2, 3], [4, -3, -6], [7, -6, 0]]
|
||||
>>> [a[i][i] for i in range(len(a))]
|
||||
[1, -3, 0]
|
||||
|
||||
See Also
|
||||
========
|
||||
|
||||
sympy.polys.matrices.domainmatrix.DomainMatrix.det
|
||||
|
||||
References
|
||||
==========
|
||||
|
||||
.. [1] https://en.wikipedia.org/wiki/Bareiss_algorithm
|
||||
.. [2] https://www.math.usm.edu/perry/Research/Thesis_DRL.pdf
|
||||
"""
|
||||
# Bareiss algorithm
|
||||
# https://www.math.usm.edu/perry/Research/Thesis_DRL.pdf
|
||||
|
||||
# a is (m x n)
|
||||
m = len(a)
|
||||
if not m:
|
||||
return K.one
|
||||
n = len(a[0])
|
||||
|
||||
exquo = K.exquo
|
||||
# uf keeps track of the sign change from row swaps
|
||||
uf = K.one
|
||||
|
||||
for k in range(n-1):
|
||||
if not a[k][k]:
|
||||
for i in range(k+1, n):
|
||||
if a[i][k]:
|
||||
a[k], a[i] = a[i], a[k]
|
||||
uf = -uf
|
||||
break
|
||||
else:
|
||||
return K.zero
|
||||
|
||||
akkm1 = a[k-1][k-1] if k else K.one
|
||||
|
||||
for i in range(k+1, n):
|
||||
for j in range(k+1, n):
|
||||
a[i][j] = exquo(a[i][j]*a[k][k] - a[i][k]*a[k][j], akkm1)
|
||||
|
||||
return uf * a[-1][-1]
|
||||
|
||||
|
||||
def ddm_iinv(ainv, a, K):
|
||||
"""ainv <-- inv(a)
|
||||
|
||||
Compute the inverse of a matrix $a$ over a field $K$ using Gauss-Jordan
|
||||
elimination. The result is stored in $ainv$.
|
||||
|
||||
Uses division in the ground domain which should be an exact field.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.polys.matrices.ddm import ddm_iinv, ddm_imatmul
|
||||
>>> from sympy import QQ
|
||||
>>> a = [[QQ(1), QQ(2)], [QQ(3), QQ(4)]]
|
||||
>>> ainv = [[None, None], [None, None]]
|
||||
>>> ddm_iinv(ainv, a, QQ)
|
||||
>>> ainv
|
||||
[[-2, 1], [3/2, -1/2]]
|
||||
>>> result = [[QQ(0), QQ(0)], [QQ(0), QQ(0)]]
|
||||
>>> ddm_imatmul(result, a, ainv)
|
||||
>>> result
|
||||
[[1, 0], [0, 1]]
|
||||
|
||||
See Also
|
||||
========
|
||||
|
||||
ddm_irref: the underlying routine.
|
||||
"""
|
||||
if not K.is_Field:
|
||||
raise DMDomainError('Not a field')
|
||||
|
||||
# a is (m x n)
|
||||
m = len(a)
|
||||
if not m:
|
||||
return
|
||||
n = len(a[0])
|
||||
if m != n:
|
||||
raise DMNonSquareMatrixError
|
||||
|
||||
eye = [[K.one if i==j else K.zero for j in range(n)] for i in range(n)]
|
||||
Aaug = [row + eyerow for row, eyerow in zip(a, eye)]
|
||||
pivots = ddm_irref(Aaug)
|
||||
if pivots != list(range(n)):
|
||||
raise DMNonInvertibleMatrixError('Matrix det == 0; not invertible.')
|
||||
ainv[:] = [row[n:] for row in Aaug]
|
||||
|
||||
|
||||
def ddm_ilu_split(L, U, K):
|
||||
"""L, U <-- LU(U)
|
||||
|
||||
Compute the LU decomposition of a matrix $L$ in place and store the lower
|
||||
and upper triangular matrices in $L$ and $U$, respectively. Returns a list
|
||||
of row swaps that were performed.
|
||||
|
||||
Uses division in the ground domain which should be an exact field.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.polys.matrices.ddm import ddm_ilu_split
|
||||
>>> from sympy import QQ
|
||||
>>> L = [[QQ(0), QQ(0)], [QQ(0), QQ(0)]]
|
||||
>>> U = [[QQ(1), QQ(2)], [QQ(3), QQ(4)]]
|
||||
>>> swaps = ddm_ilu_split(L, U, QQ)
|
||||
>>> swaps
|
||||
[]
|
||||
>>> L
|
||||
[[0, 0], [3, 0]]
|
||||
>>> U
|
||||
[[1, 2], [0, -2]]
|
||||
|
||||
See Also
|
||||
========
|
||||
|
||||
ddm_ilu
|
||||
ddm_ilu_solve
|
||||
"""
|
||||
m = len(U)
|
||||
if not m:
|
||||
return []
|
||||
n = len(U[0])
|
||||
|
||||
swaps = ddm_ilu(U)
|
||||
|
||||
zeros = [K.zero] * min(m, n)
|
||||
for i in range(1, m):
|
||||
j = min(i, n)
|
||||
L[i][:j] = U[i][:j]
|
||||
U[i][:j] = zeros[:j]
|
||||
|
||||
return swaps
|
||||
|
||||
|
||||
def ddm_ilu(a):
|
||||
"""a <-- LU(a)
|
||||
|
||||
Computes the LU decomposition of a matrix in place. Returns a list of
|
||||
row swaps that were performed.
|
||||
|
||||
Uses division in the ground domain which should be an exact field.
|
||||
|
||||
This is only suitable for domains like :ref:`GF(p)`, :ref:`QQ`, :ref:`QQ_I`
|
||||
and :ref:`QQ(a)`. With a rational function field like :ref:`K(x)` it is
|
||||
better to clear denominators and use division-free algorithms. Pivoting is
|
||||
used to avoid exact zeros but not for floating point accuracy so :ref:`RR`
|
||||
and :ref:`CC` are not suitable (use :func:`ddm_irref` instead).
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.polys.matrices.dense import ddm_ilu
|
||||
>>> from sympy import QQ
|
||||
>>> a = [[QQ(1, 2), QQ(1, 3)], [QQ(1, 4), QQ(1, 5)]]
|
||||
>>> swaps = ddm_ilu(a)
|
||||
>>> swaps
|
||||
[]
|
||||
>>> a
|
||||
[[1/2, 1/3], [1/2, 1/30]]
|
||||
|
||||
The same example using ``Matrix``:
|
||||
|
||||
>>> from sympy import Matrix, S
|
||||
>>> M = Matrix([[S(1)/2, S(1)/3], [S(1)/4, S(1)/5]])
|
||||
>>> L, U, swaps = M.LUdecomposition()
|
||||
>>> L
|
||||
Matrix([
|
||||
[ 1, 0],
|
||||
[1/2, 1]])
|
||||
>>> U
|
||||
Matrix([
|
||||
[1/2, 1/3],
|
||||
[ 0, 1/30]])
|
||||
>>> swaps
|
||||
[]
|
||||
|
||||
See Also
|
||||
========
|
||||
|
||||
ddm_irref
|
||||
ddm_ilu_solve
|
||||
sympy.matrices.matrixbase.MatrixBase.LUdecomposition
|
||||
"""
|
||||
m = len(a)
|
||||
if not m:
|
||||
return []
|
||||
n = len(a[0])
|
||||
|
||||
swaps = []
|
||||
|
||||
for i in range(min(m, n)):
|
||||
if not a[i][i]:
|
||||
for ip in range(i+1, m):
|
||||
if a[ip][i]:
|
||||
swaps.append((i, ip))
|
||||
a[i], a[ip] = a[ip], a[i]
|
||||
break
|
||||
else:
|
||||
# M = Matrix([[1, 0, 0, 0], [0, 0, 0, 0], [0, 0, 1, 1], [0, 0, 1, 2]])
|
||||
continue
|
||||
for j in range(i+1, m):
|
||||
l_ji = a[j][i] / a[i][i]
|
||||
a[j][i] = l_ji
|
||||
for k in range(i+1, n):
|
||||
a[j][k] -= l_ji * a[i][k]
|
||||
|
||||
return swaps
|
||||
|
||||
|
||||
def ddm_ilu_solve(x, L, U, swaps, b):
|
||||
"""x <-- solve(L*U*x = swaps(b))
|
||||
|
||||
Solve a linear system, $A*x = b$, given an LU factorization of $A$.
|
||||
|
||||
Uses division in the ground domain which must be a field.
|
||||
|
||||
Modifies $x$ in place.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
Compute the LU decomposition of $A$ (in place):
|
||||
|
||||
>>> from sympy import QQ
|
||||
>>> from sympy.polys.matrices.dense import ddm_ilu, ddm_ilu_solve
|
||||
>>> A = [[QQ(1), QQ(2)], [QQ(3), QQ(4)]]
|
||||
>>> swaps = ddm_ilu(A)
|
||||
>>> A
|
||||
[[1, 2], [3, -2]]
|
||||
>>> L = U = A
|
||||
|
||||
Solve the linear system:
|
||||
|
||||
>>> b = [[QQ(5)], [QQ(6)]]
|
||||
>>> x = [[None], [None]]
|
||||
>>> ddm_ilu_solve(x, L, U, swaps, b)
|
||||
>>> x
|
||||
[[-4], [9/2]]
|
||||
|
||||
See Also
|
||||
========
|
||||
|
||||
ddm_ilu
|
||||
Compute the LU decomposition of a matrix in place.
|
||||
ddm_ilu_split
|
||||
Compute the LU decomposition of a matrix and separate $L$ and $U$.
|
||||
sympy.polys.matrices.domainmatrix.DomainMatrix.lu_solve
|
||||
Higher level interface to this function.
|
||||
"""
|
||||
m = len(U)
|
||||
if not m:
|
||||
return
|
||||
n = len(U[0])
|
||||
|
||||
m2 = len(b)
|
||||
if not m2:
|
||||
raise DMShapeError("Shape mismtch")
|
||||
o = len(b[0])
|
||||
|
||||
if m != m2:
|
||||
raise DMShapeError("Shape mismtch")
|
||||
if m < n:
|
||||
raise NotImplementedError("Underdetermined")
|
||||
|
||||
if swaps:
|
||||
b = [row[:] for row in b]
|
||||
for i1, i2 in swaps:
|
||||
b[i1], b[i2] = b[i2], b[i1]
|
||||
|
||||
# solve Ly = b
|
||||
y = [[None] * o for _ in range(m)]
|
||||
for k in range(o):
|
||||
for i in range(m):
|
||||
rhs = b[i][k]
|
||||
for j in range(i):
|
||||
rhs -= L[i][j] * y[j][k]
|
||||
y[i][k] = rhs
|
||||
|
||||
if m > n:
|
||||
for i in range(n, m):
|
||||
for j in range(o):
|
||||
if y[i][j]:
|
||||
raise DMNonInvertibleMatrixError
|
||||
|
||||
# Solve Ux = y
|
||||
for k in range(o):
|
||||
for i in reversed(range(n)):
|
||||
if not U[i][i]:
|
||||
raise DMNonInvertibleMatrixError
|
||||
rhs = y[i][k]
|
||||
for j in range(i+1, n):
|
||||
rhs -= U[i][j] * x[j][k]
|
||||
x[i][k] = rhs / U[i][i]
|
||||
|
||||
|
||||
def ddm_berk(M, K):
|
||||
"""
|
||||
Berkowitz algorithm for computing the characteristic polynomial.
|
||||
|
||||
Explanation
|
||||
===========
|
||||
|
||||
The Berkowitz algorithm is a division-free algorithm for computing the
|
||||
characteristic polynomial of a matrix over any commutative ring using only
|
||||
arithmetic in the coefficient ring.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy import Matrix
|
||||
>>> from sympy.polys.matrices.dense import ddm_berk
|
||||
>>> from sympy.polys.domains import ZZ
|
||||
>>> M = [[ZZ(1), ZZ(2)], [ZZ(3), ZZ(4)]]
|
||||
>>> ddm_berk(M, ZZ)
|
||||
[[1], [-5], [-2]]
|
||||
>>> Matrix(M).charpoly()
|
||||
PurePoly(lambda**2 - 5*lambda - 2, lambda, domain='ZZ')
|
||||
|
||||
See Also
|
||||
========
|
||||
|
||||
sympy.polys.matrices.domainmatrix.DomainMatrix.charpoly
|
||||
The high-level interface to this function.
|
||||
|
||||
References
|
||||
==========
|
||||
|
||||
.. [1] https://en.wikipedia.org/wiki/Samuelson%E2%80%93Berkowitz_algorithm
|
||||
"""
|
||||
m = len(M)
|
||||
if not m:
|
||||
return [[K.one]]
|
||||
n = len(M[0])
|
||||
|
||||
if m != n:
|
||||
raise DMShapeError("Not square")
|
||||
|
||||
if n == 1:
|
||||
return [[K.one], [-M[0][0]]]
|
||||
|
||||
a = M[0][0]
|
||||
R = [M[0][1:]]
|
||||
C = [[row[0]] for row in M[1:]]
|
||||
A = [row[1:] for row in M[1:]]
|
||||
|
||||
q = ddm_berk(A, K)
|
||||
|
||||
T = [[K.zero] * n for _ in range(n+1)]
|
||||
for i in range(n):
|
||||
T[i][i] = K.one
|
||||
T[i+1][i] = -a
|
||||
for i in range(2, n+1):
|
||||
if i == 2:
|
||||
AnC = C
|
||||
else:
|
||||
C = AnC
|
||||
AnC = [[K.zero] for row in C]
|
||||
ddm_imatmul(AnC, A, C)
|
||||
RAnC = [[K.zero]]
|
||||
ddm_imatmul(RAnC, R, AnC)
|
||||
for j in range(0, n+1-i):
|
||||
T[i+j][j] = -RAnC[0][0]
|
||||
|
||||
qout = [[K.zero] for _ in range(n+1)]
|
||||
ddm_imatmul(qout, T, q)
|
||||
return qout
|
||||
@@ -0,0 +1,35 @@
|
||||
"""
|
||||
sympy.polys.matrices.dfm
|
||||
|
||||
Provides the :class:`DFM` class if ``GROUND_TYPES=flint'``. Otherwise, ``DFM``
|
||||
is a placeholder class that raises NotImplementedError when instantiated.
|
||||
"""
|
||||
|
||||
from sympy.external.gmpy import GROUND_TYPES
|
||||
|
||||
if GROUND_TYPES == "flint": # pragma: no cover
|
||||
# When python-flint is installed we will try to use it for dense matrices
|
||||
# if the domain is supported by python-flint.
|
||||
from ._dfm import DFM
|
||||
|
||||
else: # pragma: no cover
|
||||
# Other code should be able to import this and it should just present as a
|
||||
# version of DFM that does not support any domains.
|
||||
class DFM_dummy:
|
||||
"""
|
||||
Placeholder class for DFM when python-flint is not installed.
|
||||
"""
|
||||
def __init__(*args, **kwargs):
|
||||
raise NotImplementedError("DFM requires GROUND_TYPES=flint.")
|
||||
|
||||
@classmethod
|
||||
def _supports_domain(cls, domain):
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
def _get_flint_func(cls, domain):
|
||||
raise NotImplementedError("DFM requires GROUND_TYPES=flint.")
|
||||
|
||||
# mypy really struggles with this kind of conditional type assignment.
|
||||
# Maybe there is a better way to annotate this rather than type: ignore.
|
||||
DFM = DFM_dummy # type: ignore
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,122 @@
|
||||
"""
|
||||
|
||||
Module for the DomainScalar class.
|
||||
|
||||
A DomainScalar represents an element which is in a particular
|
||||
Domain. The idea is that the DomainScalar class provides the
|
||||
convenience routines for unifying elements with different domains.
|
||||
|
||||
It assists in Scalar Multiplication and getitem for DomainMatrix.
|
||||
|
||||
"""
|
||||
from ..constructor import construct_domain
|
||||
|
||||
from sympy.polys.domains import Domain, ZZ
|
||||
|
||||
|
||||
class DomainScalar:
|
||||
r"""
|
||||
docstring
|
||||
"""
|
||||
|
||||
def __new__(cls, element, domain):
|
||||
if not isinstance(domain, Domain):
|
||||
raise TypeError("domain should be of type Domain")
|
||||
if not domain.of_type(element):
|
||||
raise TypeError("element %s should be in domain %s" % (element, domain))
|
||||
return cls.new(element, domain)
|
||||
|
||||
@classmethod
|
||||
def new(cls, element, domain):
|
||||
obj = super().__new__(cls)
|
||||
obj.element = element
|
||||
obj.domain = domain
|
||||
return obj
|
||||
|
||||
def __repr__(self):
|
||||
return repr(self.element)
|
||||
|
||||
@classmethod
|
||||
def from_sympy(cls, expr):
|
||||
[domain, [element]] = construct_domain([expr])
|
||||
return cls.new(element, domain)
|
||||
|
||||
def to_sympy(self):
|
||||
return self.domain.to_sympy(self.element)
|
||||
|
||||
def to_domain(self, domain):
|
||||
element = domain.convert_from(self.element, self.domain)
|
||||
return self.new(element, domain)
|
||||
|
||||
def convert_to(self, domain):
|
||||
return self.to_domain(domain)
|
||||
|
||||
def unify(self, other):
|
||||
domain = self.domain.unify(other.domain)
|
||||
return self.to_domain(domain), other.to_domain(domain)
|
||||
|
||||
def __bool__(self):
|
||||
return bool(self.element)
|
||||
|
||||
def __add__(self, other):
|
||||
if not isinstance(other, DomainScalar):
|
||||
return NotImplemented
|
||||
self, other = self.unify(other)
|
||||
return self.new(self.element + other.element, self.domain)
|
||||
|
||||
def __sub__(self, other):
|
||||
if not isinstance(other, DomainScalar):
|
||||
return NotImplemented
|
||||
self, other = self.unify(other)
|
||||
return self.new(self.element - other.element, self.domain)
|
||||
|
||||
def __mul__(self, other):
|
||||
if not isinstance(other, DomainScalar):
|
||||
if isinstance(other, int):
|
||||
other = DomainScalar(ZZ(other), ZZ)
|
||||
else:
|
||||
return NotImplemented
|
||||
|
||||
self, other = self.unify(other)
|
||||
return self.new(self.element * other.element, self.domain)
|
||||
|
||||
def __floordiv__(self, other):
|
||||
if not isinstance(other, DomainScalar):
|
||||
return NotImplemented
|
||||
self, other = self.unify(other)
|
||||
return self.new(self.domain.quo(self.element, other.element), self.domain)
|
||||
|
||||
def __mod__(self, other):
|
||||
if not isinstance(other, DomainScalar):
|
||||
return NotImplemented
|
||||
self, other = self.unify(other)
|
||||
return self.new(self.domain.rem(self.element, other.element), self.domain)
|
||||
|
||||
def __divmod__(self, other):
|
||||
if not isinstance(other, DomainScalar):
|
||||
return NotImplemented
|
||||
self, other = self.unify(other)
|
||||
q, r = self.domain.div(self.element, other.element)
|
||||
return (self.new(q, self.domain), self.new(r, self.domain))
|
||||
|
||||
def __pow__(self, n):
|
||||
if not isinstance(n, int):
|
||||
return NotImplemented
|
||||
return self.new(self.element**n, self.domain)
|
||||
|
||||
def __pos__(self):
|
||||
return self.new(+self.element, self.domain)
|
||||
|
||||
def __neg__(self):
|
||||
return self.new(-self.element, self.domain)
|
||||
|
||||
def __eq__(self, other):
|
||||
if not isinstance(other, DomainScalar):
|
||||
return NotImplemented
|
||||
return self.element == other.element and self.domain == other.domain
|
||||
|
||||
def is_zero(self):
|
||||
return self.element == self.domain.zero
|
||||
|
||||
def is_one(self):
|
||||
return self.element == self.domain.one
|
||||
@@ -0,0 +1,90 @@
|
||||
"""
|
||||
|
||||
Routines for computing eigenvectors with DomainMatrix.
|
||||
|
||||
"""
|
||||
from sympy.core.symbol import Dummy
|
||||
|
||||
from ..agca.extensions import FiniteExtension
|
||||
from ..factortools import dup_factor_list
|
||||
from ..polyroots import roots
|
||||
from ..polytools import Poly
|
||||
from ..rootoftools import CRootOf
|
||||
|
||||
from .domainmatrix import DomainMatrix
|
||||
|
||||
|
||||
def dom_eigenvects(A, l=Dummy('lambda')):
|
||||
charpoly = A.charpoly()
|
||||
rows, cols = A.shape
|
||||
domain = A.domain
|
||||
_, factors = dup_factor_list(charpoly, domain)
|
||||
|
||||
rational_eigenvects = []
|
||||
algebraic_eigenvects = []
|
||||
for base, exp in factors:
|
||||
if len(base) == 2:
|
||||
field = domain
|
||||
eigenval = -base[1] / base[0]
|
||||
|
||||
EE_items = [
|
||||
[eigenval if i == j else field.zero for j in range(cols)]
|
||||
for i in range(rows)]
|
||||
EE = DomainMatrix(EE_items, (rows, cols), field)
|
||||
|
||||
basis = (A - EE).nullspace(divide_last=True)
|
||||
rational_eigenvects.append((field, eigenval, exp, basis))
|
||||
else:
|
||||
minpoly = Poly.from_list(base, l, domain=domain)
|
||||
field = FiniteExtension(minpoly)
|
||||
eigenval = field(l)
|
||||
|
||||
AA_items = [
|
||||
[Poly.from_list([item], l, domain=domain).rep for item in row]
|
||||
for row in A.rep.to_ddm()]
|
||||
AA_items = [[field(item) for item in row] for row in AA_items]
|
||||
AA = DomainMatrix(AA_items, (rows, cols), field)
|
||||
EE_items = [
|
||||
[eigenval if i == j else field.zero for j in range(cols)]
|
||||
for i in range(rows)]
|
||||
EE = DomainMatrix(EE_items, (rows, cols), field)
|
||||
|
||||
basis = (AA - EE).nullspace(divide_last=True)
|
||||
algebraic_eigenvects.append((field, minpoly, exp, basis))
|
||||
|
||||
return rational_eigenvects, algebraic_eigenvects
|
||||
|
||||
|
||||
def dom_eigenvects_to_sympy(
|
||||
rational_eigenvects, algebraic_eigenvects,
|
||||
Matrix, **kwargs
|
||||
):
|
||||
result = []
|
||||
|
||||
for field, eigenvalue, multiplicity, eigenvects in rational_eigenvects:
|
||||
eigenvects = eigenvects.rep.to_ddm()
|
||||
eigenvalue = field.to_sympy(eigenvalue)
|
||||
new_eigenvects = [
|
||||
Matrix([field.to_sympy(x) for x in vect])
|
||||
for vect in eigenvects]
|
||||
result.append((eigenvalue, multiplicity, new_eigenvects))
|
||||
|
||||
for field, minpoly, multiplicity, eigenvects in algebraic_eigenvects:
|
||||
eigenvects = eigenvects.rep.to_ddm()
|
||||
l = minpoly.gens[0]
|
||||
|
||||
eigenvects = [[field.to_sympy(x) for x in vect] for vect in eigenvects]
|
||||
|
||||
degree = minpoly.degree()
|
||||
minpoly = minpoly.as_expr()
|
||||
eigenvals = roots(minpoly, l, **kwargs)
|
||||
if len(eigenvals) != degree:
|
||||
eigenvals = [CRootOf(minpoly, l, idx) for idx in range(degree)]
|
||||
|
||||
for eigenvalue in eigenvals:
|
||||
new_eigenvects = [
|
||||
Matrix([x.subs(l, eigenvalue) for x in vect])
|
||||
for vect in eigenvects]
|
||||
result.append((eigenvalue, multiplicity, new_eigenvects))
|
||||
|
||||
return result
|
||||
@@ -0,0 +1,67 @@
|
||||
"""
|
||||
|
||||
Module to define exceptions to be used in sympy.polys.matrices modules and
|
||||
classes.
|
||||
|
||||
Ideally all exceptions raised in these modules would be defined and documented
|
||||
here and not e.g. imported from matrices. Also ideally generic exceptions like
|
||||
ValueError/TypeError would not be raised anywhere.
|
||||
|
||||
"""
|
||||
|
||||
|
||||
class DMError(Exception):
|
||||
"""Base class for errors raised by DomainMatrix"""
|
||||
pass
|
||||
|
||||
|
||||
class DMBadInputError(DMError):
|
||||
"""list of lists is inconsistent with shape"""
|
||||
pass
|
||||
|
||||
|
||||
class DMDomainError(DMError):
|
||||
"""domains do not match"""
|
||||
pass
|
||||
|
||||
|
||||
class DMNotAField(DMDomainError):
|
||||
"""domain is not a field"""
|
||||
pass
|
||||
|
||||
|
||||
class DMFormatError(DMError):
|
||||
"""mixed dense/sparse not supported"""
|
||||
pass
|
||||
|
||||
|
||||
class DMNonInvertibleMatrixError(DMError):
|
||||
"""The matrix in not invertible"""
|
||||
pass
|
||||
|
||||
|
||||
class DMRankError(DMError):
|
||||
"""matrix does not have expected rank"""
|
||||
pass
|
||||
|
||||
|
||||
class DMShapeError(DMError):
|
||||
"""shapes are inconsistent"""
|
||||
pass
|
||||
|
||||
|
||||
class DMNonSquareMatrixError(DMShapeError):
|
||||
"""The matrix is not square"""
|
||||
pass
|
||||
|
||||
|
||||
class DMValueError(DMError):
|
||||
"""The value passed is invalid"""
|
||||
pass
|
||||
|
||||
|
||||
__all__ = [
|
||||
'DMError', 'DMBadInputError', 'DMDomainError', 'DMFormatError',
|
||||
'DMRankError', 'DMShapeError', 'DMNotAField',
|
||||
'DMNonInvertibleMatrixError', 'DMNonSquareMatrixError', 'DMValueError'
|
||||
]
|
||||
@@ -0,0 +1,230 @@
|
||||
#
|
||||
# sympy.polys.matrices.linsolve module
|
||||
#
|
||||
# This module defines the _linsolve function which is the internal workhorse
|
||||
# used by linsolve. This computes the solution of a system of linear equations
|
||||
# using the SDM sparse matrix implementation in sympy.polys.matrices.sdm. This
|
||||
# is a replacement for solve_lin_sys in sympy.polys.solvers which is
|
||||
# inefficient for large sparse systems due to the use of a PolyRing with many
|
||||
# generators:
|
||||
#
|
||||
# https://github.com/sympy/sympy/issues/20857
|
||||
#
|
||||
# The implementation of _linsolve here handles:
|
||||
#
|
||||
# - Extracting the coefficients from the Expr/Eq input equations.
|
||||
# - Constructing a domain and converting the coefficients to
|
||||
# that domain.
|
||||
# - Using the SDM.rref, SDM.nullspace etc methods to generate the full
|
||||
# solution working with arithmetic only in the domain of the coefficients.
|
||||
#
|
||||
# The routines here are particularly designed to be efficient for large sparse
|
||||
# systems of linear equations although as well as dense systems. It is
|
||||
# possible that for some small dense systems solve_lin_sys which uses the
|
||||
# dense matrix implementation DDM will be more efficient. With smaller systems
|
||||
# though the bulk of the time is spent just preprocessing the inputs and the
|
||||
# relative time spent in rref is too small to be noticeable.
|
||||
#
|
||||
|
||||
from collections import defaultdict
|
||||
|
||||
from sympy.core.add import Add
|
||||
from sympy.core.mul import Mul
|
||||
from sympy.core.singleton import S
|
||||
|
||||
from sympy.polys.constructor import construct_domain
|
||||
from sympy.polys.solvers import PolyNonlinearError
|
||||
|
||||
from .sdm import (
|
||||
SDM,
|
||||
sdm_irref,
|
||||
sdm_particular_from_rref,
|
||||
sdm_nullspace_from_rref
|
||||
)
|
||||
|
||||
from sympy.utilities.misc import filldedent
|
||||
|
||||
|
||||
def _linsolve(eqs, syms):
|
||||
|
||||
"""Solve a linear system of equations.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
Solve a linear system with a unique solution:
|
||||
|
||||
>>> from sympy import symbols, Eq
|
||||
>>> from sympy.polys.matrices.linsolve import _linsolve
|
||||
>>> x, y = symbols('x, y')
|
||||
>>> eqs = [Eq(x + y, 1), Eq(x - y, 2)]
|
||||
>>> _linsolve(eqs, [x, y])
|
||||
{x: 3/2, y: -1/2}
|
||||
|
||||
In the case of underdetermined systems the solution will be expressed in
|
||||
terms of the unknown symbols that are unconstrained:
|
||||
|
||||
>>> _linsolve([Eq(x + y, 0)], [x, y])
|
||||
{x: -y, y: y}
|
||||
|
||||
"""
|
||||
# Number of unknowns (columns in the non-augmented matrix)
|
||||
nsyms = len(syms)
|
||||
|
||||
# Convert to sparse augmented matrix (len(eqs) x (nsyms+1))
|
||||
eqsdict, const = _linear_eq_to_dict(eqs, syms)
|
||||
Aaug = sympy_dict_to_dm(eqsdict, const, syms)
|
||||
K = Aaug.domain
|
||||
|
||||
# sdm_irref has issues with float matrices. This uses the ddm_rref()
|
||||
# function. When sdm_rref() can handle float matrices reasonably this
|
||||
# should be removed...
|
||||
if K.is_RealField or K.is_ComplexField:
|
||||
Aaug = Aaug.to_ddm().rref()[0].to_sdm()
|
||||
|
||||
# Compute reduced-row echelon form (RREF)
|
||||
Arref, pivots, nzcols = sdm_irref(Aaug)
|
||||
|
||||
# No solution:
|
||||
if pivots and pivots[-1] == nsyms:
|
||||
return None
|
||||
|
||||
# Particular solution for non-homogeneous system:
|
||||
P = sdm_particular_from_rref(Arref, nsyms+1, pivots)
|
||||
|
||||
# Nullspace - general solution to homogeneous system
|
||||
# Note: using nsyms not nsyms+1 to ignore last column
|
||||
V, nonpivots = sdm_nullspace_from_rref(Arref, K.one, nsyms, pivots, nzcols)
|
||||
|
||||
# Collect together terms from particular and nullspace:
|
||||
sol = defaultdict(list)
|
||||
for i, v in P.items():
|
||||
sol[syms[i]].append(K.to_sympy(v))
|
||||
for npi, Vi in zip(nonpivots, V):
|
||||
sym = syms[npi]
|
||||
for i, v in Vi.items():
|
||||
sol[syms[i]].append(sym * K.to_sympy(v))
|
||||
|
||||
# Use a single call to Add for each term:
|
||||
sol = {s: Add(*terms) for s, terms in sol.items()}
|
||||
|
||||
# Fill in the zeros:
|
||||
zero = S.Zero
|
||||
for s in set(syms) - set(sol):
|
||||
sol[s] = zero
|
||||
|
||||
# All done!
|
||||
return sol
|
||||
|
||||
|
||||
def sympy_dict_to_dm(eqs_coeffs, eqs_rhs, syms):
|
||||
"""Convert a system of dict equations to a sparse augmented matrix"""
|
||||
elems = set(eqs_rhs).union(*(e.values() for e in eqs_coeffs))
|
||||
K, elems_K = construct_domain(elems, field=True, extension=True)
|
||||
elem_map = dict(zip(elems, elems_K))
|
||||
neqs = len(eqs_coeffs)
|
||||
nsyms = len(syms)
|
||||
sym2index = dict(zip(syms, range(nsyms)))
|
||||
eqsdict = []
|
||||
for eq, rhs in zip(eqs_coeffs, eqs_rhs):
|
||||
eqdict = {sym2index[s]: elem_map[c] for s, c in eq.items()}
|
||||
if rhs:
|
||||
eqdict[nsyms] = -elem_map[rhs]
|
||||
if eqdict:
|
||||
eqsdict.append(eqdict)
|
||||
sdm_aug = SDM(enumerate(eqsdict), (neqs, nsyms + 1), K)
|
||||
return sdm_aug
|
||||
|
||||
|
||||
def _linear_eq_to_dict(eqs, syms):
|
||||
"""Convert a system Expr/Eq equations into dict form, returning
|
||||
the coefficient dictionaries and a list of syms-independent terms
|
||||
from each expression in ``eqs```.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.polys.matrices.linsolve import _linear_eq_to_dict
|
||||
>>> from sympy.abc import x
|
||||
>>> _linear_eq_to_dict([2*x + 3], {x})
|
||||
([{x: 2}], [3])
|
||||
"""
|
||||
coeffs = []
|
||||
ind = []
|
||||
symset = set(syms)
|
||||
for e in eqs:
|
||||
if e.is_Equality:
|
||||
coeff, terms = _lin_eq2dict(e.lhs, symset)
|
||||
cR, tR = _lin_eq2dict(e.rhs, symset)
|
||||
# there were no nonlinear errors so now
|
||||
# cancellation is allowed
|
||||
coeff -= cR
|
||||
for k, v in tR.items():
|
||||
if k in terms:
|
||||
terms[k] -= v
|
||||
else:
|
||||
terms[k] = -v
|
||||
# don't store coefficients of 0, however
|
||||
terms = {k: v for k, v in terms.items() if v}
|
||||
c, d = coeff, terms
|
||||
else:
|
||||
c, d = _lin_eq2dict(e, symset)
|
||||
coeffs.append(d)
|
||||
ind.append(c)
|
||||
return coeffs, ind
|
||||
|
||||
|
||||
def _lin_eq2dict(a, symset):
|
||||
"""return (c, d) where c is the sym-independent part of ``a`` and
|
||||
``d`` is an efficiently calculated dictionary mapping symbols to
|
||||
their coefficients. A PolyNonlinearError is raised if non-linearity
|
||||
is detected.
|
||||
|
||||
The values in the dictionary will be non-zero.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.polys.matrices.linsolve import _lin_eq2dict
|
||||
>>> from sympy.abc import x, y
|
||||
>>> _lin_eq2dict(x + 2*y + 3, {x, y})
|
||||
(3, {x: 1, y: 2})
|
||||
"""
|
||||
if a in symset:
|
||||
return S.Zero, {a: S.One}
|
||||
elif a.is_Add:
|
||||
terms_list = defaultdict(list)
|
||||
coeff_list = []
|
||||
for ai in a.args:
|
||||
ci, ti = _lin_eq2dict(ai, symset)
|
||||
coeff_list.append(ci)
|
||||
for mij, cij in ti.items():
|
||||
terms_list[mij].append(cij)
|
||||
coeff = Add(*coeff_list)
|
||||
terms = {sym: Add(*coeffs) for sym, coeffs in terms_list.items()}
|
||||
return coeff, terms
|
||||
elif a.is_Mul:
|
||||
terms = terms_coeff = None
|
||||
coeff_list = []
|
||||
for ai in a.args:
|
||||
ci, ti = _lin_eq2dict(ai, symset)
|
||||
if not ti:
|
||||
coeff_list.append(ci)
|
||||
elif terms is None:
|
||||
terms = ti
|
||||
terms_coeff = ci
|
||||
else:
|
||||
# since ti is not null and we already have
|
||||
# a term, this is a cross term
|
||||
raise PolyNonlinearError(filldedent('''
|
||||
nonlinear cross-term: %s''' % a))
|
||||
coeff = Mul._from_args(coeff_list)
|
||||
if terms is None:
|
||||
return coeff, {}
|
||||
else:
|
||||
terms = {sym: coeff * c for sym, c in terms.items()}
|
||||
return coeff * terms_coeff, terms
|
||||
elif not a.has_xfree(symset):
|
||||
return a, {}
|
||||
else:
|
||||
raise PolyNonlinearError('nonlinear term: %s' % a)
|
||||
@@ -0,0 +1,94 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from math import floor as mfloor
|
||||
|
||||
from sympy.polys.domains import ZZ, QQ
|
||||
from sympy.polys.matrices.exceptions import DMRankError, DMShapeError, DMValueError, DMDomainError
|
||||
|
||||
|
||||
def _ddm_lll(x, delta=QQ(3, 4), return_transform=False):
|
||||
if QQ(1, 4) >= delta or delta >= QQ(1, 1):
|
||||
raise DMValueError("delta must lie in range (0.25, 1)")
|
||||
if x.shape[0] > x.shape[1]:
|
||||
raise DMShapeError("input matrix must have shape (m, n) with m <= n")
|
||||
if x.domain != ZZ:
|
||||
raise DMDomainError("input matrix domain must be ZZ")
|
||||
m = x.shape[0]
|
||||
n = x.shape[1]
|
||||
k = 1
|
||||
y = x.copy()
|
||||
y_star = x.zeros((m, n), QQ)
|
||||
mu = x.zeros((m, m), QQ)
|
||||
g_star = [QQ(0, 1) for _ in range(m)]
|
||||
half = QQ(1, 2)
|
||||
T = x.eye(m, ZZ) if return_transform else None
|
||||
linear_dependent_error = "input matrix contains linearly dependent rows"
|
||||
|
||||
def closest_integer(x):
|
||||
return ZZ(mfloor(x + half))
|
||||
|
||||
def lovasz_condition(k: int) -> bool:
|
||||
return g_star[k] >= ((delta - mu[k][k - 1] ** 2) * g_star[k - 1])
|
||||
|
||||
def mu_small(k: int, j: int) -> bool:
|
||||
return abs(mu[k][j]) <= half
|
||||
|
||||
def dot_rows(x, y, rows: tuple[int, int]):
|
||||
return sum(x[rows[0]][z] * y[rows[1]][z] for z in range(x.shape[1]))
|
||||
|
||||
def reduce_row(T, mu, y, rows: tuple[int, int]):
|
||||
r = closest_integer(mu[rows[0]][rows[1]])
|
||||
y[rows[0]] = [y[rows[0]][z] - r * y[rows[1]][z] for z in range(n)]
|
||||
mu[rows[0]][:rows[1]] = [mu[rows[0]][z] - r * mu[rows[1]][z] for z in range(rows[1])]
|
||||
mu[rows[0]][rows[1]] -= r
|
||||
if return_transform:
|
||||
T[rows[0]] = [T[rows[0]][z] - r * T[rows[1]][z] for z in range(m)]
|
||||
|
||||
for i in range(m):
|
||||
y_star[i] = [QQ.convert_from(z, ZZ) for z in y[i]]
|
||||
for j in range(i):
|
||||
row_dot = dot_rows(y, y_star, (i, j))
|
||||
try:
|
||||
mu[i][j] = row_dot / g_star[j]
|
||||
except ZeroDivisionError:
|
||||
raise DMRankError(linear_dependent_error)
|
||||
y_star[i] = [y_star[i][z] - mu[i][j] * y_star[j][z] for z in range(n)]
|
||||
g_star[i] = dot_rows(y_star, y_star, (i, i))
|
||||
while k < m:
|
||||
if not mu_small(k, k - 1):
|
||||
reduce_row(T, mu, y, (k, k - 1))
|
||||
if lovasz_condition(k):
|
||||
for l in range(k - 2, -1, -1):
|
||||
if not mu_small(k, l):
|
||||
reduce_row(T, mu, y, (k, l))
|
||||
k += 1
|
||||
else:
|
||||
nu = mu[k][k - 1]
|
||||
alpha = g_star[k] + nu ** 2 * g_star[k - 1]
|
||||
try:
|
||||
beta = g_star[k - 1] / alpha
|
||||
except ZeroDivisionError:
|
||||
raise DMRankError(linear_dependent_error)
|
||||
mu[k][k - 1] = nu * beta
|
||||
g_star[k] = g_star[k] * beta
|
||||
g_star[k - 1] = alpha
|
||||
y[k], y[k - 1] = y[k - 1], y[k]
|
||||
mu[k][:k - 1], mu[k - 1][:k - 1] = mu[k - 1][:k - 1], mu[k][:k - 1]
|
||||
for i in range(k + 1, m):
|
||||
xi = mu[i][k]
|
||||
mu[i][k] = mu[i][k - 1] - nu * xi
|
||||
mu[i][k - 1] = mu[k][k - 1] * mu[i][k] + xi
|
||||
if return_transform:
|
||||
T[k], T[k - 1] = T[k - 1], T[k]
|
||||
k = max(k - 1, 1)
|
||||
assert all(lovasz_condition(i) for i in range(1, m))
|
||||
assert all(mu_small(i, j) for i in range(m) for j in range(i))
|
||||
return y, T
|
||||
|
||||
|
||||
def ddm_lll(x, delta=QQ(3, 4)):
|
||||
return _ddm_lll(x, delta=delta, return_transform=False)[0]
|
||||
|
||||
|
||||
def ddm_lll_transform(x, delta=QQ(3, 4)):
|
||||
return _ddm_lll(x, delta=delta, return_transform=True)
|
||||
@@ -0,0 +1,540 @@
|
||||
'''Functions returning normal forms of matrices'''
|
||||
|
||||
from collections import defaultdict
|
||||
|
||||
from .domainmatrix import DomainMatrix
|
||||
from .exceptions import DMDomainError, DMShapeError
|
||||
from sympy.ntheory.modular import symmetric_residue
|
||||
from sympy.polys.domains import QQ, ZZ
|
||||
|
||||
|
||||
# TODO (future work):
|
||||
# There are faster algorithms for Smith and Hermite normal forms, which
|
||||
# we should implement. See e.g. the Kannan-Bachem algorithm:
|
||||
# <https://www.researchgate.net/publication/220617516_Polynomial_Algorithms_for_Computing_the_Smith_and_Hermite_Normal_Forms_of_an_Integer_Matrix>
|
||||
|
||||
|
||||
def smith_normal_form(m):
|
||||
'''
|
||||
Return the Smith Normal Form of a matrix `m` over the ring `domain`.
|
||||
This will only work if the ring is a principal ideal domain.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy import ZZ
|
||||
>>> from sympy.polys.matrices import DomainMatrix
|
||||
>>> from sympy.polys.matrices.normalforms import smith_normal_form
|
||||
>>> m = DomainMatrix([[ZZ(12), ZZ(6), ZZ(4)],
|
||||
... [ZZ(3), ZZ(9), ZZ(6)],
|
||||
... [ZZ(2), ZZ(16), ZZ(14)]], (3, 3), ZZ)
|
||||
>>> print(smith_normal_form(m).to_Matrix())
|
||||
Matrix([[1, 0, 0], [0, 10, 0], [0, 0, 30]])
|
||||
|
||||
'''
|
||||
invs = invariant_factors(m)
|
||||
smf = DomainMatrix.diag(invs, m.domain, m.shape)
|
||||
return smf
|
||||
|
||||
|
||||
def is_smith_normal_form(m):
|
||||
'''
|
||||
Checks that the matrix is in Smith Normal Form
|
||||
'''
|
||||
domain = m.domain
|
||||
shape = m.shape
|
||||
zero = domain.zero
|
||||
m = m.to_list()
|
||||
|
||||
for i in range(shape[0]):
|
||||
for j in range(shape[1]):
|
||||
if i == j:
|
||||
continue
|
||||
if not m[i][j] == zero:
|
||||
return False
|
||||
|
||||
upper = min(shape[0], shape[1])
|
||||
for i in range(1, upper):
|
||||
if m[i-1][i-1] == zero:
|
||||
if m[i][i] != zero:
|
||||
return False
|
||||
else:
|
||||
r = domain.div(m[i][i], m[i-1][i-1])[1]
|
||||
if r != zero:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def add_columns(m, i, j, a, b, c, d):
|
||||
# replace m[:, i] by a*m[:, i] + b*m[:, j]
|
||||
# and m[:, j] by c*m[:, i] + d*m[:, j]
|
||||
for k in range(len(m)):
|
||||
e = m[k][i]
|
||||
m[k][i] = a*e + b*m[k][j]
|
||||
m[k][j] = c*e + d*m[k][j]
|
||||
|
||||
|
||||
def invariant_factors(m):
|
||||
'''
|
||||
Return the tuple of abelian invariants for a matrix `m`
|
||||
(as in the Smith-Normal form)
|
||||
|
||||
References
|
||||
==========
|
||||
|
||||
[1] https://en.wikipedia.org/wiki/Smith_normal_form#Algorithm
|
||||
[2] https://web.archive.org/web/20200331143852/https://sierra.nmsu.edu/morandi/notes/SmithNormalForm.pdf
|
||||
|
||||
'''
|
||||
domain = m.domain
|
||||
shape = m.shape
|
||||
m = m.to_list()
|
||||
return _smith_normal_decomp(m, domain, shape=shape, full=False)
|
||||
|
||||
|
||||
def smith_normal_decomp(m):
|
||||
'''
|
||||
Return the Smith-Normal form decomposition of matrix `m`.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy import ZZ
|
||||
>>> from sympy.polys.matrices import DomainMatrix
|
||||
>>> from sympy.polys.matrices.normalforms import smith_normal_decomp
|
||||
>>> m = DomainMatrix([[ZZ(12), ZZ(6), ZZ(4)],
|
||||
... [ZZ(3), ZZ(9), ZZ(6)],
|
||||
... [ZZ(2), ZZ(16), ZZ(14)]], (3, 3), ZZ)
|
||||
>>> a, s, t = smith_normal_decomp(m)
|
||||
>>> assert a == s * m * t
|
||||
'''
|
||||
domain = m.domain
|
||||
rows, cols = shape = m.shape
|
||||
m = m.to_list()
|
||||
|
||||
invs, s, t = _smith_normal_decomp(m, domain, shape=shape, full=True)
|
||||
smf = DomainMatrix.diag(invs, domain, shape).to_dense()
|
||||
|
||||
s = DomainMatrix(s, domain=domain, shape=(rows, rows))
|
||||
t = DomainMatrix(t, domain=domain, shape=(cols, cols))
|
||||
return smf, s, t
|
||||
|
||||
|
||||
def _smith_normal_decomp(m, domain, shape, full):
|
||||
'''
|
||||
Return the tuple of abelian invariants for a matrix `m`
|
||||
(as in the Smith-Normal form). If `full=True` then invertible matrices
|
||||
``s, t`` such that the product ``s, m, t`` is the Smith Normal Form
|
||||
are also returned.
|
||||
'''
|
||||
if not domain.is_PID:
|
||||
msg = f"The matrix entries must be over a principal ideal domain, but got {domain}"
|
||||
raise ValueError(msg)
|
||||
|
||||
rows, cols = shape
|
||||
zero = domain.zero
|
||||
one = domain.one
|
||||
|
||||
def eye(n):
|
||||
return [[one if i == j else zero for i in range(n)] for j in range(n)]
|
||||
|
||||
if 0 in shape:
|
||||
if full:
|
||||
return (), eye(rows), eye(cols)
|
||||
else:
|
||||
return ()
|
||||
|
||||
if full:
|
||||
s = eye(rows)
|
||||
t = eye(cols)
|
||||
|
||||
def add_rows(m, i, j, a, b, c, d):
|
||||
# replace m[i, :] by a*m[i, :] + b*m[j, :]
|
||||
# and m[j, :] by c*m[i, :] + d*m[j, :]
|
||||
for k in range(len(m[0])):
|
||||
e = m[i][k]
|
||||
m[i][k] = a*e + b*m[j][k]
|
||||
m[j][k] = c*e + d*m[j][k]
|
||||
|
||||
def clear_column():
|
||||
# make m[1:, 0] zero by row and column operations
|
||||
pivot = m[0][0]
|
||||
for j in range(1, rows):
|
||||
if m[j][0] == zero:
|
||||
continue
|
||||
d, r = domain.div(m[j][0], pivot)
|
||||
if r == zero:
|
||||
add_rows(m, 0, j, 1, 0, -d, 1)
|
||||
if full:
|
||||
add_rows(s, 0, j, 1, 0, -d, 1)
|
||||
else:
|
||||
a, b, g = domain.gcdex(pivot, m[j][0])
|
||||
d_0 = domain.exquo(m[j][0], g)
|
||||
d_j = domain.exquo(pivot, g)
|
||||
add_rows(m, 0, j, a, b, d_0, -d_j)
|
||||
if full:
|
||||
add_rows(s, 0, j, a, b, d_0, -d_j)
|
||||
pivot = g
|
||||
|
||||
def clear_row():
|
||||
# make m[0, 1:] zero by row and column operations
|
||||
pivot = m[0][0]
|
||||
for j in range(1, cols):
|
||||
if m[0][j] == zero:
|
||||
continue
|
||||
d, r = domain.div(m[0][j], pivot)
|
||||
if r == zero:
|
||||
add_columns(m, 0, j, 1, 0, -d, 1)
|
||||
if full:
|
||||
add_columns(t, 0, j, 1, 0, -d, 1)
|
||||
else:
|
||||
a, b, g = domain.gcdex(pivot, m[0][j])
|
||||
d_0 = domain.exquo(m[0][j], g)
|
||||
d_j = domain.exquo(pivot, g)
|
||||
add_columns(m, 0, j, a, b, d_0, -d_j)
|
||||
if full:
|
||||
add_columns(t, 0, j, a, b, d_0, -d_j)
|
||||
pivot = g
|
||||
|
||||
# permute the rows and columns until m[0,0] is non-zero if possible
|
||||
ind = [i for i in range(rows) if m[i][0] != zero]
|
||||
if ind and ind[0] != zero:
|
||||
m[0], m[ind[0]] = m[ind[0]], m[0]
|
||||
if full:
|
||||
s[0], s[ind[0]] = s[ind[0]], s[0]
|
||||
else:
|
||||
ind = [j for j in range(cols) if m[0][j] != zero]
|
||||
if ind and ind[0] != zero:
|
||||
for row in m:
|
||||
row[0], row[ind[0]] = row[ind[0]], row[0]
|
||||
if full:
|
||||
for row in t:
|
||||
row[0], row[ind[0]] = row[ind[0]], row[0]
|
||||
|
||||
# make the first row and column except m[0,0] zero
|
||||
while (any(m[0][i] != zero for i in range(1,cols)) or
|
||||
any(m[i][0] != zero for i in range(1,rows))):
|
||||
clear_column()
|
||||
clear_row()
|
||||
|
||||
def to_domain_matrix(m):
|
||||
return DomainMatrix(m, shape=(len(m), len(m[0])), domain=domain)
|
||||
|
||||
if m[0][0] != 0:
|
||||
c = domain.canonical_unit(m[0][0])
|
||||
if domain.is_Field:
|
||||
c = 1 / m[0][0]
|
||||
if c != domain.one:
|
||||
m[0][0] *= c
|
||||
if full:
|
||||
s[0] = [elem * c for elem in s[0]]
|
||||
|
||||
if 1 in shape:
|
||||
invs = ()
|
||||
else:
|
||||
lower_right = [r[1:] for r in m[1:]]
|
||||
ret = _smith_normal_decomp(lower_right, domain,
|
||||
shape=(rows - 1, cols - 1), full=full)
|
||||
if full:
|
||||
invs, s_small, t_small = ret
|
||||
s2 = [[1] + [0]*(rows-1)] + [[0] + row for row in s_small]
|
||||
t2 = [[1] + [0]*(cols-1)] + [[0] + row for row in t_small]
|
||||
s, s2, t, t2 = list(map(to_domain_matrix, [s, s2, t, t2]))
|
||||
s = s2 * s
|
||||
t = t * t2
|
||||
s = s.to_list()
|
||||
t = t.to_list()
|
||||
else:
|
||||
invs = ret
|
||||
|
||||
if m[0][0]:
|
||||
result = [m[0][0]]
|
||||
result.extend(invs)
|
||||
# in case m[0] doesn't divide the invariants of the rest of the matrix
|
||||
for i in range(len(result)-1):
|
||||
a, b = result[i], result[i+1]
|
||||
if b and domain.div(b, a)[1] != zero:
|
||||
if full:
|
||||
x, y, d = domain.gcdex(a, b)
|
||||
else:
|
||||
d = domain.gcd(a, b)
|
||||
|
||||
alpha = domain.div(a, d)[0]
|
||||
if full:
|
||||
beta = domain.div(b, d)[0]
|
||||
add_rows(s, i, i + 1, 1, 0, x, 1)
|
||||
add_columns(t, i, i + 1, 1, y, 0, 1)
|
||||
add_rows(s, i, i + 1, 1, -alpha, 0, 1)
|
||||
add_columns(t, i, i + 1, 1, 0, -beta, 1)
|
||||
add_rows(s, i, i + 1, 0, 1, -1, 0)
|
||||
|
||||
result[i+1] = b * alpha
|
||||
result[i] = d
|
||||
else:
|
||||
break
|
||||
else:
|
||||
if full:
|
||||
if rows > 1:
|
||||
s = s[1:] + [s[0]]
|
||||
if cols > 1:
|
||||
t = [row[1:] + [row[0]] for row in t]
|
||||
result = invs + (m[0][0],)
|
||||
|
||||
if full:
|
||||
return tuple(result), s, t
|
||||
else:
|
||||
return tuple(result)
|
||||
|
||||
|
||||
def _gcdex(a, b):
|
||||
r"""
|
||||
This supports the functions that compute Hermite Normal Form.
|
||||
|
||||
Explanation
|
||||
===========
|
||||
|
||||
Let x, y be the coefficients returned by the extended Euclidean
|
||||
Algorithm, so that x*a + y*b = g. In the algorithms for computing HNF,
|
||||
it is critical that x, y not only satisfy the condition of being small
|
||||
in magnitude -- namely that |x| <= |b|/g, |y| <- |a|/g -- but also that
|
||||
y == 0 when a | b.
|
||||
|
||||
"""
|
||||
x, y, g = ZZ.gcdex(a, b)
|
||||
if a != 0 and b % a == 0:
|
||||
y = 0
|
||||
x = -1 if a < 0 else 1
|
||||
return x, y, g
|
||||
|
||||
|
||||
def _hermite_normal_form(A):
|
||||
r"""
|
||||
Compute the Hermite Normal Form of DomainMatrix *A* over :ref:`ZZ`.
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
A : :py:class:`~.DomainMatrix` over domain :ref:`ZZ`.
|
||||
|
||||
Returns
|
||||
=======
|
||||
|
||||
:py:class:`~.DomainMatrix`
|
||||
The HNF of matrix *A*.
|
||||
|
||||
Raises
|
||||
======
|
||||
|
||||
DMDomainError
|
||||
If the domain of the matrix is not :ref:`ZZ`.
|
||||
|
||||
References
|
||||
==========
|
||||
|
||||
.. [1] Cohen, H. *A Course in Computational Algebraic Number Theory.*
|
||||
(See Algorithm 2.4.5.)
|
||||
|
||||
"""
|
||||
if not A.domain.is_ZZ:
|
||||
raise DMDomainError('Matrix must be over domain ZZ.')
|
||||
# We work one row at a time, starting from the bottom row, and working our
|
||||
# way up.
|
||||
m, n = A.shape
|
||||
A = A.to_ddm().copy()
|
||||
# Our goal is to put pivot entries in the rightmost columns.
|
||||
# Invariant: Before processing each row, k should be the index of the
|
||||
# leftmost column in which we have so far put a pivot.
|
||||
k = n
|
||||
for i in range(m - 1, -1, -1):
|
||||
if k == 0:
|
||||
# This case can arise when n < m and we've already found n pivots.
|
||||
# We don't need to consider any more rows, because this is already
|
||||
# the maximum possible number of pivots.
|
||||
break
|
||||
k -= 1
|
||||
# k now points to the column in which we want to put a pivot.
|
||||
# We want zeros in all entries to the left of the pivot column.
|
||||
for j in range(k - 1, -1, -1):
|
||||
if A[i][j] != 0:
|
||||
# Replace cols j, k by lin combs of these cols such that, in row i,
|
||||
# col j has 0, while col k has the gcd of their row i entries. Note
|
||||
# that this ensures a nonzero entry in col k.
|
||||
u, v, d = _gcdex(A[i][k], A[i][j])
|
||||
r, s = A[i][k] // d, A[i][j] // d
|
||||
add_columns(A, k, j, u, v, -s, r)
|
||||
b = A[i][k]
|
||||
# Do not want the pivot entry to be negative.
|
||||
if b < 0:
|
||||
add_columns(A, k, k, -1, 0, -1, 0)
|
||||
b = -b
|
||||
# The pivot entry will be 0 iff the row was 0 from the pivot col all the
|
||||
# way to the left. In this case, we are still working on the same pivot
|
||||
# col for the next row. Therefore:
|
||||
if b == 0:
|
||||
k += 1
|
||||
# If the pivot entry is nonzero, then we want to reduce all entries to its
|
||||
# right in the sense of the division algorithm, i.e. make them all remainders
|
||||
# w.r.t. the pivot as divisor.
|
||||
else:
|
||||
for j in range(k + 1, n):
|
||||
q = A[i][j] // b
|
||||
add_columns(A, j, k, 1, -q, 0, 1)
|
||||
# Finally, the HNF consists of those columns of A in which we succeeded in making
|
||||
# a nonzero pivot.
|
||||
return DomainMatrix.from_rep(A.to_dfm_or_ddm())[:, k:]
|
||||
|
||||
|
||||
def _hermite_normal_form_modulo_D(A, D):
|
||||
r"""
|
||||
Perform the mod *D* Hermite Normal Form reduction algorithm on
|
||||
:py:class:`~.DomainMatrix` *A*.
|
||||
|
||||
Explanation
|
||||
===========
|
||||
|
||||
If *A* is an $m \times n$ matrix of rank $m$, having Hermite Normal Form
|
||||
$W$, and if *D* is any positive integer known in advance to be a multiple
|
||||
of $\det(W)$, then the HNF of *A* can be computed by an algorithm that
|
||||
works mod *D* in order to prevent coefficient explosion.
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
A : :py:class:`~.DomainMatrix` over :ref:`ZZ`
|
||||
$m \times n$ matrix, having rank $m$.
|
||||
D : :ref:`ZZ`
|
||||
Positive integer, known to be a multiple of the determinant of the
|
||||
HNF of *A*.
|
||||
|
||||
Returns
|
||||
=======
|
||||
|
||||
:py:class:`~.DomainMatrix`
|
||||
The HNF of matrix *A*.
|
||||
|
||||
Raises
|
||||
======
|
||||
|
||||
DMDomainError
|
||||
If the domain of the matrix is not :ref:`ZZ`, or
|
||||
if *D* is given but is not in :ref:`ZZ`.
|
||||
|
||||
DMShapeError
|
||||
If the matrix has more rows than columns.
|
||||
|
||||
References
|
||||
==========
|
||||
|
||||
.. [1] Cohen, H. *A Course in Computational Algebraic Number Theory.*
|
||||
(See Algorithm 2.4.8.)
|
||||
|
||||
"""
|
||||
if not A.domain.is_ZZ:
|
||||
raise DMDomainError('Matrix must be over domain ZZ.')
|
||||
if not ZZ.of_type(D) or D < 1:
|
||||
raise DMDomainError('Modulus D must be positive element of domain ZZ.')
|
||||
|
||||
def add_columns_mod_R(m, R, i, j, a, b, c, d):
|
||||
# replace m[:, i] by (a*m[:, i] + b*m[:, j]) % R
|
||||
# and m[:, j] by (c*m[:, i] + d*m[:, j]) % R
|
||||
for k in range(len(m)):
|
||||
e = m[k][i]
|
||||
m[k][i] = symmetric_residue((a * e + b * m[k][j]) % R, R)
|
||||
m[k][j] = symmetric_residue((c * e + d * m[k][j]) % R, R)
|
||||
|
||||
W = defaultdict(dict)
|
||||
|
||||
m, n = A.shape
|
||||
if n < m:
|
||||
raise DMShapeError('Matrix must have at least as many columns as rows.')
|
||||
A = A.to_list()
|
||||
k = n
|
||||
R = D
|
||||
for i in range(m - 1, -1, -1):
|
||||
k -= 1
|
||||
for j in range(k - 1, -1, -1):
|
||||
if A[i][j] != 0:
|
||||
u, v, d = _gcdex(A[i][k], A[i][j])
|
||||
r, s = A[i][k] // d, A[i][j] // d
|
||||
add_columns_mod_R(A, R, k, j, u, v, -s, r)
|
||||
b = A[i][k]
|
||||
if b == 0:
|
||||
A[i][k] = b = R
|
||||
u, v, d = _gcdex(b, R)
|
||||
for ii in range(m):
|
||||
W[ii][i] = u*A[ii][k] % R
|
||||
if W[i][i] == 0:
|
||||
W[i][i] = R
|
||||
for j in range(i + 1, m):
|
||||
q = W[i][j] // W[i][i]
|
||||
add_columns(W, j, i, 1, -q, 0, 1)
|
||||
R //= d
|
||||
return DomainMatrix(W, (m, m), ZZ).to_dense()
|
||||
|
||||
|
||||
def hermite_normal_form(A, *, D=None, check_rank=False):
|
||||
r"""
|
||||
Compute the Hermite Normal Form of :py:class:`~.DomainMatrix` *A* over
|
||||
:ref:`ZZ`.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy import ZZ
|
||||
>>> from sympy.polys.matrices import DomainMatrix
|
||||
>>> from sympy.polys.matrices.normalforms import hermite_normal_form
|
||||
>>> m = DomainMatrix([[ZZ(12), ZZ(6), ZZ(4)],
|
||||
... [ZZ(3), ZZ(9), ZZ(6)],
|
||||
... [ZZ(2), ZZ(16), ZZ(14)]], (3, 3), ZZ)
|
||||
>>> print(hermite_normal_form(m).to_Matrix())
|
||||
Matrix([[10, 0, 2], [0, 15, 3], [0, 0, 2]])
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
A : $m \times n$ ``DomainMatrix`` over :ref:`ZZ`.
|
||||
|
||||
D : :ref:`ZZ`, optional
|
||||
Let $W$ be the HNF of *A*. If known in advance, a positive integer *D*
|
||||
being any multiple of $\det(W)$ may be provided. In this case, if *A*
|
||||
also has rank $m$, then we may use an alternative algorithm that works
|
||||
mod *D* in order to prevent coefficient explosion.
|
||||
|
||||
check_rank : boolean, optional (default=False)
|
||||
The basic assumption is that, if you pass a value for *D*, then
|
||||
you already believe that *A* has rank $m$, so we do not waste time
|
||||
checking it for you. If you do want this to be checked (and the
|
||||
ordinary, non-modulo *D* algorithm to be used if the check fails), then
|
||||
set *check_rank* to ``True``.
|
||||
|
||||
Returns
|
||||
=======
|
||||
|
||||
:py:class:`~.DomainMatrix`
|
||||
The HNF of matrix *A*.
|
||||
|
||||
Raises
|
||||
======
|
||||
|
||||
DMDomainError
|
||||
If the domain of the matrix is not :ref:`ZZ`, or
|
||||
if *D* is given but is not in :ref:`ZZ`.
|
||||
|
||||
DMShapeError
|
||||
If the mod *D* algorithm is used but the matrix has more rows than
|
||||
columns.
|
||||
|
||||
References
|
||||
==========
|
||||
|
||||
.. [1] Cohen, H. *A Course in Computational Algebraic Number Theory.*
|
||||
(See Algorithms 2.4.5 and 2.4.8.)
|
||||
|
||||
"""
|
||||
if not A.domain.is_ZZ:
|
||||
raise DMDomainError('Matrix must be over domain ZZ.')
|
||||
if D is not None and (not check_rank or A.convert_to(QQ).rank() == A.shape[0]):
|
||||
return _hermite_normal_form_modulo_D(A, D)
|
||||
else:
|
||||
return _hermite_normal_form(A)
|
||||
422
venv/lib/python3.12/site-packages/sympy/polys/matrices/rref.py
Normal file
422
venv/lib/python3.12/site-packages/sympy/polys/matrices/rref.py
Normal file
@@ -0,0 +1,422 @@
|
||||
# Algorithms for computing the reduced row echelon form of a matrix.
|
||||
#
|
||||
# We need to choose carefully which algorithms to use depending on the domain,
|
||||
# shape, and sparsity of the matrix as well as things like the bit count in the
|
||||
# case of ZZ or QQ. This is important because the algorithms have different
|
||||
# performance characteristics in the extremes of dense vs sparse.
|
||||
#
|
||||
# In all cases we use the sparse implementations but we need to choose between
|
||||
# Gauss-Jordan elimination with division and fraction-free Gauss-Jordan
|
||||
# elimination. For very sparse matrices over ZZ with low bit counts it is
|
||||
# asymptotically faster to use Gauss-Jordan elimination with division. For
|
||||
# dense matrices with high bit counts it is asymptotically faster to use
|
||||
# fraction-free Gauss-Jordan.
|
||||
#
|
||||
# The most important thing is to get the extreme cases right because it can
|
||||
# make a big difference. In between the extremes though we have to make a
|
||||
# choice and here we use empirically determined thresholds based on timings
|
||||
# with random sparse matrices.
|
||||
#
|
||||
# In the case of QQ we have to consider the denominators as well. If the
|
||||
# denominators are small then it is faster to clear them and use fraction-free
|
||||
# Gauss-Jordan over ZZ. If the denominators are large then it is faster to use
|
||||
# Gauss-Jordan elimination with division over QQ.
|
||||
#
|
||||
# Timings for the various algorithms can be found at
|
||||
#
|
||||
# https://github.com/sympy/sympy/issues/25410
|
||||
# https://github.com/sympy/sympy/pull/25443
|
||||
|
||||
from sympy.polys.domains import ZZ
|
||||
|
||||
from sympy.polys.matrices.sdm import SDM, sdm_irref, sdm_rref_den
|
||||
from sympy.polys.matrices.ddm import DDM
|
||||
from sympy.polys.matrices.dense import ddm_irref, ddm_irref_den
|
||||
|
||||
|
||||
def _dm_rref(M, *, method='auto'):
|
||||
"""
|
||||
Compute the reduced row echelon form of a ``DomainMatrix``.
|
||||
|
||||
This function is the implementation of :meth:`DomainMatrix.rref`.
|
||||
|
||||
Chooses the best algorithm depending on the domain, shape, and sparsity of
|
||||
the matrix as well as things like the bit count in the case of :ref:`ZZ` or
|
||||
:ref:`QQ`. The result is returned over the field associated with the domain
|
||||
of the Matrix.
|
||||
|
||||
See Also
|
||||
========
|
||||
|
||||
sympy.polys.matrices.domainmatrix.DomainMatrix.rref
|
||||
The ``DomainMatrix`` method that calls this function.
|
||||
sympy.polys.matrices.rref._dm_rref_den
|
||||
Alternative function for computing RREF with denominator.
|
||||
"""
|
||||
method, use_fmt = _dm_rref_choose_method(M, method, denominator=False)
|
||||
|
||||
M, old_fmt = _dm_to_fmt(M, use_fmt)
|
||||
|
||||
if method == 'GJ':
|
||||
# Use Gauss-Jordan with division over the associated field.
|
||||
Mf = _to_field(M)
|
||||
M_rref, pivots = _dm_rref_GJ(Mf)
|
||||
|
||||
elif method == 'FF':
|
||||
# Use fraction-free GJ over the current domain.
|
||||
M_rref_f, den, pivots = _dm_rref_den_FF(M)
|
||||
M_rref = _to_field(M_rref_f) / den
|
||||
|
||||
elif method == 'CD':
|
||||
# Clear denominators and use fraction-free GJ in the associated ring.
|
||||
_, Mr = M.clear_denoms_rowwise(convert=True)
|
||||
M_rref_f, den, pivots = _dm_rref_den_FF(Mr)
|
||||
M_rref = _to_field(M_rref_f) / den
|
||||
|
||||
else:
|
||||
raise ValueError(f"Unknown method for rref: {method}")
|
||||
|
||||
M_rref, _ = _dm_to_fmt(M_rref, old_fmt)
|
||||
|
||||
# Invariants:
|
||||
# - M_rref is in the same format (sparse or dense) as the input matrix.
|
||||
# - M_rref is in the associated field domain and any denominator was
|
||||
# divided in (so is implicitly 1 now).
|
||||
|
||||
return M_rref, pivots
|
||||
|
||||
|
||||
def _dm_rref_den(M, *, keep_domain=True, method='auto'):
|
||||
"""
|
||||
Compute the reduced row echelon form of a ``DomainMatrix`` with denominator.
|
||||
|
||||
This function is the implementation of :meth:`DomainMatrix.rref_den`.
|
||||
|
||||
Chooses the best algorithm depending on the domain, shape, and sparsity of
|
||||
the matrix as well as things like the bit count in the case of :ref:`ZZ` or
|
||||
:ref:`QQ`. The result is returned over the same domain as the input matrix
|
||||
unless ``keep_domain=False`` in which case the result might be over an
|
||||
associated ring or field domain.
|
||||
|
||||
See Also
|
||||
========
|
||||
|
||||
sympy.polys.matrices.domainmatrix.DomainMatrix.rref_den
|
||||
The ``DomainMatrix`` method that calls this function.
|
||||
sympy.polys.matrices.rref._dm_rref
|
||||
Alternative function for computing RREF without denominator.
|
||||
"""
|
||||
method, use_fmt = _dm_rref_choose_method(M, method, denominator=True)
|
||||
|
||||
M, old_fmt = _dm_to_fmt(M, use_fmt)
|
||||
|
||||
if method == 'FF':
|
||||
# Use fraction-free GJ over the current domain.
|
||||
M_rref, den, pivots = _dm_rref_den_FF(M)
|
||||
|
||||
elif method == 'GJ':
|
||||
# Use Gauss-Jordan with division over the associated field.
|
||||
M_rref_f, pivots = _dm_rref_GJ(_to_field(M))
|
||||
|
||||
# Convert back to the ring?
|
||||
if keep_domain and M_rref_f.domain != M.domain:
|
||||
_, M_rref = M_rref_f.clear_denoms(convert=True)
|
||||
|
||||
if pivots:
|
||||
den = M_rref[0, pivots[0]].element
|
||||
else:
|
||||
den = M_rref.domain.one
|
||||
else:
|
||||
# Possibly an associated field
|
||||
M_rref = M_rref_f
|
||||
den = M_rref.domain.one
|
||||
|
||||
elif method == 'CD':
|
||||
# Clear denominators and use fraction-free GJ in the associated ring.
|
||||
_, Mr = M.clear_denoms_rowwise(convert=True)
|
||||
|
||||
M_rref_r, den, pivots = _dm_rref_den_FF(Mr)
|
||||
|
||||
if keep_domain and M_rref_r.domain != M.domain:
|
||||
# Convert back to the field
|
||||
M_rref = _to_field(M_rref_r) / den
|
||||
den = M.domain.one
|
||||
else:
|
||||
# Possibly an associated ring
|
||||
M_rref = M_rref_r
|
||||
|
||||
if pivots:
|
||||
den = M_rref[0, pivots[0]].element
|
||||
else:
|
||||
den = M_rref.domain.one
|
||||
else:
|
||||
raise ValueError(f"Unknown method for rref: {method}")
|
||||
|
||||
M_rref, _ = _dm_to_fmt(M_rref, old_fmt)
|
||||
|
||||
# Invariants:
|
||||
# - M_rref is in the same format (sparse or dense) as the input matrix.
|
||||
# - If keep_domain=True then M_rref and den are in the same domain as the
|
||||
# input matrix
|
||||
# - If keep_domain=False then M_rref might be in an associated ring or
|
||||
# field domain but den is always in the same domain as M_rref.
|
||||
|
||||
return M_rref, den, pivots
|
||||
|
||||
|
||||
def _dm_to_fmt(M, fmt):
|
||||
"""Convert a matrix to the given format and return the old format."""
|
||||
old_fmt = M.rep.fmt
|
||||
if old_fmt == fmt:
|
||||
pass
|
||||
elif fmt == 'dense':
|
||||
M = M.to_dense()
|
||||
elif fmt == 'sparse':
|
||||
M = M.to_sparse()
|
||||
else:
|
||||
raise ValueError(f'Unknown format: {fmt}') # pragma: no cover
|
||||
return M, old_fmt
|
||||
|
||||
|
||||
# These are the four basic implementations that we want to choose between:
|
||||
|
||||
|
||||
def _dm_rref_GJ(M):
|
||||
"""Compute RREF using Gauss-Jordan elimination with division."""
|
||||
if M.rep.fmt == 'sparse':
|
||||
return _dm_rref_GJ_sparse(M)
|
||||
else:
|
||||
return _dm_rref_GJ_dense(M)
|
||||
|
||||
|
||||
def _dm_rref_den_FF(M):
|
||||
"""Compute RREF using fraction-free Gauss-Jordan elimination."""
|
||||
if M.rep.fmt == 'sparse':
|
||||
return _dm_rref_den_FF_sparse(M)
|
||||
else:
|
||||
return _dm_rref_den_FF_dense(M)
|
||||
|
||||
|
||||
def _dm_rref_GJ_sparse(M):
|
||||
"""Compute RREF using sparse Gauss-Jordan elimination with division."""
|
||||
M_rref_d, pivots, _ = sdm_irref(M.rep)
|
||||
M_rref_sdm = SDM(M_rref_d, M.shape, M.domain)
|
||||
pivots = tuple(pivots)
|
||||
return M.from_rep(M_rref_sdm), pivots
|
||||
|
||||
|
||||
def _dm_rref_GJ_dense(M):
|
||||
"""Compute RREF using dense Gauss-Jordan elimination with division."""
|
||||
partial_pivot = M.domain.is_RR or M.domain.is_CC
|
||||
ddm = M.rep.to_ddm().copy()
|
||||
pivots = ddm_irref(ddm, _partial_pivot=partial_pivot)
|
||||
M_rref_ddm = DDM(ddm, M.shape, M.domain)
|
||||
pivots = tuple(pivots)
|
||||
return M.from_rep(M_rref_ddm.to_dfm_or_ddm()), pivots
|
||||
|
||||
|
||||
def _dm_rref_den_FF_sparse(M):
|
||||
"""Compute RREF using sparse fraction-free Gauss-Jordan elimination."""
|
||||
M_rref_d, den, pivots = sdm_rref_den(M.rep, M.domain)
|
||||
M_rref_sdm = SDM(M_rref_d, M.shape, M.domain)
|
||||
pivots = tuple(pivots)
|
||||
return M.from_rep(M_rref_sdm), den, pivots
|
||||
|
||||
|
||||
def _dm_rref_den_FF_dense(M):
|
||||
"""Compute RREF using sparse fraction-free Gauss-Jordan elimination."""
|
||||
ddm = M.rep.to_ddm().copy()
|
||||
den, pivots = ddm_irref_den(ddm, M.domain)
|
||||
M_rref_ddm = DDM(ddm, M.shape, M.domain)
|
||||
pivots = tuple(pivots)
|
||||
return M.from_rep(M_rref_ddm.to_dfm_or_ddm()), den, pivots
|
||||
|
||||
|
||||
def _dm_rref_choose_method(M, method, *, denominator=False):
|
||||
"""Choose the fastest method for computing RREF for M."""
|
||||
|
||||
if method != 'auto':
|
||||
if method.endswith('_dense'):
|
||||
method = method[:-len('_dense')]
|
||||
use_fmt = 'dense'
|
||||
else:
|
||||
use_fmt = 'sparse'
|
||||
|
||||
else:
|
||||
# The sparse implementations are always faster
|
||||
use_fmt = 'sparse'
|
||||
|
||||
K = M.domain
|
||||
|
||||
if K.is_ZZ:
|
||||
method = _dm_rref_choose_method_ZZ(M, denominator=denominator)
|
||||
elif K.is_QQ:
|
||||
method = _dm_rref_choose_method_QQ(M, denominator=denominator)
|
||||
elif K.is_RR or K.is_CC:
|
||||
# TODO: Add partial pivot support to the sparse implementations.
|
||||
method = 'GJ'
|
||||
use_fmt = 'dense'
|
||||
elif K.is_EX and M.rep.fmt == 'dense' and not denominator:
|
||||
# Do not switch to the sparse implementation for EX because the
|
||||
# domain does not have proper canonicalization and the sparse
|
||||
# implementation gives equivalent but non-identical results over EX
|
||||
# from performing arithmetic in a different order. Specifically
|
||||
# test_issue_23718 ends up getting a more complicated expression
|
||||
# when using the sparse implementation. Probably the best fix for
|
||||
# this is something else but for now we stick with the dense
|
||||
# implementation for EX if the matrix is already dense.
|
||||
method = 'GJ'
|
||||
use_fmt = 'dense'
|
||||
else:
|
||||
# This is definitely suboptimal. More work is needed to determine
|
||||
# the best method for computing RREF over different domains.
|
||||
if denominator:
|
||||
method = 'FF'
|
||||
else:
|
||||
method = 'GJ'
|
||||
|
||||
return method, use_fmt
|
||||
|
||||
|
||||
def _dm_rref_choose_method_QQ(M, *, denominator=False):
|
||||
"""Choose the fastest method for computing RREF over QQ."""
|
||||
# The same sorts of considerations apply here as in the case of ZZ. Here
|
||||
# though a new more significant consideration is what sort of denominators
|
||||
# we have and what to do with them so we focus on that.
|
||||
|
||||
# First compute the density. This is the average number of non-zero entries
|
||||
# per row but only counting rows that have at least one non-zero entry
|
||||
# since RREF can ignore fully zero rows.
|
||||
density, _, ncols = _dm_row_density(M)
|
||||
|
||||
# For sparse matrices use Gauss-Jordan elimination over QQ regardless.
|
||||
if density < min(5, ncols/2):
|
||||
return 'GJ'
|
||||
|
||||
# Compare the bit-length of the lcm of the denominators to the bit length
|
||||
# of the numerators.
|
||||
#
|
||||
# The threshold here is empirical: we prefer rref over QQ if clearing
|
||||
# denominators would result in a numerator matrix having 5x the bit size of
|
||||
# the current numerators.
|
||||
numers, denoms = _dm_QQ_numers_denoms(M)
|
||||
numer_bits = max([n.bit_length() for n in numers], default=1)
|
||||
|
||||
denom_lcm = ZZ.one
|
||||
for d in denoms:
|
||||
denom_lcm = ZZ.lcm(denom_lcm, d)
|
||||
if denom_lcm.bit_length() > 5*numer_bits:
|
||||
return 'GJ'
|
||||
|
||||
# If we get here then the matrix is dense and the lcm of the denominators
|
||||
# is not too large compared to the numerators. For particularly small
|
||||
# denominators it is fastest just to clear them and use fraction-free
|
||||
# Gauss-Jordan over ZZ. With very small denominators this is a little
|
||||
# faster than using rref_den over QQ but there is an intermediate regime
|
||||
# where rref_den over QQ is significantly faster. The small denominator
|
||||
# case is probably very common because small fractions like 1/2 or 1/3 are
|
||||
# often seen in user inputs.
|
||||
|
||||
if denom_lcm.bit_length() < 50:
|
||||
return 'CD'
|
||||
else:
|
||||
return 'FF'
|
||||
|
||||
|
||||
def _dm_rref_choose_method_ZZ(M, *, denominator=False):
|
||||
"""Choose the fastest method for computing RREF over ZZ."""
|
||||
# In the extreme of very sparse matrices and low bit counts it is faster to
|
||||
# use Gauss-Jordan elimination over QQ rather than fraction-free
|
||||
# Gauss-Jordan over ZZ. In the opposite extreme of dense matrices and high
|
||||
# bit counts it is faster to use fraction-free Gauss-Jordan over ZZ. These
|
||||
# two extreme cases need to be handled differently because they lead to
|
||||
# different asymptotic complexities. In between these two extremes we need
|
||||
# a threshold for deciding which method to use. This threshold is
|
||||
# determined empirically by timing the two methods with random matrices.
|
||||
|
||||
# The disadvantage of using empirical timings is that future optimisations
|
||||
# might change the relative speeds so this can easily become out of date.
|
||||
# The main thing is to get the asymptotic complexity right for the extreme
|
||||
# cases though so the precise value of the threshold is hopefully not too
|
||||
# important.
|
||||
|
||||
# Empirically determined parameter.
|
||||
PARAM = 10000
|
||||
|
||||
# First compute the density. This is the average number of non-zero entries
|
||||
# per row but only counting rows that have at least one non-zero entry
|
||||
# since RREF can ignore fully zero rows.
|
||||
density, nrows_nz, ncols = _dm_row_density(M)
|
||||
|
||||
# For small matrices use QQ if more than half the entries are zero.
|
||||
if nrows_nz < 10:
|
||||
if density < ncols/2:
|
||||
return 'GJ'
|
||||
else:
|
||||
return 'FF'
|
||||
|
||||
# These are just shortcuts for the formula below.
|
||||
if density < 5:
|
||||
return 'GJ'
|
||||
elif density > 5 + PARAM/nrows_nz:
|
||||
return 'FF' # pragma: no cover
|
||||
|
||||
# Maximum bitsize of any entry.
|
||||
elements = _dm_elements(M)
|
||||
bits = max([e.bit_length() for e in elements], default=1)
|
||||
|
||||
# Wideness parameter. This is 1 for square or tall matrices but >1 for wide
|
||||
# matrices.
|
||||
wideness = max(1, 2/3*ncols/nrows_nz)
|
||||
|
||||
max_density = (5 + PARAM/(nrows_nz*bits**2)) * wideness
|
||||
|
||||
if density < max_density:
|
||||
return 'GJ'
|
||||
else:
|
||||
return 'FF'
|
||||
|
||||
|
||||
def _dm_row_density(M):
|
||||
"""Density measure for sparse matrices.
|
||||
|
||||
Defines the "density", ``d`` as the average number of non-zero entries per
|
||||
row except ignoring rows that are fully zero. RREF can ignore fully zero
|
||||
rows so they are excluded. By definition ``d >= 1`` except that we define
|
||||
``d = 0`` for the zero matrix.
|
||||
|
||||
Returns ``(density, nrows_nz, ncols)`` where ``nrows_nz`` counts the number
|
||||
of nonzero rows and ``ncols`` is the number of columns.
|
||||
"""
|
||||
# Uses the SDM dict-of-dicts representation.
|
||||
ncols = M.shape[1]
|
||||
rows_nz = M.rep.to_sdm().values()
|
||||
if not rows_nz:
|
||||
return 0, 0, ncols
|
||||
else:
|
||||
nrows_nz = len(rows_nz)
|
||||
density = sum(map(len, rows_nz)) / nrows_nz
|
||||
return density, nrows_nz, ncols
|
||||
|
||||
|
||||
def _dm_elements(M):
|
||||
"""Return nonzero elements of a DomainMatrix."""
|
||||
elements, _ = M.to_flat_nz()
|
||||
return elements
|
||||
|
||||
|
||||
def _dm_QQ_numers_denoms(Mq):
|
||||
"""Returns the numerators and denominators of a DomainMatrix over QQ."""
|
||||
elements = _dm_elements(Mq)
|
||||
numers = [e.numerator for e in elements]
|
||||
denoms = [e.denominator for e in elements]
|
||||
return numers, denoms
|
||||
|
||||
|
||||
def _to_field(M):
|
||||
"""Convert a DomainMatrix to a field if possible."""
|
||||
K = M.domain
|
||||
if K.has_assoc_Field:
|
||||
return M.to_field()
|
||||
else:
|
||||
return M
|
||||
2197
venv/lib/python3.12/site-packages/sympy/polys/matrices/sdm.py
Normal file
2197
venv/lib/python3.12/site-packages/sympy/polys/matrices/sdm.py
Normal file
File diff suppressed because it is too large
Load Diff
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,558 @@
|
||||
from sympy.testing.pytest import raises
|
||||
from sympy.external.gmpy import GROUND_TYPES
|
||||
|
||||
from sympy.polys import ZZ, QQ
|
||||
|
||||
from sympy.polys.matrices.ddm import DDM
|
||||
from sympy.polys.matrices.exceptions import (
|
||||
DMShapeError, DMNonInvertibleMatrixError, DMDomainError,
|
||||
DMBadInputError)
|
||||
|
||||
|
||||
def test_DDM_init():
|
||||
items = [[ZZ(0), ZZ(1), ZZ(2)], [ZZ(3), ZZ(4), ZZ(5)]]
|
||||
shape = (2, 3)
|
||||
ddm = DDM(items, shape, ZZ)
|
||||
assert ddm.shape == shape
|
||||
assert ddm.rows == 2
|
||||
assert ddm.cols == 3
|
||||
assert ddm.domain == ZZ
|
||||
|
||||
raises(DMBadInputError, lambda: DDM([[ZZ(2), ZZ(3)]], (2, 2), ZZ))
|
||||
raises(DMBadInputError, lambda: DDM([[ZZ(1)], [ZZ(2), ZZ(3)]], (2, 2), ZZ))
|
||||
|
||||
|
||||
def test_DDM_getsetitem():
|
||||
ddm = DDM([[ZZ(2), ZZ(3)], [ZZ(4), ZZ(5)]], (2, 2), ZZ)
|
||||
|
||||
assert ddm[0][0] == ZZ(2)
|
||||
assert ddm[0][1] == ZZ(3)
|
||||
assert ddm[1][0] == ZZ(4)
|
||||
assert ddm[1][1] == ZZ(5)
|
||||
|
||||
raises(IndexError, lambda: ddm[2][0])
|
||||
raises(IndexError, lambda: ddm[0][2])
|
||||
|
||||
ddm[0][0] = ZZ(-1)
|
||||
assert ddm[0][0] == ZZ(-1)
|
||||
|
||||
|
||||
def test_DDM_str():
|
||||
ddm = DDM([[ZZ(0), ZZ(1)], [ZZ(2), ZZ(3)]], (2, 2), ZZ)
|
||||
if GROUND_TYPES == 'gmpy': # pragma: no cover
|
||||
assert str(ddm) == '[[0, 1], [2, 3]]'
|
||||
assert repr(ddm) == 'DDM([[mpz(0), mpz(1)], [mpz(2), mpz(3)]], (2, 2), ZZ)'
|
||||
else: # pragma: no cover
|
||||
assert repr(ddm) == 'DDM([[0, 1], [2, 3]], (2, 2), ZZ)'
|
||||
assert str(ddm) == '[[0, 1], [2, 3]]'
|
||||
|
||||
|
||||
def test_DDM_eq():
|
||||
items = [[ZZ(0), ZZ(1)], [ZZ(2), ZZ(3)]]
|
||||
ddm1 = DDM(items, (2, 2), ZZ)
|
||||
ddm2 = DDM(items, (2, 2), ZZ)
|
||||
|
||||
assert (ddm1 == ddm1) is True
|
||||
assert (ddm1 == items) is False
|
||||
assert (items == ddm1) is False
|
||||
assert (ddm1 == ddm2) is True
|
||||
assert (ddm2 == ddm1) is True
|
||||
|
||||
assert (ddm1 != ddm1) is False
|
||||
assert (ddm1 != items) is True
|
||||
assert (items != ddm1) is True
|
||||
assert (ddm1 != ddm2) is False
|
||||
assert (ddm2 != ddm1) is False
|
||||
|
||||
ddm3 = DDM([[ZZ(0), ZZ(1)], [ZZ(3), ZZ(3)]], (2, 2), ZZ)
|
||||
ddm3 = DDM(items, (2, 2), QQ)
|
||||
|
||||
assert (ddm1 == ddm3) is False
|
||||
assert (ddm3 == ddm1) is False
|
||||
assert (ddm1 != ddm3) is True
|
||||
assert (ddm3 != ddm1) is True
|
||||
|
||||
|
||||
def test_DDM_convert_to():
|
||||
ddm = DDM([[ZZ(1), ZZ(2)]], (1, 2), ZZ)
|
||||
assert ddm.convert_to(ZZ) == ddm
|
||||
ddmq = ddm.convert_to(QQ)
|
||||
assert ddmq.domain == QQ
|
||||
|
||||
|
||||
def test_DDM_zeros():
|
||||
ddmz = DDM.zeros((3, 4), QQ)
|
||||
assert list(ddmz) == [[QQ(0)] * 4] * 3
|
||||
assert ddmz.shape == (3, 4)
|
||||
assert ddmz.domain == QQ
|
||||
|
||||
def test_DDM_ones():
|
||||
ddmone = DDM.ones((2, 3), QQ)
|
||||
assert list(ddmone) == [[QQ(1)] * 3] * 2
|
||||
assert ddmone.shape == (2, 3)
|
||||
assert ddmone.domain == QQ
|
||||
|
||||
def test_DDM_eye():
|
||||
ddmz = DDM.eye(3, QQ)
|
||||
f = lambda i, j: QQ(1) if i == j else QQ(0)
|
||||
assert list(ddmz) == [[f(i, j) for i in range(3)] for j in range(3)]
|
||||
assert ddmz.shape == (3, 3)
|
||||
assert ddmz.domain == QQ
|
||||
|
||||
|
||||
def test_DDM_copy():
|
||||
ddm1 = DDM([[QQ(1)], [QQ(2)]], (2, 1), QQ)
|
||||
ddm2 = ddm1.copy()
|
||||
assert (ddm1 == ddm2) is True
|
||||
ddm1[0][0] = QQ(-1)
|
||||
assert (ddm1 == ddm2) is False
|
||||
ddm2[0][0] = QQ(-1)
|
||||
assert (ddm1 == ddm2) is True
|
||||
|
||||
|
||||
def test_DDM_transpose():
|
||||
ddm = DDM([[QQ(1)], [QQ(2)]], (2, 1), QQ)
|
||||
ddmT = DDM([[QQ(1), QQ(2)]], (1, 2), QQ)
|
||||
assert ddm.transpose() == ddmT
|
||||
ddm02 = DDM([], (0, 2), QQ)
|
||||
ddm02T = DDM([[], []], (2, 0), QQ)
|
||||
assert ddm02.transpose() == ddm02T
|
||||
assert ddm02T.transpose() == ddm02
|
||||
ddm0 = DDM([], (0, 0), QQ)
|
||||
assert ddm0.transpose() == ddm0
|
||||
|
||||
|
||||
def test_DDM_add():
|
||||
A = DDM([[ZZ(1)], [ZZ(2)]], (2, 1), ZZ)
|
||||
B = DDM([[ZZ(3)], [ZZ(4)]], (2, 1), ZZ)
|
||||
C = DDM([[ZZ(4)], [ZZ(6)]], (2, 1), ZZ)
|
||||
AQ = DDM([[QQ(1)], [QQ(2)]], (2, 1), QQ)
|
||||
assert A + B == A.add(B) == C
|
||||
|
||||
raises(DMShapeError, lambda: A + DDM([[ZZ(5)]], (1, 1), ZZ))
|
||||
raises(TypeError, lambda: A + ZZ(1))
|
||||
raises(TypeError, lambda: ZZ(1) + A)
|
||||
raises(DMDomainError, lambda: A + AQ)
|
||||
raises(DMDomainError, lambda: AQ + A)
|
||||
|
||||
|
||||
def test_DDM_sub():
|
||||
A = DDM([[ZZ(1)], [ZZ(2)]], (2, 1), ZZ)
|
||||
B = DDM([[ZZ(3)], [ZZ(4)]], (2, 1), ZZ)
|
||||
C = DDM([[ZZ(-2)], [ZZ(-2)]], (2, 1), ZZ)
|
||||
AQ = DDM([[QQ(1)], [QQ(2)]], (2, 1), QQ)
|
||||
D = DDM([[ZZ(5)]], (1, 1), ZZ)
|
||||
assert A - B == A.sub(B) == C
|
||||
|
||||
raises(TypeError, lambda: A - ZZ(1))
|
||||
raises(TypeError, lambda: ZZ(1) - A)
|
||||
raises(DMShapeError, lambda: A - D)
|
||||
raises(DMShapeError, lambda: D - A)
|
||||
raises(DMShapeError, lambda: A.sub(D))
|
||||
raises(DMShapeError, lambda: D.sub(A))
|
||||
raises(DMDomainError, lambda: A - AQ)
|
||||
raises(DMDomainError, lambda: AQ - A)
|
||||
raises(DMDomainError, lambda: A.sub(AQ))
|
||||
raises(DMDomainError, lambda: AQ.sub(A))
|
||||
|
||||
|
||||
def test_DDM_neg():
|
||||
A = DDM([[ZZ(1)], [ZZ(2)]], (2, 1), ZZ)
|
||||
An = DDM([[ZZ(-1)], [ZZ(-2)]], (2, 1), ZZ)
|
||||
assert -A == A.neg() == An
|
||||
assert -An == An.neg() == A
|
||||
|
||||
|
||||
def test_DDM_mul():
|
||||
A = DDM([[ZZ(1)]], (1, 1), ZZ)
|
||||
A2 = DDM([[ZZ(2)]], (1, 1), ZZ)
|
||||
assert A * ZZ(2) == A2
|
||||
assert ZZ(2) * A == A2
|
||||
raises(TypeError, lambda: [[1]] * A)
|
||||
raises(TypeError, lambda: A * [[1]])
|
||||
|
||||
|
||||
def test_DDM_matmul():
|
||||
A = DDM([[ZZ(1)], [ZZ(2)]], (2, 1), ZZ)
|
||||
B = DDM([[ZZ(3), ZZ(4)]], (1, 2), ZZ)
|
||||
AB = DDM([[ZZ(3), ZZ(4)], [ZZ(6), ZZ(8)]], (2, 2), ZZ)
|
||||
BA = DDM([[ZZ(11)]], (1, 1), ZZ)
|
||||
|
||||
assert A @ B == A.matmul(B) == AB
|
||||
assert B @ A == B.matmul(A) == BA
|
||||
|
||||
raises(TypeError, lambda: A @ 1)
|
||||
raises(TypeError, lambda: A @ [[3, 4]])
|
||||
|
||||
Bq = DDM([[QQ(3), QQ(4)]], (1, 2), QQ)
|
||||
|
||||
raises(DMDomainError, lambda: A @ Bq)
|
||||
raises(DMDomainError, lambda: Bq @ A)
|
||||
|
||||
C = DDM([[ZZ(1)]], (1, 1), ZZ)
|
||||
|
||||
assert A @ C == A.matmul(C) == A
|
||||
|
||||
raises(DMShapeError, lambda: C @ A)
|
||||
raises(DMShapeError, lambda: C.matmul(A))
|
||||
|
||||
Z04 = DDM([], (0, 4), ZZ)
|
||||
Z40 = DDM([[]]*4, (4, 0), ZZ)
|
||||
Z50 = DDM([[]]*5, (5, 0), ZZ)
|
||||
Z05 = DDM([], (0, 5), ZZ)
|
||||
Z45 = DDM([[0] * 5] * 4, (4, 5), ZZ)
|
||||
Z54 = DDM([[0] * 4] * 5, (5, 4), ZZ)
|
||||
Z00 = DDM([], (0, 0), ZZ)
|
||||
|
||||
assert Z04 @ Z45 == Z04.matmul(Z45) == Z05
|
||||
assert Z45 @ Z50 == Z45.matmul(Z50) == Z40
|
||||
assert Z00 @ Z04 == Z00.matmul(Z04) == Z04
|
||||
assert Z50 @ Z00 == Z50.matmul(Z00) == Z50
|
||||
assert Z00 @ Z00 == Z00.matmul(Z00) == Z00
|
||||
assert Z50 @ Z04 == Z50.matmul(Z04) == Z54
|
||||
|
||||
raises(DMShapeError, lambda: Z05 @ Z40)
|
||||
raises(DMShapeError, lambda: Z05.matmul(Z40))
|
||||
|
||||
|
||||
def test_DDM_hstack():
|
||||
A = DDM([[ZZ(1), ZZ(2), ZZ(3)]], (1, 3), ZZ)
|
||||
B = DDM([[ZZ(4), ZZ(5)]], (1, 2), ZZ)
|
||||
C = DDM([[ZZ(6)]], (1, 1), ZZ)
|
||||
|
||||
Ah = A.hstack(B)
|
||||
assert Ah.shape == (1, 5)
|
||||
assert Ah.domain == ZZ
|
||||
assert Ah == DDM([[ZZ(1), ZZ(2), ZZ(3), ZZ(4), ZZ(5)]], (1, 5), ZZ)
|
||||
|
||||
Ah = A.hstack(B, C)
|
||||
assert Ah.shape == (1, 6)
|
||||
assert Ah.domain == ZZ
|
||||
assert Ah == DDM([[ZZ(1), ZZ(2), ZZ(3), ZZ(4), ZZ(5), ZZ(6)]], (1, 6), ZZ)
|
||||
|
||||
|
||||
def test_DDM_vstack():
|
||||
A = DDM([[ZZ(1)], [ZZ(2)], [ZZ(3)]], (3, 1), ZZ)
|
||||
B = DDM([[ZZ(4)], [ZZ(5)]], (2, 1), ZZ)
|
||||
C = DDM([[ZZ(6)]], (1, 1), ZZ)
|
||||
|
||||
Ah = A.vstack(B)
|
||||
assert Ah.shape == (5, 1)
|
||||
assert Ah.domain == ZZ
|
||||
assert Ah == DDM([[ZZ(1)], [ZZ(2)], [ZZ(3)], [ZZ(4)], [ZZ(5)]], (5, 1), ZZ)
|
||||
|
||||
Ah = A.vstack(B, C)
|
||||
assert Ah.shape == (6, 1)
|
||||
assert Ah.domain == ZZ
|
||||
assert Ah == DDM([[ZZ(1)], [ZZ(2)], [ZZ(3)], [ZZ(4)], [ZZ(5)], [ZZ(6)]], (6, 1), ZZ)
|
||||
|
||||
|
||||
def test_DDM_applyfunc():
|
||||
A = DDM([[ZZ(1), ZZ(2), ZZ(3)]], (1, 3), ZZ)
|
||||
B = DDM([[ZZ(2), ZZ(4), ZZ(6)]], (1, 3), ZZ)
|
||||
assert A.applyfunc(lambda x: 2*x, ZZ) == B
|
||||
|
||||
def test_DDM_rref():
|
||||
|
||||
A = DDM([], (0, 4), QQ)
|
||||
assert A.rref() == (A, [])
|
||||
|
||||
A = DDM([[QQ(0), QQ(1)], [QQ(1), QQ(1)]], (2, 2), QQ)
|
||||
Ar = DDM([[QQ(1), QQ(0)], [QQ(0), QQ(1)]], (2, 2), QQ)
|
||||
pivots = [0, 1]
|
||||
assert A.rref() == (Ar, pivots)
|
||||
|
||||
A = DDM([[QQ(1), QQ(2), QQ(1)], [QQ(3), QQ(4), QQ(1)]], (2, 3), QQ)
|
||||
Ar = DDM([[QQ(1), QQ(0), QQ(-1)], [QQ(0), QQ(1), QQ(1)]], (2, 3), QQ)
|
||||
pivots = [0, 1]
|
||||
assert A.rref() == (Ar, pivots)
|
||||
|
||||
A = DDM([[QQ(3), QQ(4), QQ(1)], [QQ(1), QQ(2), QQ(1)]], (2, 3), QQ)
|
||||
Ar = DDM([[QQ(1), QQ(0), QQ(-1)], [QQ(0), QQ(1), QQ(1)]], (2, 3), QQ)
|
||||
pivots = [0, 1]
|
||||
assert A.rref() == (Ar, pivots)
|
||||
|
||||
A = DDM([[QQ(1), QQ(0)], [QQ(1), QQ(3)], [QQ(0), QQ(1)]], (3, 2), QQ)
|
||||
Ar = DDM([[QQ(1), QQ(0)], [QQ(0), QQ(1)], [QQ(0), QQ(0)]], (3, 2), QQ)
|
||||
pivots = [0, 1]
|
||||
assert A.rref() == (Ar, pivots)
|
||||
|
||||
A = DDM([[QQ(1), QQ(0), QQ(1)], [QQ(3), QQ(0), QQ(1)]], (2, 3), QQ)
|
||||
Ar = DDM([[QQ(1), QQ(0), QQ(0)], [QQ(0), QQ(0), QQ(1)]], (2, 3), QQ)
|
||||
pivots = [0, 2]
|
||||
assert A.rref() == (Ar, pivots)
|
||||
|
||||
|
||||
def test_DDM_nullspace():
|
||||
# more tests are in test_nullspace.py
|
||||
A = DDM([[QQ(1), QQ(1)], [QQ(1), QQ(1)]], (2, 2), QQ)
|
||||
Anull = DDM([[QQ(-1), QQ(1)]], (1, 2), QQ)
|
||||
nonpivots = [1]
|
||||
assert A.nullspace() == (Anull, nonpivots)
|
||||
|
||||
|
||||
def test_DDM_particular():
|
||||
A = DDM([[QQ(1), QQ(0)]], (1, 2), QQ)
|
||||
assert A.particular() == DDM.zeros((1, 1), QQ)
|
||||
|
||||
|
||||
def test_DDM_det():
|
||||
# 0x0 case
|
||||
A = DDM([], (0, 0), ZZ)
|
||||
assert A.det() == ZZ(1)
|
||||
|
||||
# 1x1 case
|
||||
A = DDM([[ZZ(2)]], (1, 1), ZZ)
|
||||
assert A.det() == ZZ(2)
|
||||
|
||||
# 2x2 case
|
||||
A = DDM([[ZZ(1), ZZ(2)], [ZZ(3), ZZ(4)]], (2, 2), ZZ)
|
||||
assert A.det() == ZZ(-2)
|
||||
|
||||
# 3x3 with swap
|
||||
A = DDM([[ZZ(1), ZZ(2), ZZ(3)], [ZZ(1), ZZ(2), ZZ(4)], [ZZ(1), ZZ(2), ZZ(5)]], (3, 3), ZZ)
|
||||
assert A.det() == ZZ(0)
|
||||
|
||||
# 2x2 QQ case
|
||||
A = DDM([[QQ(1, 2), QQ(1, 2)], [QQ(1, 3), QQ(1, 4)]], (2, 2), QQ)
|
||||
assert A.det() == QQ(-1, 24)
|
||||
|
||||
# Nonsquare error
|
||||
A = DDM([[ZZ(1)], [ZZ(2)]], (2, 1), ZZ)
|
||||
raises(DMShapeError, lambda: A.det())
|
||||
|
||||
# Nonsquare error with empty matrix
|
||||
A = DDM([], (0, 1), ZZ)
|
||||
raises(DMShapeError, lambda: A.det())
|
||||
|
||||
|
||||
def test_DDM_inv():
|
||||
A = DDM([[QQ(1, 1), QQ(2, 1)], [QQ(3, 1), QQ(4, 1)]], (2, 2), QQ)
|
||||
Ainv = DDM([[QQ(-2, 1), QQ(1, 1)], [QQ(3, 2), QQ(-1, 2)]], (2, 2), QQ)
|
||||
assert A.inv() == Ainv
|
||||
|
||||
A = DDM([[QQ(1), QQ(2)]], (1, 2), QQ)
|
||||
raises(DMShapeError, lambda: A.inv())
|
||||
|
||||
A = DDM([[ZZ(2)]], (1, 1), ZZ)
|
||||
raises(DMDomainError, lambda: A.inv())
|
||||
|
||||
A = DDM([], (0, 0), QQ)
|
||||
assert A.inv() == A
|
||||
|
||||
A = DDM([[QQ(1), QQ(2)], [QQ(2), QQ(4)]], (2, 2), QQ)
|
||||
raises(DMNonInvertibleMatrixError, lambda: A.inv())
|
||||
|
||||
|
||||
def test_DDM_lu():
|
||||
A = DDM([[QQ(1), QQ(2)], [QQ(3), QQ(4)]], (2, 2), QQ)
|
||||
L, U, swaps = A.lu()
|
||||
assert L == DDM([[QQ(1), QQ(0)], [QQ(3), QQ(1)]], (2, 2), QQ)
|
||||
assert U == DDM([[QQ(1), QQ(2)], [QQ(0), QQ(-2)]], (2, 2), QQ)
|
||||
assert swaps == []
|
||||
|
||||
A = [[1, 0, 0, 0], [0, 0, 0, 0], [0, 0, 1, 1], [0, 0, 1, 2]]
|
||||
Lexp = [[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 1, 1]]
|
||||
Uexp = [[1, 0, 0, 0], [0, 0, 0, 0], [0, 0, 1, 1], [0, 0, 0, 1]]
|
||||
to_dom = lambda rows, dom: [[dom(e) for e in row] for row in rows]
|
||||
A = DDM(to_dom(A, QQ), (4, 4), QQ)
|
||||
Lexp = DDM(to_dom(Lexp, QQ), (4, 4), QQ)
|
||||
Uexp = DDM(to_dom(Uexp, QQ), (4, 4), QQ)
|
||||
L, U, swaps = A.lu()
|
||||
assert L == Lexp
|
||||
assert U == Uexp
|
||||
assert swaps == []
|
||||
|
||||
|
||||
def test_DDM_lu_solve():
|
||||
# Basic example
|
||||
A = DDM([[QQ(1), QQ(2)], [QQ(3), QQ(4)]], (2, 2), QQ)
|
||||
b = DDM([[QQ(1)], [QQ(2)]], (2, 1), QQ)
|
||||
x = DDM([[QQ(0)], [QQ(1, 2)]], (2, 1), QQ)
|
||||
assert A.lu_solve(b) == x
|
||||
|
||||
# Example with swaps
|
||||
A = DDM([[QQ(0), QQ(2)], [QQ(3), QQ(4)]], (2, 2), QQ)
|
||||
assert A.lu_solve(b) == x
|
||||
|
||||
# Overdetermined, consistent
|
||||
A = DDM([[QQ(1), QQ(2)], [QQ(3), QQ(4)], [QQ(5), QQ(6)]], (3, 2), QQ)
|
||||
b = DDM([[QQ(1)], [QQ(2)], [QQ(3)]], (3, 1), QQ)
|
||||
assert A.lu_solve(b) == x
|
||||
|
||||
# Overdetermined, inconsistent
|
||||
b = DDM([[QQ(1)], [QQ(2)], [QQ(4)]], (3, 1), QQ)
|
||||
raises(DMNonInvertibleMatrixError, lambda: A.lu_solve(b))
|
||||
|
||||
# Square, noninvertible
|
||||
A = DDM([[QQ(1), QQ(2)], [QQ(1), QQ(2)]], (2, 2), QQ)
|
||||
b = DDM([[QQ(1)], [QQ(2)]], (2, 1), QQ)
|
||||
raises(DMNonInvertibleMatrixError, lambda: A.lu_solve(b))
|
||||
|
||||
# Underdetermined
|
||||
A = DDM([[QQ(1), QQ(2)]], (1, 2), QQ)
|
||||
b = DDM([[QQ(3)]], (1, 1), QQ)
|
||||
raises(NotImplementedError, lambda: A.lu_solve(b))
|
||||
|
||||
# Domain mismatch
|
||||
bz = DDM([[ZZ(1)], [ZZ(2)]], (2, 1), ZZ)
|
||||
raises(DMDomainError, lambda: A.lu_solve(bz))
|
||||
|
||||
# Shape mismatch
|
||||
b3 = DDM([[QQ(1)], [QQ(2)], [QQ(3)]], (3, 1), QQ)
|
||||
raises(DMShapeError, lambda: A.lu_solve(b3))
|
||||
|
||||
|
||||
def test_DDM_charpoly():
|
||||
A = DDM([], (0, 0), ZZ)
|
||||
assert A.charpoly() == [ZZ(1)]
|
||||
|
||||
A = DDM([
|
||||
[ZZ(1), ZZ(2), ZZ(3)],
|
||||
[ZZ(4), ZZ(5), ZZ(6)],
|
||||
[ZZ(7), ZZ(8), ZZ(9)]], (3, 3), ZZ)
|
||||
Avec = [ZZ(1), ZZ(-15), ZZ(-18), ZZ(0)]
|
||||
assert A.charpoly() == Avec
|
||||
|
||||
A = DDM([[ZZ(1), ZZ(2)]], (1, 2), ZZ)
|
||||
raises(DMShapeError, lambda: A.charpoly())
|
||||
|
||||
|
||||
def test_DDM_getitem():
|
||||
dm = DDM([
|
||||
[ZZ(1), ZZ(2), ZZ(3)],
|
||||
[ZZ(4), ZZ(5), ZZ(6)],
|
||||
[ZZ(7), ZZ(8), ZZ(9)]], (3, 3), ZZ)
|
||||
|
||||
assert dm.getitem(1, 1) == ZZ(5)
|
||||
assert dm.getitem(1, -2) == ZZ(5)
|
||||
assert dm.getitem(-1, -3) == ZZ(7)
|
||||
|
||||
raises(IndexError, lambda: dm.getitem(3, 3))
|
||||
|
||||
|
||||
def test_DDM_setitem():
|
||||
dm = DDM.zeros((3, 3), ZZ)
|
||||
dm.setitem(0, 0, 1)
|
||||
dm.setitem(1, -2, 1)
|
||||
dm.setitem(-1, -1, 1)
|
||||
assert dm == DDM.eye(3, ZZ)
|
||||
|
||||
raises(IndexError, lambda: dm.setitem(3, 3, 0))
|
||||
|
||||
|
||||
def test_DDM_extract_slice():
|
||||
dm = DDM([
|
||||
[ZZ(1), ZZ(2), ZZ(3)],
|
||||
[ZZ(4), ZZ(5), ZZ(6)],
|
||||
[ZZ(7), ZZ(8), ZZ(9)]], (3, 3), ZZ)
|
||||
|
||||
assert dm.extract_slice(slice(0, 3), slice(0, 3)) == dm
|
||||
assert dm.extract_slice(slice(1, 3), slice(-2)) == DDM([[4], [7]], (2, 1), ZZ)
|
||||
assert dm.extract_slice(slice(1, 3), slice(-2)) == DDM([[4], [7]], (2, 1), ZZ)
|
||||
assert dm.extract_slice(slice(2, 3), slice(-2)) == DDM([[ZZ(7)]], (1, 1), ZZ)
|
||||
assert dm.extract_slice(slice(0, 2), slice(-2)) == DDM([[1], [4]], (2, 1), ZZ)
|
||||
assert dm.extract_slice(slice(-1), slice(-1)) == DDM([[1, 2], [4, 5]], (2, 2), ZZ)
|
||||
|
||||
assert dm.extract_slice(slice(2), slice(3, 4)) == DDM([[], []], (2, 0), ZZ)
|
||||
assert dm.extract_slice(slice(3, 4), slice(2)) == DDM([], (0, 2), ZZ)
|
||||
assert dm.extract_slice(slice(3, 4), slice(3, 4)) == DDM([], (0, 0), ZZ)
|
||||
|
||||
|
||||
def test_DDM_extract():
|
||||
dm1 = DDM([
|
||||
[ZZ(1), ZZ(2), ZZ(3)],
|
||||
[ZZ(4), ZZ(5), ZZ(6)],
|
||||
[ZZ(7), ZZ(8), ZZ(9)]], (3, 3), ZZ)
|
||||
dm2 = DDM([
|
||||
[ZZ(6), ZZ(4)],
|
||||
[ZZ(3), ZZ(1)]], (2, 2), ZZ)
|
||||
assert dm1.extract([1, 0], [2, 0]) == dm2
|
||||
assert dm1.extract([-2, 0], [-1, 0]) == dm2
|
||||
|
||||
assert dm1.extract([], []) == DDM.zeros((0, 0), ZZ)
|
||||
assert dm1.extract([1], []) == DDM.zeros((1, 0), ZZ)
|
||||
assert dm1.extract([], [1]) == DDM.zeros((0, 1), ZZ)
|
||||
|
||||
raises(IndexError, lambda: dm2.extract([2], [0]))
|
||||
raises(IndexError, lambda: dm2.extract([0], [2]))
|
||||
raises(IndexError, lambda: dm2.extract([-3], [0]))
|
||||
raises(IndexError, lambda: dm2.extract([0], [-3]))
|
||||
|
||||
|
||||
def test_DDM_flat():
|
||||
dm = DDM([
|
||||
[ZZ(6), ZZ(4)],
|
||||
[ZZ(3), ZZ(1)]], (2, 2), ZZ)
|
||||
assert dm.flat() == [ZZ(6), ZZ(4), ZZ(3), ZZ(1)]
|
||||
|
||||
|
||||
def test_DDM_is_zero_matrix():
|
||||
A = DDM([[QQ(1), QQ(0)], [QQ(0), QQ(0)]], (2, 2), QQ)
|
||||
Azero = DDM.zeros((1, 2), QQ)
|
||||
assert A.is_zero_matrix() is False
|
||||
assert Azero.is_zero_matrix() is True
|
||||
|
||||
|
||||
def test_DDM_is_upper():
|
||||
# Wide matrices:
|
||||
A = DDM([
|
||||
[QQ(1), QQ(2), QQ(3), QQ(4)],
|
||||
[QQ(0), QQ(5), QQ(6), QQ(7)],
|
||||
[QQ(0), QQ(0), QQ(8), QQ(9)]
|
||||
], (3, 4), QQ)
|
||||
B = DDM([
|
||||
[QQ(1), QQ(2), QQ(3), QQ(4)],
|
||||
[QQ(0), QQ(5), QQ(6), QQ(7)],
|
||||
[QQ(0), QQ(7), QQ(8), QQ(9)]
|
||||
], (3, 4), QQ)
|
||||
assert A.is_upper() is True
|
||||
assert B.is_upper() is False
|
||||
|
||||
# Tall matrices:
|
||||
A = DDM([
|
||||
[QQ(1), QQ(2), QQ(3)],
|
||||
[QQ(0), QQ(5), QQ(6)],
|
||||
[QQ(0), QQ(0), QQ(8)],
|
||||
[QQ(0), QQ(0), QQ(0)]
|
||||
], (4, 3), QQ)
|
||||
B = DDM([
|
||||
[QQ(1), QQ(2), QQ(3)],
|
||||
[QQ(0), QQ(5), QQ(6)],
|
||||
[QQ(0), QQ(0), QQ(8)],
|
||||
[QQ(0), QQ(0), QQ(10)]
|
||||
], (4, 3), QQ)
|
||||
assert A.is_upper() is True
|
||||
assert B.is_upper() is False
|
||||
|
||||
|
||||
def test_DDM_is_lower():
|
||||
# Tall matrices:
|
||||
A = DDM([
|
||||
[QQ(1), QQ(2), QQ(3), QQ(4)],
|
||||
[QQ(0), QQ(5), QQ(6), QQ(7)],
|
||||
[QQ(0), QQ(0), QQ(8), QQ(9)]
|
||||
], (3, 4), QQ).transpose()
|
||||
B = DDM([
|
||||
[QQ(1), QQ(2), QQ(3), QQ(4)],
|
||||
[QQ(0), QQ(5), QQ(6), QQ(7)],
|
||||
[QQ(0), QQ(7), QQ(8), QQ(9)]
|
||||
], (3, 4), QQ).transpose()
|
||||
assert A.is_lower() is True
|
||||
assert B.is_lower() is False
|
||||
|
||||
# Wide matrices:
|
||||
A = DDM([
|
||||
[QQ(1), QQ(2), QQ(3)],
|
||||
[QQ(0), QQ(5), QQ(6)],
|
||||
[QQ(0), QQ(0), QQ(8)],
|
||||
[QQ(0), QQ(0), QQ(0)]
|
||||
], (4, 3), QQ).transpose()
|
||||
B = DDM([
|
||||
[QQ(1), QQ(2), QQ(3)],
|
||||
[QQ(0), QQ(5), QQ(6)],
|
||||
[QQ(0), QQ(0), QQ(8)],
|
||||
[QQ(0), QQ(0), QQ(10)]
|
||||
], (4, 3), QQ).transpose()
|
||||
assert A.is_lower() is True
|
||||
assert B.is_lower() is False
|
||||
@@ -0,0 +1,350 @@
|
||||
from sympy.testing.pytest import raises
|
||||
|
||||
from sympy.polys import ZZ, QQ
|
||||
|
||||
from sympy.polys.matrices.ddm import DDM
|
||||
from sympy.polys.matrices.dense import (
|
||||
ddm_transpose,
|
||||
ddm_iadd, ddm_isub, ddm_ineg, ddm_imatmul, ddm_imul, ddm_irref,
|
||||
ddm_idet, ddm_iinv, ddm_ilu, ddm_ilu_split, ddm_ilu_solve, ddm_berk)
|
||||
|
||||
from sympy.polys.matrices.exceptions import (
|
||||
DMDomainError,
|
||||
DMNonInvertibleMatrixError,
|
||||
DMNonSquareMatrixError,
|
||||
DMShapeError,
|
||||
)
|
||||
|
||||
|
||||
def test_ddm_transpose():
|
||||
a = [[1, 2], [3, 4]]
|
||||
assert ddm_transpose(a) == [[1, 3], [2, 4]]
|
||||
|
||||
|
||||
def test_ddm_iadd():
|
||||
a = [[1, 2], [3, 4]]
|
||||
b = [[5, 6], [7, 8]]
|
||||
ddm_iadd(a, b)
|
||||
assert a == [[6, 8], [10, 12]]
|
||||
|
||||
|
||||
def test_ddm_isub():
|
||||
a = [[1, 2], [3, 4]]
|
||||
b = [[5, 6], [7, 8]]
|
||||
ddm_isub(a, b)
|
||||
assert a == [[-4, -4], [-4, -4]]
|
||||
|
||||
|
||||
def test_ddm_ineg():
|
||||
a = [[1, 2], [3, 4]]
|
||||
ddm_ineg(a)
|
||||
assert a == [[-1, -2], [-3, -4]]
|
||||
|
||||
|
||||
def test_ddm_matmul():
|
||||
a = [[1, 2], [3, 4]]
|
||||
ddm_imul(a, 2)
|
||||
assert a == [[2, 4], [6, 8]]
|
||||
|
||||
a = [[1, 2], [3, 4]]
|
||||
ddm_imul(a, 0)
|
||||
assert a == [[0, 0], [0, 0]]
|
||||
|
||||
|
||||
def test_ddm_imatmul():
|
||||
a = [[1, 2, 3], [4, 5, 6]]
|
||||
b = [[1, 2], [3, 4], [5, 6]]
|
||||
|
||||
c1 = [[0, 0], [0, 0]]
|
||||
ddm_imatmul(c1, a, b)
|
||||
assert c1 == [[22, 28], [49, 64]]
|
||||
|
||||
c2 = [[0, 0, 0], [0, 0, 0], [0, 0, 0]]
|
||||
ddm_imatmul(c2, b, a)
|
||||
assert c2 == [[9, 12, 15], [19, 26, 33], [29, 40, 51]]
|
||||
|
||||
b3 = [[1], [2], [3]]
|
||||
c3 = [[0], [0]]
|
||||
ddm_imatmul(c3, a, b3)
|
||||
assert c3 == [[14], [32]]
|
||||
|
||||
|
||||
def test_ddm_irref():
|
||||
# Empty matrix
|
||||
A = []
|
||||
Ar = []
|
||||
pivots = []
|
||||
assert ddm_irref(A) == pivots
|
||||
assert A == Ar
|
||||
|
||||
# Standard square case
|
||||
A = [[QQ(0), QQ(1)], [QQ(1), QQ(1)]]
|
||||
Ar = [[QQ(1), QQ(0)], [QQ(0), QQ(1)]]
|
||||
pivots = [0, 1]
|
||||
assert ddm_irref(A) == pivots
|
||||
assert A == Ar
|
||||
|
||||
# m < n case
|
||||
A = [[QQ(1), QQ(2), QQ(1)], [QQ(3), QQ(4), QQ(1)]]
|
||||
Ar = [[QQ(1), QQ(0), QQ(-1)], [QQ(0), QQ(1), QQ(1)]]
|
||||
pivots = [0, 1]
|
||||
assert ddm_irref(A) == pivots
|
||||
assert A == Ar
|
||||
|
||||
# same m < n but reversed
|
||||
A = [[QQ(3), QQ(4), QQ(1)], [QQ(1), QQ(2), QQ(1)]]
|
||||
Ar = [[QQ(1), QQ(0), QQ(-1)], [QQ(0), QQ(1), QQ(1)]]
|
||||
pivots = [0, 1]
|
||||
assert ddm_irref(A) == pivots
|
||||
assert A == Ar
|
||||
|
||||
# m > n case
|
||||
A = [[QQ(1), QQ(0)], [QQ(1), QQ(3)], [QQ(0), QQ(1)]]
|
||||
Ar = [[QQ(1), QQ(0)], [QQ(0), QQ(1)], [QQ(0), QQ(0)]]
|
||||
pivots = [0, 1]
|
||||
assert ddm_irref(A) == pivots
|
||||
assert A == Ar
|
||||
|
||||
# Example with missing pivot
|
||||
A = [[QQ(1), QQ(0), QQ(1)], [QQ(3), QQ(0), QQ(1)]]
|
||||
Ar = [[QQ(1), QQ(0), QQ(0)], [QQ(0), QQ(0), QQ(1)]]
|
||||
pivots = [0, 2]
|
||||
assert ddm_irref(A) == pivots
|
||||
assert A == Ar
|
||||
|
||||
# Example with missing pivot and no replacement
|
||||
A = [[QQ(0), QQ(1)], [QQ(0), QQ(2)], [QQ(1), QQ(0)]]
|
||||
Ar = [[QQ(1), QQ(0)], [QQ(0), QQ(1)], [QQ(0), QQ(0)]]
|
||||
pivots = [0, 1]
|
||||
assert ddm_irref(A) == pivots
|
||||
assert A == Ar
|
||||
|
||||
|
||||
def test_ddm_idet():
|
||||
A = []
|
||||
assert ddm_idet(A, ZZ) == ZZ(1)
|
||||
|
||||
A = [[ZZ(2)]]
|
||||
assert ddm_idet(A, ZZ) == ZZ(2)
|
||||
|
||||
A = [[ZZ(1), ZZ(2)], [ZZ(3), ZZ(4)]]
|
||||
assert ddm_idet(A, ZZ) == ZZ(-2)
|
||||
|
||||
A = [[ZZ(1), ZZ(2), ZZ(3)], [ZZ(1), ZZ(2), ZZ(4)], [ZZ(1), ZZ(3), ZZ(5)]]
|
||||
assert ddm_idet(A, ZZ) == ZZ(-1)
|
||||
|
||||
A = [[ZZ(1), ZZ(2), ZZ(3)], [ZZ(1), ZZ(2), ZZ(4)], [ZZ(1), ZZ(2), ZZ(5)]]
|
||||
assert ddm_idet(A, ZZ) == ZZ(0)
|
||||
|
||||
A = [[QQ(1, 2), QQ(1, 2)], [QQ(1, 3), QQ(1, 4)]]
|
||||
assert ddm_idet(A, QQ) == QQ(-1, 24)
|
||||
|
||||
|
||||
def test_ddm_inv():
|
||||
A = []
|
||||
Ainv = []
|
||||
ddm_iinv(Ainv, A, QQ)
|
||||
assert Ainv == A
|
||||
|
||||
A = []
|
||||
Ainv = []
|
||||
raises(DMDomainError, lambda: ddm_iinv(Ainv, A, ZZ))
|
||||
|
||||
A = [[QQ(1), QQ(2)]]
|
||||
Ainv = [[QQ(0), QQ(0)]]
|
||||
raises(DMNonSquareMatrixError, lambda: ddm_iinv(Ainv, A, QQ))
|
||||
|
||||
A = [[QQ(1, 1), QQ(2, 1)], [QQ(3, 1), QQ(4, 1)]]
|
||||
Ainv = [[QQ(0), QQ(0)], [QQ(0), QQ(0)]]
|
||||
Ainv_expected = [[QQ(-2, 1), QQ(1, 1)], [QQ(3, 2), QQ(-1, 2)]]
|
||||
ddm_iinv(Ainv, A, QQ)
|
||||
assert Ainv == Ainv_expected
|
||||
|
||||
A = [[QQ(1, 1), QQ(2, 1)], [QQ(2, 1), QQ(4, 1)]]
|
||||
Ainv = [[QQ(0), QQ(0)], [QQ(0), QQ(0)]]
|
||||
raises(DMNonInvertibleMatrixError, lambda: ddm_iinv(Ainv, A, QQ))
|
||||
|
||||
|
||||
def test_ddm_ilu():
|
||||
A = []
|
||||
Alu = []
|
||||
swaps = ddm_ilu(A)
|
||||
assert A == Alu
|
||||
assert swaps == []
|
||||
|
||||
A = [[]]
|
||||
Alu = [[]]
|
||||
swaps = ddm_ilu(A)
|
||||
assert A == Alu
|
||||
assert swaps == []
|
||||
|
||||
A = [[QQ(1), QQ(2)], [QQ(3), QQ(4)]]
|
||||
Alu = [[QQ(1), QQ(2)], [QQ(3), QQ(-2)]]
|
||||
swaps = ddm_ilu(A)
|
||||
assert A == Alu
|
||||
assert swaps == []
|
||||
|
||||
A = [[QQ(0), QQ(2)], [QQ(3), QQ(4)]]
|
||||
Alu = [[QQ(3), QQ(4)], [QQ(0), QQ(2)]]
|
||||
swaps = ddm_ilu(A)
|
||||
assert A == Alu
|
||||
assert swaps == [(0, 1)]
|
||||
|
||||
A = [[QQ(1), QQ(2), QQ(3)], [QQ(4), QQ(5), QQ(6)], [QQ(7), QQ(8), QQ(9)]]
|
||||
Alu = [[QQ(1), QQ(2), QQ(3)], [QQ(4), QQ(-3), QQ(-6)], [QQ(7), QQ(2), QQ(0)]]
|
||||
swaps = ddm_ilu(A)
|
||||
assert A == Alu
|
||||
assert swaps == []
|
||||
|
||||
A = [[QQ(0), QQ(1), QQ(2)], [QQ(0), QQ(1), QQ(3)], [QQ(1), QQ(1), QQ(2)]]
|
||||
Alu = [[QQ(1), QQ(1), QQ(2)], [QQ(0), QQ(1), QQ(3)], [QQ(0), QQ(1), QQ(-1)]]
|
||||
swaps = ddm_ilu(A)
|
||||
assert A == Alu
|
||||
assert swaps == [(0, 2)]
|
||||
|
||||
A = [[QQ(1), QQ(2), QQ(3)], [QQ(4), QQ(5), QQ(6)]]
|
||||
Alu = [[QQ(1), QQ(2), QQ(3)], [QQ(4), QQ(-3), QQ(-6)]]
|
||||
swaps = ddm_ilu(A)
|
||||
assert A == Alu
|
||||
assert swaps == []
|
||||
|
||||
A = [[QQ(1), QQ(2)], [QQ(3), QQ(4)], [QQ(5), QQ(6)]]
|
||||
Alu = [[QQ(1), QQ(2)], [QQ(3), QQ(-2)], [QQ(5), QQ(2)]]
|
||||
swaps = ddm_ilu(A)
|
||||
assert A == Alu
|
||||
assert swaps == []
|
||||
|
||||
|
||||
def test_ddm_ilu_split():
|
||||
U = []
|
||||
L = []
|
||||
Uexp = []
|
||||
Lexp = []
|
||||
swaps = ddm_ilu_split(L, U, QQ)
|
||||
assert U == Uexp
|
||||
assert L == Lexp
|
||||
assert swaps == []
|
||||
|
||||
U = [[]]
|
||||
L = [[QQ(1)]]
|
||||
Uexp = [[]]
|
||||
Lexp = [[QQ(1)]]
|
||||
swaps = ddm_ilu_split(L, U, QQ)
|
||||
assert U == Uexp
|
||||
assert L == Lexp
|
||||
assert swaps == []
|
||||
|
||||
U = [[QQ(1), QQ(2)], [QQ(3), QQ(4)]]
|
||||
L = [[QQ(1), QQ(0)], [QQ(0), QQ(1)]]
|
||||
Uexp = [[QQ(1), QQ(2)], [QQ(0), QQ(-2)]]
|
||||
Lexp = [[QQ(1), QQ(0)], [QQ(3), QQ(1)]]
|
||||
swaps = ddm_ilu_split(L, U, QQ)
|
||||
assert U == Uexp
|
||||
assert L == Lexp
|
||||
assert swaps == []
|
||||
|
||||
U = [[QQ(1), QQ(2), QQ(3)], [QQ(4), QQ(5), QQ(6)]]
|
||||
L = [[QQ(1), QQ(0)], [QQ(0), QQ(1)]]
|
||||
Uexp = [[QQ(1), QQ(2), QQ(3)], [QQ(0), QQ(-3), QQ(-6)]]
|
||||
Lexp = [[QQ(1), QQ(0)], [QQ(4), QQ(1)]]
|
||||
swaps = ddm_ilu_split(L, U, QQ)
|
||||
assert U == Uexp
|
||||
assert L == Lexp
|
||||
assert swaps == []
|
||||
|
||||
U = [[QQ(1), QQ(2)], [QQ(3), QQ(4)], [QQ(5), QQ(6)]]
|
||||
L = [[QQ(1), QQ(0), QQ(0)], [QQ(0), QQ(1), QQ(0)], [QQ(0), QQ(0), QQ(1)]]
|
||||
Uexp = [[QQ(1), QQ(2)], [QQ(0), QQ(-2)], [QQ(0), QQ(0)]]
|
||||
Lexp = [[QQ(1), QQ(0), QQ(0)], [QQ(3), QQ(1), QQ(0)], [QQ(5), QQ(2), QQ(1)]]
|
||||
swaps = ddm_ilu_split(L, U, QQ)
|
||||
assert U == Uexp
|
||||
assert L == Lexp
|
||||
assert swaps == []
|
||||
|
||||
|
||||
def test_ddm_ilu_solve():
|
||||
# Basic example
|
||||
# A = [[QQ(1), QQ(2)], [QQ(3), QQ(4)]]
|
||||
U = [[QQ(1), QQ(2)], [QQ(0), QQ(-2)]]
|
||||
L = [[QQ(1), QQ(0)], [QQ(3), QQ(1)]]
|
||||
swaps = []
|
||||
b = DDM([[QQ(1)], [QQ(2)]], (2, 1), QQ)
|
||||
x = DDM([[QQ(0)], [QQ(0)]], (2, 1), QQ)
|
||||
xexp = DDM([[QQ(0)], [QQ(1, 2)]], (2, 1), QQ)
|
||||
ddm_ilu_solve(x, L, U, swaps, b)
|
||||
assert x == xexp
|
||||
|
||||
# Example with swaps
|
||||
# A = [[QQ(0), QQ(2)], [QQ(3), QQ(4)]]
|
||||
U = [[QQ(3), QQ(4)], [QQ(0), QQ(2)]]
|
||||
L = [[QQ(1), QQ(0)], [QQ(0), QQ(1)]]
|
||||
swaps = [(0, 1)]
|
||||
b = DDM([[QQ(1)], [QQ(2)]], (2, 1), QQ)
|
||||
x = DDM([[QQ(0)], [QQ(0)]], (2, 1), QQ)
|
||||
xexp = DDM([[QQ(0)], [QQ(1, 2)]], (2, 1), QQ)
|
||||
ddm_ilu_solve(x, L, U, swaps, b)
|
||||
assert x == xexp
|
||||
|
||||
# Overdetermined, consistent
|
||||
# A = DDM([[QQ(1), QQ(2)], [QQ(3), QQ(4)], [QQ(5), QQ(6)]], (3, 2), QQ)
|
||||
U = [[QQ(1), QQ(2)], [QQ(0), QQ(-2)], [QQ(0), QQ(0)]]
|
||||
L = [[QQ(1), QQ(0), QQ(0)], [QQ(3), QQ(1), QQ(0)], [QQ(5), QQ(2), QQ(1)]]
|
||||
swaps = []
|
||||
b = DDM([[QQ(1)], [QQ(2)], [QQ(3)]], (3, 1), QQ)
|
||||
x = DDM([[QQ(0)], [QQ(0)]], (2, 1), QQ)
|
||||
xexp = DDM([[QQ(0)], [QQ(1, 2)]], (2, 1), QQ)
|
||||
ddm_ilu_solve(x, L, U, swaps, b)
|
||||
assert x == xexp
|
||||
|
||||
# Overdetermined, inconsistent
|
||||
b = DDM([[QQ(1)], [QQ(2)], [QQ(4)]], (3, 1), QQ)
|
||||
raises(DMNonInvertibleMatrixError, lambda: ddm_ilu_solve(x, L, U, swaps, b))
|
||||
|
||||
# Square, noninvertible
|
||||
# A = DDM([[QQ(1), QQ(2)], [QQ(1), QQ(2)]], (2, 2), QQ)
|
||||
U = [[QQ(1), QQ(2)], [QQ(0), QQ(0)]]
|
||||
L = [[QQ(1), QQ(0)], [QQ(1), QQ(1)]]
|
||||
swaps = []
|
||||
b = DDM([[QQ(1)], [QQ(2)]], (2, 1), QQ)
|
||||
raises(DMNonInvertibleMatrixError, lambda: ddm_ilu_solve(x, L, U, swaps, b))
|
||||
|
||||
# Underdetermined
|
||||
# A = DDM([[QQ(1), QQ(2)]], (1, 2), QQ)
|
||||
U = [[QQ(1), QQ(2)]]
|
||||
L = [[QQ(1)]]
|
||||
swaps = []
|
||||
b = DDM([[QQ(3)]], (1, 1), QQ)
|
||||
raises(NotImplementedError, lambda: ddm_ilu_solve(x, L, U, swaps, b))
|
||||
|
||||
# Shape mismatch
|
||||
b3 = DDM([[QQ(1)], [QQ(2)], [QQ(3)]], (3, 1), QQ)
|
||||
raises(DMShapeError, lambda: ddm_ilu_solve(x, L, U, swaps, b3))
|
||||
|
||||
# Empty shape mismatch
|
||||
U = [[QQ(1)]]
|
||||
L = [[QQ(1)]]
|
||||
swaps = []
|
||||
x = [[QQ(1)]]
|
||||
b = []
|
||||
raises(DMShapeError, lambda: ddm_ilu_solve(x, L, U, swaps, b))
|
||||
|
||||
# Empty system
|
||||
U = []
|
||||
L = []
|
||||
swaps = []
|
||||
b = []
|
||||
x = []
|
||||
ddm_ilu_solve(x, L, U, swaps, b)
|
||||
assert x == []
|
||||
|
||||
|
||||
def test_ddm_charpoly():
|
||||
A = []
|
||||
assert ddm_berk(A, ZZ) == [[ZZ(1)]]
|
||||
|
||||
A = [[ZZ(1), ZZ(2), ZZ(3)], [ZZ(4), ZZ(5), ZZ(6)], [ZZ(7), ZZ(8), ZZ(9)]]
|
||||
Avec = [[ZZ(1)], [ZZ(-15)], [ZZ(-18)], [ZZ(0)]]
|
||||
assert ddm_berk(A, ZZ) == Avec
|
||||
|
||||
A = DDM([[ZZ(1), ZZ(2)]], (1, 2), ZZ)
|
||||
raises(DMShapeError, lambda: ddm_berk(A, ZZ))
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,153 @@
|
||||
from sympy.testing.pytest import raises
|
||||
|
||||
from sympy.core.symbol import S
|
||||
from sympy.polys import ZZ, QQ
|
||||
from sympy.polys.matrices.domainscalar import DomainScalar
|
||||
from sympy.polys.matrices.domainmatrix import DomainMatrix
|
||||
|
||||
|
||||
def test_DomainScalar___new__():
|
||||
raises(TypeError, lambda: DomainScalar(ZZ(1), QQ))
|
||||
raises(TypeError, lambda: DomainScalar(ZZ(1), 1))
|
||||
|
||||
|
||||
def test_DomainScalar_new():
|
||||
A = DomainScalar(ZZ(1), ZZ)
|
||||
B = A.new(ZZ(4), ZZ)
|
||||
assert B == DomainScalar(ZZ(4), ZZ)
|
||||
|
||||
|
||||
def test_DomainScalar_repr():
|
||||
A = DomainScalar(ZZ(1), ZZ)
|
||||
assert repr(A) in {'1', 'mpz(1)'}
|
||||
|
||||
|
||||
def test_DomainScalar_from_sympy():
|
||||
expr = S(1)
|
||||
B = DomainScalar.from_sympy(expr)
|
||||
assert B == DomainScalar(ZZ(1), ZZ)
|
||||
|
||||
|
||||
def test_DomainScalar_to_sympy():
|
||||
B = DomainScalar(ZZ(1), ZZ)
|
||||
expr = B.to_sympy()
|
||||
assert expr.is_Integer and expr == 1
|
||||
|
||||
|
||||
def test_DomainScalar_to_domain():
|
||||
A = DomainScalar(ZZ(1), ZZ)
|
||||
B = A.to_domain(QQ)
|
||||
assert B == DomainScalar(QQ(1), QQ)
|
||||
|
||||
|
||||
def test_DomainScalar_convert_to():
|
||||
A = DomainScalar(ZZ(1), ZZ)
|
||||
B = A.convert_to(QQ)
|
||||
assert B == DomainScalar(QQ(1), QQ)
|
||||
|
||||
|
||||
def test_DomainScalar_unify():
|
||||
A = DomainScalar(ZZ(1), ZZ)
|
||||
B = DomainScalar(QQ(2), QQ)
|
||||
A, B = A.unify(B)
|
||||
assert A.domain == B.domain == QQ
|
||||
|
||||
|
||||
def test_DomainScalar_add():
|
||||
A = DomainScalar(ZZ(1), ZZ)
|
||||
B = DomainScalar(QQ(2), QQ)
|
||||
assert A + B == DomainScalar(QQ(3), QQ)
|
||||
|
||||
raises(TypeError, lambda: A + 1.5)
|
||||
|
||||
def test_DomainScalar_sub():
|
||||
A = DomainScalar(ZZ(1), ZZ)
|
||||
B = DomainScalar(QQ(2), QQ)
|
||||
assert A - B == DomainScalar(QQ(-1), QQ)
|
||||
|
||||
raises(TypeError, lambda: A - 1.5)
|
||||
|
||||
def test_DomainScalar_mul():
|
||||
A = DomainScalar(ZZ(1), ZZ)
|
||||
B = DomainScalar(QQ(2), QQ)
|
||||
dm = DomainMatrix([[ZZ(1), ZZ(2)], [ZZ(3), ZZ(4)]], (2, 2), ZZ)
|
||||
assert A * B == DomainScalar(QQ(2), QQ)
|
||||
assert A * dm == dm
|
||||
assert B * 2 == DomainScalar(QQ(4), QQ)
|
||||
|
||||
raises(TypeError, lambda: A * 1.5)
|
||||
|
||||
|
||||
def test_DomainScalar_floordiv():
|
||||
A = DomainScalar(ZZ(-5), ZZ)
|
||||
B = DomainScalar(QQ(2), QQ)
|
||||
assert A // B == DomainScalar(QQ(-5, 2), QQ)
|
||||
C = DomainScalar(ZZ(2), ZZ)
|
||||
assert A // C == DomainScalar(ZZ(-3), ZZ)
|
||||
|
||||
raises(TypeError, lambda: A // 1.5)
|
||||
|
||||
|
||||
def test_DomainScalar_mod():
|
||||
A = DomainScalar(ZZ(5), ZZ)
|
||||
B = DomainScalar(QQ(2), QQ)
|
||||
assert A % B == DomainScalar(QQ(0), QQ)
|
||||
C = DomainScalar(ZZ(2), ZZ)
|
||||
assert A % C == DomainScalar(ZZ(1), ZZ)
|
||||
|
||||
raises(TypeError, lambda: A % 1.5)
|
||||
|
||||
|
||||
def test_DomainScalar_divmod():
|
||||
A = DomainScalar(ZZ(5), ZZ)
|
||||
B = DomainScalar(QQ(2), QQ)
|
||||
assert divmod(A, B) == (DomainScalar(QQ(5, 2), QQ), DomainScalar(QQ(0), QQ))
|
||||
C = DomainScalar(ZZ(2), ZZ)
|
||||
assert divmod(A, C) == (DomainScalar(ZZ(2), ZZ), DomainScalar(ZZ(1), ZZ))
|
||||
|
||||
raises(TypeError, lambda: divmod(A, 1.5))
|
||||
|
||||
|
||||
def test_DomainScalar_pow():
|
||||
A = DomainScalar(ZZ(-5), ZZ)
|
||||
B = A**(2)
|
||||
assert B == DomainScalar(ZZ(25), ZZ)
|
||||
|
||||
raises(TypeError, lambda: A**(1.5))
|
||||
|
||||
|
||||
def test_DomainScalar_pos():
|
||||
A = DomainScalar(QQ(2), QQ)
|
||||
B = DomainScalar(QQ(2), QQ)
|
||||
assert +A == B
|
||||
|
||||
|
||||
def test_DomainScalar_neg():
|
||||
A = DomainScalar(QQ(2), QQ)
|
||||
B = DomainScalar(QQ(-2), QQ)
|
||||
assert -A == B
|
||||
|
||||
|
||||
def test_DomainScalar_eq():
|
||||
A = DomainScalar(QQ(2), QQ)
|
||||
assert A == A
|
||||
B = DomainScalar(ZZ(-5), ZZ)
|
||||
assert A != B
|
||||
C = DomainScalar(ZZ(2), ZZ)
|
||||
assert A != C
|
||||
D = [1]
|
||||
assert A != D
|
||||
|
||||
|
||||
def test_DomainScalar_isZero():
|
||||
A = DomainScalar(ZZ(0), ZZ)
|
||||
assert A.is_zero() == True
|
||||
B = DomainScalar(ZZ(1), ZZ)
|
||||
assert B.is_zero() == False
|
||||
|
||||
|
||||
def test_DomainScalar_isOne():
|
||||
A = DomainScalar(ZZ(1), ZZ)
|
||||
assert A.is_one() == True
|
||||
B = DomainScalar(ZZ(0), ZZ)
|
||||
assert B.is_one() == False
|
||||
@@ -0,0 +1,90 @@
|
||||
"""
|
||||
Tests for the sympy.polys.matrices.eigen module
|
||||
"""
|
||||
|
||||
from sympy.core.singleton import S
|
||||
from sympy.functions.elementary.miscellaneous import sqrt
|
||||
from sympy.matrices.dense import Matrix
|
||||
|
||||
from sympy.polys.agca.extensions import FiniteExtension
|
||||
from sympy.polys.domains import QQ
|
||||
from sympy.polys.polytools import Poly
|
||||
from sympy.polys.rootoftools import CRootOf
|
||||
from sympy.polys.matrices.domainmatrix import DomainMatrix
|
||||
|
||||
from sympy.polys.matrices.eigen import dom_eigenvects, dom_eigenvects_to_sympy
|
||||
|
||||
|
||||
def test_dom_eigenvects_rational():
|
||||
# Rational eigenvalues
|
||||
A = DomainMatrix([[QQ(1), QQ(2)], [QQ(1), QQ(2)]], (2, 2), QQ)
|
||||
rational_eigenvects = [
|
||||
(QQ, QQ(3), 1, DomainMatrix([[QQ(1), QQ(1)]], (1, 2), QQ)),
|
||||
(QQ, QQ(0), 1, DomainMatrix([[QQ(-2), QQ(1)]], (1, 2), QQ)),
|
||||
]
|
||||
assert dom_eigenvects(A) == (rational_eigenvects, [])
|
||||
|
||||
# Test converting to Expr:
|
||||
sympy_eigenvects = [
|
||||
(S(3), 1, [Matrix([1, 1])]),
|
||||
(S(0), 1, [Matrix([-2, 1])]),
|
||||
]
|
||||
assert dom_eigenvects_to_sympy(rational_eigenvects, [], Matrix) == sympy_eigenvects
|
||||
|
||||
|
||||
def test_dom_eigenvects_algebraic():
|
||||
# Algebraic eigenvalues
|
||||
A = DomainMatrix([[QQ(1), QQ(2)], [QQ(3), QQ(4)]], (2, 2), QQ)
|
||||
Avects = dom_eigenvects(A)
|
||||
|
||||
# Extract the dummy to build the expected result:
|
||||
lamda = Avects[1][0][1].gens[0]
|
||||
irreducible = Poly(lamda**2 - 5*lamda - 2, lamda, domain=QQ)
|
||||
K = FiniteExtension(irreducible)
|
||||
KK = K.from_sympy
|
||||
algebraic_eigenvects = [
|
||||
(K, irreducible, 1, DomainMatrix([[KK((lamda-4)/3), KK(1)]], (1, 2), K)),
|
||||
]
|
||||
assert Avects == ([], algebraic_eigenvects)
|
||||
|
||||
# Test converting to Expr:
|
||||
sympy_eigenvects = [
|
||||
(S(5)/2 - sqrt(33)/2, 1, [Matrix([[-sqrt(33)/6 - S(1)/2], [1]])]),
|
||||
(S(5)/2 + sqrt(33)/2, 1, [Matrix([[-S(1)/2 + sqrt(33)/6], [1]])]),
|
||||
]
|
||||
assert dom_eigenvects_to_sympy([], algebraic_eigenvects, Matrix) == sympy_eigenvects
|
||||
|
||||
|
||||
def test_dom_eigenvects_rootof():
|
||||
# Algebraic eigenvalues
|
||||
A = DomainMatrix([
|
||||
[0, 0, 0, 0, -1],
|
||||
[1, 0, 0, 0, 1],
|
||||
[0, 1, 0, 0, 0],
|
||||
[0, 0, 1, 0, 0],
|
||||
[0, 0, 0, 1, 0]], (5, 5), QQ)
|
||||
Avects = dom_eigenvects(A)
|
||||
|
||||
# Extract the dummy to build the expected result:
|
||||
lamda = Avects[1][0][1].gens[0]
|
||||
irreducible = Poly(lamda**5 - lamda + 1, lamda, domain=QQ)
|
||||
K = FiniteExtension(irreducible)
|
||||
KK = K.from_sympy
|
||||
algebraic_eigenvects = [
|
||||
(K, irreducible, 1,
|
||||
DomainMatrix([
|
||||
[KK(lamda**4-1), KK(lamda**3), KK(lamda**2), KK(lamda), KK(1)]
|
||||
], (1, 5), K)),
|
||||
]
|
||||
assert Avects == ([], algebraic_eigenvects)
|
||||
|
||||
# Test converting to Expr (slow):
|
||||
l0, l1, l2, l3, l4 = [CRootOf(lamda**5 - lamda + 1, i) for i in range(5)]
|
||||
sympy_eigenvects = [
|
||||
(l0, 1, [Matrix([-1 + l0**4, l0**3, l0**2, l0, 1])]),
|
||||
(l1, 1, [Matrix([-1 + l1**4, l1**3, l1**2, l1, 1])]),
|
||||
(l2, 1, [Matrix([-1 + l2**4, l2**3, l2**2, l2, 1])]),
|
||||
(l3, 1, [Matrix([-1 + l3**4, l3**3, l3**2, l3, 1])]),
|
||||
(l4, 1, [Matrix([-1 + l4**4, l4**3, l4**2, l4, 1])]),
|
||||
]
|
||||
assert dom_eigenvects_to_sympy([], algebraic_eigenvects, Matrix) == sympy_eigenvects
|
||||
@@ -0,0 +1,301 @@
|
||||
from sympy.polys.matrices import DomainMatrix, DM
|
||||
from sympy.polys.domains import ZZ, QQ
|
||||
from sympy import Matrix
|
||||
import pytest
|
||||
|
||||
|
||||
FFLU_EXAMPLES = [
|
||||
(
|
||||
'zz_2x3',
|
||||
DM([[1, 2, 3], [4, 5, 6]], ZZ),
|
||||
DM([[1, 0], [0, 1]], ZZ),
|
||||
DM([[1, 0], [4, -3]], ZZ),
|
||||
DM([[1, 0], [0, -3]], ZZ),
|
||||
DM([[1, 2, 3], [0, -3, -6]], ZZ),
|
||||
),
|
||||
|
||||
(
|
||||
'zz_2x2',
|
||||
DM([[4, 3], [6, 3]], ZZ),
|
||||
DM([[1, 0], [0, 1]], ZZ),
|
||||
DM([[1, 0], [6, -6]], ZZ),
|
||||
DM([[4, 0], [0, -3]], ZZ),
|
||||
DM([[4, 3], [0, -3]], ZZ),
|
||||
),
|
||||
|
||||
(
|
||||
'zz_3x2',
|
||||
DM([[1, 2], [3, 4], [5, 6]], ZZ),
|
||||
DM([[1, 0, 0], [0, 1, 0], [0, 0, 1]], ZZ),
|
||||
DM([[1, 0, 0], [3, 1, 0], [5, 2, 1]], ZZ),
|
||||
DM([[1, 0], [0, -2]], ZZ),
|
||||
DM([[1, 2], [0, -2], [0, 0]], ZZ),
|
||||
),
|
||||
|
||||
(
|
||||
'zz_3x3',
|
||||
DM([[1, 2, 3], [4, 5, 6], [7, 8, 9]], ZZ),
|
||||
DM([[1, 0, 0], [0, 1, 0], [0, 0, 1]], ZZ),
|
||||
DM([[1, 0, 0], [4, 1, 0], [7, 2, 1]], ZZ),
|
||||
DM([[1, 0, 0], [0, -3, 0], [0, 0, 0]], ZZ),
|
||||
DM([[1, 2, 3], [0, -3, -6], [0, 0, 0]], ZZ),
|
||||
),
|
||||
|
||||
(
|
||||
'zz_zero',
|
||||
DM([[0, 0, 0], [0, 0, 0], [0, 0, 0]], ZZ),
|
||||
DM([[1, 0, 0], [0, 1, 0], [0, 0, 1]], ZZ),
|
||||
DM([[1, 0, 0], [0, 1, 0], [0, 0, 1]], ZZ),
|
||||
DM([[0, 0, 0], [0, 0, 0], [0, 0, 0]], ZZ),
|
||||
DM([[0, 0, 0], [0, 0, 0], [0, 0, 0]], ZZ),
|
||||
),
|
||||
|
||||
(
|
||||
'zz_empty',
|
||||
DM([], ZZ),
|
||||
DM([], ZZ),
|
||||
DM([], ZZ),
|
||||
DM([], ZZ),
|
||||
DM([], ZZ),
|
||||
),
|
||||
|
||||
(
|
||||
'zz_empty_0x2',
|
||||
DomainMatrix([], (0, 2), ZZ),
|
||||
DomainMatrix([], (0, 0), ZZ),
|
||||
DomainMatrix([], (0, 0), ZZ),
|
||||
DomainMatrix([], (0, 0), ZZ),
|
||||
DomainMatrix([], (0, 2), ZZ)
|
||||
),
|
||||
|
||||
(
|
||||
|
||||
'zz_empty_2x0',
|
||||
DomainMatrix([[], []], (2, 0), ZZ),
|
||||
DomainMatrix.eye((2, 2), ZZ),
|
||||
DomainMatrix.eye((2, 2), ZZ),
|
||||
DomainMatrix.eye((2, 2), ZZ),
|
||||
DomainMatrix([[], []], (2, 0), ZZ)
|
||||
|
||||
),
|
||||
|
||||
(
|
||||
'zz_negative',
|
||||
DM([[-1, -2], [-3, -4]], ZZ),
|
||||
DM([[1, 0], [0, 1]], ZZ),
|
||||
DM([[-1, 0], [-3, -2]], ZZ),
|
||||
DM([[-1, 0], [0, 2]], ZZ),
|
||||
DM([[-1, -2], [0, -2]], ZZ),
|
||||
),
|
||||
|
||||
(
|
||||
'zz_mixed_signs',
|
||||
DM([[1, -2], [-3, 4]], ZZ),
|
||||
DM([[1, 0], [0, 1]], ZZ),
|
||||
DM([[1, 0], [-3, 1]], ZZ),
|
||||
DM([[1, 0], [0, -2]], ZZ),
|
||||
DM([[1, -2], [0, -2]], ZZ),
|
||||
),
|
||||
|
||||
(
|
||||
'zz_upper_triangular',
|
||||
DM([[1, 2, 3], [0, 4, 5], [0, 0, 6]], ZZ),
|
||||
DM([[1, 0, 0], [0, 1, 0], [0, 0, 1]], ZZ),
|
||||
DM([[1, 0, 0], [0, 4, 0], [0, 0, 24]], ZZ),
|
||||
DM([[1, 0, 0], [0, 4, 0], [0, 0, 96]], ZZ),
|
||||
DM([[1, 2, 3], [0, 4, 5], [0, 0, 24]], ZZ),
|
||||
),
|
||||
|
||||
(
|
||||
'zz_lower_triangular',
|
||||
DM([[1, 0, 0], [2, 3, 0], [4, 5, 6]], ZZ),
|
||||
DM([[1, 0, 0], [0, 1, 0], [0, 0, 1]], ZZ),
|
||||
DM([[1, 0, 0], [2, 3, 0], [4, 5, 18]], ZZ),
|
||||
DM([[1, 0, 0], [0, 3, 0], [0, 0, 54]], ZZ),
|
||||
DM([[1, 0, 0], [0, 3, 0], [0, 0, 18]], ZZ),
|
||||
),
|
||||
|
||||
(
|
||||
'zz_diagonal',
|
||||
DM([[2, 0, 0], [0, 3, 0], [0, 0, 4]], ZZ),
|
||||
DM([[1, 0, 0], [0, 1, 0], [0, 0, 1]], ZZ),
|
||||
DM([[2, 0, 0], [0, 6, 0], [0, 0, 24]], ZZ),
|
||||
DM([[2, 0, 0], [0, 12, 0], [0, 0, 144]], ZZ),
|
||||
DM([[2, 0, 0], [0, 6, 0], [0, 0, 24]], ZZ)
|
||||
|
||||
),
|
||||
|
||||
(
|
||||
'rank_deficient_3x3',
|
||||
DM([[1, 2, 3], [2, 4, 6], [3, 6, 9]], ZZ),
|
||||
DM([[1, 0, 0], [0, 1, 0], [0, 0, 1]], ZZ),
|
||||
DM([[1, 0, 0], [2, 1, 0], [3, 0, 1]], ZZ),
|
||||
DM([[1, 0, 0], [0, 0, 0], [0, 0, 0]], ZZ),
|
||||
DM([[1, 2, 3], [0, 0, 0], [0, 0, 0]], ZZ),
|
||||
),
|
||||
|
||||
(
|
||||
'zz_1x1',
|
||||
DM([[5]], ZZ),
|
||||
DM([[1]], ZZ),
|
||||
DM([[5]], ZZ),
|
||||
DM([[5]], ZZ),
|
||||
DM([[5]], ZZ),
|
||||
),
|
||||
|
||||
(
|
||||
'zz_nx1_2rows',
|
||||
DM([[81], [54]], ZZ),
|
||||
DM([[1, 0], [0, 1]], ZZ),
|
||||
DM([[81, 0], [54, 81]], ZZ),
|
||||
DM([[81, 0], [0, 81]], ZZ),
|
||||
DM([[81], [0]], ZZ),
|
||||
),
|
||||
|
||||
(
|
||||
'zz_nx2_3rows',
|
||||
DM([[2, 7], [7, 45], [25, 84]], ZZ),
|
||||
DM([[1, 0, 0], [0, 1, 0], [0, 0, 1]], ZZ),
|
||||
DM([[2, 0, 0], [7, 82, 0], [25, 41, 41]], ZZ),
|
||||
DM([[2, 0, 0], [0, 82, 0], [0, 0, 41]], ZZ),
|
||||
DM([[2, 7], [0, 82], [0, 0]], ZZ),
|
||||
),
|
||||
|
||||
(
|
||||
|
||||
'zz_1x2',
|
||||
DM([[0, 28]], ZZ),
|
||||
DM([[1]], ZZ),
|
||||
DM([[28]], ZZ),
|
||||
DM([[28]], ZZ),
|
||||
DM([[0, 28]], ZZ)
|
||||
),
|
||||
|
||||
(
|
||||
'zz_nx3_4rows',
|
||||
DM([[84, 30, 9], [20, 59, 13], [53, 46, 81], [63, 48, 29]], ZZ),
|
||||
DM([[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]], ZZ),
|
||||
DM([[84, 0, 0, 0], [20, 365904, 0, 0], [53, 303411, 303411, 0], [63, 303411, 303411, 303411]], ZZ),
|
||||
DM([[84, 0, 0, 0], [0, 365904, 0, 0], [0, 0, 1321658316, 0], [0, 0, 0, 303411]], ZZ),
|
||||
DM([[84, 30, 9], [0, 365904, 13], [0, 0, 1321658316], [0, 0, 0]], ZZ),
|
||||
),
|
||||
|
||||
(
|
||||
'fflu_row_swap',
|
||||
DM([[0, 1, 2], [3, 4, 5], [6, 7, 8]], ZZ),
|
||||
DM([[0, 1, 0], [1, 0, 0], [0, 0, 1]], ZZ),
|
||||
DM([[3, 0, 0], [0, 3, 0], [6, -3, 1]], ZZ),
|
||||
DM([[3, 0, 0], [0, 9, 0], [0, 0, 3]], ZZ),
|
||||
DM([[3, 4, 5], [0, 3, 6], [0, 0, 0]], ZZ)
|
||||
),
|
||||
]
|
||||
|
||||
|
||||
def _check_fflu(A, P, L, D, U):
|
||||
P_field = P.to_field().to_dense()
|
||||
L_field = L.to_field().to_dense()
|
||||
D_field = D.to_field().to_dense()
|
||||
U_field = U.to_field().to_dense()
|
||||
m, n = A.shape
|
||||
assert P_field.shape == (m, m)
|
||||
assert L_field.shape == (m, m)
|
||||
assert D_field.shape == (m, m)
|
||||
assert U_field.shape == (m, n)
|
||||
assert L_field.is_lower
|
||||
assert D_field.is_diagonal
|
||||
di, d = D.inv_den()
|
||||
assert P.matmul(A).rmul(d) == L.matmul(di).matmul(U)
|
||||
assert U_field.is_upper
|
||||
|
||||
|
||||
def _to_DM(A, ans):
|
||||
if isinstance(A, DomainMatrix):
|
||||
return A
|
||||
elif isinstance(A, Matrix):
|
||||
return A.to_DM(ans.domain)
|
||||
return DomainMatrix(A.to_list(), A.shape, A.domain)
|
||||
|
||||
|
||||
def _check_fflu_result(result, A, P_ans, L_ans, D_ans, U_ans):
|
||||
P, L, D, U = result
|
||||
P = _to_DM(P, P_ans)
|
||||
L = _to_DM(L, L_ans)
|
||||
D = _to_DM(D, D_ans)
|
||||
U = _to_DM(U, U_ans)
|
||||
A = _to_DM(A, P_ans)
|
||||
m, n = A.shape
|
||||
assert P.shape == (m, m)
|
||||
assert L.shape == (m, m)
|
||||
assert D.shape == (m, m)
|
||||
assert U.shape == (m, n)
|
||||
assert L.is_lower
|
||||
assert D.is_diagonal
|
||||
di, d = D.inv_den()
|
||||
assert P.matmul(A).rmul(d) == L.matmul(di).matmul(U)
|
||||
assert U.is_upper
|
||||
|
||||
|
||||
@pytest.mark.parametrize('name, A, P_ans, L_ans, D_ans, U_ans', FFLU_EXAMPLES)
|
||||
def test_dm_dense_fflu(name, A, P_ans, L_ans, D_ans, U_ans):
|
||||
A = A.to_dense()
|
||||
_check_fflu_result(A.fflu(), A, P_ans, L_ans, D_ans, U_ans)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('name, A, P_ans, L_ans, D_ans, U_ans', FFLU_EXAMPLES)
|
||||
def test_dm_sparse_fflu(name, A, P_ans, L_ans, D_ans, U_ans):
|
||||
A = A.to_sparse()
|
||||
_check_fflu_result(A.fflu(), A, P_ans, L_ans, D_ans, U_ans)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('name, A, P_ans, L_ans, D_ans, U_ans', FFLU_EXAMPLES)
|
||||
def test_ddm_fflu(name, A, P_ans, L_ans, D_ans, U_ans):
|
||||
A = A.to_ddm()
|
||||
_check_fflu_result(A.fflu(), A, P_ans, L_ans, D_ans, U_ans)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('name, A, P_ans, L_ans, D_ans, U_ans', FFLU_EXAMPLES)
|
||||
def test_sdm_fflu(name, A, P_ans, L_ans, D_ans, U_ans):
|
||||
A = A.to_sdm()
|
||||
_check_fflu_result(A.fflu(), A, P_ans, L_ans, D_ans, U_ans)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('name, A, P_ans, L_ans, D_ans, U_ans', FFLU_EXAMPLES)
|
||||
def test_dfm_fflu(name, A, P_ans, L_ans, D_ans, U_ans):
|
||||
pytest.importorskip('flint')
|
||||
if A.domain not in (ZZ, QQ) and not A.domain.is_FF:
|
||||
pytest.skip("Domain not supported by DFM")
|
||||
A = A.to_dfm()
|
||||
_check_fflu_result(A.fflu(), A, P_ans, L_ans, D_ans, U_ans)
|
||||
|
||||
|
||||
def test_fflu_empty_matrix():
|
||||
A = DomainMatrix([], (0, 0), ZZ)
|
||||
P, L, D, U = A.fflu()
|
||||
assert P.shape == (0, 0)
|
||||
assert L.shape == (0, 0)
|
||||
assert D.shape == (0, 0)
|
||||
assert U.shape == (0, 0)
|
||||
|
||||
|
||||
def test_fflu_properties():
|
||||
A = DomainMatrix([[ZZ(1), ZZ(2)], [ZZ(3), ZZ(4)]], (2, 2), ZZ)
|
||||
P, L, D, U = A.fflu()
|
||||
assert P.shape == (2, 2)
|
||||
assert L.shape == (2, 2)
|
||||
assert D.shape == (2, 2)
|
||||
assert U.shape == (2, 2)
|
||||
assert L.is_lower
|
||||
assert U.is_upper
|
||||
assert D.is_diagonal
|
||||
di, d = D.inv_den()
|
||||
assert P.matmul(A).rmul(d) == L.matmul(di).matmul(U)
|
||||
|
||||
|
||||
def test_fflu_rank_deficient():
|
||||
A = DomainMatrix([[ZZ(1), ZZ(2)], [ZZ(2), ZZ(4)]], (2, 2), ZZ)
|
||||
P, L, D, U = A.fflu()
|
||||
assert P.shape == (2, 2)
|
||||
assert L.shape == (2, 2)
|
||||
assert D.shape == (2, 2)
|
||||
assert U.shape == (2, 2)
|
||||
assert U.getitem_sympy(1, 1) == 0
|
||||
@@ -0,0 +1,193 @@
|
||||
from sympy import ZZ, Matrix
|
||||
from sympy.polys.matrices import DM, DomainMatrix
|
||||
from sympy.polys.matrices.dense import ddm_iinv
|
||||
from sympy.polys.matrices.exceptions import DMNonInvertibleMatrixError
|
||||
from sympy.matrices.exceptions import NonInvertibleMatrixError
|
||||
|
||||
import pytest
|
||||
from sympy.testing.pytest import raises
|
||||
from sympy.core.numbers import all_close
|
||||
|
||||
from sympy.abc import x
|
||||
|
||||
|
||||
# Examples are given as adjugate matrix and determinant adj_det should match
|
||||
# these exactly but inv_den only matches after cancel_denom.
|
||||
|
||||
|
||||
INVERSE_EXAMPLES = [
|
||||
|
||||
(
|
||||
'zz_1',
|
||||
DomainMatrix([], (0, 0), ZZ),
|
||||
DomainMatrix([], (0, 0), ZZ),
|
||||
ZZ(1),
|
||||
),
|
||||
|
||||
(
|
||||
'zz_2',
|
||||
DM([[2]], ZZ),
|
||||
DM([[1]], ZZ),
|
||||
ZZ(2),
|
||||
),
|
||||
|
||||
(
|
||||
'zz_3',
|
||||
DM([[2, 0],
|
||||
[0, 2]], ZZ),
|
||||
DM([[2, 0],
|
||||
[0, 2]], ZZ),
|
||||
ZZ(4),
|
||||
),
|
||||
|
||||
(
|
||||
'zz_4',
|
||||
DM([[1, 2],
|
||||
[3, 4]], ZZ),
|
||||
DM([[ 4, -2],
|
||||
[-3, 1]], ZZ),
|
||||
ZZ(-2),
|
||||
),
|
||||
|
||||
(
|
||||
'zz_5',
|
||||
DM([[2, 2, 0],
|
||||
[0, 2, 2],
|
||||
[0, 0, 2]], ZZ),
|
||||
DM([[4, -4, 4],
|
||||
[0, 4, -4],
|
||||
[0, 0, 4]], ZZ),
|
||||
ZZ(8),
|
||||
),
|
||||
|
||||
(
|
||||
'zz_6',
|
||||
DM([[1, 2, 3],
|
||||
[4, 5, 6],
|
||||
[7, 8, 9]], ZZ),
|
||||
DM([[-3, 6, -3],
|
||||
[ 6, -12, 6],
|
||||
[-3, 6, -3]], ZZ),
|
||||
ZZ(0),
|
||||
),
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.parametrize('name, A, A_inv, den', INVERSE_EXAMPLES)
|
||||
def test_Matrix_inv(name, A, A_inv, den):
|
||||
|
||||
def _check(**kwargs):
|
||||
if den != 0:
|
||||
assert A.inv(**kwargs) == A_inv
|
||||
else:
|
||||
raises(NonInvertibleMatrixError, lambda: A.inv(**kwargs))
|
||||
|
||||
K = A.domain
|
||||
A = A.to_Matrix()
|
||||
A_inv = A_inv.to_Matrix() / K.to_sympy(den)
|
||||
_check()
|
||||
for method in ['GE', 'LU', 'ADJ', 'CH', 'LDL', 'QR']:
|
||||
_check(method=method)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('name, A, A_inv, den', INVERSE_EXAMPLES)
|
||||
def test_dm_inv_den(name, A, A_inv, den):
|
||||
if den != 0:
|
||||
A_inv_f, den_f = A.inv_den()
|
||||
assert A_inv_f.cancel_denom(den_f) == A_inv.cancel_denom(den)
|
||||
else:
|
||||
raises(DMNonInvertibleMatrixError, lambda: A.inv_den())
|
||||
|
||||
|
||||
@pytest.mark.parametrize('name, A, A_inv, den', INVERSE_EXAMPLES)
|
||||
def test_dm_inv(name, A, A_inv, den):
|
||||
A = A.to_field()
|
||||
if den != 0:
|
||||
A_inv = A_inv.to_field() / den
|
||||
assert A.inv() == A_inv
|
||||
else:
|
||||
raises(DMNonInvertibleMatrixError, lambda: A.inv())
|
||||
|
||||
|
||||
@pytest.mark.parametrize('name, A, A_inv, den', INVERSE_EXAMPLES)
|
||||
def test_ddm_inv(name, A, A_inv, den):
|
||||
A = A.to_field().to_ddm()
|
||||
if den != 0:
|
||||
A_inv = (A_inv.to_field() / den).to_ddm()
|
||||
assert A.inv() == A_inv
|
||||
else:
|
||||
raises(DMNonInvertibleMatrixError, lambda: A.inv())
|
||||
|
||||
|
||||
@pytest.mark.parametrize('name, A, A_inv, den', INVERSE_EXAMPLES)
|
||||
def test_sdm_inv(name, A, A_inv, den):
|
||||
A = A.to_field().to_sdm()
|
||||
if den != 0:
|
||||
A_inv = (A_inv.to_field() / den).to_sdm()
|
||||
assert A.inv() == A_inv
|
||||
else:
|
||||
raises(DMNonInvertibleMatrixError, lambda: A.inv())
|
||||
|
||||
|
||||
@pytest.mark.parametrize('name, A, A_inv, den', INVERSE_EXAMPLES)
|
||||
def test_dense_ddm_iinv(name, A, A_inv, den):
|
||||
A = A.to_field().to_ddm().copy()
|
||||
K = A.domain
|
||||
A_result = A.copy()
|
||||
if den != 0:
|
||||
A_inv = (A_inv.to_field() / den).to_ddm()
|
||||
ddm_iinv(A_result, A, K)
|
||||
assert A_result == A_inv
|
||||
else:
|
||||
raises(DMNonInvertibleMatrixError, lambda: ddm_iinv(A_result, A, K))
|
||||
|
||||
|
||||
@pytest.mark.parametrize('name, A, A_inv, den', INVERSE_EXAMPLES)
|
||||
def test_Matrix_adjugate(name, A, A_inv, den):
|
||||
A = A.to_Matrix()
|
||||
A_inv = A_inv.to_Matrix()
|
||||
assert A.adjugate() == A_inv
|
||||
for method in ["bareiss", "berkowitz", "bird", "laplace", "lu"]:
|
||||
assert A.adjugate(method=method) == A_inv
|
||||
|
||||
|
||||
@pytest.mark.parametrize('name, A, A_inv, den', INVERSE_EXAMPLES)
|
||||
def test_dm_adj_det(name, A, A_inv, den):
|
||||
assert A.adj_det() == (A_inv, den)
|
||||
|
||||
|
||||
def test_inverse_inexact():
|
||||
|
||||
M = Matrix([[x-0.3, -0.06, -0.22],
|
||||
[-0.46, x-0.48, -0.41],
|
||||
[-0.14, -0.39, x-0.64]])
|
||||
|
||||
Mn = Matrix([[1.0*x**2 - 1.12*x + 0.1473, 0.06*x + 0.0474, 0.22*x - 0.081],
|
||||
[0.46*x - 0.237, 1.0*x**2 - 0.94*x + 0.1612, 0.41*x - 0.0218],
|
||||
[0.14*x + 0.1122, 0.39*x - 0.1086, 1.0*x**2 - 0.78*x + 0.1164]])
|
||||
|
||||
d = 1.0*x**3 - 1.42*x**2 + 0.4249*x - 0.0546540000000002
|
||||
|
||||
Mi = Mn / d
|
||||
|
||||
M_dm = M.to_DM()
|
||||
M_dmd = M_dm.to_dense()
|
||||
M_dm_num, M_dm_den = M_dm.inv_den()
|
||||
M_dmd_num, M_dmd_den = M_dmd.inv_den()
|
||||
|
||||
# XXX: We don't check M_dm().to_field().inv() which currently uses division
|
||||
# and produces a more complicate result from gcd cancellation failing.
|
||||
# DomainMatrix.inv() over RR(x) should be changed to clear denominators and
|
||||
# use DomainMatrix.inv_den().
|
||||
|
||||
Minvs = [
|
||||
M.inv(),
|
||||
(M_dm_num.to_field() / M_dm_den).to_Matrix(),
|
||||
(M_dmd_num.to_field() / M_dmd_den).to_Matrix(),
|
||||
M_dm_num.to_Matrix() / M_dm_den.as_expr(),
|
||||
M_dmd_num.to_Matrix() / M_dmd_den.as_expr(),
|
||||
]
|
||||
|
||||
for Minv in Minvs:
|
||||
for Mi1, Mi2 in zip(Minv.flat(), Mi.flat()):
|
||||
assert all_close(Mi2, Mi1)
|
||||
@@ -0,0 +1,112 @@
|
||||
#
|
||||
# test_linsolve.py
|
||||
#
|
||||
# Test the internal implementation of linsolve.
|
||||
#
|
||||
|
||||
from sympy.testing.pytest import raises
|
||||
|
||||
from sympy.core.numbers import I
|
||||
from sympy.core.relational import Eq
|
||||
from sympy.core.singleton import S
|
||||
from sympy.abc import x, y, z
|
||||
|
||||
from sympy.polys.matrices.linsolve import _linsolve
|
||||
from sympy.polys.solvers import PolyNonlinearError
|
||||
|
||||
|
||||
def test__linsolve():
|
||||
assert _linsolve([], [x]) == {x:x}
|
||||
assert _linsolve([S.Zero], [x]) == {x:x}
|
||||
assert _linsolve([x-1,x-2], [x]) is None
|
||||
assert _linsolve([x-1], [x]) == {x:1}
|
||||
assert _linsolve([x-1, y], [x, y]) == {x:1, y:S.Zero}
|
||||
assert _linsolve([2*I], [x]) is None
|
||||
raises(PolyNonlinearError, lambda: _linsolve([x*(1 + x)], [x]))
|
||||
|
||||
|
||||
def test__linsolve_float():
|
||||
|
||||
# This should give the exact answer:
|
||||
eqs = [
|
||||
y - x,
|
||||
y - 0.0216 * x
|
||||
]
|
||||
# Should _linsolve return floats here?
|
||||
sol = {x:0, y:0}
|
||||
assert _linsolve(eqs, (x, y)) == sol
|
||||
|
||||
# Other cases should be close to eps
|
||||
|
||||
def all_close(sol1, sol2, eps=1e-15):
|
||||
close = lambda a, b: abs(a - b) < eps
|
||||
assert sol1.keys() == sol2.keys()
|
||||
return all(close(sol1[s], sol2[s]) for s in sol1)
|
||||
|
||||
eqs = [
|
||||
0.8*x + 0.8*z + 0.2,
|
||||
0.9*x + 0.7*y + 0.2*z + 0.9,
|
||||
0.7*x + 0.2*y + 0.2*z + 0.5
|
||||
]
|
||||
sol_exact = {x:-29/42, y:-11/21, z:37/84}
|
||||
sol_linsolve = _linsolve(eqs, [x,y,z])
|
||||
assert all_close(sol_exact, sol_linsolve)
|
||||
|
||||
eqs = [
|
||||
0.9*x + 0.3*y + 0.4*z + 0.6,
|
||||
0.6*x + 0.9*y + 0.1*z + 0.7,
|
||||
0.4*x + 0.6*y + 0.9*z + 0.5
|
||||
]
|
||||
sol_exact = {x:-88/175, y:-46/105, z:-1/25}
|
||||
sol_linsolve = _linsolve(eqs, [x,y,z])
|
||||
assert all_close(sol_exact, sol_linsolve)
|
||||
|
||||
eqs = [
|
||||
0.4*x + 0.3*y + 0.6*z + 0.7,
|
||||
0.4*x + 0.3*y + 0.9*z + 0.9,
|
||||
0.7*x + 0.9*y,
|
||||
]
|
||||
sol_exact = {x:-9/5, y:7/5, z:-2/3}
|
||||
sol_linsolve = _linsolve(eqs, [x,y,z])
|
||||
assert all_close(sol_exact, sol_linsolve)
|
||||
|
||||
eqs = [
|
||||
x*(0.7 + 0.6*I) + y*(0.4 + 0.7*I) + z*(0.9 + 0.1*I) + 0.5,
|
||||
0.2*I*x + 0.2*I*y + z*(0.9 + 0.2*I) + 0.1,
|
||||
x*(0.9 + 0.7*I) + y*(0.9 + 0.7*I) + z*(0.9 + 0.4*I) + 0.4,
|
||||
]
|
||||
sol_exact = {
|
||||
x:-6157/7995 - 411/5330*I,
|
||||
y:8519/15990 + 1784/7995*I,
|
||||
z:-34/533 + 107/1599*I,
|
||||
}
|
||||
sol_linsolve = _linsolve(eqs, [x,y,z])
|
||||
assert all_close(sol_exact, sol_linsolve)
|
||||
|
||||
# XXX: This system for x and y over RR(z) is problematic.
|
||||
#
|
||||
# eqs = [
|
||||
# x*(0.2*z + 0.9) + y*(0.5*z + 0.8) + 0.6,
|
||||
# 0.1*x*z + y*(0.1*z + 0.6) + 0.9,
|
||||
# ]
|
||||
#
|
||||
# linsolve(eqs, [x, y])
|
||||
# The solution for x comes out as
|
||||
#
|
||||
# -3.9e-5*z**2 - 3.6e-5*z - 8.67361737988404e-20
|
||||
# x = ----------------------------------------------
|
||||
# 3.0e-6*z**3 - 1.3e-5*z**2 - 5.4e-5*z
|
||||
#
|
||||
# The 8e-20 in the numerator should be zero which would allow z to cancel
|
||||
# from top and bottom. It should be possible to avoid this somehow because
|
||||
# the inverse of the matrix only has a quadratic factor (the determinant)
|
||||
# in the denominator.
|
||||
|
||||
|
||||
def test__linsolve_deprecated():
|
||||
raises(PolyNonlinearError, lambda:
|
||||
_linsolve([Eq(x**2, x**2 + y)], [x, y]))
|
||||
raises(PolyNonlinearError, lambda:
|
||||
_linsolve([(x + y)**2 - x**2], [x]))
|
||||
raises(PolyNonlinearError, lambda:
|
||||
_linsolve([Eq((x + y)**2, x**2)], [x]))
|
||||
@@ -0,0 +1,145 @@
|
||||
from sympy.polys.domains import ZZ, QQ
|
||||
from sympy.polys.matrices import DM
|
||||
from sympy.polys.matrices.domainmatrix import DomainMatrix
|
||||
from sympy.polys.matrices.exceptions import DMRankError, DMValueError, DMShapeError, DMDomainError
|
||||
from sympy.polys.matrices.lll import _ddm_lll, ddm_lll, ddm_lll_transform
|
||||
from sympy.testing.pytest import raises
|
||||
|
||||
|
||||
def test_lll():
|
||||
normal_test_data = [
|
||||
(
|
||||
DM([[1, 0, 0, 0, -20160],
|
||||
[0, 1, 0, 0, 33768],
|
||||
[0, 0, 1, 0, 39578],
|
||||
[0, 0, 0, 1, 47757]], ZZ),
|
||||
DM([[10, -3, -2, 8, -4],
|
||||
[3, -9, 8, 1, -11],
|
||||
[-3, 13, -9, -3, -9],
|
||||
[-12, -7, -11, 9, -1]], ZZ)
|
||||
),
|
||||
(
|
||||
DM([[20, 52, 3456],
|
||||
[14, 31, -1],
|
||||
[34, -442, 0]], ZZ),
|
||||
DM([[14, 31, -1],
|
||||
[188, -101, -11],
|
||||
[236, 13, 3443]], ZZ)
|
||||
),
|
||||
(
|
||||
DM([[34, -1, -86, 12],
|
||||
[-54, 34, 55, 678],
|
||||
[23, 3498, 234, 6783],
|
||||
[87, 49, 665, 11]], ZZ),
|
||||
DM([[34, -1, -86, 12],
|
||||
[291, 43, 149, 83],
|
||||
[-54, 34, 55, 678],
|
||||
[-189, 3077, -184, -223]], ZZ)
|
||||
)
|
||||
]
|
||||
delta = QQ(5, 6)
|
||||
for basis_dm, reduced_dm in normal_test_data:
|
||||
reduced = _ddm_lll(basis_dm.rep.to_ddm(), delta=delta)[0]
|
||||
assert reduced == reduced_dm.rep.to_ddm()
|
||||
|
||||
reduced = ddm_lll(basis_dm.rep.to_ddm(), delta=delta)
|
||||
assert reduced == reduced_dm.rep.to_ddm()
|
||||
|
||||
reduced, transform = _ddm_lll(basis_dm.rep.to_ddm(), delta=delta, return_transform=True)
|
||||
assert reduced == reduced_dm.rep.to_ddm()
|
||||
assert transform.matmul(basis_dm.rep.to_ddm()) == reduced_dm.rep.to_ddm()
|
||||
|
||||
reduced, transform = ddm_lll_transform(basis_dm.rep.to_ddm(), delta=delta)
|
||||
assert reduced == reduced_dm.rep.to_ddm()
|
||||
assert transform.matmul(basis_dm.rep.to_ddm()) == reduced_dm.rep.to_ddm()
|
||||
|
||||
reduced = basis_dm.rep.lll(delta=delta)
|
||||
assert reduced == reduced_dm.rep
|
||||
|
||||
reduced, transform = basis_dm.rep.lll_transform(delta=delta)
|
||||
assert reduced == reduced_dm.rep
|
||||
assert transform.matmul(basis_dm.rep) == reduced_dm.rep
|
||||
|
||||
reduced = basis_dm.rep.to_sdm().lll(delta=delta)
|
||||
assert reduced == reduced_dm.rep.to_sdm()
|
||||
|
||||
reduced, transform = basis_dm.rep.to_sdm().lll_transform(delta=delta)
|
||||
assert reduced == reduced_dm.rep.to_sdm()
|
||||
assert transform.matmul(basis_dm.rep.to_sdm()) == reduced_dm.rep.to_sdm()
|
||||
|
||||
reduced = basis_dm.lll(delta=delta)
|
||||
assert reduced == reduced_dm
|
||||
|
||||
reduced, transform = basis_dm.lll_transform(delta=delta)
|
||||
assert reduced == reduced_dm
|
||||
assert transform.matmul(basis_dm) == reduced_dm
|
||||
|
||||
|
||||
def test_lll_linear_dependent():
|
||||
linear_dependent_test_data = [
|
||||
DM([[0, -1, -2, -3],
|
||||
[1, 0, -1, -2],
|
||||
[2, 1, 0, -1],
|
||||
[3, 2, 1, 0]], ZZ),
|
||||
DM([[1, 0, 0, 1],
|
||||
[0, 1, 0, 1],
|
||||
[0, 0, 1, 1],
|
||||
[1, 2, 3, 6]], ZZ),
|
||||
DM([[3, -5, 1],
|
||||
[4, 6, 0],
|
||||
[10, -4, 2]], ZZ)
|
||||
]
|
||||
for not_basis in linear_dependent_test_data:
|
||||
raises(DMRankError, lambda: _ddm_lll(not_basis.rep.to_ddm()))
|
||||
raises(DMRankError, lambda: ddm_lll(not_basis.rep.to_ddm()))
|
||||
raises(DMRankError, lambda: not_basis.rep.lll())
|
||||
raises(DMRankError, lambda: not_basis.rep.to_sdm().lll())
|
||||
raises(DMRankError, lambda: not_basis.lll())
|
||||
raises(DMRankError, lambda: _ddm_lll(not_basis.rep.to_ddm(), return_transform=True))
|
||||
raises(DMRankError, lambda: ddm_lll_transform(not_basis.rep.to_ddm()))
|
||||
raises(DMRankError, lambda: not_basis.rep.lll_transform())
|
||||
raises(DMRankError, lambda: not_basis.rep.to_sdm().lll_transform())
|
||||
raises(DMRankError, lambda: not_basis.lll_transform())
|
||||
|
||||
|
||||
def test_lll_wrong_delta():
|
||||
dummy_matrix = DomainMatrix.ones((3, 3), ZZ)
|
||||
for wrong_delta in [QQ(-1, 4), QQ(0, 1), QQ(1, 4), QQ(1, 1), QQ(100, 1)]:
|
||||
raises(DMValueError, lambda: _ddm_lll(dummy_matrix.rep, delta=wrong_delta))
|
||||
raises(DMValueError, lambda: ddm_lll(dummy_matrix.rep, delta=wrong_delta))
|
||||
raises(DMValueError, lambda: dummy_matrix.rep.lll(delta=wrong_delta))
|
||||
raises(DMValueError, lambda: dummy_matrix.rep.to_sdm().lll(delta=wrong_delta))
|
||||
raises(DMValueError, lambda: dummy_matrix.lll(delta=wrong_delta))
|
||||
raises(DMValueError, lambda: _ddm_lll(dummy_matrix.rep, delta=wrong_delta, return_transform=True))
|
||||
raises(DMValueError, lambda: ddm_lll_transform(dummy_matrix.rep, delta=wrong_delta))
|
||||
raises(DMValueError, lambda: dummy_matrix.rep.lll_transform(delta=wrong_delta))
|
||||
raises(DMValueError, lambda: dummy_matrix.rep.to_sdm().lll_transform(delta=wrong_delta))
|
||||
raises(DMValueError, lambda: dummy_matrix.lll_transform(delta=wrong_delta))
|
||||
|
||||
|
||||
def test_lll_wrong_shape():
|
||||
wrong_shape_matrix = DomainMatrix.ones((4, 3), ZZ)
|
||||
raises(DMShapeError, lambda: _ddm_lll(wrong_shape_matrix.rep))
|
||||
raises(DMShapeError, lambda: ddm_lll(wrong_shape_matrix.rep))
|
||||
raises(DMShapeError, lambda: wrong_shape_matrix.rep.lll())
|
||||
raises(DMShapeError, lambda: wrong_shape_matrix.rep.to_sdm().lll())
|
||||
raises(DMShapeError, lambda: wrong_shape_matrix.lll())
|
||||
raises(DMShapeError, lambda: _ddm_lll(wrong_shape_matrix.rep, return_transform=True))
|
||||
raises(DMShapeError, lambda: ddm_lll_transform(wrong_shape_matrix.rep))
|
||||
raises(DMShapeError, lambda: wrong_shape_matrix.rep.lll_transform())
|
||||
raises(DMShapeError, lambda: wrong_shape_matrix.rep.to_sdm().lll_transform())
|
||||
raises(DMShapeError, lambda: wrong_shape_matrix.lll_transform())
|
||||
|
||||
|
||||
def test_lll_wrong_domain():
|
||||
wrong_domain_matrix = DomainMatrix.ones((3, 3), QQ)
|
||||
raises(DMDomainError, lambda: _ddm_lll(wrong_domain_matrix.rep))
|
||||
raises(DMDomainError, lambda: ddm_lll(wrong_domain_matrix.rep))
|
||||
raises(DMDomainError, lambda: wrong_domain_matrix.rep.lll())
|
||||
raises(DMDomainError, lambda: wrong_domain_matrix.rep.to_sdm().lll())
|
||||
raises(DMDomainError, lambda: wrong_domain_matrix.lll())
|
||||
raises(DMDomainError, lambda: _ddm_lll(wrong_domain_matrix.rep, return_transform=True))
|
||||
raises(DMDomainError, lambda: ddm_lll_transform(wrong_domain_matrix.rep))
|
||||
raises(DMDomainError, lambda: wrong_domain_matrix.rep.lll_transform())
|
||||
raises(DMDomainError, lambda: wrong_domain_matrix.rep.to_sdm().lll_transform())
|
||||
raises(DMDomainError, lambda: wrong_domain_matrix.lll_transform())
|
||||
@@ -0,0 +1,156 @@
|
||||
from sympy.testing.pytest import raises
|
||||
|
||||
from sympy.core.symbol import Symbol
|
||||
from sympy.polys.matrices.normalforms import (
|
||||
invariant_factors,
|
||||
smith_normal_form,
|
||||
smith_normal_decomp,
|
||||
is_smith_normal_form,
|
||||
hermite_normal_form,
|
||||
_hermite_normal_form,
|
||||
_hermite_normal_form_modulo_D
|
||||
)
|
||||
from sympy.polys.domains import ZZ, QQ
|
||||
from sympy.polys.matrices import DomainMatrix, DM
|
||||
from sympy.polys.matrices.exceptions import DMDomainError, DMShapeError
|
||||
|
||||
|
||||
def test_is_smith_normal_form():
|
||||
|
||||
snf_examples = [
|
||||
DM([[0, 0], [0, 0]], ZZ),
|
||||
DM([[1, 0], [0, 0]], ZZ),
|
||||
DM([[1, 0], [0, 1]], ZZ),
|
||||
DM([[1, 0], [0, 2]], ZZ),
|
||||
]
|
||||
|
||||
non_snf_examples = [
|
||||
DM([[0, 1], [0, 0]], ZZ),
|
||||
DM([[0, 0], [0, 1]], ZZ),
|
||||
DM([[2, 0], [0, 3]], ZZ),
|
||||
]
|
||||
|
||||
for m in snf_examples:
|
||||
assert is_smith_normal_form(m) is True
|
||||
|
||||
for m in non_snf_examples:
|
||||
assert is_smith_normal_form(m) is False
|
||||
|
||||
|
||||
def test_smith_normal():
|
||||
|
||||
m = DM([
|
||||
[12, 6, 4, 8],
|
||||
[3, 9, 6, 12],
|
||||
[2, 16, 14, 28],
|
||||
[20, 10, 10, 20]], ZZ)
|
||||
|
||||
smf = DM([
|
||||
[1, 0, 0, 0],
|
||||
[0, 10, 0, 0],
|
||||
[0, 0, 30, 0],
|
||||
[0, 0, 0, 0]], ZZ)
|
||||
|
||||
s = DM([
|
||||
[0, 1, -1, 0],
|
||||
[1, -4, 0, 0],
|
||||
[0, -2, 3, 0],
|
||||
[-2, 2, -1, 1]], ZZ)
|
||||
|
||||
t = DM([
|
||||
[1, 1, 10, 0],
|
||||
[0, -1, -2, 0],
|
||||
[0, 1, 3, -2],
|
||||
[0, 0, 0, 1]], ZZ)
|
||||
|
||||
assert smith_normal_form(m).to_dense() == smf
|
||||
assert smith_normal_decomp(m) == (smf, s, t)
|
||||
assert is_smith_normal_form(smf)
|
||||
assert smf == s * m * t
|
||||
|
||||
m00 = DomainMatrix.zeros((0, 0), ZZ).to_dense()
|
||||
m01 = DomainMatrix.zeros((0, 1), ZZ).to_dense()
|
||||
m10 = DomainMatrix.zeros((1, 0), ZZ).to_dense()
|
||||
i11 = DM([[1]], ZZ)
|
||||
|
||||
assert smith_normal_form(m00) == m00.to_sparse()
|
||||
assert smith_normal_form(m01) == m01.to_sparse()
|
||||
assert smith_normal_form(m10) == m10.to_sparse()
|
||||
assert smith_normal_form(i11) == i11.to_sparse()
|
||||
|
||||
assert smith_normal_decomp(m00) == (m00, m00, m00)
|
||||
assert smith_normal_decomp(m01) == (m01, m00, i11)
|
||||
assert smith_normal_decomp(m10) == (m10, i11, m00)
|
||||
assert smith_normal_decomp(i11) == (i11, i11, i11)
|
||||
|
||||
x = Symbol('x')
|
||||
m = DM([[x-1, 1, -1],
|
||||
[ 0, x, -1],
|
||||
[ 0, -1, x]], QQ[x])
|
||||
dx = m.domain.gens[0]
|
||||
assert invariant_factors(m) == (1, dx-1, dx**2-1)
|
||||
|
||||
zr = DomainMatrix([], (0, 2), ZZ)
|
||||
zc = DomainMatrix([[], []], (2, 0), ZZ)
|
||||
assert smith_normal_form(zr).to_dense() == zr
|
||||
assert smith_normal_form(zc).to_dense() == zc
|
||||
|
||||
assert smith_normal_form(DM([[2, 4]], ZZ)).to_dense() == DM([[2, 0]], ZZ)
|
||||
assert smith_normal_form(DM([[0, -2]], ZZ)).to_dense() == DM([[2, 0]], ZZ)
|
||||
assert smith_normal_form(DM([[0], [-2]], ZZ)).to_dense() == DM([[2], [0]], ZZ)
|
||||
|
||||
assert smith_normal_decomp(DM([[0, -2]], ZZ)) == (
|
||||
DM([[2, 0]], ZZ), DM([[-1]], ZZ), DM([[0, 1], [1, 0]], ZZ)
|
||||
)
|
||||
assert smith_normal_decomp(DM([[0], [-2]], ZZ)) == (
|
||||
DM([[2], [0]], ZZ), DM([[0, -1], [1, 0]], ZZ), DM([[1]], ZZ)
|
||||
)
|
||||
|
||||
m = DM([[3, 0, 0, 0], [0, 0, 0, 0], [0, 0, 2, 0]], ZZ)
|
||||
snf = DM([[1, 0, 0, 0], [0, 6, 0, 0], [0, 0, 0, 0]], ZZ)
|
||||
s = DM([[1, 0, 1], [2, 0, 3], [0, 1, 0]], ZZ)
|
||||
t = DM([[1, -2, 0, 0], [0, 0, 0, 1], [-1, 3, 0, 0], [0, 0, 1, 0]], ZZ)
|
||||
|
||||
assert smith_normal_form(m).to_dense() == snf
|
||||
assert smith_normal_decomp(m) == (snf, s, t)
|
||||
assert is_smith_normal_form(snf)
|
||||
assert snf == s * m * t
|
||||
|
||||
raises(ValueError, lambda: smith_normal_form(DM([[1]], ZZ[x])))
|
||||
|
||||
|
||||
def test_hermite_normal():
|
||||
m = DM([[2, 7, 17, 29, 41], [3, 11, 19, 31, 43], [5, 13, 23, 37, 47]], ZZ)
|
||||
hnf = DM([[1, 0, 0], [0, 2, 1], [0, 0, 1]], ZZ)
|
||||
assert hermite_normal_form(m) == hnf
|
||||
assert hermite_normal_form(m, D=ZZ(2)) == hnf
|
||||
assert hermite_normal_form(m, D=ZZ(2), check_rank=True) == hnf
|
||||
|
||||
m = m.transpose()
|
||||
hnf = DM([[37, 0, 19], [222, -6, 113], [48, 0, 25], [0, 2, 1], [0, 0, 1]], ZZ)
|
||||
assert hermite_normal_form(m) == hnf
|
||||
raises(DMShapeError, lambda: _hermite_normal_form_modulo_D(m, ZZ(96)))
|
||||
raises(DMDomainError, lambda: _hermite_normal_form_modulo_D(m, QQ(96)))
|
||||
|
||||
m = DM([[8, 28, 68, 116, 164], [3, 11, 19, 31, 43], [5, 13, 23, 37, 47]], ZZ)
|
||||
hnf = DM([[4, 0, 0], [0, 2, 1], [0, 0, 1]], ZZ)
|
||||
assert hermite_normal_form(m) == hnf
|
||||
assert hermite_normal_form(m, D=ZZ(8)) == hnf
|
||||
assert hermite_normal_form(m, D=ZZ(8), check_rank=True) == hnf
|
||||
|
||||
m = DM([[10, 8, 6, 30, 2], [45, 36, 27, 18, 9], [5, 4, 3, 2, 1]], ZZ)
|
||||
hnf = DM([[26, 2], [0, 9], [0, 1]], ZZ)
|
||||
assert hermite_normal_form(m) == hnf
|
||||
|
||||
m = DM([[2, 7], [0, 0], [0, 0]], ZZ)
|
||||
hnf = DM([[1], [0], [0]], ZZ)
|
||||
assert hermite_normal_form(m) == hnf
|
||||
|
||||
m = DM([[-2, 1], [0, 1]], ZZ)
|
||||
hnf = DM([[2, 1], [0, 1]], ZZ)
|
||||
assert hermite_normal_form(m) == hnf
|
||||
|
||||
m = DomainMatrix([[QQ(1)]], (1, 1), QQ)
|
||||
raises(DMDomainError, lambda: hermite_normal_form(m))
|
||||
raises(DMDomainError, lambda: _hermite_normal_form(m))
|
||||
raises(DMDomainError, lambda: _hermite_normal_form_modulo_D(m, ZZ(1)))
|
||||
@@ -0,0 +1,209 @@
|
||||
from sympy import ZZ, Matrix
|
||||
from sympy.polys.matrices import DM, DomainMatrix
|
||||
from sympy.polys.matrices.ddm import DDM
|
||||
from sympy.polys.matrices.sdm import SDM
|
||||
|
||||
import pytest
|
||||
|
||||
zeros = lambda shape, K: DomainMatrix.zeros(shape, K).to_dense()
|
||||
eye = lambda n, K: DomainMatrix.eye(n, K).to_dense()
|
||||
|
||||
|
||||
#
|
||||
# DomainMatrix.nullspace can have a divided answer or can return an undivided
|
||||
# uncanonical answer. The uncanonical answer is not unique but we can make it
|
||||
# unique by making it primitive (remove gcd). The tests here all show the
|
||||
# primitive form. We test two things:
|
||||
#
|
||||
# A.nullspace().primitive()[1] == answer.
|
||||
# A.nullspace(divide_last=True) == _divide_last(answer).
|
||||
#
|
||||
# The nullspace as returned by DomainMatrix and related classes is the
|
||||
# transpose of the nullspace as returned by Matrix. Matrix returns a list of
|
||||
# of column vectors whereas DomainMatrix returns a matrix whose rows are the
|
||||
# nullspace vectors.
|
||||
#
|
||||
|
||||
|
||||
NULLSPACE_EXAMPLES = [
|
||||
|
||||
(
|
||||
'zz_1',
|
||||
DM([[ 1, 2, 3]], ZZ),
|
||||
DM([[-2, 1, 0],
|
||||
[-3, 0, 1]], ZZ),
|
||||
),
|
||||
|
||||
(
|
||||
'zz_2',
|
||||
zeros((0, 0), ZZ),
|
||||
zeros((0, 0), ZZ),
|
||||
),
|
||||
|
||||
(
|
||||
'zz_3',
|
||||
zeros((2, 0), ZZ),
|
||||
zeros((0, 0), ZZ),
|
||||
),
|
||||
|
||||
(
|
||||
'zz_4',
|
||||
zeros((0, 2), ZZ),
|
||||
eye(2, ZZ),
|
||||
),
|
||||
|
||||
(
|
||||
'zz_5',
|
||||
zeros((2, 2), ZZ),
|
||||
eye(2, ZZ),
|
||||
),
|
||||
|
||||
(
|
||||
'zz_6',
|
||||
DM([[1, 2],
|
||||
[3, 4]], ZZ),
|
||||
zeros((0, 2), ZZ),
|
||||
),
|
||||
|
||||
(
|
||||
'zz_7',
|
||||
DM([[1, 1],
|
||||
[1, 1]], ZZ),
|
||||
DM([[-1, 1]], ZZ),
|
||||
),
|
||||
|
||||
(
|
||||
'zz_8',
|
||||
DM([[1],
|
||||
[1]], ZZ),
|
||||
zeros((0, 1), ZZ),
|
||||
),
|
||||
|
||||
(
|
||||
'zz_9',
|
||||
DM([[1, 1]], ZZ),
|
||||
DM([[-1, 1]], ZZ),
|
||||
),
|
||||
|
||||
(
|
||||
'zz_10',
|
||||
DM([[0, 0, 0, 0, 0, 1, 0, 0, 0, 0],
|
||||
[1, 0, 0, 0, 0, 0, 1, 0, 0, 0],
|
||||
[0, 1, 0, 0, 0, 0, 0, 1, 0, 0],
|
||||
[0, 0, 0, 1, 0, 0, 0, 0, 1, 0],
|
||||
[0, 0, 0, 0, 1, 0, 0, 0, 0, 1]], ZZ),
|
||||
DM([[ 0, 0, 1, 0, 0, 0, 0, 0, 0, 0],
|
||||
[-1, 0, 0, 0, 0, 0, 1, 0, 0, 0],
|
||||
[ 0, -1, 0, 0, 0, 0, 0, 1, 0, 0],
|
||||
[ 0, 0, 0, -1, 0, 0, 0, 0, 1, 0],
|
||||
[ 0, 0, 0, 0, -1, 0, 0, 0, 0, 1]], ZZ),
|
||||
),
|
||||
|
||||
]
|
||||
|
||||
|
||||
def _to_DM(A, ans):
|
||||
"""Convert the answer to DomainMatrix."""
|
||||
if isinstance(A, DomainMatrix):
|
||||
return A.to_dense()
|
||||
elif isinstance(A, DDM):
|
||||
return DomainMatrix(list(A), A.shape, A.domain).to_dense()
|
||||
elif isinstance(A, SDM):
|
||||
return DomainMatrix(dict(A), A.shape, A.domain).to_dense()
|
||||
else:
|
||||
assert False # pragma: no cover
|
||||
|
||||
|
||||
def _divide_last(null):
|
||||
"""Normalize the nullspace by the rightmost non-zero entry."""
|
||||
null = null.to_field()
|
||||
|
||||
if null.is_zero_matrix:
|
||||
return null
|
||||
|
||||
rows = []
|
||||
for i in range(null.shape[0]):
|
||||
for j in reversed(range(null.shape[1])):
|
||||
if null[i, j]:
|
||||
rows.append(null[i, :] / null[i, j])
|
||||
break
|
||||
else:
|
||||
assert False # pragma: no cover
|
||||
|
||||
return DomainMatrix.vstack(*rows)
|
||||
|
||||
|
||||
def _check_primitive(null, null_ans):
|
||||
"""Check that the primitive of the answer matches."""
|
||||
null = _to_DM(null, null_ans)
|
||||
cont, null_prim = null.primitive()
|
||||
assert null_prim == null_ans
|
||||
|
||||
|
||||
def _check_divided(null, null_ans):
|
||||
"""Check the divided answer."""
|
||||
null = _to_DM(null, null_ans)
|
||||
null_ans_norm = _divide_last(null_ans)
|
||||
assert null == null_ans_norm
|
||||
|
||||
|
||||
@pytest.mark.parametrize('name, A, A_null', NULLSPACE_EXAMPLES)
|
||||
def test_Matrix_nullspace(name, A, A_null):
|
||||
A = A.to_Matrix()
|
||||
|
||||
A_null_cols = A.nullspace()
|
||||
|
||||
# We have to patch up the case where the nullspace is empty
|
||||
if A_null_cols:
|
||||
A_null_found = Matrix.hstack(*A_null_cols)
|
||||
else:
|
||||
A_null_found = Matrix.zeros(A.cols, 0)
|
||||
|
||||
A_null_found = A_null_found.to_DM().to_field().to_dense()
|
||||
|
||||
# The Matrix result is the transpose of DomainMatrix result.
|
||||
A_null_found = A_null_found.transpose()
|
||||
|
||||
_check_divided(A_null_found, A_null)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('name, A, A_null', NULLSPACE_EXAMPLES)
|
||||
def test_dm_dense_nullspace(name, A, A_null):
|
||||
A = A.to_field().to_dense()
|
||||
A_null_found = A.nullspace(divide_last=True)
|
||||
_check_divided(A_null_found, A_null)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('name, A, A_null', NULLSPACE_EXAMPLES)
|
||||
def test_dm_sparse_nullspace(name, A, A_null):
|
||||
A = A.to_field().to_sparse()
|
||||
A_null_found = A.nullspace(divide_last=True)
|
||||
_check_divided(A_null_found, A_null)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('name, A, A_null', NULLSPACE_EXAMPLES)
|
||||
def test_ddm_nullspace(name, A, A_null):
|
||||
A = A.to_field().to_ddm()
|
||||
A_null_found, _ = A.nullspace()
|
||||
_check_divided(A_null_found, A_null)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('name, A, A_null', NULLSPACE_EXAMPLES)
|
||||
def test_sdm_nullspace(name, A, A_null):
|
||||
A = A.to_field().to_sdm()
|
||||
A_null_found, _ = A.nullspace()
|
||||
_check_divided(A_null_found, A_null)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('name, A, A_null', NULLSPACE_EXAMPLES)
|
||||
def test_dm_dense_nullspace_fracfree(name, A, A_null):
|
||||
A = A.to_dense()
|
||||
A_null_found = A.nullspace()
|
||||
_check_primitive(A_null_found, A_null)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('name, A, A_null', NULLSPACE_EXAMPLES)
|
||||
def test_dm_sparse_nullspace_fracfree(name, A, A_null):
|
||||
A = A.to_sparse()
|
||||
A_null_found = A.nullspace()
|
||||
_check_primitive(A_null_found, A_null)
|
||||
@@ -0,0 +1,737 @@
|
||||
from sympy import ZZ, QQ, ZZ_I, EX, Matrix, eye, zeros, symbols
|
||||
from sympy.polys.matrices import DM, DomainMatrix
|
||||
from sympy.polys.matrices.dense import ddm_irref_den, ddm_irref
|
||||
from sympy.polys.matrices.ddm import DDM
|
||||
from sympy.polys.matrices.sdm import SDM, sdm_irref, sdm_rref_den
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
#
|
||||
# The dense and sparse implementations of rref_den are ddm_irref_den and
|
||||
# sdm_irref_den. These can give results that differ by some factor and also
|
||||
# give different results if the order of the rows is changed. The tests below
|
||||
# show all results on lowest terms as should be returned by cancel_denom.
|
||||
#
|
||||
# The EX domain is also a case where the dense and sparse implementations
|
||||
# can give results in different forms: the results should be equivalent but
|
||||
# are not canonical because EX does not have a canonical form.
|
||||
#
|
||||
|
||||
|
||||
a, b, c, d = symbols('a, b, c, d')
|
||||
|
||||
|
||||
qq_large_1 = DM([
|
||||
[ (1,2), (1,3), (1,5), (1,7), (1,11), (1,13), (1,17), (1,19), (1,23), (1,29), (1,31)],
|
||||
[ (1,37), (1,41), (1,43), (1,47), (1,53), (1,59), (1,61), (1,67), (1,71), (1,73), (1,79)],
|
||||
[ (1,83), (1,89), (1,97),(1,101),(1,103),(1,107),(1,109),(1,113),(1,127),(1,131),(1,137)],
|
||||
[(1,139),(1,149),(1,151),(1,157),(1,163),(1,167),(1,173),(1,179),(1,181),(1,191),(1,193)],
|
||||
[(1,197),(1,199),(1,211),(1,223),(1,227),(1,229),(1,233),(1,239),(1,241),(1,251),(1,257)],
|
||||
[(1,263),(1,269),(1,271),(1,277),(1,281),(1,283),(1,293),(1,307),(1,311),(1,313),(1,317)],
|
||||
[(1,331),(1,337),(1,347),(1,349),(1,353),(1,359),(1,367),(1,373),(1,379),(1,383),(1,389)],
|
||||
[(1,397),(1,401),(1,409),(1,419),(1,421),(1,431),(1,433),(1,439),(1,443),(1,449),(1,457)],
|
||||
[(1,461),(1,463),(1,467),(1,479),(1,487),(1,491),(1,499),(1,503),(1,509),(1,521),(1,523)],
|
||||
[(1,541),(1,547),(1,557),(1,563),(1,569),(1,571),(1,577),(1,587),(1,593),(1,599),(1,601)],
|
||||
[(1,607),(1,613),(1,617),(1,619),(1,631),(1,641),(1,643),(1,647),(1,653),(1,659),(1,661)]],
|
||||
QQ)
|
||||
|
||||
qq_large_2 = qq_large_1 + 10**100 * DomainMatrix.eye(11, QQ)
|
||||
|
||||
|
||||
RREF_EXAMPLES = [
|
||||
(
|
||||
'zz_1',
|
||||
DM([[1, 2, 3]], ZZ),
|
||||
DM([[1, 2, 3]], ZZ),
|
||||
ZZ(1),
|
||||
),
|
||||
|
||||
(
|
||||
'zz_2',
|
||||
DomainMatrix([], (0, 0), ZZ),
|
||||
DomainMatrix([], (0, 0), ZZ),
|
||||
ZZ(1),
|
||||
),
|
||||
|
||||
(
|
||||
'zz_3',
|
||||
DM([[1, 2],
|
||||
[3, 4]], ZZ),
|
||||
DM([[1, 0],
|
||||
[0, 1]], ZZ),
|
||||
ZZ(1),
|
||||
),
|
||||
|
||||
(
|
||||
'zz_4',
|
||||
DM([[1, 0],
|
||||
[3, 4]], ZZ),
|
||||
DM([[1, 0],
|
||||
[0, 1]], ZZ),
|
||||
ZZ(1),
|
||||
),
|
||||
|
||||
(
|
||||
'zz_5',
|
||||
DM([[0, 2],
|
||||
[3, 4]], ZZ),
|
||||
DM([[1, 0],
|
||||
[0, 1]], ZZ),
|
||||
ZZ(1),
|
||||
),
|
||||
|
||||
(
|
||||
'zz_6',
|
||||
DM([[1, 2, 3],
|
||||
[4, 5, 6],
|
||||
[7, 8, 9]], ZZ),
|
||||
DM([[1, 0, -1],
|
||||
[0, 1, 2],
|
||||
[0, 0, 0]], ZZ),
|
||||
ZZ(1),
|
||||
),
|
||||
|
||||
(
|
||||
'zz_7',
|
||||
DM([[0, 0, 0],
|
||||
[0, 0, 0],
|
||||
[1, 0, 0]], ZZ),
|
||||
DM([[1, 0, 0],
|
||||
[0, 0, 0],
|
||||
[0, 0, 0]], ZZ),
|
||||
ZZ(1),
|
||||
),
|
||||
|
||||
(
|
||||
'zz_8',
|
||||
DM([[0, 0, 0],
|
||||
[0, 0, 0],
|
||||
[0, 0, 0]], ZZ),
|
||||
DM([[0, 0, 0],
|
||||
[0, 0, 0],
|
||||
[0, 0, 0]], ZZ),
|
||||
ZZ(1),
|
||||
),
|
||||
|
||||
(
|
||||
'zz_9',
|
||||
DM([[1, 1, 0],
|
||||
[0, 0, 2],
|
||||
[0, 0, 0]], ZZ),
|
||||
DM([[1, 1, 0],
|
||||
[0, 0, 1],
|
||||
[0, 0, 0]], ZZ),
|
||||
ZZ(1),
|
||||
),
|
||||
|
||||
(
|
||||
'zz_10',
|
||||
DM([[2, 2, 0],
|
||||
[0, 0, 2],
|
||||
[0, 0, 0]], ZZ),
|
||||
DM([[1, 1, 0],
|
||||
[0, 0, 1],
|
||||
[0, 0, 0]], ZZ),
|
||||
ZZ(1),
|
||||
),
|
||||
|
||||
(
|
||||
'zz_11',
|
||||
DM([[2, 2, 0],
|
||||
[0, 2, 2],
|
||||
[0, 0, 2]], ZZ),
|
||||
DM([[1, 0, 0],
|
||||
[0, 1, 0],
|
||||
[0, 0, 1]], ZZ),
|
||||
ZZ(1),
|
||||
),
|
||||
|
||||
(
|
||||
'zz_12',
|
||||
DM([[ 1, 2, 3],
|
||||
[ 4, 5, 6],
|
||||
[ 7, 8, 9],
|
||||
[10, 11, 12]], ZZ),
|
||||
DM([[1, 0, -1],
|
||||
[0, 1, 2],
|
||||
[0, 0, 0],
|
||||
[0, 0, 0]], ZZ),
|
||||
ZZ(1),
|
||||
),
|
||||
|
||||
(
|
||||
'zz_13',
|
||||
DM([[ 1, 2, 3],
|
||||
[ 4, 5, 6],
|
||||
[ 7, 8, 9],
|
||||
[10, 11, 13]], ZZ),
|
||||
DM([[ 1, 0, 0],
|
||||
[ 0, 1, 0],
|
||||
[ 0, 0, 1],
|
||||
[ 0, 0, 0]], ZZ),
|
||||
ZZ(1),
|
||||
),
|
||||
|
||||
(
|
||||
'zz_14',
|
||||
DM([[1, 2, 4, 3],
|
||||
[4, 5, 10, 6],
|
||||
[7, 8, 16, 9]], ZZ),
|
||||
DM([[1, 0, 0, -1],
|
||||
[0, 1, 2, 2],
|
||||
[0, 0, 0, 0]], ZZ),
|
||||
ZZ(1),
|
||||
),
|
||||
|
||||
(
|
||||
'zz_15',
|
||||
DM([[1, 2, 4, 3],
|
||||
[4, 5, 10, 6],
|
||||
[7, 8, 17, 9]], ZZ),
|
||||
DM([[1, 0, 0, -1],
|
||||
[0, 1, 0, 2],
|
||||
[0, 0, 1, 0]], ZZ),
|
||||
ZZ(1),
|
||||
),
|
||||
|
||||
(
|
||||
'zz_16',
|
||||
DM([[1, 2, 0, 1],
|
||||
[1, 1, 9, 0]], ZZ),
|
||||
DM([[1, 0, 18, -1],
|
||||
[0, 1, -9, 1]], ZZ),
|
||||
ZZ(1),
|
||||
),
|
||||
|
||||
(
|
||||
'zz_17',
|
||||
DM([[1, 1, 1],
|
||||
[1, 2, 2]], ZZ),
|
||||
DM([[1, 0, 0],
|
||||
[0, 1, 1]], ZZ),
|
||||
ZZ(1),
|
||||
),
|
||||
|
||||
(
|
||||
# Here the sparse implementation and dense implementation give very
|
||||
# different denominators: 4061232 and -1765176.
|
||||
'zz_18',
|
||||
DM([[94, 24, 0, 27, 0],
|
||||
[79, 0, 0, 0, 0],
|
||||
[85, 16, 71, 81, 0],
|
||||
[ 0, 0, 72, 77, 0],
|
||||
[21, 0, 34, 0, 0]], ZZ),
|
||||
DM([[ 1, 0, 0, 0, 0],
|
||||
[ 0, 1, 0, 0, 0],
|
||||
[ 0, 0, 1, 0, 0],
|
||||
[ 0, 0, 0, 1, 0],
|
||||
[ 0, 0, 0, 0, 0]], ZZ),
|
||||
ZZ(1),
|
||||
),
|
||||
|
||||
(
|
||||
# Let's have a denominator that cannot be cancelled.
|
||||
'zz_19',
|
||||
DM([[1, 2, 4],
|
||||
[4, 5, 6]], ZZ),
|
||||
DM([[3, 0, -8],
|
||||
[0, 3, 10]], ZZ),
|
||||
ZZ(3),
|
||||
),
|
||||
|
||||
(
|
||||
'zz_20',
|
||||
DM([[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 4]], ZZ),
|
||||
DM([[0, 0, 0, 0, 1],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0]], ZZ),
|
||||
ZZ(1),
|
||||
),
|
||||
|
||||
(
|
||||
'zz_21',
|
||||
DM([[0, 0, 0, 0, 0, 1, 0, 0, 0, 0],
|
||||
[1, 0, 0, 0, 0, 0, 1, 0, 0, 0],
|
||||
[0, 1, 0, 0, 0, 0, 0, 1, 0, 0],
|
||||
[0, 0, 0, 1, 0, 0, 0, 0, 1, 0],
|
||||
[0, 0, 0, 0, 1, 0, 0, 0, 0, 1]], ZZ),
|
||||
DM([[1, 0, 0, 0, 0, 0, 1, 0, 0, 0],
|
||||
[0, 1, 0, 0, 0, 0, 0, 1, 0, 0],
|
||||
[0, 0, 0, 1, 0, 0, 0, 0, 1, 0],
|
||||
[0, 0, 0, 0, 1, 0, 0, 0, 0, 1],
|
||||
[0, 0, 0, 0, 0, 1, 0, 0, 0, 0]], ZZ),
|
||||
ZZ(1),
|
||||
),
|
||||
|
||||
(
|
||||
'zz_22',
|
||||
DM([[1, 1, 1, 0, 1],
|
||||
[1, 1, 0, 1, 0],
|
||||
[1, 0, 1, 0, 1],
|
||||
[1, 1, 0, 1, 0],
|
||||
[1, 0, 0, 0, 0]], ZZ),
|
||||
DM([[1, 0, 0, 0, 0],
|
||||
[0, 1, 0, 0, 0],
|
||||
[0, 0, 1, 0, 1],
|
||||
[0, 0, 0, 1, 0],
|
||||
[0, 0, 0, 0, 0]], ZZ),
|
||||
ZZ(1),
|
||||
),
|
||||
|
||||
(
|
||||
'zz_large_1',
|
||||
DM([
|
||||
[ 0, 0, 0, 81, 0, 0, 75, 0, 0, 0, 0, 0, 0, 27, 0, 0, 0, 0, 0, 0],
|
||||
[ 0, 0, 0, 0, 0, 86, 0, 92, 79, 54, 0, 7, 0, 0, 0, 0, 79, 0, 0, 0],
|
||||
[89, 54, 81, 0, 0, 20, 0, 0, 0, 0, 0, 0, 51, 0, 94, 0, 0, 77, 0, 0],
|
||||
[ 0, 0, 0, 96, 0, 0, 0, 0, 0, 0, 0, 0, 48, 29, 0, 0, 5, 0, 32, 0],
|
||||
[ 0, 70, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 60, 0, 0, 0, 11],
|
||||
[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 37, 0, 43, 0, 0],
|
||||
[ 0, 0, 0, 0, 0, 38, 91, 0, 0, 0, 0, 38, 0, 0, 0, 0, 0, 26, 0, 0],
|
||||
[69, 0, 0, 0, 0, 0, 94, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 55],
|
||||
[ 0, 13, 18, 49, 49, 88, 0, 0, 35, 54, 0, 0, 51, 0, 0, 0, 0, 0, 0, 87],
|
||||
[ 0, 0, 0, 0, 31, 0, 40, 0, 0, 0, 0, 0, 0, 50, 0, 0, 0, 0, 88, 0],
|
||||
[ 0, 0, 0, 0, 0, 0, 0, 0, 98, 0, 0, 0, 15, 53, 0, 92, 0, 0, 0, 0],
|
||||
[ 0, 0, 0, 95, 0, 0, 0, 36, 0, 0, 0, 0, 0, 72, 0, 0, 0, 0, 73, 19],
|
||||
[ 0, 65, 14, 96, 0, 0, 0, 0, 0, 0, 0, 0, 0, 90, 0, 0, 0, 34, 0, 0],
|
||||
[ 0, 0, 0, 16, 39, 44, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 51, 0, 0],
|
||||
[ 0, 17, 0, 0, 0, 99, 84, 13, 50, 84, 0, 0, 0, 0, 95, 0, 43, 33, 20, 0],
|
||||
[79, 0, 17, 52, 99, 12, 69, 0, 98, 0, 68, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
[ 0, 0, 0, 82, 0, 44, 0, 0, 0, 97, 0, 0, 0, 0, 0, 10, 0, 0, 31, 0],
|
||||
[ 0, 0, 21, 0, 67, 0, 0, 0, 0, 0, 4, 0, 50, 0, 0, 0, 33, 0, 0, 0],
|
||||
[ 0, 0, 0, 0, 9, 42, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8],
|
||||
[ 0, 77, 0, 0, 0, 0, 0, 0, 0, 0, 34, 93, 0, 0, 0, 0, 47, 0, 0, 0]],
|
||||
ZZ),
|
||||
DM([[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0],
|
||||
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]], ZZ),
|
||||
ZZ(1),
|
||||
),
|
||||
|
||||
(
|
||||
'zz_large_2',
|
||||
DM([
|
||||
[ 0, 0, 0, 0, 50, 0, 6, 81, 0, 1, 86, 0, 0, 98, 82, 94, 4, 0, 0, 29],
|
||||
[ 0, 44, 43, 0, 62, 0, 0, 0, 60, 0, 0, 0, 0, 71, 9, 0, 57, 41, 0, 93],
|
||||
[ 0, 0, 28, 0, 74, 89, 42, 0, 28, 0, 6, 0, 0, 0, 44, 0, 0, 0, 77, 19],
|
||||
[ 0, 21, 82, 0, 30, 88, 0, 89, 68, 0, 0, 0, 79, 41, 0, 0, 99, 0, 0, 0],
|
||||
[31, 0, 0, 0, 19, 64, 0, 0, 79, 0, 5, 0, 72, 10, 60, 32, 64, 59, 0, 24],
|
||||
[ 0, 0, 0, 0, 0, 57, 0, 94, 0, 83, 20, 0, 0, 9, 31, 0, 49, 26, 58, 0],
|
||||
[ 0, 65, 56, 31, 64, 0, 0, 0, 0, 0, 0, 52, 85, 0, 0, 0, 0, 51, 0, 0],
|
||||
[ 0, 35, 0, 0, 0, 69, 0, 0, 64, 0, 0, 0, 0, 70, 0, 0, 90, 0, 75, 76],
|
||||
[69, 7, 0, 90, 0, 0, 84, 0, 47, 69, 19, 20, 42, 0, 0, 32, 71, 35, 0, 0],
|
||||
[39, 0, 90, 0, 0, 4, 85, 0, 0, 55, 0, 0, 0, 35, 67, 40, 0, 40, 0, 77],
|
||||
[98, 63, 0, 71, 0, 50, 0, 2, 61, 0, 38, 0, 0, 0, 0, 75, 0, 40, 33, 56],
|
||||
[ 0, 73, 0, 64, 0, 38, 0, 35, 61, 0, 0, 52, 0, 7, 0, 51, 0, 0, 0, 34],
|
||||
[ 0, 0, 28, 0, 34, 5, 63, 45, 14, 42, 60, 16, 76, 54, 99, 0, 28, 30, 0, 0],
|
||||
[58, 37, 14, 0, 0, 0, 94, 0, 0, 90, 0, 0, 0, 0, 0, 0, 0, 8, 90, 53],
|
||||
[86, 74, 94, 0, 49, 10, 60, 0, 40, 18, 0, 0, 0, 31, 60, 24, 0, 1, 0, 29],
|
||||
[53, 0, 0, 97, 0, 0, 58, 0, 0, 39, 44, 47, 0, 0, 0, 12, 50, 0, 0, 11],
|
||||
[ 4, 0, 92, 10, 28, 0, 0, 89, 0, 0, 18, 54, 23, 39, 0, 2, 0, 48, 0, 92],
|
||||
[ 0, 0, 90, 77, 95, 33, 0, 0, 49, 22, 39, 0, 0, 0, 0, 0, 0, 40, 0, 0],
|
||||
[96, 0, 0, 0, 0, 38, 86, 0, 22, 76, 0, 0, 0, 0, 83, 88, 95, 65, 72, 0],
|
||||
[81, 65, 0, 4, 60, 0, 19, 0, 0, 68, 0, 0, 89, 0, 67, 22, 0, 0, 55, 33]],
|
||||
ZZ),
|
||||
DM([
|
||||
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0],
|
||||
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]],
|
||||
ZZ),
|
||||
ZZ(1),
|
||||
),
|
||||
|
||||
(
|
||||
'zz_large_3',
|
||||
DM([
|
||||
[62,35,89,58,22,47,30,28,52,72,17,56,80,26,64,21,10,35,24,42,96,32,23,50,92,37,76,94,63,66],
|
||||
[20,47,96,34,10,98,19,6,29,2,19,92,61,94,38,41,32,9,5,94,31,58,27,41,72,85,61,62,40,46],
|
||||
[69,26,35,68,25,52,94,13,38,65,81,10,29,15,5,4,13,99,85,0,80,51,60,60,26,77,85,2,87,25],
|
||||
[99,58,69,15,52,12,18,7,27,56,12,54,21,92,38,95,33,83,28,1,44,8,29,84,92,12,2,25,46,46],
|
||||
[93,13,55,48,35,87,24,40,23,35,25,32,0,19,0,85,4,79,26,11,46,75,7,96,76,11,7,57,99,75],
|
||||
[128,85,26,51,161,173,77,78,85,103,123,58,91,147,38,91,161,36,123,81,102,25,75,59,17,150,112,65,77,143],
|
||||
[15,59,61,82,12,83,34,8,94,71,66,7,91,21,48,69,26,12,64,38,97,87,38,15,51,33,93,43,66,89],
|
||||
[74,74,53,39,69,90,41,80,32,66,40,83,87,87,61,38,12,80,24,49,37,90,19,33,56,0,46,57,56,60],
|
||||
[82,11,0,25,56,58,39,49,92,93,80,38,19,62,33,85,19,61,14,30,45,91,97,34,97,53,92,28,33,43],
|
||||
[83,79,41,16,95,35,53,45,26,4,71,76,61,69,69,72,87,92,59,72,54,11,22,83,8,57,77,55,19,22],
|
||||
[49,34,13,31,72,77,52,70,46,41,37,6,42,66,35,6,75,33,62,57,30,14,26,31,9,95,89,13,12,90],
|
||||
[29,3,49,30,51,32,77,41,38,50,16,1,87,81,93,88,58,91,83,0,38,67,29,64,60,84,5,60,23,28],
|
||||
[79,51,13,20,89,96,25,8,39,62,86,52,49,81,3,85,86,3,61,24,72,11,49,28,8,55,23,52,65,53],
|
||||
[96,86,73,20,41,20,37,18,10,61,85,24,40,83,69,41,4,92,23,99,64,33,18,36,32,56,60,98,39,24],
|
||||
[32,62,47,80,51,66,17,1,9,30,65,75,75,88,99,92,64,53,53,86,38,51,41,14,35,18,39,25,26,32],
|
||||
[39,21,8,16,33,6,35,85,75,62,43,34,18,68,71,28,32,18,12,0,81,53,1,99,3,5,45,99,35,33],
|
||||
[19,95,89,45,75,94,92,5,84,93,34,17,50,56,79,98,68,82,65,81,51,90,5,95,33,71,46,61,14,7],
|
||||
[53,92,8,49,67,84,21,79,49,95,66,48,36,14,62,97,26,45,58,31,83,48,11,89,67,72,91,34,56,89],
|
||||
[56,76,99,92,40,8,0,16,15,48,35,72,91,46,81,14,86,60,51,7,33,12,53,78,48,21,3,89,15,79],
|
||||
[81,43,33,49,6,49,36,32,57,74,87,91,17,37,31,17,67,1,40,38,69,8,3,48,59,37,64,97,11,3],
|
||||
[98,48,77,16,2,48,57,38,63,59,79,35,16,71,60,86,71,41,14,76,80,97,77,69,4,58,22,55,26,73],
|
||||
[80,47,78,44,31,48,47,29,29,62,19,21,17,24,19,3,53,93,97,57,13,54,12,10,77,66,60,75,32,21],
|
||||
[86,63,2,13,71,38,86,23,18,15,91,65,77,65,9,92,50,0,17,42,99,80,99,27,10,99,92,9,87,84],
|
||||
[66,27,72,13,13,15,72,75,39,3,14,71,15,68,10,19,49,54,11,29,47,20,63,13,97,47,24,62,16,96],
|
||||
[42,63,83,60,49,68,9,53,75,87,40,25,12,63,0,12,0,95,46,46,55,25,89,1,51,1,1,96,80,52],
|
||||
[35,9,97,13,86,39,66,48,41,57,23,38,11,9,35,72,88,13,41,60,10,64,71,23,1,5,23,57,6,19],
|
||||
[70,61,5,50,72,60,77,13,41,94,1,45,52,22,99,47,27,18,99,42,16,48,26,9,88,77,10,94,11,92],
|
||||
[55,68,58,2,72,56,81,52,79,37,1,40,21,46,27,60,37,13,97,42,85,98,69,60,76,44,42,46,29,73],
|
||||
[73,0,43,17,89,97,45,2,68,14,55,60,95,2,74,85,88,68,93,76,38,76,2,51,45,76,50,79,56,18],
|
||||
[72,58,41,39,24,80,23,79,44,7,98,75,30,6,85,60,20,58,77,71,90,51,38,80,30,15,33,10,82,8]],
|
||||
ZZ),
|
||||
Matrix([
|
||||
[eye(29) * 2028539767964472550625641331179545072876560857886207583101,
|
||||
Matrix([ 4260575808093245475167216057435155595594339172099000182569,
|
||||
169148395880755256182802335904188369274227936894862744452,
|
||||
4915975976683942569102447281579134986891620721539038348914,
|
||||
6113916866367364958834844982578214901958429746875633283248,
|
||||
5585689617819894460378537031623265659753379011388162534838,
|
||||
359776822829880747716695359574308645968094838905181892423,
|
||||
-2800926112141776386671436511182421432449325232461665113305,
|
||||
941642292388230001722444876624818265766384442910688463158,
|
||||
3648811843256146649321864698600908938933015862008642023935,
|
||||
-4104526163246702252932955226754097174212129127510547462419,
|
||||
-704814955438106792441896903238080197619233342348191408078,
|
||||
1640882266829725529929398131287244562048075707575030019335,
|
||||
-4068330845192910563212155694231438198040299927120544468520,
|
||||
136589038308366497790495711534532612862715724187671166593,
|
||||
2544937011460702462290799932536905731142196510605191645593,
|
||||
755591839174293940486133926192300657264122907519174116472,
|
||||
-3683838489869297144348089243628436188645897133242795965021,
|
||||
-522207137101161299969706310062775465103537953077871128403,
|
||||
-2260451796032703984456606059649402832441331339246756656334,
|
||||
-6476809325293587953616004856993300606040336446656916663680,
|
||||
3521944238996782387785653800944972787867472610035040989081,
|
||||
2270762115788407950241944504104975551914297395787473242379,
|
||||
-3259947194628712441902262570532921252128444706733549251156,
|
||||
-5624569821491886970999097239695637132075823246850431083557,
|
||||
-3262698255682055804320585332902837076064075936601504555698,
|
||||
5786719943788937667411185880136324396357603606944869545501,
|
||||
-955257841973865996077323863289453200904051299086000660036,
|
||||
-1294235552446355326174641248209752679127075717918392702116,
|
||||
-3718353510747301598130831152458342785269166356215331448279,
|
||||
]),],
|
||||
[zeros(1, 29), zeros(1, 1)],
|
||||
]).to_DM().to_dense(),
|
||||
ZZ(2028539767964472550625641331179545072876560857886207583101),
|
||||
),
|
||||
|
||||
|
||||
(
|
||||
'qq_1',
|
||||
DM([[(1,2), 0], [0, 2]], QQ),
|
||||
DM([[1, 0], [0, 1]], QQ),
|
||||
QQ(1),
|
||||
),
|
||||
|
||||
(
|
||||
# Standard square case
|
||||
'qq_2',
|
||||
DM([[0, 1],
|
||||
[1, 1]], QQ),
|
||||
DM([[1, 0],
|
||||
[0, 1]], QQ),
|
||||
QQ(1),
|
||||
),
|
||||
|
||||
(
|
||||
# m < n case
|
||||
'qq_3',
|
||||
DM([[1, 2, 1],
|
||||
[3, 4, 1]], QQ),
|
||||
DM([[1, 0, -1],
|
||||
[0, 1, 1]], QQ),
|
||||
QQ(1),
|
||||
),
|
||||
|
||||
(
|
||||
# same m < n but reversed
|
||||
'qq_4',
|
||||
DM([[3, 4, 1],
|
||||
[1, 2, 1]], QQ),
|
||||
DM([[1, 0, -1],
|
||||
[0, 1, 1]], QQ),
|
||||
QQ(1),
|
||||
),
|
||||
|
||||
(
|
||||
# m > n case
|
||||
'qq_5',
|
||||
DM([[1, 0],
|
||||
[1, 3],
|
||||
[0, 1]], QQ),
|
||||
DM([[1, 0],
|
||||
[0, 1],
|
||||
[0, 0]], QQ),
|
||||
QQ(1),
|
||||
),
|
||||
|
||||
(
|
||||
# Example with missing pivot
|
||||
'qq_6',
|
||||
DM([[1, 0, 1],
|
||||
[3, 0, 1]], QQ),
|
||||
DM([[1, 0, 0],
|
||||
[0, 0, 1]], QQ),
|
||||
QQ(1),
|
||||
),
|
||||
|
||||
(
|
||||
# This is intended to trigger the threshold where we give up on
|
||||
# clearing denominators.
|
||||
'qq_large_1',
|
||||
qq_large_1,
|
||||
DomainMatrix.eye(11, QQ).to_dense(),
|
||||
QQ(1),
|
||||
),
|
||||
|
||||
(
|
||||
# This is intended to trigger the threshold where we use rref_den over
|
||||
# QQ.
|
||||
'qq_large_2',
|
||||
qq_large_2,
|
||||
DomainMatrix.eye(11, QQ).to_dense(),
|
||||
QQ(1),
|
||||
),
|
||||
|
||||
(
|
||||
# Example with missing pivot and no replacement
|
||||
|
||||
# This example is just enough to show a different result from the dense
|
||||
# and sparse versions of the algorithm:
|
||||
#
|
||||
# >>> A = Matrix([[0, 1], [0, 2], [1, 0]])
|
||||
# >>> A.to_DM().to_sparse().rref_den()[0].to_Matrix()
|
||||
# Matrix([
|
||||
# [1, 0],
|
||||
# [0, 1],
|
||||
# [0, 0]])
|
||||
# >>> A.to_DM().to_dense().rref_den()[0].to_Matrix()
|
||||
# Matrix([
|
||||
# [2, 0],
|
||||
# [0, 2],
|
||||
# [0, 0]])
|
||||
#
|
||||
'qq_7',
|
||||
DM([[0, 1],
|
||||
[0, 2],
|
||||
[1, 0]], QQ),
|
||||
DM([[1, 0],
|
||||
[0, 1],
|
||||
[0, 0]], QQ),
|
||||
QQ(1),
|
||||
),
|
||||
|
||||
(
|
||||
# Gaussian integers
|
||||
'zz_i_1',
|
||||
DM([[(0,1), 1, 1],
|
||||
[ 1, 1, 1]], ZZ_I),
|
||||
DM([[1, 0, 0],
|
||||
[0, 1, 1]], ZZ_I),
|
||||
ZZ_I(1),
|
||||
),
|
||||
|
||||
(
|
||||
# EX: test_issue_23718
|
||||
'EX_1',
|
||||
DM([
|
||||
[a, b, 1],
|
||||
[c, d, 1]], EX),
|
||||
DM([[a*d - b*c, 0, -b + d],
|
||||
[ 0, a*d - b*c, a - c]], EX),
|
||||
EX(a*d - b*c),
|
||||
),
|
||||
|
||||
]
|
||||
|
||||
|
||||
def _to_DM(A, ans):
|
||||
"""Convert the answer to DomainMatrix."""
|
||||
if isinstance(A, DomainMatrix):
|
||||
return A.to_dense()
|
||||
elif isinstance(A, Matrix):
|
||||
return A.to_DM(ans.domain).to_dense()
|
||||
|
||||
if not (hasattr(A, 'shape') and hasattr(A, 'domain')):
|
||||
shape, domain = ans.shape, ans.domain
|
||||
else:
|
||||
shape, domain = A.shape, A.domain
|
||||
|
||||
if isinstance(A, (DDM, list)):
|
||||
return DomainMatrix(list(A), shape, domain).to_dense()
|
||||
elif isinstance(A, (SDM, dict)):
|
||||
return DomainMatrix(dict(A), shape, domain).to_dense()
|
||||
else:
|
||||
assert False # pragma: no cover
|
||||
|
||||
|
||||
def _pivots(A_rref):
|
||||
"""Return the pivots from the rref of A."""
|
||||
return tuple(sorted(map(min, A_rref.to_sdm().values())))
|
||||
|
||||
|
||||
def _check_cancel(result, rref_ans, den_ans):
|
||||
"""Check the cancelled result."""
|
||||
rref, den, pivots = result
|
||||
if isinstance(rref, (DDM, SDM, list, dict)):
|
||||
assert type(pivots) is list
|
||||
pivots = tuple(pivots)
|
||||
rref = _to_DM(rref, rref_ans)
|
||||
rref2, den2 = rref.cancel_denom(den)
|
||||
assert rref2 == rref_ans
|
||||
assert den2 == den_ans
|
||||
assert pivots == _pivots(rref)
|
||||
|
||||
|
||||
def _check_divide(result, rref_ans, den_ans):
|
||||
"""Check the divided result."""
|
||||
rref, pivots = result
|
||||
if isinstance(rref, (DDM, SDM, list, dict)):
|
||||
assert type(pivots) is list
|
||||
pivots = tuple(pivots)
|
||||
rref_ans = rref_ans.to_field() / den_ans
|
||||
rref = _to_DM(rref, rref_ans)
|
||||
assert rref == rref_ans
|
||||
assert _pivots(rref) == pivots
|
||||
|
||||
|
||||
@pytest.mark.parametrize('name, A, A_rref, den', RREF_EXAMPLES)
|
||||
def test_Matrix_rref(name, A, A_rref, den):
|
||||
K = A.domain
|
||||
A = A.to_Matrix()
|
||||
A_rref_found, pivots = A.rref()
|
||||
if K.is_EX:
|
||||
A_rref_found = A_rref_found.expand()
|
||||
_check_divide((A_rref_found, pivots), A_rref, den)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('name, A, A_rref, den', RREF_EXAMPLES)
|
||||
def test_dm_dense_rref(name, A, A_rref, den):
|
||||
A = A.to_field()
|
||||
_check_divide(A.rref(), A_rref, den)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('name, A, A_rref, den', RREF_EXAMPLES)
|
||||
def test_dm_dense_rref_den(name, A, A_rref, den):
|
||||
_check_cancel(A.rref_den(), A_rref, den)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('name, A, A_rref, den', RREF_EXAMPLES)
|
||||
def test_dm_sparse_rref(name, A, A_rref, den):
|
||||
A = A.to_field().to_sparse()
|
||||
_check_divide(A.rref(), A_rref, den)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('name, A, A_rref, den', RREF_EXAMPLES)
|
||||
def test_dm_sparse_rref_den(name, A, A_rref, den):
|
||||
A = A.to_sparse()
|
||||
_check_cancel(A.rref_den(), A_rref, den)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('name, A, A_rref, den', RREF_EXAMPLES)
|
||||
def test_dm_sparse_rref_den_keep_domain(name, A, A_rref, den):
|
||||
A = A.to_sparse()
|
||||
A_rref_f, den_f, pivots_f = A.rref_den(keep_domain=False)
|
||||
A_rref_f = A_rref_f.to_field() / den_f
|
||||
_check_divide((A_rref_f, pivots_f), A_rref, den)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('name, A, A_rref, den', RREF_EXAMPLES)
|
||||
def test_dm_sparse_rref_den_keep_domain_CD(name, A, A_rref, den):
|
||||
A = A.to_sparse()
|
||||
A_rref_f, den_f, pivots_f = A.rref_den(keep_domain=False, method='CD')
|
||||
A_rref_f = A_rref_f.to_field() / den_f
|
||||
_check_divide((A_rref_f, pivots_f), A_rref, den)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('name, A, A_rref, den', RREF_EXAMPLES)
|
||||
def test_dm_sparse_rref_den_keep_domain_GJ(name, A, A_rref, den):
|
||||
A = A.to_sparse()
|
||||
A_rref_f, den_f, pivots_f = A.rref_den(keep_domain=False, method='GJ')
|
||||
A_rref_f = A_rref_f.to_field() / den_f
|
||||
_check_divide((A_rref_f, pivots_f), A_rref, den)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('name, A, A_rref, den', RREF_EXAMPLES)
|
||||
def test_ddm_rref_den(name, A, A_rref, den):
|
||||
A = A.to_ddm()
|
||||
_check_cancel(A.rref_den(), A_rref, den)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('name, A, A_rref, den', RREF_EXAMPLES)
|
||||
def test_sdm_rref_den(name, A, A_rref, den):
|
||||
A = A.to_sdm()
|
||||
_check_cancel(A.rref_den(), A_rref, den)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('name, A, A_rref, den', RREF_EXAMPLES)
|
||||
def test_ddm_rref(name, A, A_rref, den):
|
||||
A = A.to_field().to_ddm()
|
||||
_check_divide(A.rref(), A_rref, den)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('name, A, A_rref, den', RREF_EXAMPLES)
|
||||
def test_sdm_rref(name, A, A_rref, den):
|
||||
A = A.to_field().to_sdm()
|
||||
_check_divide(A.rref(), A_rref, den)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('name, A, A_rref, den', RREF_EXAMPLES)
|
||||
def test_ddm_irref(name, A, A_rref, den):
|
||||
A = A.to_field().to_ddm().copy()
|
||||
pivots_found = ddm_irref(A)
|
||||
_check_divide((A, pivots_found), A_rref, den)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('name, A, A_rref, den', RREF_EXAMPLES)
|
||||
def test_ddm_irref_den(name, A, A_rref, den):
|
||||
A = A.to_ddm().copy()
|
||||
(den_found, pivots_found) = ddm_irref_den(A, A.domain)
|
||||
result = (A, den_found, pivots_found)
|
||||
_check_cancel(result, A_rref, den)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('name, A, A_rref, den', RREF_EXAMPLES)
|
||||
def test_sparse_sdm_rref(name, A, A_rref, den):
|
||||
A = A.to_field().to_sdm()
|
||||
_check_divide(sdm_irref(A)[:2], A_rref, den)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('name, A, A_rref, den', RREF_EXAMPLES)
|
||||
def test_sparse_sdm_rref_den(name, A, A_rref, den):
|
||||
A = A.to_sdm().copy()
|
||||
K = A.domain
|
||||
_check_cancel(sdm_rref_den(A, K), A_rref, den)
|
||||
@@ -0,0 +1,428 @@
|
||||
"""
|
||||
Tests for the basic functionality of the SDM class.
|
||||
"""
|
||||
|
||||
from itertools import product
|
||||
|
||||
from sympy.core.singleton import S
|
||||
from sympy.external.gmpy import GROUND_TYPES
|
||||
from sympy.testing.pytest import raises
|
||||
|
||||
from sympy.polys.domains import QQ, ZZ, EXRAW
|
||||
from sympy.polys.matrices.sdm import SDM
|
||||
from sympy.polys.matrices.ddm import DDM
|
||||
from sympy.polys.matrices.exceptions import (DMBadInputError, DMDomainError,
|
||||
DMShapeError)
|
||||
|
||||
|
||||
def test_SDM():
|
||||
A = SDM({0:{0:ZZ(1)}}, (2, 2), ZZ)
|
||||
assert A.domain == ZZ
|
||||
assert A.shape == (2, 2)
|
||||
assert dict(A) == {0:{0:ZZ(1)}}
|
||||
|
||||
raises(DMBadInputError, lambda: SDM({5:{1:ZZ(0)}}, (2, 2), ZZ))
|
||||
raises(DMBadInputError, lambda: SDM({0:{5:ZZ(0)}}, (2, 2), ZZ))
|
||||
|
||||
|
||||
def test_DDM_str():
|
||||
sdm = SDM({0:{0:ZZ(1)}, 1:{1:ZZ(1)}}, (2, 2), ZZ)
|
||||
assert str(sdm) == '{0: {0: 1}, 1: {1: 1}}'
|
||||
if GROUND_TYPES == 'gmpy': # pragma: no cover
|
||||
assert repr(sdm) == 'SDM({0: {0: mpz(1)}, 1: {1: mpz(1)}}, (2, 2), ZZ)'
|
||||
else: # pragma: no cover
|
||||
assert repr(sdm) == 'SDM({0: {0: 1}, 1: {1: 1}}, (2, 2), ZZ)'
|
||||
|
||||
|
||||
def test_SDM_new():
|
||||
A = SDM({0:{0:ZZ(1)}}, (2, 2), ZZ)
|
||||
B = A.new({}, (2, 2), ZZ)
|
||||
assert B == SDM({}, (2, 2), ZZ)
|
||||
|
||||
|
||||
def test_SDM_copy():
|
||||
A = SDM({0:{0:ZZ(1)}}, (2, 2), ZZ)
|
||||
B = A.copy()
|
||||
assert A == B
|
||||
A[0][0] = ZZ(2)
|
||||
assert A != B
|
||||
|
||||
|
||||
def test_SDM_from_list():
|
||||
A = SDM.from_list([[ZZ(0), ZZ(1)], [ZZ(1), ZZ(0)]], (2, 2), ZZ)
|
||||
assert A == SDM({0:{1:ZZ(1)}, 1:{0:ZZ(1)}}, (2, 2), ZZ)
|
||||
|
||||
raises(DMBadInputError, lambda: SDM.from_list([[ZZ(0)], [ZZ(0), ZZ(1)]], (2, 2), ZZ))
|
||||
raises(DMBadInputError, lambda: SDM.from_list([[ZZ(0), ZZ(1)]], (2, 2), ZZ))
|
||||
|
||||
|
||||
def test_SDM_to_list():
|
||||
A = SDM({0:{1: ZZ(1)}}, (2, 2), ZZ)
|
||||
assert A.to_list() == [[ZZ(0), ZZ(1)], [ZZ(0), ZZ(0)]]
|
||||
|
||||
A = SDM({}, (0, 2), ZZ)
|
||||
assert A.to_list() == []
|
||||
|
||||
A = SDM({}, (2, 0), ZZ)
|
||||
assert A.to_list() == [[], []]
|
||||
|
||||
|
||||
def test_SDM_to_list_flat():
|
||||
A = SDM({0:{1: ZZ(1)}}, (2, 2), ZZ)
|
||||
assert A.to_list_flat() == [ZZ(0), ZZ(1), ZZ(0), ZZ(0)]
|
||||
|
||||
|
||||
def test_SDM_to_dok():
|
||||
A = SDM({0:{1: ZZ(1)}}, (2, 2), ZZ)
|
||||
assert A.to_dok() == {(0, 1): ZZ(1)}
|
||||
|
||||
|
||||
def test_SDM_from_ddm():
|
||||
A = DDM([[ZZ(1), ZZ(0)], [ZZ(1), ZZ(0)]], (2, 2), ZZ)
|
||||
B = SDM.from_ddm(A)
|
||||
assert B.domain == ZZ
|
||||
assert B.shape == (2, 2)
|
||||
assert dict(B) == {0:{0:ZZ(1)}, 1:{0:ZZ(1)}}
|
||||
|
||||
|
||||
def test_SDM_to_ddm():
|
||||
A = SDM({0:{1: ZZ(1)}}, (2, 2), ZZ)
|
||||
B = DDM([[ZZ(0), ZZ(1)], [ZZ(0), ZZ(0)]], (2, 2), ZZ)
|
||||
assert A.to_ddm() == B
|
||||
|
||||
|
||||
def test_SDM_to_sdm():
|
||||
A = SDM({0:{1: ZZ(1)}}, (2, 2), ZZ)
|
||||
assert A.to_sdm() == A
|
||||
|
||||
|
||||
def test_SDM_getitem():
|
||||
A = SDM({0:{1:ZZ(1)}}, (2, 2), ZZ)
|
||||
assert A.getitem(0, 0) == ZZ.zero
|
||||
assert A.getitem(0, 1) == ZZ.one
|
||||
assert A.getitem(1, 0) == ZZ.zero
|
||||
assert A.getitem(-2, -2) == ZZ.zero
|
||||
assert A.getitem(-2, -1) == ZZ.one
|
||||
assert A.getitem(-1, -2) == ZZ.zero
|
||||
raises(IndexError, lambda: A.getitem(2, 0))
|
||||
raises(IndexError, lambda: A.getitem(0, 2))
|
||||
|
||||
|
||||
def test_SDM_setitem():
|
||||
A = SDM({0:{1:ZZ(1)}}, (2, 2), ZZ)
|
||||
A.setitem(0, 0, ZZ(1))
|
||||
assert A == SDM({0:{0:ZZ(1), 1:ZZ(1)}}, (2, 2), ZZ)
|
||||
A.setitem(1, 0, ZZ(1))
|
||||
assert A == SDM({0:{0:ZZ(1), 1:ZZ(1)}, 1:{0:ZZ(1)}}, (2, 2), ZZ)
|
||||
A.setitem(1, 0, ZZ(0))
|
||||
assert A == SDM({0:{0:ZZ(1), 1:ZZ(1)}}, (2, 2), ZZ)
|
||||
# Repeat the above test so that this time the row is empty
|
||||
A.setitem(1, 0, ZZ(0))
|
||||
assert A == SDM({0:{0:ZZ(1), 1:ZZ(1)}}, (2, 2), ZZ)
|
||||
A.setitem(0, 0, ZZ(0))
|
||||
assert A == SDM({0:{1:ZZ(1)}}, (2, 2), ZZ)
|
||||
# This time the row is there but column is empty
|
||||
A.setitem(0, 0, ZZ(0))
|
||||
assert A == SDM({0:{1:ZZ(1)}}, (2, 2), ZZ)
|
||||
raises(IndexError, lambda: A.setitem(2, 0, ZZ(1)))
|
||||
raises(IndexError, lambda: A.setitem(0, 2, ZZ(1)))
|
||||
|
||||
|
||||
def test_SDM_extract_slice():
|
||||
A = SDM({0:{0:ZZ(1), 1:ZZ(2)}, 1:{0:ZZ(3), 1:ZZ(4)}}, (2, 2), ZZ)
|
||||
B = A.extract_slice(slice(1, 2), slice(1, 2))
|
||||
assert B == SDM({0:{0:ZZ(4)}}, (1, 1), ZZ)
|
||||
|
||||
|
||||
def test_SDM_extract():
|
||||
A = SDM({0:{0:ZZ(1), 1:ZZ(2)}, 1:{0:ZZ(3), 1:ZZ(4)}}, (2, 2), ZZ)
|
||||
B = A.extract([1], [1])
|
||||
assert B == SDM({0:{0:ZZ(4)}}, (1, 1), ZZ)
|
||||
B = A.extract([1, 0], [1, 0])
|
||||
assert B == SDM({0:{0:ZZ(4), 1:ZZ(3)}, 1:{0:ZZ(2), 1:ZZ(1)}}, (2, 2), ZZ)
|
||||
B = A.extract([1, 1], [1, 1])
|
||||
assert B == SDM({0:{0:ZZ(4), 1:ZZ(4)}, 1:{0:ZZ(4), 1:ZZ(4)}}, (2, 2), ZZ)
|
||||
B = A.extract([-1], [-1])
|
||||
assert B == SDM({0:{0:ZZ(4)}}, (1, 1), ZZ)
|
||||
|
||||
A = SDM({}, (2, 2), ZZ)
|
||||
B = A.extract([0, 1, 0], [0, 0])
|
||||
assert B == SDM({}, (3, 2), ZZ)
|
||||
|
||||
A = SDM({0:{0:ZZ(1), 1:ZZ(2)}, 1:{0:ZZ(3), 1:ZZ(4)}}, (2, 2), ZZ)
|
||||
assert A.extract([], []) == SDM.zeros((0, 0), ZZ)
|
||||
assert A.extract([1], []) == SDM.zeros((1, 0), ZZ)
|
||||
assert A.extract([], [1]) == SDM.zeros((0, 1), ZZ)
|
||||
|
||||
raises(IndexError, lambda: A.extract([2], [0]))
|
||||
raises(IndexError, lambda: A.extract([0], [2]))
|
||||
raises(IndexError, lambda: A.extract([-3], [0]))
|
||||
raises(IndexError, lambda: A.extract([0], [-3]))
|
||||
|
||||
|
||||
def test_SDM_zeros():
|
||||
A = SDM.zeros((2, 2), ZZ)
|
||||
assert A.domain == ZZ
|
||||
assert A.shape == (2, 2)
|
||||
assert dict(A) == {}
|
||||
|
||||
def test_SDM_ones():
|
||||
A = SDM.ones((1, 2), QQ)
|
||||
assert A.domain == QQ
|
||||
assert A.shape == (1, 2)
|
||||
assert dict(A) == {0:{0:QQ(1), 1:QQ(1)}}
|
||||
|
||||
def test_SDM_eye():
|
||||
A = SDM.eye((2, 2), ZZ)
|
||||
assert A.domain == ZZ
|
||||
assert A.shape == (2, 2)
|
||||
assert dict(A) == {0:{0:ZZ(1)}, 1:{1:ZZ(1)}}
|
||||
|
||||
|
||||
def test_SDM_diag():
|
||||
A = SDM.diag([ZZ(1), ZZ(2)], ZZ, (2, 3))
|
||||
assert A == SDM({0:{0:ZZ(1)}, 1:{1:ZZ(2)}}, (2, 3), ZZ)
|
||||
|
||||
|
||||
def test_SDM_transpose():
|
||||
A = SDM({0:{0:ZZ(1), 1:ZZ(2)}, 1:{0:ZZ(3), 1:ZZ(4)}}, (2, 2), ZZ)
|
||||
B = SDM({0:{0:ZZ(1), 1:ZZ(3)}, 1:{0:ZZ(2), 1:ZZ(4)}}, (2, 2), ZZ)
|
||||
assert A.transpose() == B
|
||||
|
||||
A = SDM({0:{1:ZZ(2)}}, (2, 2), ZZ)
|
||||
B = SDM({1:{0:ZZ(2)}}, (2, 2), ZZ)
|
||||
assert A.transpose() == B
|
||||
|
||||
A = SDM({0:{1:ZZ(2)}}, (1, 2), ZZ)
|
||||
B = SDM({1:{0:ZZ(2)}}, (2, 1), ZZ)
|
||||
assert A.transpose() == B
|
||||
|
||||
|
||||
def test_SDM_mul():
|
||||
A = SDM({0:{0:ZZ(2)}}, (2, 2), ZZ)
|
||||
B = SDM({0:{0:ZZ(4)}}, (2, 2), ZZ)
|
||||
assert A*ZZ(2) == B
|
||||
assert ZZ(2)*A == B
|
||||
|
||||
raises(TypeError, lambda: A*QQ(1, 2))
|
||||
raises(TypeError, lambda: QQ(1, 2)*A)
|
||||
|
||||
|
||||
def test_SDM_mul_elementwise():
|
||||
A = SDM({0:{0:ZZ(2), 1:ZZ(2)}}, (2, 2), ZZ)
|
||||
B = SDM({0:{0:ZZ(4)}, 1:{0:ZZ(3)}}, (2, 2), ZZ)
|
||||
C = SDM({0:{0:ZZ(8)}}, (2, 2), ZZ)
|
||||
assert A.mul_elementwise(B) == C
|
||||
assert B.mul_elementwise(A) == C
|
||||
|
||||
Aq = A.convert_to(QQ)
|
||||
A1 = SDM({0:{0:ZZ(1)}}, (1, 1), ZZ)
|
||||
|
||||
raises(DMDomainError, lambda: Aq.mul_elementwise(B))
|
||||
raises(DMShapeError, lambda: A1.mul_elementwise(B))
|
||||
|
||||
|
||||
def test_SDM_matmul():
|
||||
A = SDM({0:{0:ZZ(2)}}, (2, 2), ZZ)
|
||||
B = SDM({0:{0:ZZ(4)}}, (2, 2), ZZ)
|
||||
assert A.matmul(A) == A*A == B
|
||||
|
||||
C = SDM({0:{0:ZZ(2)}}, (2, 2), QQ)
|
||||
raises(DMDomainError, lambda: A.matmul(C))
|
||||
|
||||
A = SDM({0:{0:ZZ(1), 1:ZZ(2)}, 1:{0:ZZ(3), 1:ZZ(4)}}, (2, 2), ZZ)
|
||||
B = SDM({0:{0:ZZ(7), 1:ZZ(10)}, 1:{0:ZZ(15), 1:ZZ(22)}}, (2, 2), ZZ)
|
||||
assert A.matmul(A) == A*A == B
|
||||
|
||||
A22 = SDM({0:{0:ZZ(4)}}, (2, 2), ZZ)
|
||||
A32 = SDM({0:{0:ZZ(2)}}, (3, 2), ZZ)
|
||||
A23 = SDM({0:{0:ZZ(4)}}, (2, 3), ZZ)
|
||||
A33 = SDM({0:{0:ZZ(8)}}, (3, 3), ZZ)
|
||||
A22 = SDM({0:{0:ZZ(8)}}, (2, 2), ZZ)
|
||||
assert A32.matmul(A23) == A33
|
||||
assert A23.matmul(A32) == A22
|
||||
# XXX: @ not supported by SDM...
|
||||
#assert A32.matmul(A23) == A32 @ A23 == A33
|
||||
#assert A23.matmul(A32) == A23 @ A32 == A22
|
||||
#raises(DMShapeError, lambda: A23 @ A22)
|
||||
raises(DMShapeError, lambda: A23.matmul(A22))
|
||||
|
||||
A = SDM({0: {0: ZZ(-1), 1: ZZ(1)}}, (1, 2), ZZ)
|
||||
B = SDM({0: {0: ZZ(-1)}, 1: {0: ZZ(-1)}}, (2, 1), ZZ)
|
||||
assert A.matmul(B) == A*B == SDM({}, (1, 1), ZZ)
|
||||
|
||||
|
||||
def test_matmul_exraw():
|
||||
|
||||
def dm(d):
|
||||
result = {}
|
||||
for i, row in d.items():
|
||||
row = {j:val for j, val in row.items() if val}
|
||||
if row:
|
||||
result[i] = row
|
||||
return SDM(result, (2, 2), EXRAW)
|
||||
|
||||
values = [S.NegativeInfinity, S.NegativeOne, S.Zero, S.One, S.Infinity]
|
||||
for a, b, c, d in product(*[values]*4):
|
||||
Ad = dm({0: {0:a, 1:b}, 1: {0:c, 1:d}})
|
||||
Ad2 = dm({0: {0:a*a + b*c, 1:a*b + b*d}, 1:{0:c*a + d*c, 1: c*b + d*d}})
|
||||
assert Ad * Ad == Ad2
|
||||
|
||||
|
||||
def test_SDM_add():
|
||||
A = SDM({0:{1:ZZ(1)}, 1:{0:ZZ(2), 1:ZZ(3)}}, (2, 2), ZZ)
|
||||
B = SDM({0:{0:ZZ(1)}, 1:{0:ZZ(-2), 1:ZZ(3)}}, (2, 2), ZZ)
|
||||
C = SDM({0:{0:ZZ(1), 1:ZZ(1)}, 1:{1:ZZ(6)}}, (2, 2), ZZ)
|
||||
assert A.add(B) == B.add(A) == A + B == B + A == C
|
||||
|
||||
A = SDM({0:{1:ZZ(1)}}, (2, 2), ZZ)
|
||||
B = SDM({0:{0:ZZ(1)}, 1:{0:ZZ(-2), 1:ZZ(3)}}, (2, 2), ZZ)
|
||||
C = SDM({0:{0:ZZ(1), 1:ZZ(1)}, 1:{0:ZZ(-2), 1:ZZ(3)}}, (2, 2), ZZ)
|
||||
assert A.add(B) == B.add(A) == A + B == B + A == C
|
||||
|
||||
raises(TypeError, lambda: A + [])
|
||||
|
||||
|
||||
def test_SDM_sub():
|
||||
A = SDM({0:{1:ZZ(1)}, 1:{0:ZZ(2), 1:ZZ(3)}}, (2, 2), ZZ)
|
||||
B = SDM({0:{0:ZZ(1)}, 1:{0:ZZ(-2), 1:ZZ(3)}}, (2, 2), ZZ)
|
||||
C = SDM({0:{0:ZZ(-1), 1:ZZ(1)}, 1:{0:ZZ(4)}}, (2, 2), ZZ)
|
||||
assert A.sub(B) == A - B == C
|
||||
|
||||
raises(TypeError, lambda: A - [])
|
||||
|
||||
|
||||
def test_SDM_neg():
|
||||
A = SDM({0:{1:ZZ(1)}, 1:{0:ZZ(2), 1:ZZ(3)}}, (2, 2), ZZ)
|
||||
B = SDM({0:{1:ZZ(-1)}, 1:{0:ZZ(-2), 1:ZZ(-3)}}, (2, 2), ZZ)
|
||||
assert A.neg() == -A == B
|
||||
|
||||
|
||||
def test_SDM_convert_to():
|
||||
A = SDM({0:{1:ZZ(1)}, 1:{0:ZZ(2), 1:ZZ(3)}}, (2, 2), ZZ)
|
||||
B = SDM({0:{1:QQ(1)}, 1:{0:QQ(2), 1:QQ(3)}}, (2, 2), QQ)
|
||||
C = A.convert_to(QQ)
|
||||
assert C == B
|
||||
assert C.domain == QQ
|
||||
|
||||
D = A.convert_to(ZZ)
|
||||
assert D == A
|
||||
assert D.domain == ZZ
|
||||
|
||||
|
||||
def test_SDM_hstack():
|
||||
A = SDM({0:{1:ZZ(1)}}, (2, 2), ZZ)
|
||||
B = SDM({1:{1:ZZ(1)}}, (2, 2), ZZ)
|
||||
AA = SDM({0:{1:ZZ(1), 3:ZZ(1)}}, (2, 4), ZZ)
|
||||
AB = SDM({0:{1:ZZ(1)}, 1:{3:ZZ(1)}}, (2, 4), ZZ)
|
||||
assert SDM.hstack(A) == A
|
||||
assert SDM.hstack(A, A) == AA
|
||||
assert SDM.hstack(A, B) == AB
|
||||
|
||||
|
||||
def test_SDM_vstack():
|
||||
A = SDM({0:{1:ZZ(1)}}, (2, 2), ZZ)
|
||||
B = SDM({1:{1:ZZ(1)}}, (2, 2), ZZ)
|
||||
AA = SDM({0:{1:ZZ(1)}, 2:{1:ZZ(1)}}, (4, 2), ZZ)
|
||||
AB = SDM({0:{1:ZZ(1)}, 3:{1:ZZ(1)}}, (4, 2), ZZ)
|
||||
assert SDM.vstack(A) == A
|
||||
assert SDM.vstack(A, A) == AA
|
||||
assert SDM.vstack(A, B) == AB
|
||||
|
||||
|
||||
def test_SDM_applyfunc():
|
||||
A = SDM({0:{1:ZZ(1)}}, (2, 2), ZZ)
|
||||
B = SDM({0:{1:ZZ(2)}}, (2, 2), ZZ)
|
||||
assert A.applyfunc(lambda x: 2*x, ZZ) == B
|
||||
|
||||
|
||||
def test_SDM_inv():
|
||||
A = SDM({0:{0:QQ(1), 1:QQ(2)}, 1:{0:QQ(3), 1:QQ(4)}}, (2, 2), QQ)
|
||||
B = SDM({0:{0:QQ(-2), 1:QQ(1)}, 1:{0:QQ(3, 2), 1:QQ(-1, 2)}}, (2, 2), QQ)
|
||||
assert A.inv() == B
|
||||
|
||||
|
||||
def test_SDM_det():
|
||||
A = SDM({0:{0:QQ(1), 1:QQ(2)}, 1:{0:QQ(3), 1:QQ(4)}}, (2, 2), QQ)
|
||||
assert A.det() == QQ(-2)
|
||||
|
||||
|
||||
def test_SDM_lu():
|
||||
A = SDM({0:{0:QQ(1), 1:QQ(2)}, 1:{0:QQ(3), 1:QQ(4)}}, (2, 2), QQ)
|
||||
L = SDM({0:{0:QQ(1)}, 1:{0:QQ(3), 1:QQ(1)}}, (2, 2), QQ)
|
||||
#U = SDM({0:{0:QQ(1), 1:QQ(2)}, 1:{0:QQ(3), 1:QQ(-2)}}, (2, 2), QQ)
|
||||
#swaps = []
|
||||
# This doesn't quite work. U has some nonzero elements in the lower part.
|
||||
#assert A.lu() == (L, U, swaps)
|
||||
assert A.lu()[0] == L
|
||||
|
||||
|
||||
def test_SDM_lu_solve():
|
||||
A = SDM({0:{0:QQ(1), 1:QQ(2)}, 1:{0:QQ(3), 1:QQ(4)}}, (2, 2), QQ)
|
||||
b = SDM({0:{0:QQ(1)}, 1:{0:QQ(2)}}, (2, 1), QQ)
|
||||
x = SDM({1:{0:QQ(1, 2)}}, (2, 1), QQ)
|
||||
assert A.matmul(x) == b
|
||||
assert A.lu_solve(b) == x
|
||||
|
||||
|
||||
def test_SDM_charpoly():
|
||||
A = SDM({0:{0:ZZ(1), 1:ZZ(2)}, 1:{0:ZZ(3), 1:ZZ(4)}}, (2, 2), ZZ)
|
||||
assert A.charpoly() == [ZZ(1), ZZ(-5), ZZ(-2)]
|
||||
|
||||
|
||||
def test_SDM_nullspace():
|
||||
# More tests are in test_nullspace.py
|
||||
A = SDM({0:{0:QQ(1), 1:QQ(1)}}, (2, 2), QQ)
|
||||
assert A.nullspace()[0] == SDM({0:{0:QQ(-1), 1:QQ(1)}}, (1, 2), QQ)
|
||||
|
||||
|
||||
def test_SDM_rref():
|
||||
# More tests are in test_rref.py
|
||||
|
||||
A = SDM({0:{0:QQ(1), 1:QQ(2)},
|
||||
1:{0:QQ(3), 1:QQ(4)}}, (2, 2), QQ)
|
||||
A_rref = SDM({0:{0:QQ(1)}, 1:{1:QQ(1)}}, (2, 2), QQ)
|
||||
assert A.rref() == (A_rref, [0, 1])
|
||||
|
||||
A = SDM({0: {0: QQ(1), 1: QQ(2), 2: QQ(2)},
|
||||
1: {0: QQ(3), 2: QQ(4)}}, (2, 3), ZZ)
|
||||
A_rref = SDM({0: {0: QQ(1,1), 2: QQ(4,3)},
|
||||
1: {1: QQ(1,1), 2: QQ(1,3)}}, (2, 3), QQ)
|
||||
assert A.rref() == (A_rref, [0, 1])
|
||||
|
||||
|
||||
def test_SDM_particular():
|
||||
A = SDM({0:{0:QQ(1)}}, (2, 2), QQ)
|
||||
Apart = SDM.zeros((1, 2), QQ)
|
||||
assert A.particular() == Apart
|
||||
|
||||
|
||||
def test_SDM_is_zero_matrix():
|
||||
A = SDM({0: {0: QQ(1)}}, (2, 2), QQ)
|
||||
Azero = SDM.zeros((1, 2), QQ)
|
||||
assert A.is_zero_matrix() is False
|
||||
assert Azero.is_zero_matrix() is True
|
||||
|
||||
|
||||
def test_SDM_is_upper():
|
||||
A = SDM({0: {0: QQ(1), 1: QQ(2), 2: QQ(3), 3: QQ(4)},
|
||||
1: {1: QQ(5), 2: QQ(6), 3: QQ(7)},
|
||||
2: {2: QQ(8), 3: QQ(9)}}, (3, 4), QQ)
|
||||
B = SDM({0: {0: QQ(1), 1: QQ(2), 2: QQ(3), 3: QQ(4)},
|
||||
1: {1: QQ(5), 2: QQ(6), 3: QQ(7)},
|
||||
2: {1: QQ(7), 2: QQ(8), 3: QQ(9)}}, (3, 4), QQ)
|
||||
assert A.is_upper() is True
|
||||
assert B.is_upper() is False
|
||||
|
||||
|
||||
def test_SDM_is_lower():
|
||||
A = SDM({0: {0: QQ(1), 1: QQ(2), 2: QQ(3), 3: QQ(4)},
|
||||
1: {1: QQ(5), 2: QQ(6), 3: QQ(7)},
|
||||
2: {2: QQ(8), 3: QQ(9)}}, (3, 4), QQ
|
||||
).transpose()
|
||||
B = SDM({0: {0: QQ(1), 1: QQ(2), 2: QQ(3), 3: QQ(4)},
|
||||
1: {1: QQ(5), 2: QQ(6), 3: QQ(7)},
|
||||
2: {1: QQ(7), 2: QQ(8), 3: QQ(9)}}, (3, 4), QQ
|
||||
).transpose()
|
||||
assert A.is_lower() is True
|
||||
assert B.is_lower() is False
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user