Nyimbi Odero

Series 01 · a numbered run · kept in public

X0.2.3

Chapter: Phase 0 — The Bridge · Module 0.2 — Linear Algebra and Geometry for Robotics

Lesson 0.2.3 — The Exponential Map, Twists, and Screw Theory: From so(3)/se(3) to the Product of Exponentials

Week 3 · Prerequisites: 0.2.1, 0.2.2, 0.1.2.

Prior results used. This lesson uses 0.2.1’s SE(3) composition rule, inverse, and point/vector distinction; 0.2.2’s so(3), Rodrigues’ formula, exp/log on SO(3), and the π-singularity; and 0.1.2’s autograd-over-matmul-chains result. Each is restated at the point of use.

Hands forward. This lesson hands forward one capability: the learner can represent a joint’s motion as a twist, exponentiate a twist to a pose, compose a chain of joints as a product of exponentials, transform a twist between frames with the adjoint, and diagnose the four twist-related silent bugs from their symptoms — the capability that Lesson 1.1.1 (the PoE forward-kinematics lesson) and every Jacobian, velocity, and pose-learning lesson in Phase 1–3 assumes.

Running case. The same 6-DOF MuJoCo arm from 0.2.1–0.2.2: six revolute joints with alternating z/y axes, link lengths L_i = a = 0.2, and the end-effector at the origin of the sixth frame. Its chain FK is T(q) = T_1(q_1)·…·T_6(q_6) with T_i = Rot_{axis_i}(q_i)·Trans_x(a); its product-of-exponentials FK is T(q) = exp(ξ̂₁q₁)·…·exp(ξ̂₆q₆)·M with M = Trans_x(6a), and this lesson’s central result is that the two formulas describe the same arm.


§L.0 — Lesson map, objectives, and misconceptions

§L.0.1 — Lesson map

PartContent
Aso(3) and se(3) as Lie algebras; the bracket; algebra vs. group
BThe exp map on SE(3): the twist, the left Jacobian, and the two forms (unit vs. arbitrary)
CScrew motion: axis, pitch, point on the axis; classification
DThe adjoint representation; body vs. spatial twists; velocities
EFailure 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:

  1. State what a Lie algebra is, distinguish so(3) from SO(3) and se(3) from SE(3), and compute the bracket [ξ₁, ξ₂] for two twists.
  2. Apply the exp map exp : se(3) → SE(3) in both its unit-twist form and its arbitrary-twist form, and compute exp(ξ̂ q) by hand, showing the left Jacobian’s contribution.
  3. State what a screw motion is (axis, pitch, point on the axis), compute the pitch of a given twist, and classify the twist as pure rotation, pure translation, or general screw.
  4. Express the running case’s 6-DOF FK as a product of exponentials, with the home configuration and the six spatial twists, and verify that it matches the chain FK of 0.2.1 at a non-trivial configuration.
  5. Derive the adjoint representation Ad_T = [[R, 0], [t̂R, R]], and transform a twist between frames by hand, showing the t × (Rω) cross-product term.
  6. Distinguish a body twist from a spatial twist, and state the frame convention for each.
  7. Diagnose four silent twist bugs — missing V in exp, body vs. spatial confusion, missing cross-product in Ad, wrong reference point in the twist — from their symptoms.
  8. Produce a written “twist audit” of a kinematic pipeline: each joint’s twist, the frame it is expressed in, the composition order, and the adjoint conversions between frames.

§L.0.3 — Misconception inventory

Wrong model the learner probably holdsWhy it is attractiveWhere displacedThe correct model in one sentence
M1 — “so(3) and SE(3) are the same objects.”They are both “rotations” or “poses,” and the notation looks like a typo.§L.1 (Part A)so(3) is the Lie algebra — a 3-dimensional vector space of skew matrices — and SO(3) is the Lie group — a curved manifold of rotations; the exp map bridges them.
M2 — “exp((ω, v)) is (exp(ω̂), v).”The rotation part’s exp is 0.2.2’s, and the translation looks like it should just be v.§L.2 (Part B)exp(ξ̂) = (exp(ω̂), V(ω)·v) where V is the left Jacobian; the translation is V·v, not v, and the difference is the rotation-translation coupling.
M3 — “A twist is an angular velocity and a linear velocity.”The 6-vector (ω, v) looks like (angular, linear), and the analogy to a rigid body’s velocity is tempting.§L.2 and §L.3A twist’s v is the velocity of a point on the screw axis — equivalently, the velocity of the origin plus the ω × q correction — not the velocity of the origin unless the origin is on the axis.
M4 — “The adjoint is the 4×4 T applied to a 6-vector.”A twist is a 6-vector, a pose is a 4×4, and “transform the twist by the pose” sounds like a matrix-vector product.§L.4 (Part D)The adjoint is a 6×6 matrix Ad_T = [[R, 0], [t̂R, R]], not T; the linear part transforms as R v + t × (Rω), with a cross-product term that T’s multiplication does not have.
M5 — “Body and spatial twists are interchangeable.”Both are 6-vectors, both describe the same joint’s motion, and the notation does not always show the frame.§L.4 (Part D)A body twist is expressed in the joint’s own frame and is constant; a spatial twist is expressed in the base frame and is constant only at the home configuration; the two are related by the adjoint, and mixing them is a silent FK error.

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 seventh lesson; the warm-up draws from 0.2.1 and 0.2.2. 0.2.2 is one step back and is excluded.

Warm-up 1 (from 0.2.1, SE(3) composition). For T₁ = (R₁, t₁) and T₂ = (R₂, t₂), write the composition rule and state which frame each translation is in. Answer: T₁·T₂ = (R₁R₂, R₁t₂ + t₁); t₂ is in T₁’s child frame and must be rotated by R₁ into T₁’s parent frame before adding. How this is used: §L.2’s exp(ξ̂) has the same structure — the rotation’s exp and the translation’s V·v are the analogue of R and Rt₂ + t₁, and the V matrix is what plays the role of the R factor.

Warm-up 2 (from 0.2.1, point vs. vector). A point p transforms as T[p; 1]; a direction v transforms as T[v; 0]. Why does a point’s velocity not transform like a direction? Answer: the velocity of a point is d/dt(Rp + t) = Ṙp + ṫ, which contains both the rotational part ω̂(Rp) and the translational part ṫ — the velocity transforms under the adjoint (Part D), not under T, and its transformation has a cross-product term that a direction’s does not. How this is used: §L.4’s “the adjoint is the velocity’s transformation law, and it is not T” is exactly this observation.

Warm-up 3 (from 0.2.2, the SO(3) exp and log). R = Rz(θ). Write its exp and log representations. Answer: R = exp(θ·ẑ) with ẑ the skew matrix of (0, 0, 1), and log(R) = θ·ẑ. At θ = π, the axis is ambiguous and the log is singular. How this is used: §L.2’s SE(3) exp is the so(3) exp plus the left Jacobian’s translation; the SO(3) singularity at π is inherited by the SE(3) log.

Warm-up 4 (from 0.2.2, the double cover). The quaternion q and −q represent the same rotation. Is the exponential coordinate ω double-covered? Answer: No — exp(ω̂) is injective on ‖ω‖ < π, so each rotation has a unique ω in that ball; the π boundary is where the map fails to be injective (both πu and −πu map to the same half-turn), but the interior is a diffeomorphism. How this is used: §L.4’s “the se(3) exp is a diffeomorphism on a suitable domain” is the SO(3) result extended to SE(3).


Part A — so(3) and se(3) as Lie Algebras

§L.1 — Objects: algebra vs. group, the bracket, and the two spaces

The Lie algebra of a Lie group G is the tangent space at the identity, written 𝔤 = T_e G, a vector space with a bilinear, antisymmetric bracket [·,·] : 𝔤 × 𝔤 → 𝔤.

For SO(3), the Lie algebra is so(3) = {ω̂ ∈ ℝ^{3×3} : ω̂ᵀ = −ω̂} — the 3×3 skew-symmetric matrices, a 3-dimensional vector space with basis ê₁, ê₂, ê₃ corresponding to ω = (ω₁, ω₂, ω₃) via ω̂ = ω₁ê₁ + ω₂ê₂ + ω₃ê₃. The bracket is the matrix commutator [ω̂₁, ω̂₂] = ω̂₁ω̂₂ − ω̂₂ω̂₁, which corresponds to \widehat{ω₁ × ω₂} — the cross product of the underlying vectors.

For SE(3), the Lie algebra is se(3) = {ξ̂ = [[ω̂, v], [0, 0]] : ω̂ ∈ so(3), v ∈ ℝ³} — a 6-dimensional vector space with coordinates ξ = (ω, v), called a twist. The bracket is [(ω₁,v₁), (ω₂,v₂)] = (ω₁×ω₂, ω₁×v₂ − ω₂×v₁).

The key structural facts:

  • The algebra is a vector space (you can add and scale twists); the group is a curved manifold (you cannot add rotations).
  • The bracket measures the group’s non-commutativity: [A, B] is the leading-order failure of exp(A)exp(B) = exp(A+B).
  • The exp map exp : 𝔤 → G is the bridge, a diffeomorphism on a neighborhood of the identity.

§L.1.1 — Result: the algebra is a vector space, the group is curved, and the bracket measures the failure of exp(A+B) = exp(A)exp(B)

Claim. For A, B ∈ 𝔤, exp(A+B) = exp(A)exp(B) if and only if [A, B] = 0. In general, the Baker–Campbell–Hausdorff formula is log(exp(A)exp(B)) = A + B + ½[A,B] + (1/12)[A,[A,B]] − (1/12)[B,[A,B]] + …, and the bracket is the leading-order correction.

Proof sketch. The BCH series is exact, and its terms are all iterated brackets. When [A, B] = 0, all higher terms vanish, so log(exp(A)exp(B)) = A + B. When [A, B] ≠ 0, the ½[A,B] term is the first non-vanishing correction, and it is the measure of the non-commutativity.

Corollary. The group is non-abelian for both SO(3) and SE(3), so a joint’s composition exp(ξ̂₁q₁)exp(ξ̂₂q₂) is not exp(ξ̂₁q₁ + ξ̂₂q₂), and the product of exponentials is a product, not a sum.

Corollary. The bracket’s antisymmetry ([A, A] = 0) and the Jacobi identity ([A,[B,C]] + [B,[C,A]] + [C,[A,B]] = 0) are the Lie algebra’s axioms, and they are the structural properties that make the BCH formula well-defined.

§L.1.2 — Worked-example ladder

WE-L.1.a — Fully worked: the bracket of two so(3) elements, by hand

Input. ω₁ = (1, 0, 0), ω₂ = (0, 1, 0).

Trace.

  1. ω̂₁ = [[0,0,0],[0,0,-1],[0,1,0]], ω̂₂ = [[0,0,1],[0,0,0],[-1,0,0]].
  2. ω̂₁ω̂₂ = [[0,0,0],[1,0,0],[0,0,0]].
  3. ω̂₂ω̂₁ = [[0,1,0],[0,0,0],[0,0,0]].
  4. [ω̂₁, ω̂₂] = [[0,-1,0],[1,0,0],[0,0,0]] = \widehat{(0,0,1)}. So [ω₁, ω₂] = ω₁ × ω₂ = (0, 0, 1).

Output and interpretation. The bracket of the x- and y-rotations is the z-rotation, the cross product of the two axes. The result is non-zero, meaning exp(θ₁ x̂)exp(θ₂ ŷ) ≠ exp(θ₁ x̂ + θ₂ ŷ) — the z-axis is the axis of the commutator’s rotation, about which the two rotations fail to commute.

