]> gitweb.michael.orlitzky.com - sage.d.git/blob - mjo/hurwitz.py
matrix_algebra: put basis in the usual (row,column) order.
[sage.d.git] / mjo / hurwitz.py
1 from sage.misc.cachefunc import cached_method
2 from sage.combinat.free_module import CombinatorialFreeModule
3 from sage.modules.with_basis.indexed_element import IndexedFreeModuleElement
4 from sage.rings.all import AA
5
6 from mjo.matrix_algebra import MatrixAlgebra, MatrixAlgebraElement
7
8 class Octonion(IndexedFreeModuleElement):
9 def conjugate(self):
10 r"""
11 SETUP::
12
13 sage: from mjo.hurwitz import Octonions
14
15 EXAMPLES::
16
17 sage: O = Octonions()
18 sage: x = sum(O.gens())
19 sage: x.conjugate()
20 e0 - e1 - e2 - e3 - e4 - e5 - e6 - e7
21
22 TESTS::
23
24 Conjugating twice gets you the original element::
25
26 sage: set_random_seed()
27 sage: O = Octonions()
28 sage: x = O.random_element()
29 sage: x.conjugate().conjugate() == x
30 True
31
32 """
33 from sage.rings.all import ZZ
34 from sage.matrix.matrix_space import MatrixSpace
35 C = MatrixSpace(ZZ,8).diagonal_matrix((1,-1,-1,-1,-1,-1,-1,-1))
36 return self.parent().from_vector(C*self.to_vector())
37
38 def real(self):
39 r"""
40 Return the real part of this octonion.
41
42 The real part of an octonion is its projection onto the span
43 of the first generator. In other words, the "first dimension"
44 is real and the others are imaginary.
45
46 SETUP::
47
48 sage: from mjo.hurwitz import Octonions
49
50 EXAMPLES::
51
52 sage: O = Octonions()
53 sage: x = sum(O.gens())
54 sage: x.real()
55 e0
56
57 TESTS:
58
59 This method is idempotent::
60
61 sage: set_random_seed()
62 sage: O = Octonions()
63 sage: x = O.random_element()
64 sage: x.real().real() == x.real()
65 True
66
67 """
68 return (self + self.conjugate())/2
69
70 def imag(self):
71 r"""
72 Return the imaginary part of this octonion.
73
74 The imaginary part of an octonion is its projection onto the
75 orthogonal complement of the span of the first generator. In
76 other words, the "first dimension" is real and the others are
77 imaginary.
78
79 SETUP::
80
81 sage: from mjo.hurwitz import Octonions
82
83 EXAMPLES::
84
85 sage: O = Octonions()
86 sage: x = sum(O.gens())
87 sage: x.imag()
88 e1 + e2 + e3 + e4 + e5 + e6 + e7
89
90 TESTS:
91
92 This method is idempotent::
93
94 sage: set_random_seed()
95 sage: O = Octonions()
96 sage: x = O.random_element()
97 sage: x.imag().imag() == x.imag()
98 True
99
100 """
101 return (self - self.conjugate())/2
102
103 def _norm_squared(self):
104 return (self*self.conjugate()).coefficient(0)
105
106 def norm(self):
107 r"""
108 Return the norm of this octonion.
109
110 SETUP::
111
112 sage: from mjo.hurwitz import Octonions
113
114 EXAMPLES::
115
116 sage: O = Octonions()
117 sage: O.one().norm()
118 1
119
120 TESTS:
121
122 The norm is nonnegative and belongs to the base field::
123
124 sage: set_random_seed()
125 sage: O = Octonions()
126 sage: n = O.random_element().norm()
127 sage: n >= 0 and n in O.base_ring()
128 True
129
130 The norm is homogeneous::
131
132 sage: set_random_seed()
133 sage: O = Octonions()
134 sage: x = O.random_element()
135 sage: alpha = O.base_ring().random_element()
136 sage: (alpha*x).norm() == alpha.abs()*x.norm()
137 True
138
139 """
140 return self._norm_squared().sqrt()
141
142 # The absolute value notation is typically used for complex numbers...
143 # and norm() isn't supported in AA, so this lets us use abs() in all
144 # of the division algebras we need.
145 abs = norm
146
147 def inverse(self):
148 r"""
149 Return the inverse of this element if it exists.
150
151 SETUP::
152
153 sage: from mjo.hurwitz import Octonions
154
155 EXAMPLES::
156
157 sage: O = Octonions()
158 sage: x = sum(O.gens())
159 sage: x*x.inverse() == O.one()
160 True
161
162 ::
163
164 sage: O = Octonions()
165 sage: O.one().inverse() == O.one()
166 True
167
168 TESTS::
169
170 sage: set_random_seed()
171 sage: O = Octonions()
172 sage: x = O.random_element()
173 sage: x.is_zero() or ( x*x.inverse() == O.one() )
174 True
175
176 """
177 if self.is_zero():
178 raise ValueError("zero is not invertible")
179 return self.conjugate()/self._norm_squared()
180
181
182
183 class Octonions(CombinatorialFreeModule):
184 r"""
185 SETUP::
186
187 sage: from mjo.hurwitz import Octonions
188
189 EXAMPLES::
190
191 sage: Octonions()
192 Octonion algebra with base ring Algebraic Real Field
193 sage: Octonions(field=QQ)
194 Octonion algebra with base ring Rational Field
195
196 """
197 def __init__(self,
198 field=AA,
199 prefix="e"):
200
201 # Not associative, not commutative
202 from sage.categories.magmatic_algebras import MagmaticAlgebras
203 category = MagmaticAlgebras(field).FiniteDimensional()
204 category = category.WithBasis().Unital()
205
206 super().__init__(field,
207 range(8),
208 element_class=Octonion,
209 category=category,
210 prefix=prefix,
211 bracket=False)
212
213 # The product of each basis element is plus/minus another
214 # basis element that can simply be looked up on
215 # https://en.wikipedia.org/wiki/Octonion
216 e0, e1, e2, e3, e4, e5, e6, e7 = self.gens()
217 self._multiplication_table = (
218 (e0, e1, e2, e3, e4, e5, e6, e7),
219 (e1,-e0, e3,-e2, e5,-e4,-e7, e6),
220 (e2,-e3,-e0, e1, e6, e7,-e4,-e5),
221 (e3, e2,-e1,-e0, e7,-e6, e5,-e4),
222 (e4,-e5,-e6,-e7,-e0, e1, e2, e3),
223 (e5, e4,-e7, e6,-e1,-e0,-e3, e2),
224 (e6, e7, e4,-e5,-e2, e3,-e0,-e1),
225 (e7,-e6, e5, e4,-e3,-e2, e1,-e0),
226 )
227
228 def product_on_basis(self, i, j):
229 return self._multiplication_table[i][j]
230
231 def one_basis(self):
232 r"""
233 Return the monomial index (basis element) corresponding to the
234 octonion unit element.
235
236 SETUP::
237
238 sage: from mjo.hurwitz import Octonions
239
240 TESTS:
241
242 This gives the correct unit element::
243
244 sage: set_random_seed()
245 sage: O = Octonions()
246 sage: x = O.random_element()
247 sage: x*O.one() == x and O.one()*x == x
248 True
249
250 """
251 return 0
252
253 def _repr_(self):
254 return ("Octonion algebra with base ring %s" % self.base_ring())
255
256 def multiplication_table(self):
257 """
258 Return a visual representation of this algebra's multiplication
259 table (on basis elements).
260
261 SETUP::
262
263 sage: from mjo.hurwitz import Octonions
264
265 EXAMPLES:
266
267 The multiplication table is what Wikipedia says it is::
268
269 sage: Octonions().multiplication_table()
270 +----++----+-----+-----+-----+-----+-----+-----+-----+
271 | * || e0 | e1 | e2 | e3 | e4 | e5 | e6 | e7 |
272 +====++====+=====+=====+=====+=====+=====+=====+=====+
273 | e0 || e0 | e1 | e2 | e3 | e4 | e5 | e6 | e7 |
274 +----++----+-----+-----+-----+-----+-----+-----+-----+
275 | e1 || e1 | -e0 | e3 | -e2 | e5 | -e4 | -e7 | e6 |
276 +----++----+-----+-----+-----+-----+-----+-----+-----+
277 | e2 || e2 | -e3 | -e0 | e1 | e6 | e7 | -e4 | -e5 |
278 +----++----+-----+-----+-----+-----+-----+-----+-----+
279 | e3 || e3 | e2 | -e1 | -e0 | e7 | -e6 | e5 | -e4 |
280 +----++----+-----+-----+-----+-----+-----+-----+-----+
281 | e4 || e4 | -e5 | -e6 | -e7 | -e0 | e1 | e2 | e3 |
282 +----++----+-----+-----+-----+-----+-----+-----+-----+
283 | e5 || e5 | e4 | -e7 | e6 | -e1 | -e0 | -e3 | e2 |
284 +----++----+-----+-----+-----+-----+-----+-----+-----+
285 | e6 || e6 | e7 | e4 | -e5 | -e2 | e3 | -e0 | -e1 |
286 +----++----+-----+-----+-----+-----+-----+-----+-----+
287 | e7 || e7 | -e6 | e5 | e4 | -e3 | -e2 | e1 | -e0 |
288 +----++----+-----+-----+-----+-----+-----+-----+-----+
289
290 """
291 n = self.dimension()
292 # Prepend the header row.
293 M = [["*"] + list(self.gens())]
294
295 # And to each subsequent row, prepend an entry that belongs to
296 # the left-side "header column."
297 M += [ [self.monomial(i)] + [ self.monomial(i)*self.monomial(j)
298 for j in range(n) ]
299 for i in range(n) ]
300
301 from sage.misc.table import table
302 return table(M, header_row=True, header_column=True, frame=True)
303
304
305
306
307
308 class HurwitzMatrixAlgebraElement(MatrixAlgebraElement):
309 def conjugate_transpose(self):
310 r"""
311 Return the conjugate-transpose of this matrix.
312
313 SETUP::
314
315 sage: from mjo.hurwitz import HurwitzMatrixAlgebra
316
317 EXAMPLES::
318
319 sage: A = HurwitzMatrixAlgebra(2, QQbar, ZZ)
320 sage: M = A([ [ I, 2*I],
321 ....: [ 3*I, 4*I] ])
322 sage: M.conjugate_transpose()
323 +------+------+
324 | -1*I | -3*I |
325 +------+------+
326 | -2*I | -4*I |
327 +------+------+
328
329 """
330 entries = [ [ self[j,i].conjugate()
331 for j in range(self.ncols())]
332 for i in range(self.nrows()) ]
333 return self.parent()._element_constructor_(entries)
334
335 def is_hermitian(self):
336 r"""
337
338 SETUP::
339
340 sage: from mjo.hurwitz import HurwitzMatrixAlgebra
341
342 EXAMPLES::
343
344 sage: A = HurwitzMatrixAlgebra(2, QQbar, ZZ)
345 sage: M = A([ [ 0,I],
346 ....: [-I,0] ])
347 sage: M.is_hermitian()
348 True
349
350 """
351 # A tiny bit faster than checking equality with the conjugate
352 # transpose.
353 return all( self[i,j] == self[j,i].conjugate()
354 for i in range(self.nrows())
355 for j in range(self.ncols()) )
356
357
358 class HurwitzMatrixAlgebra(MatrixAlgebra):
359 r"""
360 A class of matrix algebras whose entries come from a Hurwitz
361 algebra.
362
363 For our purposes, we consider "a Hurwitz" algebra to be the real
364 or complex numbers, the quaternions, or the octonions. These are
365 typically also referred to as the Euclidean Hurwitz algebras, or
366 the normed division algebras.
367
368 By the Cayley-Dickson construction, each Hurwitz algebra is an
369 algebra over the real numbers, so we restrict the scalar field in
370 this case to be real. This also allows us to more accurately
371 produce the generators of the matrix algebra.
372 """
373 Element = HurwitzMatrixAlgebraElement
374
375 def __init__(self, n, entry_algebra, scalars, **kwargs):
376 from sage.rings.all import RR
377 if not scalars.is_subring(RR):
378 # Not perfect, but it's what we're using.
379 raise ValueError("scalar field is not real")
380
381 super().__init__(n, entry_algebra, scalars, **kwargs)
382
383 def entry_algebra_gens(self):
384 r"""
385 Return a tuple of the generators of (that is, a basis for) the
386 entries of this matrix algebra.
387
388 This works around the inconsistency in the ``gens()`` methods
389 of the real/complex numbers, quaternions, and octonions.
390
391 SETUP::
392
393 sage: from mjo.hurwitz import Octonions, HurwitzMatrixAlgebra
394
395 EXAMPLES:
396
397 The inclusion of the unit element is inconsistent across
398 (subalgebras of) Hurwitz algebras::
399
400 sage: AA.gens()
401 (1,)
402 sage: QQbar.gens()
403 (I,)
404 sage: QuaternionAlgebra(AA,1,-1).gens()
405 [i, j, k]
406 sage: Octonions().gens()
407 (e0, e1, e2, e3, e4, e5, e6, e7)
408
409 The unit element is always returned by this method, so the
410 sets of generators have cartinality 1,2,4, and 8 as you'd
411 expect::
412
413 sage: HurwitzMatrixAlgebra(2, AA, AA).entry_algebra_gens()
414 (1,)
415 sage: HurwitzMatrixAlgebra(2, QQbar, AA).entry_algebra_gens()
416 (1, I)
417 sage: Q = QuaternionAlgebra(AA,-1,-1)
418 sage: HurwitzMatrixAlgebra(2, Q, AA).entry_algebra_gens()
419 (1, i, j, k)
420 sage: O = Octonions()
421 sage: HurwitzMatrixAlgebra(2, O, AA).entry_algebra_gens()
422 (e0, e1, e2, e3, e4, e5, e6, e7)
423
424 """
425 gs = self.entry_algebra().gens()
426 one = self.entry_algebra().one()
427 if one in gs:
428 return gs
429 else:
430 return (one,) + tuple(gs)
431
432
433
434 class OctonionMatrixAlgebra(HurwitzMatrixAlgebra):
435 r"""
436 The algebra of ``n``-by-``n`` matrices with octonion entries over
437 (a subfield of) the real numbers.
438
439 The usual matrix spaces in SageMath don't support octonion entries
440 because they assume that the entries of the matrix come from a
441 commutative and associative ring, and the octonions are neither.
442
443 SETUP::
444
445 sage: from mjo.hurwitz import Octonions, OctonionMatrixAlgebra
446
447 EXAMPLES::
448
449 sage: OctonionMatrixAlgebra(3)
450 Module of 3 by 3 matrices with entries in Octonion algebra with base
451 ring Algebraic Real Field over the scalar ring Algebraic Real Field
452
453 ::
454
455 sage: OctonionMatrixAlgebra(3,scalars=QQ)
456 Module of 3 by 3 matrices with entries in Octonion algebra with
457 base ring Rational Field over the scalar ring Rational Field
458
459 ::
460
461 sage: O = Octonions(RR)
462 sage: A = OctonionMatrixAlgebra(1,O)
463 sage: A
464 Module of 1 by 1 matrices with entries in Octonion algebra with
465 base ring Real Field with 53 bits of precision over the scalar
466 ring Algebraic Real Field
467 sage: A.one()
468 +---------------------+
469 | 1.00000000000000*e0 |
470 +---------------------+
471 sage: A.gens()
472 (+---------------------+
473 | 1.00000000000000*e0 |
474 +---------------------+,
475 +---------------------+
476 | 1.00000000000000*e1 |
477 +---------------------+,
478 +---------------------+
479 | 1.00000000000000*e2 |
480 +---------------------+,
481 +---------------------+
482 | 1.00000000000000*e3 |
483 +---------------------+,
484 +---------------------+
485 | 1.00000000000000*e4 |
486 +---------------------+,
487 +---------------------+
488 | 1.00000000000000*e5 |
489 +---------------------+,
490 +---------------------+
491 | 1.00000000000000*e6 |
492 +---------------------+,
493 +---------------------+
494 | 1.00000000000000*e7 |
495 +---------------------+)
496
497 ::
498
499 sage: A = OctonionMatrixAlgebra(2)
500 sage: e0,e1,e2,e3,e4,e5,e6,e7 = A.entry_algebra().gens()
501 sage: A([ [e0+e4, e1+e5],
502 ....: [e2-e6, e3-e7] ])
503 +---------+---------+
504 | e0 + e4 | e1 + e5 |
505 +---------+---------+
506 | e2 - e6 | e3 - e7 |
507 +---------+---------+
508
509 ::
510
511 sage: A1 = OctonionMatrixAlgebra(1,scalars=QQ)
512 sage: A2 = OctonionMatrixAlgebra(1,scalars=QQ)
513 sage: cartesian_product([A1,A2])
514 Module of 1 by 1 matrices with entries in Octonion algebra with
515 base ring Rational Field over the scalar ring Rational Field (+)
516 Module of 1 by 1 matrices with entries in Octonion algebra with
517 base ring Rational Field over the scalar ring Rational Field
518
519 TESTS::
520
521 sage: set_random_seed()
522 sage: A = OctonionMatrixAlgebra(ZZ.random_element(10))
523 sage: x = A.random_element()
524 sage: x*A.one() == x and A.one()*x == x
525 True
526
527 """
528 def __init__(self, n, entry_algebra=None, scalars=AA, **kwargs):
529 if entry_algebra is None:
530 entry_algebra = Octonions(field=scalars)
531 super().__init__(n,
532 entry_algebra,
533 scalars,
534 **kwargs)
535
536 class QuaternionMatrixAlgebra(HurwitzMatrixAlgebra):
537 r"""
538 The algebra of ``n``-by-``n`` matrices with quaternion entries over
539 (a subfield of) the real numbers.
540
541 The usual matrix spaces in SageMath don't support quaternion entries
542 because they assume that the entries of the matrix come from a
543 commutative ring, and the quaternions are not commutative.
544
545 SETUP::
546
547 sage: from mjo.hurwitz import QuaternionMatrixAlgebra
548
549 EXAMPLES::
550
551 sage: QuaternionMatrixAlgebra(3)
552 Module of 3 by 3 matrices with entries in Quaternion
553 Algebra (-1, -1) with base ring Algebraic Real Field
554 over the scalar ring Algebraic Real Field
555
556 ::
557
558 sage: QuaternionMatrixAlgebra(3,scalars=QQ)
559 Module of 3 by 3 matrices with entries in Quaternion
560 Algebra (-1, -1) with base ring Rational Field over
561 the scalar ring Rational Field
562
563 ::
564
565 sage: Q = QuaternionAlgebra(RDF, -1, -1)
566 sage: A = QuaternionMatrixAlgebra(1,Q)
567 sage: A
568 Module of 1 by 1 matrices with entries in Quaternion Algebra
569 (-1.0, -1.0) with base ring Real Double Field over the scalar
570 ring Algebraic Real Field
571 sage: A.one()
572 +-----+
573 | 1.0 |
574 +-----+
575 sage: A.gens()
576 (+-----+
577 | 1.0 |
578 +-----+,
579 +---+
580 | i |
581 +---+,
582 +---+
583 | j |
584 +---+,
585 +---+
586 | k |
587 +---+)
588
589 ::
590
591 sage: A = QuaternionMatrixAlgebra(2)
592 sage: i,j,k = A.entry_algebra().gens()
593 sage: A([ [1+i, j-2],
594 ....: [k, k+j] ])
595 +-------+--------+
596 | 1 + i | -2 + j |
597 +-------+--------+
598 | k | j + k |
599 +-------+--------+
600
601 ::
602
603 sage: A1 = QuaternionMatrixAlgebra(1,scalars=QQ)
604 sage: A2 = QuaternionMatrixAlgebra(2,scalars=QQ)
605 sage: cartesian_product([A1,A2])
606 Module of 1 by 1 matrices with entries in Quaternion Algebra
607 (-1, -1) with base ring Rational Field over the scalar ring
608 Rational Field (+) Module of 2 by 2 matrices with entries in
609 Quaternion Algebra (-1, -1) with base ring Rational Field over
610 the scalar ring Rational Field
611
612 TESTS::
613
614 sage: set_random_seed()
615 sage: A = QuaternionMatrixAlgebra(ZZ.random_element(10))
616 sage: x = A.random_element()
617 sage: x*A.one() == x and A.one()*x == x
618 True
619
620 """
621 def __init__(self, n, entry_algebra=None, scalars=AA, **kwargs):
622 if entry_algebra is None:
623 # The -1,-1 gives us the "usual" definition of quaternion
624 from sage.algebras.quatalg.quaternion_algebra import (
625 QuaternionAlgebra
626 )
627 entry_algebra = QuaternionAlgebra(scalars,-1,-1)
628 super().__init__(n, entry_algebra, scalars, **kwargs)
629
630
631 class ComplexMatrixAlgebra(HurwitzMatrixAlgebra):
632 r"""
633 The algebra of ``n``-by-``n`` matrices with complex entries over
634 (a subfield of) the real numbers.
635
636 These differ from the usual complex matrix spaces in SageMath
637 because the scalar field is real (and not assumed to be the same
638 as the space from which the entries are drawn). The space of
639 `1`-by-`1` complex matrices will have dimension two, for example.
640
641 SETUP::
642
643 sage: from mjo.hurwitz import ComplexMatrixAlgebra
644
645 EXAMPLES::
646
647 sage: ComplexMatrixAlgebra(3)
648 Module of 3 by 3 matrices with entries in Algebraic Field
649 over the scalar ring Algebraic Real Field
650
651 ::
652
653 sage: ComplexMatrixAlgebra(3,scalars=QQ)
654 Module of 3 by 3 matrices with entries in Algebraic Field
655 over the scalar ring Rational Field
656
657 ::
658
659 sage: A = ComplexMatrixAlgebra(1,CC)
660 sage: A
661 Module of 1 by 1 matrices with entries in Complex Field with
662 53 bits of precision over the scalar ring Algebraic Real Field
663 sage: A.one()
664 +------------------+
665 | 1.00000000000000 |
666 +------------------+
667 sage: A.gens()
668 (+------------------+
669 | 1.00000000000000 |
670 +------------------+,
671 +--------------------+
672 | 1.00000000000000*I |
673 +--------------------+)
674
675 ::
676
677 sage: A = ComplexMatrixAlgebra(2)
678 sage: (I,) = A.entry_algebra().gens()
679 sage: A([ [1+I, 1],
680 ....: [-1, -I] ])
681 +-------+----+
682 | I + 1 | 1 |
683 +-------+----+
684 | -1 | -I |
685 +-------+----+
686
687 ::
688
689 sage: A1 = ComplexMatrixAlgebra(1,scalars=QQ)
690 sage: A2 = ComplexMatrixAlgebra(2,scalars=QQ)
691 sage: cartesian_product([A1,A2])
692 Module of 1 by 1 matrices with entries in Algebraic Field over
693 the scalar ring Rational Field (+) Module of 2 by 2 matrices with
694 entries in Algebraic Field over the scalar ring Rational Field
695
696 TESTS::
697
698 sage: set_random_seed()
699 sage: A = ComplexMatrixAlgebra(ZZ.random_element(10))
700 sage: x = A.random_element()
701 sage: x*A.one() == x and A.one()*x == x
702 True
703
704 """
705 def __init__(self, n, entry_algebra=None, scalars=AA, **kwargs):
706 if entry_algebra is None:
707 from sage.rings.all import QQbar
708 entry_algebra = QQbar
709 super().__init__(n, entry_algebra, scalars, **kwargs)