switching to high quality piper tts and added label translations
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',
|
||||
]
|
||||
@@ -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: ...
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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)
|
||||
@@ -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
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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))
|
||||
+1383
File diff suppressed because it is too large
Load Diff
+153
@@ -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())
|
||||
+156
@@ -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