Skip to content

Euler Angles

Three successive rotations about specified axes. The EulerAngle class stores three angles (phi, theta, psi) and a rotation order specifying which axes are used and in what sequence.

Rotation Orders

The 12 standard rotation sequences are defined by the EulerAngleOrder enum. Values are contiguous integers 0--11, suitable for use as branch indices in jax.lax.switch for JIT-compatible dispatch.

Symmetric Sequences

In symmetric (proper Euler) sequences, the first and third axes are the same:

Order Axes Index
XYX X-Y-X 0
XZX X-Z-X 2
YXY Y-X-Y 4
YZY Y-Z-Y 7
ZXZ Z-X-Z 9
ZYZ Z-Y-Z 11

Tait-Bryan Sequences

In Tait-Bryan sequences, all three axes are distinct:

Order Axes Index Also known as
XYZ X-Y-Z 1 Roll-Pitch-Yaw
XZY X-Z-Y 3
YXZ Y-X-Z 5
YZX Y-Z-X 6
ZXY Z-X-Y 8
ZYX Z-Y-X 10 Yaw-Pitch-Roll

Construction

from astrojax import EulerAngle, EulerAngleOrder

# XYZ Tait-Bryan angles (roll-pitch-yaw)
e = EulerAngle(EulerAngleOrder.XYZ, 30.0, 45.0, 60.0, use_degrees=True)

# Angles in radians (default)
import math
e = EulerAngle(EulerAngleOrder.ZYX, math.pi/6, math.pi/4, math.pi/3)

From a Vector

import jax.numpy as jnp

vec = jnp.array([0.5236, 0.7854, 1.0472])  # radians
e = EulerAngle.from_vector(vec, EulerAngleOrder.XYZ)

Properties

Angles are stored in radians internally:

e = EulerAngle(EulerAngleOrder.XYZ, 30.0, 45.0, 60.0, use_degrees=True)
print(e.order)  # EulerAngleOrder.XYZ
print(e.phi)    # ~0.5236 rad
print(e.theta)  # ~0.7854 rad
print(e.psi)    # ~1.0472 rad

Order Conversion

Convert between different rotation orders by going through the quaternion representation:

e_xyz = EulerAngle(EulerAngleOrder.XYZ, 30.0, 45.0, 60.0, use_degrees=True)
e_zyx = e_xyz.to_euler_angle(EulerAngleOrder.ZYX)

Conversions

from astrojax import Quaternion, RotationMatrix, EulerAxis

e = EulerAngle(EulerAngleOrder.XYZ, 30.0, 45.0, 60.0, use_degrees=True)

# To other representations
q = e.to_quaternion()
r = e.to_rotation_matrix()
ea = e.to_euler_axis()

# From other representations
e2 = EulerAngle.from_quaternion(q, EulerAngleOrder.XYZ)
e3 = EulerAngle.from_rotation_matrix(r, EulerAngleOrder.XYZ)
e4 = EulerAngle.from_euler_axis(ea, EulerAngleOrder.XYZ)

Gimbal lock

Euler angles suffer from gimbal lock when the second rotation angle theta approaches \(\pm\pi/2\) (Tait-Bryan) or \(0\) / \(\pi\) (symmetric). Near these singularities, phi and psi become ambiguous. Consider using quaternions for singularity-free representation.