Skip to article frontmatterSkip to article content
%%capture
'''
(C) Copyright 2020-2025 Murilo Marques Marinho (murilomarinho@ieee.org)

     This file is licensed in the terms of the
     Attribution-NonCommercial-ShareAlike 4.0 International (CC BY-NC-SA 4.0)
     license.

 Derivative work of:
 https://github.com/dqrobotics/learning-dqrobotics-in-matlab/tree/master/robotic_manipulators
 Contributors to this file:
     Murilo Marques Marinho (murilomarinho@ieee.org)
'''

DQ3 Dual Quaternion Basics using DQ Robotics

I found an issue

Thank you! Please report it at https://github.com/MarinhoLab/OpenExecutableBooksRobotics/issues

Introduction

Before introducing dual quaternions, it is common practice to introduce the concept of dual numbers. Let us start with that.

As always, let us make the essential installations and imports.

%%capture
%pip install dqrobotics dqrobotics-pyplot
%pip install dqrobotics dqrobotics-pyplot --break-system-packages
from dqrobotics import *
from dqrobotics_extensions.pyplot import plot
import matplotlib.pyplot as plt
from math import pi, sin, cos 

Dual Numbers

The set of dual numbers D\mathbb{D} is yet a different way to extend R\mathbb{R} . Any dual number can be written in the form

d=a+bε,\underline{ d } =a+b\varepsilon ,

where a,ba,b\in R\mathbb{R} . The algebra of dual numbers is based on the following property of the nilpotent dual unit

ε0,ε2=0.\varepsilon \not= 0,\varepsilon^2 =0\ldotp

For instance, the dual number

d1=10239ε{\underline{ d } }_1 =10-239\varepsilon

can be written using DQ Robotics as follows.

d1 = 10 - 239*E_
print(f"d1 = {d1}")
d1 =  - 10 + E*(239)

Note that the properties of the dual unit hold.

E_
E*(1)
E_ * E_
0

Operations on dual numbers

The operations on dual numbers are very similar to the operations on real numbers. We just need to respect the property ε0,ε2=0.\varepsilon \not= 0,\varepsilon^2 =0\ldotp

For instance, for the dual numbers

d1=a1+εb1,{\underline{ d } }_1 =a_1 +{\varepsilon b}_1,
d2=a2+εb2.{\underline{d} }_2 =a_2 +{\varepsilon b}_2.
d1 = 10 - 239*E_
print(f"d1 = {d1}")
d1 =  - 10 + E*(239)
d2 = 55 + 32*E_
print(f"d2 = {d2}")
d2 = 55 + E*(32)

Sum/Subtraction

d1±d2=(a1±a2)+(b1±b2)ε.{\underline{ d } }_1 \pm {\underline{ d } }_2 =\left(a_1 \pm a_2 \right)+\left(b_1 \pm b_2 \right)\varepsilon \ldotp
d1 + d2
45 + E*(271)
d1 - d2
- 65 + E*(207)

Product

In the dual number product, the nilpotent property of the dual unit shows.

d1d2=(a1a2)+ε(a1b2+a2b1)+ε2(b1b2)d1d2=(a1a2)+ε(a1b2+a2b1)\begin{array}{l} {\underline{ d } }_1 {\underline{ d } }_2 =\left(a_1 a_2 \right)+\varepsilon \left(a_1 b_2 +a_2 b_1 \right)+\varepsilon^2 \left(b_1 b_2 \right)\\ \Rightarrow {\underline{ d } }_1 {\underline{ d } }_2 =\left(a_1 a_2 \right)+\varepsilon \left(a_1 b_2 +a_2 b_1 \right) \end{array}
d1 * d2
- 550 + E*(12825)

Primary part

The operator P(.)P\left(\ldotp \right) can be used to obtain the primary part of the dual number. (The primary part is that which is not multiplied by the dual unit)

P(d)=a.P\left(\underline{ d } \right)=a.
P(d1)
- 10

Dual part

The operator D(.)D\left(\ldotp \right) can be used to obtain the dual part of the dual number. (The dual number is that which is multiplied by the dual unit)

D(d)=b.D\left(\underline{ d } \right) = b.
D(d1)
239

Dual quaternions

When we extend the concept of dual numbers to be composed of two quaternions, we have what we call dual quaternions. They compose the set H\mathcal{H} and can always be written in the form

h=h+hε,\underline{\mathit{\mathbf{h}}} =\mathit{\mathbf{h}}+\mathit{\mathbf{h}}^{\prime} \varepsilon,

