]>
gitweb.michael.orlitzky.com - sage.d.git/blob - mjo/octonions.py
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 class Octonion(IndexedFreeModuleElement
):
15 sage: from mjo.octonions import Octonions
20 sage: x = sum(O.gens())
22 e0 - e1 - e2 - e3 - e4 - e5 - e6 - e7
26 Conjugating twice gets you the original element::
28 sage: set_random_seed()
30 sage: x = O.random_element()
31 sage: x.conjugate().conjugate() == x
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.octonions 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.octonions 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.octonions 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()
144 Return the inverse of this element if it exists.
148 sage: from mjo.octonions import Octonions
152 sage: O = Octonions()
153 sage: x = sum(O.gens())
154 sage: x*x.inverse() == O.one()
159 sage: O = Octonions()
160 sage: O.one().inverse() == O.one()
165 sage: set_random_seed()
166 sage: O = Octonions()
167 sage: x = O.random_element()
168 sage: x.is_zero() or ( x*x.inverse() == O.one() )
173 raise ValueError("zero is not invertible")
174 return self
.conjugate()/self
._norm
_squared
()
177 def cayley_dickson(self
, Q
=None):
179 Return the Cayley-Dickson representation of this element in terms
180 of the quaternion algebra ``Q``.
182 The Cayley-Dickson representation is an identification of
183 octionions `x` and `y` with pairs of quaternions `(a,b)` and
184 `(c,d)` respectively such that:
186 * `x + y = (a+b, c+d)`
187 * `xy` = (ac - \bar{d}*b, da + b\bar{c})`
190 where `\bar{x}` denotes the conjugate of `x`.
194 sage: from mjo.octonions import Octonions
198 sage: O = Octonions()
199 sage: x = sum(O.gens())
200 sage: x.cayley_dickson()
201 (1 + i + j + k, 1 + i + j + k)
205 Q
= QuaternionAlgebra(self
.base_ring(), -1, -1)
208 a
= (self
.coefficient(0)*Q
.one() +
209 self
.coefficient(1)*i
+
210 self
.coefficient(2)*j
+
211 self
.coefficient(3)*k
)
212 b
= (self
.coefficient(4)*Q
.one() +
213 self
.coefficient(5)*i
+
214 self
.coefficient(6)*j
+
215 self
.coefficient(7)*k
)
217 from sage
.categories
.sets_cat
import cartesian_product
218 P
= cartesian_product([Q
,Q
])
222 class Octonions(CombinatorialFreeModule
):
226 sage: from mjo.octonions import Octonions
231 Octonion algebra with base ring Algebraic Real Field
232 sage: Octonions(field=QQ)
233 Octonion algebra with base ring Rational Field
240 # Not associative, not commutative
241 category
= MagmaticAlgebras(field
).FiniteDimensional()
242 category
= category
.WithBasis().Unital()
244 super().__init
__(field
,
246 element_class
=Octonion
,
251 # The product of each basis element is plus/minus another
252 # basis element that can simply be looked up on
253 # https://en.wikipedia.org/wiki/Octonion
254 e0
, e1
, e2
, e3
, e4
, e5
, e6
, e7
= self
.gens()
255 self
._multiplication
_table
= (
256 (e0
, e1
, e2
, e3
, e4
, e5
, e6
, e7
),
257 (e1
,-e0
, e3
,-e2
, e5
,-e4
,-e7
, e6
),
258 (e2
,-e3
,-e0
, e1
, e6
, e7
,-e4
,-e5
),
259 (e3
, e2
,-e1
,-e0
, e7
,-e6
, e5
,-e4
),
260 (e4
,-e5
,-e6
,-e7
,-e0
, e1
, e2
, e3
),
261 (e5
, e4
,-e7
, e6
,-e1
,-e0
,-e3
, e2
),
262 (e6
, e7
, e4
,-e5
,-e2
, e3
,-e0
,-e1
),
263 (e7
,-e6
, e5
, e4
,-e3
,-e2
, e1
,-e0
),
266 def product_on_basis(self
, i
, j
):
267 return self
._multiplication
_table
[i
][j
]
271 Return the monomial index (basis element) corresponding to the
272 octonion unit element.
276 sage: from mjo.octonions import Octonions
280 This gives the correct unit element::
282 sage: set_random_seed()
283 sage: O = Octonions()
284 sage: x = O.random_element()
285 sage: x*O.one() == x and O.one()*x == x
292 return ("Octonion algebra with base ring %s" % self
.base_ring())
294 def multiplication_table(self
):
296 Return a visual representation of this algebra's multiplication
297 table (on basis elements).
301 sage: from mjo.octonions import Octonions
305 The multiplication table is what Wikipedia says it is::
307 sage: Octonions().multiplication_table()
308 +----++----+-----+-----+-----+-----+-----+-----+-----+
309 | * || e0 | e1 | e2 | e3 | e4 | e5 | e6 | e7 |
310 +====++====+=====+=====+=====+=====+=====+=====+=====+
311 | e0 || e0 | e1 | e2 | e3 | e4 | e5 | e6 | e7 |
312 +----++----+-----+-----+-----+-----+-----+-----+-----+
313 | e1 || e1 | -e0 | e3 | -e2 | e5 | -e4 | -e7 | e6 |
314 +----++----+-----+-----+-----+-----+-----+-----+-----+
315 | e2 || e2 | -e3 | -e0 | e1 | e6 | e7 | -e4 | -e5 |
316 +----++----+-----+-----+-----+-----+-----+-----+-----+
317 | e3 || e3 | e2 | -e1 | -e0 | e7 | -e6 | e5 | -e4 |
318 +----++----+-----+-----+-----+-----+-----+-----+-----+
319 | e4 || e4 | -e5 | -e6 | -e7 | -e0 | e1 | e2 | e3 |
320 +----++----+-----+-----+-----+-----+-----+-----+-----+
321 | e5 || e5 | e4 | -e7 | e6 | -e1 | -e0 | -e3 | e2 |
322 +----++----+-----+-----+-----+-----+-----+-----+-----+
323 | e6 || e6 | e7 | e4 | -e5 | -e2 | e3 | -e0 | -e1 |
324 +----++----+-----+-----+-----+-----+-----+-----+-----+
325 | e7 || e7 | -e6 | e5 | e4 | -e3 | -e2 | e1 | -e0 |
326 +----++----+-----+-----+-----+-----+-----+-----+-----+
330 # Prepend the header row.
331 M
= [["*"] + list(self
.gens())]
333 # And to each subsequent row, prepend an entry that belongs to
334 # the left-side "header column."
335 M
+= [ [self
.monomial(i
)] + [ self
.monomial(i
)*self
.monomial(j
)
339 return table(M
, header_row
=True, header_column
=True, frame
=True)
342 class OctonionMatrix(IndexedFreeModuleElement
):
344 return self
.parent().nrows()
348 def to_nested_list(self
):
352 sage: from mjo.octonions import OctonionMatrixAlgebra
356 sage: MS = OctonionMatrixAlgebra(3)
357 sage: E00e0 = MS.gens()[0]
366 sage: E00e3 = MS.gens()[3]
375 sage: (E00e0 + 2*E00e3).to_nested_list()
376 [[e0 + 2*e3, 0, 0], [0, 0, 0], [0, 0, 0]]
379 zero
= self
.parent().entry_algebra().zero()
380 l
= [[zero
for j
in range(self
.ncols())] for i
in range(self
.nrows())]
381 for (k
,v
) in self
.monomial_coefficients().items():
390 sage: from mjo.octonions import OctonionMatrixAlgebra
394 sage: OctonionMatrixAlgebra(3).one()
404 return table(self
.to_nested_list(), frame
=True)._repr
_()
409 Return one long list of this matrix's entries.
413 sage: from mjo.octonions import OctonionMatrixAlgebra
417 sage: MS = OctonionMatrixAlgebra(3)
418 sage: E00e0 = MS.gens()[0]
419 sage: E00e3 = MS.gens()[3]
420 sage: (E00e0 + 2*E00e3).to_nested_list()
421 [[e0 + 2*e3, 0, 0], [0, 0, 0], [0, 0, 0]]
422 sage: (E00e0 + 2*E00e3).list()
423 [e0 + 2*e3, 0, 0, 0, 0, 0, 0, 0, 0]
426 return sum( self
.to_nested_list(), [] )
429 def __getitem__(self
, indices
):
434 sage: from mjo.octonions import OctonionMatrixAlgebra
438 sage: MS = OctonionMatrixAlgebra(2)
451 return self
.to_nested_list()[i
][j
]
458 sage: from mjo.octonions import OctonionMatrixAlgebra
462 sage: MS = OctonionMatrixAlgebra(3)
463 sage: MS.one().trace()
467 zero
= self
.parent().entry_algebra().zero()
468 return sum( (self
[i
,i
] for i
in range(self
.nrows())), zero
)
470 def matrix_space(self
):
475 sage: from mjo.octonions import OctonionMatrixAlgebra
479 sage: set_random_seed()
480 sage: MS = OctonionMatrixAlgebra(2)
481 sage: MS.random_element().matrix_space()
482 Module of 2 by 2 matrices with octonion entries over the
483 scalar ring Algebraic Real Field
488 def is_hermitian(self
):
493 sage: from mjo.octonions import OctonionMatrixAlgebra
497 sage: MS = OctonionMatrixAlgebra(3)
498 sage: MS.one().is_hermitian()
502 return all( self
[i
,j
] == self
[j
,i
].conjugate()
503 for i
in range(self
.nrows())
504 for j
in range(self
.ncols()) )
506 class OctonionMatrixAlgebra(CombinatorialFreeModule
):
508 The algebra of ``n``-by-``n`` matrices with octonion entries over
509 (a subfield of) the real numbers.
511 The usual matrix spaces in SageMath don't support octonion entries
512 because they assume that the entries of the matrix come from a
513 commutative and associative ring (i.e. very NOT the octonions).
515 Element
= OctonionMatrix
517 def __init__(self
, n
, scalars
=AA
, prefix
="E", **kwargs
):
518 # Not associative, not commutative
519 category
= MagmaticAlgebras(scalars
).FiniteDimensional()
520 category
= category
.WithBasis().Unital()
524 # Since the scalar ring is real but the entries are not,
525 # sticking a "1" in each position doesn't give us a basis for
526 # the space. We actually need to stick each of e0, e1, ... (a
527 # basis for the entry algebra itself) into each position.
528 from sage
.sets
.finite_enumerated_set
import FiniteEnumeratedSet
529 from sage
.categories
.sets_cat
import cartesian_product
531 I
= FiniteEnumeratedSet(range(n
))
532 J
= FiniteEnumeratedSet(range(n
))
533 self
._entry
_algebra
= Octonions(field
=scalars
)
534 entry_basis
= self
._entry
_algebra
.gens()
536 basis_indices
= cartesian_product([I
,J
,entry_basis
])
537 super().__init
__(scalars
,
544 return ("Module of %d by %d matrices with octonion entries"
545 " over the scalar ring %s" %
546 (self
.nrows(), self
.ncols(), self
.base_ring()) )
548 def entry_algebra(self
):
550 Return the algebra that our elements' entries come from.
552 return self
._entry
_algebra
558 def product_on_basis(self
, mon1
, mon2
):
562 return self
.monomial((i
,l
,oct1
*oct2
))
570 sage: from mjo.octonions import OctonionMatrixAlgebra
574 sage: set_random_seed()
575 sage: MS = OctonionMatrixAlgebra(ZZ.random_element(10))
576 sage: x = MS.random_element()
577 sage: x*MS.one() == x and MS.one()*x == x
581 return sum( (self
.monomial((i
,i
,self
.entry_algebra().one()))
582 for i
in range(self
.nrows()) ),
585 def from_list(self
, entries
):
587 Construct an element of this algebra from a list of lists of
592 sage: from mjo.octonions import Octonions, OctonionMatrixAlgebra
596 sage: O = Octonions(QQ)
597 sage: e0,e1,e2,e3,e4,e5,e6,e7 = O.gens()
598 sage: MS = OctonionMatrixAlgebra(2)
599 sage: MS.from_list([ [e0+e4, e1+e5],
600 ....: [e2-e6, e3-e7] ])
601 +---------+---------+
602 | e0 + e4 | e1 + e5 |
603 +---------+---------+
604 | e2 - e6 | e3 - e7 |
605 +---------+---------+
611 ncols
= len(entries
[0])
613 if (not all( len(r
) == ncols
for r
in entries
)) or (ncols
!= nrows
):
614 raise ValueError("list must be square")
617 # We have to pass through vectors to convert from the
618 # given octonion algebra to ours. Otherwise we can fail
619 # to convert an element of (for example) Octonions(QQ)
621 return self
.entry_algebra().from_vector(e_ij
.to_vector())
623 return sum( (self
.monomial( (i
,j
, convert(entries
[i
][j
])) )
624 for i
in range(nrows
)
625 for j
in range(ncols
) ),