Kano001's picture
Upload 3077 files
6a86ad5 verified
raw
history blame
31.1 kB
#
# 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 not in (ZZ, QQ):
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)
@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
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):
"""Iterater 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 domain == ZZ and self.domain == QQ:
# XXX: python-flint has no fmpz_mat.from_fmpq_mat
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(i):
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:
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
# 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 iwth 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 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