]> gitweb.michael.orlitzky.com - sage.d.git/blob - mjo/octonions.py
073765ccf1ef934ab6d9f6fc59435384bf832f36
[sage.d.git] / 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
9
10 from mjo.matrix_algebra import MatrixAlgebra
11
12 class Octonion(IndexedFreeModuleElement):
13 def conjugate(self):
14 r"""
15 SETUP::
16
17 sage: from mjo.octonions import Octonions
18
19 EXAMPLES::
20
21 sage: O = Octonions()
22 sage: x = sum(O.gens())
23 sage: x.conjugate()
24 e0 - e1 - e2 - e3 - e4 - e5 - e6 - e7
25
26 TESTS::
27
28 Conjugating twice gets you the original element::
29
30 sage: set_random_seed()
31 sage: O = Octonions()
32 sage: x = O.random_element()
33 sage: x.conjugate().conjugate() == x
34 True
35
36 """
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())
39
40 def real(self):
41 r"""
42 Return the real part of this octonion.
43
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.
47
48 SETUP::
49
50 sage: from mjo.octonions import Octonions
51
52 EXAMPLES::
53
54 sage: O = Octonions()
55 sage: x = sum(O.gens())
56 sage: x.real()
57 e0
58
59 TESTS:
60
61 This method is idempotent::
62
63 sage: set_random_seed()
64 sage: O = Octonions()
65 sage: x = O.random_element()
66 sage: x.real().real() == x.real()
67 True
68
69 """
70 return (self + self.conjugate())/2
71
72 def imag(self):
73 r"""
74 Return the imaginary part of this octonion.
75
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
79 imaginary.
80
81 SETUP::
82
83 sage: from mjo.octonions import Octonions
84
85 EXAMPLES::
86
87 sage: O = Octonions()
88 sage: x = sum(O.gens())
89 sage: x.imag()
90 e1 + e2 + e3 + e4 + e5 + e6 + e7
91
92 TESTS:
93
94 This method is idempotent::
95
96 sage: set_random_seed()
97 sage: O = Octonions()
98 sage: x = O.random_element()
99 sage: x.imag().imag() == x.imag()
100 True
101
102 """
103 return (self - self.conjugate())/2
104
105 def _norm_squared(self):
106 return (self*self.conjugate()).coefficient(0)
107
108 def norm(self):
109 r"""
110 Return the norm of this octonion.
111
112 SETUP::
113
114 sage: from mjo.octonions import Octonions
115
116 EXAMPLES::
117
118 sage: O = Octonions()
119 sage: O.one().norm()
120 1
121
122 TESTS:
123
124 The norm is nonnegative and belongs to the base field::
125
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()
130 True
131
132 The norm is homogeneous::
133
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()
139 True
140
141 """
142 return self._norm_squared().sqrt()
143
144 def inverse(self):
145 r"""
146 Return the inverse of this element if it exists.
147
148 SETUP::
149
150 sage: from mjo.octonions import Octonions
151
152 EXAMPLES::
153
154 sage: O = Octonions()
155 sage: x = sum(O.gens())
156 sage: x*x.inverse() == O.one()
157 True
158
159 ::
160
161 sage: O = Octonions()
162 sage: O.one().inverse() == O.one()
163 True
164
165 TESTS::
166
167 sage: set_random_seed()
168 sage: O = Octonions()
169 sage: x = O.random_element()
170 sage: x.is_zero() or ( x*x.inverse() == O.one() )
171 True
172
173 """
174 if self.is_zero():
175 raise ValueError("zero is not invertible")
176 return self.conjugate()/self._norm_squared()
177
178
179 def cayley_dickson(self, Q=None):
180 r"""
181 Return the Cayley-Dickson representation of this element in terms
182 of the quaternion algebra ``Q``.
183
184 The Cayley-Dickson representation is an identification of
185 octionions `x` and `y` with pairs of quaternions `(a,b)` and
186 `(c,d)` respectively such that:
187
188 * `x + y = (a+b, c+d)`
189 * `xy` = (ac - \bar{d}*b, da + b\bar{c})`
190 * `\bar{x} = (a,-b)`
191
192 where `\bar{x}` denotes the conjugate of `x`.
193
194 SETUP::
195
196 sage: from mjo.octonions import Octonions
197
198 EXAMPLES::
199
200 sage: O = Octonions()
201 sage: x = sum(O.gens())
202 sage: x.cayley_dickson()
203 (1 + i + j + k, 1 + i + j + k)
204
205 """
206 if Q is None:
207 Q = QuaternionAlgebra(self.base_ring(), -1, -1)
208
209 i,j,k = Q.gens()
210 a = (self.coefficient(0)*Q.one() +
211 self.coefficient(1)*i +
212 self.coefficient(2)*j +
213 self.coefficient(3)*k )
214 b = (self.coefficient(4)*Q.one() +
215 self.coefficient(5)*i +
216 self.coefficient(6)*j +
217 self.coefficient(7)*k )
218
219 from sage.categories.sets_cat import cartesian_product
220 P = cartesian_product([Q,Q])
221 return P((a,b))
222
223
224 class Octonions(CombinatorialFreeModule):
225 r"""
226 SETUP::
227
228 sage: from mjo.octonions import Octonions
229
230 EXAMPLES::
231
232 sage: Octonions()
233 Octonion algebra with base ring Algebraic Real Field
234 sage: Octonions(field=QQ)
235 Octonion algebra with base ring Rational Field
236
237 """
238 def __init__(self,
239 field=AA,
240 prefix="e"):
241
242 # Not associative, not commutative
243 category = MagmaticAlgebras(field).FiniteDimensional()
244 category = category.WithBasis().Unital()
245
246 super().__init__(field,
247 range(8),
248 element_class=Octonion,
249 category=category,
250 prefix=prefix,
251 bracket=False)
252
253 # The product of each basis element is plus/minus another
254 # basis element that can simply be looked up on
255 # https://en.wikipedia.org/wiki/Octonion
256 e0, e1, e2, e3, e4, e5, e6, e7 = self.gens()
257 self._multiplication_table = (
258 (e0, e1, e2, e3, e4, e5, e6, e7),
259 (e1,-e0, e3,-e2, e5,-e4,-e7, e6),
260 (e2,-e3,-e0, e1, e6, e7,-e4,-e5),
261 (e3, e2,-e1,-e0, e7,-e6, e5,-e4),
262 (e4,-e5,-e6,-e7,-e0, e1, e2, e3),
263 (e5, e4,-e7, e6,-e1,-e0,-e3, e2),
264 (e6, e7, e4,-e5,-e2, e3,-e0,-e1),
265 (e7,-e6, e5, e4,-e3,-e2, e1,-e0),
266 )
267
268 def product_on_basis(self, i, j):
269 return self._multiplication_table[i][j]
270
271 def one_basis(self):
272 r"""
273 Return the monomial index (basis element) corresponding to the
274 octonion unit element.
275
276 SETUP::
277
278 sage: from mjo.octonions import Octonions
279
280 TESTS:
281
282 This gives the correct unit element::
283
284 sage: set_random_seed()
285 sage: O = Octonions()
286 sage: x = O.random_element()
287 sage: x*O.one() == x and O.one()*x == x
288 True
289
290 """
291 return 0
292
293 def _repr_(self):
294 return ("Octonion algebra with base ring %s" % self.base_ring())
295
296 def multiplication_table(self):
297 """
298 Return a visual representation of this algebra's multiplication
299 table (on basis elements).
300
301 SETUP::
302
303 sage: from mjo.octonions import Octonions
304
305 EXAMPLES:
306
307 The multiplication table is what Wikipedia says it is::
308
309 sage: Octonions().multiplication_table()
310 +----++----+-----+-----+-----+-----+-----+-----+-----+
311 | * || e0 | e1 | e2 | e3 | e4 | e5 | e6 | e7 |
312 +====++====+=====+=====+=====+=====+=====+=====+=====+
313 | e0 || e0 | e1 | e2 | e3 | e4 | e5 | e6 | e7 |
314 +----++----+-----+-----+-----+-----+-----+-----+-----+
315 | e1 || e1 | -e0 | e3 | -e2 | e5 | -e4 | -e7 | e6 |
316 +----++----+-----+-----+-----+-----+-----+-----+-----+
317 | e2 || e2 | -e3 | -e0 | e1 | e6 | e7 | -e4 | -e5 |
318 +----++----+-----+-----+-----+-----+-----+-----+-----+
319 | e3 || e3 | e2 | -e1 | -e0 | e7 | -e6 | e5 | -e4 |
320 +----++----+-----+-----+-----+-----+-----+-----+-----+
321 | e4 || e4 | -e5 | -e6 | -e7 | -e0 | e1 | e2 | e3 |
322 +----++----+-----+-----+-----+-----+-----+-----+-----+
323 | e5 || e5 | e4 | -e7 | e6 | -e1 | -e0 | -e3 | e2 |
324 +----++----+-----+-----+-----+-----+-----+-----+-----+
325 | e6 || e6 | e7 | e4 | -e5 | -e2 | e3 | -e0 | -e1 |
326 +----++----+-----+-----+-----+-----+-----+-----+-----+
327 | e7 || e7 | -e6 | e5 | e4 | -e3 | -e2 | e1 | -e0 |
328 +----++----+-----+-----+-----+-----+-----+-----+-----+
329
330 """
331 n = self.dimension()
332 # Prepend the header row.
333 M = [["*"] + list(self.gens())]
334
335 # And to each subsequent row, prepend an entry that belongs to
336 # the left-side "header column."
337 M += [ [self.monomial(i)] + [ self.monomial(i)*self.monomial(j)
338 for j in range(n) ]
339 for i in range(n) ]
340
341 return table(M, header_row=True, header_column=True, frame=True)
342
343
344
345 class OctonionMatrixAlgebra(MatrixAlgebra):
346 r"""
347 The algebra of ``n``-by-``n`` matrices with octonion entries over
348 (a subfield of) the real numbers.
349
350 The usual matrix spaces in SageMath don't support octonion entries
351 because they assume that the entries of the matrix come from a
352 commutative and associative ring (i.e. very NOT the octonions).
353
354 SETUP::
355
356 sage: from mjo.octonions import Octonions, OctonionMatrixAlgebra
357
358 EXAMPLES::
359
360 sage: OctonionMatrixAlgebra(3)
361 Module of 3 by 3 matrices with entries in Octonion algebra with base
362 ring Algebraic Real Field over the scalar ring Algebraic Real Field
363 sage: OctonionMatrixAlgebra(3,QQ)
364 Module of 3 by 3 matrices with entries in Octonion algebra with base
365 ring Rational Field over the scalar ring Rational Field
366
367 ::
368
369 sage: O = Octonions(QQ)
370 sage: e0,e1,e2,e3,e4,e5,e6,e7 = O.gens()
371 sage: MS = OctonionMatrixAlgebra(2)
372 sage: MS.from_list([ [e0+e4, e1+e5],
373 ....: [e2-e6, e3-e7] ])
374 +---------+---------+
375 | e0 + e4 | e1 + e5 |
376 +---------+---------+
377 | e2 - e6 | e3 - e7 |
378 +---------+---------+
379
380 TESTS::
381
382 sage: set_random_seed()
383 sage: MS = OctonionMatrixAlgebra(ZZ.random_element(10))
384 sage: x = MS.random_element()
385 sage: x*MS.one() == x and MS.one()*x == x
386 True
387
388 """
389 def __init__(self, n, scalars=AA, prefix="E", **kwargs):
390 super().__init__(Octonions(field=scalars),
391 scalars,
392 n,
393 prefix=prefix,
394 **kwargs)