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
6 from mjo
.matrix_algebra
import MatrixAlgebra
, MatrixAlgebraElement
8 class Octonion(IndexedFreeModuleElement
):
13 sage: from mjo.hurwitz import Octonions
18 sage: x = sum(O.gens())
20 e0 - e1 - e2 - e3 - e4 - e5 - e6 - e7
24 Conjugating twice gets you the original element::
26 sage: set_random_seed()
28 sage: x = O.random_element()
29 sage: x.conjugate().conjugate() == x
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())
40 Return the real part of this octonion.
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.
48 sage: from mjo.hurwitz import Octonions
53 sage: x = sum(O.gens())
59 This method is idempotent::
61 sage: set_random_seed()
63 sage: x = O.random_element()
64 sage: x.real().real() == x.real()
68 return (self
+ self
.conjugate())/2
72 Return the imaginary part of this octonion.
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
81 sage: from mjo.hurwitz import Octonions
86 sage: x = sum(O.gens())
88 e1 + e2 + e3 + e4 + e5 + e6 + e7
92 This method is idempotent::
94 sage: set_random_seed()
96 sage: x = O.random_element()
97 sage: x.imag().imag() == x.imag()
101 return (self
- self
.conjugate())/2
103 def _norm_squared(self
):
104 return (self
*self
.conjugate()).coefficient(0)
108 Return the norm of this octonion.
112 sage: from mjo.hurwitz import Octonions
116 sage: O = Octonions()
122 The norm is nonnegative and belongs to the base field::
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()
130 The norm is homogeneous::
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()
140 return self
._norm
_squared
().sqrt()
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.
149 Return the inverse of this element if it exists.
153 sage: from mjo.hurwitz import Octonions
157 sage: O = Octonions()
158 sage: x = sum(O.gens())
159 sage: x*x.inverse() == O.one()
164 sage: O = Octonions()
165 sage: O.one().inverse() == O.one()
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() )
178 raise ValueError("zero is not invertible")
179 return self
.conjugate()/self
._norm
_squared
()
183 class Octonions(CombinatorialFreeModule
):
187 sage: from mjo.hurwitz import Octonions
192 Octonion algebra with base ring Algebraic Real Field
193 sage: Octonions(field=QQ)
194 Octonion algebra with base ring Rational Field
201 # Not associative, not commutative
202 from sage
.categories
.magmatic_algebras
import MagmaticAlgebras
203 category
= MagmaticAlgebras(field
).FiniteDimensional()
204 category
= category
.WithBasis().Unital()
206 super().__init
__(field
,
208 element_class
=Octonion
,
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
),
228 def product_on_basis(self
, i
, j
):
229 return self
._multiplication
_table
[i
][j
]
233 Return the monomial index (basis element) corresponding to the
234 octonion unit element.
238 sage: from mjo.hurwitz import Octonions
242 This gives the correct unit element::
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
254 return ("Octonion algebra with base ring %s" % self
.base_ring())
256 def multiplication_table(self
):
258 Return a visual representation of this algebra's multiplication
259 table (on basis elements).
263 sage: from mjo.hurwitz import Octonions
267 The multiplication table is what Wikipedia says it is::
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 +----++----+-----+-----+-----+-----+-----+-----+-----+
292 # Prepend the header row.
293 M
= [["*"] + list(self
.gens())]
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
)
301 from sage
.misc
.table
import table
302 return table(M
, header_row
=True, header_column
=True, frame
=True)
308 class HurwitzMatrixAlgebraElement(MatrixAlgebraElement
):
309 def conjugate_transpose(self
):
311 Return the conjugate-transpose of this matrix.
315 sage: from mjo.hurwitz import HurwitzMatrixAlgebra
319 sage: A = HurwitzMatrixAlgebra(2, QQbar, ZZ)
320 sage: M = A([ [ I, 2*I],
329 entries
= [ [ self
[j
,i
].conjugate()
330 for j
in range(self
.ncols())]
331 for i
in range(self
.nrows()) ]
332 return self
.parent()._element
_constructor
_(entries
)
334 def is_hermitian(self
):
339 sage: from mjo.hurwitz import HurwitzMatrixAlgebra
343 sage: A = HurwitzMatrixAlgebra(2, QQbar, ZZ)
344 sage: M = A([ [ 0,I],
346 sage: M.is_hermitian()
350 # A tiny bit faster than checking equality with the conjugate
352 return all( self
[i
,j
] == self
[j
,i
].conjugate()
353 for i
in range(self
.nrows())
354 for j
in range(self
.ncols()) )
357 class HurwitzMatrixAlgebra(MatrixAlgebra
):
359 A class of matrix algebras whose entries come from a Hurwitz
362 For our purposes, we consider "a Hurwitz" algebra to be the real
363 or complex numbers, the quaternions, or the octonions. These are
364 typically also referred to as the Euclidean Hurwitz algebras, or
365 the normed division algebras.
367 By the Cayley-Dickson construction, each Hurwitz algebra is an
368 algebra over the real numbers, so we restrict the scalar field in
369 this case to be real. This also allows us to more accurately
370 produce the generators of the matrix algebra.
372 Element
= HurwitzMatrixAlgebraElement
374 def __init__(self
, n
, entry_algebra
, scalars
, **kwargs
):
375 from sage
.rings
.all
import RR
376 if not scalars
.is_subring(RR
):
377 # Not perfect, but it's what we're using.
378 raise ValueError("scalar field is not real")
380 super().__init
__(n
, entry_algebra
, scalars
, **kwargs
)
382 def entry_algebra_gens(self
):
384 Return a tuple of the generators of (that is, a basis for) the
385 entries of this matrix algebra.
387 This works around the inconsistency in the ``gens()`` methods
388 of the real/complex numbers, quaternions, and octonions.
392 sage: from mjo.hurwitz import Octonions, HurwitzMatrixAlgebra
396 The inclusion of the unit element is inconsistent across
397 (subalgebras of) Hurwitz algebras::
403 sage: QuaternionAlgebra(AA,1,-1).gens()
405 sage: Octonions().gens()
406 (e0, e1, e2, e3, e4, e5, e6, e7)
408 The unit element is always returned by this method, so the
409 sets of generators have cartinality 1,2,4, and 8 as you'd
412 sage: HurwitzMatrixAlgebra(2, AA, AA).entry_algebra_gens()
414 sage: HurwitzMatrixAlgebra(2, QQbar, AA).entry_algebra_gens()
416 sage: Q = QuaternionAlgebra(AA,-1,-1)
417 sage: HurwitzMatrixAlgebra(2, Q, AA).entry_algebra_gens()
419 sage: O = Octonions()
420 sage: HurwitzMatrixAlgebra(2, O, AA).entry_algebra_gens()
421 (e0, e1, e2, e3, e4, e5, e6, e7)
424 gs
= self
.entry_algebra().gens()
425 one
= self
.entry_algebra().one()
429 return (one
,) + tuple(gs
)
433 class OctonionMatrixAlgebra(HurwitzMatrixAlgebra
):
435 The algebra of ``n``-by-``n`` matrices with octonion entries over
436 (a subfield of) the real numbers.
438 The usual matrix spaces in SageMath don't support octonion entries
439 because they assume that the entries of the matrix come from a
440 commutative and associative ring, and the octonions are neither.
444 sage: from mjo.hurwitz import Octonions, OctonionMatrixAlgebra
448 sage: OctonionMatrixAlgebra(3)
449 Module of 3 by 3 matrices with entries in Octonion algebra with base
450 ring Algebraic Real Field over the scalar ring Algebraic Real Field
454 sage: OctonionMatrixAlgebra(3,scalars=QQ)
455 Module of 3 by 3 matrices with entries in Octonion algebra with
456 base ring Rational Field over the scalar ring Rational Field
460 sage: O = Octonions(RR)
461 sage: A = OctonionMatrixAlgebra(1,O)
463 Module of 1 by 1 matrices with entries in Octonion algebra with
464 base ring Real Field with 53 bits of precision over the scalar
465 ring Algebraic Real Field
467 +---------------------+
468 | 1.00000000000000*e0 |
469 +---------------------+
471 (+---------------------+
472 | 1.00000000000000*e0 |
473 +---------------------+,
474 +---------------------+
475 | 1.00000000000000*e1 |
476 +---------------------+,
477 +---------------------+
478 | 1.00000000000000*e2 |
479 +---------------------+,
480 +---------------------+
481 | 1.00000000000000*e3 |
482 +---------------------+,
483 +---------------------+
484 | 1.00000000000000*e4 |
485 +---------------------+,
486 +---------------------+
487 | 1.00000000000000*e5 |
488 +---------------------+,
489 +---------------------+
490 | 1.00000000000000*e6 |
491 +---------------------+,
492 +---------------------+
493 | 1.00000000000000*e7 |
494 +---------------------+)
498 sage: A = OctonionMatrixAlgebra(2)
499 sage: e0,e1,e2,e3,e4,e5,e6,e7 = A.entry_algebra().gens()
500 sage: A([ [e0+e4, e1+e5],
501 ....: [e2-e6, e3-e7] ])
502 +---------+---------+
503 | e0 + e4 | e1 + e5 |
504 +---------+---------+
505 | e2 - e6 | e3 - e7 |
506 +---------+---------+
510 sage: A1 = OctonionMatrixAlgebra(1,scalars=QQ)
511 sage: A2 = OctonionMatrixAlgebra(1,scalars=QQ)
512 sage: cartesian_product([A1,A2])
513 Module of 1 by 1 matrices with entries in Octonion algebra with
514 base ring Rational Field over the scalar ring Rational Field (+)
515 Module of 1 by 1 matrices with entries in Octonion algebra with
516 base ring Rational Field over the scalar ring Rational Field
520 sage: set_random_seed()
521 sage: A = OctonionMatrixAlgebra(ZZ.random_element(10))
522 sage: x = A.random_element()
523 sage: x*A.one() == x and A.one()*x == x
527 def __init__(self
, n
, entry_algebra
=None, scalars
=AA
, **kwargs
):
528 if entry_algebra
is None:
529 entry_algebra
= Octonions(field
=scalars
)
535 class QuaternionMatrixAlgebra(HurwitzMatrixAlgebra
):
537 The algebra of ``n``-by-``n`` matrices with quaternion entries over
538 (a subfield of) the real numbers.
540 The usual matrix spaces in SageMath don't support quaternion entries
541 because they assume that the entries of the matrix come from a
542 commutative ring, and the quaternions are not commutative.
546 sage: from mjo.hurwitz import QuaternionMatrixAlgebra
550 sage: QuaternionMatrixAlgebra(3)
551 Module of 3 by 3 matrices with entries in Quaternion
552 Algebra (-1, -1) with base ring Algebraic Real Field
553 over the scalar ring Algebraic Real Field
557 sage: QuaternionMatrixAlgebra(3,scalars=QQ)
558 Module of 3 by 3 matrices with entries in Quaternion
559 Algebra (-1, -1) with base ring Rational Field over
560 the scalar ring Rational Field
564 sage: Q = QuaternionAlgebra(RDF, -1, -1)
565 sage: A = QuaternionMatrixAlgebra(1,Q)
567 Module of 1 by 1 matrices with entries in Quaternion Algebra
568 (-1.0, -1.0) with base ring Real Double Field over the scalar
569 ring Algebraic Real Field
590 sage: A = QuaternionMatrixAlgebra(2)
591 sage: i,j,k = A.entry_algebra().gens()
592 sage: A([ [1+i, j-2],
602 sage: A1 = QuaternionMatrixAlgebra(1,scalars=QQ)
603 sage: A2 = QuaternionMatrixAlgebra(2,scalars=QQ)
604 sage: cartesian_product([A1,A2])
605 Module of 1 by 1 matrices with entries in Quaternion Algebra
606 (-1, -1) with base ring Rational Field over the scalar ring
607 Rational Field (+) Module of 2 by 2 matrices with entries in
608 Quaternion Algebra (-1, -1) with base ring Rational Field over
609 the scalar ring Rational Field
613 sage: set_random_seed()
614 sage: A = QuaternionMatrixAlgebra(ZZ.random_element(10))
615 sage: x = A.random_element()
616 sage: x*A.one() == x and A.one()*x == x
620 def __init__(self
, n
, entry_algebra
=None, scalars
=AA
, **kwargs
):
621 if entry_algebra
is None:
622 # The -1,-1 gives us the "usual" definition of quaternion
623 from sage
.algebras
.quatalg
.quaternion_algebra
import (
626 entry_algebra
= QuaternionAlgebra(scalars
,-1,-1)
627 super().__init
__(n
, entry_algebra
, scalars
, **kwargs
)
630 class ComplexMatrixAlgebra(HurwitzMatrixAlgebra
):
632 The algebra of ``n``-by-``n`` matrices with complex entries over
633 (a subfield of) the real numbers.
635 These differ from the usual complex matrix spaces in SageMath
636 because the scalar field is real (and not assumed to be the same
637 as the space from which the entries are drawn). The space of
638 `1`-by-`1` complex matrices will have dimension two, for example.
642 sage: from mjo.hurwitz import ComplexMatrixAlgebra
646 sage: ComplexMatrixAlgebra(3)
647 Module of 3 by 3 matrices with entries in Algebraic Field
648 over the scalar ring Algebraic Real Field
652 sage: ComplexMatrixAlgebra(3,scalars=QQ)
653 Module of 3 by 3 matrices with entries in Algebraic Field
654 over the scalar ring Rational Field
658 sage: A = ComplexMatrixAlgebra(1,CC)
660 Module of 1 by 1 matrices with entries in Complex Field with
661 53 bits of precision over the scalar ring Algebraic Real Field
667 (+------------------+
669 +------------------+,
670 +--------------------+
671 | 1.00000000000000*I |
672 +--------------------+)
676 sage: A = ComplexMatrixAlgebra(2)
677 sage: (I,) = A.entry_algebra().gens()
688 sage: A1 = ComplexMatrixAlgebra(1,scalars=QQ)
689 sage: A2 = ComplexMatrixAlgebra(2,scalars=QQ)
690 sage: cartesian_product([A1,A2])
691 Module of 1 by 1 matrices with entries in Algebraic Field over
692 the scalar ring Rational Field (+) Module of 2 by 2 matrices with
693 entries in Algebraic Field over the scalar ring Rational Field
697 sage: set_random_seed()
698 sage: A = ComplexMatrixAlgebra(ZZ.random_element(10))
699 sage: x = A.random_element()
700 sage: x*A.one() == x and A.one()*x == x
704 def __init__(self
, n
, entry_algebra
=None, scalars
=AA
, **kwargs
):
705 if entry_algebra
is None:
706 from sage
.rings
.all
import QQbar
707 entry_algebra
= QQbar
708 super().__init
__(n
, entry_algebra
, scalars
, **kwargs
)