Self-explanation 1. Before reading on: why is the bracket of two skew matrices itself skew, and what does this tell you about so(3)?

Answer. (ω̂₁ω̂₂ − ω̂₂ω̂₁)ᵀ = ω̂₂ᵀω̂₁ᵀ − ω̂₁ᵀω̂₂ᵀ = (−ω̂₂)(−ω̂₁) − (−ω̂₁)(−ω̂₂) = ω̂₂ω̂₁ − ω̂₁ω̂₂ = −[ω̂₁, ω̂₂], so the bracket is skew. The closure is the defining property of a Lie algebra, and the reason so(3) is 3-dimensional (the bracket of two 3-vectors is a 3-vector) rather than something larger.

WE-L.1.b — Completion problem

Input. ξ₁ = (0, 0, 1, 0, 0, 0) (rotation about z through the origin), ξ₂ = (0, 1, 0, 0, 0, 0) (rotation about y through the origin).

Trace (final 25% as prompts).

  1. ω₁ = (0,0,1), v₁ = 0; ω₂ = (0,1,0), v₂ = 0.
  2. ω₁ × ω₂ = (0,0,1) × (0,1,0) = (0·0 − 1·1, 1·0 − 0·0, 0·1 − 0·0) = (−1, 0, 0). ⟨your turn: verify⟩
  3. ω₁ × v₂ − ω₂ × v₁ = 0 − 0 = 0. ⟨your turn: verify⟩
  4. [ξ₁, ξ₂] = (−1, 0, 0, 0, 0, 0). ⟨your turn: what does the bracket’s linear part being zero mean?⟩

Answers.

  1. (−1, 0, 0) — the −x-axis.
  2. Both cross products vanish, because v₁ = v₂ = 0.
  3. The linear part is zero because both twists are pure rotations about the origin, and their bracket is a pure rotation about the cross product’s axis, also through the origin. The general twist’s bracket can have a non-zero linear part when the linear velocities are non-zero.

The lesson. The bracket’s angular part is the cross product of the angular parts; the linear part is ω₁ × v₂ − ω₂ × v₁, vanishing for pure rotations and non-zero for general screws. The bracket is the only operation beyond vector addition that a Lie algebra has, and its structure is what the exp map “integrates” into the group’s non-commutativity.

WE-L.1.c — Faded: the BCH correction for two so(3) rotations

Setup. A = θ₁ x̂, B = θ₂ ŷ, with θ₁ = θ₂ = 0.1 rad.

Question. What is the first-order BCH correction ½[A, B]? What is exp(A)exp(B) vs. exp(A+B)?

You must produce the intermediate steps. The setup and the answer are given.

Answer. [A, B] = θ₁θ₂ [x̂, ŷ] = 0.01 · ẑ. The first-order BCH correction is ½·0.01·ẑ = 0.005·ẑ, so exp(A)exp(B) ≈ exp(A + B + 0.005·ẑ + O(θ³)), and the difference from exp(A+B) is a 0.005 rad rotation about z. For small angles, this is O(θ²) — small — but it is systematic and grows with the angles.

The lesson. The bracket is the first-order correction to additivity; for small angles it is O(θ²), small enough to be missed, but for chains of many joints it accumulates and the sum-and-exp approximation diverges from the product.

WE-L.1.d — Contrastive near-miss: the “add the twists” FK

Correct solution. T(q) = exp(ξ̂₁q₁)exp(ξ̂₂q₂)…exp(ξ̂₆q₆)·M — a product of exponentials.

Near-miss. T(q) = exp(ξ̂₁q₁ + ξ̂₂q₂ + … + ξ̂₆q₆)·M — a sum in the exponent.

Why the near-miss produces a plausible but incorrect number. The sum’s exp is a legal SE(3) element (a valid rigid motion), and at q = 0 it coincides with the correct product. For small q, the difference is O(q²) (the BCH correction), so the FK looks right. For larger q or longer chains, the BCH corrections accumulate, the end-effector drifts from the correct position, and the policy trained on it is a policy for a different arm. The failure is silent in the small-q test and visible only at larger q or against the chain FK.

The discrepancy, stated. The correct product and the sum differ by exp(½[ξ̂₁q₁, ξ̂₂q₂] + …) (BCH), which is a rotation about the commutator’s axis. The difference vanishes at q = 0 and at [ξ̂_i, ξ̂_j] = 0 (aligned axes), and is non-zero otherwise. The near-miss’s error is exactly the BCH correction, and it is the silent part of the “add the twists” bug.

Self-explanation 2. Before reading on: if all joints’ twists commute, does the sum-and-exp give the correct FK?

Answer. Yes — if all twists commute, the BCH higher-order terms vanish, and exp(sum) = product. The commuting case is the planar arm with all joints about the same axis (all z-rotations). The running case’s alternating z/y axes do not commute (WE-L.1.a’s bracket is non-zero), so the sum-and-exp is wrong for the running case.

§L.1.3 — Comparative table: the two spaces and the two operations

Object or operationSetStructureAdditionCompositionDimensionCode artifact
so(3)3×3 skew-symmetric matricesvector space with a bracketLegal — the sum is skew[·,·], closed3skew(ω)
SO(3)rotation matricescurved groupIllegal — R₁ + R₂ ∉ SO(3)matrix product, closed3exp_so3(ω)
se(3)4×4 blocks [[ω̂, v], [0, 0]]vector space with a bracketLegal — the sum is in se(3)[·,·], closed6xi = (ω, v)
SE(3)homogeneous matrices [[R, t], [0, 1]]curved groupIllegal — the sum is not a posematrix product, closed6exp_se3(xi, q)
exp𝔤 → Gdiffeomorphism near the identity—converts a sum into a product only when [A, B] = 0—the bridge

Design principle (P7.4). Add in the algebra, multiply in the group, and never let a sum reach the group without an exp. The sum ξ̂₁q₁ + … + ξ̂₆q₆ is a legal element of se(3), so its exponential is a legal pose — which is exactly why the sum-and-exp FK compiles, runs, and returns something plausible. The legality of the intermediate is not evidence about the result: the FK is the product of the exponentials, and the sum is its O(q²) approximation, exact only when the brackets vanish.

§L.1.4 — Concept check

Concept check (displaces M1). A learner says: “so(3) and SE(3) are the same kind of object — both are ‘rotations’ written as matrices, so the exp map is a change of notation.” Which structural facts has the learner failed to model, and what bug class follows?

Answer. Three. The dimension (3 against 6: a twist carries a translation that a rotation matrix has nowhere to put), the kind (so(3) is a vector space of skew matrices, SE(3) is a curved group of poses), and the direction of the bridge (exp is a diffeomorphism only near the identity, so it does not transport addition). The bug class is sum-for-product: the code builds exp(ξ̂₁q₁ + … + ξ̂₆q₆)·M, which is a legal pose, agrees with the correct product at q = 0, and drifts as O(q²) thereafter. Nothing raises, because the sum lives in the algebra where addition is defined, and the exponentiation conceals the substitution.

§L.1.5 — Consolidation checkpoint 1

(a) You should now be able to: (i) state the tangent-space definition of a Lie algebra and the two structures it carries (addition and bracket); (ii) state which operations are legal in so(3) and se(3) and which are not in SO(3) and SE(3), with the dimension of each; (iii) state the BCH expansion’s first term and the exact condition under which exp(A+B) = exp(A)exp(B).

(b) Transfer probe. A colleague averages two measured poses as T = (T₁ + T₂)/2 and uses the result as a calibration. Does it live in SE(3)? Answer sketch: No. The group is not closed under addition: the average of two rotation blocks has RᵀR ≠ I and det R ≠ 1 in general, and the bottom row is no longer (0, 0, 0, 1). The code runs because nothing checks the group axioms, and the matrix is a point in the ambient ℝ^{3×3}, not a rotation. The group-correct midpoint is the geodesic exp(½ log(R₂R₁ᵀ))R₁ from 0.2.2; the algebra-correct mean of two twists is (ξ₁ + ξ₂)/2 before a single exp. The learner who averages matrices has done the algebra’s operation in the group’s space.

(c) Remediation rule. If you cannot do (a)(ii) or (b), return to §L.1.2 and redo WE-L.1.d, then re-attempt (b) by asking which of the two spaces the expression (T₁ + T₂)/2 is an element of.


Part B — The Exponential Map on SE(3)

§L.2 — Objects: the twist, the left Jacobian, and the two forms

A twist is an element of se(3), written ξ = (ω, v) ∈ ℝ⁶, with ω the angular part and v the linear part. Its se(3) matrix is ξ̂ = [[ω̂, v], [0, 0]].

The exponential map exp : se(3) → SE(3) has two forms, which are the same formula viewed from different normalizations:

  • Unit-twist form — for a twist ξ = (ω, v) with ‖ω‖ = 1 and a joint angle q:
exp⁡(ξ^q)=[exp⁡(qω^)(qI+(1−cos⁡q)ω^+(q−sin⁡q)ω^2) v01]\exp(\hat{\xi} q) = \begin{bmatrix} \exp(q\hat{\omega}) & (q I + (1 - \cos q)\hat{\omega} + (q - \sin q)\hat{\omega}^2)\, v \\ 0 & 1 \end{bmatrix}
  • Arbitrary-twist form — for a twist ξ = (ω, v) with θ = ‖ω‖ (the angle baked into the twist’s magnitude):
exp⁡(ξ^)=[exp⁡(ω^)J(ω) v01],J(ω)=I+1−cos⁡θθ2ω^+θ−sin⁡θθ3ω^2\exp(\hat{\xi}) = \begin{bmatrix} \exp(\hat{\omega}) & J(\omega)\, v \\ 0 & 1 \end{bmatrix}, \quad J(\omega) = I + \frac{1 - \cos\theta}{\theta^2}\hat{\omega} + \frac{\theta - \sin\theta}{\theta^3}\hat{\omega}^2

The two agree when ξ is the unit twist scaled by q, i.e., when ξ_arbitrary = q · ξ_unit (so that θ = q and ω̂ = q ω̂_unit). The unit-twist form is the right one for the product of exponentials (the joint’s twist is fixed, the angle varies); the arbitrary-twist form is the right one for learning (the network outputs a twist whose magnitude is the angle).

The matrix J(ω) (also called V(ω)) is the left Jacobian of SE(3), and it is the only correction to the naive “translation is v” formula. Its inverse is J(ω)^{-1} = I − ½ω̂ + (1/θ² − (1+cos θ)/(2θ sin θ))ω̂², used by the log map.

The logarithm log : SE(3) → se(3) is the inverse: for T = (R, t), log(T) = (ω, v) with ω = log_{SO(3)}(R) and v = J(ω)^{-1} t.

The special cases:

  • Pure rotation (v = 0): exp(ξ̂) = (exp(ω̂), 0) — the left Jacobian is irrelevant.
  • Pure translation (ω = 0): exp(ξ̂) = (I, v) — J = I, and the translation is exactly v.
  • General screw (ω ≠ 0, v ≠ 0): the translation is J·v ≠ v, and the coupling is visible.

§L.2.1 — Result: the exp map’s translation is J·v, not v, and the difference is the rotation–translation coupling

