Quaternionic arithmetic with Clifford algebra

To cite the clifford package in publications please use Hankin (2022). This short document shows how quaternionic arithmetic may be implemented as a special case of Clifford algebras. This is done for illustrative purposes only; to manipulate quaternions in R the onion package (Hankin 2006) is much more efficient and includes more transparent idiom.

Hamilton’s Broome Bridge insight:

i2 = j2 = k2 = ijk = −1

The BBI and associativity together imply

jk = −kj = i   ki = −ik = j   ij = −ji = k   

and if we require a distributive algebra we get the quaternions. A general quaternion is of the form a + ib + jc + kd; addition is componentwise and multiplication follows from the above.

To express quaternionic multiplication using Clifford algebra we make the following identifications:

$$ \mathbf{i}\leftrightarrow -e_{12}\\ \mathbf{j}\leftrightarrow -e_{13}\\ \mathbf{k}\leftrightarrow -e_{23}\\ $$

Thus, for example, ii = (−e12)(−e12) = +e1212 = −e1122 = −1 and ij = (−e12)(−e13) = +e1213 = −e1123 = −e23 = k. The default signature [in which ei2 = +1] is fine here, but as a safety measure we can set maxdim to 3:

options(maxdim=3)  # paranoid safety measure

We might wish to multiply q1 = 1 + 2i + 3j + 4k by q2 = −2 + i − 2j + k:

q1 <- +1 + 2* -e(c(1,2)) + 3*-e(c(1,3)) + 4*-e(c(2,3))
q1
## Element of a Clifford algebra, equal to
## + 1 - 2e_12 - 3e_13 - 4e_23
q2 <- -2 + 1* -e(c(1,2)) - 2*-e(c(1,3)) + 1*-e(c(2,3))
q2
## Element of a Clifford algebra, equal to
## - 2 - 1e_12 + 2e_13 - 1e_23
q1*q2
## Element of a Clifford algebra, equal to
## - 2 - 8e_12 + 6e_13 + 14e_23

The product would correspond to −2 + 8i − 6j − 14k. Note that the “*” in “q1*q2” is a clifford product. It is possible to leverage the onion package and coerce between clifford objects and quaternions (but don’t actually do it, you crazy fool):

`clifford_to_quaternion` <- function(C){
    C <- as.clifford(C)
    tC <- disordR::elements(terms(C))
    stopifnot(all(c(tC,recursive=TRUE) <= 3))
    jj <- unlist(lapply(tC,length))
    stopifnot(all(jj <= 2))    # safety check
    stopifnot(all(jj%%2 == 0)) # safety check
    out <- matrix(c(const(C), -getcoeffs(C,list(c(1,2),c(1,3),c(2,3)) )))
    as.quaternion(out)
}
`quaternion_to_clifford` <- function(Q){
  Q <- as.numeric(Q)
  stopifnot(length(Q)==4)
  clifford(list(numeric(0),c(1,2),c(1,3),c(2,3)),c(Q[1],-Q[2:4]))
}

We may verify that these maps behave properly by defining some random-ish quaternions and Clifford objects:

q1 <- +1 + 2* -e(c(1,2)) + 3*-e(c(1,3)) + 4*-e(c(2,3))
q2 <- -2 + 1* -e(c(1,2)) - 2*-e(c(1,3)) + 1*-e(c(2,3))
H1 <- as.quaternion(c(3,-5,2,1),single=TRUE)
H2 <- as.quaternion(c(1,2,-2,2),single=TRUE)

First, check that they are inverses of one another:

c(  # check they are inverses of one another
q1 == quaternion_to_clifford(clifford_to_quaternion(q1)),
q2 == quaternion_to_clifford(clifford_to_quaternion(q2)),
H1 == clifford_to_quaternion(quaternion_to_clifford(H1)),
H2 == clifford_to_quaternion(quaternion_to_clifford(H2))
)
## [1] TRUE TRUE TRUE TRUE

Next, verify that they are homomorphisms:

c(
q1*q2 == quaternion_to_clifford(clifford_to_quaternion(q1)*clifford_to_quaternion(q2)),
H1*H2 == clifford_to_quaternion(quaternion_to_clifford(H1)*quaternion_to_clifford(H2))
)
## [1] TRUE TRUE

Note that in package idiom the asterisk, “*” represents either Clifford geometric product or Hamilton’s quaternionic multiplication depending on its arguments.

Alternative mapping

Alternatively we might consider the even subalgebra of Cl (0, 3) with general element q0 + q1e23 − q2e13 + q3e12 (note change of sign for q2). Thus

$$ \mathbf{i}\leftrightarrow e_{23}\\ \mathbf{j}\leftrightarrow -e_{13}\\ \mathbf{k}\leftrightarrow e_{12}\\ $$

A quick-and-dirty R function might be

signature(0,3)
cliff2quat <- function(C){
  out <- getcoeffs(C,list(numeric(0),c(2,3),c(1,3),c(1,2)))
  out[2] <- -out[2]
  as.quaternion(out,single=TRUE)
}

quat2cliff <- function(H){
  jj <- c(as.matrix(H))
  jj[2] <- -jj[2]
  clifford(list(numeric(0),c(2,3),c(1,3),c(1,2)),jj)
}

Then verification is straightforward:

c(
  cliff2quat(quat2cliff(H1)) == H1,
  cliff2quat(quat2cliff(H2)) == H2,
  quat2cliff(cliff2quat(q1)) == q1,
  quat2cliff(cliff2quat(q2)) == q2,
  cliff2quat(q1*q2) == cliff2quat(q1) * cliff2quat(q2),
  quat2cliff(H1*H2) == quat2cliff(H1) * quat2cliff(H2)
)
## [1] TRUE TRUE TRUE TRUE TRUE TRUE

References

Hankin, R. K. S. 2006. “Normed Division Algebras with R: Introducing the ‘Onion‘ Package.” R News 6 (2): 49–52.
———. 2022. “Clifford Algebra in R.” arXiv. https://doi.org/10.48550/ARXIV.2209.13659.