%%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://
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 is yet a different way to extend . Any dual number can be written in the form
where . The algebra of dual numbers is based on the following property of the nilpotent dual unit
For instance, the dual number
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
For instance, for the dual numbers
d1 = 10 - 239*E_
print(f"d1 = {d1}")
d1 = - 10 + E*(239)
d2 = 55 + 32*E_
print(f"d2 = {d2}")
d2 = 55 + E*(32)
d1 + d2
45 + E*(271)
d1 - d2
- 65 + E*(207)
d1 * d2
- 550 + E*(12825)
Primary part¶
The operator 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(d1)
- 10
Dual part¶
The operator 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(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 and can always be written in the form
where .
Note that , , , . 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
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)
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)
h1 + h2
- 20 + 6i + 7j + 9k + E*(9 + 15i + 14j + 16k)
h1 - h2
30 + 6i + 7j + 7k + E*(9 + 15i)
h1 * h2
- 133 - 143i - 181j - 195k + E*( - 346 - 368i - 203j - 109k)
Notice that the multiplication between dual quaternions is, in general, NOT COMMUTATIVE.
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.
conj(h1)
5 - 6i - 7j - 8k + E*(9 - 15i - 7j - 8k)
Norm
norm(h1)
13.190906 + E*(18.800831)
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 .
Imaginary part¶
Im(h1)
6i + 7j + 8k + E*(15i + 7j + 8k)
P(h1)
5 + 6i + 7j + 8k
D(h1)
9 + 15i + 7j + 8k
vec6(h1)
array([ 6., 7., 8., 15., 7., 8.])
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(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 , 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
where is the unit-norm quaternion representing the rotation; and is the quaternion representing the translation.
Remember this nomenclature:
- is a translation.
- is a rotation.
- is a pose transformation (combined translation and rotation).
Unit-norm dual-quaternions, as the name says, have unit norm
For instance, to represent the translation of 1 meter about the y-axis,
t1 = j_
print(f"t1 = {t1}")
t1 = 1j
followed by a rotation of rad about the x-axis,
r1 = cos(pi/4) + i_*sin(pi/4)
print(f"r1 = {r1}")
r1 = 0.707107 + 0.707107i
the pose transformation will be
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 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.
and
represent the same pose transformation.
No pose transformation¶
The dual quaternion that represents that there is no pose transformation is
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()

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 followed by is
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()

Reverse pose transformation¶
The reverse pose transformation can be obtained using the conjugate operation, because unit dual quaternions have unit norm. Hence,
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(x3)
0.995185 + 0.098017k
Translation¶
The operator 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.
- Store, in , the pose transformation of a translation of 1 meter in the x-axis, followed by a rotation of rad about the z-axis.
- Store, in , the pose transformation of a translation of -1 meter in the z-axis, followed by a rotation of rad about the x-axis.
- Calculate the result of the sequential rotation of the neutral reference-frame by , followed by , followed by , and store it in . Plot .
- Obtain the rotation of without using
- Obtain the translation of without using
- Obtain the pose transformation of a rotation of rad about the z-axis followed by a translation of 1 meter in the x-axis, and store it in . Is it the same as ? If it is different, why is it different?