Clifford Algebras

The Clifford algebra Cliff_n is the associative algebra (over the real numbers) generated by n anticommuting square roots of -1, henceforth denoted i_1, ..., i_n. For example, Cliff_0 is the real numbers, Cliff_1 is the complex numbers, and Cliff_2 is the quaternions. (After that, things get more complicated.) An element a of Cliff_n can be written uniquely as a sum of terms a_{j_1 ... j_n} i_1^{j_1} ... i_n^{j_n}, where the j_k are 0 or 1. This canonical ordering agrees with the standard treatment of the quaternions --- that is, i_1 = i, i_2 = j, and i_1 i_2 = k --- which in turn agrees with the standard dot and cross products.

Elements of Cliff_0 are real numbers x, represented here by Mathematica lists of the form {0, x}. For n > 0, elements of Cliff_n are represented by {n, a, b}, where a and b are elements of Cliff_{n - 1} (or any lower-order Clifford algebra). When Mathematica shows us the result of a Clifford algebra computation, it is in this form by default.

For n <= 2, Cliff_2 is a normed division algebra. The norm respects multiplication; the product of an element and its conjugate is real and equals the norm squared; from the latter property one can derive multiplicative inverses. For n >= 3, none of this is true anymore.

Construction

The clConstruction function builds a Clifford number from two lower-order ones. The clFromVector function maps a vector in R^n to the corresponding element of Cliff_n. The clCoefficients function gives the list of coefficients in standard order; the length of this list is always a power of 2. The clFromCoefficients function builds a Clifford number from its coefficient list; the length of the list must be a power of 2. The clFancyForm function returns the Clifford number as a polynomial in the symbols i[1], ..., i[n]. This is purely for output; none of the functions in this notebook take fancy-form input.

clConstruction[a_, b_] := 
{Max[a[[1]], b[[1]]] + 1, a, b};
clFromVector[v_] :=
If[Length[v] == 0,
{0, 0},
clConstruction[
clFromVector[Take[v, Length[v] - 1]], 
{0, v[[Length[v]]]}]];
clCoefficients[a_] := 
If[a[[1]] == 0,
{a[[2]]},
Flatten[{
PadRight[clCoefficients[a[[2]]], 2^(a[[1]] - 1)], 
PadRight[clCoefficients[a[[3]]], 2^(a[[1]] - 1)]}]];
clFromCoefficients[c_] := 
If[Length[c] == 1,
{0, c[[1]]},
clConstruction[
clFromCoefficients[Take[c, {1, Length[c] / 2}]],
clFromCoefficients[Take[c, {Length[c] / 2 + 1, Length[c]}]]]];
clFancyForm[a_] := 
If[a[[1]] == 0,
a[[2]],
Expand[clFancyForm[a[[2]]] + clFancyForm[a[[3]]] i[a[[1]]]]];

This code produces a real number, a complex number, and a quaternion, and displays each in its implementation form and in its fancy form.

clFromCoefficients[{a}]
clFancyForm[clFromCoefficients[{a}]]
clFromCoefficients[{a0, a1}]
clFancyForm[clFromCoefficients[{a0, a1}]]
clFromCoefficients[{a00, a10, a01, a11}]
clFancyForm[clFromCoefficients[{a00, a10, a01, a11}]]

In this Mathematica notebook, we say that a Clifford number {n, a, b} is "full" if a and b are both of order n - 1, their constituents are order n - 2, and so on --- in other words, if lower-order Clifford numbers never need to be coerced to higher order to make sense of {n, a, b}. The Clifford algebra functions in this notebook automatically handle non-full Clifford numbers by coercing lower-order numbers to higher order as needed. The user has to be aware that two Clifford numbers that look different (because their implementation forms are different) may actually be equal (because one or both is not full). This comes up, for example, when using clCoefficients and clFromCoefficients. Namely, these functions are inverses on the interface level but not on the implementation level.

aa = clConstruction[clFromCoefficients[{a00, a10}], clFromCoefficients[{a01}]]
clFancyForm[aa]
clFromCoefficients[clCoefficients[aa]]
clFancyForm[clFromCoefficients[clCoefficients[aa]]]

Algebraic Operations

Addition of elements in Cliff_n is termwise. Conjugation negates all imaginary terms. The norm is the expected Euclidean norm.