Claim. For ξ = (ω, v) with ω ≠ 0 and θ = ‖ω‖, exp(ξ̂) = (exp(ω̂), J(ω)v) with J the left Jacobian. The translation J·v equals v when ω = 0 (so J = I), when v = 0 (J·v = 0 = v), or when v lies along the rotation axis ω — the axial component is a fixed point of J, since J = I + a·\hat{u} + b·\hat{u}² acts as the identity on \hat{u} for any coefficients a, b. Outside those cases J ≠ I and J·v ≠ v.

Proof sketch. The matrix exponential exp(ξ̂) = Σ ξ̂ⁿ/n! splits into the top-left block (giving exp(ω̂)) and the top-right block Σ ω̂ⁿ⁻¹/n! · v = J(ω)·v. For ω = 0, all ω̂ terms vanish and J = I. For v = 0, J·v = 0. In the general case J ≠ I; but J is a polynomial in ω̂ with no ω̂⁰ term beyond the identity, so its action on ω̂’s own axis is the identity, and v ∥ ω is a third equality case that the “J ≠ I therefore J·v ≠ v” shorthand misses. The lab’s WE-L.2.a step 7 is exactly this case: ω = (0,0,0.7), v = (0,0,2), and J·v = (0,0,2) even though J ≠ I.

Corollary. J is the SE(3) analogue of the SO(3) exp’s sin θ / θ factor — it is the correction that accounts for the fact that the rotation and translation do not commute. In SO(3), the “spherical linear interpolation” correction is sin θ/θ; in SE(3), the correction is the J matrix. The correction is invisible on the axis and maximal perpendicular to it, which is the same geometry as the t − Rᵀt error in 0.2.1.

Corollary. J’s inverse is what the log map uses: v = J(ω)^{-1} t is the only way to recover the twist’s linear part from the pose’s translation. The inverse has the same 1/θ structure and the same π-singularity as the SO(3) log, so the SE(3) log is singular at ‖ω‖ = π.

§L.2.2 — Worked-example ladder

WE-L.2.a — Fully worked: exp of a pure-rotation twist and a general twist

Input (pure rotation). ξ = (0, 0, 1, 0, 0, 0), q = π/2. Input (general twist). ξ = (0, 0, 1, 0, 0, 1), q = π/2.

Trace (pure rotation).

  1. ω = (0,0,1), v = (0,0,0), q = π/2.
  2. exp(q ω̂) = Rz(π/2) = [[0,-1,0],[1,0,0],[0,0,1]] (from 0.2.2).
  3. t = (q I + (1 − cos q)ẑ + (q − sin q)ẑ²) · 0 = 0.
  4. exp(ξ̂ q) = [[Rz(π/2), 0], [0, 1]] — a pure rotation.

Trace (general twist).

  1. ω = (0,0,1), v = (0,0,1), q = π/2. The v is parallel to ω.
  2. ẑ·v = (0,0,1) × (0,0,1) = 0, ẑ²·v = 0.
  3. t = (π/2 I + 1·0 + (π/2 − 1)·0) · (0,0,1) = (π/2)(0,0,1) = (0, 0, π/2).
  4. exp(ξ̂ q) = [[Rz(π/2), (0,0,π/2)], [0, 1]].

Output and interpretation. The pure rotation’s exp has zero translation; the general twist’s exp has translation (0, 0, π/2). The translation is q·v (not v), because v is parallel to ω (so ẑ·v = 0 and the J matrix reduces to q I on the ω-parallel part of v). The translation is the screw’s pitch times the angle — π/2 units of translation for π/2 radians of rotation, giving a pitch of 1 per radian.

Self-explanation 3. Before reading on: in the general twist, v is parallel to ω, so t = q·v. What would t be if v = (1, 0, 0) (perpendicular to ω)?

Answer. ẑ·(1,0,0) = (0,1,0), ẑ²·(1,0,0) = (-1,0,0), so t = (π/2)(1,0,0) + (1)(0,1,0) + (π/2 − 1)(-1,0,0) = (π/2, 0, 0) + (0, 1, 0) + (1 − π/2, 0, 0) = (1, 1, 0). The translation (1, 1, 0) is not (π/2)(1,0,0) = (π/2, 0, 0) — the J matrix rotates the perpendicular v and adds a component along y, the effect of the rotation on the translation.

Self-explanation 4. Before reading on: what is the screw interpretation of the general twist’s pose?

Answer. A rotation about z by π/2 plus a translation along z of π/2 — a screw about the z-axis with pitch 1 (per radian), and the axis is the z-axis through the origin. The twist’s v has a component along ω (the pitch contribution) and a component perpendicular to ω (the “moment,” which encodes the axis’s distance from the origin).

WE-L.2.b — Completion problem

Input. ξ = (0, 0, 1, 1, 0, 0), q = π/2. The v is perpendicular to ω.

Trace (final 25% as prompts).

  1. ω = (0,0,1), v = (1,0,0), q = π/2.
  2. ẑ·v = (0,1,0), ẑ²·v = (-1,0,0). ⟨your turn: verify both cross products⟩
  3. t = (π/2 I + 1·ẑ + (π/2 − 1)·ẑ²) · (1,0,0). ⟨your turn: compute each term and sum⟩
  4. Write the 4×4 exp(ξ̂ q). ⟨your turn⟩

Answers.

  1. ẑ·(1,0,0) = (0,0,1) × (1,0,0) = (0·0 − 1·0, 1·1 − 0·0, 0·0 − 0·1) = (0, 1, 0). ẑ²·(1,0,0) = ẑ·(0,1,0) = (0,0,1) × (0,1,0) = (0·0 − 1·1, 1·0 − 0·0, 0·1 − 0·0) = (-1, 0, 0). ✓
  2. (π/2)(1,0,0) = (π/2, 0, 0); 1·(0,1,0) = (0, 1, 0); (π/2 − 1)(-1,0,0) = (1 − π/2, 0, 0). Sum: (π/2 + 1 − π/2, 1, 0) = (1, 1, 0).
  3. exp(ξ̂ π/2) = [[Rz(π/2), (1, 1, 0)], [0, 1]].

The lesson. The J matrix’s effect is visible when v ⊥ ω: the translation (1, 1, 0) is not q·v = (π/2, 0, 0), and the difference is the J matrix’s rotation of v. The correct translation requires the J matrix; the naive q·v is wrong by (J − qI)v.

WE-L.2.c — Faded: the running case’s joint 2 twist

Setup. Joint 2 is a rotation about y at the point (a, 0, 0) at the home configuration, with a = 0.2. Its spatial twist is ξ₂ = (0, 1, 0, 0, 0, a) — angular ω = (0,1,0) and linear v = (0,0,a) (from v = −ω × q = −(0,1,0) × (0.2,0,0) = (0,0,0.2)).

Question. Compute exp(ξ̂₂ q₂) at q₂ = π/2 by hand, and verify it matches the chain FK’s T₁(0)·T₂(π/2) = Trans_x(a)·Rot_y(π/2)·Trans_x(a).

You must produce the intermediate steps. The setup and the answer are given.

Answer. ω = (0,1,0), v = (0,0,a), q = π/2.

  • exp(q ω̂) = Ry(π/2) = [[0,0,1],[0,1,0],[-1,0,0]].
  • ŷ·v with v = (0,0,a): ŷ = [[0,0,1],[0,0,0],[-1,0,0]], so ŷ·(0,0,a) = (a, 0, 0); and ŷ² = [[-1,0,0],[0,0,0],[0,0,-1]], so ŷ²·(0,0,a) = (0, 0, -a).
  • t = (π/2)(0,0,a) + (1)(a,0,0) + (π/2 − 1)(0,0,-a) = (0,0,πa/2) + (a,0,0) + (0,0,-a(π/2 − 1)) = (a, 0, πa/2 − a(π/2 − 1)) = (a, 0, a).
  • exp(ξ̂₂ π/2) = [[Ry(π/2), (a, 0, a)], [0, 1]].

Chain FK: T₁(0)·T₂(π/2)·T₃(0)·…·T₆(0) = Trans_x(a)·Ry(π/2)·Trans_x(5a). Translation: (a,0,0) + Ry(π/2)·(5a,0,0) = (a,0,0) + (0,0,-5a) = (a, 0, -5a). PoE: exp(ξ̂₂ π/2)·M = [[Ry(π/2), (a,0,a)], [0,1]]·[[I, (6a,0,0)], [0,1]] = [[Ry(π/2), Ry(π/2)(6a,0,0) + (a,0,a)], [0,1]] = [[Ry(π/2), (0,0,-6a) + (a,0,a)], [0,1]] = [[Ry(π/2), (a, 0, -5a)], [0,1]]. ✓ Matches.

The lesson. The PoE’s unit-twist exp is not (exp(qω̂), q·v); the translation is (q I + (1−cos q)ω̂ + (q−sin q)ω̂²)v, and the (1−cos q) and (q−sin q) terms are what make the PoE match the chain FK. The near-miss that uses q·v gives (0,0,πa/2), which is wrong by (a, 0, a − πa/2).

WE-L.2.d — Contrastive near-miss: the exp without the left Jacobian

Correct solution. exp(ξ̂ q) = [[exp(q ω̂), (q I + (1−cos q)ω̂ + (q−sin q)ω̂²) v], [0, 1]].

Near-miss. exp(ξ̂ q) = [[exp(q ω̂), q·v], [0, 1]] — the translation is q·v (the “linear part times the angle”).

Why the near-miss produces a plausible but incorrect number. At v = 0 (pure rotation), q·v = 0, so the near-miss agrees. At v ∥ ω (pitch-only screw), J·v = q·v (the (1−cos) and (q−sin) terms vanish because ω̂·v = 0), so the near-miss agrees. At v ⊥ ω (the case in WE-L.2.b), J·v ≠ q·v, and the near-miss’s translation is wrong by (J − qI)v. The failure is silent for the pitch-only and pure-rotation cases, and visible for the perpendicular case — which is the common case in a robot arm (a joint’s link is perpendicular to the joint’s axis).

The discrepancy, stated. q·v vs. (q I + (1−cos q)ω̂ + (q−sin q)ω̂²)v differ by ((1−cos q)ω̂ + (q−sin q)ω̂²)v, which is the J matrix’s correction to the linear translation. The correction vanishes for ω̂·v = 0 (the pitch-only case) and is non-zero otherwise. For the running case’s joints, the links are perpendicular to the axes, so the correction is always non-zero, and the near-miss is always wrong.

Self-explanation 5. Before reading on: in the near-miss, if v is perpendicular to ω, what is the direction of the correction (J − qI)v?

Answer. ω̂·v = ω × v is perpendicular to both ω and v (the cross product’s direction); ω̂²·v = ω × (ω × v) = ω(ω·v) − v(ω·ω) = −v when v ⊥ ω and ‖ω‖ = 1. So (J − qI)v = (1−cos q)(ω × v) + (q−sin q)(−v) = (1−cos q)(ω × v) − (q−sin q)v. The correction has two components: one along ω × v (perpendicular to both ω and v) and one along −v (opposite to v). The first component is the “rotation of the velocity” effect; the second is the “shortening” effect — the velocity’s v component is partially converted to rotation, so the net translation along v is less than q·v.

§L.2.3 — Comparative table: the forms of the SE(3) exponential

