497 lines
13 KiB
Python
497 lines
13 KiB
Python
"""
|
|
|
|
Module for the DDM class.
|
|
|
|
The DDM class is an internal representation used by DomainMatrix. The letters
|
|
DDM stand for Dense Domain Matrix. A DDM instance represents a matrix using
|
|
elements from a polynomial Domain (e.g. ZZ, QQ, ...) in a dense-matrix
|
|
representation.
|
|
|
|
Basic usage:
|
|
|
|
>>> from sympy import ZZ, QQ
|
|
>>> from sympy.polys.matrices.ddm import DDM
|
|
>>> A = DDM([[ZZ(0), ZZ(1)], [ZZ(-1), ZZ(0)]], (2, 2), ZZ)
|
|
>>> A.shape
|
|
(2, 2)
|
|
>>> A
|
|
[[0, 1], [-1, 0]]
|
|
>>> type(A)
|
|
<class 'sympy.polys.matrices.ddm.DDM'>
|
|
>>> A @ A
|
|
[[-1, 0], [0, -1]]
|
|
|
|
The ddm_* functions are designed to operate on DDM as well as on an ordinary
|
|
list of lists:
|
|
|
|
>>> from sympy.polys.matrices.dense import ddm_idet
|
|
>>> ddm_idet(A, QQ)
|
|
1
|
|
>>> ddm_idet([[0, 1], [-1, 0]], QQ)
|
|
1
|
|
>>> A
|
|
[[-1, 0], [0, -1]]
|
|
|
|
Note that ddm_idet modifies the input matrix in-place. It is recommended to
|
|
use the DDM.det method as a friendlier interface to this instead which takes
|
|
care of copying the matrix:
|
|
|
|
>>> B = DDM([[ZZ(0), ZZ(1)], [ZZ(-1), ZZ(0)]], (2, 2), ZZ)
|
|
>>> B.det()
|
|
1
|
|
|
|
Normally DDM would not be used directly and is just part of the internal
|
|
representation of DomainMatrix which adds further functionality including e.g.
|
|
unifying domains.
|
|
|
|
The dense format used by DDM is a list of lists of elements e.g. the 2x2
|
|
identity matrix is like [[1, 0], [0, 1]]. The DDM class itself is a subclass
|
|
of list and its list items are plain lists. Elements are accessed as e.g.
|
|
ddm[i][j] where ddm[i] gives the ith row and ddm[i][j] gets the element in the
|
|
jth column of that row. Subclassing list makes e.g. iteration and indexing
|
|
very efficient. We do not override __getitem__ because it would lose that
|
|
benefit.
|
|
|
|
The core routines are implemented by the ddm_* functions defined in dense.py.
|
|
Those functions are intended to be able to operate on a raw list-of-lists
|
|
representation of matrices with most functions operating in-place. The DDM
|
|
class takes care of copying etc and also stores a Domain object associated
|
|
with its elements. This makes it possible to implement things like A + B with
|
|
domain checking and also shape checking so that the list of lists
|
|
representation is friendlier.
|
|
|
|
"""
|
|
from itertools import chain
|
|
|
|
from .exceptions import DMBadInputError, DMShapeError, DMDomainError
|
|
|
|
from .dense import (
|
|
ddm_transpose,
|
|
ddm_iadd,
|
|
ddm_isub,
|
|
ddm_ineg,
|
|
ddm_imul,
|
|
ddm_irmul,
|
|
ddm_imatmul,
|
|
ddm_irref,
|
|
ddm_idet,
|
|
ddm_iinv,
|
|
ddm_ilu_split,
|
|
ddm_ilu_solve,
|
|
ddm_berk,
|
|
)
|
|
|
|
from sympy.polys.domains import QQ
|
|
from .lll import ddm_lll, ddm_lll_transform
|
|
|
|
|
|
class DDM(list):
|
|
"""Dense matrix based on polys domain elements
|
|
|
|
This is a list subclass and is a wrapper for a list of lists that supports
|
|
basic matrix arithmetic +, -, *, **.
|
|
"""
|
|
|
|
fmt = 'dense'
|
|
|
|
def __init__(self, rowslist, shape, domain):
|
|
super().__init__(rowslist)
|
|
self.shape = self.rows, self.cols = m, n = shape
|
|
self.domain = domain
|
|
|
|
if not (len(self) == m and all(len(row) == n for row in self)):
|
|
raise DMBadInputError("Inconsistent row-list/shape")
|
|
|
|
def getitem(self, i, j):
|
|
return self[i][j]
|
|
|
|
def setitem(self, i, j, value):
|
|
self[i][j] = value
|
|
|
|
def extract_slice(self, slice1, slice2):
|
|
ddm = [row[slice2] for row in self[slice1]]
|
|
rows = len(ddm)
|
|
cols = len(ddm[0]) if ddm else len(range(self.shape[1])[slice2])
|
|
return DDM(ddm, (rows, cols), self.domain)
|
|
|
|
def extract(self, rows, cols):
|
|
ddm = []
|
|
for i in rows:
|
|
rowi = self[i]
|
|
ddm.append([rowi[j] for j in cols])
|
|
return DDM(ddm, (len(rows), len(cols)), self.domain)
|
|
|
|
def to_list(self):
|
|
return list(self)
|
|
|
|
def to_list_flat(self):
|
|
flat = []
|
|
for row in self:
|
|
flat.extend(row)
|
|
return flat
|
|
|
|
def flatiter(self):
|
|
return chain.from_iterable(self)
|
|
|
|
def flat(self):
|
|
items = []
|
|
for row in self:
|
|
items.extend(row)
|
|
return items
|
|
|
|
def to_dok(self):
|
|
return {(i, j): e for i, row in enumerate(self) for j, e in enumerate(row)}
|
|
|
|
def to_ddm(self):
|
|
return self
|
|
|
|
def to_sdm(self):
|
|
return SDM.from_list(self, self.shape, self.domain)
|
|
|
|
def convert_to(self, K):
|
|
Kold = self.domain
|
|
if K == Kold:
|
|
return self.copy()
|
|
rows = ([K.convert_from(e, Kold) for e in row] for row in self)
|
|
return DDM(rows, self.shape, K)
|
|
|
|
def __str__(self):
|
|
rowsstr = ['[%s]' % ', '.join(map(str, row)) for row in self]
|
|
return '[%s]' % ', '.join(rowsstr)
|
|
|
|
def __repr__(self):
|
|
cls = type(self).__name__
|
|
rows = list.__repr__(self)
|
|
return '%s(%s, %s, %s)' % (cls, rows, self.shape, self.domain)
|
|
|
|
def __eq__(self, other):
|
|
if not isinstance(other, DDM):
|
|
return False
|
|
return (super().__eq__(other) and self.domain == other.domain)
|
|
|
|
def __ne__(self, other):
|
|
return not self.__eq__(other)
|
|
|
|
@classmethod
|
|
def zeros(cls, shape, domain):
|
|
z = domain.zero
|
|
m, n = shape
|
|
rowslist = ([z] * n for _ in range(m))
|
|
return DDM(rowslist, shape, domain)
|
|
|
|
@classmethod
|
|
def ones(cls, shape, domain):
|
|
one = domain.one
|
|
m, n = shape
|
|
rowlist = ([one] * n for _ in range(m))
|
|
return DDM(rowlist, shape, domain)
|
|
|
|
@classmethod
|
|
def eye(cls, size, domain):
|
|
one = domain.one
|
|
ddm = cls.zeros((size, size), domain)
|
|
for i in range(size):
|
|
ddm[i][i] = one
|
|
return ddm
|
|
|
|
def copy(self):
|
|
copyrows = (row[:] for row in self)
|
|
return DDM(copyrows, self.shape, self.domain)
|
|
|
|
def transpose(self):
|
|
rows, cols = self.shape
|
|
if rows:
|
|
ddmT = ddm_transpose(self)
|
|
else:
|
|
ddmT = [[]] * cols
|
|
return DDM(ddmT, (cols, rows), self.domain)
|
|
|
|
def __add__(a, b):
|
|
if not isinstance(b, DDM):
|
|
return NotImplemented
|
|
return a.add(b)
|
|
|
|
def __sub__(a, b):
|
|
if not isinstance(b, DDM):
|
|
return NotImplemented
|
|
return a.sub(b)
|
|
|
|
def __neg__(a):
|
|
return a.neg()
|
|
|
|
def __mul__(a, b):
|
|
if b in a.domain:
|
|
return a.mul(b)
|
|
else:
|
|
return NotImplemented
|
|
|
|
def __rmul__(a, b):
|
|
if b in a.domain:
|
|
return a.mul(b)
|
|
else:
|
|
return NotImplemented
|
|
|
|
def __matmul__(a, b):
|
|
if isinstance(b, DDM):
|
|
return a.matmul(b)
|
|
else:
|
|
return NotImplemented
|
|
|
|
@classmethod
|
|
def _check(cls, a, op, b, ashape, bshape):
|
|
if a.domain != b.domain:
|
|
msg = "Domain mismatch: %s %s %s" % (a.domain, op, b.domain)
|
|
raise DMDomainError(msg)
|
|
if ashape != bshape:
|
|
msg = "Shape mismatch: %s %s %s" % (a.shape, op, b.shape)
|
|
raise DMShapeError(msg)
|
|
|
|
def add(a, b):
|
|
"""a + b"""
|
|
a._check(a, '+', b, a.shape, b.shape)
|
|
c = a.copy()
|
|
ddm_iadd(c, b)
|
|
return c
|
|
|
|
def sub(a, b):
|
|
"""a - b"""
|
|
a._check(a, '-', b, a.shape, b.shape)
|
|
c = a.copy()
|
|
ddm_isub(c, b)
|
|
return c
|
|
|
|
def neg(a):
|
|
"""-a"""
|
|
b = a.copy()
|
|
ddm_ineg(b)
|
|
return b
|
|
|
|
def mul(a, b):
|
|
c = a.copy()
|
|
ddm_imul(c, b)
|
|
return c
|
|
|
|
def rmul(a, b):
|
|
c = a.copy()
|
|
ddm_irmul(c, b)
|
|
return c
|
|
|
|
def matmul(a, b):
|
|
"""a @ b (matrix product)"""
|
|
m, o = a.shape
|
|
o2, n = b.shape
|
|
a._check(a, '*', b, o, o2)
|
|
c = a.zeros((m, n), a.domain)
|
|
ddm_imatmul(c, a, b)
|
|
return c
|
|
|
|
def mul_elementwise(a, b):
|
|
assert a.shape == b.shape
|
|
assert a.domain == b.domain
|
|
c = [[aij * bij for aij, bij in zip(ai, bi)] for ai, bi in zip(a, b)]
|
|
return DDM(c, a.shape, a.domain)
|
|
|
|
def hstack(A, *B):
|
|
"""Horizontally stacks :py:class:`~.DDM` matrices.
|
|
|
|
Examples
|
|
========
|
|
|
|
>>> from sympy import ZZ
|
|
>>> from sympy.polys.matrices.sdm import DDM
|
|
|
|
>>> A = DDM([[ZZ(1), ZZ(2)], [ZZ(3), ZZ(4)]], (2, 2), ZZ)
|
|
>>> B = DDM([[ZZ(5), ZZ(6)], [ZZ(7), ZZ(8)]], (2, 2), ZZ)
|
|
>>> A.hstack(B)
|
|
[[1, 2, 5, 6], [3, 4, 7, 8]]
|
|
|
|
>>> C = DDM([[ZZ(9), ZZ(10)], [ZZ(11), ZZ(12)]], (2, 2), ZZ)
|
|
>>> A.hstack(B, C)
|
|
[[1, 2, 5, 6, 9, 10], [3, 4, 7, 8, 11, 12]]
|
|
"""
|
|
Anew = list(A.copy())
|
|
rows, cols = A.shape
|
|
domain = A.domain
|
|
|
|
for Bk in B:
|
|
Bkrows, Bkcols = Bk.shape
|
|
assert Bkrows == rows
|
|
assert Bk.domain == domain
|
|
|
|
cols += Bkcols
|
|
|
|
for i, Bki in enumerate(Bk):
|
|
Anew[i].extend(Bki)
|
|
|
|
return DDM(Anew, (rows, cols), A.domain)
|
|
|
|
def vstack(A, *B):
|
|
"""Vertically stacks :py:class:`~.DDM` matrices.
|
|
|
|
Examples
|
|
========
|
|
|
|
>>> from sympy import ZZ
|
|
>>> from sympy.polys.matrices.sdm import DDM
|
|
|
|
>>> A = DDM([[ZZ(1), ZZ(2)], [ZZ(3), ZZ(4)]], (2, 2), ZZ)
|
|
>>> B = DDM([[ZZ(5), ZZ(6)], [ZZ(7), ZZ(8)]], (2, 2), ZZ)
|
|
>>> A.vstack(B)
|
|
[[1, 2], [3, 4], [5, 6], [7, 8]]
|
|
|
|
>>> C = DDM([[ZZ(9), ZZ(10)], [ZZ(11), ZZ(12)]], (2, 2), ZZ)
|
|
>>> A.vstack(B, C)
|
|
[[1, 2], [3, 4], [5, 6], [7, 8], [9, 10], [11, 12]]
|
|
"""
|
|
Anew = list(A.copy())
|
|
rows, cols = A.shape
|
|
domain = A.domain
|
|
|
|
for Bk in B:
|
|
Bkrows, Bkcols = Bk.shape
|
|
assert Bkcols == cols
|
|
assert Bk.domain == domain
|
|
|
|
rows += Bkrows
|
|
|
|
Anew.extend(Bk.copy())
|
|
|
|
return DDM(Anew, (rows, cols), A.domain)
|
|
|
|
def applyfunc(self, func, domain):
|
|
elements = (list(map(func, row)) for row in self)
|
|
return DDM(elements, self.shape, domain)
|
|
|
|
def scc(a):
|
|
"""Strongly connected components of a square matrix *a*.
|
|
|
|
Examples
|
|
========
|
|
|
|
>>> from sympy import ZZ
|
|
>>> from sympy.polys.matrices.sdm import DDM
|
|
>>> A = DDM([[ZZ(1), ZZ(0)], [ZZ(0), ZZ(1)]], (2, 2), ZZ)
|
|
>>> A.scc()
|
|
[[0], [1]]
|
|
|
|
See also
|
|
========
|
|
|
|
sympy.polys.matrices.domainmatrix.DomainMatrix.scc
|
|
|
|
"""
|
|
return a.to_sdm().scc()
|
|
|
|
def rref(a):
|
|
"""Reduced-row echelon form of a and list of pivots"""
|
|
b = a.copy()
|
|
K = a.domain
|
|
partial_pivot = K.is_RealField or K.is_ComplexField
|
|
pivots = ddm_irref(b, _partial_pivot=partial_pivot)
|
|
return b, pivots
|
|
|
|
def nullspace(a):
|
|
rref, pivots = a.rref()
|
|
rows, cols = a.shape
|
|
domain = a.domain
|
|
|
|
basis = []
|
|
nonpivots = []
|
|
for i in range(cols):
|
|
if i in pivots:
|
|
continue
|
|
nonpivots.append(i)
|
|
vec = [domain.one if i == j else domain.zero for j in range(cols)]
|
|
for ii, jj in enumerate(pivots):
|
|
vec[jj] -= rref[ii][i]
|
|
basis.append(vec)
|
|
|
|
return DDM(basis, (len(basis), cols), domain), nonpivots
|
|
|
|
def particular(a):
|
|
return a.to_sdm().particular().to_ddm()
|
|
|
|
def det(a):
|
|
"""Determinant of a"""
|
|
m, n = a.shape
|
|
if m != n:
|
|
raise DMShapeError("Determinant of non-square matrix")
|
|
b = a.copy()
|
|
K = b.domain
|
|
deta = ddm_idet(b, K)
|
|
return deta
|
|
|
|
def inv(a):
|
|
"""Inverse of a"""
|
|
m, n = a.shape
|
|
if m != n:
|
|
raise DMShapeError("Determinant of non-square matrix")
|
|
ainv = a.copy()
|
|
K = a.domain
|
|
ddm_iinv(ainv, a, K)
|
|
return ainv
|
|
|
|
def lu(a):
|
|
"""L, U decomposition of a"""
|
|
m, n = a.shape
|
|
K = a.domain
|
|
|
|
U = a.copy()
|
|
L = a.eye(m, K)
|
|
swaps = ddm_ilu_split(L, U, K)
|
|
|
|
return L, U, swaps
|
|
|
|
def lu_solve(a, b):
|
|
"""x where a*x = b"""
|
|
m, n = a.shape
|
|
m2, o = b.shape
|
|
a._check(a, 'lu_solve', b, m, m2)
|
|
|
|
L, U, swaps = a.lu()
|
|
x = a.zeros((n, o), a.domain)
|
|
ddm_ilu_solve(x, L, U, swaps, b)
|
|
return x
|
|
|
|
def charpoly(a):
|
|
"""Coefficients of characteristic polynomial of a"""
|
|
K = a.domain
|
|
m, n = a.shape
|
|
if m != n:
|
|
raise DMShapeError("Charpoly of non-square matrix")
|
|
vec = ddm_berk(a, K)
|
|
coeffs = [vec[i][0] for i in range(n+1)]
|
|
return coeffs
|
|
|
|
def is_zero_matrix(self):
|
|
"""
|
|
Says whether this matrix has all zero entries.
|
|
"""
|
|
zero = self.domain.zero
|
|
return all(Mij == zero for Mij in self.flatiter())
|
|
|
|
def is_upper(self):
|
|
"""
|
|
Says whether this matrix is upper-triangular. True can be returned
|
|
even if the matrix is not square.
|
|
"""
|
|
zero = self.domain.zero
|
|
return all(Mij == zero for i, Mi in enumerate(self) for Mij in Mi[:i])
|
|
|
|
def is_lower(self):
|
|
"""
|
|
Says whether this matrix is lower-triangular. True can be returned
|
|
even if the matrix is not square.
|
|
"""
|
|
zero = self.domain.zero
|
|
return all(Mij == zero for i, Mi in enumerate(self) for Mij in Mi[i+1:])
|
|
|
|
def lll(A, delta=QQ(3, 4)):
|
|
return ddm_lll(A, delta=delta)
|
|
|
|
def lll_transform(A, delta=QQ(3, 4)):
|
|
return ddm_lll_transform(A, delta=delta)
|
|
|
|
|
|
from .sdm import SDM
|