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
« prev ^ index » next coverage.py v7.9.1, created at 2025-06-14 15:55 +0200
1from sympy.core.sympify import _sympify
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
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.
14 Examples
15 ========
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]
26 The length of the diagonal -- the lesser of the two dimensions of `M` --
27 is accessed through the `diagonal_length` property:
29 >>> D.diagonal_length
30 2
31 >>> DiagonalMatrix(MatrixSymbol('x', n + 1, n)).diagonal_length
32 n
34 When one of the dimensions is symbolic the other will be treated as
35 though it is smaller:
37 >>> tall = DiagonalMatrix(MatrixSymbol('x', n, 3))
38 >>> tall.diagonal_length
39 3
40 >>> tall[10, 1]
41 0
43 When the size of the diagonal is not known, a value of None will
44 be returned:
46 >>> DiagonalMatrix(MatrixSymbol('x', n, m)).diagonal_length is None
47 True
49 """
50 arg = property(lambda self: self.args[0])
52 shape = property(lambda self: self.arg.shape) # type:ignore
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
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)
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.
91 Examples
92 ========
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)
102 The diagonal can be addressed like a matrix or vector and will
103 return the corresponding element of the original matrix:
105 >>> diag[1, 0] == diag[1] == x[1, 1]
106 True
108 The length of the diagonal -- the lesser of the two dimensions of `M` --
109 is accessed through the `diagonal_length` property:
111 >>> diag.diagonal_length
112 2
113 >>> DiagonalOf(MatrixSymbol('x', n + 1, n)).diagonal_length
114 n
116 When only one of the dimensions is symbolic the other will be
117 treated as though it is smaller:
119 >>> dtall = DiagonalOf(MatrixSymbol('x', n, 3))
120 >>> dtall.diagonal_length
121 3
123 When the size of the diagonal is not known, a value of None will
124 be returned:
126 >>> DiagonalOf(MatrixSymbol('x', n, m)).diagonal_length is None
127 True
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
149 @property
150 def diagonal_length(self):
151 return self.shape[0]
153 def _entry(self, i, j, **kwargs):
154 return self.arg._entry(i, i, **kwargs)
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
174 @property
175 def shape(self):
176 return self._shape
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
187 def _eval_transpose(self):
188 return self
190 def as_explicit(self):
191 from sympy.matrices.dense import diag
192 return diag(*list(self._vector.as_explicit()))
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)
219def diagonalize_vector(vector):
220 return DiagMatrix(vector).doit()