To cite the clifford
package in publications please use
Hankin (2022). This short document
shows how dual quaternions may be implemented using Clifford algebra.
The dual quaternions are an interesting and useful mathematical object
which finds applications in solid-body mechanics. Consider the following
multiplication table:
## 1 i j k ε εi εj εk
## 1 1 i j k ε εi εj εk
## i i -1 k -j εi -ε εk -εj
## j j -k -1 i εj -εk -ε εi
## k k j -i -1 εk εj -εi -ε
## ε ε εi εj εk 0 0 0 0
## εi εi -ε εk -εj 0 0 0 0
## εj εj -εk -ε εi 0 0 0 0
## εk εk εj -εi -ε 0 0 0 0
Thus, for example, ij = k (not −k). Here, i, j, k are the
unit quaternion basis elements (so i2 = j2 = k2 = ijk = −1)
and ε is the dual unit that
commutes with i, j, k and
satisfies ε2 = 0.
We can implement this in a rough-and-ready way with the
onion
package (Hankin 2006) by observing that a
dual quaternion may be represented as A + εB, where
A and B are quaternions. Then
(A + εB)(C + εD) = = AC + ε(AD + BC)
(note the absence of a BD term, as ε2 = 0). Very crude R
idiom for this would be to define a DQ
object as a list of
quaternions, as in:
DQ_example <- list(as.quaternion(c(5,8,-3,3),single=TRUE), as.quaternion(c(-1,2,1,12),single=TRUE))
DQ_example
## [[1]]
## [1]
## Re 5
## i 8
## j -3
## k 3
##
## [[2]]
## [1]
## Re -1
## i 2
## j 1
## k 12
Then the product can be implemented as follows:
DQ_prod_DQ <- function(DQ1,DQ2){
A <- DQ1[[1]] ; B <- DQ1[[2]] ; C <- DQ2[[1]] ; D <- DQ2[[2]]
list(A*C, A*D+B*C)
}
We will use follow the coercion used in
inst/quaternion_clifford.Rmd
(not the alternative
mapping, which requires a different signature) to coerce from cliffords
to quaternions. We may map the dual quaternions to Clifford objects by
additionally identifying ε
with e4; to ensure
e42 = 0
we work with Cl(3, 0)
which in package idiom is set by executing
signature(3,0)
.
## Element of a Clifford algebra, equal to
## the zero clifford element (0)
$$ \mathbf{i}\leftrightarrow -e_{12}\\ \mathbf{j}\leftrightarrow -e_{13}\\ \mathbf{k}\leftrightarrow -e_{23}\\ \varepsilon\leftrightarrow e_4 $$
Conversion functions would be
`cliff_to_DQ` <- function(C){ # terms such as e_3 and e_123 and e_34 are silently discarded
quat <- getcoeffs(C,list(numeric(0),c(1,2),c(1,3),c(2,3)))
quat[-1] <- -quat[-1]
epsi <- getcoeffs(C,list(4,c(1,2,4),c(1,3,4),c(2,3,4)))
epsi[-1] <- -epsi[-1]
return(list(as.quaternion(quat,single=TRUE),as.quaternion(epsi,single=TRUE)))
}
`DQ_to_cliff` <- function(DQ){ # DQ is a two-element list of quaternions
jj1 <- c(as.matrix(DQ[[1]]))
jj1[-1] <- -jj1[-1]
jj2 <- c(as.matrix(DQ[[2]]))
jj2[-1] <- -jj2[-1]
clifford(list(numeric(0),c(1,2),c(1,3),c(2,3),4,c(1,2,4),c(1,3,4),c(2,3,4)),c(jj1,jj2))
}
Check that DQ_to_cliff()
is indeed a homomorphism:
DQ1 <- list(as.quaternion(c(5,6,2,-7),single=TRUE),as.quaternion(c(-3,1,4,8),single=TRUE))
DQ2 <- list(as.quaternion(c(-1,3,1,4),single=TRUE),as.quaternion(c(1,9,-7,4),single=TRUE))
LHS <- DQ_to_cliff(DQ1) * DQ_to_cliff(DQ2)
RHS <- DQ_to_cliff(DQ_prod_DQ(DQ1,DQ2))
LHS == RHS
## [1] TRUE
Checking that cliff_to_DQ()
is a homomorphism follows
the same line of reasoning:
C1 <- clifford(list(numeric(0),c(1,3),c(1,2,4),c(1,3,4)),c(3,7,11,13))
C2 <- clifford(list(numeric(0),c(1,2),c(1,3),c(1,3,4)),c(2,5,6,17))
LHS <- cliff_to_DQ(C1*C2)
RHS <- DQ_prod_DQ(cliff_to_DQ(C1),cliff_to_DQ(C2))
identical(LHS,RHS)
## [1] TRUE
The final verification is to check that functions
cliff_to_DQ()
and DQ_to_cliff()
are
isomorphisms:
## [1] TRUE
## [1] TRUE