FormTranslation blockValid whenWhere it is the right choiceFailure mode if misapplied
Unit-twist, exp(ξ̂ q) with ‖ω‖ = 1(q I + (1−cos q)ω̂ + (q−sin q)ω̂²)vThe twist is normalized and the angle is the joint’s qProduct of exponentials: the twist is fixed, q variesA non-unit twist silently rescales the angle and the pitch
Arbitrary-twist, exp(ξ̂) with θ = ‖ω‖J(ω)v, J = I + (1−cos θ)/θ² ω̂ + (θ−sin θ)/θ³ ω̂²The angle is baked into the twist’s magnitudeLearning: the network emits the twistWith a unit twist the angle is lost — the pose is always θ = 1
Naive linear, (exp(ω̂), q·v)q·vv = 0, or v ∥ ωNever as a general formulaWrong by (J − qI)v whenever v has a component off the axis
Pure rotation (v = 0)0v = 0A revolute joint through the originJ is irrelevant, so a missing-J bug hides here
Pure translation (ω = 0)vω = 0A prismatic jointJ = I, so a missing-J bug hides here too
Log, v = J(ω)⁻¹ tinverse map‖ω‖ < πRecovering a twist from a poseUsing v = t is off by (J⁻¹ − I)t and inherits the π singularity

Design principle (P7.5). Fix the normalization before you fix the formula. The unit-twist and arbitrary-twist forms are the same series under a rescaling of ξ; the only thing that distinguishes them is where the angle lives. Decide that first — a joint’s angle belongs in the exponent, a learned pose’s angle belongs in the twist’s magnitude — and the formula follows. A pipeline that mixes the two is not wrong in its algebra, it is wrong in its bookkeeping: the same twist read as arbitrary when it was unit produces a pose whose angle is 1 radian.

§L.2.4 — Concept check

Concept check (displaces M2). A learner says: “exp is applied blockwise, so exp((ω, v)) is (exp(ω̂), v) — the rotation part exponentiates and the translation part is carried through.” What is the missing object, and what bug class does the omission produce?

Answer. The missing object is the left Jacobian J(ω): the translation block of the matrix exponential is Σ ω̂ⁿ⁻¹/n! · v = J(ω)v, not v, because the ω̂ powers in the series act on v. exp is not a blockwise map — the off-diagonal block of ξ̂ couples the two halves. The bug class is a translation that is right whenever the screw is degenerate: it is exact for a pure rotation (v = 0), for a pure translation (ω = 0), and for a pitch-only screw (v ∥ ω), so every unit test built on those three cases passes. It is wrong by (J − qI)v for any link perpendicular to its joint’s axis, which in a serial arm is every link.

§L.2.5 — Consolidation checkpoint 2

(a) You should now be able to: (i) write both forms of exp_se3 and state the normalization each assumes; (ii) state the three cases in which J·v = v and why each one is degenerate; (iii) state the log’s formula v = J(ω)⁻¹t and the domain on which it is defined.

(b) Transfer probe. A network outputs the arbitrary-form twist ξ = (0, 0, 2, 1, 0, 0), so θ = 2 rad about z with a linear part perpendicular to the axis. What translation does exp(ξ̂) produce, and what does the naive v predict? Answer sketch: J·v = (0.4546, 0.7081, 0), of magnitude 0.8415, against the naive v = (1, 0, 0). The naive answer is wrong in direction as well as magnitude: J rotates the perpendicular component into y and shortens the x component, which is the same (1−cos θ)ω̂ + (θ−sin θ)ω̂² correction that WE-L.2.b exhibits at q = π/2. A learner who reports (1, 0, 0) has applied the blockwise exp; one who reports only the magnitude 0.8415 has found the correction but not its direction.

(c) Remediation rule. If you cannot do (a)(ii) or (b), return to §L.2.2 and redo WE-L.2.b, then re-attempt (b) by expanding (I + aω̂ + bω̂²)v term by term with ω̂v and ω̂²v written out.


Part C — Screw Motion: Axis, Pitch, and Classification

§L.3 — Objects: the screw parameters and the classification

A screw motion is a rigid motion that can be described as a rotation about an axis coupled with a translation along that axis. The four screw parameters are:

  1. Axis direction u ∈ ℝ³, ‖u‖ = 1 — the direction of the screw’s axis. If ω ≠ 0, u = ω/‖ω‖; if ω = 0, the “axis” is the direction of v (a pure translation), u = v/‖v‖.
  2. Point on the axis q ∈ ℝ³ — any point on the axis, computed as q = (ω × v)/‖ω‖² (for ω ≠ 0), which is the point on the axis closest to the origin.
  3. Pitch h ∈ ℝ — the translation per radian along the axis, h = (ω · v)/‖ω‖² (for ω ≠ 0); for a pure translation (ω = 0), the pitch is infinite (or, by convention, h = ∞).
  4. Angle θ = ‖ω‖ — the rotation’s magnitude (for a pure translation, θ = ‖v‖ is the translation’s magnitude, and the “rotation” is 0).

The classification of a twist:

  • Pure rotation — ω ≠ 0, v = 0 (or ω · v = 0 and q such that v = −ω × q): the pitch is 0, and the motion is a pure rotation about the axis through q.
  • Pure translation — ω = 0, v ≠ 0: the “axis” is in the direction of v, and the motion is a pure translation.
  • General screw — ω ≠ 0, v ≠ 0, ω · v ≠ 0: the motion is a rotation about an axis plus a translation along it, with a finite non-zero pitch.

The screw’s exp is the same SE(3) exp: exp(ξ̂) with ξ being the twist’s coordinates.

§L.3.1 — Result: the pitch and the point on the axis are the two numbers that classify a twist, and the pure cases are degenerate screws

Claim. Every twist ξ = (ω, v) ∈ se(3) is either a pure rotation (ω ≠ 0, v = 0), a pure translation (ω = 0), or a general screw with pitch h = (ω·v)/‖ω‖² and axis through q = (ω × v)/‖ω‖². The three cases are exhaustive and mutually exclusive (except at ξ = 0).

Proof sketch. If ω = 0, the twist is a translation. If ω ≠ 0 and v = 0, the twist is a pure rotation. If ω ≠ 0 and v ≠ 0, decompose v into its component along ω (which contributes a pitch h = (ω·v)/‖ω‖²) and its component perpendicular to ω (which, combined with ω, defines the axis’s point q by v_⊥ = −ω × q). The decomposition is unique, and the three cases correspond to which components are zero.

Corollary. The screw parameters (u, q, h, θ) are a minimal (4-number) description of a twist? No — a twist has 6 numbers, and the screw parameters (u, q, h, θ) have u (2 DOF, since ‖u‖ = 1), q (2 DOF, since q is constrained to the plane perpendicular to u), h (1 DOF), and θ (1 DOF) = 6 numbers. So the screw parameters are equivalent to the twist, but redundant in the sense that the axis point q has a free choice along the axis (which does not change the motion).

Corollary. The pitch is the only screw parameter that distinguishes a general screw from a pure rotation; a pure rotation has pitch 0, a pure translation has infinite pitch, and a general screw has a finite non-zero pitch. The point on the axis q is the “moment arm” of the twist — it is where the axis is, and it is what changes when the reference frame changes (the pitch and the axis direction are frame-invariant, but the point q is not).

§L.3.2 — Worked-example ladder

WE-L.3.a — Fully worked: classifying a twist, by hand

Input. ξ = (0, 0, 1, 2, 3, 4).

Trace.

  1. ω = (0,0,1), v = (2,3,4), ‖ω‖ = 1, ‖v‖ = √29 ≈ 5.39.
  2. ω·v = 0·2 + 0·3 + 1·4 = 4. So the pitch is h = 4/1² = 4 (translation of 4 per radian along z).
  3. ω × v = (0·4 − 1·3, 1·2 − 0·4, 0·3 − 0·2) = (-3, 2, 0). q = (ω × v)/‖ω‖² = (-3, 2, 0).
  4. So the screw is: axis along z through q = (-3, 2, 0), pitch 4, angle θ = 1.

Output and interpretation. The twist is a general screw — rotation about z through (-3, 2, 0) with a pitch of 4 (translating 4 units along z per radian). The point q is the moment arm: the component of v perpendicular to ω is (2, 3, 0), and −ω × q = −(0,0,1) × (-3, 2, 0) = −(0·0 − 1·2, 1·(-3) − 0·0, 0·2 − 0·(-3)) = −(-2, -3, 0) = (2, 3, 0). So v_⊥ = (2, 3, 0) = −ω × q, which confirms q is correct.

Self-explanation 6. Before reading on: what is the physical motion of a point at the origin under this twist over a small time dt?

Answer. The point at the origin has velocity v = (2, 3, 4). Over dt, it moves by (2dt, 3dt, 4dt) — the z-component is the pitch (translation along the axis), and the x- and y-components are the rotation’s effect at the origin (the origin is off-axis, at distance ‖q‖ = √13 ≈ 3.61 from the axis, so the rotation about the axis sweeps the origin in the (2, 3, 0) direction). The v’s direction is (2, 3, 4)/5.39 ≈ (0.37, 0.56, 0.74), which is not along the axis (0, 0, 1) — it’s a combination of the pitch and the rotation’s contribution.

WE-L.3.b — Completion problem

Input. ξ = (0, 0, 0, 1, 2, 3) (pure translation).

Trace (final 25% as prompts).

  1. ω = (0,0,0), v = (1,2,3), ‖v‖ = √14. ⟨your turn: is this a pure translation?⟩
  2. The “axis” is the direction of v. ⟨your turn: what is the pitch? Is it finite, infinite, or undefined?⟩
  3. The angle θ = ‖v‖ (for the “translation magnitude”). ⟨your turn: what is the point q?⟩

Answers.

  1. Yes — ω = 0, so the twist is a pure translation.
  2. The pitch is infinite (or “undefined” by convention): the motion has translation with no rotation, so the “translation per radian” is ‖v‖/0 = ∞.
  3. The point q is undefined — the axis of a pure translation is not a line but a direction (any line in the direction of v has the same screw motion). The “point on the axis” convention does not apply.

The lesson. A pure translation is the degenerate screw — it has an axis direction but no position, and its pitch is infinite. The screw parameters’ q and h are undefined (or infinite) in this case, and the twist’s classification is determined by the zero angular part, not by the formula’s limits.

WE-L.3.c — Faded: the running case’s joint 1 and joint 2 screws

Setup. Joint 1’s twist is ξ₁ = (0, 0, 1, 0, 0, 0); joint 2’s is ξ₂ = (0, 1, 0, 0, 0, a) with a = 0.2.

Question. Classify each twist (pure rotation, pure translation, or general screw), and compute each screw’s pitch.

You must produce the intermediate steps. The setup and the answer are given.

Answer. Joint 1: ω = (0,0,1), v = 0, so it is a pure rotation with pitch h = 0 (no translation along the axis). Joint 2: ω = (0,1,0), v = (0,0,0.2), ω·v = 0, so it is also a pure rotation with pitch h = 0 — the v is perpendicular to ω, so the v encodes the axis’s position (the axis is at q = (a, 0, 0)) but not a pitch.

The lesson. Both of the running case’s joints are pure rotations with zero pitch, because the link’s translation is perpendicular to the joint’s axis (the link is along x, the axis is z or y, so the cross product ω × v is the moment arm, not a pitch). The v’s role is to encode the position of the axis, not a pitch — the pitch is zero, and the v’s perpendicular component is what makes the twist a “rotation about an off-origin axis.”

WE-L.3.d — Contrastive near-miss: the wrong reference point

Correct solution. Joint 2’s twist at q_2 = 0 is ξ₂ = (0, 1, 0, 0, 0, a) — the axis is at (a, 0, 0) at the home configuration.

Near-miss. The learner uses ξ₂ = (0, 1, 0, 0, 0, 0) — the axis is at the origin, not at (a, 0, 0).

