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

110 statements  

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

1from sympy.core.sympify import _sympify 

2 

3from sympy.matrices.expressions import MatrixExpr 

4from sympy.core import S, Eq, Ge 

5from sympy.core.mul import Mul 

6from sympy.functions.special.tensor_functions import KroneckerDelta 

7 

8 

9class DiagonalMatrix(MatrixExpr): 

10 """DiagonalMatrix(M) will create a matrix expression that 

11 behaves as though all off-diagonal elements, 

12 `M[i, j]` where `i != j`, are zero. 

13 

14 Examples 

15 ======== 

16 

17 >>> from sympy import MatrixSymbol, DiagonalMatrix, Symbol 

18 >>> n = Symbol('n', integer=True) 

19 >>> m = Symbol('m', integer=True) 

20 >>> D = DiagonalMatrix(MatrixSymbol('x', 2, 3)) 

21 >>> D[1, 2] 

22 0 

23 >>> D[1, 1] 

24 x[1, 1] 

25 

26 The length of the diagonal -- the lesser of the two dimensions of `M` -- 

27 is accessed through the `diagonal_length` property: 

28 

29 >>> D.diagonal_length 

30 2 

31 >>> DiagonalMatrix(MatrixSymbol('x', n + 1, n)).diagonal_length 

32 n 

33 

34 When one of the dimensions is symbolic the other will be treated as 

35 though it is smaller: 

36 

37 >>> tall = DiagonalMatrix(MatrixSymbol('x', n, 3)) 

38 >>> tall.diagonal_length 

39 3 

40 >>> tall[10, 1] 

41 0 

42 

43 When the size of the diagonal is not known, a value of None will 

44 be returned: 

45 

46 >>> DiagonalMatrix(MatrixSymbol('x', n, m)).diagonal_length is None 

47 True 

48 

49 """ 

50 arg = property(lambda self: self.args[0]) 

51 

52 shape = property(lambda self: self.arg.shape) # type:ignore 

53 

54 @property 

55 def diagonal_length(self): 

56 r, c = self.shape 

57 if r.is_Integer and c.is_Integer: 

58 m = min(r, c) 

59 elif r.is_Integer and not c.is_Integer: 

60 m = r 

61 elif c.is_Integer and not r.is_Integer: 

62 m = c 

63 elif r == c: 

64 m = r 

65 else: 

66 try: 

67 m = min(r, c) 

68 except TypeError: 

69 m = None 

70 return m 

71 

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

73 if self.diagonal_length is not None: 

74 if Ge(i, self.diagonal_length) is S.true: 

75 return S.Zero 

76 elif Ge(j, self.diagonal_length) is S.true: 

77 return S.Zero 

78 eq = Eq(i, j) 

79 if eq is S.true: 

80 return self.arg[i, i] 

81 elif eq is S.false: 

82 return S.Zero 

83 return self.arg[i, j]*KroneckerDelta(i, j) 

84 

85 

86class DiagonalOf(MatrixExpr): 

87 """DiagonalOf(M) will create a matrix expression that 

88 is equivalent to the diagonal of `M`, represented as 

89 a single column matrix. 

90 

91 Examples 

92 ======== 

93 

94 >>> from sympy import MatrixSymbol, DiagonalOf, Symbol 

95 >>> n = Symbol('n', integer=True) 

96 >>> m = Symbol('m', integer=True) 

97 >>> x = MatrixSymbol('x', 2, 3) 

98 >>> diag = DiagonalOf(x) 

99 >>> diag.shape 

100 (2, 1) 

101 

102 The diagonal can be addressed like a matrix or vector and will 

103 return the corresponding element of the original matrix: 

104 

105 >>> diag[1, 0] == diag[1] == x[1, 1] 

106 True 

107 

108 The length of the diagonal -- the lesser of the two dimensions of `M` -- 

109 is accessed through the `diagonal_length` property: 

110 

111 >>> diag.diagonal_length 

112 2 

113 >>> DiagonalOf(MatrixSymbol('x', n + 1, n)).diagonal_length 

114 n 

115 

116 When only one of the dimensions is symbolic the other will be 

117 treated as though it is smaller: 

118 

119 >>> dtall = DiagonalOf(MatrixSymbol('x', n, 3)) 

120 >>> dtall.diagonal_length 

121 3 

122 

123 When the size of the diagonal is not known, a value of None will 

124 be returned: 

125 

126 >>> DiagonalOf(MatrixSymbol('x', n, m)).diagonal_length is None 

127 True 

128 

129 """ 

130 arg = property(lambda self: self.args[0]) 

131 @property 

132 def shape(self): 

133 r, c = self.arg.shape 

134 if r.is_Integer and c.is_Integer: 

135 m = min(r, c) 

136 elif r.is_Integer and not c.is_Integer: 

137 m = r 

138 elif c.is_Integer and not r.is_Integer: 

139 m = c 

140 elif r == c: 

141 m = r 

142 else: 

143 try: 

144 m = min(r, c) 

145 except TypeError: 

146 m = None 

147 return m, S.One 

148 

149 @property 

150 def diagonal_length(self): 

151 return self.shape[0] 

152 

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

154 return self.arg._entry(i, i, **kwargs) 

155 

156 

157class DiagMatrix(MatrixExpr): 

158 """ 

159 Turn a vector into a diagonal matrix. 

160 """ 

161 def __new__(cls, vector): 

162 vector = _sympify(vector) 

163 obj = MatrixExpr.__new__(cls, vector) 

164 shape = vector.shape 

165 dim = shape[1] if shape[0] == 1 else shape[0] 

166 if vector.shape[0] != 1: 

167 obj._iscolumn = True 

168 else: 

169 obj._iscolumn = False 

170 obj._shape = (dim, dim) 

171 obj._vector = vector 

172 return obj 

173 

174 @property 

175 def shape(self): 

176 return self._shape 

177 

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

179 if self._iscolumn: 

180 result = self._vector._entry(i, 0, **kwargs) 

181 else: 

182 result = self._vector._entry(0, j, **kwargs) 

183 if i != j: 

184 result *= KroneckerDelta(i, j) 

185 return result 

186 

187 def _eval_transpose(self): 

188 return self 

189 

190 def as_explicit(self): 

191 from sympy.matrices.dense import diag 

192 return diag(*list(self._vector.as_explicit())) 

193 

194 def doit(self, **hints): 

195 from sympy.assumptions import ask, Q 

196 from sympy.matrices.expressions.matmul import MatMul 

197 from sympy.matrices.expressions.transpose import Transpose 

198 from sympy.matrices.dense import eye 

199 from sympy.matrices.matrices import MatrixBase 

200 vector = self._vector 

201 # This accounts for shape (1, 1) and identity matrices, among others: 

202 if ask(Q.diagonal(vector)): 

203 return vector 

204 if isinstance(vector, MatrixBase): 

205 ret = eye(max(vector.shape)) 

206 for i in range(ret.shape[0]): 

207 ret[i, i] = vector[i] 

208 return type(vector)(ret) 

209 if vector.is_MatMul: 

210 matrices = [arg for arg in vector.args if arg.is_Matrix] 

211 scalars = [arg for arg in vector.args if arg not in matrices] 

212 if scalars: 

213 return Mul.fromiter(scalars)*DiagMatrix(MatMul.fromiter(matrices).doit()).doit() 

214 if isinstance(vector, Transpose): 

215 vector = vector.arg 

216 return DiagMatrix(vector) 

217 

218 

219def diagonalize_vector(vector): 

220 return DiagMatrix(vector).doit()