Coverage for /usr/lib/python3/dist-packages/sympy/matrices/repmatrix.py: 37%
271 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
1from collections import defaultdict
3from operator import index as index_
5from sympy.core.expr import Expr
6from sympy.core.kind import Kind, NumberKind, UndefinedKind
7from sympy.core.numbers import Integer, Rational
8from sympy.core.sympify import _sympify, SympifyError
9from sympy.core.singleton import S
10from sympy.polys.domains import ZZ, QQ, EXRAW
11from sympy.polys.matrices import DomainMatrix
12from sympy.utilities.exceptions import sympy_deprecation_warning
13from sympy.utilities.iterables import is_sequence
14from sympy.utilities.misc import filldedent
16from .common import classof
17from .matrices import MatrixBase, MatrixKind, ShapeError
20class RepMatrix(MatrixBase):
21 """Matrix implementation based on DomainMatrix as an internal representation.
23 The RepMatrix class is a superclass for Matrix, ImmutableMatrix,
24 SparseMatrix and ImmutableSparseMatrix which are the main usable matrix
25 classes in SymPy. Most methods on this class are simply forwarded to
26 DomainMatrix.
27 """
29 #
30 # MatrixBase is the common superclass for all of the usable explicit matrix
31 # classes in SymPy. The idea is that MatrixBase is an abstract class though
32 # and that subclasses will implement the lower-level methods.
33 #
34 # RepMatrix is a subclass of MatrixBase that uses DomainMatrix as an
35 # internal representation and delegates lower-level methods to
36 # DomainMatrix. All of SymPy's standard explicit matrix classes subclass
37 # RepMatrix and so use DomainMatrix internally.
38 #
39 # A RepMatrix uses an internal DomainMatrix with the domain set to ZZ, QQ
40 # or EXRAW. The EXRAW domain is equivalent to the previous implementation
41 # of Matrix that used Expr for the elements. The ZZ and QQ domains are used
42 # when applicable just because they are compatible with the previous
43 # implementation but are much more efficient. Other domains such as QQ[x]
44 # are not used because they differ from Expr in some way (e.g. automatic
45 # expansion of powers and products).
46 #
48 _rep: DomainMatrix
50 def __eq__(self, other):
51 # Skip sympify for mutable matrices...
52 if not isinstance(other, RepMatrix):
53 try:
54 other = _sympify(other)
55 except SympifyError:
56 return NotImplemented
57 if not isinstance(other, RepMatrix):
58 return NotImplemented
60 return self._rep.unify_eq(other._rep)
62 @classmethod
63 def _unify_element_sympy(cls, rep, element):
64 domain = rep.domain
65 element = _sympify(element)
67 if domain != EXRAW:
68 # The domain can only be ZZ, QQ or EXRAW
69 if element.is_Integer:
70 new_domain = domain
71 elif element.is_Rational:
72 new_domain = QQ
73 else:
74 new_domain = EXRAW
76 # XXX: This converts the domain for all elements in the matrix
77 # which can be slow. This happens e.g. if __setitem__ changes one
78 # element to something that does not fit in the domain
79 if new_domain != domain:
80 rep = rep.convert_to(new_domain)
81 domain = new_domain
83 if domain != EXRAW:
84 element = new_domain.from_sympy(element)
86 if domain == EXRAW and not isinstance(element, Expr):
87 sympy_deprecation_warning(
88 """
89 non-Expr objects in a Matrix is deprecated. Matrix represents
90 a mathematical matrix. To represent a container of non-numeric
91 entities, Use a list of lists, TableForm, NumPy array, or some
92 other data structure instead.
93 """,
94 deprecated_since_version="1.9",
95 active_deprecations_target="deprecated-non-expr-in-matrix",
96 stacklevel=4,
97 )
99 return rep, element
101 @classmethod
102 def _dod_to_DomainMatrix(cls, rows, cols, dod, types):
104 if not all(issubclass(typ, Expr) for typ in types):
105 sympy_deprecation_warning(
106 """
107 non-Expr objects in a Matrix is deprecated. Matrix represents
108 a mathematical matrix. To represent a container of non-numeric
109 entities, Use a list of lists, TableForm, NumPy array, or some
110 other data structure instead.
111 """,
112 deprecated_since_version="1.9",
113 active_deprecations_target="deprecated-non-expr-in-matrix",
114 stacklevel=6,
115 )
117 rep = DomainMatrix(dod, (rows, cols), EXRAW)
119 if all(issubclass(typ, Rational) for typ in types):
120 if all(issubclass(typ, Integer) for typ in types):
121 rep = rep.convert_to(ZZ)
122 else:
123 rep = rep.convert_to(QQ)
125 return rep
127 @classmethod
128 def _flat_list_to_DomainMatrix(cls, rows, cols, flat_list):
130 elements_dod = defaultdict(dict)
131 for n, element in enumerate(flat_list):
132 if element != 0:
133 i, j = divmod(n, cols)
134 elements_dod[i][j] = element
136 types = set(map(type, flat_list))
138 rep = cls._dod_to_DomainMatrix(rows, cols, elements_dod, types)
139 return rep
141 @classmethod
142 def _smat_to_DomainMatrix(cls, rows, cols, smat):
144 elements_dod = defaultdict(dict)
145 for (i, j), element in smat.items():
146 if element != 0:
147 elements_dod[i][j] = element
149 types = set(map(type, smat.values()))
151 rep = cls._dod_to_DomainMatrix(rows, cols, elements_dod, types)
152 return rep
154 def flat(self):
155 return self._rep.to_sympy().to_list_flat()
157 def _eval_tolist(self):
158 return self._rep.to_sympy().to_list()
160 def _eval_todok(self):
161 return self._rep.to_sympy().to_dok()
163 def _eval_values(self):
164 return list(self.todok().values())
166 def copy(self):
167 return self._fromrep(self._rep.copy())
169 @property
170 def kind(self) -> MatrixKind:
171 domain = self._rep.domain
172 element_kind: Kind
173 if domain in (ZZ, QQ):
174 element_kind = NumberKind
175 elif domain == EXRAW:
176 kinds = {e.kind for e in self.values()}
177 if len(kinds) == 1:
178 [element_kind] = kinds
179 else:
180 element_kind = UndefinedKind
181 else: # pragma: no cover
182 raise RuntimeError("Domain should only be ZZ, QQ or EXRAW")
183 return MatrixKind(element_kind)
185 def _eval_has(self, *patterns):
186 # if the matrix has any zeros, see if S.Zero
187 # has the pattern. If _smat is full length,
188 # the matrix has no zeros.
189 zhas = False
190 dok = self.todok()
191 if len(dok) != self.rows*self.cols:
192 zhas = S.Zero.has(*patterns)
193 return zhas or any(value.has(*patterns) for value in dok.values())
195 def _eval_is_Identity(self):
196 if not all(self[i, i] == 1 for i in range(self.rows)):
197 return False
198 return len(self.todok()) == self.rows
200 def _eval_is_symmetric(self, simpfunc):
201 diff = (self - self.T).applyfunc(simpfunc)
202 return len(diff.values()) == 0
204 def _eval_transpose(self):
205 """Returns the transposed SparseMatrix of this SparseMatrix.
207 Examples
208 ========
210 >>> from sympy import SparseMatrix
211 >>> a = SparseMatrix(((1, 2), (3, 4)))
212 >>> a
213 Matrix([
214 [1, 2],
215 [3, 4]])
216 >>> a.T
217 Matrix([
218 [1, 3],
219 [2, 4]])
220 """
221 return self._fromrep(self._rep.transpose())
223 def _eval_col_join(self, other):
224 return self._fromrep(self._rep.vstack(other._rep))
226 def _eval_row_join(self, other):
227 return self._fromrep(self._rep.hstack(other._rep))
229 def _eval_extract(self, rowsList, colsList):
230 return self._fromrep(self._rep.extract(rowsList, colsList))
232 def __getitem__(self, key):
233 return _getitem_RepMatrix(self, key)
235 @classmethod
236 def _eval_zeros(cls, rows, cols):
237 rep = DomainMatrix.zeros((rows, cols), ZZ)
238 return cls._fromrep(rep)
240 @classmethod
241 def _eval_eye(cls, rows, cols):
242 rep = DomainMatrix.eye((rows, cols), ZZ)
243 return cls._fromrep(rep)
245 def _eval_add(self, other):
246 return classof(self, other)._fromrep(self._rep + other._rep)
248 def _eval_matrix_mul(self, other):
249 return classof(self, other)._fromrep(self._rep * other._rep)
251 def _eval_matrix_mul_elementwise(self, other):
252 selfrep, otherrep = self._rep.unify(other._rep)
253 newrep = selfrep.mul_elementwise(otherrep)
254 return classof(self, other)._fromrep(newrep)
256 def _eval_scalar_mul(self, other):
257 rep, other = self._unify_element_sympy(self._rep, other)
258 return self._fromrep(rep.scalarmul(other))
260 def _eval_scalar_rmul(self, other):
261 rep, other = self._unify_element_sympy(self._rep, other)
262 return self._fromrep(rep.rscalarmul(other))
264 def _eval_Abs(self):
265 return self._fromrep(self._rep.applyfunc(abs))
267 def _eval_conjugate(self):
268 rep = self._rep
269 domain = rep.domain
270 if domain in (ZZ, QQ):
271 return self.copy()
272 else:
273 return self._fromrep(rep.applyfunc(lambda e: e.conjugate()))
275 def equals(self, other, failing_expression=False):
276 """Applies ``equals`` to corresponding elements of the matrices,
277 trying to prove that the elements are equivalent, returning True
278 if they are, False if any pair is not, and None (or the first
279 failing expression if failing_expression is True) if it cannot
280 be decided if the expressions are equivalent or not. This is, in
281 general, an expensive operation.
283 Examples
284 ========
286 >>> from sympy import Matrix
287 >>> from sympy.abc import x
288 >>> A = Matrix([x*(x - 1), 0])
289 >>> B = Matrix([x**2 - x, 0])
290 >>> A == B
291 False
292 >>> A.simplify() == B.simplify()
293 True
294 >>> A.equals(B)
295 True
296 >>> A.equals(2)
297 False
299 See Also
300 ========
301 sympy.core.expr.Expr.equals
302 """
303 if self.shape != getattr(other, 'shape', None):
304 return False
306 rv = True
307 for i in range(self.rows):
308 for j in range(self.cols):
309 ans = self[i, j].equals(other[i, j], failing_expression)
310 if ans is False:
311 return False
312 elif ans is not True and rv is True:
313 rv = ans
314 return rv
317class MutableRepMatrix(RepMatrix):
318 """Mutable matrix based on DomainMatrix as the internal representation"""
320 #
321 # MutableRepMatrix is a subclass of RepMatrix that adds/overrides methods
322 # to make the instances mutable. MutableRepMatrix is a superclass for both
323 # MutableDenseMatrix and MutableSparseMatrix.
324 #
326 is_zero = False
328 def __new__(cls, *args, **kwargs):
329 return cls._new(*args, **kwargs)
331 @classmethod
332 def _new(cls, *args, copy=True, **kwargs):
333 if copy is False:
334 # The input was rows, cols, [list].
335 # It should be used directly without creating a copy.
336 if len(args) != 3:
337 raise TypeError("'copy=False' requires a matrix be initialized as rows,cols,[list]")
338 rows, cols, flat_list = args
339 else:
340 rows, cols, flat_list = cls._handle_creation_inputs(*args, **kwargs)
341 flat_list = list(flat_list) # create a shallow copy
343 rep = cls._flat_list_to_DomainMatrix(rows, cols, flat_list)
345 return cls._fromrep(rep)
347 @classmethod
348 def _fromrep(cls, rep):
349 obj = super().__new__(cls)
350 obj.rows, obj.cols = rep.shape
351 obj._rep = rep
352 return obj
354 def copy(self):
355 return self._fromrep(self._rep.copy())
357 def as_mutable(self):
358 return self.copy()
360 def __setitem__(self, key, value):
361 """
363 Examples
364 ========
366 >>> from sympy import Matrix, I, zeros, ones
367 >>> m = Matrix(((1, 2+I), (3, 4)))
368 >>> m
369 Matrix([
370 [1, 2 + I],
371 [3, 4]])
372 >>> m[1, 0] = 9
373 >>> m
374 Matrix([
375 [1, 2 + I],
376 [9, 4]])
377 >>> m[1, 0] = [[0, 1]]
379 To replace row r you assign to position r*m where m
380 is the number of columns:
382 >>> M = zeros(4)
383 >>> m = M.cols
384 >>> M[3*m] = ones(1, m)*2; M
385 Matrix([
386 [0, 0, 0, 0],
387 [0, 0, 0, 0],
388 [0, 0, 0, 0],
389 [2, 2, 2, 2]])
391 And to replace column c you can assign to position c:
393 >>> M[2] = ones(m, 1)*4; M
394 Matrix([
395 [0, 0, 4, 0],
396 [0, 0, 4, 0],
397 [0, 0, 4, 0],
398 [2, 2, 4, 2]])
399 """
400 rv = self._setitem(key, value)
401 if rv is not None:
402 i, j, value = rv
403 self._rep, value = self._unify_element_sympy(self._rep, value)
404 self._rep.rep.setitem(i, j, value)
406 def _eval_col_del(self, col):
407 self._rep = DomainMatrix.hstack(self._rep[:,:col], self._rep[:,col+1:])
408 self.cols -= 1
410 def _eval_row_del(self, row):
411 self._rep = DomainMatrix.vstack(self._rep[:row,:], self._rep[row+1:, :])
412 self.rows -= 1
414 def _eval_col_insert(self, col, other):
415 other = self._new(other)
416 return self.hstack(self[:,:col], other, self[:,col:])
418 def _eval_row_insert(self, row, other):
419 other = self._new(other)
420 return self.vstack(self[:row,:], other, self[row:,:])
422 def col_op(self, j, f):
423 """In-place operation on col j using two-arg functor whose args are
424 interpreted as (self[i, j], i).
426 Examples
427 ========
429 >>> from sympy import eye
430 >>> M = eye(3)
431 >>> M.col_op(1, lambda v, i: v + 2*M[i, 0]); M
432 Matrix([
433 [1, 2, 0],
434 [0, 1, 0],
435 [0, 0, 1]])
437 See Also
438 ========
439 col
440 row_op
441 """
442 for i in range(self.rows):
443 self[i, j] = f(self[i, j], i)
445 def col_swap(self, i, j):
446 """Swap the two given columns of the matrix in-place.
448 Examples
449 ========
451 >>> from sympy import Matrix
452 >>> M = Matrix([[1, 0], [1, 0]])
453 >>> M
454 Matrix([
455 [1, 0],
456 [1, 0]])
457 >>> M.col_swap(0, 1)
458 >>> M
459 Matrix([
460 [0, 1],
461 [0, 1]])
463 See Also
464 ========
466 col
467 row_swap
468 """
469 for k in range(0, self.rows):
470 self[k, i], self[k, j] = self[k, j], self[k, i]
472 def row_op(self, i, f):
473 """In-place operation on row ``i`` using two-arg functor whose args are
474 interpreted as ``(self[i, j], j)``.
476 Examples
477 ========
479 >>> from sympy import eye
480 >>> M = eye(3)
481 >>> M.row_op(1, lambda v, j: v + 2*M[0, j]); M
482 Matrix([
483 [1, 0, 0],
484 [2, 1, 0],
485 [0, 0, 1]])
487 See Also
488 ========
489 row
490 zip_row_op
491 col_op
493 """
494 for j in range(self.cols):
495 self[i, j] = f(self[i, j], j)
497 def row_swap(self, i, j):
498 """Swap the two given rows of the matrix in-place.
500 Examples
501 ========
503 >>> from sympy import Matrix
504 >>> M = Matrix([[0, 1], [1, 0]])
505 >>> M
506 Matrix([
507 [0, 1],
508 [1, 0]])
509 >>> M.row_swap(0, 1)
510 >>> M
511 Matrix([
512 [1, 0],
513 [0, 1]])
515 See Also
516 ========
518 row
519 col_swap
520 """
521 for k in range(0, self.cols):
522 self[i, k], self[j, k] = self[j, k], self[i, k]
524 def zip_row_op(self, i, k, f):
525 """In-place operation on row ``i`` using two-arg functor whose args are
526 interpreted as ``(self[i, j], self[k, j])``.
528 Examples
529 ========
531 >>> from sympy import eye
532 >>> M = eye(3)
533 >>> M.zip_row_op(1, 0, lambda v, u: v + 2*u); M
534 Matrix([
535 [1, 0, 0],
536 [2, 1, 0],
537 [0, 0, 1]])
539 See Also
540 ========
541 row
542 row_op
543 col_op
545 """
546 for j in range(self.cols):
547 self[i, j] = f(self[i, j], self[k, j])
549 def copyin_list(self, key, value):
550 """Copy in elements from a list.
552 Parameters
553 ==========
555 key : slice
556 The section of this matrix to replace.
557 value : iterable
558 The iterable to copy values from.
560 Examples
561 ========
563 >>> from sympy import eye
564 >>> I = eye(3)
565 >>> I[:2, 0] = [1, 2] # col
566 >>> I
567 Matrix([
568 [1, 0, 0],
569 [2, 1, 0],
570 [0, 0, 1]])
571 >>> I[1, :2] = [[3, 4]]
572 >>> I
573 Matrix([
574 [1, 0, 0],
575 [3, 4, 0],
576 [0, 0, 1]])
578 See Also
579 ========
581 copyin_matrix
582 """
583 if not is_sequence(value):
584 raise TypeError("`value` must be an ordered iterable, not %s." % type(value))
585 return self.copyin_matrix(key, type(self)(value))
587 def copyin_matrix(self, key, value):
588 """Copy in values from a matrix into the given bounds.
590 Parameters
591 ==========
593 key : slice
594 The section of this matrix to replace.
595 value : Matrix
596 The matrix to copy values from.
598 Examples
599 ========
601 >>> from sympy import Matrix, eye
602 >>> M = Matrix([[0, 1], [2, 3], [4, 5]])
603 >>> I = eye(3)
604 >>> I[:3, :2] = M
605 >>> I
606 Matrix([
607 [0, 1, 0],
608 [2, 3, 0],
609 [4, 5, 1]])
610 >>> I[0, 1] = M
611 >>> I
612 Matrix([
613 [0, 0, 1],
614 [2, 2, 3],
615 [4, 4, 5]])
617 See Also
618 ========
620 copyin_list
621 """
622 rlo, rhi, clo, chi = self.key2bounds(key)
623 shape = value.shape
624 dr, dc = rhi - rlo, chi - clo
625 if shape != (dr, dc):
626 raise ShapeError(filldedent("The Matrix `value` doesn't have the "
627 "same dimensions "
628 "as the in sub-Matrix given by `key`."))
630 for i in range(value.rows):
631 for j in range(value.cols):
632 self[i + rlo, j + clo] = value[i, j]
634 def fill(self, value):
635 """Fill self with the given value.
637 Notes
638 =====
640 Unless many values are going to be deleted (i.e. set to zero)
641 this will create a matrix that is slower than a dense matrix in
642 operations.
644 Examples
645 ========
647 >>> from sympy import SparseMatrix
648 >>> M = SparseMatrix.zeros(3); M
649 Matrix([
650 [0, 0, 0],
651 [0, 0, 0],
652 [0, 0, 0]])
653 >>> M.fill(1); M
654 Matrix([
655 [1, 1, 1],
656 [1, 1, 1],
657 [1, 1, 1]])
659 See Also
660 ========
662 zeros
663 ones
664 """
665 value = _sympify(value)
666 if not value:
667 self._rep = DomainMatrix.zeros(self.shape, EXRAW)
668 else:
669 elements_dod = {i: {j: value for j in range(self.cols)} for i in range(self.rows)}
670 self._rep = DomainMatrix(elements_dod, self.shape, EXRAW)
673def _getitem_RepMatrix(self, key):
674 """Return portion of self defined by key. If the key involves a slice
675 then a list will be returned (if key is a single slice) or a matrix
676 (if key was a tuple involving a slice).
678 Examples
679 ========
681 >>> from sympy import Matrix, I
682 >>> m = Matrix([
683 ... [1, 2 + I],
684 ... [3, 4 ]])
686 If the key is a tuple that does not involve a slice then that element
687 is returned:
689 >>> m[1, 0]
690 3
692 When a tuple key involves a slice, a matrix is returned. Here, the
693 first column is selected (all rows, column 0):
695 >>> m[:, 0]
696 Matrix([
697 [1],
698 [3]])
700 If the slice is not a tuple then it selects from the underlying
701 list of elements that are arranged in row order and a list is
702 returned if a slice is involved:
704 >>> m[0]
705 1
706 >>> m[::2]
707 [1, 3]
708 """
709 if isinstance(key, tuple):
710 i, j = key
711 try:
712 return self._rep.getitem_sympy(index_(i), index_(j))
713 except (TypeError, IndexError):
714 if (isinstance(i, Expr) and not i.is_number) or (isinstance(j, Expr) and not j.is_number):
715 if ((j < 0) is True) or ((j >= self.shape[1]) is True) or\
716 ((i < 0) is True) or ((i >= self.shape[0]) is True):
717 raise ValueError("index out of boundary")
718 from sympy.matrices.expressions.matexpr import MatrixElement
719 return MatrixElement(self, i, j)
721 if isinstance(i, slice):
722 i = range(self.rows)[i]
723 elif is_sequence(i):
724 pass
725 else:
726 i = [i]
727 if isinstance(j, slice):
728 j = range(self.cols)[j]
729 elif is_sequence(j):
730 pass
731 else:
732 j = [j]
733 return self.extract(i, j)
735 else:
736 # Index/slice like a flattened list
737 rows, cols = self.shape
739 # Raise the appropriate exception:
740 if not rows * cols:
741 return [][key]
743 rep = self._rep.rep
744 domain = rep.domain
745 is_slice = isinstance(key, slice)
747 if is_slice:
748 values = [rep.getitem(*divmod(n, cols)) for n in range(rows * cols)[key]]
749 else:
750 values = [rep.getitem(*divmod(index_(key), cols))]
752 if domain != EXRAW:
753 to_sympy = domain.to_sympy
754 values = [to_sympy(val) for val in values]
756 if is_slice:
757 return values
758 else:
759 return values[0]