Why the near-miss produces a plausible but incorrect number. At q = 0, both twists give exp(0) = I, so the FK at q = 0 is the same. For q_2 ≠ 0, the near-miss’s twist is a rotation about y through the origin, which moves the end-effector to a different point from the correct twist (which is a rotation about y through (a, 0, 0)). The difference is (R_y(q_2) − I)(a, 0, 0) — the rotation’s effect on the moment arm — which is non-zero for q_2 ≠ 0 and grows with q_2. The failure is silent in the q = 0 test and visible in the q ≠ 0 test against the chain FK.

The discrepancy, stated. The correct twist has v = (0, 0, a) and the near-miss has v = 0. The difference is the moment arm −ω × q = (0, 0, a), which is what makes the rotation about (a, 0, 0) different from the rotation about the origin. The near-miss’s FK is the FK of a different arm — one whose joints are all at the origin — and the end-effector position differs by (R_y(q_2) − I)(a, 0, 0).

Self-explanation 7. Before reading on: in the near-miss, if the arm’s link lengths were zero (a = 0), would the near-miss match the correct twist?

Answer. Yes — if a = 0, all the joints are at the origin (the arm is a “ball joint” with no links), and the correct twist is ξ_i = (ω_i, 0), which is the near-miss’s twist. The a is the link length, and it is what makes the moment arm non-zero. The near-miss is the FK of the zero-link arm, and the correct twist is the FK of the a-link arm.

§L.3.3 — Comparative table: the screw parameters and their degenerate cases

ParameterFormula (for ω ≠ 0)DOFValue for a pure rotationValue for a pure translationWhat breaks if it is read as a velocity
Axis direction uω/‖ω‖2Definedv/‖v‖ (a direction, not a line)It is the rotation axis, not the direction of travel
Point on the axis q(ω × v)/‖ω‖²2The point nearest the origin on the axisUndefined — any line along v servesReading q as the point that moves along v puts the axis in the wrong place
Pitch h(ω · v)/‖ω‖²10∞ (‖v‖/0)It is translation per radian, not a speed
Angle θ‖ω‖1The joint’s q‖v‖ by conventionIt is a displacement, not an angular rate
Classificationwhich of ω, v, ω·v vanish—v = 0ω = 0A general screw misread as a rotation loses its pitch

Design principle (P7.6). Classify the twist before you compute anything from it. The formulas for q and h divide by ‖ω‖², so they are defined on exactly the complement of the pure-translation case, and the “infinite pitch” of a prismatic joint is not a large number — it is the formula’s denominator being zero. Read the classification off the zeros first, then apply the formulas to the case that admits them. A pipeline that computes h unconditionally returns nan or an enormous number for a prismatic joint, and either value propagates into a controller as if it were a pitch.

§L.3.4 — Concept check

Concept check (displaces M3). A learner says: “A twist is (ω, v) — the angular velocity and the linear velocity — so v is how fast the body’s origin is moving.” Which model is missing, and what bug class does it produce?

Answer. The missing model is that v is the velocity of a point on the screw axis, which equals the origin’s velocity only when the origin lies on that axis. For a joint at q ≠ 0, the origin’s velocity is v + ω × (0 − q) = v − ω × q, and the two differ by the moment arm. The bug class is a twist built from the wrong point: the learner writes ξ = (ω, 0) for a joint whose axis is at (a, 0, 0), the FK is exact at q = 0 because exp(0) = I for any twist, and it diverges from the chain FK for every q ≠ 0 by (R(q) − I)(a, 0, 0). Nothing raises: (ω, 0) is a perfectly legal twist, and it is the correct twist of a different arm whose joints all sit at the origin.

§L.3.5 — Consolidation checkpoint 3

(a) You should now be able to: (i) compute the four screw parameters from a twist and state each one’s degree of freedom; (ii) classify a twist as pure rotation, pure translation, or general screw from its zeros, and state which formulas are undefined in each degenerate case; (iii) state why the pitch and the axis direction survive a change of frame while the point q does not.

(b) Transfer probe. Take joint 1’s twist ξ₁ = (0, 0, 1, 0, 0, 0) — a pure rotation about z through the origin — and move the frame by t = (2, 0, 0). What are the new screw parameters, and is the screw’s class unchanged? Answer sketch: Ad_T ξ₁ = (0, 0, 1, 0, −2, 0), since R = I, Rv = 0 and t × (Rω) = (2,0,0) × (0,0,1) = (0,−2,0). The pitch is ω·v/‖ω‖² = 0 — unchanged — and q = (ω × v)/‖ω‖² = (2, 0, 0), which is exactly the translation t. The class is unchanged: still a pure rotation, now about the axis through (2, 0, 0). The pitch and the axis direction are frame-invariant; q moved with the frame, which is the sense in which q is a moment arm rather than a property of the motion.

(c) Remediation rule. If you cannot do (b), return to §L.3.2 and redo WE-L.3.d, then re-attempt (b) by asking which of the four parameters the adjoint is allowed to change.


Part D — The Adjoint Representation and Velocity Transformations

§L.4 — Objects: Ad_T, the body/spatial distinction, and velocity transformation

The adjoint representation of SE(3) is a map Ad : SE(3) → GL(6), defined for T = (R, t) by:

AdT=[R0t^RR]Ad_T = \begin{bmatrix} R & 0 \\ \hat{t} R & R \end{bmatrix}

a 6×6 matrix. Its action on a twist ξ = (ω, v) is:

AdT[ωv]=[Rωt^Rω+Rv]=[Rωt×(Rω)+Rv]Ad_T \begin{bmatrix} ω \\ v \end{bmatrix} = \begin{bmatrix} Rω \\ \hat{t}Rω + Rv \end{bmatrix} = \begin{bmatrix} Rω \\ t × (Rω) + Rv \end{bmatrix}

The angular part transforms as a direction (Rω); the linear part transforms as a velocity (Rv + t × (Rω)) — the Rv is the direction part, and the t × (Rω) is the cross-product term from the frame’s translation.

The body vs. spatial distinction:

  • A spatial twist $ξ_s$ is expressed in the base frame, and it is Ad_T · $ξ_b$ where T is the pose of the body frame relative to the base.
  • A body twist $ξ_b$ is expressed in the body’s own frame, and it is constant for a given joint.
  • The two are related by $ξ_s$ = Ad_T $ξ_b$ and $ξ_b$ = Ad_{T^{-1}} $ξ_s$.

For the running case, the body twists are constant (each joint’s axis in its own frame), and the spatial twists are $Ad_{T_i(0)}$ $ξ_b$,i where $T_i(0$) is the joint’s home pose — which is why the spatial twists depend on the home configuration’s $q_i$ and the body twists do not.

§L.4.1 — Result: the adjoint is the velocity’s transformation law, and it is not T

Claim. For T = (R, t) ∈ SE(3) and ξ = (ω, v) ∈ se(3), Ad_T ξ = (Rω, t × (Rω) + Rv), and this is not the same as T · [ω; v] (which is not a legal operation, because T is 4×4 and [ω; v] is 6×1). The adjoint’s linear part has a cross-product term t × (Rω) that a simple T-multiplication does not have.

Proof sketch. The adjoint is the derivative of the conjugation map T ↦ T T' T^{-1} at the identity, which is Ad_T ξ̂ = T ξ̂ T^{-1} for ξ̂ ∈ se(3). Computing T ξ̂ T^{-1} with T = [[R, t], [0, 1]] and ξ̂ = [[ω̂, v], [0, 0]] gives [[Rω̂R^{-1}, Rv − Rω̂R^{-1}t], [0, 0]] = [[\widehat{Rω}, Rv − (Rω) × t], [0, 0]], which is Ad_T ξ = (Rω, Rv + t × (Rω)). The cross-product term is the t × (Rω).

Corollary. The adjoint is the velocity’s transformation law, not the pose’s — it is what transforms a twist (a velocity) between frames, and it is different from T’s action on a point or a direction. The cross-product term is the “moment arm” of the translation on the angular velocity.

Corollary. The adjoint’s inverse is Ad_{T^{-1}} = [[Rᵀ, 0], [−Rᵀt̂, Rᵀ]], which is the inverse of the 6×6 matrix, and it satisfies Ad_T Ad_{T^{-1}} = $I_6$. The inverse’s cross-product term is −Rᵀt̂, which is the reverse transformation.

§L.4.2 — Worked-example ladder

WE-L.4.a — Fully worked: the adjoint of a specific transform, by hand

Input. T = [[0, -1, 0, 1], [1, 0, 0, 2], [0, 0, 1, 3], [0, 0, 0, 1]] (Rz(π/2) with t = (1, 2, 3)), ξ = (0, 0, 1, 0, 0, 0) (rotation about z).

Trace.

  1. ω = (0,0,1), v = 0.
  2. Rω = Rz(π/2)·(0,0,1) = (0,0,1).
  3. Rv = 0.
  4. t × (Rω) = (1,2,3) × (0,0,1) = (2·1 − 3·0, 3·0 − 1·1, 1·0 − 2·0) = (2, -1, 0).
  5. Ad_T ξ = (0, 0, 1, 2, -1, 0).

Output and interpretation. The transformed twist is a rotation about z with a linear part (2, -1, 0) — the rotation is about the same axis, but the point of the rotation has shifted: the original twist is a rotation about z through the origin, and the transformed twist is a rotation about z through the point t = (1, 2, 3). The linear part (2, -1, 0) is −ω × q at q = (1, 2, 3): ω × q = (0,0,1) × (1,2,3) = (0·3 − 1·2, 1·1 − 0·3, 0·2 − 0·1) = (-2, 1, 0), so −ω × q = (2, -1, 0). The adjoint by T shifts the axis by t.

Self-explanation 8. Before reading on: in step 4, why is t × (Rω) = (2, -1, 0) and not (0, 0, 0)? What does it mean geometrically?

Answer. t × (Rω) is non-zero because t is not parallel to Rω. Geometrically, t × (Rω) is the moment of the translation about the rotation axis — the component of the axis’s shift that is perpendicular to the axis. If t were along the axis (t ∥ Rω), the cross product would be zero, and the adjoint would only rotate the twist (no shift). Since t = (1, 2, 3) is not along z, the cross product is non-zero, and the transformed twist’s axis is shifted by t perpendicular to z — i.e., the axis moves from the origin to (1, 2, 0) (the perpendicular part of t).

WE-L.4.b — Completion problem

Input. Same T as above, ξ = (0, 0, 1, 0, 0, 1) (rotation about z with pitch 1).

Trace (final 25% as prompts).

  1. ω = (0,0,1), v = (0,0,1).
  2. Rω = (0,0,1), Rv = (0,0,1). ⟨your turn: verify Rv by computing Rz(π/2)·(0,0,1)⟩
  3. t × (Rω) = (1,2,3) × (0,0,1) = (2, -1, 0). ⟨your turn: verify⟩
  4. Ad_T ξ = (Rω, Rv + t × (Rω)) = (0,0,1, 2, -1, 1). ⟨your turn: sum the components⟩

Answers.

  1. Rz(π/2)·(0,0,1) = (0,0,1) (the z-axis is fixed by a z-rotation). ✓
  2. (2, -1, 0). ✓
  3. (0,0,1) + (2, -1, 0) + (0,0,1) — the third summand is wrong. Rv = (0,0,1) is the only Rv contribution; adding it twice counts it once as Rv and once as if it were a second moment term. The correct sum is Rv + t × (Rω) = (0,0,1) + (2, -1, 0) = (2, -1, 1), so Ad_T ξ = (0, 0, 1, 2, -1, 1).

