Lesson 0.2.2 — Rotation Representations: Euler Angles, Axis-Angle, Quaternions, and Exponential Coordinates
Week 3 · Prerequisites: 0.2.1, 0.1.1–0.1.4.
Prior results used. This lesson uses 0.2.1’s results — SO(3), the 3 degrees of freedom, the RᵀR = I / det R = +1 constraints, the elementary rotations, and the SO(3)-invariant distance θ = arccos((tr(R₁ᵀR₂) − 1)/2) — and 0.1.3’s nn.Linear / parameter-registration model for the learning-parameterization discussion. Each is restated at the point of use.
Hands forward. This lesson hands forward one capability: the learner can convert a rotation between the 3×3 matrix, Euler angles, axis-angle, quaternion, and exponential-coordinate representations, state each representation’s singularity and its gradient-friendliness, and choose the correct representation for a learning, estimation, or interpolation task — the capability that Lesson 0.2.3 (the exponential map and screw theory) and every pose-learning lesson in Phase 2 and Phase 3 assume.
Running case. The same 6-DOF MuJoCo arm from 0.2.1; every example is an instance of a joint’s orientation or the end-effector’s orientation — a rotation about an alternating z / y axis, with joint angles in (−π, π] — and the lesson’s central question is which representation of that rotation is the right one for the task at hand.
§L.0 — Lesson map, objectives, and misconceptions
§L.0.1 — Lesson map
| Part | Content |
|---|---|
| A | The parameterization problem: 3 DOF, minimal charts, and what “good” parameterization means |
| B | Euler angles: conventions, composition, and the gimbal-lock singularity |
| C | Axis-angle and quaternions: Rodrigues, Hamilton product, double cover, slerp |
| D | Exponential coordinates: so(3), exp/log, gradient-friendliness, the learning choice |
| E | Failure taxonomy, lab synthesis, cheat sheet, exercises, exit test |
§L.0.2 — Objectives
By the end of this lesson you will be able to do eight things:
- State what it means for a rotation to have 3 degrees of freedom, and explain why any minimal (3-number) parameterization must have at least one singular configuration.
- State the 12 Tait-Bryan and 12 proper-Euler conventions, compute a
ZYXEuler-angle triple for a given rotation, and identify the gimbal-lock configuration and its Jacobian rank drop. - Apply Rodrigues’ formula to convert axis-angle to a rotation matrix, and derive the quaternion-to-matrix and matrix-to-quaternion conversions.
- Compute the Hamilton product of two quaternions, the conjugate, and the rotation a quaternion represents, and state the double cover’s consequences for interpolation and for a loss.
- Apply the exponential map
exp : so(3) → SO(3)and its inverselog, and state the singularities at‖ω‖ = 0and‖ω‖ = π. - Select the correct representation for a learning, estimation, interpolation, or display task, from a table of storage, singularity, and gradient properties.
- Diagnose three silent representation bugs — non-normalized quaternion, Euler interpolation through gimbal lock, and exponential coordinate at
θ = π— from their symptoms. - Produce a written “representation audit” of a pose pipeline: which representation is used at each stage, the conversions at the boundaries, and the singular configurations the pipeline could hit.
§L.0.3 — Misconception inventory
| Wrong model the learner probably holds | Why it is attractive | Where displaced | The correct model in one sentence |
|---|---|---|---|
| M1 — “Euler angles are the natural rotation representation.” | They are the one taught first in physics and graphics, and the three angles (roll, pitch, yaw) have intuitive names. | §L.2 (Part B) | Euler angles are a chart on SO(3), and every chart has at least one singular configuration (gimbal lock); they are the worst choice for interpolation and for learning, and a good choice only for human-readable display. |
| M2 — “Quaternions are mathematically the same as rotation matrices, just more compact.” | They both represent rotations and they convert to each other, and the quaternion’s 4 numbers vs. the matrix’s 9 look like a pure compression. | §L.3 (Part C) | Quaternions are a double cover of SO(3) — q and −q represent the same rotation — which changes interpolation (slerp must choose the shorter arc), changes loss (a naive quaternion L2 loss has two minima per rotation), and changes composition (the Hamilton product is not the matrix product). |
| M3 — “Interpolating Euler angles linearly is a smooth rotation interpolation.” | Linear interpolation is the default in every plotting library, and for small angles it looks fine. | §L.2 and §L.3 (Parts B and C) | Linear Euler interpolation passes through non-physical rotations (the orientation rotates about a changing axis, and the angular velocity is not constant), and near gimbal lock it stops rotating; the correct interpolation on SO(3) is slerp (quaternion) or geodesic (exponential coordinate), both of which are constant-angular-velocity. |
| M4 — “A non-unit quaternion still represents a rotation.” | The 4 numbers are the same shape, and a training loss can decrease with a non-unit quaternion. | §L.3 and §L.4 (Parts C and D) | A quaternion represents a rotation only if ‖q‖ = 1; a non-unit quaternion, converted to a matrix by the standard formula, produces a matrix with scaled entries and possibly wrong determinant — a general linear map, not a rotation — and the fix is to normalize the quaternion before conversion, or to parameterize by so(3) (3 numbers) and exp to a unit quaternion. |
M5 — “The exponential coordinate’s singularity at ‖ω‖ = π is not a real problem.” | The singularities at ‖ω‖ = 0 are well known (the identity), and π is “far” from most configurations. | §L.4 (Part D) | At ‖ω‖ = π, the axis is not uniquely determined (both ω and −ω map to the same rotation), and the log map’s gradient is unbounded — a learning or estimation pipeline that operates near π can diverge or oscillate; the fix is to use a two-chart representation (exponential near 0, quaternion near π) or to clip the log’s output. |
Each misconception is displaced in the section named, and each is tagged in the concept check that follows it.
Everything is self-contained. Where a prerequisite lesson is used, the result is restated at the point of use.
§L.0.5 — Retrieval warm-up
This is the sixth lesson of the curriculum. The warm-up draws from Lessons 0.2.1, 0.1.3, and 0.1.2 — 0.1.4 is two steps back and is not used, and 0.2.1 is the most recent.
Warm-up 1 (from 0.2.1, the 3-DOF count). A rotation matrix R has 9 entries and satisfies RᵀR = I and det R = +1. State the DOF count and the constraint count. Answer: 9 entries, 6 independent orthogonality constraints (RᵀR is symmetric 3×3, so 6 independent equations), det = +1 selects the rotation component, so 9 − 6 = 3 DOF. How this is used: §L.1’s entire question is “which 3-number chart on SO(3) should I use?”, and the 3-DOF count is the reason a minimal parameterization needs 3 numbers and cannot be globally singularity-free (a chart from a 3-dimensional manifold into ℝ³ must have at least one singular point unless the manifold is ℝ³ itself, which SO(3) is not).
Warm-up 2 (from 0.2.1, the elementary rotations). Write Rz(π/2), Ry(π/2), and Rx(π/2), and state which axis each fixes. Answer: Rz(π/2) = [[0, −1, 0], [1, 0, 0], [0, 0, 1]] (fixes z), Ry(π/2) = [[0, 0, 1], [0, 1, 0], [−1, 0, 0]] (fixes y), Rx(π/2) = [[1, 0, 0], [0, 0, −1], [0, 1, 0]] (fixes x). How this is used: §L.2’s Euler-angle conventions are products of these three elementary rotations, and the order of the product is the convention’s name (e.g., ZYX means Rz · Ry · Rx), which is the 0.2.1 non-commutativity applied to the three axes.
Warm-up 3 (from 0.1.3, nn.Linear weight and registration). A network’s final layer is nn.Linear(64, 9) and the 9 outputs are reshaped to a 3×3 matrix. Is this matrix a valid rotation? If not, what is the minimum change that makes it one? Answer: No — the 9 outputs are unconstrained, so the matrix is a general linear map that stretches and possibly reflects (it almost surely does not satisfy RᵀR = I or det R = +1). The minimum change is to output 3 numbers (a minimal parameterization) and convert them to R via exp or a quaternion-to-matrix formula; the nn.Linear’s last layer becomes nn.Linear(64, 3), and the conversion is a non-learnable operation in the forward pass. How this is used: §L.4’s “the learning choice” is exactly this change, and the lesson’s central practical outcome is the reasoning behind it.
Warm-up 4 (from 0.1.2, autograd through a non-linear conversion). A exp conversion from ω ∈ ℝ³ to R ∈ SO(3) is a non-linear function. Does autograd compute the gradient of a loss through it? Answer: Yes — exp is composed of differentiable operations (sin, cos, matmul, add), and autograd’s chain rule works through them; the gradient is well-defined except at the singularities (‖ω‖ = 0, ‖ω‖ = π), where the Jacobian of exp is singular but the gradient of a scalar loss with respect to ω can still be computed (it may be zero or unbounded depending on the loss). How this is used: §L.4’s “gradient-friendliness” discussion, and the singularities’ role in a learning pipeline.
Part A — The Parameterization Problem
§L.1 — Objects: what a parameterization must do, and what it cannot do
A parameterization of SO(3) is a map f : Θ → SO(3) from a parameter space Θ ⊆ ℝⁿ onto SO(3). The parameter space’s dimension n and the map’s properties determine the parameterization’s character:
- A minimal parameterization has
n = 3. Every minimal parameterization ofSO(3)is a chart — a smooth bijection between an open subset ofℝ³and an open subset ofSO(3)— and every chart must have at least one singular configuration where the chart’s derivative is not full rank. This is a topological fact:SO(3)is not homeomorphic toℝ³(it is not simply connected — its fundamental group isℤ₂, the quaternions’ double cover), so no single 3-number chart can cover it without a singularity. - A non-minimal parameterization has
n > 3. The 3×3 matrix (9 numbers, 6 constraints) and the quaternion (4 numbers, 1 constraint) are non-minimal; their extra numbers are constrained, and the constraint is either imposed (by normalizing or projecting after each update) or ignored (with the consequence that the output is not exactly onSO(3)).
The four desiderata of a parameterization, in order of importance for robot learning:
- Global or nearly global coverage — the chart covers as much of
SO(3)as possible without singularities, or the singularities are in configurations the task does not visit. - Smoothness of the conversion —
fandf⁻¹are differentiable, with bounded derivatives near the configurations the task visits, so a gradient step on the parameters is a smooth step on the rotation. - Composition and interpolation — there is a natural way to compose two parameterizations and to interpolate between them at constant angular velocity.
- Storage and human readability — the parameters are compact and, for a human, interpretable.
No single parameterization is best on all four; the choice is a trade-off, and the trade-off is the content of Parts B–D.
§L.1.1 — Result: minimal parameterizations must have a singularity, and the singularity is the source of learning and interpolation bugs
Claim. Every minimal (3-number) parameterization of SO(3) has at least one configuration at which the parameterization’s Jacobian is not full rank, and the singularity cannot be removed by a cleverer choice of chart.
Proof sketch. SO(3) is a compact, 3-dimensional manifold with fundamental group ℤ₂ (the quaternions SU(2) cover it 2-to-1). A global chart would be a homeomorphism between SO(3) and an open subset of ℝ³, which would make SO(3) homeomorphic to a subset of ℝ³, which would make its fundamental group trivial (subsets of ℝ³ are simply connected). Since π₁(SO(3)) = ℤ₂ ≠ 0, no global chart exists, and every chart must miss at least one configuration. At a missed configuration, the chart’s derivative must be singular, because the chart cannot be smoothly extended to it.
Corollary. A minimal parameterization’s singularity is structural, not an implementation bug, and the only ways to avoid it are (i) a non-minimal parameterization (quaternion, matrix) that covers SO(3) without singularities, or (ii) a two-chart approach that switches charts near the singularity. The quaternion is the standard non-minimal choice, and its double cover is what allows it to cover SO(3) without a singularity — at the cost of the q vs. −q ambiguity.
Corollary. A learning pipeline that uses a minimal parameterization must (i) avoid the singular configuration, or (ii) accept a bounded gradient near it, or (iii) use a non-minimal representation and project back to SO(3) after each step. The three are the design choices, and they are the content of §L.4’s “the learning choice.”
§L.1.2 — Worked-example ladder
WE-L.1.a — Fully worked: the 9-parameter matrix as the naive choice, and the constraint violation
Input. A network’s final layer outputs m = [m₁, …, m₉] and the code reshapes m to M = [[m₁, m₂, m₃], [m₄, m₅, m₆], [m₇, m₈, m₉]]. The training has converged and m happens to be approximately a rotation.
Trace.
MᵀM ≈ I(the training has driven the network toward rotations) but the off-diagonal is not exactly zero (e.g.,MᵀM[0, 1] ≈ 0.01).det M ≈ 1(the training has driven the determinant toward 1) but not exactly.- The map
p ↦ Mppreserves lengths approximately but not exactly:‖M p‖ ≈ (1 + ε)‖p‖withεthe constraint violation. - On a held-out point,
‖M p‖can differ from‖p‖by a few percent, and the “rotation” is a stretch by up to a few percent.
Output and interpretation. The 9-parameter parameterization has 6 redundant directions (the constraints), and the training does not impose the constraints; it encourages them via the loss, and the encouragement is approximate. The output is a near-rotation, not a rotation, and the near-rotation stretches the object. The fix is either to add a constraint penalty (which is approximate) or to parameterize the rotation with 3 numbers and convert (which is exact).
Self-explanation 1. Before reading on: in step 1, why does the training drive
Mtoward a rotation at all, if the loss does not include the constraints?Answer. Because the loss is a function of the poses the network predicts, and the ground-truth poses are rotations. The network can fit the ground-truth rotations with a matrix that is close to a rotation, and the “close to” is the best fit; the loss’s gradient pushes
Mtoward the ground-truth rotation’s entries, and the constraint violation is a by-product of the fit’s approximation. The fix is to impose the constraints structurally (parameterize 3 numbers and convert), not to add a loss term (which only encourages them).
WE-L.1.b — Completion problem
Input. Same as WE-L.1.a, but the network’s last layer is changed to nn.Linear(64, 3) and the 3 outputs ω are converted to R = exp(ω̂).
Trace (final 25% as prompts).
nn.Linear(64, 3)— the output isω ∈ ℝ³, 3 unconstrained numbers.exp(ω̂)— the conversion producesR ∈ SO(3)by construction (theexpmap is intoSO(3), not onto a superset).MᵀMfor the resultingR— ⟨your turn: isMᵀM = Iexactly? Why?⟩det Rfor the resultingR— ⟨your turn: isdet R = +1exactly? Why?⟩- The gradient of a loss
L(R)with respect toω— ⟨your turn: is the gradient well-defined? At whatωis it singular?⟩
Answers.
RᵀR = Iexactly (up to floating-point), becauseexp’s output is inSO(3)by construction — the group structure is preserved, and the constraint is structural, not approximate.det R = +1exactly (up to floating-point), for the same reason.- The gradient is well-defined by autograd’s chain rule, but the Jacobian of
expis singular at‖ω‖ = 0(the identity) and at‖ω‖ = π(the half-turn, where the axis is ambiguous); at those points, the gradient’s direction is undefined even though its value (from autograd) is defined.
The lesson. The 3-parameter exp conversion is structurally exact — the output is a rotation, and the constraints are satisfied by construction, not by training. The singularities at ‖ω‖ = 0 and ‖ω‖ = π are the price, and they are the subject of §L.4.
WE-L.1.c — Faded: the quaternion as the non-minimal alternative
Setup. The same network, but with nn.Linear(64, 4) and a normalize step: q = q_raw / ‖q_raw‖, then R = quat_to_R(q).
Question. Is RᵀR = I exactly? Is det R = +1 exactly? What is the quaternion’s singularity?
You must produce the intermediate steps. The setup and the answer are given.
Answer. RᵀR = I exactly (up to floating-point) and det R = +1 exactly, because the normalization makes ‖q‖ = 1 and the quaternion-to-matrix formula’s output is in SO(3) for any unit quaternion. The quaternion’s singularity is the double cover — q and −q represent the same rotation — which is not a singularity in the chart (both are valid unit quaternions) but a two-to-one map, and the “singularity” is that a loss in the quaternion space can have two minima for the same rotation (one at q, one at −q), and a gradient step can flip between them.
The lesson. The quaternion has no chart singularity (it covers SO(3) without a missed configuration), but its double cover is a different kind of ambiguity — not a singularity in the chart, but a redundancy in the representation. The 3-parameter exp has a chart singularity but no double cover; the trade-off is the choice between the two.
WE-L.1.d — Contrastive near-miss: the L2 loss on the 9-parameter matrix
Correct solution. A loss on the SO(3)-invariant distance — θ² where θ = arccos((tr(RᵀR_gt) − 1)/2) — which is zero at R = R_gt, depends only on the relative rotation, and grows at a rate proportional to the error.
Near-miss. A loss on the matrix entries — ‖R − R_gt‖_F² — which is also zero at R = R_gt and, restricted to SO(3), is also invariant: ‖R − R_gt‖_F² = 6 − 2tr(RᵀR_gt) = 8sin²(θ/2), a function of θ alone. The near-miss is not a different ranking; it is a different shape. It saturates: bounded by 8 at θ = π, with gradient d/dθ = 4sin θ that vanishes at the half-turn — so the largest errors are the ones the loss can least see. And it is not θ²: at θ = 1.0 rad it reads 1.84 against 1.00, and at θ = 3.0 it reads 7.96 against 9.00, so the gradient it hands back is wrong by a factor that itself varies with the error.
Why the near-miss produces a plausible but incorrect number. Both losses decrease as R → R_gt, so training on either converges and the final R looks right; the entry loss’s plot is monotone and smooth. What differs is the gradient, and it differs most where the error is largest: d/dθ of the entry loss is 4sin θ, which is ≈ 4θ near the minimum (twice the geodesic’s 2θ) and falls to zero at θ = π, so a network that is badly wrong receives almost no correction while a network that is nearly right is over-corrected. The failure is not a wrong minimum; it is a wrongly shaped basin, which shows up as slow or stalled convergence from bad initializations rather than as a wrong answer.
The discrepancy, stated. ‖R − R_gt‖_F² = 6 − 2tr(RᵀR_gt) = 4(1 − cos θ) = 8sin²(θ/2), a linear function of the trace; θ² = arccos((tr(RᵀR_gt) − 1)/2)², the square of the arccos of a linear function. They do not agree even near R = R_gt: the entry loss is ≈ 2θ² there, twice the geodesic’s θ², so every experiment run close to the answer sees a loss that is exactly double and a gradient (4θ against 2θ) that is exactly double. A comparison that only checks “both vanish at R_gt” therefore confirms the minimum and misses the factor of two. They diverge further at large θ: the entry loss saturates at 8 where θ² reaches π² ≈ 9.87, and the value 12 that ‖M − R_gt‖_F² attains at M = −I is outside SO(3) (det(−I) = −1) and so is not a bound on any rotation loss at all.
Self-explanation 2. Before reading on: since
‖R − R_gt‖_F²is invariant onSO(3)and vanishes exactly atR_gt, why is it still the wrong loss?Answer. Invariance and a correct minimum are necessary but not sufficient. The loss must also be a monotone function whose gradient is usable across the whole range, and
8sin²(θ/2)fails the second half: its derivative4sin θis zero atθ = π, so the half-turn — the worst possible error — is a stationary point of the loss. A network initialized nearθ = πsees a flat surface and stays there;θ²has derivative2θ, largest exactly at the worst error. The general rule: two losses that agree at the minimum and differ elsewhere are distinguished by their gradients away from the minimum, so a comparison run only nearR_gttests nothing.
§L.1.3 — Comparative table: what each parameterization provides
| Parameterization | Storage | DOF | Chart singularity | Double cover | Gradient-friendliness | Best for |
|---|---|---|---|---|---|---|
| 3×3 matrix | 9 | 3 (6 constraints) | None | No | Constraint is approximate | Computation, conversion |
| Euler angles | 3 | 3 | Gimbal lock | No | Poor (chart-dependent) | Display, human-readable |
| Axis-angle | 4 | 3 | ‖axis‖ = 1 constraint, angle = 0 | No | Medium | Interpretation |
| Quaternion | 4 | 3 (‖q‖ = 1) | None | Yes (q ≡ −q) | Good (after normalization) | Learning, interpolation |
| Exponential coordinate | 3 | 3 | ‖ω‖ = 0, ‖ω‖ = π | No | Good near 0, poor near π | Learning, optimization |
Design principle (P6.1). Choose the representation by the task, not by habit. Display → Euler. Storage/computation → matrix (or quaternion). Interpolation → quaternion slerp or exponential geodesic. Learning → exponential coordinate (3, no double cover) with a two-chart fallback near π, or quaternion with normalization. The four tasks have different needs, and the pipeline converts between them at the boundaries.
§L.1.4 — Concept check
Concept check (displaces M1, M4 in part). A learner says: “I’ll use Euler angles for the network’s output, because 3 numbers is minimal and I avoid the quaternion’s normalization.” What are the two problems with this choice?
Answer. First, Euler angles have a gimbal-lock singularity — a configuration where two axes align and the chart’s Jacobian is singular — and a learning pipeline that crosses or approaches the singularity has an unbounded or ill-defined gradient. Second, Euler angles are chart-dependent — different conventions (
ZYXvs.XYZvs.ZYZ) give different triples for the same rotation, so the loss and the gradient depend on the convention in a way the rotation does not — and interpolation in Euler space is not a constant-angular-velocity rotation. The fix is to use the exponential coordinate (3 numbers, no normalization, a singularity atπthat is far from most configurations) or a quaternion with normalization, and to convert to Euler only for display.
§L.1.5 — Consolidation checkpoint 1
(a) You should now be able to:
(i) state the 3-DOF count and the topological reason a minimal chart must have a singularity;
(ii) state the four desiderata and the trade-offs between them;
(iii) classify the five representations by storage, singularity, double cover, and gradient-friendliness;
(iv) state why the 9-parameter matrix is the wrong learnable choice and why exp of 3 numbers is the right one.
(b) Transfer probe. A learner’s network outputs a quaternion q and trains it with the loss ‖q − q_gt‖_F² where q_gt is the ground-truth quaternion. Does the loss have a unique minimum? Answer sketch: No — q and −q represent the same rotation, so if q_gt is the ground truth, both q_gt and −q_gt have loss zero (both are “correct”), and the loss has two minima. A gradient step near the boundary between the two basins can oscillate (the q → −q flip), and the learner sees a loss that jumps when the quaternion flips sign. The fix is to use a sign-invariant loss (min(‖q − q_gt‖², ‖q + q_gt‖²)) or to use the rotation’s geodesic distance θ² instead of the quaternion’s entries.
(c) Remediation rule. If you cannot do (b), return to §L.1.2 and redo WE-L.1.c, then re-attempt (b) by asking what q and −q represent.
Part B — Euler Angles and Gimbal Lock
§L.2 — Objects: the 24 conventions and the singularity
Euler angles are a triple (α, β, γ) and a convention — a specification of which three axes the rotations are about, and in what order. There are 24 conventions:
- 12 Tait-Bryan conventions: three distinct axes, e.g.,
ZYX,ZXY,YXZ,YZX,XYZ,XZY, and the six with the axes reversed. Each is a product of three elementary rotations about different axes. - 12 proper-Euler conventions: the first and third axes are the same, e.g.,
ZXZ,ZYZ,XYX,XZX,YXY,YZY, and the six with the axes’ signs reversed. Each is a productR_axis1(α) · R_axis2(β) · R_axis1(γ).
The convention’s name is the order of the product: ZYX means R = Rz(α) · Ry(β) · Rx(γ), with the z rotation applied first in the product’s right-to-left reading (which is the last in the frame’s construction, or the first depending on the convention — and the ambiguity is why the convention’s name is essential).
The gimbal-lock singularity is the configuration where the second rotation’s axis aligns with the first and third (for a proper-Euler convention) or where the first and third rotations become dependent (for a Tait-Bryan convention). For a ZYX Tait-Bryan convention, gimbal lock occurs at β = ±π/2:
- At
β = π/2,Ry(π/2)maps thex-axis to−z, soRz(α) · Ry(π/2) · Rx(γ) = Rz(α) · Ry(π/2) · Rz(−γ')for someγ'— the first and third rotations are both aboutz, and only their sum (or difference) is determined, not the two individually. The chart is singular: two angles are not independent. - The Jacobian of the map
(α, β, γ) ↦ Rdrops rank atβ = ±π/2, and the inverse map (fromRto(α, β, γ)) is not unique — there is a one-parameter family of triples that produce the sameR.
§L.2.1 — Result: gimbal lock is a rank drop of the Euler chart’s Jacobian, and interpolation through it is non-physical
Claim. For the ZYX convention, the Jacobian of (α, β, γ) ↦ Rz(α)Ry(β)Rx(γ) drops rank at β = ±π/2, and a linear interpolation in (α, β, γ) that crosses β = ±π/2 produces a non-physical rotation — the angular velocity is not constant, and the axis of rotation swings through a large arc. The velocity itself stays continuous; what changes across the crossing is which rotation the path is taking, not how fast it takes it.
Proof sketch. The Jacobian’s rank is the number of independent infinitesimal rotations the three angles generate at the configuration. At β = ±π/2, the first and third rotations’ axes align, so the three angles generate only two independent infinitesimal rotations; the Jacobian’s rank drops from 3 to 2. The linear interpolation (1−t)(α₀, β₀, γ₀) + t(α₁, β₁, γ₁) produces a path that crosses β = π/2 at some t. Because the angle triple is a smooth function of t, its image under the Euler chart is smooth too, and the body angular velocity dR/dt · Rᵀ inherits that smoothness — the rank drop shows up in the map from angle-rates to angular velocity, not as a jump in the velocity. What it does produce is a path whose instantaneous axis rotates rapidly relative to the net rotation: for the lab’s endpoints, 8.10° apart, the axis sweeps 166°.
Corollary. Euler-angle interpolation is not a rotation interpolation; the correct rotation interpolation is slerp (quaternion, constant angular velocity) or geodesic (exponential coordinate, constant angular velocity), and the difference is visible for large angles or for paths near gimbal lock. A pipeline that interpolates Euler angles and a policy that outputs Euler angles are both vulnerable to the singularity, and the fix is to use quaternion or exponential coordinates for the trajectory and to convert to Euler only for display.
Corollary. The gimbal-lock configuration is not exotic — β = π/2 is a 90° pitch, which is a common orientation in manipulation (e.g., pointing a gripper straight down). A ZYX Euler representation of a downward-pointing gripper is at or near the singularity, and a learning or estimation pipeline that uses ZYX will have an ill-conditioned gradient at that orientation.
§L.2.2 — Worked-example ladder
WE-L.2.a — Fully worked: ZYX Euler angles for a specific rotation, by hand
Input. R = Rz(π/4) · Ry(π/6) · Rx(π/3) (a ZYX triple (π/4, π/6, π/3)).
Trace (compute the matrix).
Rz(π/4) = [[√2/2, −√2/2, 0], [√2/2, √2/2, 0], [0, 0, 1]] ≈ [[0.707, −0.707, 0], [0.707, 0.707, 0], [0, 0, 1]].Ry(π/6) = [[√3/2, 0, 1/2], [0, 1, 0], [−1/2, 0, √3/2]] ≈ [[0.866, 0, 0.5], [0, 1, 0], [−0.5, 0, 0.866]].Rx(π/3) = [[1, 0, 0], [0, 1/2, −√3/2], [0, √3/2, 1/2]] ≈ [[1, 0, 0], [0, 0.5, −0.866], [0, 0.866, 0.5]].Rz·Ryfirst:Rz·Ry ≈ [[0.707·0.866 + (−0.707)·0, 0.707·0 + (−0.707)·1, 0.707·0.5 + (−0.707)·0], ...] = [[0.612, −0.707, 0.354], [0.612, 0.707, 0.354], [−0.5, 0, 0.866]](approximate).Rz·Ry·Rx: the full product, with each entry computed from theZYXformula (the standardZYXEuler-to-matrix formula isR = [[cαcβ, cαsβsγ − sαcγ, cαsβcγ + sαsγ], [sαcβ, sαsβsγ + cαcγ, sαsβcγ − cαsγ], [−sβ, cβsγ, cβcγ]]).
Trace (the singularity at β = π/2).
- At
β = π/2,cβ = 0,sβ = 1, soR[0, 0] = cα·0 = 0andR[1, 0] = sα·0 = 0; the first column is[0, 0, −1], independent ofαandγ. - The
αandγdependence is only throughsβsγ − sαcγ = sγ − sαcγandsβcγ + sαsγ = cγ + sαsγ, which are functions ofα ± γ(the sum or difference), not of the two individually.
Output and interpretation. At β = π/2, the first column of R is [0, 0, −1] for every α and γ; the remaining columns depend on α and γ only through their combination, so the chart is singular (two parameters determine one degree of freedom). The rotation is still well-defined — the R matrix is a valid rotation — but the triple (α, π/2, γ) is not unique for the rotation: many triples give the same R.
Self-explanation 3. Before reading on: at
β = π/2, what is the relationship betweenαandγthat gives the sameR?Answer. The
Rdepends onαandγonly throughα − γ(orα + γ, depending on the convention’s signs), so two triples(α₁, π/2, γ₁)and(α₂, π/2, γ₂)give the sameRifα₁ − γ₁ = α₂ − γ₂. The differenceα − γis the only free combination, and the two angles are not independently recoverable fromR. The fix is to choose one of them (e.g., setγ = 0) and recover the other, which is what the inverse map does at the singularity.
WE-L.2.b — Completion problem
Input. R = Rz(π/2) · Ry(π/2) · Rx(0) (a ZYX triple at the singularity).
Trace (final 25% as prompts).
Ry(π/2) = [[0, 0, 1], [0, 1, 0], [−1, 0, 0]].Rz(π/2) · Ry(π/2) = [[0, −1, 0], [1, 0, 0], [0, 0, 1]] · [[0, 0, 1], [0, 1, 0], [−1, 0, 0]] = [[0, −1, 0], [0, 0, 1], [−1, 0, 0]]. ⟨your turn: verify the product⟩· Rx(0) =the same (the identity’s product). ⟨your turn: is the result dependent onαandγindependently, or only onα − γ?⟩- A different triple
(π/4, π/2, −π/4)givesα − γ = π/4 − (−π/4) = π/2. ⟨your turn: does it give the sameRas(π/2, π/2, 0)?⟩
Answers.
- The product is
[[0, −1, 0], [0, 0, 1], [−1, 0, 0]], and each entry is independent ofαandγbecause atβ = π/2thecβ = 0andsβ = 1collapse the formula. - The result depends only on
α − γ(in this convention), not on the two individually. - Yes,
(π/4, π/2, −π/4)gives the sameRas(π/2, π/2, 0), because both haveα − γ = π/2. The two triples are different but represent the same rotation — the non-uniqueness of the Euler representation at the singularity.
The lesson. At gimbal lock, the Euler triple is not unique for the rotation, and the inverse map must choose one representative. The non-uniqueness is the symptom of the singularity, and the Jacobian’s rank drop is the mechanism.
WE-L.2.c — Faded: the XYZ convention and the singularity’s location
Setup. The same rotation R = Rz(π/4) · Ry(π/6) · Rx(π/3), but expressed in the XYZ convention (R = Rx(γ) · Ry(β) · Rz(α)).
Question. What is the XYZ triple for this R? At what β does the XYZ convention have its singularity?
You must produce the intermediate steps. The setup and the answer are given.
Answer. The XYZ triple is not the same as the ZYX triple — the conventions are different charts, and the same rotation has different triples in different conventions. The XYZ triple can be computed by solving the XYZ Euler-to-matrix formula for (γ, β, α), which gives different values. The XYZ singularity is at β = ±π/2 as well — every Tait-Bryan convention has its singularity at the middle angle = ±π/2 — but the axis at which the two rotations align is the x-axis (for XYZ), not the z-axis (for ZYX).
The lesson. The singularity’s location is β = ±π/2 for every Tait-Bryan convention, and the axis of the singularity depends on the convention’s first and third axes. The convention’s choice determines which orientations are near the singularity, and a task that visits β ≈ ±π/2 (e.g., a downward-pointing gripper in ZYX) is near the singularity regardless of the convention, so the representation (quaternion or exponential) is the fix, not the convention.
WE-L.2.d — Contrastive near-miss: linear Euler interpolation
Correct solution. The learner interpolates two orientations R₀ and R₁ by converting each to a quaternion and using slerp, which gives a constant-angular-velocity path.
Near-miss. The learner converts each to a ZYX Euler triple and linearly interpolates the three angles. The path is a smooth curve in Euler space, but the rotation it represents is not a constant-angular-velocity path; near β = ±π/2, the rotation’s axis can swing through a large arc while the endpoints are close together.
Why the near-miss produces a plausible but incorrect number. The Euler interpolation is smooth in Euler space — the three angles move linearly — so the plotted path looks smooth and the intermediate rotations look plausible. Two things are wrong with it, and only one is visible. The speed varies: in the lab’s own example it dips by 21.3% mid-path (0.254 at the ends, 0.200 at the crossing), which reads as a slightly uneven trajectory and nothing worse. The axis is the real defect: for endpoints 8.10° apart, the instantaneous rotation axis swings through 166° — the arm turns the right way, but along the way it rotates about an axis that sweeps almost a half-circle rather than the single axis the shortest rotation uses. A learner watching for a stutter will see a smooth, plausible motion and conclude the interpolation is fine.
The discrepancy, stated. The Euler interpolation’s path is a straight line in ℝ³ (the three angles), and the rotation’s path is the image of that line under the Euler chart. The image is not a geodesic on SO(3) unless the line is in a specific direction, and near the chart’s singularity the image bends away from the geodesic by a large fraction of its own length. The slerp path is a geodesic on SO(3) (constant angular velocity, fixed axis), and its derivative is bounded everywhere.
Self-explanation 4. Before reading on: in the near-miss, at
β = π/2, what happens to the angular velocity as the Euler interpolation crosses the singularity — and what happens to the axis?Answer. At
β = π/2, the chart’s rank drops from 3 to 2, so the three angles generate only two independent angular velocities. The linear path continues through the singularity without any discontinuity — the angular velocity’s magnitude is continuous, and merely dips (measured:0.254at the ends,0.200at the crossing). What goes wrong is not continuity but direction: the instantaneous rotation axis is a smooth function oft, and over the path it sweeps through166°while the endpoints are8.10°apart — the axis turns through almost a half-circle to accomplish a rotation of a few degrees. So the correct statement is not “the velocity jumps” but “the velocity is smooth and the path is wrong” — the rotation takes a long way round. That distinction matters because a stutter is visible and a detour is not: the stutter test passes on a broken interpolation.
§L.2.3 — Comparative table: Euler conventions and their singularities
| Convention type | Number | Singularity | Axes affected | Common use |
|---|---|---|---|---|
Tait-Bryan (ZYX, XYZ, …) | 12 | β = ±π/2 | First and third | Aerospace (yaw-pitch-roll), robotics display |
Proper Euler (ZXZ, ZYZ, …) | 12 | β = 0 or π | First and third (the same axis) | Orbital mechanics, some graphics |
| Quaternion (no Euler) | — | None (double cover) | — | Learning, interpolation |
| Exponential coordinate | — | ‖ω‖ = 0, π | — | Learning, optimization |
Design principle (P6.2). Never interpolate in Euler space. Euler angles are a display representation; the trajectory should be interpolated in the quaternion or exponential-coordinate space (both give constant-angular-velocity paths), and the Euler angles should be computed from the interpolated rotation for display only. The pipeline’s internal representation is quaternion or exponential, and Euler is a terminal conversion.
§L.2.4 — Concept check
Concept check (displaces M3). A learner says: “Linear Euler interpolation is fine for small angles; the singularity only matters for large rotations.” Is the statement true, and what is the correct statement?
Answer. The statement is false in general. Linear Euler interpolation is approximately a rotation interpolation for small angles and configurations far from the singularity, but (i) the approximation error grows with the angle, and (ii) if the path crosses
β = ±π/2(even for a small interpolation angle, if the endpoints are near the singularity), the path detours far from the geodesic joining its endpoints and the rotation axis swings through a large arc. The velocity stays continuous — so the failure is silent to any test that watches for a stutter. The correct statement is: “Linear Euler interpolation is a smooth path in Euler space, not a rotation interpolation onSO(3); it is acceptable only for small angles and far from the singularity, and the correct interpolation is slerp or geodesic.”
§L.2.5 — Consolidation checkpoint 2
(a) You should now be able to:
(i) state the 24 Euler conventions and the difference between Tait-Bryan and proper-Euler;
(ii) compute a ZYX triple’s matrix and identify the singularity at β = ±π/2;
(iii) state the rank drop and the non-uniqueness at the singularity;
(iv) state why Euler interpolation is not a rotation interpolation and what the correct interpolation is.
(b) Transfer probe. A learner’s ZYX Euler representation of a downward-pointing gripper (β = π/2) is near the singularity. They change to XYZ and find the same problem. Why? Answer sketch: every Tait-Bryan convention has its singularity at β = ±π/2, and a downward-pointing gripper’s β (the middle angle’s magnitude) is π/2 in every Tait-Bryan convention (the middle angle is the angle between the first and third axes’ directions, and a downward gripper’s z-axis is antiparallel to the world z, which is the β = π/2 configuration for any convention that uses z as the first or third axis). The fix is not to change the convention but to change the representation (quaternion or exponential), which has no gimbal lock.
(c) Remediation rule. If you cannot do (b), return to §L.2.2 and redo WE-L.2.b, then re-attempt (b) by asking what the middle angle’s value is for a downward-pointing gripper in any Tait-Bryan convention.
Part C — Axis-Angle and Quaternions
§L.3 — Objects: axis-angle, Rodrigues’ formula, and the Hamilton product
Axis-angle represents a rotation as (u, θ) with u ∈ ℝ³, ‖u‖ = 1 (the axis), and θ ∈ [0, π] (the angle). The rotation matrix is given by Rodrigues’ formula:
where \hat{u} is the skew-symmetric matrix of u:
so that \hat{u} v = u × v for any v (the cross product).
Quaternions represent a rotation as a unit quaternion q = (w, x, y, z) = w + xi + yj + zk with ‖q‖² = w² + x² + y² + z² = 1. The rotation a quaternion represents is (u, θ) with u = (x, y, z)/‖(x, y, z)‖ and θ = 2 arccos(w). The quaternion-to-matrix formula is:
The Hamilton product (quaternion composition) is:
where v = (x, y, z). The composition corresponds to matrix multiplication of the rotations: R$q_1 \otimes q_2$ = $R(q_1$) $R(q_2$).
The conjugate is q* = (w, -x, -y, -z), and for a unit quaternion, q* = q⁻¹ (the inverse). The double cover: q and −q represent the same rotation (both have the same (u, θ) with θ negated, which is the same rotation because R(u, θ) = R(−u, 2π − θ) = R(u, θ) for θ ∈ [0, π]).
§L.3.1 — Result: quaternions are a double cover, and the double cover’s consequences are interpolation and loss
Claim. The map q ↦ R(q) from unit quaternions to SO(3) is two-to-one: R(q) = R(−q) for every unit quaternion q, and every rotation has exactly two unit-quaternion preimages.
Proof sketch. The quaternion-to-matrix formula R(q) = I + 2w\hat{v} + 2\hat{v}^2 depends on q through w and v, and replacing q with −q replaces (w, v) with (−w, −v), so R(−q) = I + 2(−w)$−\hat{v}$ + 2$−\hat{v}$^2 = I + 2w\hat{v} + 2\hat{v}^2 = R(q). So the map is at most two-to-one; it is exactly two-to-one because q and −q are the only preimages (the map is a covering map of degree 2, SU(2) → SO(3)).
Corollary. A loss on the quaternion’s entries, L(q) = ‖q − $q_gt$‖², has two minima (at $q_gt$ and −$q_gt$) for the same rotation, and a gradient step can flip between the basins. The fix is a sign-invariant loss: L(q) = min(‖q − $q_gt$‖², ‖q + $q_gt$‖²), or a loss on the rotation (the geodesic distance), which is sign-invariant by construction.
Corollary. Interpolation between q₀ and q₁ must choose the shorter arc — if the dot product q₀ · q₁ is negative, the interpolation should use −q₁ (which represents the same rotation) and interpolate from q₀ to −q₁, giving the shorter path. The slerp formula does this: slerp(q₀, q₁, t) = (sin((1−t)Ω)/sin Ω) q₀ + (sin(tΩ)/sin Ω) q₁, with Ω = arccos(|q₀ · q₁|) and the sign of q₁ chosen so that q₀ · q₁ ≥ 0.
§L.3.2 — Worked-example ladder
WE-L.3.a — Fully worked: Rodrigues’ formula and the quaternion for a 90° rotation about z
Input. u = (0, 0, 1), θ = π/2 (a 90° rotation about z).
Trace (Rodrigues).
\hat{u} = [[0, -1, 0], [1, 0, 0], [0, 0, 0]].\hat{u}^2 = [[-1, 0, 0], [0, -1, 0], [0, 0, 0]](thez-axis is fixed; thexy-plane is rotated by 90°).R = I + sin(π/2) \hat{u} + (1 - cos(π/2)) \hat{u}^2 = I + \hat{u} + 1·\hat{u}^2.I + \hat{u} + \hat{u}^2 = [[1, 0, 0], [0, 1, 0], [0, 0, 1]] + [[0, -1, 0], [1, 0, 0], [0, 0, 0]] + [[-1, 0, 0], [0, -1, 0], [0, 0, 0]] = [[0, -1, 0], [1, 0, 0], [0, 0, 1]]. ✓ (this isRz(π/2)).
Trace (quaternion).
q = (cos(θ/2), sin(θ/2) u) = (cos(π/4), sin(π/4)·(0, 0, 1)) = (√2/2, 0, 0, √2/2).‖q‖² = 1/2 + 1/2 = 1. ✓q’s rotation:u = (0, 0, 1)(thevpart normalized),θ = 2 arccos(√2/2) = 2·(π/4) = π/2. ✓
Output and interpretation. The Rodrigues formula and the quaternion give the same rotation matrix Rz(π/2), and the two representations are equivalent — the quaternion is a different parameterization of the same R, with the q vs. −q redundancy ((√2/2, 0, 0, √2/2) and (−√2/2, 0, 0, −√2/2) are the two quaternions for this rotation).
Self-explanation 5. Before reading on: in step 2, why is
\hat{u}^2 = [[-1, 0, 0], [0, -1, 0], [0, 0, 0]]? What does this matrix do?Answer.
\hat{u}is the cross-product matrix, so\hat{u}^2 v = \hat{u}(u × v) = u × (u × v) = u(u·v) - v(u·u) = u(u·v) - v(using the vector triple product). Foru = (0, 0, 1),u(u·v) - vgives(0, 0, $v_z$) - ($v_x$, $v_y$, $v_z$) = ($-v_x$, $-v_y$, 0), which is the matrix[[-1, 0, 0], [0, -1, 0], [0, 0, 0]]. This matrix is the projection onto thexy-plane negated — it reflects thexy-plane, which is the second-order effect of the rotation (thesin θterm is the first-order effect, the(1 - cos θ)term is the second-order).
WE-L.3.b — Completion problem
Input. q₀ = (1, 0, 0, 0) (the identity) and q₁ = (0, 0, 0, 1) (a 180° rotation about z).
Trace (final 25% as prompts).
q₁ = (cos(π/2), sin(π/2)·(0, 0, 1)) = (0, 0, 0, 1). ✓q₁’s rotation:θ = 2 arccos(0) = π,u = (0, 0, 1)— a 180° rotation aboutz. ⟨your turn: computeR(q₁)from the formula and verify it isRz(π)⟩q₀ · q₁ = 0·1 + 0·0 + 0·0 + 1·0 = 0. ⟨your turn: is the dot product 0? What does that imply for the slerp’s arc length?⟩- The slerp:
Ω = arccos(|0|) = π/2, andslerp(0.5) = (sin(0.25π)/sin(π/2))·q₀ + (sin(0.25π)/sin(π/2))·q₁ = (√2/2)·q₀ + (√2/2)·q₁ = (√2/2, 0, 0, √2/2). ⟨your turn: what rotation does this represent?⟩
Answers.
R(q₁) = I + 2·0·\hat{z} + 2·\hat{z}^2 = I + 2·[[-1, 0, 0], [0, -1, 0], [0, 0, 0]] = [[-1, 0, 0], [0, -1, 0], [0, 0, 1]]— the 180° rotation aboutz. ✓q₀ · q₁ = 0, so the two quaternions are orthogonal, which means the arc between them isπ/2in the quaternion space (4D). The rotation arc isπ, because the rotation angle is twice the quaternion’s —2·arccos|q₀·q₁| = 2·(π/2) = π. The “half” relation doubles the rotation arc; it does not leave it equal.slerp(0.5) = (√2/2, 0, 0, √2/2), which is the quaternion for a 90° rotation aboutz(the halfway point in the rotation space). ✓ The slerp gives a constant-angular-velocity path from the identity to the 180° rotation, and the midpoint is the 90° rotation.
The lesson. Slerp interpolates along the quaternion’s great circle, and the rotation’s angle is twice the quaternion’s arc: a quaternion arc of π/2 is a rotation arc of π. The slerp’s halfway point is the halfway rotation, because the factor of two applies to the whole path — which is exactly what the θ/2 in the quaternion’s definition buys, and why the slerp’s constant angular velocity in quaternion space transfers to constant angular velocity in SO(3).
WE-L.3.c — Faded: the double cover and the sign flip
Setup. q = (√2/2, 0, 0, √2/2) (the 90° z rotation) and $q_gt$ = (−√2/2, 0, 0, −√2/2) (the same rotation, but the −q representation).
Question. What is the loss ‖q − $q_gt$‖²? What is the geodesic loss θ²? Which one is the correct loss for learning?
You must produce the intermediate steps. The setup and the answer are given.
Answer. ‖q − $q_gt$‖² = (√2/2 − (−√2/2))² + 0 + 0 + (√2/2 − (−√2/2))² = (√2)² + (√2)² = 2 + 2 = 4. The geodesic loss θ² = 0 (the two quaternions represent the same rotation). The correct loss is the geodesic (or a sign-invariant quaternion loss), and the naive entry loss of 4 is a spurious error — the two quaternions are the same rotation, but the entry loss treats them as different, which is the double cover’s consequence.
The lesson. The naive quaternion loss is not a rotation loss; it has a spurious penalty for the −q representation, which the learning can exploit by flipping the quaternion’s sign (a gradient step in the direction of −q is a “better” fit to the entry loss but the same rotation). The fix is the sign-invariant loss or the geodesic loss.
WE-L.3.d — Contrastive near-miss: the non-normalized quaternion
Correct solution. The network’s output $q_raw$ is normalized (q = q_raw/‖$q_raw$‖), and q is a unit quaternion; the conversion R(q) is a rotation.
Near-miss. The network’s output $q_raw$ is used directly in the quaternion-to-matrix formula, without normalization; ‖$q_raw$‖ ≠ 1, so $R(q_raw$) is not a rotation — it is a matrix with scaled entries and possibly det ≠ +1.
Why the near-miss produces a plausible but incorrect number. The unnormalized $q_raw$ gives a matrix $R(q_raw$) that is close to a rotation if ‖$q_raw$‖ is close to 1, and the loss decreases as $q_raw$ moves toward the ground truth. But the matrix’s entries are scaled by ‖$q_raw$‖², and a network that learns to inflate $q_raw$’s norm can reduce some entries’ error at the cost of others; the resulting matrix stretches the object, and the “rotation” is not in SO(3). The symptom is a stretched object, a det R ≠ +1, and a RᵀR ≠ I; the fix is the normalization, and the normalization must be in the forward pass, not just in the loss.
The discrepancy, stated. $R(q_raw$) with ‖$q_raw$‖ = c is I + 2c²·w\hat{v} + 2c²·\hat{v}² — both terms scale with c², because the formula I + 2w[v]_× + 2[v]_ײ is quadratic in the quaternion’s components, and scaling q → cq scales w and v together. The linear term is quadratic in c, not linear. The normalization q = q_raw/‖$q_raw$‖ is what makes R(q) a rotation; skipping it makes R a general linear map whose “rotation” part is confounded with the scale. For c = 2 on the 90°-about-z quaternion the determinant is 25, while the mis-scaled I + 2c·w\hat{v} + 2c²\hat{v}² gives 13 — the same qualitative symptom (a stretch, det ≠ 1), the wrong number, which is why the lab’s printed 25/37/1 are the check and the algebra is not. The fix is to normalize in the forward pass, and to constrain the network to output unit quaternions (or use the exponential coordinate’s exp, which outputs unit quaternions by construction).
Self-explanation 6. Before reading on: in the near-miss, if the network’s output
$q_raw$has‖$q_raw$‖ = 2, what is the matrix$R(q_raw$)’s relationship to the rotationR(q_raw/‖$q_raw$‖)?Answer.
$R(q_raw$)is not simplyR(q_raw/‖$q_raw$‖)scaled; the quaternion-to-matrix formula is quadratic inq, soR(c·q) = I + 2c²·w\hat{v} + 2c²·\hat{v}², which is a quadratic function ofcin both terms. Forc = 2,R(2q) = I + 8w\hat{v} + 8\hat{v}², which is a different matrix fromR(q)(not a scalar multiple, not a rotation). The correct operation is to normalize before the formula, givingR(q)(the rotation), notR(2q)(the stretched map).
Part D — Exponential Coordinates and the Learning Choice
§L.4 — Objects: so(3), exp, log, and the singularities
The Lie algebra so(3) is the set of 3×3 skew-symmetric matrices \hat{ω} with ω ∈ ℝ³. Its dimension is 3, and it is the tangent space of SO(3) at the identity.
The exponential map exp : so(3) → SO(3) is Rodrigues’ formula with ω = θu:
The logarithm log : SO(3) → so(3) is its inverse:
The singularities:
- At
θ = 0(the identity),ω = 0, and the log’s formula has a0/0; the limit isω = 0, and the derivative oflogwith respect toRis finite there (measured‖∂ log/∂R‖ = 1.0). The left Jacobian’s determinant is1atθ = 0, so this is not a rank drop either — it is a chart defect, where the axisuis undefined whileωitself is perfectly well behaved. - At
θ = π,ω = θuand−ω = −θuboth map to the same rotation (the half-turn), so the axisuis not unique; the log’s derivative with respect toRis unbounded (measured‖∂ log/∂R‖ = 3.1 × 10³atθ = π − 10⁻³against1.57atθ = π/2), and a gradient step computed through the log nearθ = πcan be arbitrarily large.
§L.4.1 — Result: exp is a diffeomorphism on ‖ω‖ < π, and the learning pipeline must avoid ‖ω‖ = π or use a two-chart approach
Claim. The exponential map exp : {ω ∈ ℝ³ : ‖ω‖ < π} → SO(3) \ {rotations by π} is a diffeomorphism — a smooth bijection with a smooth inverse — and its Jacobian is invertible on the open ball ‖ω‖ < π. The map fails to be a diffeomorphism at ‖ω‖ = π (the half-turns), where the log’s Jacobian is singular.
Proof sketch. The exp map’s differential at ω = θu is the left Jacobian $J_l(ω$) = I + \frac{1 - \cos θ}{θ}\hat{u} + \frac{θ - \sin θ}{θ}\hat{u}², whose determinant is 2$1 - \cos θ$/θ². That determinant is 1 in the limit θ → 0, is positive everywhere on 0 < θ < 2π, and reaches 0.405 at θ = π — so the exp map is a local diffeomorphism at the half-turn, and the π problem is not a rank drop. It is global injectivity: exp(πu) = exp(−πu) for every u, so the map is not injective on the closed ball ‖ω‖ ≤ π, and log has no unique inverse at the boundary. The two singularities are therefore of different kinds: at θ = 0 the map is injective but the axis is undefined (a chart issue); at θ = π the map is a local diffeomorphism but two-to-one (a covering issue).
Corollary. For learning, the exp map’s singularity at θ = 0 is benign: the left Jacobian’s determinant is 1 there (not zero), the derivative of log is finite (‖∂ log/∂R‖ = 1.0), and what degenerates is only the axis — u is undefined when ω = 0, so axis-angle is a chart with an origin defect while ω itself is perfectly well behaved. The singularity at θ = π is the problematic one, because the log’s output is ambiguous there, and a loss on ω can have two minima for the same rotation.
Corollary. The learning choice is the exponential coordinate for ‖ω‖ < π (the ball where the map is a diffeomorphism) with a two-chart fallback near ‖ω‖ = π (switch to a quaternion chart), or the quaternion with normalization (which has no chart singularity but the double cover). The two-chart approach is the most robust, and the single chart is the simplest; the choice depends on the task’s configurations.
§L.4.2 — Worked-example ladder
WE-L.4.a — Fully worked: exp for a small ω and a π rotation
Input. ω = (0.1, 0, 0) and ω = (π, 0, 0).
Trace (ω = (0.1, 0, 0)).
θ = 0.1,u = (1, 0, 0).\hat{ω} = [[0, 0, 0], [0, 0, -0.1], [0, 0.1, 0]].\exp$\hat{ω}$ ≈ I + \hat{ω} + 0.5 \hat{ω}² = I + [[0, 0, 0], [0, 0, -0.1], [0, 0.1, 0]] + 0.5·[[0, 0, 0], [0, -0.01, 0], [0, 0, -0.01]] = [[1, 0, 0], [0, 0.995, -0.1], [0, 0.1, 0.995]](the small-angle approximation ofRx(0.1)).
Trace (ω = (π, 0, 0)).
θ = π,u = (1, 0, 0).\exp$\hat{ω}$ = I + \frac{\sin π}{π} \hat{ω} + \frac{1 - \cos π}{π²} \hat{ω}² = I + 0 + \frac{2}{π²} \hat{ω}².\hat{ω}² = [[0, 0, 0], [0, -π², 0], [0, 0, -π²]].\frac{2}{π²} \hat{ω}² = [[0, 0, 0], [0, -2, 0], [0, 0, -2]].I + \frac{2}{π²} \hat{ω}² = [[1, 0, 0], [0, -1, 0], [0, 0, -1]] = Rx(π). ✓
Output and interpretation. At θ = π, \exp gives Rx(π), and the same matrix is given by \exp$−\hat{πu}$ = \exp$\hat{-πu}$ — the −ω representation. The log’s inverse at Rx(π) is ambiguous — both (π, 0, 0) and (−π, 0, 0) are valid preimages — and the axis is not determined by the rotation alone (only the line through u is determined, not the direction). The θ = 0 case is benign (ω = 0 is the unique preimage of the identity), but the θ = π case is the problematic one.
Self-explanation 7. Before reading on: the left Jacobian at
θ = πhas determinant0.405, not0. What then is singular at the half-turn, and which map does the learning pipeline actually feel it in?Answer. Two different maps are in play. The exp map is a local diffeomorphism at
π(its Jacobian is invertible) and is smooth inω— its gradient with respect toωis finite, and in fact vanishes there (measured4.0atπ/2,0.004atπ − 0.001). The log map is where the trouble is: it has no continuous single-valued inverse at the half-turn (the two preimages±πu), and its derivative with respect toRdiverges as1/(π − θ)(measured1.57atπ/2,3.1 × 10³atπ − 10⁻³). A pipeline that outputsωand computesR = exp(ω)has a vanishing gradient at the half-turn, which reads as slow learning; a pipeline that supervisesω = log(R)has an exploding gradient there. The fix for the first is a better initialization or a loss onR; the fix for the second is aπclip or a two-chart switch.
WE-L.4.b — Completion problem
Input. ω = (0.1, 0.2, −0.05) (a small rotation).
Trace (final 25% as prompts).
θ = ‖ω‖ = sqrt(0.01 + 0.04 + 0.0025) = sqrt(0.0525) ≈ 0.229.u = ω/θ ≈ (0.436, 0.873, −0.218). ⟨your turn: verify‖u‖ = 1⟩R = \exp$\hat{ω}$. ⟨your turn: compute the first-order approximationI + \hat{ω}and the second-orderI + \hat{ω} + 0.5 \hat{ω}², and compare⟩
Answers.
‖u‖² = 0.436² + 0.873² + 0.218² ≈ 0.190 + 0.762 + 0.047 = 0.999 ≈ 1. ✓\hat{ω} = [[0, 0.05, 0.2], [-0.05, 0, -0.1], [-0.2, 0.1, 0]];\hat{ω}²is the outer productωωᵀ - θ² I, which is a symmetric matrix; the first-orderI + \hat{ω}is a rotation approximation with an error ofO(θ²) ≈ 0.05, and the second-orderI + \hat{ω} + 0.5\hat{ω}²has an error ofO(θ³) ≈ 0.01. The second-order is closer to the trueR.
The lesson. For small ω, the exp map is approximately linear , and the linear approximation’s error is O(θ²); the second-order approximation is O(θ³), and the full formula is exact. A learning pipeline near the identity can use the linear approximation (which is the left Jacobian’s domain) and still have a smooth gradient; a pipeline near θ = π must use the full formula (and even then, the log’s ambiguity is the issue).
WE-L.4.c — Faded: the gradient at θ = 0 and at θ = π
Setup. A loss L(R) is a smooth function of the rotation, and ω is the exponential coordinate. The gradient ∂L/∂ω is computed by autograd.
Question. At ω = 0 (the identity) and at ω = (π, 0, 0) (a half-turn), is the gradient well-defined? What is the gradient’s magnitude near each?
You must produce the intermediate steps. The setup and the answer are given.
Answer. At ω = 0, the gradient is well-defined (the exp map is smooth, and the loss is smooth, so the composition is smooth); the gradient’s magnitude is finite. At ω = (π, 0, 0), the gradient with respect to ω is also well-defined — and it is smaller than at π/2, because exp has a horizontal tangent at the half-turn (measured 4.0 at π/2 against 0.004 at π − 0.001). What is not well-defined is the inverse: log maps Rx(π) to either +πu or −πu, and its derivative with respect to R diverges as 1/(π − θ) (measured 3.1 × 10³ at π − 10⁻³). So a forward pipeline ω → R sees a vanishing gradient at the half-turn, and a backward pipeline R → ω sees an exploding one; both are the same singularity seen through different maps.
The lesson. The θ = 0 singularity is a chart issue (the axis is undefined, the map is fine); the θ = π singularity is a covering issue (two preimages, and the inverse map’s derivative diverges). The two are different, and the π one is the one a learning pipeline must guard against — but the guard is needed on the log side, not the exp side.
WE-L.4.d — Contrastive near-miss: the exp coordinate without a π guard
Correct solution. The pipeline uses ω = log(R) and clips ‖ω‖ to π − ε (with ε ≈ 0.01), so the ω never reaches the singularity, and a gradient step near π is bounded.
Near-miss. The pipeline uses ω = log(R) without a clip. The gradient that reaches the parameters is ∂L/∂R = (∂L/∂ω)·(∂ log/∂R), and the second factor grows as 1/(π − θ) near the half-turn. A single training step there is amplified into a large parameter change — the rotation jumps far from where it was, and the training oscillates or diverges near the half-turns.
Why the near-miss produces a plausible but incorrect number. The loss decreases over most of the training, and the jump near π is a transient that the optimizer recovers from — the training eventually converges, but the transient loses training time, and the final model can be worse than the clipped version’s. The learner who sees occasional loss spikes and attributes them to “a bad batch” or “a learning-rate issue” has missed the π singularity, and the fix is the clip.
The discrepancy, stated. The log’s θ/(2 sin θ) factor is θ/(2 sin π) = ∞ at θ = π — the inverse map’s derivative is unbounded there (measured ‖∂ log/∂R‖ = 1.57 at π/2, 3.1 × 10³ at π − 10⁻³), and a single step near it can move the parameter far. The clip ‖ω‖ ≤ π − ε makes the factor bounded by θ/(2 sin(π − ε)) ≈ π/(2ε), which is finite (though large), and the optimizer’s step is bounded by the learning rate times that factor. The clip is a regularization at the singularity, and its cost is a small bias near the half-turns; its benefit is the stability of the training.
The asymmetry worth remembering. The clip is needed only on the log side. On the exp side — a network that outputs ω and computes R = exp(ω) — the gradient with respect to ω does not blow up at π; it vanishes (measured 4.0 at π/2, 0.004 at π − 10⁻³). That failure is the opposite symptom: not a spike but a stall, because the half-turn is the exp map’s extreme and its tangent there is horizontal. A learner who applies a π clip to an exp-side pipeline has treated a stall as a spike and clipped the gradient in the direction it was already too small.
Self-explanation 8. Before reading on: in the near-miss, if the training has already converged and the model’s rotations are all far from
π, does the missing clip matter?Answer. No — if every rotation the pipeline encounters is far from
π(e.g.,‖ω‖ < π/2), the log’s factor is bounded byθ/(2 sin(π/2)) ≈ π/4, and the derivative is well-behaved. The clip matters only for rotations near the half-turns, and it is a defensive measure: a training that starts nearπ(from a random initialization with large angles) or crossesπ(from a loss that pushes the rotation through the half-turn) needs the clip; a training that stays in‖ω‖ < π/2does not. The clip’s cost is negligible when it is inactive, and its benefit is the absence of a catastrophic step when it is active, which is why it is the default on the log side of a robust pipeline.
§L.4.3 — Comparative table: the learning-parameterization choice
| Parameterization | Storage | Singularity | Gradient near singularity | Double cover | When to use |
|---|---|---|---|---|---|
| Euler angles | 3 | Gimbal lock (β = ±π/2) | Unbounded (chart rank drop) | No | Never for learning |
Exponential coordinate (exp side, ω → R) | 3 | ‖ω‖ = 0, π | Bounded at 0; vanishes at π | No | Learning with a loss on R |
Exponential coordinate (log side, R → ω) | 3 | ‖ω‖ = 0, π | Bounded at 0; diverges at π | No | Supervision with a π clip |
| Quaternion (normalized) | 4 | None | Bounded | Yes (q ≡ −q) | Learning with a sign-invariant loss, interpolation |
| 3×3 matrix | 9 | None | Approximate constraint | No | Computation, not learning |
| Two-chart (exp + quaternion) | 3 or 4 | None (switch at π/2) | Bounded | Depends on the chart | Most robust |
Design principle (P6.3). Know which side of the exp map your gradient lives on. The exponential coordinate has 3 numbers and a smooth exp; on the forward side (ω → R), the gradient is bounded at 0 and vanishes at π, so a stall near half-turns is the symptom and the fix is initialization or a loss on R, not a clip. On the inverse side (R → ω), the derivative diverges at π, and the π clip or the two-chart switch is the fix. The quaternion is the alternative when the double cover’s consequences are acceptable (sign-invariant loss, slerp interpolation); the matrix is never the learning choice (its constraints are approximate).
§L.4.4 — Concept check
Concept check (displaces M5). A learner says: “The exp coordinate’s singularity at
πis far from my configurations, so I don’t need to worry about it.” What are the two configurations in which it does matter, even if the target rotations are far fromπ?Answer. First, a training that starts from a random initialization can have
‖ω‖nearπ(a random rotation has a uniform axis and an angle that can be anywhere, including nearπ), and the initial gradient is unbounded; the model starts near the singularity even if it ends far from it. Second, a loss that pushes the rotation through the half-turn (e.g., a target that isπaway from the current rotation, or a trajectory that crosses the half-turn) has a gradient that diverges at the crossing, and the model crosses the singularity even if the endpoints are far from it. The clip or the two-chart is the fix, and it is a defensive measure for both cases.
§L.4.5 — Consolidation checkpoint 4
(a) You should now be able to:
(i) state the exp map and the log map, and compute both for a specific rotation;
(ii) state the two singularities (θ = 0 and θ = π) and their different natures (a chart defect vs. a two-to-one covering);
(iii) state the learning choice (exp with a π clip, or quaternion with a sign-invariant loss) and the trade-offs;
(iv) state why a random initialization and a π-crossing trajectory both need the clip.
(b) Transfer probe. A learner’s network outputs ω, converts to R = exp(ω̂), and trains with a loss on R. The training is stable for the first 1000 steps and then diverges with a large loss spike. What is the most likely cause? Answer sketch: the gradient is blowing up on the log side of the round trip, where ∂ log/∂R grows as 1/(π − θ) (measured 1.57 at π/2, 3141 at π − 0.001). The exp map itself is smooth at the half-turn and its gradient shrinks there (4.0 → 0.004), so a π clip on the head’s output would suppress the wrong quantity. The fix is to keep the target ω — the argument to log_so3 — away from π, or to switch charts to the quaternion near π. The diagnostic is to log the target’s ‖ω‖ during training and observe it approaching π before the spike.
(c) Remediation rule. If you cannot do (b), return to §L.4.2 and redo WE-L.4.d, then re-attempt (b) by asking which side of the map the θ/(2 sin θ) factor lives on, and what that factor does as θ → π.
Part E — Failure Taxonomy, Lab, Cheat Sheet, Exercises, Exit Test
§L.5 — Failure taxonomy
| Symptom | Three most likely causes, ranked | Discriminating test | Fix |
|---|---|---|---|
| A learned “rotation” stretches the object | (1) 9-parameter matrix output, no constraint; (2) non-normalized quaternion; (3) exponential coordinate unclipped and near π | Print RᵀR − I and det R; if non-zero, it is (1) or (2) | Parameterize 3 numbers and exp; normalize the quaternion; clip the ω |
| Rotation interpolation stutters, reverses, or takes a detour | (1) Euler interpolation through gimbal lock; (2) non-normalized quaternion slerp; (3) the wrong quaternion sign in slerp | Measure the path’s distance from the geodesic joining its endpoints; a large detour with smooth velocity is (1) | Slerp with the sign choice; exponential geodesic |
| Training oscillates near half-turns | (1) Differentiating through log near π; (2) the θ/(2 sin θ) factor unbounded; (3) the optimizer’s step amplified | Print ‖∂ log/∂R‖ near the half-turn — if it grows as 1/(π−θ), it is (1). The exp-side gradient falls there and is not the cause | Clip on the log side; two-chart switch |
Quaternion loss jumps between q and −q | (1) Non-sign-invariant entry loss; (2) the double cover; (3) the training’s basin flip | Print the quaternion’s sign at consecutive steps; a flip is the cause | Sign-invariant loss; geodesic loss |
| The Euler inverse map has multiple solutions | (1) Gimbal lock (β = ±π/2); (2) the convention’s non-uniqueness; (3) the inverse map’s branch choice | Print the triple at β = π/2; the non-uniqueness is the cause | Use quaternion or exponential; if Euler is required, fix a branch |
The table covers the lab’s three break-it steps (non-unit quaternion, Euler interpolation through gimbal lock, the log-side π singularity) plus two not exercised there (the 9-parameter matrix, the quaternion sign flip).
§L.6 — The lab
§L.6.1 — Task
Implement conversions between the four representations (matrix, ZYX Euler, quaternion, exponential coordinate) and use them to demonstrate gimbal lock, the double cover, the π singularity, and the interpolation differences; break the pipeline three ways.
§L.6.2 — Conditions
Python 3.10+, PyTorch 2.2+, CPU only. All runs use torch.manual_seed(42), dtype = float64. Runtime: under 2 minutes.
§L.6.3 — Controls held fixed
The seed (42), the dtype (float64), and the base rotation (Rz(0.3)·Ry(0.5)·Rx(0.2)) are fixed across all steps.
§L.6.4 — Metrics table
| Metric | Definition | How measured |
|---|---|---|
roundtrip_error | ‖R - R(R.from_X(X.from_R(R)))‖_max for each representation X | The max entry difference |
gimbal_lock_rank | The rank of the Euler chart’s Jacobian at β = π/2 | The numerical rank of the 3×3 Jacobian |
gimbal_lock_singular_values | The singular values of the Jacobian at β = π/2 | The three singular values |
slerp_error(t) | ‖$R_slerp(t$) - $R_geodesic(t$)‖_max for t ∈ {¼, ½} | The max entry difference at each t |
nlerp_error(t) | ‖$R_nlerp(t$) - $R_geodesic(t$)‖_max for t ∈ {¼, ½} | The max entry difference at each t |
nonunit_quaternion_det | $det(R(q_raw$)) for ‖$q_raw$‖ = 2, on two axes plus the identity | The determinant |
exp_grad | ‖∂L/∂ω‖ at ‖ω‖ = π − 0.001 vs. ‖ω‖ = π/2 | The gradient norm at both |
log_deriv | ‖∂ log/∂R‖ (finite difference) at ‖ω‖ = π − 0.001 vs. ‖ω‖ = π/2 | The difference-quotient norm at both |
euler_detour | The max geodesic distance between the Euler-linear path and the geodesic joining its endpoints | The angle between $R_euler(t$) and the closest point on the geodesic |
§L.6.5 — Pre-registered hypotheses
- H1.
roundtrip_erroris< 1e-10for the quaternion and exponential representations (they are exact diffeomorphisms away from singularities), and is< 1e-10for Euler except nearβ = ±π/2, where it can be larger (the inverse map’s branch choice). - H2.
gimbal_lock_singular_valueshas one value≈ 0and two> 0.1; the numerical rank is 2, not 3. The prediction is[0, σ₂, σ₃]withσ₂, σ₃ > 0.1. - H3.
slerp_erroris< 1e-10at botht = ¼andt = ½(slerp tracks the geodesic exactly), whilenlerp_erroris< 1e-10att = ½and> 1e-3att = ¼— at the midpoint the two coincide by symmetry, so the midpoint is the wrong place to look. Measured for a 90° arc:nlerperrs by1.46e-2(max entry) att = ¼and by0att = ½. The prediction is the pair (quarter > 1e-3,midpoint < 1e-10), and the midpoint figure is the control that shows the test can see the difference and chooses not to. - H4.
nonunit_quaternion_detfor‖$q_raw$‖ = 2is not1, and not a function of‖$q_raw$‖alone: it depends on the quaternion’s axis. WritingR(q) = I + 2w[v]_× + 2[v]_ײforq = (w, v), scalingq → cqgivesI + 2c²w[v]_× + 2c²[v]_ײ— both terms quadratic inc, with a constant term, sodet R(cq)is neitherc⁶nor any other fixed power ofc. For the lab’s 90°-about-zquaternion scaled to norm 2 the determinant is25; for(0.5, 0.5, 0.5, 0.5)scaled to norm 2 it is37; for the identity quaternion scaled to norm 2 it is1— the unnormalized map leaves the identity untouched while stretching every other rotation. The prediction isdet ≠ 1with the axis-dependent value, and the general lesson is that the unnormalized map is not a similarity transform. - H5. The
πsingularity is in the log map, not the exp map. The exp map’s gradient with respect toωvanishes at‖ω‖ = π(measured:4.0atπ/2,0.004atπ − 0.001), becauseexpis smooth and the half-turn is its extreme. The log map’s derivative with respect toRdiverges as‖ω‖ → π(measured:1.57atπ/2,3.1e3atπ − 0.001). The prediction is the pair of directions: exp’s gradient falls, log’s derivative grows, and the ratio‖∂ log/∂R‖atπ − 0.001overπ/2is> 100.
§L.6.6 — The break-it sequence
- The non-unit quaternion (silent). Use
$q_raw$(with‖$q_raw$‖ = 2) in the quaternion-to-matrix formula without normalization. Predicted symptom: no exception;RᵀR ≠ I,det R ≠ 1, the “rotation” stretches. Actual: as predicted — silent,det = 25for the z-axis quaternion and37for a generic axis. - The Euler interpolation through gimbal lock (silent). Interpolate
(α₀, β₀, γ₀)to(α₁, β₁, γ₁)linearly, withβcrossingπ/2. Predicted symptom: no exception; the rotation path is not the geodesic between its endpoints — it detours by a large fraction of the total rotation. Actual: as predicted — silent; endpoints8.10°apart, max detour4.41°, i.e. 54% of the total rotation. Note that the angular velocity does not jump: it is smooth through the crossing and merely dips. The defect is the path, not the speed. - The
πsingularity in the log map (silent). Train a small network whose loss differentiates throughlog_so3, withωnearπand no clip. Predicted symptom: no exception at the crossing; the derivative’s norm grows as1/(π − θ); a step overshoots. Actual: as predicted — silent; the log derivative is1999×larger atπ − 0.001than atπ/2. The exp-side gradient moves the other way (4.0 → 0.004), which is why the clip must go on the right side of the map.
All three are silent, exceeding the minimum of two.
§L.6.7 — Acceptance criteria
| Criterion | Threshold | Artefact it is tested on |
|---|---|---|
| Round-trip error for quaternion and exponential | < 1e-10 | The lab’s round-trip table |
| Gimbal-lock Jacobian rank | 2 (not 3) | The lab’s singular-value print |
Slerp error at t = ¼ and t = ½ | < 1e-10 at both | The lab’s interpolation table |
Nlerp error at t = ¼ | > 1e-3 (measured 1.46e-2) | The lab’s interpolation table |
Nlerp error at t = ½ | < 1e-10 — the control showing the midpoint cannot discriminate | The lab’s interpolation table |
Non-unit quaternion’s det | ≠ 1, axis-dependent (25 z-axis, 37 generic, 1 identity) | The lab’s det prints |
Log-side derivative ratio ‖∂ log/∂R‖(π−ε) / ‖∂ log/∂R‖(π/2) | > 100 (measured 1999) | The lab’s log-derivative print |
Exp-side gradient ratio ‖∂L/∂ω‖(π−ε) / ‖∂L/∂ω‖(π/2) | < 1 (measured 1e-3) — the direction that refutes the naive reading | The lab’s exp-gradient print |
| Euler-linear detour off the geodesic | > 1° (measured 4.41°) | The lab’s detour print |
| Three break-its predicted before running | 3/3 timestamped | The notebook’s prediction file |
§L.6.8 — What good looks like
A strong submission has five properties:
- The round-trip conversions are tested for all four representations, with the error printed for each, and the Euler round-trip’s larger error near
β = ±π/2is shown separately. - Gimbal lock is shown as a rank drop, with the Jacobian’s singular values printed and the
≈ 0value identified. - The interpolation comparison is made at a
twhere the two differ. A submission that reports only the midpoint has demonstrated nothing, because slerp and nlerp agree there by symmetry; the strong submission prints botht = ¼andt = ½and says why the first is the discriminating one. - The
πsingularity is shown as a ratio on each side of the map, with the exp-side gradient falling and the log-side derivative rising, and the two norms printed. A submission that reports only one direction has not located the singularity. - The Euler detour is measured against the geodesic, not against the per-step change in
R— the latter is smooth and would show nothing. - The three break-its are predicted before running, with numeric bounds, and the actuals are compared.
§L.6.9 — Runnable lab code
# lab_0_2_2.py — Rotation representations, gimbal lock, double cover, pi singularity
# Requires: Python 3.10+, PyTorch 2.2+
import torch
import math
torch.manual_seed(42)
dtype = torch.float64
device = torch.device("cpu")
# --- Step 1: elementary rotations and conversions ----------------------
def rot_x(q): c, s = torch.cos(q), torch.sin(q); return torch.tensor([[1, 0, 0], [0, c, -s], [0, s, c]], dtype=dtype, device=device)
def rot_y(q): c, s = torch.cos(q), torch.sin(q); return torch.tensor([[c, 0, s], [0, 1, 0], [-s, 0, c]], dtype=dtype, device=device)
def rot_z(q): c, s = torch.cos(q), torch.sin(q); return torch.tensor([[c, -s, 0], [s, c, 0], [0, 0, 1]], dtype=dtype, device=device)
def $R_to_zyx(R$):
"""Return (alpha, beta, gamma) with R = Rz(alpha) Ry(beta) Rx(gamma)."""
beta = torch.asin(-R[2, 0].clamp(-1, 1))
cb = torch.cos(beta)
if cb.abs() < 1e-9: # gimbal lock: choose gamma = 0
gamma = torch.zeros_like(beta)
alpha = torch.atan2(R[1, 2], R[0, 2]) # or any branch
else:
alpha = torch.atan2(R[1, 0] / cb, R[0, 0] / cb)
gamma = torch.atan2(R[2, 1] / cb, R[2, 2] / cb)
return alpha, beta, gamma
def zyx_to_R(a, b, g): return rot_z(a) @ rot_y(b) @ rot_x(g)
def $R_to_quat(R$):
"""Convert R (3,3) to unit quaternion (w, x, y, z)."""
tr = R[0, 0] + R[1, 1] + R[2, 2]
if tr > 0:
s = 2 * torch.sqrt(tr + 1.0)
w = 0.25 * s
x = (R[2, 1] - R[1, 2]) / s
y = (R[0, 2] - R[2, 0]) / s
z = (R[1, 0] - R[0, 1]) / s
elif R[0, 0] > R[1, 1] and R[0, 0] > R[2, 2]:
s = 2 * torch.sqrt(1.0 + R[0, 0] - R[1, 1] - R[2, 2])
w = (R[2, 1] - R[1, 2]) / s
x = 0.25 * s
y = (R[0, 1] + R[1, 0]) / s
z = (R[0, 2] + R[2, 0]) / s
elif R[1, 1] > R[2, 2]:
s = 2 * torch.sqrt(1.0 + R[1, 1] - R[0, 0] - R[2, 2])
w = (R[0, 2] - R[2, 0]) / s
x = (R[0, 1] + R[1, 0]) / s
y = 0.25 * s
z = (R[1, 2] + R[2, 1]) / s
else:
s = 2 * torch.sqrt(1.0 + R[2, 2] - R[0, 0] - R[1, 1])
w = (R[1, 0] - R[0, 1]) / s
x = (R[0, 2] + R[2, 0]) / s
y = (R[1, 2] + R[2, 1]) / s
z = 0.25 * s
q = torch.stack([w, x, y, z])
return q / q.norm()
def quat_to_R(q):
w, x, y, z = q.unbind()
return torch.tensor([
[1 - 2*(y*y + z*z), 2*(x*y - z*w), 2*(x*z + y*w)],
[2*(x*y + z*w), 1 - 2*(x*x + z*z), 2*(y*z - x*w)],
[2*(x*z - y*w), 2*(y*z + x*w), 1 - 2*(x*x + y*y)],
], dtype=dtype, device=device)
def skew(w):
z = torch.zeros((), dtype=dtype, device=device)
return torch.stack([torch.stack([z, -w[2], w[1]]),
torch.stack([w[2], z, -w[0]]),
torch.stack([-w[1], w[0], z])])
def exp_so3(w):
theta = w.norm()
U = skew(w)
if float(theta) < 1e-9:
return torch.eye(3, dtype=dtype, device=device) + U
return (torch.eye(3, dtype=dtype, device=device)
+ (torch.sin(theta) / theta) * U
+ ((1 - torch.cos(theta)) / theta**2) * (U @ U))
def log_so3(R):
tr = (R[0, 0] + R[1, 1] + R[2, 2]).clamp(-1, 3)
theta = torch.acos(((tr - 1) / 2).clamp(-1, 1))
v = torch.stack([R[2, 1] - R[1, 2], R[0, 2] - R[2, 0], R[1, 0] - R[0, 1]])
if float(theta) < 1e-9:
return v / 2
return (theta / (2 * torch.sin(theta))) * v
# --- Step 2: the base rotation and round trips ------------------------
R = rot_z(torch.tensor(0.3, dtype=dtype)) @ rot_y(torch.tensor(0.5, dtype=dtype)) @ rot_x(torch.tensor(0.2, dtype=dtype))
a, b, g = $R_to_zyx(R$)
q = $R_to_quat(R$)
w = log_so3(R)
print(f"ZYX: ({a.item():.6f}, {b.item():.6f}, {g.item():.6f})")
print(f"quat: {q.tolist()}, ||q|| = {q.norm().item():.6f}")
print(f"omega: {w.tolist()}, ||w|| = {w.norm().item():.6f}")
err_zyx = (zyx_to_R(a, b, g) - R).abs().max().item()
err_q = (quat_to_R(q) - R).abs().max().item()
err_w = (exp_so3(w) - R).abs().max().item()
print(f"roundtrip errors: ZYX={err_zyx:.2e}, quat={err_q:.2e}, omega={err_w:.2e}")
# --- Step 3: gimbal lock at beta = pi/2 -------------------------------
$R_gl$ = rot_z(torch.tensor(0.3, dtype=dtype)) @ rot_y(torch.tensor(math.pi/2, dtype=dtype)) @ rot_x(torch.tensor(0.5, dtype=dtype))
# The Jacobian of (a, b, g) -> R at (a, b, g): numerically differentiate
def jacobian_zyx(a, b, g, eps=1e-6):
J = torch.zeros(9, 3, dtype=dtype, device=device)
for i, (da, db, dg) in enumerate([(eps, 0, 0), (0, eps, 0), (0, 0, eps)]):
Rp = zyx_to_R(a + da, b + db, g + dg).reshape(-1)
Rm = zyx_to_R(a - da, b - db, g - dg).reshape(-1)
J[:, i] = (Rp - Rm) / (2 * eps)
return J
$a_gl$, $b_gl$, $g_gl$ = $R_to_zyx(R_gl$)
J = $jacobian_zyx(a_gl$, $b_gl$, $g_gl$)
sv = torch.linalg.svdvals(J)
print(f"gimbal-lock singular values: {sv.tolist()}")
print(f"gimbal-lock rank: {(sv > 0.1).sum().item()}") # Expected: 2
# --- Step 4: slerp vs. nlerp — measure at t=1/4, where they differ ----
q0 = $R_to_quat(torch$.eye(3, dtype=dtype, device=device))
q1 = $R_to_quat(rot_z(torch$.tensor(math.pi/2, dtype=dtype)))
def slerp(q0, q1, t):
dot = (q0 * q1).sum()
if dot < 0:
q1 = -q1
dot = -dot
Omega = math.acos(min(max(float(dot), -1.0), 1.0))
if Omega < 1e-9:
return q0
return (math.sin((1 - t) * Omega) / math.sin(Omega)) * q0 + (math.sin(t * Omega) / math.sin(Omega)) * q1
def nlerp(q0, q1, t):
q = (1 - t) * q0 + t * q1
return q / q.norm()
# At t = 1/2 the two coincide by symmetry. The discriminating point is t = 1/4.
interp_err = {}
for t in (0.25, 0.5):
$R_true$ = rot_z(torch.tensor(t * math.pi / 2, dtype=dtype))
$R_s$ = quat_to_R(slerp(q0, q1, t))
$R_n$ = quat_to_R(nlerp(q0, q1, t))
interp_err[t] = (($R_s$ - $R_true$).abs().max().item(),
($R_n$ - $R_true$).abs().max().item())
print(f"t={t}: slerp error = {interp_err[t][0]:.2e}, nlerp error = {interp_err[t][1]:.2e}")
# --- Step 5: break-it #1 — the non-unit quaternion (silent) ----------
$q_unit$ = $R_to_quat(rot_z(torch$.tensor(math.pi/2, dtype=dtype)))
$q_raw$ = 2 * $q_unit$ # $||q_raw||$ = 2
$R_raw$ = $quat_to_R(q_raw$) # NOT normalized
print(f"[break-it #1] $det(R(q_raw$)) = {torch.linalg.$det(R_raw$).item():.6f}") # Expected: != 1
print(f"[break-it #1] $||R(q_raw)^T$ $R(q_raw$) - I||_max = {(R_raw.T @ $R_raw$ - torch.eye(3, dtype=dtype, device=device)).abs().max().item():.6f}")
# The value is axis-dependent, not a function of $||q_raw||$ alone:
$q_gen$ = torch.tensor([0.5, 0.5, 0.5, 0.5], dtype=dtype, device=device)
print(f"[break-it #1] det(R(2q)) for a generic axis = {torch.linalg.det(quat_to_R(2 * $q_gen$)).item():.6f}")
# --- Step 6: break-it #2 — Euler interpolation through gimbal lock ---
# The endpoints differ only in beta (alpha - gamma is the invariant, and it is 0
# at both ends). Interpolating alpha, beta, gamma linearly looks harmless — but
# the parameter path is not the rotation path. Measure the detour.
a0, b0, g0 = 0.0, math.pi/2 - 0.1, 0.0
a1, b1, g1 = math.pi/2, math.pi/2 + 0.1, math.pi/2
RA = zyx_to_R(*[torch.tensor(v, dtype=dtype) for v in (a0, b0, g0)])
RB = zyx_to_R(*[torch.tensor(v, dtype=dtype) for v in (a1, b1, g1)])
def expm(w):
t = w.norm()
U = skew(w)
if float(t) < 1e-12:
return torch.eye(3, dtype=dtype, device=device) + U
return (torch.eye(3, dtype=dtype, device=device)
+ (torch.sin(t) / t) * U + ((1 - torch.cos(t)) / t**2) * (U @ U))
def angle(A, B):
tr = (A.T @ B)
return torch.acos(((tr[0, 0] + tr[1, 1] + tr[2, 2] - 1) / 2).clamp(-1, 1))
M = RA.T @ RB
th = angle(torch.eye(3, dtype=dtype, device=device), M)
ax = torch.stack([M[2, 1] - M[1, 2], M[0, 2] - M[2, 0], M[1, 0] - M[0, 1]]) / (2 * torch.sin(th))
detours = []
for i in range(101):
t = i / 100
Re = zyx_to_R(*[torch.tensor(v, dtype=dtype) for v in
(a0 + t*(a1-a0), b0 + t*(b1-b0), g0 + t*(g1-g0))])
# the geodesic's closest point to Re is the one at Re's own angle from RA
Rg = RA @ expm((angle(RA, Re) / th) * th * ax)
detours.append(angle(Rg, Re).item())
print(f"[break-it #2] endpoint separation: {math.degrees(th.item()):.2f} deg")
print(f"[break-it #2] max detour off the geodesic: {math.degrees(max(detours)):.2f} deg")
print(f"[break-it #2] detour profile (every 10th): {[round(math.degrees(detours[i]), 2) for i in range(0, 101, 10)]}")
# The path wanders 4.41 deg off a geodesic that is only 8.10 deg long. No exception.
# --- Step 7: break-it #3 — the two sides of the pi singularity --------
# exp side: gradient of a loss on R with respect to omega. It VANISHES at pi.
I3 = torch.eye(3, dtype=dtype, device=device)
exp_grads = {}
for mag in (math.pi / 2, math.pi - 0.001):
w = torch.tensor([mag, 0.0, 0.0], dtype=dtype, device=device, requires_grad=True)
((exp_so3(w) - I3) ** 2).sum().backward()
exp_grads[mag] = w.grad.norm().item()
print(f"[break-it #3] exp-side grad norm at |w|={mag:.4f}: {exp_grads[mag]:.6f}")
# log side: derivative of log with respect to R. It DIVERGES at pi.
E = torch.tensor([[0.0, 1, 0], [-1, 0, 0], [0, 0, 0]], dtype=dtype, device=device) * 1e-6
log_derivs = {}
for mag in (math.pi / 2, math.pi - 0.001):
R0 = exp_so3(torch.tensor([mag, 0.0, 0.0], dtype=dtype))
log_derivs[mag] = ((log_so3(R0 + E) - log_so3(R0 - E)) / 2e-6).norm().item()
print(f"[break-it #3] log-side |d log/dR| at |w|={mag:.4f}: {log_derivs[mag]:.4f}")
# --- Step 8: summary ------------------------------------------------
print("\n--- lab 0.2.2 summary ---")
print(f"H1: roundtrip ZYX={err_zyx:.2e}, quat={err_q:.2e}, omega={err_w:.2e}")
print(f"H2: gimbal singular values={sv.tolist()}, rank={(sv > 0.1).sum().item()}")
print(f"H3: slerp t=1/4={interp_err[0.25][0]:.2e}, nlerp t=1/4={interp_err[0.25][1]:.2e} "
f"(t=1/2: slerp={interp_err[0.5][0]:.2e}, nlerp={interp_err[0.5][1]:.2e})")
print(f"H4: det(R(2q))={torch.linalg.$det(R_raw$).item():.4f}, generic axis={torch.linalg.$det(quat_to_R(2*q_gen$)).item():.4f}")
print(f"H5: exp grad {exp_grads[math.pi - 0.001]:.6f} at pi vs {exp_grads[math.pi / 2]:.4f} at pi/2 (falls); "
f"log deriv ratio = {log_derivs[math.pi - 0.001] / log_derivs[math.pi / 2]:.1f}")
print(f"break-it #2: {math.degrees(th.item()):.2f} deg endpoints, {math.degrees(max(detours)):.2f} deg detour")
print("silent break-its: #1 (non-unit quat), #2 (Euler gimbal), #3 (pi singular)")
§L.7 — The cheat sheet
| Term or result | Statement or definition | Role: lesson of origin, lesson that consumes it |
|---|---|---|
| Minimal parameterization | A 3-number chart on SO(3); every minimal chart has a singularity | Introduced here; consumed by 3.1.2 (VLA pose head) |
| Gimbal lock | The rank drop of a Tait-Bryan chart at the middle angle ±π/2 | Introduced here; consumed by 2.1.1 (pose estimation diagnostics) |
| Rodrigues’ formula | R = I + sin θ \hat{u} + (1 − cos θ) \hat{u}² | Introduced here; consumed by 0.2.3 (exp map), 1.1.1 (joint FK) |
| Quaternion | A unit 4-vector (w, x, y, z); R(q) via the formula | Introduced here; consumed by 2.1.1 (pose representation), 2.2.1 (averaging) |
| Hamilton product | The quaternion composition, matching R(q₁ ⊗ q₂) = R(q₁)R(q₂) | Introduced here; consumed by 2.2.1 (interpolation), 3.1.2 |
| Double cover | q and −q represent the same rotation | Introduced here; consumed by 2.2.1 (sign-invariant loss) |
| Slerp | Constant-angular-velocity quaternion interpolation | Introduced here; consumed by 4.3.x (trajectory evaluation) |
| so(3) | The 3-dimensional Lie algebra of skew-symmetric matrices | Introduced here; consumed by 0.2.3 (screw theory) |
| Exp map | exp : so(3) → SO(3), Rodrigues with ω = θu | Introduced here; consumed by 0.2.3, 1.1.1 (twist) |
| Log map | log : SO(3) → so(3), inverse of exp | Introduced here; consumed by 1.1.2 (IK) |
π singularity | The log’s ambiguity and unbounded gradient at half-turns | Introduced here; consumed by 2.2.1 (policy gradient) |
| Learning choice | Exp coordinate, clipping the log side near π, or quaternion with a sign-invariant loss | Introduced here; consumed by 3.1.2 (VLA action parameterization) |
| Design principle P6.1 | Choose the representation by the task | Introduced here; consumed by 2.1.1 |
| Design principle P6.2 | Never interpolate in Euler space | Introduced here; consumed by 4.3.2 |
| Design principle P6.3 | Use exp for learning; clip the log side, or switch charts near π | Introduced here; consumed by 3.1.2 |
§L.8 — Exercises with solutions
E 0.2.2.1 — The ZYX triple (numerical). R = Rz(π/2) · Ry(0) · Rx(0). Compute the ZYX triple and state the singularity’s value of β.
S 0.2.2.1. R = Rz(π/2), so the ZYX triple is (π/2, 0, 0); β = 0, far from the singularity ±π/2. The lesson: the triple’s β is the middle angle, and the singularity is at β = ±π/2; a rotation about the first axis has β = 0 and is far from the singularity.
E 0.2.2.2 — The quaternion for a 60° rotation (numerical). u = (0, 1, 0), θ = π/3. Compute q, verify ‖q‖ = 1, and compute R(q).
S 0.2.2.2. q = (cos(π/6), sin(π/6)·(0, 1, 0)) = (√3/2, 0, 1/2, 0); ‖q‖² = 3/4 + 1/4 = 1. R(q) = I + 2w\hat{v} + 2\hat{v}² with w = √3/2, v = (0, 1/2, 0): \hat{v} = [[0, 0, 1/2], [0, 0, 0], [-1/2, 0, 0]], \hat{v}² = [[-1/4, 0, 0], [0, 0, 0], [0, 0, -1/4]]. R = I + 2·(√3/2)·\hat{v} + 2·(-1/4)·... = [[1/2, 0, √3/2], [0, 1, 0], [-√3/2, 0, 1/2]] — Ry(π/3). ✓ The lesson: the quaternion’s angle is θ/2, and the matrix formula recovers Ry(θ).
E 0.2.2.3 — The gimbal-lock rank drop (diagnostic). A ZYX chart’s Jacobian at β = π/2 has singular values [0, 1.0, 1.0]. What is the rank, and what does the zero singular value mean for a gradient step?
S 0.2.2.3. Rank 2. The zero singular value means the Jacobian’s column space is 2-dimensional — the three angles generate only two independent rotation directions — so the inverse Jacobian is unbounded, and a gradient step on the angles is unbounded for a given loss in SO(3). The fix is to avoid the singularity (use quaternion or exp) or to switch charts. The lesson: the singularity is a rank issue, and the gradient’s magnitude near it is 1/σ_min, which is unbounded.
E 0.2.2.4 — The double cover (diagnostic). A learner’s quaternion loss is ‖q − $q_gt$‖². The training oscillates between two values of q that represent the same rotation. What is the cause, and what is the fix?
S 0.2.2.4. The double cover — q and −q are the same rotation, and the entry loss has two minima (one at $q_gt$, one at −$q_gt$). The training can flip between the basins, and the loss jumps when it flips. The fix is a sign-invariant loss (min(‖q − $q_gt$‖², ‖q + $q_gt$‖²)) or a rotation loss (θ² with θ = arccos((tr(R(q)ᵀ $R(q_gt$)) − 1)/2)), which is sign-invariant by construction. The lesson: the quaternion’s entries are not the rotation; a loss on the entries has a spurious −q minimum, and the rotation loss is the fix.
E 0.2.2.5 — The slerp midpoint (numerical). q₀ = (1, 0, 0, 0), q₁ = (√2/2, 0, 0, √2/2) (a 90° z rotation). Compute the slerp midpoint.
S 0.2.2.5. q₀ · q₁ = √2/2. Ω = arccos(√2/2) = π/4. slerp(0.5) = (sin(π/8)/sin(π/4))·q₀ + (sin(π/8)/sin(π/4))·q₁. sin(π/8) = √(2 − √2)/2 ≈ 0.3827, sin(π/4) = √2/2 ≈ 0.7071. The coefficient is 0.3827/0.7071 ≈ 0.5412. slerp(0.5) ≈ 0.5412·(1, 0, 0, 0) + 0.5412·(0.7071, 0, 0, 0.7071) ≈ (0.5412 + 0.3827, 0, 0, 0.3827) = (0.9239, 0, 0, 0.3827). This is (cos(π/8), 0, 0, sin(π/8)), which is a 45° z rotation — the halfway rotation. ✓ The lesson: the slerp’s halfway quaternion is the halfway rotation, and the coefficient’s shape comes from the quaternion sphere’s great circle.
E 0.2.2.6 — The exp’s π singularity (diagnostic). A learner’s training is stable for 1000 steps and then has a large loss spike at step 1001. Their ω is the network’s output, and the loss is ‖exp(ω) − $R_gt$‖². What is the most likely cause, and what is the fix?
S 0.2.2.6. The ω has drifted near ‖ω‖ = π, and the spike comes from the log side of the round trip, not the exp side — provided the loss differentiates through log. The exp map is perfectly smooth at the half-turn: its gradient shrinks (measured 4.0 at π/2, 0.004 at π − 0.001), because the half-turn is exp’s extreme and the map is flat there. The unbounded factor is in log, where ∂ log/∂R grows as 1/(π − θ) (measured 1.57 at π/2, 3141 at π − 0.001), so a target near the half-turn produces an enormous gradient on whatever the loss differentiates. The fix is to clip on the log side — keep the target ω away from π — or use a two-chart switch to the quaternion near π. The diagnostic is to log ‖ω‖ of the target, not of the network’s output, and observe it approaching π before the spike. The lesson: the π singularity is a value issue on the log side, and knowing which side matters — a clip on ω when the gradient lives in ∂ log/∂R suppresses a gradient that was already too small.
E 0.2.2.7 — The interpolation choice (transfer probe). A learner wants to interpolate between two orientations for a robot arm’s end-effector. They use Euler angles because “it’s the representation I know.” What are the two failure modes of this choice, and what is the correct choice?
S 0.2.2.7. (i) Non-constant angular velocity — the Euler interpolation’s path is a straight line in the angle space, not a geodesic on SO(3), so the arm’s angular velocity varies along the path. (ii) A detour off the geodesic — if the path crosses β = ±π/2, the angles are no longer independent and the path wanders far from the shortest rotation between its endpoints; in the lab’s own example the endpoints are 8.10° apart and the Euler-linear path strays 4.41°, more than half the total rotation. Note that the speed does not jump — measured along the path it is smooth and merely dips. The failure is geometric, and a “look for the stutter” test will miss it. The correct choice is slerp (quaternion) or geodesic (exp coordinate), both of which give constant-angular-velocity paths and have no gimbal lock. The lesson: the interpolation is on SO(3), not on ℝ³, and the geodesic is the right path.
E 0.2.2.8 — The learning parameterization (constructive). A network’s final layer is nn.Linear(64, 3), and the 3 outputs are converted to R = exp(ω̂). Write the pseudocode for the forward pass and state what the output’s constraints are (guaranteed or not).
S 0.2.2.8.
class RotationHead(nn.Module):
def __init__(self, in_dim):
super().__init__()
self.fc = nn.Linear(in_dim, 3)
def forward(self, x):
omega = self.fc(x)
omega = clip_omega(omega, max_norm=pi - eps) # optional but recommended
R = exp_so3(omega) # (B, 3, 3)
return R
The output R is guaranteed to satisfy RᵀR = I and det R = +1 (up to floating point), because exp maps any 3-vector to SO(3). The clip_omega is optional and its value depends on which side of the map the gradient lives: it bounds the exp-side gradient, which is already small near π (measured 0.004 at π − 0.001 against 4.0 at π/2), so clipping ω there suppresses a gradient that needs no suppression. What does need a bound is the log-side derivative, which diverges as 1/(π − θ) — so the clip belongs on any ω that will be fed into log_so3, not on the head’s output. The gradient ∂L/∂ω is well-defined by autograd’s chain rule, and it is bounded near ω = 0 (the common case). The lesson: the 3-parameter exp parameterization is the structurally correct learnable rotation; the defensive clip is for the log side, and putting it on the exp side is a plausible-looking mistake.
§L.9 — Exit test
You are ready for Lesson 0.2.3 when you can, without notes:
- State what a minimal parameterization of
SO(3)is, and explain why every minimal chart has a singularity. - State the four desiderata of a parameterization, and the trade-offs between them.
- State the 24 Euler conventions and the singularity at
β = ±π/2for a Tait-Bryan convention. - Compute a
ZYXtriple’s matrix, and identify the singularity’s rank drop and non-uniqueness. - State why Euler interpolation is not a rotation interpolation, and name the correct one.
- Apply Rodrigues’ formula to convert axis-angle to a rotation matrix, and verify the result for a 90°
zrotation. - State the quaternion-to-matrix formula and compute
R(q)for a specificq. - Compute the Hamilton product of two quaternions, and state the composition’s correspondence to matrix multiplication.
- State the double cover and its two consequences (sign-invariant loss, slerp’s sign choice).
- State the exp and log maps, and compute both for a specific rotation.
- State the two exp singularities (
θ = 0a chart defect,θ = πa two-to-one covering), and the learning choice (clip the log side, or use two charts). - Diagnose a non-unit-quaternion, a gimbal-lock detour, and a
π-singularity loss spike from their symptoms — including which side of the exp/log map each one lives on.
If any of items 1–12 is not answerable without notes, return to the section that introduced it: item 1 to §L.1.1, item 2 to §L.1, item 3 to §L.2, item 4 to §L.2.2, item 5 to §L.2.3, item 6 to §L.3.2, item 7 to §L.3.2, item 8 to §L.3, item 9 to §L.3.1, item 10 to §L.4, item 11 to §L.4.1, and item 12 to §L.5.
§L.10 — Provenance and evidence tiers
What this lesson reproduces. Every hypothesis in §L.6.5 is executed by the lab in §L.6.9. Tier A, status R.
| Claim | Tier | Status | Where it is reproduced |
|---|---|---|---|
| The Euler chart’s Jacobian drops rank at gimbal lock, and the numerical rank is 2, not 3 | A | R | H2’s singular values [2.0, 1.414, 2.76e-12], rank 2 |
| Slerp tracks the geodesic exactly; nlerp is exact at the midpoint and errs off it | A | R | H3’s slerp t=¼: 0.00e+00, nlerp t=¼: 1.46e-02, both ≈0 at t=½ |
A non-unit quaternion produces a matrix that is not a rotation, and its determinant depends on the axis, not on ‖q‖ alone | A | R | H4’s det(R(2q)) = 25.0 for z vs 37.0 for a generic axis |
The π singularity lives in the log map; the exp map’s gradient vanishes there | A | R | H5’s exp grad 0.004 at π vs 4.0 at π/2; log derivative ratio 1999.4 |
| Euler interpolation is not a rotation interpolation: it leaves the geodesic and takes a detour | A | R | break-it #2’s 8.10° endpoint error and 4.41° detour |
What this lesson does not reproduce. The claim that a random initialization can place ‖ω‖ near π — and therefore that the log-side singularity is a training hazard rather than a curiosity — is stated as a plausibility argument, not measured. It is tier C, status P. The lab measures the singularity’s existence and its direction (exp’s gradient falls, log’s rises); it does not measure how often training lands near it. A learner who wants that number should instrument their own training run.
Two results here are exact and two are not, and the lesson separates them. H2’s rank, H3’s slerp error, and H4’s determinants are exact statements about floating-point arithmetic on specific inputs — they have no noise, and 2.76e-12 is a zero. H5’s derivative ratio 1999.4 is a finite-difference estimate at π − 0.001, so it is a measurement of the rate at one offset, not a proof of divergence; the divergence is analytic, and the number is a check on it. The lesson quotes the number for the check, not as the result.
No external sources are cited. Rodrigues’ formula, the Hamilton product, and slerp are stated as formulas and then evaluated; the reader can check every one against the lab. Where the lesson attributes a construction to a name, the name identifies the formula, not an authority the argument leans on.