clSum[a_, b_] := 
If[a[[1]] < b[[1]],
{b[[1]], clSum[a, b[[2]]], b[[3]]},
If[a[[1]] > b[[1]],
{a[[1]], clSum[a[[2]], b], a[[3]]},
If[a[[1]] == 0,
{0, a[[2]] + b[[2]]},
{a[[1]], clSum[a[[2]], b[[2]]], clSum[a[[3]], b[[3]]]}]]];
clNegation[a_] := 
If[a[[1]] == 0,
{0, -a[[2]]},
{a[[1]], clNegation[a[[2]]], clNegation[a[[3]]]}];
clNegativeConjugate[a_] := 
If[a[[1]] == 0,
{0, -a[[2]]},
{a[[1]], clNegativeConjugate[a[[2]]], a[[3]]}];
clConjugate[a_] := 
If[a[[1]] == 0,
{0, a[[2]]},
{a[[1]], clConjugate[a[[2]]], clNegation[a[[3]]]}];
clNorm[a_] := 
Sqrt[Dot[clCoefficients[a], clCoefficients[a]]];

Multiplication is complicated. Notice how i_n passes through the other i_k:

  i_n (i_1^{j_1} ... i_{n - 1}^{j_{n - 1}}) = (-1)^{j_1 + ... + j_{n - 1}} (i_1^{j_1} ... i_{n - 1}^{j_{n - 1}}) i_n.

There is an involution, which in this notebook we call "confugation", that multiplies i_1^{j_1} ... i_{n - 1}^{j_{n - 1}} by (-1)^{j_1 + ... + j_{n - 1}} and extends linearly to Cliff_{n-1}. That is, the confugate a^ of a equals a with all terms that contain an odd number of i_k negated. Then i_n a = a^ {i_n} for all a in Cliff_{n-1}. This is the key to multiplication:

  {n, a, b}.{n, c, d} = (a + b i_n).(c + d i_n)
                               = a c + b i_n c + a d i_n + b i_n d i_n
                               = a c + b c^ i_n + a d i_n + b d^ i_n i_n
                               = (a c - b d^) + (a d + b c^) i_n.

clNegativeConfugate[a_] := 
If[a[[1]] == 0, 
-a,
{a[[1]], clNegativeConfugate[a[[2]]], clConfugate[a[[3]]]}];
clConfugate[a_] := 
If[a[[1]] == 0, 
a,
{a[[1]], clConfugate[a[[2]]], clNegativeConfugate[a[[3]]]}];
clProduct[a_, b_] :=  
If[a[[1]] < b[[1]],
{b[[1]], clProduct[a, b[[2]]], clProduct[a, b[[3]]]},
If[a[[1]] > b[[1]],
{a[[1]], clProduct[a[[2]], b], clProduct[a[[3]], clConfugate[b]]},
If[a[[1]] == 0,
{0, a[[2]] * b[[2]]},
{a[[1]], 
clSum[
clProduct[a[[2]], b[[2]]], 
clProduct[a[[3]], clNegativeConfugate[b[[3]]]]],
clSum[
clProduct[a[[2]], b[[3]]], 
clProduct[a[[3]], clConfugate[b[[2]]]]]}]]];

As I've already mentioned, all of the Clifford algebra functions in this notebook automatically coerce lower-order Clifford algebra elements to higher order as needed. For example, the quaternion a_00 + a_10 i + a_01 j + a_11 k and the quaternion built from the real number b_00 and the complex number b_01 + b_11 i multiply correctly.

clProduct[clFromCoefficients[{a00, a10, a01, a11}], clConstruction[clFromCoefficients[{b00}], clFromCoefficients[{b01, b11}]]]
clProduct[clFromCoefficients[{a00, a10, a01, a11}], clFromCoefficients[{b00, 0, b01, b11}]]

Whereas conjugation negates all terms with one or more imaginary units, confugation negates all terms with an odd number of imaginary units.

clFancyForm[clConjugate[clFromCoefficients[{a00, a10, a01, a11}]]]
clFancyForm[clConfugate[clFromCoefficients[{a00, a10, a01, a11}]]]

Clifford Algebras As Matrix Algebras (Or Not)

I was hoping to give a simple recursive embedding of Clifford algebras into matrix algebras, but the relationship is not so simple. Baez's page 10 and Wikipedia's "Classification of Clifford algebras" say that Cliff_0 = R, Cliff_1 = C, Cliff_2 = H, Cliff_3 = H + H, Cliff_4 = 2x2 H, Cliff_5 = 4x4 C, Cliff_6 = 8x8 R, Cliff_7 = 8x8 R + 8x8 R, and thereafter everything is 8-periodic up to tensoring by 16x16 R. Anyway, here is the recursive construction, so that we can see how it goes wrong.