The lesson. The adjoint’s linear part has two contributions: Rv (the twist’s linear part, transformed as a direction) and t × (Rω) (the moment arm from the translation). Both are non-zero here (the pitch is v = (0,0,1) and the moment is (2, -1, 0)), and the total is (2, -1, 1). The pitch is the invariant that catches the error: a screw’s pitch ω·v / ‖ω‖² must survive the adjoint unchanged, and it does — (0,0,1)·(2,-1,1) = 1 — whereas the mis-summed (2, -1, 2) gives pitch 2. That is the H4 gate, and it is worth checking every adjoint against it.

WE-L.4.c — Faded: the running case’s velocity in two frames

Setup. The end-effector’s velocity in the spatial (base) frame is $v_s$ = (1, 0, 0, 0, 0, 0) (a pure translation along x), and the end-effector’s pose is T = [[0, -1, 0, 1], [1, 0, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]] (a π/2 rotation about z at position (1, 0, 0)).

Question. What is the velocity in the body frame? Use $ξ_b$ = Ad_{T^{-1}} $ξ_s$.

You must produce the intermediate steps. The setup and the answer are given.

Answer. Rᵀ = [[0, 1, 0], [-1, 0, 0], [0, 0, 1]], so Rᵀt = Rᵀ(1,0,0) = (0, -1, 0) and −Rᵀt = (0, 1, 0). Hence T^{-1} = [[0, 1, 0, 0], [-1, 0, 0, 1], [0, 0, 1, 0], [0, 0, 0, 1]] — Rz(−π/2) at (0, 1, 0).

Ad_{T^{-1}} $ξ_s$ = (Rᵀ $ω_s$, Rᵀ $v_s$ + (−Rᵀt) × (Rᵀ $ω_s$)). $ω_s$ = 0, $v_s$ = (1,0,0), Rᵀ $v_s$ = Rᵀ(1,0,0) = (0, -1, 0). (−Rᵀt) × (Rᵀ $ω_s$) = (0,1,0) × 0 = 0. So $ξ_b$ = (0, 0, 0, 0, -1, 0).

The lesson. The spatial velocity (1, 0, 0) (translation along x in the base frame) becomes the body velocity (0, -1, 0) (translation along −y in the body frame), because the body frame is rotated π/2 about z. The magnitude is preserved (both are unit vectors), and the direction is rotated by Rᵀ — the adjoint’s R part for the angular part, and the Rᵀ part for the linear part (since the linear part of a pure translation is a direction, not a velocity, and transforms by Rᵀ in the inverse adjoint).

WE-L.4.d — Contrastive near-miss: the 4×4 T applied to a twist

Correct solution. The adjoint Ad_T (a 6×6 matrix) transforms the twist: $ξ_A$ = Ad_T $ξ_B$.

Near-miss. The learner applies the 4×4 T to the twist’s linear part and the 3×3 R to the twist’s angular part: $ω_A$ = R $ω_B$, $v_A$ = T · $v_B$ (treating $v_B$ as a point). This gives $v_A$ = R $v_B$ + t, missing the cross-product term (and adding a spurious t).

Why the near-miss produces a plausible but incorrect number. The near-miss’s $ω_A$ = R $ω_B$ is correct (the angular part transforms as a direction). The near-miss’s $v_A$ = R $v_B$ + t is wrong by the cross-product term t × (Rω_B) and has an extra t that shouldn’t be there (the twist’s v is a velocity, not a point, so it should not get the +t). The result is a “velocity” that is R $v_B$ + t — the true velocity plus a spurious translation, and missing the cross-product term. The failure is silent (the result is a 3-vector), and the end-effector’s velocity is systematically wrong.

The discrepancy, stated. The correct adjoint Ad_T ξ = (Rω, Rv + t × (Rω)) vs. the near-miss (Rω, Rv + t). The near-miss is missing the cross-product term t × (Rω) and adding the spurious t. The two are equal only when t × (Rω) = 0 (i.e., t is parallel to Rω) and t = 0 (which is contradictory unless t = 0), so the near-miss is essentially never correct.

Self-explanation 9. Before reading on: in the near-miss, why does the twist’s v not get the +t that a point gets?

Answer. Because v is a velocity, not a point. A velocity’s transformation is $v_A$ = R $v_B$ + t × (Rω_B), which is the velocity of the point at B’s origin (in B’s frame, which is $v_B$), plus the velocity contributed by the rotation about the axis at A’s origin. The t × (Rω_B) term is the moment arm of the rotation, not a translation of the velocity. The +t that a point gets is because the point’s position includes the translation; the velocity’s value does not include a +t — it includes the t × (Rω) term. The near-miss’s +t is the mistake of treating a velocity like a point.

§L.4.3 — Comparative table: the transformation laws of the four objects

ObjectSizeLawCross-product termInverseSymptom when the wrong law is used
Point3$p_A$ = R $p_B$ + tnone — the +t is the translation$p_B$ = Rᵀ($p_A$ − t)The point is placed correctly and the velocity is not
Direction3$d_A$ = R $d_B$none, no +t$d_B$ = Rᵀ $d_A$A direction acquires a spurious translation
Pose4×4$T_A^B$ composed in chain ordernone(Rᵀ, −Rᵀt)A pose is treated as a velocity and loses its frame
Twist6Ad_T ξ = (Rω, Rv + t × (Rω))t × (Rω)Ad_{T⁻¹} = [[Rᵀ, 0], [−Rᵀt̂, Rᵀ]]Missing t×(Rω) and adding +t — the near-miss of WE-L.4.d

Design principle (P7.7). Identify the object before you pick its transformation law. The four laws share the R factor and differ in the translation handling: a point gets +t, a direction gets nothing, a pose is not transformed by a matrix-vector product at all, and a twist gets t × (Rω) and not +t. Because the rotation part is the same in all four, a wrong choice is invisible in the angular half of the answer and appears only in the linear half — which is why the discriminator is the pitch, the one scalar the adjoint must preserve.

§L.4.4 — Concept check

Concept check (displaces M4). A learner says: “The adjoint is just the pose applied to a twist — T is 4×4 and the twist is 6 numbers, so I pad the twist to a 4-vector and multiply.” What is the missing structure, and what bug class follows?

Answer. The adjoint is a 6×6 matrix [[R, 0], [t̂R, R]], and it is the derivative of conjugation T ξ̂ T⁻¹, not a matrix-vector product in the pose’s own dimension. Padding cannot work: a twist is not a homogeneous point, and there is no fourth coordinate to append. The bug class is the near-miss of WE-L.4.d — $ω_A$ = Rω_B correct, $v_A$ = Rv_B + t wrong both ways, missing the cross-product term t × (Rω_B) and adding a translation that a velocity does not receive. It is silent because the output is a well-formed 3-vector, and it is detectable only against a quantity the transform must leave alone: the pitch ω·v/‖ω‖², which Rv + t changes and Rv + t × (Rω) does not.

§L.4.5 — Consolidation checkpoint 4

(a) You should now be able to: (i) write Ad_T and its inverse in block form and state why the linear part carries t̂R and not R; (ii) transform a twist between frames by hand and check the result against the pitch; (iii) state the body/spatial distinction and which of the two is constant as the arm moves.

(b) Transfer probe. A body twist is $ξ_b$ = (0, 0, 1, 0, 0, 0) and the body’s pose is T = (I, (0, 0, 1)) — the body is one unit up the z-axis. Compute the spatial twist, then say whether the two are the same physical motion. Answer sketch: t × (Rω) = (0,0,1) × (0,0,1) = 0, so $ξ_s$ = Ad_T $ξ_b$ = (0, 0, 1, 0, 0, 0) — identical to $ξ_b$. They are the same physical motion, and the adjoint is the identity on twists whose axis is parallel to the frame’s translation: translating a frame along the rotation axis does not move the axis, so nothing changes. The learner who expects every adjoint to change the twist has not modeled t × (Rω) as a moment arm — a moment arm is zero when the lever is parallel to the force.

(c) Remediation rule. If you cannot do (b), return to §L.4.2 and redo WE-L.4.a, then re-attempt (b) by asking which of the two terms in Rv + t × (Rω) can be non-zero when t ∥ ω.


Part E — Failure Taxonomy, Lab, Cheat Sheet, Exercises, Exit Test

§L.5 — Failure taxonomy

SymptomThree most likely causes, rankedDiscriminating testFix
PoE FK matches at q = 0 but drifts at q ≠ 0(1) Missing J in exp (using q·v); (2) wrong twist (body vs. spatial); (3) exp composition in the wrong orderCompare against the chain FK at a specific non-zero q; if the drift is proportional to q, it is (1)Add the (1−cos)ω̂ + (q−sin)ω̂² terms; use the correct twist; use the chain order
Velocity transformed by Ad_T is the wrong magnitude(1) Missing cross-product term t×(Rω); (2) using T instead of Ad_T; (3) the R vs. Rᵀ confusionCompute the adjoint by hand and compare; if the result has t but no t×, it is (2)Use Ad_T = [[R, 0], [t̂R, R]]
The screw’s pitch is wrong(1) Pitch formula (ω·v)/‖ω‖² vs. (ω·v)/‖ω‖; (2) v vs. J·v confusion; (3) the axis direction is wrongCompute the pitch from the twist and from the pose’s translation, and compareUse (ω·v)/‖ω‖² (with the norm squared); check the twist
The body and spatial twists disagree(1) The frame convention is confused; (2) the adjoint’s R is used instead of Rᵀ (or vice versa); (3) the twist’s reference point is wrongCompute Ad_T $ξ_b$ and compare to the spatial twist directlyUse $ξ_s$ = Ad_T $ξ_b$ with the correct T = $T_body_to_spatial$
The chain FK and PoE FK differ at q = 0(1) The home configuration M is wrong; (2) the twists’ reference is wrong; (3) the product’s order is wrongCheck exp(0)·M = M and compare to $T_chain(0$)Fix M (should be the chain FK at q = 0); fix the twists

§L.6 — The lab

§L.6.1 — Task

Implement exp_so3, exp_se3 (both forms), and Ad_T, and use them to verify the PoE FK against the chain FK; then break the PoE three ways.

§L.6.2 — Conditions

Python 3.10+, PyTorch 2.2+, CPU only. dtype = float64. Seed 42. Runtime under 2 minutes.

§L.6.3 — Controls held fixed

Seed, dtype, link length a = 0.2, and the test configuration q = [0.3, 0.5, -0.2, 0.1, 0.4, -0.3] are fixed.

§L.6.4 — Metrics table

MetricDefinitionHow measured
poe_vs_chain_error‖$T_poe(q$) − $T_chain(q$)‖_max at the test qThe max entry difference
poe_vs_chain_q0The same at q = 0Both FK formulas at q = 0
exp_jacobian_effect‖$t_arbitrary$ − v‖ for a twist with v ⊥ ωThe difference between J·v and v
adjoint_check‖Ad_T $ξ_b$ − $ξ_s$‖ where $ξ_s$ = Ad_T $ξ_b$The identity check
adjoint_vs_T_error‖Ad_T ξ − (Rω, Rv + t)‖The near-miss’s error

