]> gitweb.michael.orlitzky.com - sage.d.git/blob - mjo/hurwitz.py
ccc8219b1a92036c6ac92f118339c160a885977b
[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 is_hermitian(self):
310 r"""
311
312 SETUP::
313
314 sage: from mjo.hurwitz import HurwitzMatrixAlgebra
315
316 EXAMPLES::
317
318 sage: A = HurwitzMatrixAlgebra(2, QQbar, ZZ)
319 sage: M = A([ [ 0,I],
320 ....: [-I,0] ])
321 sage: M.is_hermitian()
322 True
323
324 """
325 return all( self[i,j] == self[j,i].conjugate()
326 for i in range(self.nrows())
327 for j in range(self.ncols()) )
328
329
330 class HurwitzMatrixAlgebra(MatrixAlgebra):
331 r"""
332 A class of matrix algebras whose entries come from a Hurwitz
333 algebra.
334
335 For our purposes, we consider "a Hurwitz" algebra to be the real
336 or complex numbers, the quaternions, or the octonions. These are
337 typically also referred to as the Euclidean Hurwitz algebras, or
338 the normed division algebras.
339
340 By the Cayley-Dickson construction, each Hurwitz algebra is an
341 algebra over the real numbers, so we restrict the scalar field in
342 this case to be real. This also allows us to more accurately
343 produce the generators of the matrix algebra.
344 """
345 Element = HurwitzMatrixAlgebraElement
346
347 def __init__(self, n, entry_algebra, scalars, **kwargs):
348 from sage.rings.all import RR
349 if not scalars.is_subring(RR):
350 # Not perfect, but it's what we're using.
351 raise ValueError("scalar field is not real")
352
353 super().__init__(n, entry_algebra, scalars, **kwargs)
354
355 def entry_algebra_gens(self):
356 r"""
357 Return a tuple of the generators of (that is, a basis for) the
358 entries of this matrix algebra.
359
360 This works around the inconsistency in the ``gens()`` methods
361 of the real/complex numbers, quaternions, and octonions.
362
363 SETUP::
364
365 sage: from mjo.hurwitz import Octonions, HurwitzMatrixAlgebra
366
367 EXAMPLES:
368
369 The inclusion of the unit element is inconsistent across
370 (subalgebras of) Hurwitz algebras::
371
372 sage: AA.gens()
373 (1,)
374 sage: QQbar.gens()
375 (I,)
376 sage: QuaternionAlgebra(AA,1,-1).gens()
377 [i, j, k]
378 sage: Octonions().gens()
379 (e0, e1, e2, e3, e4, e5, e6, e7)
380
381 The unit element is always returned by this method, so the
382 sets of generators have cartinality 1,2,4, and 8 as you'd
383 expect::
384
385 sage: HurwitzMatrixAlgebra(2, AA, AA).entry_algebra_gens()
386 (1,)
387 sage: HurwitzMatrixAlgebra(2, QQbar, AA).entry_algebra_gens()
388 (1, I)
389 sage: Q = QuaternionAlgebra(AA,-1,-1)
390 sage: HurwitzMatrixAlgebra(2, Q, AA).entry_algebra_gens()
391 (1, i, j, k)
392 sage: O = Octonions()
393 sage: HurwitzMatrixAlgebra(2, O, AA).entry_algebra_gens()
394 (e0, e1, e2, e3, e4, e5, e6, e7)
395
396 """
397 gs = self.entry_algebra().gens()
398 one = self.entry_algebra().one()
399 if one in gs:
400 return gs
401 else:
402 return (one,) + tuple(gs)
403
404
405
406 class OctonionMatrixAlgebra(HurwitzMatrixAlgebra):
407 r"""
408 The algebra of ``n``-by-``n`` matrices with octonion entries over
409 (a subfield of) the real numbers.
410
411 The usual matrix spaces in SageMath don't support octonion entries
412 because they assume that the entries of the matrix come from a
413 commutative and associative ring, and the octonions are neither.
414
415 SETUP::
416
417 sage: from mjo.hurwitz import Octonions, OctonionMatrixAlgebra
418
419 EXAMPLES::
420
421 sage: OctonionMatrixAlgebra(3)
422 Module of 3 by 3 matrices with entries in Octonion algebra with base
423 ring Algebraic Real Field over the scalar ring Algebraic Real Field
424
425 ::
426
427 sage: OctonionMatrixAlgebra(3,scalars=QQ)
428 Module of 3 by 3 matrices with entries in Octonion algebra with
429 base ring Rational Field over the scalar ring Rational Field
430
431 ::
432
433 sage: O = Octonions(RR)
434 sage: A = OctonionMatrixAlgebra(1,O)
435 sage: A
436 Module of 1 by 1 matrices with entries in Octonion algebra with
437 base ring Real Field with 53 bits of precision over the scalar
438 ring Algebraic Real Field
439 sage: A.one()
440 +---------------------+
441 | 1.00000000000000*e0 |
442 +---------------------+
443 sage: A.gens()
444 (+---------------------+
445 | 1.00000000000000*e0 |
446 +---------------------+,
447 +---------------------+
448 | 1.00000000000000*e1 |
449 +---------------------+,
450 +---------------------+
451 | 1.00000000000000*e2 |
452 +---------------------+,
453 +---------------------+
454 | 1.00000000000000*e3 |
455 +---------------------+,
456 +---------------------+
457 | 1.00000000000000*e4 |
458 +---------------------+,
459 +---------------------+
460 | 1.00000000000000*e5 |
461 +---------------------+,
462 +---------------------+
463 | 1.00000000000000*e6 |
464 +---------------------+,
465 +---------------------+
466 | 1.00000000000000*e7 |
467 +---------------------+)
468
469 ::
470
471 sage: A = OctonionMatrixAlgebra(2)
472 sage: e0,e1,e2,e3,e4,e5,e6,e7 = A.entry_algebra().gens()
473 sage: A([ [e0+e4, e1+e5],
474 ....: [e2-e6, e3-e7] ])
475 +---------+---------+
476 | e0 + e4 | e1 + e5 |
477 +---------+---------+
478 | e2 - e6 | e3 - e7 |
479 +---------+---------+
480
481 ::
482
483 sage: A1 = OctonionMatrixAlgebra(1,scalars=QQ)
484 sage: A2 = OctonionMatrixAlgebra(1,scalars=QQ)
485 sage: cartesian_product([A1,A2])
486 Module of 1 by 1 matrices with entries in Octonion algebra with
487 base ring Rational Field over the scalar ring Rational Field (+)
488 Module of 1 by 1 matrices with entries in Octonion algebra with
489 base ring Rational Field over the scalar ring Rational Field
490
491 TESTS::
492
493 sage: set_random_seed()
494 sage: A = OctonionMatrixAlgebra(ZZ.random_element(10))
495 sage: x = A.random_element()
496 sage: x*A.one() == x and A.one()*x == x
497 True
498
499 """
500 def __init__(self, n, entry_algebra=None, scalars=AA, **kwargs):
501 if entry_algebra is None:
502 entry_algebra = Octonions(field=scalars)
503 super().__init__(n,
504 entry_algebra,
505 scalars,
506 **kwargs)
507
508 class QuaternionMatrixAlgebra(HurwitzMatrixAlgebra):
509 r"""
510 The algebra of ``n``-by-``n`` matrices with quaternion entries over
511 (a subfield of) the real numbers.
512
513 The usual matrix spaces in SageMath don't support quaternion entries
514 because they assume that the entries of the matrix come from a
515 commutative ring, and the quaternions are not commutative.
516
517 SETUP::
518
519 sage: from mjo.hurwitz import QuaternionMatrixAlgebra
520
521 EXAMPLES::
522
523 sage: QuaternionMatrixAlgebra(3)
524 Module of 3 by 3 matrices with entries in Quaternion
525 Algebra (-1, -1) with base ring Algebraic Real Field
526 over the scalar ring Algebraic Real Field
527
528 ::
529
530 sage: QuaternionMatrixAlgebra(3,scalars=QQ)
531 Module of 3 by 3 matrices with entries in Quaternion
532 Algebra (-1, -1) with base ring Rational Field over
533 the scalar ring Rational Field
534
535 ::
536
537 sage: Q = QuaternionAlgebra(RDF, -1, -1)
538 sage: A = QuaternionMatrixAlgebra(1,Q)
539 sage: A
540 Module of 1 by 1 matrices with entries in Quaternion Algebra
541 (-1.0, -1.0) with base ring Real Double Field over the scalar
542 ring Algebraic Real Field
543 sage: A.one()
544 +-----+
545 | 1.0 |
546 +-----+
547 sage: A.gens()
548 (+-----+
549 | 1.0 |
550 +-----+,
551 +---+
552 | i |
553 +---+,
554 +---+
555 | j |
556 +---+,
557 +---+
558 | k |
559 +---+)
560
561 ::
562
563 sage: A = QuaternionMatrixAlgebra(2)
564 sage: i,j,k = A.entry_algebra().gens()
565 sage: A([ [1+i, j-2],
566 ....: [k, k+j] ])
567 +-------+--------+
568 | 1 + i | -2 + j |
569 +-------+--------+
570 | k | j + k |
571 +-------+--------+
572
573 ::
574
575 sage: A1 = QuaternionMatrixAlgebra(1,scalars=QQ)
576 sage: A2 = QuaternionMatrixAlgebra(2,scalars=QQ)
577 sage: cartesian_product([A1,A2])
578 Module of 1 by 1 matrices with entries in Quaternion Algebra
579 (-1, -1) with base ring Rational Field over the scalar ring
580 Rational Field (+) Module of 2 by 2 matrices with entries in
581 Quaternion Algebra (-1, -1) with base ring Rational Field over
582 the scalar ring Rational Field
583
584 TESTS::
585
586 sage: set_random_seed()
587 sage: A = QuaternionMatrixAlgebra(ZZ.random_element(10))
588 sage: x = A.random_element()
589 sage: x*A.one() == x and A.one()*x == x
590 True
591
592 """
593 def __init__(self, n, entry_algebra=None, scalars=AA, **kwargs):
594 if entry_algebra is None:
595 # The -1,-1 gives us the "usual" definition of quaternion
596 from sage.algebras.quatalg.quaternion_algebra import (
597 QuaternionAlgebra
598 )
599 entry_algebra = QuaternionAlgebra(scalars,-1,-1)
600 super().__init__(n, entry_algebra, scalars, **kwargs)
601
602
603 class ComplexMatrixAlgebra(HurwitzMatrixAlgebra):
604 r"""
605 The algebra of ``n``-by-``n`` matrices with complex entries over
606 (a subfield of) the real numbers.
607
608 These differ from the usual complex matrix spaces in SageMath
609 because the scalar field is real (and not assumed to be the same
610 as the space from which the entries are drawn). The space of
611 `1`-by-`1` complex matrices will have dimension two, for example.
612
613 SETUP::
614
615 sage: from mjo.hurwitz import ComplexMatrixAlgebra
616
617 EXAMPLES::
618
619 sage: ComplexMatrixAlgebra(3)
620 Module of 3 by 3 matrices with entries in Algebraic Field
621 over the scalar ring Algebraic Real Field
622
623 ::
624
625 sage: ComplexMatrixAlgebra(3,scalars=QQ)
626 Module of 3 by 3 matrices with entries in Algebraic Field
627 over the scalar ring Rational Field
628
629 ::
630
631 sage: A = ComplexMatrixAlgebra(1,CC)
632 sage: A
633 Module of 1 by 1 matrices with entries in Complex Field with
634 53 bits of precision over the scalar ring Algebraic Real Field
635 sage: A.one()
636 +------------------+
637 | 1.00000000000000 |
638 +------------------+
639 sage: A.gens()
640 (+------------------+
641 | 1.00000000000000 |
642 +------------------+,
643 +--------------------+
644 | 1.00000000000000*I |
645 +--------------------+)
646
647 ::
648
649 sage: A = ComplexMatrixAlgebra(2)
650 sage: (I,) = A.entry_algebra().gens()
651 sage: A([ [1+I, 1],
652 ....: [-1, -I] ])
653 +-------+----+
654 | I + 1 | 1 |
655 +-------+----+
656 | -1 | -I |
657 +-------+----+
658
659 ::
660
661 sage: A1 = ComplexMatrixAlgebra(1,scalars=QQ)
662 sage: A2 = ComplexMatrixAlgebra(2,scalars=QQ)
663 sage: cartesian_product([A1,A2])
664 Module of 1 by 1 matrices with entries in Algebraic Field over
665 the scalar ring Rational Field (+) Module of 2 by 2 matrices with
666 entries in Algebraic Field over the scalar ring Rational Field
667
668 TESTS::
669
670 sage: set_random_seed()
671 sage: A = ComplexMatrixAlgebra(ZZ.random_element(10))
672 sage: x = A.random_element()
673 sage: x*A.one() == x and A.one()*x == x
674 True
675
676 """
677 def __init__(self, n, entry_algebra=None, scalars=AA, **kwargs):
678 if entry_algebra is None:
679 from sage.rings.all import QQbar
680 entry_algebra = QQbar
681 super().__init__(n, entry_algebra, scalars, **kwargs)