clBlockMatrix[m11_, m12_, m21_, m22_] := 
Join[Transpose[Join[Transpose[m11], Transpose[m12]]], Transpose[Join[Transpose[m21], Transpose[m22]]]];
clMatrix[a_] := 
If[a[[1]] == 0,
{{a[[2]]}},
clBlockMatrix[clMatrix[a[[2]]], clMatrix[a[[3]]], clMatrix[clNegativeConjugate[a[[3]]]], clMatrix[clConjugate[a[[2]]]]]];
clFromMatrix[m_] := 
clFromCoefficients[m[[1]]];

The embedding into matrix algebras works for Cliff_0, Cliff_1, and Cliff_2. This example demonstrates it for Cliff_2.

aa = clFromCoefficients[{a00, a10, a01, a11}];
bb = clFromCoefficients[{b00, b10, b01, b11}];
clFromMatrix[clMatrix[aa].clMatrix[bb]]  -
clProduct[aa, bb]

The embedding is transposed from what I'd like, in that a + b i corresponds to {{a, b}, {-b, a}} instead of {{a, -b}, {b, a}}. I have not been able to fix it. Constructing the correspondence in the way I'd like yields an order-reversing algebra homomorphism (since the transpose is order-reversing). For quaternions, it turns out to be equivalent to using a left-handed cross product. This bugs me, but I give up, because the map doesn't work for Cliff_3 (or higher Cliff_n) anyway.

aa = clFromCoefficients[{a000, a100, a010, a110, a001, a101, a011, a111}];
bb = clFromCoefficients[{b000, b100, b010, b110, b001, b101, b011, b111}];
clFromMatrix[clMatrix[aa].clMatrix[bb]] -
clProduct[aa, bb]

Quaternions And Rotations

This code rotates a vector v about a unit vector u through an angle of 2 alpha, by regarding the vector as a quaternion and conjugating it by exp(u alpha).

expualpha = clFromCoefficients[{Cos[alpha], u1 Sin[alpha], u2 Sin[alpha], u3 Sin[alpha]}]
rotatedvector = clProduct[clProduct[
expualpha,
clFromCoefficients[{0, v1, v2, v3}]], 
clConjugate[expualpha]] // Simplify;
rotatedvector = Take[clCoefficients[rotatedvector], {2, 4}]

This is linear in the coordinates of v, so we should be able to write it as a 3x3 matrix times the 3x1 v. We do this using some Mathematica trickery, and then we check our answer.

quatmatrix = Transpose[{
Coefficient[rotatedvector, v1], 
Coefficient[rotatedvector, v2], 
Coefficient[rotatedvector, v3]}]
rotatedvector - quatmatrix.{v1, v2, v3} // Simplify

Appendix F of the OpenGL Programming Guide says that the matrix is uuT + cos(alpha) (I - uuT) + sin(alpha) s, where s = {{0, -u3, u2}, {u3, 0, -u1}, {-u2, u1, 0}}, and alpha is twice the alpha above.

u = {u1, u2, u3}
uuT = Transpose[{u}].{u}
openglmatrix = uuT + Cos[2 alpha] (IdentityMatrix[3] - uuT) + Sin[2 alpha] {{0,-u3,u2},{u3,0,-u1},{-u2,u1,0}}

After taking into account that u is a unit vector, the two matrices end up identical.

quatmatrix - openglmatrix // Simplify

Now let's do the same thing with the quaternion in rectangular form a + b.

rotatedvector = clProduct[clProduct[
clFromCoefficients[{a, b1, b2, b3}],
clFromCoefficients[{0, v1, v2, v3}]], 
clFromCoefficients[{a, -b1, -b2, -b3}]] // Simplify;
rotatedvector = Take[clCoefficients[rotatedvector], {2, 4}]
quatmatrix = Transpose[{Coefficient[rotatedvector, v1], Coefficient[rotatedvector, v2], Coefficient[rotatedvector, v3]}];
quatmatrix // MatrixForm
rotatedvector - quatmatrix.{v1, v2, v3} // Simplify
b = {b1, b2, b3};
bbT = Transpose[{b}].{b};
openglmatrix = 1/(1 - a^2) bbT + (2 a^2 - 1) (IdentityMatrix[3] - 1/(1 - a^2) bbT) + 2a {{0,-b3,b2},{b3,0,-b1},{-b2,b1,0}} // Simplify;
quatmatrix - openglmatrix // Simplify

Pin_n and Spin_n

