Coverage for /usr/lib/python3/dist-packages/sympy/matrices/expressions/matpow.py: 26%

97 statements  

« prev     ^ index     » next       coverage.py v7.9.1, created at 2025-06-14 15:55 +0200

1from .matexpr import MatrixExpr 

2from .special import Identity 

3from sympy.core import S 

4from sympy.core.expr import ExprBuilder 

5from sympy.core.cache import cacheit 

6from sympy.core.power import Pow 

7from sympy.core.sympify import _sympify 

8from sympy.matrices import MatrixBase 

9from sympy.matrices.common import NonSquareMatrixError 

10 

11 

12class MatPow(MatrixExpr): 

13 def __new__(cls, base, exp, evaluate=False, **options): 

14 base = _sympify(base) 

15 if not base.is_Matrix: 

16 raise TypeError("MatPow base should be a matrix") 

17 

18 if base.is_square is False: 

19 raise NonSquareMatrixError("Power of non-square matrix %s" % base) 

20 

21 exp = _sympify(exp) 

22 obj = super().__new__(cls, base, exp) 

23 

24 if evaluate: 

25 obj = obj.doit(deep=False) 

26 

27 return obj 

28 

29 @property 

30 def base(self): 

31 return self.args[0] 

32 

33 @property 

34 def exp(self): 

35 return self.args[1] 

36 

37 @property 

38 def shape(self): 

39 return self.base.shape 

40 

41 @cacheit 

42 def _get_explicit_matrix(self): 

43 return self.base.as_explicit()**self.exp 

44 

45 def _entry(self, i, j, **kwargs): 

46 from sympy.matrices.expressions import MatMul 

47 A = self.doit() 

48 if isinstance(A, MatPow): 

49 # We still have a MatPow, make an explicit MatMul out of it. 

50 if A.exp.is_Integer and A.exp.is_positive: 

51 A = MatMul(*[A.base for k in range(A.exp)]) 

52 elif not self._is_shape_symbolic(): 

53 return A._get_explicit_matrix()[i, j] 

54 else: 

55 # Leave the expression unevaluated: 

56 from sympy.matrices.expressions.matexpr import MatrixElement 

57 return MatrixElement(self, i, j) 

58 return A[i, j] 

59 

60 def doit(self, **hints): 

61 if hints.get('deep', True): 

62 base, exp = (arg.doit(**hints) for arg in self.args) 

63 else: 

64 base, exp = self.args 

65 

66 # combine all powers, e.g. (A ** 2) ** 3 -> A ** 6 

67 while isinstance(base, MatPow): 

68 exp *= base.args[1] 

69 base = base.args[0] 

70 

71 if isinstance(base, MatrixBase): 

72 # Delegate 

73 return base ** exp 

74 

75 # Handle simple cases so that _eval_power() in MatrixExpr sub-classes can ignore them 

76 if exp == S.One: 

77 return base 

78 if exp == S.Zero: 

79 return Identity(base.rows) 

80 if exp == S.NegativeOne: 

81 from sympy.matrices.expressions import Inverse 

82 return Inverse(base).doit(**hints) 

83 

84 eval_power = getattr(base, '_eval_power', None) 

85 if eval_power is not None: 

86 return eval_power(exp) 

87 

88 return MatPow(base, exp) 

89 

90 def _eval_transpose(self): 

91 base, exp = self.args 

92 return MatPow(base.T, exp) 

93 

94 def _eval_derivative(self, x): 

95 return Pow._eval_derivative(self, x) 

96 

97 def _eval_derivative_matrix_lines(self, x): 

98 from sympy.tensor.array.expressions.array_expressions import ArrayContraction 

99 from ...tensor.array.expressions.array_expressions import ArrayTensorProduct 

100 from .matmul import MatMul 

101 from .inverse import Inverse 

102 exp = self.exp 

103 if self.base.shape == (1, 1) and not exp.has(x): 

104 lr = self.base._eval_derivative_matrix_lines(x) 

105 for i in lr: 

106 subexpr = ExprBuilder( 

107 ArrayContraction, 

108 [ 

109 ExprBuilder( 

110 ArrayTensorProduct, 

111 [ 

112 Identity(1), 

113 i._lines[0], 

114 exp*self.base**(exp-1), 

115 i._lines[1], 

116 Identity(1), 

117 ] 

118 ), 

119 (0, 3, 4), (5, 7, 8) 

120 ], 

121 validator=ArrayContraction._validate 

122 ) 

123 i._first_pointer_parent = subexpr.args[0].args 

124 i._first_pointer_index = 0 

125 i._second_pointer_parent = subexpr.args[0].args 

126 i._second_pointer_index = 4 

127 i._lines = [subexpr] 

128 return lr 

129 if (exp > 0) == True: 

130 newexpr = MatMul.fromiter([self.base for i in range(exp)]) 

131 elif (exp == -1) == True: 

132 return Inverse(self.base)._eval_derivative_matrix_lines(x) 

133 elif (exp < 0) == True: 

134 newexpr = MatMul.fromiter([Inverse(self.base) for i in range(-exp)]) 

135 elif (exp == 0) == True: 

136 return self.doit()._eval_derivative_matrix_lines(x) 

137 else: 

138 raise NotImplementedError("cannot evaluate %s derived by %s" % (self, x)) 

139 return newexpr._eval_derivative_matrix_lines(x) 

140 

141 def _eval_inverse(self): 

142 return MatPow(self.base, -self.exp)