quaternions — scalar-last quaternion algebra
Purpose
Stateless scalar-last
[x, y, z, w]quaternion ops for the attitude path: Hamilton product,
attitude error, exponential-map integration, R↔q conversion, and trajectory sign-continuity.
No config, no state — pure math consumed by the controllers and guidance.
Role in the system
- Stateless support library (
utils/), consumed by the GNC stack — not a pipeline module. quat_eps_eta(R)is the load-bearing export: breve_controller and base_controller call it to
split the base/EE attitude-error rotation into the vector partεand scalarηfor the pointing loop.R_to_quat/quat_to_Rback the frame↔quaternion conversions used across geometry and the
guidance frame builders (base_guidance SLERP, continuity enforcement before differentiation).- Cross-validated against
scipy.spatial.transform.Rotationin the__main__smoke (mul, error,
integrate, R↔q all match to 1e-6).
Inputs / Outputs
- In: scalar-last quaternions
[x,y,z,w], 3×3 rotation matrices, body ratesω,(M,4)quaternion sequences. - Out: quaternions, rotation matrices, rotation vectors, and the
(ε, η)attitude-error split.
Key functions
quat_mul— Hamilton productq ⊗ r—utils/quaternions.py:9quat_error— attitude-error quaternionq_des ⊗ q⁻¹(shorter-arc sign) —utils/quaternions.py:39quat_integrate— advanceqbyω·dtvia the exponential map; renormalizes —utils/quaternions.py:94R_to_quat/quat_to_R— 4-branch Shepperd matrix↔quaternion conversion —utils/quaternions.py:59/:45quat_from_axes— quaternion from three orthonormal body-axis columns (trace formula) —utils/quaternions.py:28enforce_quat_continuity_xyzw— kill double-cover sign flips in a(M,4)sequence —utils/quaternions.py:117quat_eps_eta— shorter-arc(ε col, η scalar)split, the controllers’ entry point —utils/quaternions.py:126
Footguns
Everything here is scalar-LAST
[x, y, z, w]The whole file assumes the
scipy …Rotation.as_quat()default layout. Mixing in a scalar-first
[w, x, y, z]quaternion silently produces wrong results — no shape error to catch it. (utils/INSIGHTS.md[convention])
quat_errorsign encodes the shorter arc
quat_errorreturnsq_err = q_des ⊗ q⁻¹(conjugate inverse for unit quats). The sign picks the
shorter rotation; anything integrating this error should checkq_err[3] ≥ 0to stay on the short
path — this is exactly whatquat_eps_etadoes (if q[3] < 0: q = -q). (utils/INSIGHTS.md[footgun])
Enforce continuity before interpolating or differentiating a quaternion trajectory
enforce_quat_continuity_xyzwflipsq[i]wheneverq[i-1]·q[i] < 0. Run it before any
trajectory SLERP or finite-difference, or the double-cover discontinuity injects a spurious
180°-class jump. (utils/INSIGHTS.md[convention])
quat_from_axesis undefined near a 180° rotationIt uses the Shepperd trace formula directly (not scipy) and divides by
4·qw, which blows up as
trace → −1(≈180°). For robustness prefer the scipy-backedR_to_quatbelow it, which branches on
all four cases. (utils/INSIGHTS.md[footgun])
Pseudocode (exponential-map integration)
quat_integrate(q, ω, dt):
θ = ‖ω‖ · dt
if ‖ω‖ < eps: dq ≈ [0.5·ω·dt, 1] # small-angle fallback
else: dq = [axis·sin(θ/2), cos(θ/2)] # axis = ω/‖ω‖
return normalize(q ⊗ dq) # left-multiply, renormalize → no drift
Related
geometry · breve_controller · base_controller · base_guidance · robot · terminology