§L.6.5 — Pre-registered hypotheses

  • H1. poe_vs_chain_error < 1e-10 at the test q — the PoE and the chain FK match exactly (up to floating point) when the twists and M are correct. The prediction is < 1e-10.
  • H2. exp_jacobian_effect > 0.1 for a twist with v ⊥ ω and q = π/2 — the J matrix’s effect is large, not negligible. The prediction is > 0.1.
  • H3. adjoint_vs_T_error > 1.0 — the 4×4 T-multiplication’s result is very different from the adjoint’s. The prediction is > 1.0.
  • H4. Ad_T ξ preserves the pitch — the pitch of Ad_T ξ equals the pitch of ξ (the pitch is invariant under a change of frame). The prediction is equality within 1e-10.

§L.6.6 — The break-it sequence

  1. Missing J (silent). Replace exp_se3’s translation with q·v. Predicted symptom: no exception; the PoE vs. chain FK drifts at q ≠ 0. Actual: as predicted — silent.
  2. Wrong twist (silent). Use ξ = (ω, 0) (origin-axis) instead of ξ = (ω, −ω×q) (off-origin). Predicted symptom: no exception; the FK differs by the moment arm. Actual: as predicted — silent.
  3. Wrong adjoint (silent). Use T instead of Ad_T for the velocity. Predicted symptom: no exception; the velocity is wrong by the cross-product term. Actual: as predicted — silent.

All three are silent.

§L.6.7 — Acceptance criteria

CriterionThresholdArtefact it is tested on
PoE vs. chain at test q< 1e-10The lab’s error print
J effect> 0.1The lab’s exp_jacobian_effect
Adjoint vs. T error> 1.0The lab’s comparison
Pitch preserved under adjoint< 1e-10The lab’s pitch check
Three break-its predicted3/3The prediction file

§L.6.8 — What good looks like

A strong submission has five properties:

  1. The PoE and chain FKs match at the test q (not just q = 0), with the error printed as a number, and the match is not taken for granted.
  2. The J matrix’s effect is shown numerically — the difference between J·v and v is a specific number, not just “non-zero.”
  3. The adjoint’s cross-product term is isolated — the submission shows Ad_T ξ = (Rω, Rv + t×(Rω)) and Rv + t separately, and the difference is the cross-product term.
  4. The pitch’s invariance is verified — the pitch of the body twist and the spatial twist are equal, and the equality is printed.
  5. 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_3.py — Twists, exp_se3, PoE FK, and the adjoint
# Requires: Python 3.10+, PyTorch 2.2+

import torch, math
torch.manual_seed(42)
dtype, device = torch.float64, torch.device("cpu")

def make_T(R, t):
    T = torch.eye(4, dtype=dtype, device=device); T[:3, :3] = R; T[:3, 3] = t; return T

def skew(w):
    return torch.tensor([[0, -w[2], w[1]], [w[2], 0, -w[0]], [-w[1], w[0], 0]], dtype=dtype, device=device)

def exp_so3(w):
    th = w.norm()
    if th < 1e-12: return torch.eye(3, dtype=dtype, device=device) + skew(w)
    u = w / th; U = skew(u)
    return torch.eye(3, dtype=dtype, device=device) + math.sin(th)*U + (1 - math.cos(th))*(U @ U)

def exp_se3_unit(xi, q):
    """Unit twist (||omega||=1) at joint angle q."""
    w, v = xi[:3], xi[3:]
    th = q; W = skew(w)
    R = torch.eye(3, dtype=dtype, device=device) + math.sin(th)*W + (1 - math.cos(th))*(W @ W)
    J = th*torch.eye(3, dtype=dtype, device=device) + (1 - math.cos(th))*W + (th - math.sin(th))*(W @ W)
    return make_T(R, J @ v)

def exp_se3_arbitrary(xi):
    """Arbitrary twist, angle baked into the twist magnitude."""
    w, v = xi[:3], xi[3:]
    th = w.norm(); W = skew(w)
    if th < 1e-12: return make_T(torch.eye(3, dtype=dtype, device=device), v)
    R = exp_so3(w)
    J = torch.eye(3, dtype=dtype, device=device) + ((1 - math.cos(th))/th**2)*W + ((th - math.sin(th))/th**3)*(W @ W)
    return make_T(R, J @ v)

def adjoint(T):
    R, t = T[:3, :3], T[:3, 3]
    A = torch.zeros(6, 6, dtype=dtype, device=device)
    A[:3, :3] = R; A[3:, 3:] = R; A[3:, :3] = skew(t) @ R
    return A

# --- The running case: home configuration and spatial twists ---
a = 0.2
M = make_T(torch.eye(3, dtype=dtype, device=device), torch.tensor([6*a, 0., 0.], dtype=dtype, device=device))

# Spatial twists at home config: joint i is at (i-1)*a along x, axis alternates z, y, z, ...
axes = ['z', 'y', 'z', 'y', 'z', 'y']
twists = []
for i, ax in enumerate(axes):
    $q_i$ = torch.tensor([(i)*a, 0., 0.], dtype=dtype, device=device)
    w = torch.tensor([0., 0., 1.] if ax == 'z' else [0., 1., 0.], dtype=dtype, device=device)
    v = -torch.cross(w, $q_i$)  # v = -omega x q
    twists.append(torch.cat([w, v]))
print("twists (spatial):")
for i, xi in enumerate(twists):
    print(f"  xi_{i+1}: {xi.tolist()}")

# --- Chain FK (from 0.2.1) ---
def rot_z(q): c, s = math.cos(q), math.sin(q); return torch.tensor([[c,-s,0],[s,c,0],[0,0,1]], dtype=dtype, device=device)
def rot_y(q): c, s = math.cos(q), math.sin(q); return torch.tensor([[c,0,s],[0,1,0],[-s,0,c]], dtype=dtype, device=device)

def chain_fk(q):
    T = torch.eye(4, dtype=dtype, device=device)
    for i, ax in enumerate(axes):
        R = rot_z(q[i]) if ax == 'z' else rot_y(q[i])
        # The link offset is expressed in the ROTATED frame: it is R·[a,0,0],
        # not [a,0,0]. Getting this backwards is the bug H1 exists to catch.
        T = T @ make_T(R, R @ torch.tensor([a, 0., 0.], dtype=dtype, device=device))
    return T

def poe_fk(q):
    T = torch.eye(4, dtype=dtype, device=device)
    for i in range(6):
        T = T @ exp_se3_unit(twists[i], q[i])
    return T @ M

q = torch.tensor([0.3, 0.5, -0.2, 0.1, 0.4, -0.3], dtype=dtype, device=device)
$T_chain$ = chain_fk(q); $T_poe$ = poe_fk(q)
print$f"\nchain t: {T_chain[:3, 3].tolist()}"$
print(f"poe   t: {T_poe[:3, 3].tolist()}")
print(f"poe_vs_chain_error: {(T_poe - $T_chain$).abs().max().item():.2e}")

# --- exp_jacobian_effect ---
xi = torch.tensor([0., 0., 1., 1., 0., 0.], dtype=dtype, device=device)
$q_test$ = math.pi / 2
$T_exp$ = exp_se3_unit(xi, $q_test$)
print$f"\nexp translation: {T_exp[:3, 3].tolist()}"$  # (1, 1, 0)
print(f"naive q*v: {(q_test * xi[3:]).tolist()}")  # (π/2, 0, 0) — the translation without J
print(f"exp_jacobian_effect: {(T_exp[:3, 3] - $q_test$ * xi[3:]).norm().item():.4f}")

# --- adjoint ---
T = make_T(rot_z(math.pi/2), torch.tensor([1., 2., 3.], dtype=dtype, device=device))
xi_b = torch.tensor([0., 0., 1., 0., 0., 0.], dtype=dtype, device=device)
xi_s = adjoint(T) @ xi_b
print$f"\nAd_T xi_b: {xi_s.tolist()}"$
print(f"T (4x4) applied to (omega, v): {(T[:3, :3] @ xi_b[:3]).tolist()} (omega only)")

# --- pitch invariance ---
def pitch(xi):
    w, v = xi[:3], xi[3:]
    return (w @ v) / (w @ w).clamp(min=1e-12)

print$f"\npitch(xi_b): {pitch(xi_b).item():.6f}"$
print(f"pitch(Ad_T xi_b): {pitch(xi_s).item():.6f}")  # should be 0

# xi_b has v = 0, so its pitch is 0 trivially -- a zero that any map would
# produce. Test the invariance on a twist with a NON-ZERO linear part, or the
# check cannot tell Ad_T from a map that discards v.
xi_b2 = torch.tensor([0., 0., 1., 0.3, 0.5, 0.7], dtype=dtype, device=device)
xi_s2 = adjoint(T) @ xi_b2
print(f"pitch(xi_b2) = {pitch(xi_b2).item():.6f}, "
      f"pitch(Ad_T xi_b2) = {pitch(xi_s2).item():.6f} (non-degenerate: the two must match)")

# --- break-it #1: missing J ---
def exp_se3_missing_J(xi, q):
    w, v = xi[:3], xi[3:]
    W = skew(w)
    R = torch.eye(3, dtype=dtype, device=device) + math.sin(q)*W + (1 - math.cos(q))*(W @ W)
    return make_T(R, q * v)  # missing the (1-cos) and (q-sin) terms

def poe_missing_J(q):
    T = torch.eye(4, dtype=dtype, device=device)
    for i in range(6):
        T = T @ exp_se3_missing_J(twists[i], q[i])
    return T @ M
print(f"\n[break-it #1] missing J: t = {poe_missing_J(q)[:3, 3].tolist()}")
print(f"[break-it #1] correct:   t = {T_poe[:3, 3].tolist()}")

# --- break-it #2: wrong twist (origin axis) ---
twists_wrong = []
for i, ax in enumerate(axes):
    w = torch.tensor([0., 0., 1.] if ax == 'z' else [0., 1., 0.], dtype=dtype, device=device)
    twists_wrong.append(torch.cat([w, torch.zeros(3, dtype=dtype, device=device)]))
def poe_wrong_twists(q):
    T = torch.eye(4, dtype=dtype, device=device)
    for i in range(6):
        T = T @ exp_se3_unit(twists_wrong[i], q[i])
    return T @ M
print(f"\n[break-it #2] origin-axis: t = {poe_wrong_twists(q)[:3, 3].tolist()}")
print(f"[break-it #2] correct:     t = {T_poe[:3, 3].tolist()}")

# --- break-it #3: wrong adjoint (4x4 T instead of 6x6 Ad) ---
xi_wrong = torch.cat([T[:3, :3] @ xi_b[:3], T[:3, 3] + T[:3, :3] @ xi_b[3:]])
print(f"\n[break-it #3] T (4x4) applied: {xi_wrong.tolist()}")
print(f"[break-it #3] Ad_T applied:    {xi_s.tolist()}")

# --- summary ---
print("\n--- lab 0.2.3 summary ---")
print(f"H1: poe_vs_chain_error = {(T_poe - $T_chain$).abs().max().item():.2e}")
print(f"H2: exp_jacobian_effect = {(T_exp[:3, 3] - $q_test$ * xi[3:]).norm().item():.4f}")
print(f"H3: Ad_T vs T error = {(xi_s - xi_wrong).norm().item():.4f}")
print(f"H4: pitch(xi_b)={pitch(xi_b).item():.6f}, pitch(Ad_T xi_b)={pitch(xi_s).item():.6f}")

§L.7 — The cheat sheet

