Chapter 3: Representations of Orientation
Lesson 3: Unit Quaternions and Interpolation
This lesson introduces unit quaternions as a Lie-group representation of 3D orientation, explains their algebra and relationship to axis–angle rotations, and develops geodesic interpolation on the 3-sphere (S3) such as spherical linear interpolation (Slerp). We emphasize how these tools are used in robotic motion generation to obtain smooth, singularity-free orientation trajectories.
1. Conceptual Overview and Motivation
In previous lessons, we introduced rotation matrices \( \mathbf{R} \in \mathrm{SO}(3) \), Euler angles, and axis–angle representation. Each has advantages and limitations: Euler angles are compact but suffer from singularities (gimbal lock), while rotation matrices are globally well-defined but redundant (nine parameters with orthonormality constraints).
Unit quaternions provide a smooth, globally nonsingular representation of orientation with four parameters and a single unit-length constraint. As a Lie group isomorphic to the unit 3-sphere \( \mathcal{S}^3 \subset \mathbb{R}^4 \), they are particularly convenient for:
- Interpolating orientations along shortest geodesics on \( \mathcal{S}^3 \),
- Composing rotations via quaternion multiplication,
- Avoiding Euler-angle singularities in control and trajectory generation.
Conceptually, we may view unit quaternions as “rotation points” on \( \mathcal{S}^3 \), with geodesic paths corresponding to constant-speed rotational motion in 3D space. The following diagram summarizes the role of quaternions in a typical robot orientation pipeline.
flowchart TD
A["Orientation keyframes (R0, R1, ...)"] --> B["Convert each to unit quaternions q0, q1, ..."]
B --> C["Choose interpolation rule on S3 (Slerp / Nlerp)"]
C --> D["Evaluate interpolated quaternion q(t) for desired time"]
D --> E["Convert q(t) to rotation matrix or Euler angles"]
E --> F["Use as end-effector / joint orientation command"]
In this lesson we focus on mathematical structure: quaternion algebra, their interpretation as rotations, and how interpolation on \( \mathcal{S}^3 \) leads to algorithms like Slerp. Detailed conversion formulas between representations are deferred to the next lesson.
2. Quaternion Algebra and Unit Sphere
A quaternion is a 4-tuple consisting of a scalar part and a 3D vector part:
\[ q = \begin{bmatrix} q_0 \\ \mathbf{q}_v \end{bmatrix} = \begin{bmatrix} q_0 \\ q_1 \\ q_2 \\ q_3 \end{bmatrix}, \quad q_0 \in \mathbb{R},\; \mathbf{q}_v = (q_1,q_2,q_3)^\top \in \mathbb{R}^3. \]
The set of all quaternions forms a 4D real vector space with addition and scalar multiplication defined component-wise. The key operation is the Hamilton product (or quaternion multiplication). For two quaternions \( p = (p_0,\mathbf{p}_v) \) and \( q = (q_0,\mathbf{q}_v) \), their product \( p \otimes q \) is
\[ p \otimes q = \begin{bmatrix} p_0 q_0 - \mathbf{p}_v^\top \mathbf{q}_v \\ p_0 \mathbf{q}_v + q_0 \mathbf{p}_v + \mathbf{p}_v \times \mathbf{q}_v \end{bmatrix}. \]
The quaternion conjugate and norm are defined by
\[ \bar{q} = \begin{bmatrix} q_0 \\ -\mathbf{q}_v \end{bmatrix}, \qquad \lVert q \rVert^2 = q_0^2 + \lVert \mathbf{q}_v \rVert^2. \]
For nonzero \( q \), the inverse is \( q^{-1} = \bar{q} / \lVert q \rVert^2 \). A unit quaternion satisfies \( \lVert q \rVert = 1 \) and has inverse \( q^{-1} = \bar{q} \). The set of all unit quaternions is a 3D manifold:
\[ \mathcal{S}^3 = \left\{ q \in \mathbb{R}^4 \;\middle|\; \lVert q \rVert = 1 \right\}. \]
Under the Hamilton product, the set of unit quaternions is a Lie group. This group is a double cover of \( \mathrm{SO}(3) \): each rotation matrix corresponds to exactly two antipodal unit quaternions \( q \) and \( -q \).
3. Unit Quaternions from Axis–Angle Rotations
Recall that any proper rotation in 3D can be parameterized by an axis–angle pair \( (\mathbf{u},\theta) \), where \( \mathbf{u} \in \mathbb{R}^3 \) is a unit vector and \( \theta \in \mathbb{R} \) is the rotation angle. The corresponding unit quaternion is
\[ q(\mathbf{u},\theta) = \begin{bmatrix} \cos(\theta / 2) \\ \mathbf{u} \,\sin(\theta / 2) \end{bmatrix} = \begin{bmatrix} \cos(\theta / 2) \\ u_x \sin(\theta / 2) \\ u_y \sin(\theta / 2) \\ u_z \sin(\theta / 2) \end{bmatrix}. \]
Proposition. If \( \lVert \mathbf{u} \rVert = 1 \), then \( q(\mathbf{u},\theta) \) is a unit quaternion.
Proof. We compute
\[ \lVert q(\mathbf{u},\theta) \rVert^2 = \cos^2(\theta/2) + \lVert \mathbf{u} \rVert^2 \sin^2(\theta/2) = \cos^2(\theta/2) + \sin^2(\theta/2) = 1. \]
Thus, axis–angle and unit quaternions are equivalent ways of describing elements of \( \mathrm{SO}(3) \), up to the sign ambiguity \( q \sim -q \).
To rotate a vector \( \mathbf{x} \in \mathbb{R}^3 \), we embed it into a pure quaternion
\[ x^q = \begin{bmatrix} 0 \\ \mathbf{x} \end{bmatrix}, \]
and define the rotated vector via conjugation:
\[ x^{q'} = q \otimes x^q \otimes q^{-1}, \qquad \mathbf{x}' = \operatorname{vec}(x^{q'}), \]
where \( \operatorname{vec}(\cdot) \) extracts the 3D vector part of the quaternion. A classical identity, obtained by expanding the Hamilton products, is
\[ \mathbf{x}' = \mathbf{x} + 2 q_0 (\mathbf{q}_v \times \mathbf{x}) + 2 \mathbf{q}_v \times (\mathbf{q}_v \times \mathbf{x}), \]
which coincides with the action of the corresponding rotation matrix \( \mathbf{R}(q) \) on \( \mathbf{x} \).
Double cover. Let \( q \) be a unit quaternion. Then
\[ (-q) \otimes x^q \otimes (-q)^{-1} = (-q) \otimes x^q \otimes (-\bar{q}) = q \otimes x^q \otimes \bar{q}, \]
so \( q \) and \( -q \) generate the same rotation on \( \mathbf{x} \). Hence each \( \mathbf{R} \in \mathrm{SO}(3) \) corresponds to two antipodal points on \( \mathcal{S}^3 \).
4. Quaternion Distance and Geodesics on \( \mathcal{S}^3 \)
Endowing \( \mathcal{S}^3 \) with the metric inherited from \( \mathbb{R}^4 \), the geodesic (shortest path) between two unit quaternions \( q_0,q_1 \in \mathcal{S}^3 \) lies on the great circle that they span. The angle between them is
\[ \theta = \arccos\left( q_0^\top q_1 \right), \]
where \( q_0^\top q_1 \) is the Euclidean inner product in \( \mathbb{R}^4 \). Because \( q \sim -q \) represent the same rotation, we always choose the representative so that \( q_0^\top q_1 \ge 0 \), ensuring that \( \theta \in [0,\pi] \) corresponds to the shortest arc.
For robotics, this angle is directly related to the physical relative rotation between orientations. In fact, the relative rotation \( q_{\mathrm{rel}} = q_0^{-1} \otimes q_1 \) has an axis–angle representation \( (\mathbf{u},\theta) \), and the physical rotation angle in 3D space is \( \theta \).
5. Spherical Linear Interpolation (Slerp)
Suppose we have two unit quaternions \( q_0,q_1 \in \mathcal{S}^3 \) representing the start and end orientations of a robot end-effector. We want a smooth interpolation \( q(t) \) such that:
- \( q(0) = q_0 \) and \( q(1) = q_1 \),
- \( q(t) \in \mathcal{S}^3 \) for all \( t \in [0,1] \),
- The path is a constant-speed geodesic on \( \mathcal{S}^3 \).
The spherical linear interpolation (Slerp) satisfies these properties. Let \( d = q_0^\top q_1 \) and, if needed, replace \( q_1 \) with \( -q_1 \) so that \( d \ge 0 \). Define \( \theta = \arccos(d) \). Then:
\[ \operatorname{slerp}(q_0,q_1,t) = \frac{\sin((1-t)\theta)}{\sin \theta}\, q_0 + \frac{\sin(t\theta)}{\sin \theta}\, q_1, \quad 0 \le t \le 1. \]
Boundary conditions. For \( t=0 \), the first coefficient becomes 1 and the second 0, so \( q(0)=q_0 \). For \( t=1 \), the roles are reversed and \( q(1)=q_1 \). For intermediate \( t \), the coefficients trace a great-circle arc on \( \mathcal{S}^3 \).
Lie-group view. We can also write Slerp using the exponential and logarithm maps of the unit-quaternion group, analogous to exponential coordinates of \( \mathrm{SO}(3) \):
\[ q_{\mathrm{rel}} = q_0^{-1} \otimes q_1, \qquad q(t) = q_0 \otimes \exp\!\left( t \, \log(q_{\mathrm{rel}}) \right). \]
The logarithm maps \( q_{\mathrm{rel}} \) to a tangent vector at the identity (an element of the Lie algebra), and the scaled exponential maps it back to the group along a geodesic.
Numerically, when \( \theta \) is extremely small, we can approximate Slerp via normalized linear interpolation (Nlerp), discussed in the next section.
flowchart TD
I["Input: unit quaternions q0, q1, parameter t in [0,1]"] --> N["Normalize q0, q1"]
N --> S["Compute dot = q0^T q1; if dot < 0, set q1 = -q1"]
S --> T["theta = arccos(dot)"]
T --> C["If theta is small: \nuse normalized linear interpolation"]
T --> F["Else: compute Slerp coefficients \nand q(t) on S3"]
C --> O["Return interpolated quaternion q(t)"]
F --> O
6. Normalized Linear Interpolation (Nlerp) and Comparison
Slerp is mathematically exact but involves trigonometric functions, which can be relatively expensive in embedded controllers. A simpler alternative is normalized linear interpolation (Nlerp):
\[ \operatorname{nlerp}(q_0,q_1,t) = \frac{(1-t)\,q_0 + t\,q_1}{\lVert (1-t)\,q_0 + t\,q_1 \rVert}, \quad 0 \le t \le 1. \]
Nlerp preserves end points and keeps quaternions on \( \mathcal{S}^3 \) after normalization. However:
- The trajectory is not exactly a geodesic on \( \mathcal{S}^3 \),
- Angular velocity magnitude is not constant along the path.
In many robotic applications (e.g., low-speed manipulator motion), Nlerp can be an acceptable approximation to Slerp. For high-performance tasks (e.g., fast camera motions, dynamic orientation control) Slerp is generally preferred.
7. Python Implementation — Unit Quaternions and Slerp
In Python, we can either implement quaternion operations from scratch
using numpy, or rely on libraries such as
scipy.spatial.transform.Rotation or robotics toolboxes.
Below is a minimal, self-contained implementation suitable for
FK/trajectory labs in robotics.
import numpy as np
def quat_normalize(q):
q = np.asarray(q, dtype=float)
return q / np.linalg.norm(q)
def quat_conjugate(q):
q = np.asarray(q, dtype=float)
return np.array([q[0], -q[1], -q[2], -q[3]])
def quat_multiply(q1, q2):
"""Hamilton product q = q1 ⊗ q2."""
w1, x1, y1, z1 = q1
w2, x2, y2, z2 = q2
w = w1*w2 - x1*x2 - y1*y2 - z1*z2
x = w1*x2 + x1*w2 + y1*z2 - z1*y2
y = w1*y2 - x1*z2 + y1*w2 + z1*x2
z = w1*z2 + x1*y2 - y1*x2 + z1*w2
return np.array([w, x, y, z])
def quat_from_axis_angle(axis, theta):
"""Axis is a 3D vector, theta in radians."""
axis = np.asarray(axis, dtype=float)
axis = axis / np.linalg.norm(axis)
half = 0.5 * theta
s = np.sin(half)
return np.array([np.cos(half), axis[0]*s, axis[1]*s, axis[2]*s])
def quat_rotate(q, v):
"""Rotate 3D vector v by unit quaternion q."""
q = quat_normalize(q)
v = np.asarray(v, dtype=float)
vq = np.concatenate([[0.0], v])
return quat_multiply(quat_multiply(q, vq), quat_conjugate(q))[1:]
def quat_slerp(q0, q1, t):
"""Spherical linear interpolation between q0 and q1."""
q0 = quat_normalize(q0)
q1 = quat_normalize(q1)
dot = float(np.dot(q0, q1))
# Use the shortest arc on S3
if dot < 0.0:
q1 = -q1
dot = -dot
# Clamp to handle numerical drift
dot = np.clip(dot, -1.0, 1.0)
theta = np.arccos(dot)
# If the angle is tiny, fall back to normalized lerp
if theta < 1e-6:
q = (1.0 - t) * q0 + t * q1
return quat_normalize(q)
sin_theta = np.sin(theta)
w0 = np.sin((1.0 - t) * theta) / sin_theta
w1 = np.sin(t * theta) / sin_theta
q = w0 * q0 + w1 * q1
return quat_normalize(q)
# Example: interpolate between two axis-angle orientations
if __name__ == "__main__":
q0 = quat_from_axis_angle([0, 0, 1], 0.0)
q1 = quat_from_axis_angle([0, 0, 1], np.deg2rad(90.0))
t = 0.5
qm = quat_slerp(q0, q1, t)
print("Mid quaternion:", qm)
v = np.array([1.0, 0.0, 0.0])
print("Rotated vector at t=0.5:", quat_rotate(qm, v))
In practice, you can wrap this module inside a higher-level kinematics library to manage orientation interpolation for trajectories in task space.
8. C++ Implementation — Unit Quaternions with Eigen
In C++, the Eigen library provides both a generic
Quaternion class and conversion utilities to and from
rotation matrices. Below is a lightweight example illustrating Slerp and
vector rotation using Eigen::Quaterniond.
#include <iostream>
#include <Eigen/Dense>
using Quaternion = Eigen::Quaterniond;
using Vec3 = Eigen::Vector3d;
Quaternion quatFromAxisAngle(const Vec3 &axis, double theta) {
Vec3 u = axis.normalized();
double half = 0.5 * theta;
double s = std::sin(half);
return Quaternion(std::cos(half), u.x() * s, u.y() * s, u.z() * s);
}
Quaternion quatSlerp(const Quaternion &q0_in,
const Quaternion &q1_in,
double t) {
Quaternion q0 = q0_in.normalized();
Quaternion q1 = q1_in.normalized();
double dot = q0.w()*q1.w() + q0.vec().dot(q1.vec());
// Ensure shortest path
if (dot < 0.0) {
q1.coeffs() = -q1.coeffs();
dot = -dot;
}
dot = std::max(-1.0, std::min(1.0, dot));
double theta = std::acos(dot);
if (theta < 1e-6) {
Quaternion q = Quaternion(
(1.0 - t) * q0.w() + t * q1.w(),
(1.0 - t) * q0.x() + t * q1.x(),
(1.0 - t) * q0.y() + t * q1.y(),
(1.0 - t) * q0.z() + t * q1.z()
);
return q.normalized();
}
double sin_theta = std::sin(theta);
double w0 = std::sin((1.0 - t) * theta) / sin_theta;
double w1 = std::sin(t * theta) / sin_theta;
return Quaternion(
w0 * q0.w() + w1 * q1.w(),
w0 * q0.x() + w1 * q1.x(),
w0 * q0.y() + w1 * q1.y(),
w0 * q0.z() + w1 * q1.z()
).normalized();
}
int main() {
Quaternion q0 = quatFromAxisAngle(Vec3(0, 0, 1), 0.0);
Quaternion q1 = quatFromAxisAngle(Vec3(0, 0, 1), M_PI / 2.0);
double t = 0.5;
Quaternion qm = quatSlerp(q0, q1, t);
Vec3 v(1.0, 0.0, 0.0);
Vec3 v_rot = qm * v; // Eigen applies rotation via q * v * q^{-1}
std::cout << "qm = " << qm.coeffs().transpose() << std::endl;
std::cout << "v_rot = " << v_rot.transpose() << std::endl;
return 0;
}
This pattern is typical in robotics codebases: unit quaternions are used internally while interfaces may expose rotation matrices or Euler angles for compatibility with other subsystems.
9. Java Implementation — Quaternion Class and Slerp
Java has fewer standardized robotics libraries than C++ or MATLAB, but
quaternions are easy to implement. The following example shows a simple
Quaternion class with Slerp. It can be integrated into a
kinematics module for Java-based robot simulators.
public final class Quaternion {
public final double w, x, y, z;
public Quaternion(double w, double x, double y, double z) {
this.w = w; this.x = x; this.y = y; this.z = z;
}
public static Quaternion fromAxisAngle(double[] axis, double theta) {
double norm = Math.sqrt(axis[0]*axis[0] +
axis[1]*axis[1] +
axis[2]*axis[2]);
double ux = axis[0] / norm;
double uy = axis[1] / norm;
double uz = axis[2] / norm;
double half = 0.5 * theta;
double s = Math.sin(half);
return new Quaternion(Math.cos(half), ux*s, uy*s, uz*s);
}
public Quaternion normalized() {
double n = Math.sqrt(w*w + x*x + y*y + z*z);
return new Quaternion(w / n, x / n, y / n, z / n);
}
public Quaternion conjugate() {
return new Quaternion(w, -x, -y, -z);
}
public Quaternion multiply(Quaternion q) {
double nw = w*q.w - x*q.x - y*q.y - z*q.z;
double nx = w*q.x + x*q.w + y*q.z - z*q.y;
double ny = w*q.y - x*q.z + y*q.w + z*q.x;
double nz = w*q.z + x*q.y - y*q.x + z*q.w;
return new Quaternion(nw, nx, ny, nz);
}
public double dot(Quaternion q) {
return w*q.w + x*q.x + y*q.y + z*q.z;
}
public static Quaternion slerp(Quaternion q0_in,
Quaternion q1_in,
double t) {
Quaternion q0 = q0_in.normalized();
Quaternion q1 = q1_in.normalized();
double dot = q0.dot(q1);
// Ensure shortest arc
if (dot < 0.0) {
q1 = new Quaternion(-q1.w, -q1.x, -q1.y, -q1.z);
dot = -dot;
}
dot = Math.max(-1.0, Math.min(1.0, dot));
double theta = Math.acos(dot);
if (theta < 1e-6) {
// Nlerp fallback
double w = (1.0 - t)*q0.w + t*q1.w;
double x = (1.0 - t)*q0.x + t*q1.x;
double y = (1.0 - t)*q0.y + t*q1.y;
double z = (1.0 - t)*q0.z + t*q1.z;
return new Quaternion(w, x, y, z).normalized();
}
double sinTheta = Math.sin(theta);
double w0 = Math.sin((1.0 - t)*theta) / sinTheta;
double w1 = Math.sin(t*theta) / sinTheta;
Quaternion q = new Quaternion(
w0*q0.w + w1*q1.w,
w0*q0.x + w1*q1.x,
w0*q0.y + w1*q1.y,
w0*q0.z + w1*q1.z
);
return q.normalized();
}
public double[] rotateVector(double[] v) {
// Convert v into a pure quaternion and apply q v q^{-1}
Quaternion vq = new Quaternion(0.0, v[0], v[1], v[2]);
Quaternion q = this.normalized();
Quaternion res = q.multiply(vq).multiply(q.conjugate());
return new double[]{res.x, res.y, res.z};
}
}
This implementation can be extended with methods for converting to rotation matrices or Euler angles, which are covered in the next lesson.
10. MATLAB / Simulink Implementation
MATLAB’s Robotics System Toolbox includes quaternion primitives (e.g.,
quaternion, rotmat, eul). Here we
first implement basic Slerp manually, then comment on Simulink
integration.
function q = quat_from_axis_angle(axis, theta)
axis = axis(:) / norm(axis);
half = 0.5 * theta;
s = sin(half);
q = [cos(half); axis * s]; % [w; x; y; z]
end
function q = quat_normalize(q)
q = q(:);
q = q / norm(q);
end
function q = quat_multiply(q1, q2)
w1 = q1(1); x1 = q1(2); y1 = q1(3); z1 = q1(4);
w2 = q2(1); x2 = q2(2); y2 = q2(3); z2 = q2(4);
w = w1*w2 - x1*x2 - y1*y2 - z1*z2;
x = w1*x2 + x1*w2 + y1*z2 - z1*y2;
y = w1*y2 - x1*z2 + y1*w2 + z1*x2;
z = w1*z2 + x1*y2 - y1*x2 + z1*w2;
q = [w; x; y; z];
end
function v_rot = quat_rotate(q, v)
q = quat_normalize(q);
vq = [0; v(:)];
q_conj = [q(1); -q(2:4)];
res = quat_multiply(quat_multiply(q, vq), q_conj);
v_rot = res(2:4);
end
function q = quat_slerp(q0, q1, t)
q0 = quat_normalize(q0);
q1 = quat_normalize(q1);
dot = dot(q0, q1);
if dot < 0
q1 = -q1;
dot = -dot;
end
dot = max(-1.0, min(1.0, dot));
theta = acos(dot);
if theta < 1e-6
q = quat_normalize((1 - t)*q0 + t*q1);
return;
end
sin_theta = sin(theta);
w0 = sin((1 - t)*theta) / sin_theta;
w1 = sin(t*theta) / sin_theta;
q = quat_normalize(w0*q0 + w1*q1);
end
% Example script
q0 = quat_from_axis_angle([0;0;1], 0);
q1 = quat_from_axis_angle([0;0;1], pi/2);
t = 0.5;
qm = quat_slerp(q0, q1, t)
v = [1; 0; 0];
v_rot = quat_rotate(qm, v)
In Simulink, you can wrap quat_slerp inside a MATLAB
Function block and connect it to blocks representing keyframe
quaternions and interpolation parameters. Alternatively, the Robotics
System Toolbox provides ready-made blocks for converting between
quaternions and rotation matrices, which can be composed with custom
Slerp logic.
11. Wolfram Mathematica Implementation
Mathematica has built-in support for quaternions via the kernel package
Quaternions` in some versions, but a direct symbolic
implementation is straightforward and useful when deriving analytic
expressions for robotic kinematics.
quatNormalize[{w_, x_, y_, z_}] := Module[{n},
n = Sqrt[w^2 + x^2 + y^2 + z^2];
{w, x, y, z} / n
];
quatFromAxisAngle[{ux_, uy_, uz_}, theta_] := Module[
{u, half, s},
u = {ux, uy, uz}/Sqrt[ux^2 + uy^2 + uz^2];
half = theta/2;
s = Sin[half];
{Cos[half], u[[1]] s, u[[2]] s, u[[3]] s}
];
quatMultiply[{w1_, x1_, y1_, z1_}, {w2_, x2_, y2_, z2_}] :=
Module[{w, x, y, z},
w = w1 w2 - x1 x2 - y1 y2 - z1 z2;
x = w1 x2 + x1 w2 + y1 z2 - z1 y2;
y = w1 y2 - x1 z2 + y1 w2 + z1 x2;
z = w1 z2 + x1 y2 - y1 x2 + z1 w2;
{w, x, y, z}
];
quatConjugate[{w_, x_, y_, z_}] := {w, -x, -y, -z};
quatRotate[q_, {vx_, vy_, vz_}] := Module[
{qn, vq, res},
qn = quatNormalize[q];
vq = {0, vx, vy, vz};
res = quatMultiply[quatMultiply[qn, vq], quatConjugate[qn]];
Rest[res]
];
quatSlerp[q0in_, q1in_, t_] := Module[
{q0, q1, dot, theta, sinTheta, w0, w1, q},
q0 = quatNormalize[q0in];
q1 = quatNormalize[q1in];
dot = q0.q1;
If[dot < 0,
q1 = -q1;
dot = -dot;
];
dot = Max[-1.0, Min[1.0, dot]];
theta = ArcCos[dot];
If[theta < 10^-6,
q = (1 - t) q0 + t q1;
Return[quatNormalize[q]];
];
sinTheta = Sin[theta];
w0 = Sin[(1 - t) theta]/sinTheta;
w1 = Sin[t theta]/sinTheta;
q = w0 q0 + w1 q1;
quatNormalize[q]
];
(* Example usage *)
q0 = quatFromAxisAngle[{0, 0, 1}, 0];
q1 = quatFromAxisAngle[{0, 0, 1}, Pi/2];
qm = quatSlerp[q0, q1, 1/2];
vRot = quatRotate[qm, {1, 0, 0}]
Mathematica’s symbolic capabilities make it ideal for verifying properties of quaternion-based interpolation, such as constant angular velocity and equivalence with matrix exponential trajectories.
12. Problems and Solutions
Problem 1 (Composition on a Common Axis). Let \( \mathbf{u} \) be a unit axis. Consider two rotations about the same axis with angles \( \theta_1 \) and \( \theta_2 \). Show that \( q(\mathbf{u},\theta_1) \otimes q(\mathbf{u},\theta_2) = q(\mathbf{u},\theta_1+\theta_2) \).
Solution. Write
\[ q(\mathbf{u},\theta_i) = \begin{bmatrix} \cos(\theta_i/2) \\ \mathbf{u} \sin(\theta_i/2) \end{bmatrix} = \begin{bmatrix} c_i \\ \mathbf{u} s_i \end{bmatrix}, \quad i=1,2. \]
Using the Hamilton product with \( \mathbf{p}_v = \mathbf{q}_v = \mathbf{u} s_i \) and the fact that \( \mathbf{u} \times \mathbf{u} = \mathbf{0} \), we obtain
\[ q(\mathbf{u},\theta_1) \otimes q(\mathbf{u},\theta_2) = \begin{bmatrix} c_1 c_2 - s_1 s_2 \\ (c_1 s_2 + c_2 s_1)\,\mathbf{u} \end{bmatrix}. \]
By trigonometric identities, \( \cos(\alpha+\beta) = \cos\alpha\cos\beta - \sin\alpha\sin\beta \), \( \sin(\alpha+\beta) = \sin\alpha\cos\beta + \cos\alpha\sin\beta \), so with \( \alpha = \theta_1/2 \), \( \beta = \theta_2/2 \) we get
\[ q(\mathbf{u},\theta_1) \otimes q(\mathbf{u},\theta_2) = \begin{bmatrix} \cos\big((\theta_1+\theta_2)/2\big) \\ \mathbf{u}\,\sin\big((\theta_1+\theta_2)/2\big) \end{bmatrix} = q(\mathbf{u},\theta_1+\theta_2). \]
Problem 2 (Geodesic Interpretation of Slerp). Assume \( q_0 = [1,0,0,0]^\top \) (identity rotation) and \( q_1 = q(\mathbf{u},\theta) \). Show that \( \operatorname{slerp}(q_0,q_1,t) = q(\mathbf{u},t\theta) \).
Solution. In this special case, \( q_0^\top q_1 = \cos(\theta/2) \), so \( \theta = \arccos(q_0^\top q_1) \) is the same angle used in the axis–angle representation. Slerp becomes
\[ q(t) = \frac{\sin((1-t)\theta)}{\sin\theta} q_0 + \frac{\sin(t\theta)}{\sin\theta} q_1 = \begin{bmatrix} \dfrac{\sin((1-t)\theta)}{\sin\theta} \\ \dfrac{\sin(t\theta)}{\sin\theta}\,\mathbf{u}\sin(\theta/2) \end{bmatrix}. \]
Using \( \sin((1-t)\theta) = \sin\theta \cos(t\theta) - \cos\theta \sin(t\theta) \) and trigonometric identities, one can simplify the scalar part to \( \cos(t\theta/2) \) and the vector part to \( \mathbf{u}\sin(t\theta/2) \), yielding \( q(t) = q(\mathbf{u},t\theta) \). Thus Slerp corresponds to uniform rotation about \( \mathbf{u} \).
Problem 3 (Double Cover of SO(3)). Show explicitly that \( q \) and \( -q \) induce the same rotation on any \( \mathbf{x} \in \mathbb{R}^3 \).
Solution. Let \( x^q = [0,\mathbf{x}]^\top \). Then,
\[ (-q) \otimes x^q \otimes (-q)^{-1} = (-q) \otimes x^q \otimes (-\bar{q}) = q \otimes x^q \otimes \bar{q}, \]
because quaternion multiplication is bilinear and \( (-1) \otimes q = -q \). Therefore the 3D vector \( \mathbf{x}' \) obtained from the vector part of either expression is identical. Hence \( q \) and \( -q \) act identically on vectors, proving the double-cover property.
Problem 4 (Nlerp vs. Slerp Normalization Error). Let \( q_0,q_1 \in \mathcal{S}^3 \) be unit quaternions with small geodesic distance. Show that the unnormalized linear interpolation \( \tilde{q}(t) = (1-t) q_0 + t q_1 \) deviates from unit length only by second-order terms in the angle between \( q_0 \) and \( q_1 \).
Solution sketch. Let \( q_1 = q_0 + \delta \) with \( \lVert \delta \rVert \) small. Then \( \tilde{q}(t) = q_0 + t\delta \) and
\[ \lVert \tilde{q}(t) \rVert^2 = \lVert q_0 \rVert^2 + 2 t q_0^\top \delta + t^2 \lVert \delta \rVert^2. \]
Since \( \lVert q_0 \rVert = 1 \) and \( q_0^\top \delta = 0 \) to first order (because \( q_1 \) lies on \( \mathcal{S}^3 \)), the first-order term vanishes and the deviation from unit length is \( \mathcal{O}(\lVert \delta \rVert^2) \). Thus, for small angular separations, Nlerp stays close to the unit sphere.
Problem 5 (Quaternion Rotation Formula). Starting from \( \mathbf{x}' = \mathbf{x} + 2 q_0 (\mathbf{q}_v \times \mathbf{x}) + 2 \mathbf{q}_v \times (\mathbf{q}_v \times \mathbf{x}) \), show that this expression coincides with the Rodrigues rotation formula derived in the axis–angle lesson when \( q = q(\mathbf{u},\theta) \).
Solution sketch. Substitute \( q_0 = \cos(\theta/2) \), \( \mathbf{q}_v = \mathbf{u}\sin(\theta/2) \) and use vector identities for double cross products. After simplification, the expression matches
\[ \mathbf{x}' = \mathbf{x} \cos\theta + (\mathbf{u} \times \mathbf{x}) \sin\theta + \mathbf{u}(\mathbf{u}^\top \mathbf{x})(1-\cos\theta), \]
which is exactly the Rodrigues formula. This confirms that quaternion conjugation reproduces the same rotation as the corresponding rotation matrix.
13. Summary
In this lesson we introduced unit quaternions as a compact, nonsingular representation of 3D orientation. We developed the Hamilton product, conjugation, and norm, and showed how axis–angle rotations map to unit quaternions on \( \mathcal{S}^3 \). We then characterized quaternion distance and geodesics and constructed spherical linear interpolation (Slerp), which yields constant-speed rotational motion along shortest arcs. We compared Slerp with normalized linear interpolation and implemented quaternion operations and interpolation in Python, C++, Java, MATLAB/Simulink, and Wolfram Mathematica.
In the next lesson, we will derive explicit conversion formulas between quaternions, rotation matrices, Euler angles, and axis–angle, and discuss numerical issues when switching representations inside a robot kinematics pipeline.
14. References
- Hamilton, W. R. (1844). On quaternions; or on a new system of imaginaries in algebra. Philosophical Magazine, 25, 489–495.
- Shoemake, K. (1985). Animating rotation with quaternion curves. ACM SIGGRAPH Computer Graphics, 19(3), 245–254.
- Hanson, A. J. (2006). Visualizing quaternions. IEEE Transactions on Visualization and Computer Graphics, 12(4), 1–15.
- Altmann, S. L. (1986). Rotations, quaternions, and double groups. Oxford Mathematical Monographs.
- Diebel, J. (2006). Representing attitude: Euler angles, unit quaternions, and rotation vectors. Technical Report, Stanford University.
- Park, F. C. (1995). Distance metrics on the rigid-body motions with applications to mechanism design. Journal of Mechanical Design, 117(1), 48–54.
- Kuffner, J. J. (2004). Effective sampling and distance metrics for 3D rigid body path planning. Robotics: Science and Systems Workshop.
- Bar-Itzhack, I. Y. (2000). New method for extracting the quaternion from a rotation matrix. Journal of Guidance, Control, and Dynamics, 23(6), 1085–1087.
- Kuipers, J. B. (1999). Quaternions and Rotation Sequences. Princeton University Press.