where h,h\mathit{\mathbf{h}},\mathit{\mathbf{h}}^{\prime} \in H\mathbb{H} .

Note that RH\mathbb{R}\subset \mathcal{H} , CH\mathbb{C}\subset \mathcal{H} , HH\mathbb{H}\subset \mathcal{H} , DH\mathbb{D}\subset \mathcal{H} . That is, the real numbers, the complex numbers, the quaternions, and the dual numbers are all subsets of dual quaternions.

For instance, the dual quaternion

h1=5+6ı^+7ȷ^+8k^+ε(9+15ı^+7ȷ^+8k^){\underline{\mathit{\mathbf{h}}} }_1 =5+6\hat{\imath } +7\hat{\jmath } +8\hat{k} +\varepsilon \left(9+15\hat{\imath } +7\hat{\jmath } +8\hat{k} \right)

can be written using DQ Robotics as follows

h1 = 5 + 6*i_ + 7*j_ + 8*k_ + E_*(9 + 15*i_ + 7*j_ + 8*k_)
print(f"h1 = {h1}")
h1 = 5 + 6i + 7j + 8k + E*(9 + 15i + 7j + 8k)

Operations on dual quaternions

Considering h1,h2{\underline{\mathit{\mathbf{h}}} }_1 ,{\underline{\mathit{\mathbf{h}}} }_2 \in H\mathcal{H} , for example.

h1 = 5 + 6*i_ + 7*j_ + 8*k_ + E_*(9 + 15*i_ + 7*j_ + 8*k_)
print(f"h1 = {h1}")
h1 = 5 + 6i + 7j + 8k + E*(9 + 15i + 7j + 8k)
h2 = -25 + 1*k_ + E_*( 7*j_ + 8*k_)
print(f"h2 = {h2}")
h2 =  - 25 + 1k + E*(7j + 8k)

Sum/Subtraction

h1±h2=(h1±h2)+ε(h1±h2).{\underline{\mathit{\mathbf{h}}} }_1 \pm {\underline{\mathit{\mathbf{h}}} }_2 =\left({\mathit{\mathbf{h}}}_1 \pm {\mathit{\mathbf{h}}}_2 \right)+\varepsilon \left({\mathit{\mathbf{h}}}_1^{\prime } \pm {\mathit{\mathbf{h}}}_2^{\prime } \right).
h1 + h2
- 20 + 6i + 7j + 9k + E*(9 + 15i + 14j + 16k)
h1 - h2
30 + 6i + 7j + 7k + E*(9 + 15i)

Multiplication

h1h2=(h1h2)+ε(h1h2+h2h1).{\underline{\mathit{\mathbf{h}}} }_1 {\underline{\mathit{\mathbf{h}}} }_2 =\left({\mathit{\mathbf{h}}}_1 {\mathit{\mathbf{h}}}_2 \right)+\varepsilon \left({\mathit{\mathbf{h}}}_1 {\mathit{\mathbf{h}}}_2^{\prime } +{\mathit{\mathbf{h}}}_2 {\mathit{\mathbf{h}}}_1^{\prime } \right).
h1 * h2
- 133 - 143i - 181j - 195k + E*( - 346 - 368i - 203j - 109k)

Notice that the multiplication between dual quaternions is, in general, NOT COMMUTATIVE.

h1h2h2h1.{\mathit{\mathbf{h}}}_1 {\mathit{\mathbf{h}}}_2 \not= {{\mathit{\mathbf{h}}}_2 \mathit{\mathbf{h}}}_1.
h3 = h1 * h2
print(f"h3 = {h3}")
h3 =  - 133 - 143i - 181j - 195k + E*( - 346 - 368i - 203j - 109k)
h4 = h2 * h1
print(f"h4 = {h4}")
h4 =  - 133 - 157i - 169j - 195k + E*( - 346 - 382i - 77j - 193k)
if h3==h4:
    print('h1*h2 is equal to h2*h1')
else:
    print('h1*h2 is not equal to h2*h1')
h1*h2 is not equal to h2*h1

Conjugation

The conjugate of the dual quaternion is obtained by taking the conjugate of the primary part and the dual part of the dual quaternion. Both of which are quaternions. This same logic applies for many other operators.

(h1)=(h1)+ε(h1).{\left({\underline{\mathit{\mathbf{h}}} }_1 \right)}^* ={\left({\mathit{\mathbf{h}}}_1 \right)}^* +{\varepsilon \left({\mathit{\mathbf{h}}}_1^{\prime } \right)}^*.
conj(h1)
5 - 6i - 7j - 8k + E*(9 - 15i - 7j - 8k)

