]>
gitweb.michael.orlitzky.com - sage.d.git/blob - mjo/octonions.py
1 from sage
.algebras
.quatalg
.quaternion_algebra
import QuaternionAlgebra
2 from sage
.combinat
.free_module
import CombinatorialFreeModule
3 from sage
.modules
.with_basis
.indexed_element
import IndexedFreeModuleElement
4 from sage
.categories
.magmatic_algebras
import MagmaticAlgebras
5 from sage
.rings
.all
import AA
, ZZ
6 from sage
.matrix
.matrix_space
import MatrixSpace
7 from sage
.misc
.table
import table
9 class Octonion(IndexedFreeModuleElement
):
14 sage: from mjo.octonions import Octonions
19 sage: x = sum(O.gens())
21 e0 - e1 - e2 - e3 - e4 - e5 - e6 - e7
25 Conjugating twice gets you the original element::
27 sage: set_random_seed()
29 sage: x = O.random_element()
30 sage: x.conjugate().conjugate() == x
34 C
= MatrixSpace(ZZ
,8).diagonal_matrix((1,-1,-1,-1,-1,-1,-1,-1))
35 return self
.parent().from_vector(C
*self
.to_vector())
39 Return the real part of this octonion.
41 The real part of an octonion is its projection onto the span
42 of the first generator. In other words, the "first dimension"
43 is real and the others are imaginary.
47 sage: from mjo.octonions import Octonions
52 sage: x = sum(O.gens())
58 This method is idempotent::
60 sage: set_random_seed()
62 sage: x = O.random_element()
63 sage: x.real().real() == x.real()
67 return (self
+ self
.conjugate())/2
71 Return the imaginary part of this octonion.
73 The imaginary part of an octonion is its projection onto the
74 orthogonal complement of the span of the first generator. In
75 other words, the "first dimension" is real and the others are
80 sage: from mjo.octonions import Octonions
85 sage: x = sum(O.gens())
87 e1 + e2 + e3 + e4 + e5 + e6 + e7
91 This method is idempotent::
93 sage: set_random_seed()
95 sage: x = O.random_element()
96 sage: x.imag().imag() == x.imag()
100 return (self
- self
.conjugate())/2
102 def _norm_squared(self
):
103 return (self
*self
.conjugate()).coefficient(0)
107 Return the norm of this octonion.
111 sage: from mjo.octonions import Octonions
115 sage: O = Octonions()
121 The norm is nonnegative and belongs to the base field::
123 sage: set_random_seed()
124 sage: O = Octonions()
125 sage: n = O.random_element().norm()
126 sage: n >= 0 and n in O.base_ring()
129 The norm is homogeneous::
131 sage: set_random_seed()
132 sage: O = Octonions()
133 sage: x = O.random_element()
134 sage: alpha = O.base_ring().random_element()
135 sage: (alpha*x).norm() == alpha.abs()*x.norm()
139 return self
._norm
_squared
().sqrt()
143 Return the inverse of this element if it exists.
147 sage: from mjo.octonions import Octonions
151 sage: O = Octonions()
152 sage: x = sum(O.gens())
153 sage: x*x.inverse() == O.one()
158 sage: O = Octonions()
159 sage: O.one().inverse() == O.one()
164 sage: set_random_seed()
165 sage: O = Octonions()
166 sage: x = O.random_element()
167 sage: x.is_zero() or ( x*x.inverse() == O.one() )
172 raise ValueError("zero is not invertible")
173 return self
.conjugate()/self
._norm
_squared
()
176 def cayley_dickson(self
, Q
=None):
178 Return the Cayley-Dickson representation of this element in terms
179 of the quaternion algebra ``Q``.
181 The Cayley-Dickson representation is an identification of
182 octionions `x` and `y` with pairs of quaternions `(a,b)` and
183 `(c,d)` respectively such that:
185 * `x + y = (a+b, c+d)`
186 * `xy` = (ac - \bar{d}*b, da + b\bar{c})`
189 where `\bar{x}` denotes the conjugate of `x`.
193 sage: from mjo.octonions import Octonions
197 sage: O = Octonions()
198 sage: x = sum(O.gens())
199 sage: x.cayley_dickson()
200 (1 + i + j + k, 1 + i + j + k)
204 Q
= QuaternionAlgebra(self
.base_ring(), -1, -1)
207 a
= (self
.coefficient(0)*Q
.one() +
208 self
.coefficient(1)*i
+
209 self
.coefficient(2)*j
+
210 self
.coefficient(3)*k
)
211 b
= (self
.coefficient(4)*Q
.one() +
212 self
.coefficient(5)*i
+
213 self
.coefficient(6)*j
+
214 self
.coefficient(7)*k
)
216 from sage
.categories
.sets_cat
import cartesian_product
217 P
= cartesian_product([Q
,Q
])
221 class Octonions(CombinatorialFreeModule
):
225 sage: from mjo.octonions import Octonions
230 Octonion algebra with base ring Algebraic Real Field
231 sage: Octonions(field=QQ)
232 Octonion algebra with base ring Rational Field
239 # Not associative, not commutative
240 category
= MagmaticAlgebras(field
).FiniteDimensional()
241 category
= category
.WithBasis().Unital()
243 super().__init
__(field
,
245 element_class
=Octonion
,
250 # The product of each basis element is plus/minus another
251 # basis element that can simply be looked up on
252 # https://en.wikipedia.org/wiki/Octonion
253 e0
, e1
, e2
, e3
, e4
, e5
, e6
, e7
= self
.gens()
254 self
._multiplication
_table
= (
255 (e0
, e1
, e2
, e3
, e4
, e5
, e6
, e7
),
256 (e1
,-e0
, e3
,-e2
, e5
,-e4
,-e7
, e6
),
257 (e2
,-e3
,-e0
, e1
, e6
, e7
,-e4
,-e5
),
258 (e3
, e2
,-e1
,-e0
, e7
,-e6
, e5
,-e4
),
259 (e4
,-e5
,-e6
,-e7
,-e0
, e1
, e2
, e3
),
260 (e5
, e4
,-e7
, e6
,-e1
,-e0
,-e3
, e2
),
261 (e6
, e7
, e4
,-e5
,-e2
, e3
,-e0
,-e1
),
262 (e7
,-e6
, e5
, e4
,-e3
,-e2
, e1
,-e0
),
265 def product_on_basis(self
, i
, j
):
266 return self
._multiplication
_table
[i
][j
]
270 Return the monomial index (basis element) corresponding to the
271 octonion unit element.
275 sage: from mjo.octonions import Octonions
279 This gives the correct unit element::
281 sage: set_random_seed()
282 sage: O = Octonions()
283 sage: x = O.random_element()
284 sage: x*O.one() == x and O.one()*x == x
291 return ("Octonion algebra with base ring %s" % self
.base_ring())
293 def multiplication_table(self
):
295 Return a visual representation of this algebra's multiplication
296 table (on basis elements).
300 sage: from mjo.octonions import Octonions
304 The multiplication table is what Wikipedia says it is::
306 sage: Octonions().multiplication_table()
307 +----++----+-----+-----+-----+-----+-----+-----+-----+
308 | * || e0 | e1 | e2 | e3 | e4 | e5 | e6 | e7 |
309 +====++====+=====+=====+=====+=====+=====+=====+=====+
310 | e0 || e0 | e1 | e2 | e3 | e4 | e5 | e6 | e7 |
311 +----++----+-----+-----+-----+-----+-----+-----+-----+
312 | e1 || e1 | -e0 | e3 | -e2 | e5 | -e4 | -e7 | e6 |
313 +----++----+-----+-----+-----+-----+-----+-----+-----+
314 | e2 || e2 | -e3 | -e0 | e1 | e6 | e7 | -e4 | -e5 |
315 +----++----+-----+-----+-----+-----+-----+-----+-----+
316 | e3 || e3 | e2 | -e1 | -e0 | e7 | -e6 | e5 | -e4 |
317 +----++----+-----+-----+-----+-----+-----+-----+-----+
318 | e4 || e4 | -e5 | -e6 | -e7 | -e0 | e1 | e2 | e3 |
319 +----++----+-----+-----+-----+-----+-----+-----+-----+
320 | e5 || e5 | e4 | -e7 | e6 | -e1 | -e0 | -e3 | e2 |
321 +----++----+-----+-----+-----+-----+-----+-----+-----+
322 | e6 || e6 | e7 | e4 | -e5 | -e2 | e3 | -e0 | -e1 |
323 +----++----+-----+-----+-----+-----+-----+-----+-----+
324 | e7 || e7 | -e6 | e5 | e4 | -e3 | -e2 | e1 | -e0 |
325 +----++----+-----+-----+-----+-----+-----+-----+-----+
329 # Prepend the header row.
330 M
= [["*"] + list(self
.gens())]
332 # And to each subsequent row, prepend an entry that belongs to
333 # the left-side "header column."
334 M
+= [ [self
.monomial(i
)] + [ self
.monomial(i
)*self
.monomial(j
)
338 return table(M
, header_row
=True, header_column
=True, frame
=True)
341 class OctonionMatrix
:
343 A pseudo-matrix class that supports octonion entries.
345 Matrices in SageMath can't have base rings that are
346 non-commutative, much less non-associative. The "matrix" scaling,
347 addition, and multiplication operations for this class are all
348 wholly inefficient, but are hand-written to guarantee that they
349 are performed in the correct order. Of course, it can't guarantee
350 that you won't write something visually ambiguous like
351 `A*B*C`... but you already have that problem with the
352 non-associative octonions themselves.
354 This class is only as sophisticated as it need to be to implement
355 the Jordan and inner-products in the space of Hermitian matrices
356 with octonion entries, namely ``(X*Y+Y*X)/2`` and
357 ``(X*Y).trace().real()`` for two octonion matrices ``X`` and
362 These are not matrices in the usual sense! Matrix
363 multiplication is associative. Multiplication of octonion
364 "matrices" cannot be, since the multiplication of the
365 underlying octonions is not (consider two 1-by-1 matrices each
366 containing a single octonion).
368 def __init__(self
, entries
):
370 Initialize this matrix with a list of lists in (row,column) order,
371 just like in SageMath.
373 self
._nrows
= len(entries
)
378 # We don't check that you haven't supplied two rows (or
379 # columns) of different lengths!
380 self
._ncols
= len(entries
[0])
382 self
._entries
= entries
384 def __getitem__(self
, indices
):
388 sage: from mjo.octonions import Octonions, OctonionMatrix
392 sage: O = Octonions(field=QQ)
393 sage: M = OctonionMatrix([ [O.one(), O.zero()],
394 ....: [O.zero(), O.one() ] ])
405 return self
._entries
[i
][j
]
411 sage: from mjo.octonions import Octonions, OctonionMatrix
415 sage: O = Octonions(field=QQ)
416 sage: M = OctonionMatrix([ [O.one(), O.zero()],
417 ....: [O.zero(), O.one() ],
418 ....: [O.zero(), O.zero()] ])
430 sage: from mjo.octonions import Octonions, OctonionMatrix
434 sage: O = Octonions(field=QQ)
435 sage: M = OctonionMatrix([ [O.one(), O.zero()],
436 ....: [O.zero(), O.one() ],
437 ....: [O.zero(), O.zero()] ])
445 return table(self
._entries
, frame
=True)._repr
_()
447 def __mul__(self
,rhs
):
452 sage: from mjo.octonions import Octonions, OctonionMatrix
456 sage: O = Octonions(QQ)
457 sage: e1 = O.monomial(1)
458 sage: e2 = O.monomial(2)
463 sage: E1 = OctonionMatrix([[e1]])
464 sage: E2 = OctonionMatrix([[e2]])
475 if not self
.ncols() == rhs
.nrows():
476 raise ValueError("dimension mismatch")
482 C
= lambda i
,j
: sum( self
[i
,k
]*rhs
[k
,j
] for k
in range(n
) )
483 return OctonionMatrix([ [C(i
,j
) for j
in range(m
)]
484 for i
in range(p
) ] )
486 def __rmul__(self
,scalar
):
491 sage: from mjo.octonions import Octonions, OctonionMatrix
495 sage: O = Octonions(QQ)
496 sage: M = OctonionMatrix([[O.one(), O.zero()],
497 ....: [O.zero(),O.one() ] ])
506 # SCALAR GOES ON THE LEFT HERE
507 return OctonionMatrix([ [scalar
*self
[i
,j
]
508 for j
in range(self
.ncols())]
509 for i
in range(self
.nrows()) ])
511 def __add__(self
,rhs
):
515 sage: from mjo.octonions import Octonions, OctonionMatrix
519 sage: O = Octonions(QQ)
520 sage: e0,e1,e2,e3,e4,e5,e6,e7 = O.gens()
521 sage: A = OctonionMatrix([ [e0,e1],
523 sage: B = OctonionMatrix([ [e4,e5],
526 +---------+---------+
527 | e0 + e4 | e1 + e5 |
528 +---------+---------+
529 | e2 + e6 | e3 + e7 |
530 +---------+---------+
533 if not self
.ncols() == rhs
.ncols():
534 raise ValueError("column dimension mismatch")
535 if not self
.nrows() == rhs
.nrows():
536 raise ValueError("row dimension mismatch")
537 return OctonionMatrix([ [self
[i
,j
] + rhs
[i
,j
]
538 for j
in range(self
.ncols())]
539 for i
in range(self
.nrows()) ])