123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487 |
- """
- 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,
- )
- 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:])
- from .sdm import SDM
|