Skip to content

Quaternions

Quaternions are an efficient way of representing 3-dimensional rotations. The satkit package includes its own implementation of quaternions. They are the default output class when computing rotations between coordinate frames.

Quaternions are operationally equivalent to 3x3 rotation matrices, sometimes called direction-cosine matrices or DCMs. They have the advantage of being more computationally efficient, and unlike DCMs can be easily renormalized such that they do not lose their unitary nature as multiplies are compounded.

For an excellent overview of quaternions, see: https://en.wikipedia.org/wiki/Quaternions_and_spatial_rotation

The quaternion class in this Python package is a thin Python wrapper around the unit quaternion module provided by the Rust nalgebra package.

Quaternion Rotation

The satkit package represents a quaternion rotation by a left-sided multiply of a quaternion by a vector, similar to a left-sided multiply of a DCM by a vector. Compounded rotations are represented by multiplications of quaternions. The right-most quaternion represents the first applied rotation, followed by rotations represented by the quaternion on the immediate left, as with the DCM.

Let us define rotation matrices \(R_x\), \(R_y\), and \(R_z\) that represent right-handed rotations of a vector about the \(\hat{x}\), \(\hat{y}\), and \(\hat{z}\) unit vectors, respectively:

\[ R_x(\theta)~=~\left [ \begin{array}{ccc} 1 & 0 & 0 \\ 0 & \cos(\theta) & -\sin(\theta) \\ 0 & \sin(\theta) & \cos(\theta) \end{array} \right ] \]
\[ R_y(\theta)~=~\left [ \begin{array}{ccc} \cos(\theta) & 0 & \sin(\theta) \\ 0 & 1 & 0 \\ -\sin(\theta) & 0 & \cos(\theta) \end{array} \right ] \]
\[ R_z(\theta)~=~\left [ \begin{array}{ccc} \cos(\theta) & -\sin(\theta) & 0 \\ \sin(\theta) & \cos(\theta) & 0 \\ 0 & 0 & 1 \end{array} \right ] \]

These equivalent rotations are defined in Python as satkit.quaternion.rotx, satkit.quaternion.roty, and satkit.quaternion.rotz functions, respectively. The functions take as an input the angle of rotation in radians.

The Python code below computes the rotation of the \(\hat{x}\) vector by \(\pi/2\) about the \(\hat{z}\) axis using both the traditional rotation matrices and then with the satkit quaternion. In both cases, since this is a right-handed rotation of a vector, the resulting vector is \(\hat{y}\).

import satkit as sk
import math as m
import numpy as np

# Create the xhat vector
xhat = np.array([1, 0, 0], np.float64)

# Rotation matrix that rotates about zhat axis by input angle in radians
rotz = lambda x:np.array([[np.cos(x), -np.sin(x), 0], [np.sin(x), np.cos(x), 0],[0, 0, 1]])

# Traditional rotation using matrix multiply
# Matrix multiply should produce yhat: [0, 1, 0]
yhat = rotz(m.pi/2) @ xhat

# Equivalent rotation using quaternions
yhat = sk.quaternion.rotz(m.pi/2) * xhat

# Extract equivalent rotation from quaternion
# and do it via matrix multiplication
yhat = sk.quaternion.rotz(m.pi/2).to_rotation_matrix() @ xhat

Compounding Rotations

Quaternions can be multiplied by one another, which is functionally equivalent to multiplying rotation matrices, and follows the same order of operations.

This is shown in the example code below:

import satkit as sk
import math as m
import numpy as np

# Create the xhat vector
xhat = np.array([1, 0, 0], np.float64)

# Rotation matrix that rotates about xhat axis by input angle in radians
rotx = lambda x: np.array([[1, 0, 0],[0, np.cos(x), -np.sin(x)],[0,np.sin(x),np.cos(x)]])
# Rotation matrix that rotates about zhat axis by input angle in radians
rotz = lambda x: np.array([[np.cos(x), -np.sin(x), 0], [ np.sin(x), np.cos(x), 0],[0, 0, 1]])

# Rotating xhat about zhat axis by pi/2 then xhat axis by pi/2 produces zhat:
r1 = rotx(m.pi/2)
r2 = rotz(m.pi/2)
zhat_r = r1 @ r2 @ xhat

# Same thing with quaternions
q1 = sk.quaternion.rotx(m.pi/2)
q2 = sk.quaternion.rotz(m.pi/2)
zhat_q = q1 * q2 * xhat