]> gitweb.michael.orlitzky.com - sage.d.git/blob - mjo/octonions.py
octonions: add a test for Cartesian products.
[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 HurwitzMatrixAlgebra
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 # 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.
147 abs = norm
148
149 def inverse(self):
150 r"""
151 Return the inverse of this element if it exists.
152
153 SETUP::
154
155 sage: from mjo.octonions import Octonions
156
157 EXAMPLES::
158
159 sage: O = Octonions()
160 sage: x = sum(O.gens())
161 sage: x*x.inverse() == O.one()
162 True
163
164 ::
165
166 sage: O = Octonions()
167 sage: O.one().inverse() == O.one()
168 True
169
170 TESTS::
171
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() )
176 True
177
178 """
179 if self.is_zero():
180 raise ValueError("zero is not invertible")
181 return self.conjugate()/self._norm_squared()
182
183
184 def cayley_dickson(self, Q=None):
185 r"""
186 Return the Cayley-Dickson representation of this element in terms
187 of the quaternion algebra ``Q``.
188
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:
192
193 * `x + y = (a+b, c+d)`
194 * `xy` = (ac - \bar{d}*b, da + b\bar{c})`
195 * `\bar{x} = (a,-b)`
196
197 where `\bar{x}` denotes the conjugate of `x`.
198
199 SETUP::
200
201 sage: from mjo.octonions import Octonions
202
203 EXAMPLES::
204
205 sage: O = Octonions()
206 sage: x = sum(O.gens())
207 sage: x.cayley_dickson()
208 (1 + i + j + k, 1 + i + j + k)
209
210 """
211 if Q is None:
212 Q = QuaternionAlgebra(self.base_ring(), -1, -1)
213
214 i,j,k = Q.gens()
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 )
223
224 from sage.categories.sets_cat import cartesian_product
225 P = cartesian_product([Q,Q])
226 return P((a,b))
227
228
229 class Octonions(CombinatorialFreeModule):
230 r"""
231 SETUP::
232
233 sage: from mjo.octonions import Octonions
234
235 EXAMPLES::
236
237 sage: Octonions()
238 Octonion algebra with base ring Algebraic Real Field
239 sage: Octonions(field=QQ)
240 Octonion algebra with base ring Rational Field
241
242 """
243 def __init__(self,
244 field=AA,
245 prefix="e"):
246
247 # Not associative, not commutative
248 category = MagmaticAlgebras(field).FiniteDimensional()
249 category = category.WithBasis().Unital()
250
251 super().__init__(field,
252 range(8),
253 element_class=Octonion,
254 category=category,
255 prefix=prefix,
256 bracket=False)
257
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),
271 )
272
273 def product_on_basis(self, i, j):
274 return self._multiplication_table[i][j]
275
276 def one_basis(self):
277 r"""
278 Return the monomial index (basis element) corresponding to the
279 octonion unit element.
280
281 SETUP::
282
283 sage: from mjo.octonions import Octonions
284
285 TESTS:
286
287 This gives the correct unit element::
288
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
293 True
294
295 """
296 return 0
297
298 def _repr_(self):
299 return ("Octonion algebra with base ring %s" % self.base_ring())
300
301 def multiplication_table(self):
302 """
303 Return a visual representation of this algebra's multiplication
304 table (on basis elements).
305
306 SETUP::
307
308 sage: from mjo.octonions import Octonions
309
310 EXAMPLES:
311
312 The multiplication table is what Wikipedia says it is::
313
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 +----++----+-----+-----+-----+-----+-----+-----+-----+
334
335 """
336 n = self.dimension()
337 # Prepend the header row.
338 M = [["*"] + list(self.gens())]
339
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)
343 for j in range(n) ]
344 for i in range(n) ]
345
346 return table(M, header_row=True, header_column=True, frame=True)
347
348
349
350 class OctonionMatrixAlgebra(HurwitzMatrixAlgebra):
351 r"""
352 The algebra of ``n``-by-``n`` matrices with octonion entries over
353 (a subfield of) the real numbers.
354
355 The usual matrix spaces in SageMath don't support octonion entries
356 because they assume that the entries of the matrix come from a
357 commutative and associative ring (i.e. very NOT the octonions).
358
359 SETUP::
360
361 sage: from mjo.octonions import Octonions, OctonionMatrixAlgebra
362
363 EXAMPLES::
364
365 sage: OctonionMatrixAlgebra(3)
366 Module of 3 by 3 matrices with entries in Octonion algebra with base
367 ring Algebraic Real Field over the scalar ring Algebraic Real Field
368 sage: OctonionMatrixAlgebra(3,QQ)
369 Module of 3 by 3 matrices with entries in Octonion algebra with base
370 ring Rational Field over the scalar ring Rational Field
371
372 ::
373
374 sage: O = Octonions(QQ)
375 sage: e0,e1,e2,e3,e4,e5,e6,e7 = O.gens()
376 sage: MS = OctonionMatrixAlgebra(2)
377 sage: MS([ [e0+e4, e1+e5],
378 ....: [e2-e6, e3-e7] ])
379 +---------+---------+
380 | e0 + e4 | e1 + e5 |
381 +---------+---------+
382 | e2 - e6 | e3 - e7 |
383 +---------+---------+
384
385 ::
386
387 sage: A1 = OctonionMatrixAlgebra(1,QQ)
388 sage: A2 = OctonionMatrixAlgebra(1,QQ)
389 sage: cartesian_product([A1,A2])
390 Module of 1 by 1 matrices with entries in Octonion algebra with
391 base ring Rational Field over the scalar ring Rational Field (+)
392 Module of 1 by 1 matrices with entries in Octonion algebra with
393 base ring Rational Field over the scalar ring Rational Field
394
395 TESTS::
396
397 sage: set_random_seed()
398 sage: MS = OctonionMatrixAlgebra(ZZ.random_element(10))
399 sage: x = MS.random_element()
400 sage: x*MS.one() == x and MS.one()*x == x
401 True
402
403 """
404 def __init__(self, n, scalars=AA, prefix="E", **kwargs):
405 super().__init__(Octonions(field=scalars),
406 scalars,
407 n,
408 prefix=prefix,
409 **kwargs)