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 is_hermitian(self
):
314 sage: from mjo.hurwitz import HurwitzMatrixAlgebra
318 sage: A = HurwitzMatrixAlgebra(QQbar, ZZ, 2)
319 sage: M = A([ [ 0,I],
321 sage: M.is_hermitian()
325 return all( self
[i
,j
] == self
[j
,i
].conjugate()
326 for i
in range(self
.nrows())
327 for j
in range(self
.ncols()) )
330 class HurwitzMatrixAlgebra(MatrixAlgebra
):
332 A class of matrix algebras whose entries come from a Hurwitz
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.
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.
345 Element
= HurwitzMatrixAlgebraElement
347 def __init__(self
, entry_algebra
, scalars
, n
, **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")
353 super().__init
__(entry_algebra
, scalars
, n
, **kwargs
)
355 def entry_algebra_gens(self
):
357 Return a tuple of the generators of (that is, a basis for) the
358 entries of this matrix algebra.
360 This works around the inconsistency in the ``gens()`` methods
361 of the real/complex numbers, quaternions, and octonions.
365 sage: from mjo.hurwitz import Octonions, HurwitzMatrixAlgebra
369 The inclusion of the unit element is inconsistent across
370 (subalgebras of) Hurwitz algebras::
376 sage: QuaternionAlgebra(AA,1,-1).gens()
378 sage: Octonions().gens()
379 (e0, e1, e2, e3, e4, e5, e6, e7)
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
385 sage: HurwitzMatrixAlgebra(AA, AA, 2).entry_algebra_gens()
387 sage: HurwitzMatrixAlgebra(QQbar, AA, 2).entry_algebra_gens()
389 sage: Q = QuaternionAlgebra(AA,-1,-1)
390 sage: HurwitzMatrixAlgebra(Q, AA, 2).entry_algebra_gens()
392 sage: O = Octonions()
393 sage: HurwitzMatrixAlgebra(O, AA, 2).entry_algebra_gens()
394 (e0, e1, e2, e3, e4, e5, e6, e7)
397 gs
= self
.entry_algebra().gens()
398 one
= self
.entry_algebra().one()
402 return (one
,) + tuple(gs
)
406 class OctonionMatrixAlgebra(HurwitzMatrixAlgebra
):
408 The algebra of ``n``-by-``n`` matrices with octonion entries over
409 (a subfield of) the real numbers.
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.
417 sage: from mjo.hurwitz import OctonionMatrixAlgebra
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 sage: OctonionMatrixAlgebra(3,QQ)
425 Module of 3 by 3 matrices with entries in Octonion algebra with base
426 ring Rational Field over the scalar ring Rational Field
430 sage: A = OctonionMatrixAlgebra(2)
431 sage: e0,e1,e2,e3,e4,e5,e6,e7 = A.entry_algebra().gens()
432 sage: A([ [e0+e4, e1+e5],
433 ....: [e2-e6, e3-e7] ])
434 +---------+---------+
435 | e0 + e4 | e1 + e5 |
436 +---------+---------+
437 | e2 - e6 | e3 - e7 |
438 +---------+---------+
442 sage: A1 = OctonionMatrixAlgebra(1,QQ)
443 sage: A2 = OctonionMatrixAlgebra(1,QQ)
444 sage: cartesian_product([A1,A2])
445 Module of 1 by 1 matrices with entries in Octonion algebra with
446 base ring Rational Field over the scalar ring Rational Field (+)
447 Module of 1 by 1 matrices with entries in Octonion algebra with
448 base ring Rational Field over the scalar ring Rational Field
452 sage: set_random_seed()
453 sage: A = OctonionMatrixAlgebra(ZZ.random_element(10))
454 sage: x = A.random_element()
455 sage: x*A.one() == x and A.one()*x == x
459 def __init__(self
, n
, scalars
=AA
, prefix
="E", **kwargs
):
460 super().__init
__(Octonions(field
=scalars
),
466 class QuaternionMatrixAlgebra(HurwitzMatrixAlgebra
):
468 The algebra of ``n``-by-``n`` matrices with quaternion entries over
469 (a subfield of) the real numbers.
471 The usual matrix spaces in SageMath don't support quaternion entries
472 because they assume that the entries of the matrix come from a
473 commutative ring, and the quaternions are not commutative.
477 sage: from mjo.hurwitz import QuaternionMatrixAlgebra
481 sage: QuaternionMatrixAlgebra(3)
482 Module of 3 by 3 matrices with entries in Quaternion
483 Algebra (-1, -1) with base ring Algebraic Real Field
484 over the scalar ring Algebraic Real Field
485 sage: QuaternionMatrixAlgebra(3,QQ)
486 Module of 3 by 3 matrices with entries in Quaternion
487 Algebra (-1, -1) with base ring Rational Field over
488 the scalar ring Rational Field
492 sage: A = QuaternionMatrixAlgebra(2)
493 sage: i,j,k = A.entry_algebra().gens()
494 sage: A([ [1+i, j-2],
504 sage: A1 = QuaternionMatrixAlgebra(1,QQ)
505 sage: A2 = QuaternionMatrixAlgebra(2,QQ)
506 sage: cartesian_product([A1,A2])
507 Module of 1 by 1 matrices with entries in Quaternion Algebra
508 (-1, -1) with base ring Rational Field over the scalar ring
509 Rational Field (+) Module of 2 by 2 matrices with entries in
510 Quaternion Algebra (-1, -1) with base ring Rational Field over
511 the scalar ring Rational Field
515 sage: set_random_seed()
516 sage: A = QuaternionMatrixAlgebra(ZZ.random_element(10))
517 sage: x = A.random_element()
518 sage: x*A.one() == x and A.one()*x == x
522 def __init__(self
, n
, scalars
=AA
, **kwargs
):
523 # The -1,-1 gives us the "usual" definition of quaternion
524 from sage
.algebras
.quatalg
.quaternion_algebra
import QuaternionAlgebra
525 Q
= QuaternionAlgebra(scalars
,-1,-1)
526 super().__init
__(Q
, scalars
, n
, **kwargs
)
529 class ComplexMatrixAlgebra(HurwitzMatrixAlgebra
):
531 The algebra of ``n``-by-``n`` matrices with complex entries over
532 (a subfield of) the real numbers.
534 These differ from the usual complex matrix spaces in SageMath
535 because the scalar field is real (and not assumed to be the same
536 as the space from which the entries are drawn). The space of
537 `1`-by-`1` complex matrices will have dimension two, for example.
541 sage: from mjo.hurwitz import ComplexMatrixAlgebra
545 sage: ComplexMatrixAlgebra(3)
546 Module of 3 by 3 matrices with entries in Algebraic Field
547 over the scalar ring Algebraic Real Field
548 sage: ComplexMatrixAlgebra(3,QQ)
549 Module of 3 by 3 matrices with entries in Algebraic Field
550 over the scalar ring Rational Field
554 sage: A = ComplexMatrixAlgebra(2)
555 sage: (I,) = A.entry_algebra().gens()
566 sage: A1 = ComplexMatrixAlgebra(1,QQ)
567 sage: A2 = ComplexMatrixAlgebra(2,QQ)
568 sage: cartesian_product([A1,A2])
569 Module of 1 by 1 matrices with entries in Algebraic Field over
570 the scalar ring Rational Field (+) Module of 2 by 2 matrices with
571 entries in Algebraic Field over the scalar ring Rational Field
575 sage: set_random_seed()
576 sage: A = ComplexMatrixAlgebra(ZZ.random_element(10))
577 sage: x = A.random_element()
578 sage: x*A.one() == x and A.one()*x == x
582 def __init__(self
, n
, scalars
=AA
, **kwargs
):
583 from sage
.rings
.all
import QQbar
584 super().__init
__(QQbar
, scalars
, n
, **kwargs
)