Term or resultStatement or definitionRole
so(3)3×3 skew-symmetric matrices; 3-dim vector spaceIntroduced in 0.2.2; restated here as the algebra half of the algebra-vs-group distinction
se(3)4×4 matrices [[ω̂, v], [0, 0]]; 6-dim vector spaceIntroduced here; consumed by 1.1.1
Bracket[A, B] = AB − BA; on se(3), [(ω₁,v₁),(ω₂,v₂)] = (ω₁×ω₂, ω₁×v₂ − ω₂×v₁)Introduced here; consumed by 1.1.1
TwistA 6-vector ξ = (ω, v) ∈ se(3)Introduced here; consumed by 1.1.1, 1.1.2
Left JacobianJ(ω) = I + (1−cos θ)/θ² ω̂ + (θ−sin θ)/θ³ ω̂²Introduced here; consumed by 1.1.1, 1.1.2
exp_se3exp(ξ̂ q) = [[exp(qω̂), (q I + (1−cos q)ω̂ + (q−sin q)ω̂²)v], [0, 1]]Introduced here; consumed by 1.1.1
log_se3The inverse; v = J(ω)^{-1} tIntroduced here; consumed by 1.1.2
Screw motionAxis (u, q), pitch h, angle θIntroduced here; consumed by 1.1.1
Pitchh = (ω·v)/‖ω‖²Introduced here; consumed by 1.1.1
AdjointAd_T = [[R, 0], [t̂R, R]]; transforms twistsIntroduced here; consumed by 1.1.1, 1.1.2
Body vs. spatialBody in joint’s frame (constant); spatial in base (constant at home)Introduced here; consumed by 1.1.1
PoE FKT(q) = exp(ξ̂₁q₁)…exp(ξ̂₆q₆)·MIntroduced here; consumed by 1.1.1
Design principle P7.1Use the unit-twist form for PoE, the arbitrary-twist form for learningIntroduced here; consumed by 1.1.1, 3.1.2
Design principle P7.2Verify PoE against the chain at a non-zero qIntroduced here; consumed by 1.1.1
Design principle P7.3The adjoint is the velocity’s law, not TIntroduced here; consumed by 1.1.1
Design principle P7.4Add in the algebra, multiply in the groupIntroduced here; consumed by 1.1.1’s PoE
Design principle P7.5Fix the normalization before you fix the formulaIntroduced here; consumed by 1.1.1, 3.1.2
Design principle P7.6Classify the twist before you compute anything from itIntroduced here; consumed by 1.1.1, 1.1.2
Design principle P7.7Identify the object before you pick its transformation lawIntroduced here; consumed by 1.1.1, 2.1.1

§L.8 — Exercises with solutions

E 0.2.3.1 — The bracket (numerical). ξ₁ = (0, 1, 0, 0, 0, 0), ξ₂ = (0, 0, 1, 0, 0, 0). Compute [ξ₁, ξ₂].

S 0.2.3.1. ω₁ = (0,1,0), ω₂ = (0,0,1), v₁ = v₂ = 0. ω₁ × ω₂ = (1·1 − 0·0, 0·0 − 0·1, 0·0 − 1·0) = (1, 0, 0). ω₁ × v₂ − ω₂ × v₁ = 0. So [ξ₁, ξ₂] = (1, 0, 0, 0, 0, 0). The lesson: the bracket of the y- and z-rotations is the x-rotation; the pattern x̂ → ŷ → ẑ → x̂ is the cyclic structure of so(3)’s bracket.

E 0.2.3.2 — exp of a pure rotation (numerical). ξ = (0, 0, 1, 0, 0, 0), q = π. Compute exp(ξ̂ q).

S 0.2.3.2. ω = (0,0,1), v = 0, q = π. exp(qω̂) = Rz(π) = [[-1, 0, 0], [0, -1, 0], [0, 0, 1]]. t = 0. So exp = [[Rz(π), 0], [0, 1]]. The lesson: a pure rotation about z by π gives Rz(π) with zero translation, and the π is the half-turn that 0.2.2 flagged as the log’s singularity.

E 0.2.3.3 — exp of a general twist (numerical). ξ = (0, 0, 1, 1, 0, 0), q = π/2. Compute exp(ξ̂ q).

S 0.2.3.3. From WE-L.2.b, t = (1, 1, 0), so exp = [[Rz(π/2), (1, 1, 0)], [0, 1]]. The lesson: the J matrix’s effect is (1, 1, 0) − (π/2, 0, 0) = (1 − π/2, 1, 0), the difference from the naive q·v.

E 0.2.3.4 — The adjoint (numerical). T = [[-1, 0, 0, 3], [0, -1, 0, 4], [0, 0, 1, 5], [0, 0, 0, 1]] (Rz(π) with t = (3,4,5)), ξ = (0, 0, 1, 0, 0, 0). Compute Ad_T ξ.

S 0.2.3.4. Rω = Rz(π)·(0,0,1) = (0,0,1). Rv = 0. t × (Rω) = (3,4,5) × (0,0,1) = (4·1 − 5·0, 5·0 − 3·1, 3·0 − 4·0) = (4, -3, 0). So Ad_T ξ = (0, 0, 1, 4, -3, 0). The lesson: the adjoint shifts the rotation’s axis from the origin to t, and the linear part is the moment arm t × (Rω).

E 0.2.3.5 — The pitch (diagnostic). A twist’s pitch is h = 0. What does this mean physically, and what class of twist is it?

S 0.2.3.5. h = (ω·v)/‖ω‖² = 0 means ω·v = 0, so the linear part v is perpendicular to the axis ω. Physically, this is a pure rotation — the motion has no translation along the axis, and the v is entirely the moment arm (the axis is off-origin, and the v encodes its position). The class is pure rotation, and it is the running case’s joints’ class. The lesson: h = 0 distinguishes a pure rotation from a general screw, and the v’s role in a pure rotation is to encode the axis’s position, not a pitch.

E 0.2.3.6 — The PoE vs. chain (diagnostic). A learner’s PoE FK matches the chain FK at q = 0 but not at q ≠ 0. What are the three likely causes, and the discriminating test?

S 0.2.3.6. Causes: (1) missing J in the exp (the naive q·v), which drifts proportionally to q; (2) wrong twist (the body twist instead of the spatial, or the wrong reference point), which drifts by the moment arm; (3) wrong composition order, which gives a different product. Discriminating tests: for (1), the drift’s dependence on q (linear for (1), non-linear for (2) and (3)); for (2), compare the twist’s v to −ω × $q_home$; for (3), compare the product to the chain’s order (joint 1 to joint 6, not reversed). The lesson: the q = 0 test is necessary and not sufficient; the q ≠ 0 test is the real test, and the drift’s shape distinguishes the causes.

E 0.2.3.7 — The adjoint vs. T (diagnostic). A learner transforms a velocity by applying the 4×4 T and gets a velocity that is R v + t. What is the error, and what is the correct velocity?

S 0.2.3.7. The error is: (a) missing the cross-product term t × (Rω), and (b) adding a spurious +t (the twist’s v is a velocity, not a point). The correct velocity is R v + t × (R ω), and the difference from the near-miss R v + t is (R v + t × Rω) − (R v + t) = t × Rω − t, the negation of t − t × Rω. For a pure translation velocity (ω = 0), the correct is R v (no +t, no cross-product) and the near-miss’s R v + t is wrong by +t. The lesson: the 4×4 T is the point/direction action, not the velocity action; the velocity’s action is the 6×6 adjoint, and the difference is the cross-product term. Write the subtraction in the order you computed it — the sign of a difference quoted backwards passes every magnitude check.

E 0.2.3.8 — The PoE implementation (constructive). Write pseudocode for poe_fk(q, twists, M) and state what must be true of twists and M.

S 0.2.3.8.

function poe_fk(q, twists, M):
    T = identity(4)
    for i in 0 .. len(twists)-1:
        T = T @ exp_se3_unit(twists[i], q[i])
    return T @ M

The twists[i] must be unit twists (‖ω‖ = 1) at the home configuration, and M must be the chain FK at q = 0. The exp_se3_unit must use the unit-twist form with the (q I + (1−cos q)ω̂ + (q−sin q)ω̂²)v translation, not the naive q·v. The lesson: the PoE’s correctness depends on three things: unit twists at home, the correct M, and the J matrix in the exp; any one wrong makes the FK wrong at q ≠ 0 and correct at q = 0.


§L.9 — Exit test

You are ready for Lesson 0.3.1 when you can, without notes:

  1. State what a Lie algebra is, distinguish so(3) from SO(3) and se(3) from SE(3), and state the bracket’s role.
  2. Compute the bracket of two twists by hand, and state the BCH formula’s first-order correction.
  3. State the SE(3) exp’s unit-twist and arbitrary-twist forms, and explain why they are the same formula viewed differently.
  4. Compute exp(ξ̂ q) for a given twist and angle, showing the J matrix’s contribution.
  5. State the screw parameters (axis, point, pitch, angle) and classify a twist as pure rotation, pure translation, or general screw.
  6. State the running case’s spatial twists and home configuration, and write the PoE FK.
  7. Verify the PoE FK against the chain FK at a specific non-zero q, and state the three conditions for the match.
  8. State the adjoint Ad_T = [[R, 0], [t̂R, R]] and transform a twist between frames, showing the t × (Rω) term.
  9. Distinguish a body twist from a spatial twist, and state the frame convention for each.
  10. State why the adjoint is not the 4×4 T applied to a 6-vector, and the symptom of using T.
  11. Diagnose a PoE-vs-chain mismatch, a wrong adjoint, and a wrong pitch, and name the test for each.
  12. State the three conditions for the PoE’s correctness (unit twists, correct M, correct J), and the test that verifies each.

If any item is not answerable without notes, return to the section that introduced it: 1 to §L.1, 2 to §L.1.1, 3 to §L.2, 4 to §L.2.2, 5 to §L.3.1, 6 to §L.4.2, 7 to §L.4.2 and §L.6.9, 8 to §L.4.1, 9 to §L.4, 10 to §L.4.4, 11 to §L.5, 12 to E 0.2.3.8.

§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. These claims are tier A, status R.

ClaimTierStatusWhere it is reproduced
The PoE product and the chained homogeneous transforms agree at the test configuration: error 0.00e+00ARH1
The J matrix’s effect is large, not negligible: 1.1514 at q = π/2ARH2
A twist transformed by a 4×4 multiply differs from the adjoint’s result by 4.3589 — the wrong operation is not a near-missARH3, and the break-it’s paired vectors
The adjoint preserves pitch: both ξ and Ad_T ξ have pitch 0.000000ARH4
Applying T to a twist and applying Ad_T give visibly different 6-vectorsARBreak-it #3, printed as [0,0,1,1,2,3] against [0,0,1,2,-1,0]

What this lesson does not reproduce. The claim that the pitch is invariant under a change of frame is checked only on a degenerate twist. H4’s xi_b = (0, 0, 1, 0, 0, 0) has v = 0, so its pitch is 0/|ω|² = 0 whatever T is — the test cannot distinguish an invariant map from one that destroys the linear part entirely. The invariance itself is true and was verified separately: with v = (0.3, 0.5, 0.7) the pitch goes 0.700000 → 0.700000 under Ad_T, so the claim is tier A, status S — reproduced, but not by the shipped test. The lab now prints both the degenerate and the non-degenerate case.

The 4.3589 is the lesson’s identification result. H3 exists to show that T·ξ is not a small perturbation of Ad_T·ξ — it is a different object. The break-it’s two printed vectors make the difference concrete: the first has a non-zero linear part in the last component (3), the second has a rotation-induced coupling (2, -1). A reader who thinks the adjoint is a notational convenience should compare those two lines.

No external sources are cited in this lesson. The PoE product, the exponential map, and the adjoint are built in Parts A–C and executed by the lab. No paper is named; the lesson defines every object it uses.