Norm

h1=h1(h1).\left|\right|{\underline{\mathit{\mathbf{h}}} }_1 \left|\right|=\sqrt{{\underline{\mathit{\mathbf{h}}} }_1 {\left({\underline{\mathit{\mathbf{h}}} }_1 \right)}^* }.
norm(h1)
13.190906 + E*(18.800831)

Real part

Re(h1)=Re(h1)+ϵRe(h1).\textrm{Re}\left({\underline{\mathit{\mathbf{h}}} }_1 \right)=\textrm{Re}\left({\mathit{\mathbf{h}}}_1 \right)+\epsilon \textrm{Re}\left({\mathit{\mathbf{h}}}_1^{\prime } \right).
Re(h1)
5 + E*(9)

If the real part of a dual quaternion is zero, it is called a pure dual quaternion, and belongs to the set Hp{\mathcal{H}}_p .

Imaginary part

Im(h1)=Im(h1)+ϵIm(h1).\textrm{Im}\left({\underline{\mathit{\mathbf{h}}} }_1 \right)=\textrm{Im}\left({\mathit{\mathbf{h}}}_1 \right)+\epsilon \textrm{Im}\left({\mathit{\mathbf{h}}}_1^{\prime } \right).
Im(h1)
6i + 7j + 8k + E*(15i + 7j + 8k)

If the imaginary part of a dual quaternion is zero, it is a dual number.

Primary part

P(h1)=h1.P\left({\underline{\mathit{\mathbf{h}}} }_1 \right)={\mathit{\mathbf{h}}}_1.
P(h1)
5 + 6i + 7j + 8k

Dual part

D(h1)=h1.D\left({\underline{\mathit{\mathbf{h}}} }_1 \right)={\mathit{\mathbf{h}}}_1^{\prime }.
D(h1)
9 + 15i + 7j + 8k

Vec6

The imaginary part of dual quaternions can be mapped to R6{\mathbb{R}}^6 as follows

vec6(h1)=[vec3(h1)vec3(h1)].{\textrm{vec}}_6 \left({\underline{\mathit{\mathbf{h}}} }_1 \right)=\left\lbrack \begin{array}{c} {\textrm{vec}}_3 \left({\mathit{\mathbf{h}}}_1 \right)\\ {\textrm{vec}}_3 \left({\mathit{\mathbf{h}}}_1^{\prime } \right) \end{array}\right\rbrack.
vec6(h1)
array([ 6., 7., 8., 15., 7., 8.])

Vec8

Quaternions can be mapped to R8{\mathbb{R}}^8 as follows

vec4(h1)=[vec4(h1)vec4(h1)]{\textrm{vec}}_4 \left({\underline{\mathit{\mathbf{h}}} }_1 \right)=\left\lbrack \begin{array}{c} {\textrm{vec}}_4 \left({\mathit{\mathbf{h}}}_1 \right)\\ {\textrm{vec}}_4 \left({\mathit{\mathbf{h}}}_1^{\prime } \right) \end{array}\right\rbrack
vec8(h1)
array([ 5., 6., 7., 8., 9., 15., 7., 8.])

Hamilton Operators

The hamilton operators are useful to provide a form of commutativity in the dual quaternion multiplication.

vec8(h1h2)=H8+(h1)vec8(h2)=H8(h2)vec8(h1){\textrm{vec}}_8 \left({\underline{\mathit{\mathbf{h}}} }_1 {\underline{\mathit{\mathbf{h}}} }_2 \right)=\overset{+}{{\mathit{\mathbf{H}}}_8 } \left({\underline{\mathit{\mathbf{h}}} }_1 \right){\textrm{vec}}_8 \left({\underline{\mathit{\mathbf{h}}} }_2 \right)=\overset{-}{{\mathit{\mathbf{H}}}_8 } \left({\underline{\mathit{\mathbf{h}}} }_2 \right){\textrm{vec}}_8 \left({\underline{\mathit{\mathbf{h}}} }_1 \right)
vec8(h1*h2)
array([-133., -143., -181., -195., -346., -368., -203., -109.])
hamiplus8(h1)*vec8(h2)
array([[-125., -0., -0., -8., 0., 0., 0., 0.], [-150., 0., -0., 7., 0., 0., 0., 0.], [-175., 0., 0., -6., 0., 0., 0., 0.], [-200., -0., 0., 5., 0., 0., 0., 0.], [-225., -0., -0., -8., 0., -0., -49., -64.], [-375., 0., -0., 7., 0., 0., -56., 56.], [-175., 0., 0., -15., 0., 0., 35., -48.], [-200., -0., 0., 9., 0., -0., 42., 40.]])
haminus8(h2)*vec8(h1)
array([[-125., -0., -0., -8., 0., 0., 0., 0.], [ 0., -150., 7., -0., 0., 0., 0., 0.], [ 0., -6., -175., 0., 0., 0., 0., 0.], [ 5., 0., -0., -200., 0., 0., 0., 0.], [ 0., -0., -49., -64., -225., -0., -0., -8.], [ 0., 0., 56., -56., 0., -375., 7., -0.], [ 35., -48., 0., 0., 0., -15., -175., 0.], [ 40., 42., -0., 0., 9., 0., -0., -200.]])

