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