]> gitweb.michael.orlitzky.com - sage.d.git/blob - mjo/matrix_algebra.py
eja: fix some tests, speed improvements.
[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: self.sum( (self.monomial((i,i,entry_one))
189 for i in range(self.nrows()) ) )
190
191 if "Associative" in entry_algebra.category().axioms():
192 category = category.Associative()
193
194 self._nrows = n
195
196 # Since the scalar ring is real but the entries are not,
197 # sticking a "1" in each position doesn't give us a basis for
198 # the space. We actually need to stick each of e0, e1, ... (a
199 # basis for the entry algebra itself) into each position.
200 self._entry_algebra = entry_algebra
201
202 # Needs to make the (overridden) method call when, for example,
203 # the entry algebra is the complex numbers and its gens() method
204 # lies to us.
205 entry_basis = self.entry_algebra_gens()
206
207 basis_indices = [(i,j,e) for i in range(n)
208 for j in range(n)
209 for e in entry_basis]
210
211 super().__init__(scalars,
212 basis_indices,
213 category=category,
214 prefix=prefix,
215 bracket='(')
216
217 def _repr_(self):
218 return ("Module of %d by %d matrices with entries in %s"
219 " over the scalar ring %s" %
220 (self.nrows(),
221 self.ncols(),
222 self.entry_algebra(),
223 self.base_ring()) )
224
225 def entry_algebra(self):
226 r"""
227 Return the algebra that our elements' entries come from.
228 """
229 return self._entry_algebra
230
231 def entry_algebra_gens(self):
232 r"""
233 Return a tuple of the generators of (that is, a basis for) the
234 entries of this matrix algebra.
235
236 This can be overridden in subclasses to work around the
237 inconsistency in the ``gens()`` methods of the various
238 entry algebras.
239 """
240 return self.entry_algebra().gens()
241
242 def _entry_algebra_element_to_vector(self, entry):
243 r"""
244 Return a vector representation (of length equal to the cardinality
245 of :meth:`entry_algebra_gens`) of the given ``entry``.
246
247 This can be overridden in subclasses to work around the fact that
248 real numbers, complex numbers, quaternions, et cetera, all require
249 different incantations to turn them into a vector.
250
251 It only makes sense to "guess" here in the superclass when no
252 subclass that overrides :meth:`entry_algebra_gens` exists. So
253 if you have a special subclass for your annoying entry algebra,
254 override this with the correct implementation there instead of
255 adding a bunch of awkward cases to this superclass method.
256
257 SETUP::
258
259 sage: from mjo.hurwitz import Octonions
260 sage: from mjo.matrix_algebra import MatrixAlgebra
261
262 EXAMPLES:
263
264 Real numbers::
265
266 sage: A = MatrixAlgebra(1, AA, QQ)
267 sage: A._entry_algebra_element_to_vector(AA(17))
268 (17)
269
270 Octonions::
271
272 sage: A = MatrixAlgebra(1, Octonions(), QQ)
273 sage: e = A.entry_algebra_gens()
274 sage: A._entry_algebra_element_to_vector(e[0])
275 (1, 0, 0, 0, 0, 0, 0, 0)
276 sage: A._entry_algebra_element_to_vector(e[1])
277 (0, 1, 0, 0, 0, 0, 0, 0)
278 sage: A._entry_algebra_element_to_vector(e[2])
279 (0, 0, 1, 0, 0, 0, 0, 0)
280 sage: A._entry_algebra_element_to_vector(e[3])
281 (0, 0, 0, 1, 0, 0, 0, 0)
282 sage: A._entry_algebra_element_to_vector(e[4])
283 (0, 0, 0, 0, 1, 0, 0, 0)
284 sage: A._entry_algebra_element_to_vector(e[5])
285 (0, 0, 0, 0, 0, 1, 0, 0)
286 sage: A._entry_algebra_element_to_vector(e[6])
287 (0, 0, 0, 0, 0, 0, 1, 0)
288 sage: A._entry_algebra_element_to_vector(e[7])
289 (0, 0, 0, 0, 0, 0, 0, 1)
290
291 Sage matrices::
292
293 sage: MS = MatrixSpace(QQ,2)
294 sage: A = MatrixAlgebra(1, MS, QQ)
295 sage: A._entry_algebra_element_to_vector(MS([[1,2],[3,4]]))
296 (1, 2, 3, 4)
297
298 """
299 if hasattr(entry, 'to_vector'):
300 return entry.to_vector()
301
302 from sage.modules.free_module import FreeModule
303 d = len(self.entry_algebra_gens())
304 V = FreeModule(self.entry_algebra().base_ring(), d)
305
306 if hasattr(entry, 'list'):
307 # sage matrices
308 return V(entry.list())
309
310 # This works in AA, and will crash if it doesn't know what to
311 # do, and that's fine because then I don't know what to do
312 # either.
313 return V((entry,))
314
315
316
317 def nrows(self):
318 return self._nrows
319 ncols = nrows
320
321 def product_on_basis(self, mon1, mon2):
322 r"""
323
324 SETUP::
325
326 sage: from mjo.hurwitz import Octonions
327 sage: from mjo.matrix_algebra import MatrixAlgebra
328
329 TESTS::
330
331 sage: O = Octonions(QQ)
332 sage: e = O.gens()
333 sage: e[2]*e[1]
334 -e3
335 sage: A = MatrixAlgebra(2,O,QQ)
336 sage: A.product_on_basis( (0,0,e[2]), (0,0,e[1]) )
337 +-----+---+
338 | -e3 | 0 |
339 +-----+---+
340 | 0 | 0 |
341 +-----+---+
342
343 """
344 (i,j,e1) = mon1
345 (k,l,e2) = mon2
346 if j == k:
347 # There's no reason to expect e1*e2 to itself be a monomial,
348 # so we have to do some manual conversion to get one.
349 p = self._entry_algebra_element_to_vector(e1*e2)
350
351 # We have to convert alpha_g because a priori it lives in the
352 # base ring of the entry algebra.
353 R = self.base_ring()
354 return self.sum_of_terms( (((i,l,g), R(alpha_g))
355 for (alpha_g, g)
356 in zip(p, self.entry_algebra_gens()) ),
357 distinct=True)
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_of_terms( (((i,j,g), R(alpha_g))
418 for (alpha_g, g)
419 in zip(p, self.entry_algebra_gens()) ),
420 distinct=True)
421
422 return self.sum( entry_to_element(i,j,entries[i][j])
423 for j in range(ncols)
424 for i in range(nrows) )
425
426
427 def _element_constructor_(self, elt):
428 if elt in self:
429 return self
430 else:
431 return self.from_list(elt)