Unit Dual Quaternions motion of rigid bodies

Unit dual quaternions compose the set S\underline{{\mathcal{S}}} , which represent pose transformations of the reference frame of rigid bodies in three dimensional space.

A unit dual quaternion representing a translation followed by a rotation can always be written in the form

x=r+ε12t.r\underline{\mathit{\mathbf{x}}} =\mathit{\mathbf{r}}+\varepsilon \frac{1}{2}\mathit{\mathbf{t}}\ldotp \mathit{\mathbf{r}}

where r\mathit{\mathbf{r}}\in S3{\mathbb{S}}^3 is the unit-norm quaternion representing the rotation; and tt Hp\in {\mathbb{H}}_p is the quaternion representing the translation.

Remember this nomenclature:

  • tt Hp\in {\mathbb{H}}_p is a translation.
  • r\mathit{\mathbf{r}}\in S3{\mathbb{S}}^3 is a rotation.
  • x\underline{\mathit{\mathbf{x}}} \in S\underline{{\mathcal{S}}} is a pose transformation (combined translation and rotation).

Unit-norm dual-quaternions, as the name says, have unit norm

x=1.\left|\right|\underline{\mathit{\mathbf{x}}}\left|\right|=1\ldotp

For instance, to represent the translation of 1 meter about the y-axis,

t1=ȷ^.{\mathit{\mathbf{t}}}_1 =\hat{\jmath }.
t1 = j_
print(f"t1 = {t1}")
t1 = 1j

followed by a rotation of π2\frac{\pi }{2} rad about the x-axis,

r1=cos(π4)+ı^sin(π4)r_1 =\cos \left(\frac{\pi }{4}\right)+\hat{\imath } \sin \left(\frac{\pi }{4}\right)
r1 = cos(pi/4) + i_*sin(pi/4)
print(f"r1 = {r1}")
r1 = 0.707107 + 0.707107i

the pose transformation will be

x1=r1+ε12t1r1=cos(π4)+ı^sin(π4)+12ε(ȷ^cos(π4)k^sin(π4)){\underline{\mathit{\mathbf{x}}} }_1 ={\mathit{\mathbf{r}}}_1 +\varepsilon \frac{1}{2}{\mathit{\mathbf{t}}}_1 {\mathit{\mathbf{r}}}_1 =\cos \left(\frac{\pi }{4}\right)+\hat{\imath } \sin \left(\frac{\pi }{4}\right)+\frac{1}{2}\varepsilon \left(\hat{\jmath } \cos \left(\frac{\pi }{4}\right)-\hat{k} \sin \left(\frac{\pi }{4}\right)\right)
x1 = r1 + 0.5*E_*t1*r1
print(f"x1 = {x1}")
x1 = 0.707107 + 0.707107i + E*(0.353553j - 0.353553k)

We can check that x1{\underline{\mathit{\mathbf{x}}} }_1 indeed has unit norm

norm(x1)
1

Dual quaternion double-cover property

Given the way that the pose transformation is based on quaternions, the double cover also extends to dual quaternions.

x1{\underline{\mathit{\mathbf{x}}} }_1

and

x1-{\underline{\mathit{\mathbf{x}}} }_1

represent the same pose transformation.

No pose transformation

The dual quaternion that represents that there is no pose transformation is

x1=1{\underline{\mathit{\mathbf{x}}} }_1 =1
x1 = DQ([1])

Plotting unit dual quaternions

Using DQ Robotics, unit dual quaternions can be plotted on screen the same way as unit quaternions.

def setup_plot():
    plt.xlabel('x [m]')
    plt.xlim([-0.3, 0.3])
    plt.ylabel('y [m]')
    plt.ylim([-0.3, 0.3])
    plt.gca().set_zlabel('z [m]')
    plt.gca().set_zlim([-0.3, 0.3])
    plt.tight_layout()

