]>
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)