1 from sage
.misc
.cachefunc
import cached_method
2 from sage
.algebras
.quatalg
.quaternion_algebra
import QuaternionAlgebra
3 from sage
.combinat
.free_module
import CombinatorialFreeModule
4 from sage
.modules
.with_basis
.indexed_element
import IndexedFreeModuleElement
5 from sage
.categories
.magmatic_algebras
import MagmaticAlgebras
6 from sage
.rings
.all
import AA
, ZZ
7 from sage
.matrix
.matrix_space
import MatrixSpace
8 from sage
.misc
.table
import table
10 from mjo
.matrix_algebra
import MatrixAlgebra
, MatrixAlgebraElement
12 class Octonion(IndexedFreeModuleElement
):
17 sage: from mjo.hurwitz import Octonions
22 sage: x = sum(O.gens())
24 e0 - e1 - e2 - e3 - e4 - e5 - e6 - e7
28 Conjugating twice gets you the original element::
30 sage: set_random_seed()
32 sage: x = O.random_element()
33 sage: x.conjugate().conjugate() == x
37 C
= MatrixSpace(ZZ
,8).diagonal_matrix((1,-1,-1,-1,-1,-1,-1,-1))
38 return self
.parent().from_vector(C
*self
.to_vector())
42 Return the real part of this octonion.
44 The real part of an octonion is its projection onto the span
45 of the first generator. In other words, the "first dimension"
46 is real and the others are imaginary.
50 sage: from mjo.hurwitz import Octonions
55 sage: x = sum(O.gens())
61 This method is idempotent::
63 sage: set_random_seed()
65 sage: x = O.random_element()
66 sage: x.real().real() == x.real()
70 return (self
+ self
.conjugate())/2
74 Return the imaginary part of this octonion.
76 The imaginary part of an octonion is its projection onto the
77 orthogonal complement of the span of the first generator. In
78 other words, the "first dimension" is real and the others are
83 sage: from mjo.hurwitz import Octonions
88 sage: x = sum(O.gens())
90 e1 + e2 + e3 + e4 + e5 + e6 + e7
94 This method is idempotent::
96 sage: set_random_seed()
98 sage: x = O.random_element()
99 sage: x.imag().imag() == x.imag()
103 return (self
- self
.conjugate())/2
105 def _norm_squared(self
):
106 return (self
*self
.conjugate()).coefficient(0)
110 Return the norm of this octonion.
114 sage: from mjo.hurwitz import Octonions
118 sage: O = Octonions()
124 The norm is nonnegative and belongs to the base field::
126 sage: set_random_seed()
127 sage: O = Octonions()
128 sage: n = O.random_element().norm()
129 sage: n >= 0 and n in O.base_ring()
132 The norm is homogeneous::
134 sage: set_random_seed()
135 sage: O = Octonions()
136 sage: x = O.random_element()
137 sage: alpha = O.base_ring().random_element()
138 sage: (alpha*x).norm() == alpha.abs()*x.norm()
142 return self
._norm
_squared
().sqrt()
144 # The absolute value notation is typically used for complex numbers...
145 # and norm() isn't supported in AA, so this lets us use abs() in all
146 # of the division algebras we need.
151 Return the inverse of this element if it exists.
155 sage: from mjo.hurwitz import Octonions
159 sage: O = Octonions()
160 sage: x = sum(O.gens())
161 sage: x*x.inverse() == O.one()
166 sage: O = Octonions()
167 sage: O.one().inverse() == O.one()
172 sage: set_random_seed()
173 sage: O = Octonions()
174 sage: x = O.random_element()
175 sage: x.is_zero() or ( x*x.inverse() == O.one() )
180 raise ValueError("zero is not invertible")
181 return self
.conjugate()/self
._norm
_squared
()
184 def cayley_dickson(self
, Q
=None):
186 Return the Cayley-Dickson representation of this element in terms
187 of the quaternion algebra ``Q``.
189 The Cayley-Dickson representation is an identification of
190 octionions `x` and `y` with pairs of quaternions `(a,b)` and
191 `(c,d)` respectively such that:
193 * `x + y = (a+b, c+d)`
194 * `xy` = (ac - \bar{d}*b, da + b\bar{c})`
197 where `\bar{x}` denotes the conjugate of `x`.
201 sage: from mjo.hurwitz import Octonions
205 sage: O = Octonions()
206 sage: x = sum(O.gens())
207 sage: x.cayley_dickson()
208 (1 + i + j + k, 1 + i + j + k)
212 Q
= QuaternionAlgebra(self
.base_ring(), -1, -1)
215 a
= (self
.coefficient(0)*Q
.one() +
216 self
.coefficient(1)*i
+
217 self
.coefficient(2)*j
+
218 self
.coefficient(3)*k
)
219 b
= (self
.coefficient(4)*Q
.one() +
220 self
.coefficient(5)*i
+
221 self
.coefficient(6)*j
+
222 self
.coefficient(7)*k
)
224 from sage
.categories
.sets_cat
import cartesian_product
225 P
= cartesian_product([Q
,Q
])
229 class Octonions(CombinatorialFreeModule
):
233 sage: from mjo.hurwitz import Octonions
238 Octonion algebra with base ring Algebraic Real Field
239 sage: Octonions(field=QQ)
240 Octonion algebra with base ring Rational Field
247 # Not associative, not commutative
248 category
= MagmaticAlgebras(field
).FiniteDimensional()
249 category
= category
.WithBasis().Unital()
251 super().__init
__(field
,
253 element_class
=Octonion
,
258 # The product of each basis element is plus/minus another
259 # basis element that can simply be looked up on
260 # https://en.wikipedia.org/wiki/Octonion
261 e0
, e1
, e2
, e3
, e4
, e5
, e6
, e7
= self
.gens()
262 self
._multiplication
_table
= (
263 (e0
, e1
, e2
, e3
, e4
, e5
, e6
, e7
),
264 (e1
,-e0
, e3
,-e2
, e5
,-e4
,-e7
, e6
),
265 (e2
,-e3
,-e0
, e1
, e6
, e7
,-e4
,-e5
),
266 (e3
, e2
,-e1
,-e0
, e7
,-e6
, e5
,-e4
),
267 (e4
,-e5
,-e6
,-e7
,-e0
, e1
, e2
, e3
),
268 (e5
, e4
,-e7
, e6
,-e1
,-e0
,-e3
, e2
),
269 (e6
, e7
, e4
,-e5
,-e2
, e3
,-e0
,-e1
),
270 (e7
,-e6
, e5
, e4
,-e3
,-e2
, e1
,-e0
),
273 def product_on_basis(self
, i
, j
):
274 return self
._multiplication
_table
[i
][j
]
278 Return the monomial index (basis element) corresponding to the
279 octonion unit element.
283 sage: from mjo.hurwitz import Octonions
287 This gives the correct unit element::
289 sage: set_random_seed()
290 sage: O = Octonions()
291 sage: x = O.random_element()
292 sage: x*O.one() == x and O.one()*x == x
299 return ("Octonion algebra with base ring %s" % self
.base_ring())
301 def multiplication_table(self
):
303 Return a visual representation of this algebra's multiplication
304 table (on basis elements).
308 sage: from mjo.hurwitz import Octonions
312 The multiplication table is what Wikipedia says it is::
314 sage: Octonions().multiplication_table()
315 +----++----+-----+-----+-----+-----+-----+-----+-----+
316 | * || e0 | e1 | e2 | e3 | e4 | e5 | e6 | e7 |
317 +====++====+=====+=====+=====+=====+=====+=====+=====+
318 | e0 || e0 | e1 | e2 | e3 | e4 | e5 | e6 | e7 |
319 +----++----+-----+-----+-----+-----+-----+-----+-----+
320 | e1 || e1 | -e0 | e3 | -e2 | e5 | -e4 | -e7 | e6 |
321 +----++----+-----+-----+-----+-----+-----+-----+-----+
322 | e2 || e2 | -e3 | -e0 | e1 | e6 | e7 | -e4 | -e5 |
323 +----++----+-----+-----+-----+-----+-----+-----+-----+
324 | e3 || e3 | e2 | -e1 | -e0 | e7 | -e6 | e5 | -e4 |
325 +----++----+-----+-----+-----+-----+-----+-----+-----+
326 | e4 || e4 | -e5 | -e6 | -e7 | -e0 | e1 | e2 | e3 |
327 +----++----+-----+-----+-----+-----+-----+-----+-----+
328 | e5 || e5 | e4 | -e7 | e6 | -e1 | -e0 | -e3 | e2 |
329 +----++----+-----+-----+-----+-----+-----+-----+-----+
330 | e6 || e6 | e7 | e4 | -e5 | -e2 | e3 | -e0 | -e1 |
331 +----++----+-----+-----+-----+-----+-----+-----+-----+
332 | e7 || e7 | -e6 | e5 | e4 | -e3 | -e2 | e1 | -e0 |
333 +----++----+-----+-----+-----+-----+-----+-----+-----+
337 # Prepend the header row.
338 M
= [["*"] + list(self
.gens())]
340 # And to each subsequent row, prepend an entry that belongs to
341 # the left-side "header column."
342 M
+= [ [self
.monomial(i
)] + [ self
.monomial(i
)*self
.monomial(j
)
346 return table(M
, header_row
=True, header_column
=True, frame
=True)
352 class HurwitzMatrixAlgebraElement(MatrixAlgebraElement
):
353 def is_hermitian(self
):
358 sage: from mjo.hurwitz import HurwitzMatrixAlgebra
362 sage: A = HurwitzMatrixAlgebra(QQbar, ZZ, 2)
363 sage: M = A([ [ 0,I],
365 sage: M.is_hermitian()
369 return all( self
[i
,j
] == self
[j
,i
].conjugate()
370 for i
in range(self
.nrows())
371 for j
in range(self
.ncols()) )
374 class HurwitzMatrixAlgebra(MatrixAlgebra
):
376 A class of matrix algebras whose entries come from a Hurwitz
379 For our purposes, we consider "a Hurwitz" algebra to be the real
380 or complex numbers, the quaternions, or the octonions. These are
381 typically also referred to as the Euclidean Hurwitz algebras, or
382 the normed division algebras.
384 By the Cayley-Dickson construction, each Hurwitz algebra is an
385 algebra over the real numbers, so we restrict the scalar field in
386 this case to be real. This also allows us to more accurately
387 produce the generators of the matrix algebra.
389 Element
= HurwitzMatrixAlgebraElement
391 def __init__(self
, entry_algebra
, scalars
, n
, **kwargs
):
392 from sage
.rings
.all
import RR
393 if not scalars
.is_subring(RR
):
394 # Not perfect, but it's what we're using.
395 raise ValueError("scalar field is not real")
397 super().__init
__(entry_algebra
, scalars
, n
, **kwargs
)
399 def entry_algebra_gens(self
):
401 Return the generators of (that is, a basis for) the entries of
404 This works around the inconsistency in the ``gens()`` methods
405 of the real/complex numbers, quaternions, and octonions.
409 sage: from mjo.hurwitz import Octonions, HurwitzMatrixAlgebra
413 The inclusion of the unit element is inconsistent across
414 (subalgebras of) Hurwitz algebras::
420 sage: QuaternionAlgebra(AA,1,-1).gens()
422 sage: Octonions().gens()
423 (e0, e1, e2, e3, e4, e5, e6, e7)
425 The unit element is always returned by this method, so the
426 sets of generators have cartinality 1,2,4, and 8 as you'd
429 sage: HurwitzMatrixAlgebra(AA, AA, 2).entry_algebra_gens()
431 sage: HurwitzMatrixAlgebra(QQbar, AA, 2).entry_algebra_gens()
433 sage: Q = QuaternionAlgebra(AA,-1,-1)
434 sage: HurwitzMatrixAlgebra(Q, AA, 2).entry_algebra_gens()
436 sage: O = Octonions()
437 sage: HurwitzMatrixAlgebra(O, AA, 2).entry_algebra_gens()
438 (e0, e1, e2, e3, e4, e5, e6, e7)
441 gs
= self
.entry_algebra().gens()
442 one
= self
.entry_algebra().one()
446 return (one
,) + tuple(gs
)
450 class OctonionMatrixAlgebra(HurwitzMatrixAlgebra
):
452 The algebra of ``n``-by-``n`` matrices with octonion entries over
453 (a subfield of) the real numbers.
455 The usual matrix spaces in SageMath don't support octonion entries
456 because they assume that the entries of the matrix come from a
457 commutative and associative ring, and the octonions are neither.
461 sage: from mjo.hurwitz import OctonionMatrixAlgebra
465 sage: OctonionMatrixAlgebra(3)
466 Module of 3 by 3 matrices with entries in Octonion algebra with base
467 ring Algebraic Real Field over the scalar ring Algebraic Real Field
468 sage: OctonionMatrixAlgebra(3,QQ)
469 Module of 3 by 3 matrices with entries in Octonion algebra with base
470 ring Rational Field over the scalar ring Rational Field
474 sage: A = OctonionMatrixAlgebra(2)
475 sage: e0,e1,e2,e3,e4,e5,e6,e7 = A.entry_algebra().gens()
476 sage: A([ [e0+e4, e1+e5],
477 ....: [e2-e6, e3-e7] ])
478 +---------+---------+
479 | e0 + e4 | e1 + e5 |
480 +---------+---------+
481 | e2 - e6 | e3 - e7 |
482 +---------+---------+
486 sage: A1 = OctonionMatrixAlgebra(1,QQ)
487 sage: A2 = OctonionMatrixAlgebra(1,QQ)
488 sage: cartesian_product([A1,A2])
489 Module of 1 by 1 matrices with entries in Octonion algebra with
490 base ring Rational Field over the scalar ring Rational Field (+)
491 Module of 1 by 1 matrices with entries in Octonion algebra with
492 base ring Rational Field over the scalar ring Rational Field
496 sage: set_random_seed()
497 sage: A = OctonionMatrixAlgebra(ZZ.random_element(10))
498 sage: x = A.random_element()
499 sage: x*A.one() == x and A.one()*x == x
503 def __init__(self
, n
, scalars
=AA
, prefix
="E", **kwargs
):
504 super().__init
__(Octonions(field
=scalars
),
510 class QuaternionMatrixAlgebra(HurwitzMatrixAlgebra
):
512 The algebra of ``n``-by-``n`` matrices with quaternion entries over
513 (a subfield of) the real numbers.
515 The usual matrix spaces in SageMath don't support quaternion entries
516 because they assume that the entries of the matrix come from a
517 commutative ring, and the quaternions are not commutative.
521 sage: from mjo.hurwitz import QuaternionMatrixAlgebra
525 sage: QuaternionMatrixAlgebra(3)
526 Module of 3 by 3 matrices with entries in Quaternion
527 Algebra (-1, -1) with base ring Algebraic Real Field
528 over the scalar ring Algebraic Real Field
529 sage: QuaternionMatrixAlgebra(3,QQ)
530 Module of 3 by 3 matrices with entries in Quaternion
531 Algebra (-1, -1) with base ring Rational Field over
532 the scalar ring Rational Field
536 sage: A = QuaternionMatrixAlgebra(2)
537 sage: i,j,k = A.entry_algebra().gens()
538 sage: A([ [1+i, j-2],
548 sage: A1 = QuaternionMatrixAlgebra(1,QQ)
549 sage: A2 = QuaternionMatrixAlgebra(2,QQ)
550 sage: cartesian_product([A1,A2])
551 Module of 1 by 1 matrices with entries in Quaternion Algebra
552 (-1, -1) with base ring Rational Field over the scalar ring
553 Rational Field (+) Module of 2 by 2 matrices with entries in
554 Quaternion Algebra (-1, -1) with base ring Rational Field over
555 the scalar ring Rational Field
559 sage: set_random_seed()
560 sage: A = QuaternionMatrixAlgebra(ZZ.random_element(10))
561 sage: x = A.random_element()
562 sage: x*A.one() == x and A.one()*x == x
566 def __init__(self
, n
, scalars
=AA
, **kwargs
):
567 # The -1,-1 gives us the "usual" definition of quaternion
568 Q
= QuaternionAlgebra(scalars
,-1,-1)
569 super().__init
__(Q
, scalars
, n
, **kwargs
)