Pin_n is the group inside Cliff_n consisting of all products of unit vectors from R^n. It is a connected double-cover of the orthogonal group O_n. The covering map sends a unit vector in R^n to reflection across the hyperplane perpendicular to the unit vector. Spin_n is the subgroup of Pin_n consisting of all products of evenly many unit vectors from R^n. It is a connected double-cover of the special orthogonal group SO_n via the covering map just given. Pin_n is a disconnected double-cover of Spin_n in the same way that O_n is a disconnected double cover of SO_n. We want to understand all of these statements explicitly and in detail.

Define an element of Cliff_n to be "even" if all of its terms contain an even number of i_k; define it to be "odd" if all of its terms contain an odd number of i_k. Notice that the even and odd elements form the +1- and -1-eigenspaces for confugation. Define an element of Cliff_n to be "homogeneous" if it is either even or odd. Most elements of Cliff_n are not homogeneous. However, the product of two odd elements is even, the product of two even elements is even, and the product of an even element and an odd element is odd. Moreover, all vectors in R^n map to odd elements of Cliff_n. Thus all elements of Pin_n are homogeneous and all elements of Spin_n are even. Notice that Cliff_n = R^{2^n} as a Euclidean vector space, and the even and odd parts are each equal to R^{2^{n - 1}}.

For n > 2 the norm on Cliff_n does not respect multiplication --- that is, |a b| != |a| |b|.

!!Are they proper subsets, or are they everything? I think they're everything. And does the same equality hold for higher Cliff_n? (I don't have either containment there yet.)

Cliff_1

Cliff_1 is the complex numbers. O_1 = {1, -1} and SO_1 = {1}. Pin_1 = {1, i, -1, -i} and Spin_1 = {1, -1}. In this case only, the double-covers Pin_n -> O_n and Spin_n -> SO_n that should be connected are instead disconnected.

Cliff_2

Cliff_2 is the quaternions. This example shows that the sign conventions used here agree with the standard treatment of quaternion multiplication in terms of the dot and cross products.

Flatten[{a00 b00 - Dot[{a10, a01, a11}, {b10, b01, b11}], a00 {b10, b01, b11} + b00 {a10, a01, a11} + Cross[{a10, a01, a11}, {b10, b01, b11}]}]
clCoefficients[clProduct[clFromCoefficients[{a00, a10, a01, a11}], clFromCoefficients[{b00, b10, b01, b11}]]]

This example demonstrates that the even part of Cliff_2 is isomorphic to Cliff_1 in a rather simple way.

clCoefficients[clProduct[clFromCoefficients[{a00, 0, 0, a11}], clFromCoefficients[{b00, 0, 0, b11}]]]
clCoefficients[clProduct[clFromCoefficients[{a0, a1}], clFromCoefficients[{b0, b1}]]]

A vector of length 1 in R^2 looks like {cos a, sin a}. This maps into Cliff_2 as the quaternion cos a i_1 + sin a i_2 = {0, cos a, sin a, 0}. Products of these repeat in form every four steps. In fact, once we take into account phase changes by Pi, they repeat in form every two steps.

one = clFromVector[{Cos[t1], Sin[t1]}];
clCoefficients[one]
two = Simplify[clProduct[one, clFromVector[{Cos[t2], Sin[t2]}]]];
clCoefficients[two]
three = Simplify[clProduct[two, clFromVector[{Cos[t3], Sin[t3]}]]];
clCoefficients[three]
four = Simplify[clProduct[three, clFromVector[{Cos[t4], Sin[t4]}]]];
clCoefficients[four]
five = Simplify[clProduct[four, clFromVector[{Cos[t5], Sin[t5]}]]];
clCoefficients[five]

Thus Pin_2 is the subset of Cliff_2 consisting of elements of the forms {0, cos a, sin a, 0} and {cos a, 0, 0, sin a}. Spin_2 is the subset consisting of elements of the form {cos a, 0, 0, sin a}. The map from Pin_2 to O_2 sends {0, cos a, sin a 0} to reflection of R^2 across the line perpendicular to {cos a, sin a} --- that is, to the matrix {{-cos 2a, -sin 2a}, {-sin 2a, cos 2a}}. The restricted map from Spin_2 to SO_2 sends {cos a, 0, 0, sin a} to rotation by 2a --- that is, to the matrix {{cos 2a, -sin 2a}, {sin 2a, cos 2a}}. (I leave the calculations to the reader.) Notice how Spin_2 and SO_2 are circles, and the covering map Spin_2 -> SO_2 wraps the circle around itself twice. Similarly, Pin_2 and O_2 are each two circles, and the covering map wraps each circle around itself twice.

