Coverage for /usr/lib/python3/dist-packages/sympy/polys/matrices/domainmatrix.py: 30%
396 statements
« prev ^ index » next coverage.py v7.9.1, created at 2025-06-14 15:55 +0200
« prev ^ index » next coverage.py v7.9.1, created at 2025-06-14 15:55 +0200
1"""
3Module for the DomainMatrix class.
5A DomainMatrix represents a matrix with elements that are in a particular
6Domain. Each DomainMatrix internally wraps a DDM which is used for the
7lower-level operations. The idea is that the DomainMatrix class provides the
8convenience routines for converting between Expr and the poly domains as well
9as unifying matrices with different domains.
11"""
12from functools import reduce
13from typing import Union as tUnion, Tuple as tTuple
15from sympy.core.sympify import _sympify
17from ..domains import Domain
19from ..constructor import construct_domain
21from .exceptions import (DMNonSquareMatrixError, DMShapeError,
22 DMDomainError, DMFormatError, DMBadInputError,
23 DMNotAField)
25from .ddm import DDM
27from .sdm import SDM
29from .domainscalar import DomainScalar
31from sympy.polys.domains import ZZ, EXRAW, QQ
34def DM(rows, domain):
35 """Convenient alias for DomainMatrix.from_list
37 Examples
38 =======
40 >>> from sympy import ZZ
41 >>> from sympy.polys.matrices import DM
42 >>> DM([[1, 2], [3, 4]], ZZ)
43 DomainMatrix([[1, 2], [3, 4]], (2, 2), ZZ)
45 See also
46 =======
48 DomainMatrix.from_list
49 """
50 return DomainMatrix.from_list(rows, domain)
53class DomainMatrix:
54 r"""
55 Associate Matrix with :py:class:`~.Domain`
57 Explanation
58 ===========
60 DomainMatrix uses :py:class:`~.Domain` for its internal representation
61 which makes it faster than the SymPy Matrix class (currently) for many
62 common operations, but this advantage makes it not entirely compatible
63 with Matrix. DomainMatrix are analogous to numpy arrays with "dtype".
64 In the DomainMatrix, each element has a domain such as :ref:`ZZ`
65 or :ref:`QQ(a)`.
68 Examples
69 ========
71 Creating a DomainMatrix from the existing Matrix class:
73 >>> from sympy import Matrix
74 >>> from sympy.polys.matrices import DomainMatrix
75 >>> Matrix1 = Matrix([
76 ... [1, 2],
77 ... [3, 4]])
78 >>> A = DomainMatrix.from_Matrix(Matrix1)
79 >>> A
80 DomainMatrix({0: {0: 1, 1: 2}, 1: {0: 3, 1: 4}}, (2, 2), ZZ)
82 Directly forming a DomainMatrix:
84 >>> from sympy import ZZ
85 >>> from sympy.polys.matrices import DomainMatrix
86 >>> A = DomainMatrix([
87 ... [ZZ(1), ZZ(2)],
88 ... [ZZ(3), ZZ(4)]], (2, 2), ZZ)
89 >>> A
90 DomainMatrix([[1, 2], [3, 4]], (2, 2), ZZ)
92 See Also
93 ========
95 DDM
96 SDM
97 Domain
98 Poly
100 """
101 rep: tUnion[SDM, DDM]
102 shape: tTuple[int, int]
103 domain: Domain
105 def __new__(cls, rows, shape, domain, *, fmt=None):
106 """
107 Creates a :py:class:`~.DomainMatrix`.
109 Parameters
110 ==========
112 rows : Represents elements of DomainMatrix as list of lists
113 shape : Represents dimension of DomainMatrix
114 domain : Represents :py:class:`~.Domain` of DomainMatrix
116 Raises
117 ======
119 TypeError
120 If any of rows, shape and domain are not provided
122 """
123 if isinstance(rows, (DDM, SDM)):
124 raise TypeError("Use from_rep to initialise from SDM/DDM")
125 elif isinstance(rows, list):
126 rep = DDM(rows, shape, domain)
127 elif isinstance(rows, dict):
128 rep = SDM(rows, shape, domain)
129 else:
130 msg = "Input should be list-of-lists or dict-of-dicts"
131 raise TypeError(msg)
133 if fmt is not None:
134 if fmt == 'sparse':
135 rep = rep.to_sdm()
136 elif fmt == 'dense':
137 rep = rep.to_ddm()
138 else:
139 raise ValueError("fmt should be 'sparse' or 'dense'")
141 return cls.from_rep(rep)
143 def __getnewargs__(self):
144 rep = self.rep
145 if isinstance(rep, DDM):
146 arg = list(rep)
147 elif isinstance(rep, SDM):
148 arg = dict(rep)
149 else:
150 raise RuntimeError # pragma: no cover
151 return arg, self.shape, self.domain
153 def __getitem__(self, key):
154 i, j = key
155 m, n = self.shape
156 if not (isinstance(i, slice) or isinstance(j, slice)):
157 return DomainScalar(self.rep.getitem(i, j), self.domain)
159 if not isinstance(i, slice):
160 if not -m <= i < m:
161 raise IndexError("Row index out of range")
162 i = i % m
163 i = slice(i, i+1)
164 if not isinstance(j, slice):
165 if not -n <= j < n:
166 raise IndexError("Column index out of range")
167 j = j % n
168 j = slice(j, j+1)
170 return self.from_rep(self.rep.extract_slice(i, j))
172 def getitem_sympy(self, i, j):
173 return self.domain.to_sympy(self.rep.getitem(i, j))
175 def extract(self, rowslist, colslist):
176 return self.from_rep(self.rep.extract(rowslist, colslist))
178 def __setitem__(self, key, value):
179 i, j = key
180 if not self.domain.of_type(value):
181 raise TypeError
182 if isinstance(i, int) and isinstance(j, int):
183 self.rep.setitem(i, j, value)
184 else:
185 raise NotImplementedError
187 @classmethod
188 def from_rep(cls, rep):
189 """Create a new DomainMatrix efficiently from DDM/SDM.
191 Examples
192 ========
194 Create a :py:class:`~.DomainMatrix` with an dense internal
195 representation as :py:class:`~.DDM`:
197 >>> from sympy.polys.domains import ZZ
198 >>> from sympy.polys.matrices import DomainMatrix
199 >>> from sympy.polys.matrices.ddm import DDM
200 >>> drep = DDM([[ZZ(1), ZZ(2)], [ZZ(3), ZZ(4)]], (2, 2), ZZ)
201 >>> dM = DomainMatrix.from_rep(drep)
202 >>> dM
203 DomainMatrix([[1, 2], [3, 4]], (2, 2), ZZ)
205 Create a :py:class:`~.DomainMatrix` with a sparse internal
206 representation as :py:class:`~.SDM`:
208 >>> from sympy.polys.matrices import DomainMatrix
209 >>> from sympy.polys.matrices.sdm import SDM
210 >>> from sympy import ZZ
211 >>> drep = SDM({0:{1:ZZ(1)},1:{0:ZZ(2)}}, (2, 2), ZZ)
212 >>> dM = DomainMatrix.from_rep(drep)
213 >>> dM
214 DomainMatrix({0: {1: 1}, 1: {0: 2}}, (2, 2), ZZ)
216 Parameters
217 ==========
219 rep: SDM or DDM
220 The internal sparse or dense representation of the matrix.
222 Returns
223 =======
225 DomainMatrix
226 A :py:class:`~.DomainMatrix` wrapping *rep*.
228 Notes
229 =====
231 This takes ownership of rep as its internal representation. If rep is
232 being mutated elsewhere then a copy should be provided to
233 ``from_rep``. Only minimal verification or checking is done on *rep*
234 as this is supposed to be an efficient internal routine.
236 """
237 if not isinstance(rep, (DDM, SDM)):
238 raise TypeError("rep should be of type DDM or SDM")
239 self = super().__new__(cls)
240 self.rep = rep
241 self.shape = rep.shape
242 self.domain = rep.domain
243 return self
246 @classmethod
247 def from_list(cls, rows, domain):
248 r"""
249 Convert a list of lists into a DomainMatrix
251 Parameters
252 ==========
254 rows: list of lists
255 Each element of the inner lists should be either the single arg,
256 or tuple of args, that would be passed to the domain constructor
257 in order to form an element of the domain. See examples.
259 Returns
260 =======
262 DomainMatrix containing elements defined in rows
264 Examples
265 ========
267 >>> from sympy.polys.matrices import DomainMatrix
268 >>> from sympy import FF, QQ, ZZ
269 >>> A = DomainMatrix.from_list([[1, 0, 1], [0, 0, 1]], ZZ)
270 >>> A
271 DomainMatrix([[1, 0, 1], [0, 0, 1]], (2, 3), ZZ)
272 >>> B = DomainMatrix.from_list([[1, 0, 1], [0, 0, 1]], FF(7))
273 >>> B
274 DomainMatrix([[1 mod 7, 0 mod 7, 1 mod 7], [0 mod 7, 0 mod 7, 1 mod 7]], (2, 3), GF(7))
275 >>> C = DomainMatrix.from_list([[(1, 2), (3, 1)], [(1, 4), (5, 1)]], QQ)
276 >>> C
277 DomainMatrix([[1/2, 3], [1/4, 5]], (2, 2), QQ)
279 See Also
280 ========
282 from_list_sympy
284 """
285 nrows = len(rows)
286 ncols = 0 if not nrows else len(rows[0])
287 conv = lambda e: domain(*e) if isinstance(e, tuple) else domain(e)
288 domain_rows = [[conv(e) for e in row] for row in rows]
289 return DomainMatrix(domain_rows, (nrows, ncols), domain)
292 @classmethod
293 def from_list_sympy(cls, nrows, ncols, rows, **kwargs):
294 r"""
295 Convert a list of lists of Expr into a DomainMatrix using construct_domain
297 Parameters
298 ==========
300 nrows: number of rows
301 ncols: number of columns
302 rows: list of lists
304 Returns
305 =======
307 DomainMatrix containing elements of rows
309 Examples
310 ========
312 >>> from sympy.polys.matrices import DomainMatrix
313 >>> from sympy.abc import x, y, z
314 >>> A = DomainMatrix.from_list_sympy(1, 3, [[x, y, z]])
315 >>> A
316 DomainMatrix([[x, y, z]], (1, 3), ZZ[x,y,z])
318 See Also
319 ========
321 sympy.polys.constructor.construct_domain, from_dict_sympy
323 """
324 assert len(rows) == nrows
325 assert all(len(row) == ncols for row in rows)
327 items_sympy = [_sympify(item) for row in rows for item in row]
329 domain, items_domain = cls.get_domain(items_sympy, **kwargs)
331 domain_rows = [[items_domain[ncols*r + c] for c in range(ncols)] for r in range(nrows)]
333 return DomainMatrix(domain_rows, (nrows, ncols), domain)
335 @classmethod
336 def from_dict_sympy(cls, nrows, ncols, elemsdict, **kwargs):
337 """
339 Parameters
340 ==========
342 nrows: number of rows
343 ncols: number of cols
344 elemsdict: dict of dicts containing non-zero elements of the DomainMatrix
346 Returns
347 =======
349 DomainMatrix containing elements of elemsdict
351 Examples
352 ========
354 >>> from sympy.polys.matrices import DomainMatrix
355 >>> from sympy.abc import x,y,z
356 >>> elemsdict = {0: {0:x}, 1:{1: y}, 2: {2: z}}
357 >>> A = DomainMatrix.from_dict_sympy(3, 3, elemsdict)
358 >>> A
359 DomainMatrix({0: {0: x}, 1: {1: y}, 2: {2: z}}, (3, 3), ZZ[x,y,z])
361 See Also
362 ========
364 from_list_sympy
366 """
367 if not all(0 <= r < nrows for r in elemsdict):
368 raise DMBadInputError("Row out of range")
369 if not all(0 <= c < ncols for row in elemsdict.values() for c in row):
370 raise DMBadInputError("Column out of range")
372 items_sympy = [_sympify(item) for row in elemsdict.values() for item in row.values()]
373 domain, items_domain = cls.get_domain(items_sympy, **kwargs)
375 idx = 0
376 items_dict = {}
377 for i, row in elemsdict.items():
378 items_dict[i] = {}
379 for j in row:
380 items_dict[i][j] = items_domain[idx]
381 idx += 1
383 return DomainMatrix(items_dict, (nrows, ncols), domain)
385 @classmethod
386 def from_Matrix(cls, M, fmt='sparse',**kwargs):
387 r"""
388 Convert Matrix to DomainMatrix
390 Parameters
391 ==========
393 M: Matrix
395 Returns
396 =======
398 Returns DomainMatrix with identical elements as M
400 Examples
401 ========
403 >>> from sympy import Matrix
404 >>> from sympy.polys.matrices import DomainMatrix
405 >>> M = Matrix([
406 ... [1.0, 3.4],
407 ... [2.4, 1]])
408 >>> A = DomainMatrix.from_Matrix(M)
409 >>> A
410 DomainMatrix({0: {0: 1.0, 1: 3.4}, 1: {0: 2.4, 1: 1.0}}, (2, 2), RR)
412 We can keep internal representation as ddm using fmt='dense'
413 >>> from sympy import Matrix, QQ
414 >>> from sympy.polys.matrices import DomainMatrix
415 >>> A = DomainMatrix.from_Matrix(Matrix([[QQ(1, 2), QQ(3, 4)], [QQ(0, 1), QQ(0, 1)]]), fmt='dense')
416 >>> A.rep
417 [[1/2, 3/4], [0, 0]]
419 See Also
420 ========
422 Matrix
424 """
425 if fmt == 'dense':
426 return cls.from_list_sympy(*M.shape, M.tolist(), **kwargs)
428 return cls.from_dict_sympy(*M.shape, M.todod(), **kwargs)
430 @classmethod
431 def get_domain(cls, items_sympy, **kwargs):
432 K, items_K = construct_domain(items_sympy, **kwargs)
433 return K, items_K
435 def copy(self):
436 return self.from_rep(self.rep.copy())
438 def convert_to(self, K):
439 r"""
440 Change the domain of DomainMatrix to desired domain or field
442 Parameters
443 ==========
445 K : Represents the desired domain or field.
446 Alternatively, ``None`` may be passed, in which case this method
447 just returns a copy of this DomainMatrix.
449 Returns
450 =======
452 DomainMatrix
453 DomainMatrix with the desired domain or field
455 Examples
456 ========
458 >>> from sympy import ZZ, ZZ_I
459 >>> from sympy.polys.matrices import DomainMatrix
460 >>> A = DomainMatrix([
461 ... [ZZ(1), ZZ(2)],
462 ... [ZZ(3), ZZ(4)]], (2, 2), ZZ)
464 >>> A.convert_to(ZZ_I)
465 DomainMatrix([[1, 2], [3, 4]], (2, 2), ZZ_I)
467 """
468 if K is None:
469 return self.copy()
470 return self.from_rep(self.rep.convert_to(K))
472 def to_sympy(self):
473 return self.convert_to(EXRAW)
475 def to_field(self):
476 r"""
477 Returns a DomainMatrix with the appropriate field
479 Returns
480 =======
482 DomainMatrix
483 DomainMatrix with the appropriate field
485 Examples
486 ========
488 >>> from sympy import ZZ
489 >>> from sympy.polys.matrices import DomainMatrix
490 >>> A = DomainMatrix([
491 ... [ZZ(1), ZZ(2)],
492 ... [ZZ(3), ZZ(4)]], (2, 2), ZZ)
494 >>> A.to_field()
495 DomainMatrix([[1, 2], [3, 4]], (2, 2), QQ)
497 """
498 K = self.domain.get_field()
499 return self.convert_to(K)
501 def to_sparse(self):
502 """
503 Return a sparse DomainMatrix representation of *self*.
505 Examples
506 ========
508 >>> from sympy.polys.matrices import DomainMatrix
509 >>> from sympy import QQ
510 >>> A = DomainMatrix([[1, 0],[0, 2]], (2, 2), QQ)
511 >>> A.rep
512 [[1, 0], [0, 2]]
513 >>> B = A.to_sparse()
514 >>> B.rep
515 {0: {0: 1}, 1: {1: 2}}
516 """
517 if self.rep.fmt == 'sparse':
518 return self
520 return self.from_rep(SDM.from_ddm(self.rep))
522 def to_dense(self):
523 """
524 Return a dense DomainMatrix representation of *self*.
526 Examples
527 ========
529 >>> from sympy.polys.matrices import DomainMatrix
530 >>> from sympy import QQ
531 >>> A = DomainMatrix({0: {0: 1}, 1: {1: 2}}, (2, 2), QQ)
532 >>> A.rep
533 {0: {0: 1}, 1: {1: 2}}
534 >>> B = A.to_dense()
535 >>> B.rep
536 [[1, 0], [0, 2]]
538 """
539 if self.rep.fmt == 'dense':
540 return self
542 return self.from_rep(SDM.to_ddm(self.rep))
544 @classmethod
545 def _unify_domain(cls, *matrices):
546 """Convert matrices to a common domain"""
547 domains = {matrix.domain for matrix in matrices}
548 if len(domains) == 1:
549 return matrices
550 domain = reduce(lambda x, y: x.unify(y), domains)
551 return tuple(matrix.convert_to(domain) for matrix in matrices)
553 @classmethod
554 def _unify_fmt(cls, *matrices, fmt=None):
555 """Convert matrices to the same format.
557 If all matrices have the same format, then return unmodified.
558 Otherwise convert both to the preferred format given as *fmt* which
559 should be 'dense' or 'sparse'.
560 """
561 formats = {matrix.rep.fmt for matrix in matrices}
562 if len(formats) == 1:
563 return matrices
564 if fmt == 'sparse':
565 return tuple(matrix.to_sparse() for matrix in matrices)
566 elif fmt == 'dense':
567 return tuple(matrix.to_dense() for matrix in matrices)
568 else:
569 raise ValueError("fmt should be 'sparse' or 'dense'")
571 def unify(self, *others, fmt=None):
572 """
573 Unifies the domains and the format of self and other
574 matrices.
576 Parameters
577 ==========
579 others : DomainMatrix
581 fmt: string 'dense', 'sparse' or `None` (default)
582 The preferred format to convert to if self and other are not
583 already in the same format. If `None` or not specified then no
584 conversion if performed.
586 Returns
587 =======
589 Tuple[DomainMatrix]
590 Matrices with unified domain and format
592 Examples
593 ========
595 Unify the domain of DomainMatrix that have different domains:
597 >>> from sympy import ZZ, QQ
598 >>> from sympy.polys.matrices import DomainMatrix
599 >>> A = DomainMatrix([[ZZ(1), ZZ(2)]], (1, 2), ZZ)
600 >>> B = DomainMatrix([[QQ(1, 2), QQ(2)]], (1, 2), QQ)
601 >>> Aq, Bq = A.unify(B)
602 >>> Aq
603 DomainMatrix([[1, 2]], (1, 2), QQ)
604 >>> Bq
605 DomainMatrix([[1/2, 2]], (1, 2), QQ)
607 Unify the format (dense or sparse):
609 >>> A = DomainMatrix([[ZZ(1), ZZ(2)]], (1, 2), ZZ)
610 >>> B = DomainMatrix({0:{0: ZZ(1)}}, (2, 2), ZZ)
611 >>> B.rep
612 {0: {0: 1}}
614 >>> A2, B2 = A.unify(B, fmt='dense')
615 >>> B2.rep
616 [[1, 0], [0, 0]]
618 See Also
619 ========
621 convert_to, to_dense, to_sparse
623 """
624 matrices = (self,) + others
625 matrices = DomainMatrix._unify_domain(*matrices)
626 if fmt is not None:
627 matrices = DomainMatrix._unify_fmt(*matrices, fmt=fmt)
628 return matrices
630 def to_Matrix(self):
631 r"""
632 Convert DomainMatrix to Matrix
634 Returns
635 =======
637 Matrix
638 MutableDenseMatrix for the DomainMatrix
640 Examples
641 ========
643 >>> from sympy import ZZ
644 >>> from sympy.polys.matrices import DomainMatrix
645 >>> A = DomainMatrix([
646 ... [ZZ(1), ZZ(2)],
647 ... [ZZ(3), ZZ(4)]], (2, 2), ZZ)
649 >>> A.to_Matrix()
650 Matrix([
651 [1, 2],
652 [3, 4]])
654 See Also
655 ========
657 from_Matrix
659 """
660 from sympy.matrices.dense import MutableDenseMatrix
661 elemlist = self.rep.to_list()
662 elements_sympy = [self.domain.to_sympy(e) for row in elemlist for e in row]
663 return MutableDenseMatrix(*self.shape, elements_sympy)
665 def to_list(self):
666 return self.rep.to_list()
668 def to_list_flat(self):
669 return self.rep.to_list_flat()
671 def to_dok(self):
672 return self.rep.to_dok()
674 def __repr__(self):
675 return 'DomainMatrix(%s, %r, %r)' % (str(self.rep), self.shape, self.domain)
677 def transpose(self):
678 """Matrix transpose of ``self``"""
679 return self.from_rep(self.rep.transpose())
681 def flat(self):
682 rows, cols = self.shape
683 return [self[i,j].element for i in range(rows) for j in range(cols)]
685 @property
686 def is_zero_matrix(self):
687 return self.rep.is_zero_matrix()
689 @property
690 def is_upper(self):
691 """
692 Says whether this matrix is upper-triangular. True can be returned
693 even if the matrix is not square.
694 """
695 return self.rep.is_upper()
697 @property
698 def is_lower(self):
699 """
700 Says whether this matrix is lower-triangular. True can be returned
701 even if the matrix is not square.
702 """
703 return self.rep.is_lower()
705 @property
706 def is_square(self):
707 return self.shape[0] == self.shape[1]
709 def rank(self):
710 rref, pivots = self.rref()
711 return len(pivots)
713 def hstack(A, *B):
714 r"""Horizontally stack the given matrices.
716 Parameters
717 ==========
719 B: DomainMatrix
720 Matrices to stack horizontally.
722 Returns
723 =======
725 DomainMatrix
726 DomainMatrix by stacking horizontally.
728 Examples
729 ========
731 >>> from sympy import ZZ
732 >>> from sympy.polys.matrices import DomainMatrix
734 >>> A = DomainMatrix([[ZZ(1), ZZ(2)], [ZZ(3), ZZ(4)]], (2, 2), ZZ)
735 >>> B = DomainMatrix([[ZZ(5), ZZ(6)], [ZZ(7), ZZ(8)]], (2, 2), ZZ)
736 >>> A.hstack(B)
737 DomainMatrix([[1, 2, 5, 6], [3, 4, 7, 8]], (2, 4), ZZ)
739 >>> C = DomainMatrix([[ZZ(9), ZZ(10)], [ZZ(11), ZZ(12)]], (2, 2), ZZ)
740 >>> A.hstack(B, C)
741 DomainMatrix([[1, 2, 5, 6, 9, 10], [3, 4, 7, 8, 11, 12]], (2, 6), ZZ)
743 See Also
744 ========
746 unify
747 """
748 A, *B = A.unify(*B, fmt='dense')
749 return DomainMatrix.from_rep(A.rep.hstack(*(Bk.rep for Bk in B)))
751 def vstack(A, *B):
752 r"""Vertically stack the given matrices.
754 Parameters
755 ==========
757 B: DomainMatrix
758 Matrices to stack vertically.
760 Returns
761 =======
763 DomainMatrix
764 DomainMatrix by stacking vertically.
766 Examples
767 ========
769 >>> from sympy import ZZ
770 >>> from sympy.polys.matrices import DomainMatrix
772 >>> A = DomainMatrix([[ZZ(1), ZZ(2)], [ZZ(3), ZZ(4)]], (2, 2), ZZ)
773 >>> B = DomainMatrix([[ZZ(5), ZZ(6)], [ZZ(7), ZZ(8)]], (2, 2), ZZ)
774 >>> A.vstack(B)
775 DomainMatrix([[1, 2], [3, 4], [5, 6], [7, 8]], (4, 2), ZZ)
777 >>> C = DomainMatrix([[ZZ(9), ZZ(10)], [ZZ(11), ZZ(12)]], (2, 2), ZZ)
778 >>> A.vstack(B, C)
779 DomainMatrix([[1, 2], [3, 4], [5, 6], [7, 8], [9, 10], [11, 12]], (6, 2), ZZ)
781 See Also
782 ========
784 unify
785 """
786 A, *B = A.unify(*B, fmt='dense')
787 return DomainMatrix.from_rep(A.rep.vstack(*(Bk.rep for Bk in B)))
789 def applyfunc(self, func, domain=None):
790 if domain is None:
791 domain = self.domain
792 return self.from_rep(self.rep.applyfunc(func, domain))
794 def __add__(A, B):
795 if not isinstance(B, DomainMatrix):
796 return NotImplemented
797 A, B = A.unify(B, fmt='dense')
798 return A.add(B)
800 def __sub__(A, B):
801 if not isinstance(B, DomainMatrix):
802 return NotImplemented
803 A, B = A.unify(B, fmt='dense')
804 return A.sub(B)
806 def __neg__(A):
807 return A.neg()
809 def __mul__(A, B):
810 """A * B"""
811 if isinstance(B, DomainMatrix):
812 A, B = A.unify(B, fmt='dense')
813 return A.matmul(B)
814 elif B in A.domain:
815 return A.scalarmul(B)
816 elif isinstance(B, DomainScalar):
817 A, B = A.unify(B)
818 return A.scalarmul(B.element)
819 else:
820 return NotImplemented
822 def __rmul__(A, B):
823 if B in A.domain:
824 return A.rscalarmul(B)
825 elif isinstance(B, DomainScalar):
826 A, B = A.unify(B)
827 return A.rscalarmul(B.element)
828 else:
829 return NotImplemented
831 def __pow__(A, n):
832 """A ** n"""
833 if not isinstance(n, int):
834 return NotImplemented
835 return A.pow(n)
837 def _check(a, op, b, ashape, bshape):
838 if a.domain != b.domain:
839 msg = "Domain mismatch: %s %s %s" % (a.domain, op, b.domain)
840 raise DMDomainError(msg)
841 if ashape != bshape:
842 msg = "Shape mismatch: %s %s %s" % (a.shape, op, b.shape)
843 raise DMShapeError(msg)
844 if a.rep.fmt != b.rep.fmt:
845 msg = "Format mismatch: %s %s %s" % (a.rep.fmt, op, b.rep.fmt)
846 raise DMFormatError(msg)
848 def add(A, B):
849 r"""
850 Adds two DomainMatrix matrices of the same Domain
852 Parameters
853 ==========
855 A, B: DomainMatrix
856 matrices to add
858 Returns
859 =======
861 DomainMatrix
862 DomainMatrix after Addition
864 Raises
865 ======
867 DMShapeError
868 If the dimensions of the two DomainMatrix are not equal
870 ValueError
871 If the domain of the two DomainMatrix are not same
873 Examples
874 ========
876 >>> from sympy import ZZ
877 >>> from sympy.polys.matrices import DomainMatrix
878 >>> A = DomainMatrix([
879 ... [ZZ(1), ZZ(2)],
880 ... [ZZ(3), ZZ(4)]], (2, 2), ZZ)
881 >>> B = DomainMatrix([
882 ... [ZZ(4), ZZ(3)],
883 ... [ZZ(2), ZZ(1)]], (2, 2), ZZ)
885 >>> A.add(B)
886 DomainMatrix([[5, 5], [5, 5]], (2, 2), ZZ)
888 See Also
889 ========
891 sub, matmul
893 """
894 A._check('+', B, A.shape, B.shape)
895 return A.from_rep(A.rep.add(B.rep))
898 def sub(A, B):
899 r"""
900 Subtracts two DomainMatrix matrices of the same Domain
902 Parameters
903 ==========
905 A, B: DomainMatrix
906 matrices to subtract
908 Returns
909 =======
911 DomainMatrix
912 DomainMatrix after Subtraction
914 Raises
915 ======
917 DMShapeError
918 If the dimensions of the two DomainMatrix are not equal
920 ValueError
921 If the domain of the two DomainMatrix are not same
923 Examples
924 ========
926 >>> from sympy import ZZ
927 >>> from sympy.polys.matrices import DomainMatrix
928 >>> A = DomainMatrix([
929 ... [ZZ(1), ZZ(2)],
930 ... [ZZ(3), ZZ(4)]], (2, 2), ZZ)
931 >>> B = DomainMatrix([
932 ... [ZZ(4), ZZ(3)],
933 ... [ZZ(2), ZZ(1)]], (2, 2), ZZ)
935 >>> A.sub(B)
936 DomainMatrix([[-3, -1], [1, 3]], (2, 2), ZZ)
938 See Also
939 ========
941 add, matmul
943 """
944 A._check('-', B, A.shape, B.shape)
945 return A.from_rep(A.rep.sub(B.rep))
947 def neg(A):
948 r"""
949 Returns the negative of DomainMatrix
951 Parameters
952 ==========
954 A : Represents a DomainMatrix
956 Returns
957 =======
959 DomainMatrix
960 DomainMatrix after Negation
962 Examples
963 ========
965 >>> from sympy import ZZ
966 >>> from sympy.polys.matrices import DomainMatrix
967 >>> A = DomainMatrix([
968 ... [ZZ(1), ZZ(2)],
969 ... [ZZ(3), ZZ(4)]], (2, 2), ZZ)
971 >>> A.neg()
972 DomainMatrix([[-1, -2], [-3, -4]], (2, 2), ZZ)
974 """
975 return A.from_rep(A.rep.neg())
977 def mul(A, b):
978 r"""
979 Performs term by term multiplication for the second DomainMatrix
980 w.r.t first DomainMatrix. Returns a DomainMatrix whose rows are
981 list of DomainMatrix matrices created after term by term multiplication.
983 Parameters
984 ==========
986 A, B: DomainMatrix
987 matrices to multiply term-wise
989 Returns
990 =======
992 DomainMatrix
993 DomainMatrix after term by term multiplication
995 Examples
996 ========
998 >>> from sympy import ZZ
999 >>> from sympy.polys.matrices import DomainMatrix
1000 >>> A = DomainMatrix([
1001 ... [ZZ(1), ZZ(2)],
1002 ... [ZZ(3), ZZ(4)]], (2, 2), ZZ)
1003 >>> B = DomainMatrix([
1004 ... [ZZ(1), ZZ(1)],
1005 ... [ZZ(0), ZZ(1)]], (2, 2), ZZ)
1007 >>> A.mul(B)
1008 DomainMatrix([[DomainMatrix([[1, 1], [0, 1]], (2, 2), ZZ),
1009 DomainMatrix([[2, 2], [0, 2]], (2, 2), ZZ)],
1010 [DomainMatrix([[3, 3], [0, 3]], (2, 2), ZZ),
1011 DomainMatrix([[4, 4], [0, 4]], (2, 2), ZZ)]], (2, 2), ZZ)
1013 See Also
1014 ========
1016 matmul
1018 """
1019 return A.from_rep(A.rep.mul(b))
1021 def rmul(A, b):
1022 return A.from_rep(A.rep.rmul(b))
1024 def matmul(A, B):
1025 r"""
1026 Performs matrix multiplication of two DomainMatrix matrices
1028 Parameters
1029 ==========
1031 A, B: DomainMatrix
1032 to multiply
1034 Returns
1035 =======
1037 DomainMatrix
1038 DomainMatrix after multiplication
1040 Examples
1041 ========
1043 >>> from sympy import ZZ
1044 >>> from sympy.polys.matrices import DomainMatrix
1045 >>> A = DomainMatrix([
1046 ... [ZZ(1), ZZ(2)],
1047 ... [ZZ(3), ZZ(4)]], (2, 2), ZZ)
1048 >>> B = DomainMatrix([
1049 ... [ZZ(1), ZZ(1)],
1050 ... [ZZ(0), ZZ(1)]], (2, 2), ZZ)
1052 >>> A.matmul(B)
1053 DomainMatrix([[1, 3], [3, 7]], (2, 2), ZZ)
1055 See Also
1056 ========
1058 mul, pow, add, sub
1060 """
1062 A._check('*', B, A.shape[1], B.shape[0])
1063 return A.from_rep(A.rep.matmul(B.rep))
1065 def _scalarmul(A, lamda, reverse):
1066 if lamda == A.domain.zero:
1067 return DomainMatrix.zeros(A.shape, A.domain)
1068 elif lamda == A.domain.one:
1069 return A.copy()
1070 elif reverse:
1071 return A.rmul(lamda)
1072 else:
1073 return A.mul(lamda)
1075 def scalarmul(A, lamda):
1076 return A._scalarmul(lamda, reverse=False)
1078 def rscalarmul(A, lamda):
1079 return A._scalarmul(lamda, reverse=True)
1081 def mul_elementwise(A, B):
1082 assert A.domain == B.domain
1083 return A.from_rep(A.rep.mul_elementwise(B.rep))
1085 def __truediv__(A, lamda):
1086 """ Method for Scalar Division"""
1087 if isinstance(lamda, int) or ZZ.of_type(lamda):
1088 lamda = DomainScalar(ZZ(lamda), ZZ)
1090 if not isinstance(lamda, DomainScalar):
1091 return NotImplemented
1093 A, lamda = A.to_field().unify(lamda)
1094 if lamda.element == lamda.domain.zero:
1095 raise ZeroDivisionError
1096 if lamda.element == lamda.domain.one:
1097 return A.to_field()
1099 return A.mul(1 / lamda.element)
1101 def pow(A, n):
1102 r"""
1103 Computes A**n
1105 Parameters
1106 ==========
1108 A : DomainMatrix
1110 n : exponent for A
1112 Returns
1113 =======
1115 DomainMatrix
1116 DomainMatrix on computing A**n
1118 Raises
1119 ======
1121 NotImplementedError
1122 if n is negative.
1124 Examples
1125 ========
1127 >>> from sympy import ZZ
1128 >>> from sympy.polys.matrices import DomainMatrix
1129 >>> A = DomainMatrix([
1130 ... [ZZ(1), ZZ(1)],
1131 ... [ZZ(0), ZZ(1)]], (2, 2), ZZ)
1133 >>> A.pow(2)
1134 DomainMatrix([[1, 2], [0, 1]], (2, 2), ZZ)
1136 See Also
1137 ========
1139 matmul
1141 """
1142 nrows, ncols = A.shape
1143 if nrows != ncols:
1144 raise DMNonSquareMatrixError('Power of a nonsquare matrix')
1145 if n < 0:
1146 raise NotImplementedError('Negative powers')
1147 elif n == 0:
1148 return A.eye(nrows, A.domain)
1149 elif n == 1:
1150 return A
1151 elif n % 2 == 1:
1152 return A * A**(n - 1)
1153 else:
1154 sqrtAn = A ** (n // 2)
1155 return sqrtAn * sqrtAn
1157 def scc(self):
1158 """Compute the strongly connected components of a DomainMatrix
1160 Explanation
1161 ===========
1163 A square matrix can be considered as the adjacency matrix for a
1164 directed graph where the row and column indices are the vertices. In
1165 this graph if there is an edge from vertex ``i`` to vertex ``j`` if
1166 ``M[i, j]`` is nonzero. This routine computes the strongly connected
1167 components of that graph which are subsets of the rows and columns that
1168 are connected by some nonzero element of the matrix. The strongly
1169 connected components are useful because many operations such as the
1170 determinant can be computed by working with the submatrices
1171 corresponding to each component.
1173 Examples
1174 ========
1176 Find the strongly connected components of a matrix:
1178 >>> from sympy import ZZ
1179 >>> from sympy.polys.matrices import DomainMatrix
1180 >>> M = DomainMatrix([[ZZ(1), ZZ(0), ZZ(2)],
1181 ... [ZZ(0), ZZ(3), ZZ(0)],
1182 ... [ZZ(4), ZZ(6), ZZ(5)]], (3, 3), ZZ)
1183 >>> M.scc()
1184 [[1], [0, 2]]
1186 Compute the determinant from the components:
1188 >>> MM = M.to_Matrix()
1189 >>> MM
1190 Matrix([
1191 [1, 0, 2],
1192 [0, 3, 0],
1193 [4, 6, 5]])
1194 >>> MM[[1], [1]]
1195 Matrix([[3]])
1196 >>> MM[[0, 2], [0, 2]]
1197 Matrix([
1198 [1, 2],
1199 [4, 5]])
1200 >>> MM.det()
1201 -9
1202 >>> MM[[1], [1]].det() * MM[[0, 2], [0, 2]].det()
1203 -9
1205 The components are given in reverse topological order and represent a
1206 permutation of the rows and columns that will bring the matrix into
1207 block lower-triangular form:
1209 >>> MM[[1, 0, 2], [1, 0, 2]]
1210 Matrix([
1211 [3, 0, 0],
1212 [0, 1, 2],
1213 [6, 4, 5]])
1215 Returns
1216 =======
1218 List of lists of integers
1219 Each list represents a strongly connected component.
1221 See also
1222 ========
1224 sympy.matrices.matrices.MatrixBase.strongly_connected_components
1225 sympy.utilities.iterables.strongly_connected_components
1227 """
1228 rows, cols = self.shape
1229 assert rows == cols
1230 return self.rep.scc()
1232 def rref(self):
1233 r"""
1234 Returns reduced-row echelon form and list of pivots for the DomainMatrix
1236 Returns
1237 =======
1239 (DomainMatrix, list)
1240 reduced-row echelon form and list of pivots for the DomainMatrix
1242 Raises
1243 ======
1245 ValueError
1246 If the domain of DomainMatrix not a Field
1248 Examples
1249 ========
1251 >>> from sympy import QQ
1252 >>> from sympy.polys.matrices import DomainMatrix
1253 >>> A = DomainMatrix([
1254 ... [QQ(2), QQ(-1), QQ(0)],
1255 ... [QQ(-1), QQ(2), QQ(-1)],
1256 ... [QQ(0), QQ(0), QQ(2)]], (3, 3), QQ)
1258 >>> rref_matrix, rref_pivots = A.rref()
1259 >>> rref_matrix
1260 DomainMatrix([[1, 0, 0], [0, 1, 0], [0, 0, 1]], (3, 3), QQ)
1261 >>> rref_pivots
1262 (0, 1, 2)
1264 See Also
1265 ========
1267 convert_to, lu
1269 """
1270 if not self.domain.is_Field:
1271 raise DMNotAField('Not a field')
1272 rref_ddm, pivots = self.rep.rref()
1273 return self.from_rep(rref_ddm), tuple(pivots)
1275 def columnspace(self):
1276 r"""
1277 Returns the columnspace for the DomainMatrix
1279 Returns
1280 =======
1282 DomainMatrix
1283 The columns of this matrix form a basis for the columnspace.
1285 Examples
1286 ========
1288 >>> from sympy import QQ
1289 >>> from sympy.polys.matrices import DomainMatrix
1290 >>> A = DomainMatrix([
1291 ... [QQ(1), QQ(-1)],
1292 ... [QQ(2), QQ(-2)]], (2, 2), QQ)
1293 >>> A.columnspace()
1294 DomainMatrix([[1], [2]], (2, 1), QQ)
1296 """
1297 if not self.domain.is_Field:
1298 raise DMNotAField('Not a field')
1299 rref, pivots = self.rref()
1300 rows, cols = self.shape
1301 return self.extract(range(rows), pivots)
1303 def rowspace(self):
1304 r"""
1305 Returns the rowspace for the DomainMatrix
1307 Returns
1308 =======
1310 DomainMatrix
1311 The rows of this matrix form a basis for the rowspace.
1313 Examples
1314 ========
1316 >>> from sympy import QQ
1317 >>> from sympy.polys.matrices import DomainMatrix
1318 >>> A = DomainMatrix([
1319 ... [QQ(1), QQ(-1)],
1320 ... [QQ(2), QQ(-2)]], (2, 2), QQ)
1321 >>> A.rowspace()
1322 DomainMatrix([[1, -1]], (1, 2), QQ)
1324 """
1325 if not self.domain.is_Field:
1326 raise DMNotAField('Not a field')
1327 rref, pivots = self.rref()
1328 rows, cols = self.shape
1329 return self.extract(range(len(pivots)), range(cols))
1331 def nullspace(self):
1332 r"""
1333 Returns the nullspace for the DomainMatrix
1335 Returns
1336 =======
1338 DomainMatrix
1339 The rows of this matrix form a basis for the nullspace.
1341 Examples
1342 ========
1344 >>> from sympy import QQ
1345 >>> from sympy.polys.matrices import DomainMatrix
1346 >>> A = DomainMatrix([
1347 ... [QQ(1), QQ(-1)],
1348 ... [QQ(2), QQ(-2)]], (2, 2), QQ)
1349 >>> A.nullspace()
1350 DomainMatrix([[1, 1]], (1, 2), QQ)
1352 """
1353 if not self.domain.is_Field:
1354 raise DMNotAField('Not a field')
1355 return self.from_rep(self.rep.nullspace()[0])
1357 def inv(self):
1358 r"""
1359 Finds the inverse of the DomainMatrix if exists
1361 Returns
1362 =======
1364 DomainMatrix
1365 DomainMatrix after inverse
1367 Raises
1368 ======
1370 ValueError
1371 If the domain of DomainMatrix not a Field
1373 DMNonSquareMatrixError
1374 If the DomainMatrix is not a not Square DomainMatrix
1376 Examples
1377 ========
1379 >>> from sympy import QQ
1380 >>> from sympy.polys.matrices import DomainMatrix
1381 >>> A = DomainMatrix([
1382 ... [QQ(2), QQ(-1), QQ(0)],
1383 ... [QQ(-1), QQ(2), QQ(-1)],
1384 ... [QQ(0), QQ(0), QQ(2)]], (3, 3), QQ)
1385 >>> A.inv()
1386 DomainMatrix([[2/3, 1/3, 1/6], [1/3, 2/3, 1/3], [0, 0, 1/2]], (3, 3), QQ)
1388 See Also
1389 ========
1391 neg
1393 """
1394 if not self.domain.is_Field:
1395 raise DMNotAField('Not a field')
1396 m, n = self.shape
1397 if m != n:
1398 raise DMNonSquareMatrixError
1399 inv = self.rep.inv()
1400 return self.from_rep(inv)
1402 def det(self):
1403 r"""
1404 Returns the determinant of a Square DomainMatrix
1406 Returns
1407 =======
1409 S.Complexes
1410 determinant of Square DomainMatrix
1412 Raises
1413 ======
1415 ValueError
1416 If the domain of DomainMatrix not a Field
1418 Examples
1419 ========
1421 >>> from sympy import ZZ
1422 >>> from sympy.polys.matrices import DomainMatrix
1423 >>> A = DomainMatrix([
1424 ... [ZZ(1), ZZ(2)],
1425 ... [ZZ(3), ZZ(4)]], (2, 2), ZZ)
1427 >>> A.det()
1428 -2
1430 """
1431 m, n = self.shape
1432 if m != n:
1433 raise DMNonSquareMatrixError
1434 return self.rep.det()
1436 def lu(self):
1437 r"""
1438 Returns Lower and Upper decomposition of the DomainMatrix
1440 Returns
1441 =======
1443 (L, U, exchange)
1444 L, U are Lower and Upper decomposition of the DomainMatrix,
1445 exchange is the list of indices of rows exchanged in the decomposition.
1447 Raises
1448 ======
1450 ValueError
1451 If the domain of DomainMatrix not a Field
1453 Examples
1454 ========
1456 >>> from sympy import QQ
1457 >>> from sympy.polys.matrices import DomainMatrix
1458 >>> A = DomainMatrix([
1459 ... [QQ(1), QQ(-1)],
1460 ... [QQ(2), QQ(-2)]], (2, 2), QQ)
1461 >>> A.lu()
1462 (DomainMatrix([[1, 0], [2, 1]], (2, 2), QQ), DomainMatrix([[1, -1], [0, 0]], (2, 2), QQ), [])
1464 See Also
1465 ========
1467 lu_solve
1469 """
1470 if not self.domain.is_Field:
1471 raise DMNotAField('Not a field')
1472 L, U, swaps = self.rep.lu()
1473 return self.from_rep(L), self.from_rep(U), swaps
1475 def lu_solve(self, rhs):
1476 r"""
1477 Solver for DomainMatrix x in the A*x = B
1479 Parameters
1480 ==========
1482 rhs : DomainMatrix B
1484 Returns
1485 =======
1487 DomainMatrix
1488 x in A*x = B
1490 Raises
1491 ======
1493 DMShapeError
1494 If the DomainMatrix A and rhs have different number of rows
1496 ValueError
1497 If the domain of DomainMatrix A not a Field
1499 Examples
1500 ========
1502 >>> from sympy import QQ
1503 >>> from sympy.polys.matrices import DomainMatrix
1504 >>> A = DomainMatrix([
1505 ... [QQ(1), QQ(2)],
1506 ... [QQ(3), QQ(4)]], (2, 2), QQ)
1507 >>> B = DomainMatrix([
1508 ... [QQ(1), QQ(1)],
1509 ... [QQ(0), QQ(1)]], (2, 2), QQ)
1511 >>> A.lu_solve(B)
1512 DomainMatrix([[-2, -1], [3/2, 1]], (2, 2), QQ)
1514 See Also
1515 ========
1517 lu
1519 """
1520 if self.shape[0] != rhs.shape[0]:
1521 raise DMShapeError("Shape")
1522 if not self.domain.is_Field:
1523 raise DMNotAField('Not a field')
1524 sol = self.rep.lu_solve(rhs.rep)
1525 return self.from_rep(sol)
1527 def _solve(A, b):
1528 # XXX: Not sure about this method or its signature. It is just created
1529 # because it is needed by the holonomic module.
1530 if A.shape[0] != b.shape[0]:
1531 raise DMShapeError("Shape")
1532 if A.domain != b.domain or not A.domain.is_Field:
1533 raise DMNotAField('Not a field')
1534 Aaug = A.hstack(b)
1535 Arref, pivots = Aaug.rref()
1536 particular = Arref.from_rep(Arref.rep.particular())
1537 nullspace_rep, nonpivots = Arref[:,:-1].rep.nullspace()
1538 nullspace = Arref.from_rep(nullspace_rep)
1539 return particular, nullspace
1541 def charpoly(self):
1542 r"""
1543 Returns the coefficients of the characteristic polynomial
1544 of the DomainMatrix. These elements will be domain elements.
1545 The domain of the elements will be same as domain of the DomainMatrix.
1547 Returns
1548 =======
1550 list
1551 coefficients of the characteristic polynomial
1553 Raises
1554 ======
1556 DMNonSquareMatrixError
1557 If the DomainMatrix is not a not Square DomainMatrix
1559 Examples
1560 ========
1562 >>> from sympy import ZZ
1563 >>> from sympy.polys.matrices import DomainMatrix
1564 >>> A = DomainMatrix([
1565 ... [ZZ(1), ZZ(2)],
1566 ... [ZZ(3), ZZ(4)]], (2, 2), ZZ)
1568 >>> A.charpoly()
1569 [1, -5, -2]
1571 """
1572 m, n = self.shape
1573 if m != n:
1574 raise DMNonSquareMatrixError("not square")
1575 return self.rep.charpoly()
1577 @classmethod
1578 def eye(cls, shape, domain):
1579 r"""
1580 Return identity matrix of size n
1582 Examples
1583 ========
1585 >>> from sympy.polys.matrices import DomainMatrix
1586 >>> from sympy import QQ
1587 >>> DomainMatrix.eye(3, QQ)
1588 DomainMatrix({0: {0: 1}, 1: {1: 1}, 2: {2: 1}}, (3, 3), QQ)
1590 """
1591 if isinstance(shape, int):
1592 shape = (shape, shape)
1593 return cls.from_rep(SDM.eye(shape, domain))
1595 @classmethod
1596 def diag(cls, diagonal, domain, shape=None):
1597 r"""
1598 Return diagonal matrix with entries from ``diagonal``.
1600 Examples
1601 ========
1603 >>> from sympy.polys.matrices import DomainMatrix
1604 >>> from sympy import ZZ
1605 >>> DomainMatrix.diag([ZZ(5), ZZ(6)], ZZ)
1606 DomainMatrix({0: {0: 5}, 1: {1: 6}}, (2, 2), ZZ)
1608 """
1609 if shape is None:
1610 N = len(diagonal)
1611 shape = (N, N)
1612 return cls.from_rep(SDM.diag(diagonal, domain, shape))
1614 @classmethod
1615 def zeros(cls, shape, domain, *, fmt='sparse'):
1616 """Returns a zero DomainMatrix of size shape, belonging to the specified domain
1618 Examples
1619 ========
1621 >>> from sympy.polys.matrices import DomainMatrix
1622 >>> from sympy import QQ
1623 >>> DomainMatrix.zeros((2, 3), QQ)
1624 DomainMatrix({}, (2, 3), QQ)
1626 """
1627 return cls.from_rep(SDM.zeros(shape, domain))
1629 @classmethod
1630 def ones(cls, shape, domain):
1631 """Returns a DomainMatrix of 1s, of size shape, belonging to the specified domain
1633 Examples
1634 ========
1636 >>> from sympy.polys.matrices import DomainMatrix
1637 >>> from sympy import QQ
1638 >>> DomainMatrix.ones((2,3), QQ)
1639 DomainMatrix([[1, 1, 1], [1, 1, 1]], (2, 3), QQ)
1641 """
1642 return cls.from_rep(DDM.ones(shape, domain))
1644 def __eq__(A, B):
1645 r"""
1646 Checks for two DomainMatrix matrices to be equal or not
1648 Parameters
1649 ==========
1651 A, B: DomainMatrix
1652 to check equality
1654 Returns
1655 =======
1657 Boolean
1658 True for equal, else False
1660 Raises
1661 ======
1663 NotImplementedError
1664 If B is not a DomainMatrix
1666 Examples
1667 ========
1669 >>> from sympy import ZZ
1670 >>> from sympy.polys.matrices import DomainMatrix
1671 >>> A = DomainMatrix([
1672 ... [ZZ(1), ZZ(2)],
1673 ... [ZZ(3), ZZ(4)]], (2, 2), ZZ)
1674 >>> B = DomainMatrix([
1675 ... [ZZ(1), ZZ(1)],
1676 ... [ZZ(0), ZZ(1)]], (2, 2), ZZ)
1677 >>> A.__eq__(A)
1678 True
1679 >>> A.__eq__(B)
1680 False
1682 """
1683 if not isinstance(A, type(B)):
1684 return NotImplemented
1685 return A.domain == B.domain and A.rep == B.rep
1687 def unify_eq(A, B):
1688 if A.shape != B.shape:
1689 return False
1690 if A.domain != B.domain:
1691 A, B = A.unify(B)
1692 return A == B
1694 def lll(A, delta=QQ(3, 4)):
1695 """
1696 Performs the Lenstra–Lenstra–Lovász (LLL) basis reduction algorithm.
1697 See [1]_ and [2]_.
1699 Parameters
1700 ==========
1702 delta : QQ, optional
1703 The Lovász parameter. Must be in the interval (0.25, 1), with larger
1704 values producing a more reduced basis. The default is 0.75 for
1705 historical reasons.
1707 Returns
1708 =======
1710 The reduced basis as a DomainMatrix over ZZ.
1712 Throws
1713 ======
1715 DMValueError: if delta is not in the range (0.25, 1)
1716 DMShapeError: if the matrix is not of shape (m, n) with m <= n
1717 DMDomainError: if the matrix domain is not ZZ
1718 DMRankError: if the matrix contains linearly dependent rows
1720 Examples
1721 ========
1723 >>> from sympy.polys.domains import ZZ, QQ
1724 >>> from sympy.polys.matrices import DM
1725 >>> x = DM([[1, 0, 0, 0, -20160],
1726 ... [0, 1, 0, 0, 33768],
1727 ... [0, 0, 1, 0, 39578],
1728 ... [0, 0, 0, 1, 47757]], ZZ)
1729 >>> y = DM([[10, -3, -2, 8, -4],
1730 ... [3, -9, 8, 1, -11],
1731 ... [-3, 13, -9, -3, -9],
1732 ... [-12, -7, -11, 9, -1]], ZZ)
1733 >>> assert x.lll(delta=QQ(5, 6)) == y
1735 Notes
1736 =====
1738 The implementation is derived from the Maple code given in Figures 4.3
1739 and 4.4 of [3]_ (pp.68-69). It uses the efficient method of only calculating
1740 state updates as they are required.
1742 See also
1743 ========
1745 lll_transform
1747 References
1748 ==========
1750 .. [1] https://en.wikipedia.org/wiki/Lenstra–Lenstra–Lovász_lattice_basis_reduction_algorithm
1751 .. [2] https://web.archive.org/web/20221029115428/https://web.cs.elte.hu/~lovasz/scans/lll.pdf
1752 .. [3] Murray R. Bremner, "Lattice Basis Reduction: An Introduction to the LLL Algorithm and Its Applications"
1754 """
1755 return DomainMatrix.from_rep(A.rep.lll(delta=delta))
1757 def lll_transform(A, delta=QQ(3, 4)):
1758 """
1759 Performs the Lenstra–Lenstra–Lovász (LLL) basis reduction algorithm
1760 and returns the reduced basis and transformation matrix.
1762 Explanation
1763 ===========
1765 Parameters, algorithm and basis are the same as for :meth:`lll` except that
1766 the return value is a tuple `(B, T)` with `B` the reduced basis and
1767 `T` a transformation matrix. The original basis `A` is transformed to
1768 `B` with `T*A == B`. If only `B` is needed then :meth:`lll` should be
1769 used as it is a little faster.
1771 Examples
1772 ========
1774 >>> from sympy.polys.domains import ZZ, QQ
1775 >>> from sympy.polys.matrices import DM
1776 >>> X = DM([[1, 0, 0, 0, -20160],
1777 ... [0, 1, 0, 0, 33768],
1778 ... [0, 0, 1, 0, 39578],
1779 ... [0, 0, 0, 1, 47757]], ZZ)
1780 >>> B, T = X.lll_transform(delta=QQ(5, 6))
1781 >>> T * X == B
1782 True
1784 See also
1785 ========
1787 lll
1789 """
1790 reduced, transform = A.rep.lll_transform(delta=delta)
1791 return DomainMatrix.from_rep(reduced), DomainMatrix.from_rep(transform)