]> gitweb.michael.orlitzky.com - sage.d.git/blob - mjo/matrix_algebra.py
eja: rename operator_inner_product -> operator_trace inner_product.
[sage.d.git] / mjo / matrix_algebra.py
1 from sage.misc.table import table
2 from sage.categories.magmatic_algebras import MagmaticAlgebras
3 from sage.misc.cachefunc import cached_method
4 from sage.combinat.free_module import CombinatorialFreeModule
5 from sage.modules.with_basis.indexed_element import IndexedFreeModuleElement
6
7 class MatrixAlgebraElement(IndexedFreeModuleElement):
8 def nrows(self):
9 return self.parent().nrows()
10 ncols = nrows
11
12 @cached_method
13 def rows(self):
14 r"""
15 SETUP::
16
17 sage: from mjo.matrix_algebra import MatrixAlgebra
18
19 EXAMPLES::
20
21 sage: M = MatrixAlgebra(2, QQbar,RDF)
22 sage: A = M.monomial((0,0,1)) + 4*M.monomial((0,1,1))
23 sage: A
24 +-----+-----+
25 | 1.0 | 4.0 |
26 +-----+-----+
27 | 0 | 0 |
28 +-----+-----+
29 sage: A.rows()
30 [[1.0, 4.0], [0, 0]]
31
32 """
33 zero = self.parent().entry_algebra().zero()
34 l = [[zero for j in range(self.ncols())] for i in range(self.nrows())]
35 for (k,v) in self.monomial_coefficients().items():
36 (i,j,e) = k
37 l[i][j] += v*e
38 return l
39
40 def _repr_(self):
41 r"""
42 Display this matrix as a table.
43
44 The SageMath Matrix class representation is not easily reusable,
45 but using a table fakes it.
46
47 SETUP::
48
49 sage: from mjo.matrix_algebra import MatrixAlgebra
50
51 EXAMPLES::
52
53 sage: MatrixAlgebra(2,ZZ,ZZ).zero()
54 +---+---+
55 | 0 | 0 |
56 +---+---+
57 | 0 | 0 |
58 +---+---+
59
60 TESTS::
61
62 sage: MatrixAlgebra(0,ZZ,ZZ).zero()
63 []
64
65 """
66 if self.nrows() == 0 or self.ncols() == 0:
67 # Otherwise we get a crash or a blank space, depending on
68 # how hard we work for it. This is what MatrixSpace(...,
69 # 0) returns.
70 return "[]"
71
72 return table(self.rows(), frame=True)._repr_()
73
74
75 def list(self):
76 r"""
77 Return one long list of this matrix's entries.
78
79 SETUP::
80
81 sage: from mjo.matrix_algebra import MatrixAlgebra
82
83 EXAMPLES::
84
85 sage: A = MatrixAlgebra(2,ZZ,ZZ)
86 sage: A([[1,2],[3,4]]).list()
87 [1, 2, 3, 4]
88
89 """
90 return sum( self.rows(), [] )
91
92
93 def __getitem__(self, indices):
94 r"""
95
96 SETUP::
97
98 sage: from mjo.matrix_algebra import MatrixAlgebra
99
100 EXAMPLES::
101
102 sage: M = MatrixAlgebra(2,ZZ,ZZ)([[1,2],[3,4]])
103 sage: M[0,0]
104 1
105 sage: M[0,1]
106 2
107 sage: M[1,0]
108 3
109 sage: M[1,1]
110 4
111
112 """
113 i,j = indices
114 d = self.monomial_coefficients()
115 A = self.parent().entry_algebra()
116 return A.sum( d[k]*k[2]
117 for k in d
118 if k[0] == i and k[1] == j )
119
120
121 def trace(self):
122 r"""
123 Return the sum of this matrix's diagonal entries.
124
125 SETUP::
126
127 sage: from mjo.matrix_algebra import MatrixAlgebra
128
129 EXAMPLES:
130
131 The trace (being a sum of entries) belongs to the same algebra
132 as those entries, and NOT the scalar ring::
133
134 sage: entries = MatrixSpace(ZZ,2)
135 sage: scalars = ZZ
136 sage: M = MatrixAlgebra(2, entries, scalars)
137 sage: I = entries.one()
138 sage: Z = entries.zero()
139 sage: M([[I,Z],[Z,I]]).trace()
140 [2 0]
141 [0 2]
142
143 """
144 d = self.monomial_coefficients()
145 A = self.parent().entry_algebra()
146 return A.sum( d[k]*k[2] for k in d if k[0] == k[1] )
147
148 def matrix_space(self):
149 r"""
150
151 SETUP::
152
153 sage: from mjo.matrix_algebra import MatrixAlgebra
154
155 TESTS::
156
157 sage: entries = QuaternionAlgebra(QQ,-1,-1)
158 sage: M = MatrixAlgebra(3, entries, QQ)
159 sage: M.random_element().matrix_space() == M
160 True
161
162 """
163 return self.parent()
164
165
166 class MatrixAlgebra(CombinatorialFreeModule):
167 r"""
168 An algebra of ``n``-by-``n`` matrices over an arbitrary scalar
169 ring whose entries come from a magmatic algebra that need not
170 be the same as the scalars.
171
172 The usual matrix spaces in SageMath don't support separate spaces
173 for the entries and the scalars; in particular they assume that
174 the entries come from a commutative and associative ring. This
175 is problematic in several interesting matrix algebras, like those
176 where the entries are quaternions or octonions.
177
178 SETUP::
179
180 sage: from mjo.matrix_algebra import MatrixAlgebra
181
182 EXAMPLES::
183
184 The existence of a unit element is determined dynamically::
185
186 sage: MatrixAlgebra(2,ZZ,ZZ).one()
187 +---+---+
188 | 1 | 0 |
189 +---+---+
190 | 0 | 1 |
191 +---+---+
192
193 """
194 Element = MatrixAlgebraElement
195
196 def __init__(self, n, entry_algebra, scalars, prefix="A", **kwargs):
197
198 category = MagmaticAlgebras(scalars).FiniteDimensional()
199 category = category.WithBasis()
200
201 if "Unital" in entry_algebra.category().axioms():
202 category = category.Unital()
203 entry_one = entry_algebra.one()
204 self.one = lambda: self.sum( (self.monomial((i,i,entry_one))
205 for i in range(self.nrows()) ) )
206
207 if "Associative" in entry_algebra.category().axioms():
208 category = category.Associative()
209
210 self._nrows = n
211
212 # Since the scalar ring is (say) real but the entries are not,
213 # sticking a "1" in each position doesn't give us a basis for
214 # the space. We actually need to stick each of e0, e1, ... (a
215 # basis for the entry algebra itself) into each position.
216 self._entry_algebra = entry_algebra
217
218 # Needs to make the (overridden) method call when, for example,
219 # the entry algebra is the complex numbers and its gens() method
220 # lies to us.
221 entry_basis = self.entry_algebra_gens()
222
223 basis_indices = [(i,j,e) for i in range(n)
224 for j in range(n)
225 for e in entry_basis]
226
227 super().__init__(scalars,
228 basis_indices,
229 category=category,
230 prefix=prefix,
231 bracket='(')
232
233 def _repr_(self):
234 return ("Module of %d by %d matrices with entries in %s"
235 " over the scalar ring %s" %
236 (self.nrows(),
237 self.ncols(),
238 self.entry_algebra(),
239 self.base_ring()) )
240
241 def entry_algebra(self):
242 r"""
243 Return the algebra that our elements' entries come from.
244 """
245 return self._entry_algebra
246
247 def entry_algebra_gens(self):
248 r"""
249 Return a tuple of the generators of (that is, a basis for) the
250 entries of this matrix algebra.
251
252 This can be overridden in subclasses to work around the
253 inconsistency in the ``gens()`` methods of the various
254 entry algebras.
255 """
256 return self.entry_algebra().gens()
257
258 def _entry_algebra_element_to_vector(self, entry):
259 r"""
260 Return a vector representation (of length equal to the cardinality
261 of :meth:`entry_algebra_gens`) of the given ``entry``.
262
263 This can be overridden in subclasses to work around the fact that
264 real numbers, complex numbers, quaternions, et cetera, all require
265 different incantations to turn them into a vector.
266
267 It only makes sense to "guess" here in the superclass when no
268 subclass that overrides :meth:`entry_algebra_gens` exists. So
269 if you have a special subclass for your annoying entry algebra,
270 override this with the correct implementation there instead of
271 adding a bunch of awkward cases to this superclass method.
272
273 SETUP::
274
275 sage: from mjo.hurwitz import Octonions
276 sage: from mjo.matrix_algebra import MatrixAlgebra
277
278 EXAMPLES:
279
280 Real numbers::
281
282 sage: A = MatrixAlgebra(1, AA, QQ)
283 sage: A._entry_algebra_element_to_vector(AA(17))
284 (17)
285
286 Octonions::
287
288 sage: A = MatrixAlgebra(1, Octonions(), QQ)
289 sage: e = A.entry_algebra_gens()
290 sage: A._entry_algebra_element_to_vector(e[0])
291 (1, 0, 0, 0, 0, 0, 0, 0)
292 sage: A._entry_algebra_element_to_vector(e[1])
293 (0, 1, 0, 0, 0, 0, 0, 0)
294 sage: A._entry_algebra_element_to_vector(e[2])
295 (0, 0, 1, 0, 0, 0, 0, 0)
296 sage: A._entry_algebra_element_to_vector(e[3])
297 (0, 0, 0, 1, 0, 0, 0, 0)
298 sage: A._entry_algebra_element_to_vector(e[4])
299 (0, 0, 0, 0, 1, 0, 0, 0)
300 sage: A._entry_algebra_element_to_vector(e[5])
301 (0, 0, 0, 0, 0, 1, 0, 0)
302 sage: A._entry_algebra_element_to_vector(e[6])
303 (0, 0, 0, 0, 0, 0, 1, 0)
304 sage: A._entry_algebra_element_to_vector(e[7])
305 (0, 0, 0, 0, 0, 0, 0, 1)
306
307 Sage matrices::
308
309 sage: MS = MatrixSpace(QQ,2)
310 sage: A = MatrixAlgebra(1, MS, QQ)
311 sage: A._entry_algebra_element_to_vector(MS([[1,2],[3,4]]))
312 (1, 2, 3, 4)
313
314 """
315 if hasattr(entry, 'to_vector'):
316 return entry.to_vector()
317
318 from sage.modules.free_module import FreeModule
319 d = len(self.entry_algebra_gens())
320 V = FreeModule(self.entry_algebra().base_ring(), d)
321
322 if hasattr(entry, 'list'):
323 # sage matrices
324 return V(entry.list())
325
326 # This works in AA, and will crash if it doesn't know what to
327 # do, and that's fine because then I don't know what to do
328 # either.
329 return V((entry,))
330
331
332
333 def nrows(self):
334 return self._nrows
335 ncols = nrows
336
337 def product_on_basis(self, mon1, mon2):
338 r"""
339
340 SETUP::
341
342 sage: from mjo.hurwitz import Octonions
343 sage: from mjo.matrix_algebra import MatrixAlgebra
344
345 TESTS::
346
347 sage: O = Octonions(QQ)
348 sage: e = O.gens()
349 sage: e[2]*e[1]
350 -e3
351 sage: A = MatrixAlgebra(2,O,QQ)
352 sage: A.product_on_basis( (0,0,e[2]), (0,0,e[1]) )
353 +-----+---+
354 | -e3 | 0 |
355 +-----+---+
356 | 0 | 0 |
357 +-----+---+
358
359 """
360 (i,j,e1) = mon1
361 (k,l,e2) = mon2
362 if j == k:
363 # There's no reason to expect e1*e2 to itself be a monomial,
364 # so we have to do some manual conversion to get one.
365 p = self._entry_algebra_element_to_vector(e1*e2)
366
367 # We have to convert alpha_g because a priori it lives in the
368 # base ring of the entry algebra.
369 R = self.base_ring()
370 return self.sum_of_terms( (((i,l,g), R(alpha_g))
371 for (alpha_g, g)
372 in zip(p, self.entry_algebra_gens()) ),
373 distinct=True)
374 else:
375 return self.zero()
376
377 def from_list(self, entries):
378 r"""
379 Construct an element of this algebra from a list of lists of
380 entries.
381
382 SETUP::
383
384 sage: from mjo.hurwitz import ComplexMatrixAlgebra
385
386 EXAMPLES::
387
388 sage: A = ComplexMatrixAlgebra(2, QQbar, ZZ)
389 sage: M = A.from_list([[0,I],[-I,0]])
390 sage: M
391 +----+---+
392 | 0 | I |
393 +----+---+
394 | -I | 0 |
395 +----+---+
396 sage: M.to_vector()
397 (0, 0, 0, 1, 0, -1, 0, 0)
398
399 """
400 nrows = len(entries)
401 ncols = 0
402 if nrows > 0:
403 ncols = len(entries[0])
404
405 if (not all( len(r) == ncols for r in entries )) or (ncols != nrows):
406 raise ValueError("list must be square")
407
408 def convert(e_ij):
409 if e_ij in self.entry_algebra():
410 # Don't re-create an element if it already lives where
411 # it should!
412 return e_ij
413
414 try:
415 # This branch works with e.g. QQbar, where no
416 # to/from_vector() methods are available.
417 return self.entry_algebra()(e_ij)
418 except TypeError:
419 # We have to pass through vectors to convert from the
420 # given entry algebra to ours. Otherwise we can fail to
421 # convert an element of (for example) Octonions(QQ) to
422 # Octonions(AA).
423 return self.entry_algebra().from_vector(e_ij.to_vector())
424
425 def entry_to_element(i,j,entry):
426 # Convert an entry at i,j to a matrix whose only non-zero
427 # entry is i,j and corresponds to the entry.
428 p = self._entry_algebra_element_to_vector(entry)
429
430 # We have to convert alpha_g because a priori it lives in the
431 # base ring of the entry algebra.
432 R = self.base_ring()
433 return self.sum_of_terms( (((i,j,g), R(alpha_g))
434 for (alpha_g, g)
435 in zip(p, self.entry_algebra_gens()) ),
436 distinct=True)
437
438 return self.sum( entry_to_element(i,j,entries[i][j])
439 for j in range(ncols)
440 for i in range(nrows) )
441
442
443 def _element_constructor_(self, elt):
444 if elt in self:
445 return self
446 else:
447 return self.from_list(elt)