Spin_2 is geometrically identical to the unit circle in a copy of R^2. Its Lie algebra is the tangent space to 1, which is identified with the vectors perpendicular to 1 in this R^2, which is identified with the imaginary even elements in Cliff_2 --- those of the form a_11 i_1 i_2. Define as usual

    exp a_11 i_1 i_2 = SUM_{k = 0}^\infty 1/k! a_11^k (i_1 i_2)^k.

Notice that i_1 i_2 squares to -1, so that factor in the term cycles through i_1 i_2, -1, -i_1, i_2, 1, just like powers of i do in the complex numbers. Indeed, under the identification of the even part of Cliff_2 with Cliff_1, this exponential map is just complex exponentiation. So apparently it maps onto Spin_2.

Cliff_3

This example demonstrates that in Cliff_3 the norm does not respect multiplication.

cliff3example = clFromCoefficients[{1/Sqrt[2], 0, 0, 0, 0, 0, 0, 1 / Sqrt[2]}];
clNorm[cliff3example] * clNorm[cliff3example]
clNorm[clProduct[cliff3example, cliff3example]]

This example demonstrates that in Cliff_3 the product of an element with its conjugate is not necessarily real.

cliff3example = clFromCoefficients[{a000, a100, a010, a110, a001, a101, a011, a111}]
clNorm[cliff3example]
clProduct[cliff3example, clConjugate[cliff3example]]

This example demonstrates that the even part of Cliff_3 is isomorphic to Cliff_2 in a rather simple way. The isomorphism respects the norm, so the unit even subset of Cliff_3 is identified with the unit subset of Cliff_2.

clCoefficients[clProduct[clFromCoefficients[{a000, 0, 0, a110, 0, a101, a011, 0}], clFromCoefficients[{b000, 0, 0, b110, 0, b101, b011, 0}]]]
clCoefficients[clProduct[clFromCoefficients[{a00, a10, a01, a11}], clFromCoefficients[{b00, b10, b01, b11}]]]

This example shows that the product of two norm-1 even elements of Cliff_3 is again a norm-1 even element of Cliff_3. The same goes for even and odd, and odd and odd; the product of two homogeneous norm-1 elements is again norm-1 homogeneous.

aa = clFromCoefficients[{a000, 0, 0, a110, 0, a101, a011, 0}]
bb = clFromCoefficients[{b000, 0, 0, b110, 0, b101, b011, 0}]
Expand[clNorm[clProduct[aa, bb]]^2 - clNorm[aa]^2 * clNorm[bb]^2] /. {a000^2 + a110^2 + a101^2 + a011^2 -> 1, b000^2 + b110^2 + b101^2 + b011^2 -> 1}

Now we show that any unit even element in Cliff_3 can be written as a product of two vectors. By the following calculation, this amounts to showing that for any given real numbers a, b, c, d such that a^2 + b^2 + c^2 + d^2 there exist unit vectors x and y in R^3 such that Dot[x, y] = -a and Cross[x, y] = {d, -c, b}. Setting w = -a and v = {d, -c, b} so that w^2 + |v|^2 = 1, this amounts to showing that for any w in R and v in R^3 with w^2 + |v|^2 there exist unit x, y such that w = Dot[x, y] and v = Cross[x, y]. This is true; just let x and y be two unit vectors perpendicular to v, and adjust one of them until the angle works out.

clCoefficients[clProduct[clFromVector[{x1, x2, x3}], clFromVector[{y1, y2, y3}]]]

Now we know that all unit even elements in Cliff_3 are products of two vectors, hence in Spin_3. Thus Spin_3 equals the set of all unit even elements in Cliff_3. (We already had the other containment.) We have already identified this subset with the unit elements in Cliff_2. This completes the identification of Spin_3 with the unit quaternions.

Presumably the unit homogeneous elements in Cliff_3 are exactly Pin_3, but to be honest I've only proved that Pin_3 is contained in the unit homogeneous elements.

Geometrically, Spin_3 is the unit sphere in a copy of R^4, so the tangent space will be elements in that R^4 that are perpendicular to 1. Hence the Lie algebra is the set of imaginary even elements --- those of the form a110 i_1 i_2 + a101 i_1 i_3 + a011 i_2 i_3. We can define an exponential map for these imaginary even elements, using the usual power series. This seems complicated, but remember the norm-respecting algebra isomorphism to Cliff_2; the exponential map on the Cliff_3 side must have the same properties. It must map imaginary even elements of Cliff_3 onto Spin_3. It must send vectors of magnitude k 2 Pi to 1, vectors of magnitude k 2 Pi + Pi to -1, and vectors of magnitude k Pi + Pi / 2 into the imaginary part of Spin_3.