--- title: "Dual quaternions via Clifford algebra" author: "Robin K. S. Hankin" bibliography: clifford.bib link-citations: true vignette: > %\VignetteIndexEntry{Dual quaternions via Clifford algebra} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r out.width='15%', out.extra='style="float:right; padding:10px"',echo=FALSE} knitr::include_graphics(system.file("help/figures/clifford.png", package = "clifford")) knitr::include_graphics(system.file("help/figures/onion.png", package = "onion")) ``` ```{r setup, include=FALSE} knitr::opts_chunk$set(echo = TRUE) library("clifford") library("onion") library("disordR") ``` To cite the `clifford` package in publications please use @hankin2022_clifford. 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: ```{r,echo=FALSE} M <- noquote(matrix(c( " 1", " i", " j", " k", " ε", " εi", " εj", " εk", " i", " -1", " k", " -j", " εi", " -ε", " εk", "-εj", " j", " -k", " -1", " i", " εj", "-εk", " -ε", " εi", " k", " j", " -i", " -1", " εk", " εj", "-εi", " -ε", " ε", " εi", " εj", " εk", " 0", " 0", " 0", " 0", "εi", " -ε", " εk", "-εj", " 0", " 0", " 0", " 0", "εj", "-εk", " -ε", " εi", " 0", " 0", " 0", " 0", "εk", " εj", "-εi", " -ε"," 0", " 0", " 0", " 0" ),8,8,byrow=TRUE)) rownames(M) <- c(" 1"," i"," j"," k"," ε","εi","εj","εk") colnames(M) <- c(" 1"," i"," j"," k"," ε"," εi"," εj"," εk") ``` ```{r,echo=FALSE,print=TRUE} M ``` Thus, for example, $ij=k$ (not $-k$). Here, $i,j,k$ are the unit quaternion basis elements (so $i^2=j^2=k^2=ijk=-1$) and $\varepsilon$ is the dual unit that commutes with $i,j,k$ and satisfies $\varepsilon^2=0$. We can implement this in a rough-and-ready way with the `onion` package [@hankin2006_onion] by observing that a dual quaternion may be represented as $A+\varepsilon B$, where $A$ and $B$ are quaternions. Then \[ (A+\varepsilon B)(C+\varepsilon D)== AC + \varepsilon(AD+BC)\] (note the absence of a $BD$ term, as $\varepsilon^2=0$). Very crude R idiom for this would be to define a `DQ` object as a list of quaternions, as in: ```{r} DQ_example <- list(as.quaternion(c(5,8,-3,3),single=TRUE), as.quaternion(c(-1,2,1,12),single=TRUE)) DQ_example ``` Then the product can be implemented as follows: ```{r} 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 $\varepsilon$ with $e_4$; to ensure $e_4^2=0$ we work with $Cl(3,0)$ which in package idiom is set by executing `signature(3,0)`. ```{r} signature(3,0) e(4)*e(4) ``` \[ \mathbf{i}\leftrightarrow -e_{12}\\ \mathbf{j}\leftrightarrow -e_{13}\\ \mathbf{k}\leftrightarrow -e_{23}\\ \varepsilon\leftrightarrow e_4 \] Conversion functions would be ```{r definecoercion} `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: ```{r} 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 ``` Checking that `cliff_to_DQ()` is a homomorphism follows the same line of reasoning: ```{r} 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) ``` The final verification is to check that functions `cliff_to_DQ()` and `DQ_to_cliff()` are isomorphisms: ```{r} identical(DQ1,cliff_to_DQ(DQ_to_cliff(DQ1))) identical(C1,DQ_to_cliff(cliff_to_DQ(C1))) ``` ## References