]> gitweb.michael.orlitzky.com - sage.d.git/blob - mjo/octonions.py
New module mjo.octonions implementing the algebra of Octonions().
[sage.d.git] / mjo / octonions.py
1 from sage.combinat.free_module import CombinatorialFreeModule
2 from sage.modules.with_basis.indexed_element import IndexedFreeModuleElement
3 from sage.categories.magmatic_algebras import MagmaticAlgebras
4 from sage.rings.all import AA, ZZ
5 from sage.matrix.matrix_space import MatrixSpace
6 from sage.misc.table import table
7
8 class Octonion(IndexedFreeModuleElement):
9 def conjugate(self):
10 r"""
11 SETUP::
12
13 sage: from mjo.octonions import Octonions
14
15 EXAMPLES::
16
17 sage: O = Octonions()
18 sage: x = sum(O.gens())
19 sage: x.conjugate()
20 e0 - e1 - e2 - e3 - e4 - e5 - e6 - e7
21
22 TESTS::
23
24 Conjugating twice gets you the original element::
25
26 sage: set_random_seed()
27 sage: O = Octonions()
28 sage: x = O.random_element()
29 sage: x.conjugate().conjugate() == x
30 True
31
32 """
33 C = MatrixSpace(ZZ,8).diagonal_matrix((1,-1,-1,-1,-1,-1,-1,-1))
34 return self.parent().from_vector(C*self.to_vector())
35
36 def real(self):
37 r"""
38 Return the real part of this octonion.
39
40 The real part of an octonion is its projection onto the span
41 of the first generator. In other words, the "first dimension"
42 is real and the others are imaginary.
43
44 SETUP::
45
46 sage: from mjo.octonions import Octonions
47
48 EXAMPLES::
49
50 sage: O = Octonions()
51 sage: x = sum(O.gens())
52 sage: x.real()
53 e0
54
55 TESTS:
56
57 This method is idempotent::
58
59 sage: set_random_seed()
60 sage: O = Octonions()
61 sage: x = O.random_element()
62 sage: x.real().real() == x.real()
63 True
64
65 """
66 return (self + self.conjugate())/2
67
68 def imag(self):
69 r"""
70 Return the imaginary part of this octonion.
71
72 The imaginary part of an octonion is its projection onto the
73 orthogonal complement of the span of the first generator. In
74 other words, the "first dimension" is real and the others are
75 imaginary.
76
77 SETUP::
78
79 sage: from mjo.octonions import Octonions
80
81 EXAMPLES::
82
83 sage: O = Octonions()
84 sage: x = sum(O.gens())
85 sage: x.imag()
86 e1 + e2 + e3 + e4 + e5 + e6 + e7
87
88 TESTS:
89
90 This method is idempotent::
91
92 sage: set_random_seed()
93 sage: O = Octonions()
94 sage: x = O.random_element()
95 sage: x.imag().imag() == x.imag()
96 True
97
98 """
99 return (self - self.conjugate())/2
100
101 def _norm_squared(self):
102 return (self*self.conjugate()).coefficient(0)
103
104 def norm(self):
105 r"""
106 Return the norm of this octonion.
107
108 SETUP::
109
110 sage: from mjo.octonions import Octonions
111
112 EXAMPLES::
113
114 sage: O = Octonions()
115 sage: O.one().norm()
116 1
117
118 TESTS:
119
120 The norm is nonnegative and belongs to the base field::
121
122 sage: set_random_seed()
123 sage: O = Octonions()
124 sage: n = O.random_element().norm()
125 sage: n >= 0 and n in O.base_ring()
126 True
127
128 The norm is homogeneous::
129
130 sage: set_random_seed()
131 sage: O = Octonions()
132 sage: x = O.random_element()
133 sage: alpha = O.base_ring().random_element()
134 sage: (alpha*x).norm() == alpha.abs()*x.norm()
135 True
136
137 """
138 return self._norm_squared().sqrt()
139
140 def inverse(self):
141 r"""
142 Return the inverse of this element if it exists.
143
144 SETUP::
145
146 sage: from mjo.octonions import Octonions
147
148 EXAMPLES::
149
150 sage: O = Octonions()
151 sage: x = sum(O.gens())
152 sage: x*x.inverse() == O.one()
153 True
154
155 ::
156
157 sage: O = Octonions()
158 sage: O.one().inverse() == O.one()
159 True
160
161 TESTS::
162
163 sage: set_random_seed()
164 sage: O = Octonions()
165 sage: x = O.random_element()
166 sage: x.is_zero() or ( x*x.inverse() == O.one() )
167 True
168
169 """
170 if self.is_zero():
171 raise ValueError("zero is not invertible")
172 return self.conjugate()/self._norm_squared()
173
174 class Octonions(CombinatorialFreeModule):
175 r"""
176 SETUP::
177
178 sage: from mjo.octonions import Octonions
179
180 EXAMPLES::
181
182 sage: Octonions()
183 Octonion algebra with base ring Algebraic Real Field
184 sage: Octonions(field=QQ)
185 Octonion algebra with base ring Rational Field
186
187 """
188 def __init__(self,
189 field=AA,
190 prefix="e"):
191
192 # Not associative, not commutative
193 category = MagmaticAlgebras(field).FiniteDimensional()
194 category = category.WithBasis().Unital()
195
196 super().__init__(field,
197 range(8),
198 element_class=Octonion,
199 category=category,
200 prefix=prefix,
201 bracket=False)
202
203 # The product of each basis element is plus/minus another
204 # basis element that can simply be looked up on
205 # https://en.wikipedia.org/wiki/Octonion
206 e0, e1, e2, e3, e4, e5, e6, e7 = self.gens()
207 self._multiplication_table = (
208 (e0, e1, e2, e3, e4, e5, e6, e7),
209 (e1,-e0, e3,-e2, e5,-e4,-e7, e6),
210 (e2,-e3,-e0, e1, e6, e7,-e4,-e5),
211 (e3, e2,-e1,-e0, e7,-e6, e5,-e4),
212 (e4,-e5,-e6,-e7,-e0, e1, e2, e3),
213 (e5, e4,-e7, e6,-e1,-e0,-e3, e2),
214 (e6, e7, e4,-e5,-e2, e3,-e0,-e1),
215 (e7,-e6, e5, e4,-e3,-e2, e1,-e0),
216 )
217
218 def product_on_basis(self, i, j):
219 return self._multiplication_table[i][j]
220
221 def one_basis(self):
222 r"""
223 Return the monomial index (basis element) corresponding to the
224 octonion unit element.
225
226 SETUP::
227
228 sage: from mjo.octonions import Octonions
229
230 TESTS:
231
232 This gives the correct unit element::
233
234 sage: set_random_seed()
235 sage: O = Octonions()
236 sage: x = O.random_element()
237 sage: x*O.one() == x and O.one()*x == x
238 True
239
240 """
241 return 0
242
243 def _repr_(self):
244 return ("Octonion algebra with base ring %s" % self.base_ring())
245
246 def multiplication_table(self):
247 """
248 Return a visual representation of this algebra's multiplication
249 table (on basis elements).
250
251 SETUP::
252
253 sage: from mjo.octonions import Octonions
254
255 EXAMPLES:
256
257 The multiplication table is what Wikipedia says it is::
258
259 sage: Octonions().multiplication_table()
260 +----++----+-----+-----+-----+-----+-----+-----+-----+
261 | * || e0 | e1 | e2 | e3 | e4 | e5 | e6 | e7 |
262 +====++====+=====+=====+=====+=====+=====+=====+=====+
263 | e0 || e0 | e1 | e2 | e3 | e4 | e5 | e6 | e7 |
264 +----++----+-----+-----+-----+-----+-----+-----+-----+
265 | e1 || e1 | -e0 | e3 | -e2 | e5 | -e4 | -e7 | e6 |
266 +----++----+-----+-----+-----+-----+-----+-----+-----+
267 | e2 || e2 | -e3 | -e0 | e1 | e6 | e7 | -e4 | -e5 |
268 +----++----+-----+-----+-----+-----+-----+-----+-----+
269 | e3 || e3 | e2 | -e1 | -e0 | e7 | -e6 | e5 | -e4 |
270 +----++----+-----+-----+-----+-----+-----+-----+-----+
271 | e4 || e4 | -e5 | -e6 | -e7 | -e0 | e1 | e2 | e3 |
272 +----++----+-----+-----+-----+-----+-----+-----+-----+
273 | e5 || e5 | e4 | -e7 | e6 | -e1 | -e0 | -e3 | e2 |
274 +----++----+-----+-----+-----+-----+-----+-----+-----+
275 | e6 || e6 | e7 | e4 | -e5 | -e2 | e3 | -e0 | -e1 |
276 +----++----+-----+-----+-----+-----+-----+-----+-----+
277 | e7 || e7 | -e6 | e5 | e4 | -e3 | -e2 | e1 | -e0 |
278 +----++----+-----+-----+-----+-----+-----+-----+-----+
279
280 """
281 n = self.dimension()
282 # Prepend the header row.
283 M = [["*"] + list(self.gens())]
284
285 # And to each subsequent row, prepend an entry that belongs to
286 # the left-side "header column."
287 M += [ [self.monomial(i)] + [ self.monomial(i)*self.monomial(j)
288 for j in range(n) ]
289 for i in range(n) ]
290
291 return table(M, header_row=True, header_column=True, frame=True)