plt.figure()
ax = plt.axes(projection='3d')
plot(x1)
setup_plot()
plt.show()
<Figure size 640x480 with 1 Axes>

Sequential pose transforrmations

Sequential pose transformations are obtained by post-multiplication of unit dual quaternions. For example, the transformation between the neutral reference frame by x1{\underline{\mathit{\mathbf{x}}} }_1 followed by x2{\underline{\mathit{\mathbf{x}}} }_2 is

x3=x1x2{\underline{\mathit{\mathbf{x}}} }_3 ={\underline{\mathit{\mathbf{x}}} }_1 {\underline{\mathit{\mathbf{x}}} }_2
print('Sequential pose transformations')
Sequential pose transformations
# Define the initial pose
t1 = 0
r1 = 1
x1 = r1 + 0.5*E_*t1*r1

# Define the second pose transformation
t2 = 0.1*j_
r2 = cos(pi/4) + i_*sin(pi/4)
x2 = r2 + 0.5*E_*t2*r2

# Define the third pose transformation
t3 = -0.1*k_ + 0.2*i_
r3 = cos(pi/32) + k_*sin(pi/32)
x3 = r3 + 0.5*E_*t3*r3

# Plot using subplot
plt.figure(figsize=(15,5))
ax1 = plt.subplot(1,3,1, projection='3d')
plot(x1)
setup_plot()
ax1.title.set_text('$x_1$')
ax2 = plt.subplot(1,3,2, projection='3d')
plot(x1 * x2)
setup_plot()
ax2.title.set_text('$x_1 x_2$')
ax3 = plt.subplot(1,3,3, projection='3d')
plot(x1 * x2 * x3)
setup_plot()
ax3.title.set_text('$x_1 x_2 x_3$')

plt.show()
<Figure size 1500x500 with 3 Axes>

Reverse pose transformation

The reverse pose transformation can be obtained using the conjugate operation, because unit dual quaternions have unit norm. Hence,

x(x)=1\underline{\mathit{\mathbf{x}}} {\left(\underline{\mathit{\mathbf{x}}} \right)}^* =1

for any unit dual quaternion.

For example,

x3
0.995185 + 0.098017k + E*(0.004901 + 0.099518i - 0.009802j - 0.049759k)
x3 * conj(x3)
1

Rotation

The operator rotation(.)\textrm{rotation}\left(\ldotp \right) can be used to retrieve the rotation represented by a unit dual quaternion.

rotation(x3)
0.995185 + 0.098017k

Translation

The operator translation(.)\textrm{translation}\left(\ldotp \right) can be used to retrive the translation represented by a unit dual quaternion.

translation(x3)
0.2i - 0.1k

Homework

## Homework example
# Essential imports
from dqrobotics import *
from math import pi, sin, cos 

## Solutions
# Question 1

# Question 2

# Question 3

Following the template above to create a script called [dual_quaternion_basics_homework_part1.py], do the following.

  1. Store, in x1{\underline{\mathit{\mathbf{x}}} }_1 , the pose transformation of a translation of 1 meter in the x-axis, followed by a rotation of π8\frac{\pi }{8} rad about the z-axis.
  2. Store, in x2{\underline{\mathit{\mathbf{x}}} }_2 , the pose transformation of a translation of -1 meter in the z-axis, followed by a rotation of π2-\frac{\pi }{2} rad about the x-axis.
  3. Calculate the result of the sequential rotation of the neutral reference-frame by x0=1{\underline{\mathit{\mathbf{x}}} }_0 =1 , followed by x1{\underline{\mathit{\mathbf{x}}} }_1 , followed by x2{\underline{\mathit{\mathbf{x}}} }_2 , and store it in x3{\underline{\mathit{\mathbf{x}}} }_3 . Plot x3{\underline{\mathit{\mathbf{x}}} }_3 .
  4. Obtain the rotation of x3{\underline{\mathit{\mathbf{x}}} }_3 without using rotation(.)\textrm{rotation}\left(\ldotp \right)
  5. Obtain the translation of x3{\underline{\mathit{\mathbf{x}}} }_3 without using translation(.)\textrm{translation}\left(\ldotp \right)
  6. Obtain the pose transformation of a rotation of π8\frac{\pi }{8} rad about the z-axis followed by a translation of 1 meter in the x-axis, and store it in x4{\underline{\mathit{\mathbf{x}}} }_4 . Is it the same as x1{\underline{\mathit{\mathbf{x}}} }_1 ? If it is different, why is it different?