Coverage for /usr/lib/python3/dist-packages/sympy/matrices/expressions/matexpr.py: 39%
511 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 __future__ import annotations
2from functools import wraps
4from sympy.core import S, Integer, Basic, Mul, Add
5from sympy.core.assumptions import check_assumptions
6from sympy.core.decorators import call_highest_priority
7from sympy.core.expr import Expr, ExprBuilder
8from sympy.core.logic import FuzzyBool
9from sympy.core.symbol import Str, Dummy, symbols, Symbol
10from sympy.core.sympify import SympifyError, _sympify
11from sympy.external.gmpy import SYMPY_INTS
12from sympy.functions import conjugate, adjoint
13from sympy.functions.special.tensor_functions import KroneckerDelta
14from sympy.matrices.common import NonSquareMatrixError
15from sympy.matrices.matrices import MatrixKind, MatrixBase
16from sympy.multipledispatch import dispatch
17from sympy.utilities.misc import filldedent
20def _sympifyit(arg, retval=None):
21 # This version of _sympifyit sympifies MutableMatrix objects
22 def deco(func):
23 @wraps(func)
24 def __sympifyit_wrapper(a, b):
25 try:
26 b = _sympify(b)
27 return func(a, b)
28 except SympifyError:
29 return retval
31 return __sympifyit_wrapper
33 return deco
36class MatrixExpr(Expr):
37 """Superclass for Matrix Expressions
39 MatrixExprs represent abstract matrices, linear transformations represented
40 within a particular basis.
42 Examples
43 ========
45 >>> from sympy import MatrixSymbol
46 >>> A = MatrixSymbol('A', 3, 3)
47 >>> y = MatrixSymbol('y', 3, 1)
48 >>> x = (A.T*A).I * A * y
50 See Also
51 ========
53 MatrixSymbol, MatAdd, MatMul, Transpose, Inverse
54 """
55 __slots__: tuple[str, ...] = ()
57 # Should not be considered iterable by the
58 # sympy.utilities.iterables.iterable function. Subclass that actually are
59 # iterable (i.e., explicit matrices) should set this to True.
60 _iterable = False
62 _op_priority = 11.0
64 is_Matrix: bool = True
65 is_MatrixExpr: bool = True
66 is_Identity: FuzzyBool = None
67 is_Inverse = False
68 is_Transpose = False
69 is_ZeroMatrix = False
70 is_MatAdd = False
71 is_MatMul = False
73 is_commutative = False
74 is_number = False
75 is_symbol = False
76 is_scalar = False
78 kind: MatrixKind = MatrixKind()
80 def __new__(cls, *args, **kwargs):
81 args = map(_sympify, args)
82 return Basic.__new__(cls, *args, **kwargs)
84 # The following is adapted from the core Expr object
86 @property
87 def shape(self) -> tuple[Expr, Expr]:
88 raise NotImplementedError
90 @property
91 def _add_handler(self):
92 return MatAdd
94 @property
95 def _mul_handler(self):
96 return MatMul
98 def __neg__(self):
99 return MatMul(S.NegativeOne, self).doit()
101 def __abs__(self):
102 raise NotImplementedError
104 @_sympifyit('other', NotImplemented)
105 @call_highest_priority('__radd__')
106 def __add__(self, other):
107 return MatAdd(self, other).doit()
109 @_sympifyit('other', NotImplemented)
110 @call_highest_priority('__add__')
111 def __radd__(self, other):
112 return MatAdd(other, self).doit()
114 @_sympifyit('other', NotImplemented)
115 @call_highest_priority('__rsub__')
116 def __sub__(self, other):
117 return MatAdd(self, -other).doit()
119 @_sympifyit('other', NotImplemented)
120 @call_highest_priority('__sub__')
121 def __rsub__(self, other):
122 return MatAdd(other, -self).doit()
124 @_sympifyit('other', NotImplemented)
125 @call_highest_priority('__rmul__')
126 def __mul__(self, other):
127 return MatMul(self, other).doit()
129 @_sympifyit('other', NotImplemented)
130 @call_highest_priority('__rmul__')
131 def __matmul__(self, other):
132 return MatMul(self, other).doit()
134 @_sympifyit('other', NotImplemented)
135 @call_highest_priority('__mul__')
136 def __rmul__(self, other):
137 return MatMul(other, self).doit()
139 @_sympifyit('other', NotImplemented)
140 @call_highest_priority('__mul__')
141 def __rmatmul__(self, other):
142 return MatMul(other, self).doit()
144 @_sympifyit('other', NotImplemented)
145 @call_highest_priority('__rpow__')
146 def __pow__(self, other):
147 return MatPow(self, other).doit()
149 @_sympifyit('other', NotImplemented)
150 @call_highest_priority('__pow__')
151 def __rpow__(self, other):
152 raise NotImplementedError("Matrix Power not defined")
154 @_sympifyit('other', NotImplemented)
155 @call_highest_priority('__rtruediv__')
156 def __truediv__(self, other):
157 return self * other**S.NegativeOne
159 @_sympifyit('other', NotImplemented)
160 @call_highest_priority('__truediv__')
161 def __rtruediv__(self, other):
162 raise NotImplementedError()
163 #return MatMul(other, Pow(self, S.NegativeOne))
165 @property
166 def rows(self):
167 return self.shape[0]
169 @property
170 def cols(self):
171 return self.shape[1]
173 @property
174 def is_square(self) -> bool | None:
175 rows, cols = self.shape
176 if isinstance(rows, Integer) and isinstance(cols, Integer):
177 return rows == cols
178 if rows == cols:
179 return True
180 return None
182 def _eval_conjugate(self):
183 from sympy.matrices.expressions.adjoint import Adjoint
184 return Adjoint(Transpose(self))
186 def as_real_imag(self, deep=True, **hints):
187 return self._eval_as_real_imag()
189 def _eval_as_real_imag(self):
190 real = S.Half * (self + self._eval_conjugate())
191 im = (self - self._eval_conjugate())/(2*S.ImaginaryUnit)
192 return (real, im)
194 def _eval_inverse(self):
195 return Inverse(self)
197 def _eval_determinant(self):
198 return Determinant(self)
200 def _eval_transpose(self):
201 return Transpose(self)
203 def _eval_power(self, exp):
204 """
205 Override this in sub-classes to implement simplification of powers. The cases where the exponent
206 is -1, 0, 1 are already covered in MatPow.doit(), so implementations can exclude these cases.
207 """
208 return MatPow(self, exp)
210 def _eval_simplify(self, **kwargs):
211 if self.is_Atom:
212 return self
213 else:
214 from sympy.simplify import simplify
215 return self.func(*[simplify(x, **kwargs) for x in self.args])
217 def _eval_adjoint(self):
218 from sympy.matrices.expressions.adjoint import Adjoint
219 return Adjoint(self)
221 def _eval_derivative_n_times(self, x, n):
222 return Basic._eval_derivative_n_times(self, x, n)
224 def _eval_derivative(self, x):
225 # `x` is a scalar:
226 if self.has(x):
227 # See if there are other methods using it:
228 return super()._eval_derivative(x)
229 else:
230 return ZeroMatrix(*self.shape)
232 @classmethod
233 def _check_dim(cls, dim):
234 """Helper function to check invalid matrix dimensions"""
235 ok = check_assumptions(dim, integer=True, nonnegative=True)
236 if ok is False:
237 raise ValueError(
238 "The dimension specification {} should be "
239 "a nonnegative integer.".format(dim))
242 def _entry(self, i, j, **kwargs):
243 raise NotImplementedError(
244 "Indexing not implemented for %s" % self.__class__.__name__)
246 def adjoint(self):
247 return adjoint(self)
249 def as_coeff_Mul(self, rational=False):
250 """Efficiently extract the coefficient of a product."""
251 return S.One, self
253 def conjugate(self):
254 return conjugate(self)
256 def transpose(self):
257 from sympy.matrices.expressions.transpose import transpose
258 return transpose(self)
260 @property
261 def T(self):
262 '''Matrix transposition'''
263 return self.transpose()
265 def inverse(self):
266 if self.is_square is False:
267 raise NonSquareMatrixError('Inverse of non-square matrix')
268 return self._eval_inverse()
270 def inv(self):
271 return self.inverse()
273 def det(self):
274 from sympy.matrices.expressions.determinant import det
275 return det(self)
277 @property
278 def I(self):
279 return self.inverse()
281 def valid_index(self, i, j):
282 def is_valid(idx):
283 return isinstance(idx, (int, Integer, Symbol, Expr))
284 return (is_valid(i) and is_valid(j) and
285 (self.rows is None or
286 (i >= -self.rows) != False and (i < self.rows) != False) and
287 (j >= -self.cols) != False and (j < self.cols) != False)
289 def __getitem__(self, key):
290 if not isinstance(key, tuple) and isinstance(key, slice):
291 from sympy.matrices.expressions.slice import MatrixSlice
292 return MatrixSlice(self, key, (0, None, 1))
293 if isinstance(key, tuple) and len(key) == 2:
294 i, j = key
295 if isinstance(i, slice) or isinstance(j, slice):
296 from sympy.matrices.expressions.slice import MatrixSlice
297 return MatrixSlice(self, i, j)
298 i, j = _sympify(i), _sympify(j)
299 if self.valid_index(i, j) != False:
300 return self._entry(i, j)
301 else:
302 raise IndexError("Invalid indices (%s, %s)" % (i, j))
303 elif isinstance(key, (SYMPY_INTS, Integer)):
304 # row-wise decomposition of matrix
305 rows, cols = self.shape
306 # allow single indexing if number of columns is known
307 if not isinstance(cols, Integer):
308 raise IndexError(filldedent('''
309 Single indexing is only supported when the number
310 of columns is known.'''))
311 key = _sympify(key)
312 i = key // cols
313 j = key % cols
314 if self.valid_index(i, j) != False:
315 return self._entry(i, j)
316 else:
317 raise IndexError("Invalid index %s" % key)
318 elif isinstance(key, (Symbol, Expr)):
319 raise IndexError(filldedent('''
320 Only integers may be used when addressing the matrix
321 with a single index.'''))
322 raise IndexError("Invalid index, wanted %s[i,j]" % self)
324 def _is_shape_symbolic(self) -> bool:
325 return (not isinstance(self.rows, (SYMPY_INTS, Integer))
326 or not isinstance(self.cols, (SYMPY_INTS, Integer)))
328 def as_explicit(self):
329 """
330 Returns a dense Matrix with elements represented explicitly
332 Returns an object of type ImmutableDenseMatrix.
334 Examples
335 ========
337 >>> from sympy import Identity
338 >>> I = Identity(3)
339 >>> I
340 I
341 >>> I.as_explicit()
342 Matrix([
343 [1, 0, 0],
344 [0, 1, 0],
345 [0, 0, 1]])
347 See Also
348 ========
349 as_mutable: returns mutable Matrix type
351 """
352 if self._is_shape_symbolic():
353 raise ValueError(
354 'Matrix with symbolic shape '
355 'cannot be represented explicitly.')
356 from sympy.matrices.immutable import ImmutableDenseMatrix
357 return ImmutableDenseMatrix([[self[i, j]
358 for j in range(self.cols)]
359 for i in range(self.rows)])
361 def as_mutable(self):
362 """
363 Returns a dense, mutable matrix with elements represented explicitly
365 Examples
366 ========
368 >>> from sympy import Identity
369 >>> I = Identity(3)
370 >>> I
371 I
372 >>> I.shape
373 (3, 3)
374 >>> I.as_mutable()
375 Matrix([
376 [1, 0, 0],
377 [0, 1, 0],
378 [0, 0, 1]])
380 See Also
381 ========
382 as_explicit: returns ImmutableDenseMatrix
383 """
384 return self.as_explicit().as_mutable()
386 def __array__(self):
387 from numpy import empty
388 a = empty(self.shape, dtype=object)
389 for i in range(self.rows):
390 for j in range(self.cols):
391 a[i, j] = self[i, j]
392 return a
394 def equals(self, other):
395 """
396 Test elementwise equality between matrices, potentially of different
397 types
399 >>> from sympy import Identity, eye
400 >>> Identity(3).equals(eye(3))
401 True
402 """
403 return self.as_explicit().equals(other)
405 def canonicalize(self):
406 return self
408 def as_coeff_mmul(self):
409 return S.One, MatMul(self)
411 @staticmethod
412 def from_index_summation(expr, first_index=None, last_index=None, dimensions=None):
413 r"""
414 Parse expression of matrices with explicitly summed indices into a
415 matrix expression without indices, if possible.
417 This transformation expressed in mathematical notation:
419 `\sum_{j=0}^{N-1} A_{i,j} B_{j,k} \Longrightarrow \mathbf{A}\cdot \mathbf{B}`
421 Optional parameter ``first_index``: specify which free index to use as
422 the index starting the expression.
424 Examples
425 ========
427 >>> from sympy import MatrixSymbol, MatrixExpr, Sum
428 >>> from sympy.abc import i, j, k, l, N
429 >>> A = MatrixSymbol("A", N, N)
430 >>> B = MatrixSymbol("B", N, N)
431 >>> expr = Sum(A[i, j]*B[j, k], (j, 0, N-1))
432 >>> MatrixExpr.from_index_summation(expr)
433 A*B
435 Transposition is detected:
437 >>> expr = Sum(A[j, i]*B[j, k], (j, 0, N-1))
438 >>> MatrixExpr.from_index_summation(expr)
439 A.T*B
441 Detect the trace:
443 >>> expr = Sum(A[i, i], (i, 0, N-1))
444 >>> MatrixExpr.from_index_summation(expr)
445 Trace(A)
447 More complicated expressions:
449 >>> expr = Sum(A[i, j]*B[k, j]*A[l, k], (j, 0, N-1), (k, 0, N-1))
450 >>> MatrixExpr.from_index_summation(expr)
451 A*B.T*A.T
452 """
453 from sympy.tensor.array.expressions.from_indexed_to_array import convert_indexed_to_array
454 from sympy.tensor.array.expressions.from_array_to_matrix import convert_array_to_matrix
455 first_indices = []
456 if first_index is not None:
457 first_indices.append(first_index)
458 if last_index is not None:
459 first_indices.append(last_index)
460 arr = convert_indexed_to_array(expr, first_indices=first_indices)
461 return convert_array_to_matrix(arr)
463 def applyfunc(self, func):
464 from .applyfunc import ElementwiseApplyFunction
465 return ElementwiseApplyFunction(func, self)
468@dispatch(MatrixExpr, Expr)
469def _eval_is_eq(lhs, rhs): # noqa:F811
470 return False
472@dispatch(MatrixExpr, MatrixExpr) # type: ignore
473def _eval_is_eq(lhs, rhs): # noqa:F811
474 if lhs.shape != rhs.shape:
475 return False
476 if (lhs - rhs).is_ZeroMatrix:
477 return True
479def get_postprocessor(cls):
480 def _postprocessor(expr):
481 # To avoid circular imports, we can't have MatMul/MatAdd on the top level
482 mat_class = {Mul: MatMul, Add: MatAdd}[cls]
483 nonmatrices = []
484 matrices = []
485 for term in expr.args:
486 if isinstance(term, MatrixExpr):
487 matrices.append(term)
488 else:
489 nonmatrices.append(term)
491 if not matrices:
492 return cls._from_args(nonmatrices)
494 if nonmatrices:
495 if cls == Mul:
496 for i in range(len(matrices)):
497 if not matrices[i].is_MatrixExpr:
498 # If one of the matrices explicit, absorb the scalar into it
499 # (doit will combine all explicit matrices into one, so it
500 # doesn't matter which)
501 matrices[i] = matrices[i].__mul__(cls._from_args(nonmatrices))
502 nonmatrices = []
503 break
505 else:
506 # Maintain the ability to create Add(scalar, matrix) without
507 # raising an exception. That way different algorithms can
508 # replace matrix expressions with non-commutative symbols to
509 # manipulate them like non-commutative scalars.
510 return cls._from_args(nonmatrices + [mat_class(*matrices).doit(deep=False)])
512 if mat_class == MatAdd:
513 return mat_class(*matrices).doit(deep=False)
514 return mat_class(cls._from_args(nonmatrices), *matrices).doit(deep=False)
515 return _postprocessor
518Basic._constructor_postprocessor_mapping[MatrixExpr] = {
519 "Mul": [get_postprocessor(Mul)],
520 "Add": [get_postprocessor(Add)],
521}
524def _matrix_derivative(expr, x, old_algorithm=False):
526 if isinstance(expr, MatrixBase) or isinstance(x, MatrixBase):
527 # Do not use array expressions for explicit matrices:
528 old_algorithm = True
530 if old_algorithm:
531 return _matrix_derivative_old_algorithm(expr, x)
533 from sympy.tensor.array.expressions.from_matrix_to_array import convert_matrix_to_array
534 from sympy.tensor.array.expressions.arrayexpr_derivatives import array_derive
535 from sympy.tensor.array.expressions.from_array_to_matrix import convert_array_to_matrix
537 array_expr = convert_matrix_to_array(expr)
538 diff_array_expr = array_derive(array_expr, x)
539 diff_matrix_expr = convert_array_to_matrix(diff_array_expr)
540 return diff_matrix_expr
543def _matrix_derivative_old_algorithm(expr, x):
544 from sympy.tensor.array.array_derivatives import ArrayDerivative
545 lines = expr._eval_derivative_matrix_lines(x)
547 parts = [i.build() for i in lines]
549 from sympy.tensor.array.expressions.from_array_to_matrix import convert_array_to_matrix
551 parts = [[convert_array_to_matrix(j) for j in i] for i in parts]
553 def _get_shape(elem):
554 if isinstance(elem, MatrixExpr):
555 return elem.shape
556 return 1, 1
558 def get_rank(parts):
559 return sum([j not in (1, None) for i in parts for j in _get_shape(i)])
561 ranks = [get_rank(i) for i in parts]
562 rank = ranks[0]
564 def contract_one_dims(parts):
565 if len(parts) == 1:
566 return parts[0]
567 else:
568 p1, p2 = parts[:2]
569 if p2.is_Matrix:
570 p2 = p2.T
571 if p1 == Identity(1):
572 pbase = p2
573 elif p2 == Identity(1):
574 pbase = p1
575 else:
576 pbase = p1*p2
577 if len(parts) == 2:
578 return pbase
579 else: # len(parts) > 2
580 if pbase.is_Matrix:
581 raise ValueError("")
582 return pbase*Mul.fromiter(parts[2:])
584 if rank <= 2:
585 return Add.fromiter([contract_one_dims(i) for i in parts])
587 return ArrayDerivative(expr, x)
590class MatrixElement(Expr):
591 parent = property(lambda self: self.args[0])
592 i = property(lambda self: self.args[1])
593 j = property(lambda self: self.args[2])
594 _diff_wrt = True
595 is_symbol = True
596 is_commutative = True
598 def __new__(cls, name, n, m):
599 n, m = map(_sympify, (n, m))
600 from sympy.matrices.matrices import MatrixBase
601 if isinstance(name, str):
602 name = Symbol(name)
603 else:
604 if isinstance(name, MatrixBase):
605 if n.is_Integer and m.is_Integer:
606 return name[n, m]
607 name = _sympify(name) # change mutable into immutable
608 else:
609 name = _sympify(name)
610 if not isinstance(name.kind, MatrixKind):
611 raise TypeError("First argument of MatrixElement should be a matrix")
612 if not getattr(name, 'valid_index', lambda n, m: True)(n, m):
613 raise IndexError('indices out of range')
614 obj = Expr.__new__(cls, name, n, m)
615 return obj
617 @property
618 def symbol(self):
619 return self.args[0]
621 def doit(self, **hints):
622 deep = hints.get('deep', True)
623 if deep:
624 args = [arg.doit(**hints) for arg in self.args]
625 else:
626 args = self.args
627 return args[0][args[1], args[2]]
629 @property
630 def indices(self):
631 return self.args[1:]
633 def _eval_derivative(self, v):
635 if not isinstance(v, MatrixElement):
636 from sympy.matrices.matrices import MatrixBase
637 if isinstance(self.parent, MatrixBase):
638 return self.parent.diff(v)[self.i, self.j]
639 return S.Zero
641 M = self.args[0]
643 m, n = self.parent.shape
645 if M == v.args[0]:
646 return KroneckerDelta(self.args[1], v.args[1], (0, m-1)) * \
647 KroneckerDelta(self.args[2], v.args[2], (0, n-1))
649 if isinstance(M, Inverse):
650 from sympy.concrete.summations import Sum
651 i, j = self.args[1:]
652 i1, i2 = symbols("z1, z2", cls=Dummy)
653 Y = M.args[0]
654 r1, r2 = Y.shape
655 return -Sum(M[i, i1]*Y[i1, i2].diff(v)*M[i2, j], (i1, 0, r1-1), (i2, 0, r2-1))
657 if self.has(v.args[0]):
658 return None
660 return S.Zero
663class MatrixSymbol(MatrixExpr):
664 """Symbolic representation of a Matrix object
666 Creates a SymPy Symbol to represent a Matrix. This matrix has a shape and
667 can be included in Matrix Expressions
669 Examples
670 ========
672 >>> from sympy import MatrixSymbol, Identity
673 >>> A = MatrixSymbol('A', 3, 4) # A 3 by 4 Matrix
674 >>> B = MatrixSymbol('B', 4, 3) # A 4 by 3 Matrix
675 >>> A.shape
676 (3, 4)
677 >>> 2*A*B + Identity(3)
678 I + 2*A*B
679 """
680 is_commutative = False
681 is_symbol = True
682 _diff_wrt = True
684 def __new__(cls, name, n, m):
685 n, m = _sympify(n), _sympify(m)
687 cls._check_dim(m)
688 cls._check_dim(n)
690 if isinstance(name, str):
691 name = Str(name)
692 obj = Basic.__new__(cls, name, n, m)
693 return obj
695 @property
696 def shape(self):
697 return self.args[1], self.args[2]
699 @property
700 def name(self):
701 return self.args[0].name
703 def _entry(self, i, j, **kwargs):
704 return MatrixElement(self, i, j)
706 @property
707 def free_symbols(self):
708 return {self}
710 def _eval_simplify(self, **kwargs):
711 return self
713 def _eval_derivative(self, x):
714 # x is a scalar:
715 return ZeroMatrix(self.shape[0], self.shape[1])
717 def _eval_derivative_matrix_lines(self, x):
718 if self != x:
719 first = ZeroMatrix(x.shape[0], self.shape[0]) if self.shape[0] != 1 else S.Zero
720 second = ZeroMatrix(x.shape[1], self.shape[1]) if self.shape[1] != 1 else S.Zero
721 return [_LeftRightArgs(
722 [first, second],
723 )]
724 else:
725 first = Identity(self.shape[0]) if self.shape[0] != 1 else S.One
726 second = Identity(self.shape[1]) if self.shape[1] != 1 else S.One
727 return [_LeftRightArgs(
728 [first, second],
729 )]
732def matrix_symbols(expr):
733 return [sym for sym in expr.free_symbols if sym.is_Matrix]
736class _LeftRightArgs:
737 r"""
738 Helper class to compute matrix derivatives.
740 The logic: when an expression is derived by a matrix `X_{mn}`, two lines of
741 matrix multiplications are created: the one contracted to `m` (first line),
742 and the one contracted to `n` (second line).
744 Transposition flips the side by which new matrices are connected to the
745 lines.
747 The trace connects the end of the two lines.
748 """
750 def __init__(self, lines, higher=S.One):
751 self._lines = list(lines)
752 self._first_pointer_parent = self._lines
753 self._first_pointer_index = 0
754 self._first_line_index = 0
755 self._second_pointer_parent = self._lines
756 self._second_pointer_index = 1
757 self._second_line_index = 1
758 self.higher = higher
760 @property
761 def first_pointer(self):
762 return self._first_pointer_parent[self._first_pointer_index]
764 @first_pointer.setter
765 def first_pointer(self, value):
766 self._first_pointer_parent[self._first_pointer_index] = value
768 @property
769 def second_pointer(self):
770 return self._second_pointer_parent[self._second_pointer_index]
772 @second_pointer.setter
773 def second_pointer(self, value):
774 self._second_pointer_parent[self._second_pointer_index] = value
776 def __repr__(self):
777 built = [self._build(i) for i in self._lines]
778 return "_LeftRightArgs(lines=%s, higher=%s)" % (
779 built,
780 self.higher,
781 )
783 def transpose(self):
784 self._first_pointer_parent, self._second_pointer_parent = self._second_pointer_parent, self._first_pointer_parent
785 self._first_pointer_index, self._second_pointer_index = self._second_pointer_index, self._first_pointer_index
786 self._first_line_index, self._second_line_index = self._second_line_index, self._first_line_index
787 return self
789 @staticmethod
790 def _build(expr):
791 if isinstance(expr, ExprBuilder):
792 return expr.build()
793 if isinstance(expr, list):
794 if len(expr) == 1:
795 return expr[0]
796 else:
797 return expr[0](*[_LeftRightArgs._build(i) for i in expr[1]])
798 else:
799 return expr
801 def build(self):
802 data = [self._build(i) for i in self._lines]
803 if self.higher != 1:
804 data += [self._build(self.higher)]
805 data = list(data)
806 return data
808 def matrix_form(self):
809 if self.first != 1 and self.higher != 1:
810 raise ValueError("higher dimensional array cannot be represented")
812 def _get_shape(elem):
813 if isinstance(elem, MatrixExpr):
814 return elem.shape
815 return (None, None)
817 if _get_shape(self.first)[1] != _get_shape(self.second)[1]:
818 # Remove one-dimensional identity matrices:
819 # (this is needed by `a.diff(a)` where `a` is a vector)
820 if _get_shape(self.second) == (1, 1):
821 return self.first*self.second[0, 0]
822 if _get_shape(self.first) == (1, 1):
823 return self.first[1, 1]*self.second.T
824 raise ValueError("incompatible shapes")
825 if self.first != 1:
826 return self.first*self.second.T
827 else:
828 return self.higher
830 def rank(self):
831 """
832 Number of dimensions different from trivial (warning: not related to
833 matrix rank).
834 """
835 rank = 0
836 if self.first != 1:
837 rank += sum([i != 1 for i in self.first.shape])
838 if self.second != 1:
839 rank += sum([i != 1 for i in self.second.shape])
840 if self.higher != 1:
841 rank += 2
842 return rank
844 def _multiply_pointer(self, pointer, other):
845 from ...tensor.array.expressions.array_expressions import ArrayTensorProduct
846 from ...tensor.array.expressions.array_expressions import ArrayContraction
848 subexpr = ExprBuilder(
849 ArrayContraction,
850 [
851 ExprBuilder(
852 ArrayTensorProduct,
853 [
854 pointer,
855 other
856 ]
857 ),
858 (1, 2)
859 ],
860 validator=ArrayContraction._validate
861 )
863 return subexpr
865 def append_first(self, other):
866 self.first_pointer *= other
868 def append_second(self, other):
869 self.second_pointer *= other
872def _make_matrix(x):
873 from sympy.matrices.immutable import ImmutableDenseMatrix
874 if isinstance(x, MatrixExpr):
875 return x
876 return ImmutableDenseMatrix([[x]])
879from .matmul import MatMul
880from .matadd import MatAdd
881from .matpow import MatPow
882from .transpose import Transpose
883from .inverse import Inverse
884from .special import ZeroMatrix, Identity
885from .determinant import Determinant