The previous post discussed octonions. This post will implement octonion multiplication in Python, and then sedenion multiplication.

## Cayley-Dickson construction

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[0]*y[0] - x[1]*y[1] - x[2]*y[2] - x[3]*y[3], x[0]*y[1] + x[1]*y[0] + x[2]*y[3] - x[3]*y[2], x[0]*y[2] - x[1]*y[3] + x[2]*y[0] + x[3]*y[1], x[0]*y[3] + x[1]*y[2] - x[2]*y[1] + x[3]*y[0] ]) # 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.

## Unit tests

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.

## Sedenion multiplication

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[0] = 1 return x*mask # sedenion multiplicaton 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.

A sedenion multiplier – the world was waiting for this!

One thing you can check is that every nonzero sedenion x has a multiplicative inverse: a sedenion y such that xy = yx = 1. Just let y = x*/(xx*), which makes sense because xx* is a positive real number.

“But wait! I thought the sedenions weren’t a division algebra!”

They’re not. The definition of division algebra requires that ab = 0 implies either a = 0 or b = 0. In the nonassociative case, this is not equivalent to the existence of multiplicative inverses.

There are nonassociative algebras with multiplicative inverses that aren’t division algebras (like the sedenions) but also division algebras that don’t have multiplicative inverses!

Mercifully, the octonions is a division algebra that does have multiplicative inverses, given by the same formula as for the sedenions.

By the way, there’s a typo in the title of this post.