The previous post discussed octonions. This post will implement octonion multiplication in Python, and then sedenion multiplication.
There’s a way to bootstrap quaternion multiplication into octonion multiplication, so we’ll reuse the quaternion multiplication code from an earlier post. It’s called the Cayley-Dickson construction. For more on this construction , see John Baez’s treatise on octonions.
If you represent an octonion as a pair of quaternions, then multiplication can be defined by
(a, b) (c, d) = (ac – d*b, da + bc*)
where a star superscript on a variable means (quaternion) conjugate.
Note that this isn’t the only way to define multiplication of octonions. There are many (480?) isomorphic ways to define octonion multiplication.
(You can take the Cayley-Dickson construction one step further, creating sedenions as pairs of octonions. We’ll also provide code for sedenion multiplication below.)
Code for octonion multiplication
import numpy as np # quaternion multiplication def qmult(x, y): return np.array([ x*y - x*y - x*y - x*y, x*y + x*y + x*y - x*y, x*y - x*y + x*y + x*y, x*y + x*y - x*y + x*y ]) # quaternion conjugate def qstar(x): return x*np.array([1, -1, -1, -1]) # octonion multiplication def omult(x, y): # Split octonions into pairs of quaternions a, b = x[:4], x[4:] c, d = y[:4], y[4:] z = np.zeros(8) z[:4] = qmult(a, c) - qmult(qstar(d), b) z[4:] = qmult(d, a) + qmult(b, qstar(c)) return z
Update: See this post for refactored code. Handles quaternions, octonions, sedenions, etc. all with one function.
Here are some unit tests to verify that our multiplication has the expected properties. We begin by generating three octonions with norm 1.
from numpy.linalg import norm def random_unit_octonion(): x = np.random.normal(size=8) return x / norm(x) x = random_unit_octonion() y = random_unit_octonion() z = random_unit_octonion()
We said in the previous post that octonions satisfy the “alternative” condition, a weak form of associativity. Here we verify this property as a unit test.
eps = 1e-15 # alternative identities a = omult(omult(x, x), y) b = omult(x, omult(x, y)) assert( norm(a - b) < eps ) a = omult(omult(x, y), y) b = omult(x, omult(y, y)) assert( norm(a - b) < eps )
We also said in the previous post that the octonions satisfy the “Moufang” conditions.
# Moufang identities a = omult(z, omult(x, omult(z, y))) b = omult(omult(omult(z, x), z), y) assert( norm(a - b) < eps ) a = omult(x, omult(z, omult(y, z))) b = omult(omult(omult(x, z), y), z) assert( norm(a - b) < eps ) a = omult(omult(z,x), omult(y, z)) b = omult(omult(z, omult(x, y)), z) assert( norm(a - b) < eps ) a = omult(omult(z,x), omult(y, z)) b = omult(z, omult(omult(x, y), z)) assert( norm(a - b) < eps )
And finally, we verify that the product of unit length octonions has unit length.
# norm condition n = norm(omult(omult(x, y), z)) assert( abs(n - 1) < eps )
The next post uses the octionion multiplication code above to look at the distribution of the associator (xy)z – x(yz) to see how far multiplication is from being associative.
The only normed division algebras over the reals are the real numbers, complex numbers, quaternions, and octonions. These are real vector spaces of dimension 1, 2, 4, and 8.
You can proceed analogously and define a real algebra of dimension 16 called the sedenions. When we go from complex numbers to quaternions we lose commutativity. When we go from quaternions to octonions we lose full associativity, but retain a weak version of associativity. Even weak associativity is lost when we move from octonions to sedenions. Non-zero octonions form an alternative loop with respect to multiplication, but sedenions do not.
Sedenions have multiplicative inverses, but there are also some zero divisors, i.e. non-zero vectors whose product is zero. So senenions do not form a division algebra. If you continue the Cayley-Dickson construction past the sedenions, you keep getting zero divisors, so no more division algebras.
Here’s Python code for sedenion multiplication, building on the code above.
def ostar(x): mask = -np.ones(8) mask = 1 return x*mask # sedenion multiplication def smult(x, y): # Split sedenions into pairs of octonions a, b = x[:8], x[8:] c, d = y[:8], y[8:] z = np.zeros(16) z[:8] = omult(a, c) - omult(ostar(d), b) z[8:] = omult(d, a) + omult(b, ostar(c)) return z
As noted above, see this post for more concise code that also generalizes further.