]>
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()
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_list()
376 [[e0 + 2*e3, 0, 0], [0, 0, 0], [0, 0, 0]]
379 zero
= self
.parent().octonions().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_list(), frame
=True)._repr
_()
407 def __getitem__(self
, indices
):
412 sage: from mjo.octonions import OctonionMatrixAlgebra
416 sage: MS = OctonionMatrixAlgebra(2)
429 return self
.to_list()[i
][j
]
436 sage: from mjo.octonions import OctonionMatrixAlgebra
440 sage: MS = OctonionMatrixAlgebra(3)
441 sage: MS.one().trace()
445 zero
= self
.parent().octonions().zero()
446 return sum( (self
[i
,i
] for i
in range(self
.nrows())), zero
)
448 def matrix_space(self
):
453 sage: from mjo.octonions import OctonionMatrixAlgebra
457 sage: set_random_seed()
458 sage: MS = OctonionMatrixAlgebra(2)
459 sage: MS.random_element().matrix_space()
460 Module of 2 by 2 matrices with octonion entries over the
461 scalar ring Algebraic Real Field
467 class OctonionMatrixAlgebra(CombinatorialFreeModule
):
469 The algebra of ``n``-by-``n`` matrices with octonion entries over
470 (a subfield of) the real numbers.
472 The usual matrix spaces in SageMath don't support octonion entries
473 because they assume that the entries of the matrix come from a
474 commutative and associative ring (i.e. very NOT the octonions).
476 Element
= OctonionMatrix
478 def __init__(self
, n
, field
=AA
, prefix
="E", **kwargs
):
479 # Not associative, not commutative
480 category
= MagmaticAlgebras(field
).FiniteDimensional()
481 category
= category
.WithBasis().Unital()
485 # Since the scalar field is real but the entries are octonion,
486 # sticking a "1" in each position doesn't give us a basis for
487 # the space. We actually need to stick each of e0, e1, ..., e7
488 # (a basis for the Octonions themselves) into each position.
489 from sage
.sets
.finite_enumerated_set
import FiniteEnumeratedSet
490 from sage
.categories
.sets_cat
import cartesian_product
492 I
= FiniteEnumeratedSet(range(n
))
493 J
= FiniteEnumeratedSet(range(n
))
494 self
._octonions
= Octonions(field
=field
)
495 entry_basis
= self
._octonions
.gens()
497 basis_indices
= cartesian_product([I
,J
,entry_basis
])
498 super().__init
__(field
,
505 return ("Module of %d by %d matrices with octonion entries"
506 " over the scalar ring %s" %
507 (self
.nrows(), self
.ncols(), self
.base_ring()) )
511 Return the Octonion algebra that our elements' entries come from.
513 return self
._octonions
519 def product_on_basis(self
, mon1
, mon2
):
523 return self
.monomial((i
,l
,oct1
*oct2
))
531 sage: from mjo.octonions import OctonionMatrixAlgebra
535 sage: set_random_seed()
536 sage: MS = OctonionMatrixAlgebra(ZZ.random_element(10))
537 sage: x = MS.random_element()
538 sage: x*MS.one() == x and MS.one()*x == x
542 return sum( (self
.monomial((i
,i
,self
._octonions
.one()))
543 for i
in range(self
.nrows()) ),
546 def from_list(self
, entries
):
548 Construct an element of this algebra from a list of lists of
553 sage: from mjo.octonions import Octonions, OctonionMatrixAlgebra
557 sage: O = Octonions(QQ)
558 sage: e0,e1,e2,e3,e4,e5,e6,e7 = O.gens()
559 sage: MS = OctonionMatrixAlgebra(2)
560 sage: MS.from_list([ [e0+e4, e1+e5],
561 ....: [e2-e6, e3-e7] ])
562 +---------+---------+
563 | e0 + e4 | e1 + e5 |
564 +---------+---------+
565 | e2 - e6 | e3 - e7 |
566 +---------+---------+
572 ncols
= len(entries
[0])
574 if (not all( len(r
) == ncols
for r
in entries
)) or (ncols
!= nrows
):
575 raise ValueError("list must be square")
578 # We have to pass through vectors to convert from the
579 # given octonion algebra to ours. Otherwise we can fail
580 # to convert an element of (for example) Octonions(QQ)
582 return self
.octonions().from_vector(e_ij
.to_vector())
584 return sum( (self
.monomial( (i
,j
, convert(entries
[i
][j
])) )
585 for i
in range(nrows
)
586 for j
in range(ncols
) ),