Lesson 0.2.4 — Active vs. Passive Transformations: The Most Common Sign Errors in Robotics Code
Week 3 · Prerequisites: 0.2.1, 0.2.2, 0.2.3.
Prior results used. This lesson uses 0.2.1’s pose convention T_A^B, its composition rule and inverse, and its point/vector distinction; 0.2.2’s Rᵀ = R⁻¹; and 0.2.3’s adjoint. Each is restated at the point of use.
Hands forward. This lesson hands forward one capability: the learner can read any transform expression in a robotics codebase, state whether it is intended as a pose, an active motion, or a passive coordinate change, verify that its direction matches the intent, and diagnose the four canonical sign errors from their symptoms — the capability that every multi-frame pipeline in Phases 1–4 assumes, and without which every subsequent lesson’s FK, IK, SLAM, and pose-estimation code is silently wrong.
Running case. The same 6-DOF MuJoCo arm from 0.2.1–0.2.3, extended with a camera mounted on the end-effector and a target object seen by the camera. The object-in-world transform is T_W^Obj = T_W^B · T_B^EE · T_EE^Cam · T_Cam^Obj, and every example in this lesson is an instance of one of those four factors, of its inverse, or of the composed chain — the four canonical sign errors are the four ways to get one of those factors backward.
§L.0 — Lesson map, objectives, and misconceptions
§L.0.1 — Lesson map
| Part | Content |
|---|---|
| A | The two readings of a transform (active vs. passive) and why they are the same matrix |
| B | The four canonical sign errors: inverse rotation, inverse translation, wrong order, wrong direction |
| C | Conventions in code: naming discipline, the Transform class, and the review checklist |
| D | The compound-frame cascade: how a single sign error propagates through a chain |
| 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 the active and passive readings of a transform, and explain why they are the same matrix (not two matrices) and why the inverse is the fourth reading.
- State the three readings of
T_A^B(pose ofBinA, active motion fromBtoA, passive coordinate change fromBtoA) and the fourth (T_B^A = (T_A^B)^{-1}). - Diagnose the inverse-rotation error (
Rvs.Rᵀ) from the symptom of an object rotating the wrong way. - Diagnose the inverse-translation error (
tvs.−Rᵀt) from the symptom of a position off byRᵀt − t, of magnitude‖(R − I)t‖. - Diagnose the wrong-composition-order error from the symptom of a pose that is correct at
q = 0and drifts atq ≠ 0. - Diagnose the wrong-direction error (
T_A^Bvs.T_B^A) from the symptom of an end-effector at the origin’s position (or a camera pointing inward). - Apply a naming discipline —
T_from_towherefromis the source andtois the target — that makes each transform’s direction unambiguous in the code, and state the reading of any such name without consulting a comment. - Produce a written “frame audit” of a multi-frame pipeline: every frame, every transform, its direction, its convention, and the composition chain from the base to the object.
§L.0.3 — Misconception inventory
| Wrong model the learner probably holds | Why it is attractive | Where displaced | The correct model in one sentence |
|---|---|---|---|
M1 — “A transform T is just a matrix; its meaning is unambiguous.” | A 4×4 matrix is a well-defined mathematical object, and its action T @ [p; 1] is a well-defined computation. | §L.1 (Part A) | The matrix is unambiguous; its reading is not — the same T can be “the pose of B in A,” “the active motion of B to A,” or “the passive change of coordinates from B to A,” and the code’s correctness depends on which reading the author intended and which the caller assumes. |
M2 — “T_A^B and T_B^A are inverse, so it doesn’t matter which I write.” | They are inverses, and both describe the same pair of frames, so the choice feels like a notation preference. | §L.2 and §L.6 (Parts A and B) | They are inverses, and they produce different transforms; writing T_A^B when T_B^A is intended sends the end-effector to the opposite pose, and the error is silent because both are legal SE(3) elements. |
M3 — “The inverse is (Rᵀ, −t).” | It is the natural “transpose and negate” of (R, t) and it is correct for the identity rotation. | §L.4 (Part B) | The inverse is (Rᵀ, −Rᵀt); the −t is in the parent frame, and the inverse’s translation must be in the child frame — the naive −t is off by Rᵀt − t, which has the magnitude ‖(R − I)t‖. |
| M4 — “Composition order is a style choice; matrix multiplication is associative.” | The math is associative, and for small angles the orders are numerically close. | §L.5 (Part B) | The math is associative, but the physics is not commutative: T₁·T₂ ≠ T₂·T₁, and the order encodes the frame chain; the wrong order gives a different arm’s FK. |
| M5 — “The code’s variable name tells me the convention.” | Variable names like T_base_ee look informative, and reading them feels like reading documentation. | §L.7 (Part C) | The name is ambiguous unless it states the direction: T_base_ee could mean “pose of ee in base” or “transform from ee to base” — the same two names, opposite readings. The discipline that removes the ambiguity is T_from_to with explicit direction, or the “effect in the code” pattern p_W = T_WB @ p_B. |
Each misconception is displaced in the section named.
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 eighth lesson. The warm-up draws from 0.2.1 and 0.2.2 — 0.2.3 is one step back and is excluded.
Warm-up 1 (from 0.2.1, the pose convention). T_A^B maps B-expressed points to A-expressed points. What are the two factors of T_A^B, and which frame is each expressed in? Answer: T_A^B = (R_A^B, t_A^B), with R_A^B the orientation of B in A (a 3×3 rotation) and t_A^B the origin of B expressed in A (a 3-vector). How this is used: §L.1’s three readings of T_A^B all use the same factors; the direction (A from B) is the same in all three, and the readings differ in interpretation, not in computation.
Warm-up 2 (from 0.2.1, the inverse). Write (T_A^B)^{-1} and state which frame the inverse’s translation is in. Answer: (T_A^B)^{-1} = T_B^A = (R_A^{Bᵀ}, −R_A^{Bᵀ} t_A^B), and the inverse’s translation −R_A^{Bᵀ} t_A^B is expressed in B — the child frame of the original — not in A. How this is used: §L.4’s inverse-translation error is the mistake of using −t (in A) instead of −Rᵀt (in B), and it is the most common of the four canonical errors.
Warm-up 3 (from 0.2.2, Rᵀ = R⁻¹). For R ∈ SO(3), what is R⁻¹, and what does Rᵀ’s action on a point mean geometrically compared to R’s? Answer: R⁻¹ = Rᵀ; R rotates a point by θ about u, and Rᵀ rotates it by −θ about the same u (the inverse rotation). How this is used: §L.3’s inverse-rotation error is the mistake of using Rᵀ when R is intended (or vice versa), which reverses the direction of the rotation.
Warm-up 4 (from 0.2.2, the rotation’s physical meaning). A rotation R_A^B has a physical meaning as an orientation. If a learner says “the object is rotated 30° about z,” what are the two readings of that statement, and are they the same? Answer: “The object is rotated 30° about z” could mean (i) the object moved 30° (active) or (ii) the frame rotated 30° (passive). The two are inverses: if the object rotated +30°, the frame rotated −30° to keep the object in the same physical orientation. The object’s pose in the new frame is R(−30°) = Rᵀ(30°), not R(30°). How this is used: §L.1’s active/passive distinction is exactly this — the same 30° has two readings, and confusing them reverses the rotation’s sign.
Part A — The Two Readings of a Transform
§L.1 — Objects: active vs. passive, and why they are the same matrix
A transform T ∈ SE(3) has two readings:
- Active (the “alibi”):
Tmoves a rigid body. The body’s points change their positions in a fixed frame, and the new positions arep' = T p. The frame does not move; the body does. - Passive (the “alias”): the frame changes while the body stays fixed. The body’s coordinates change from
p^A(in frameA) top^B(in frameB), and the change isp^B = T^{-1} p^A(whereTis the pose ofBinA, sop^A = T p^B). The body does not move; the frame does.
The two readings are the same matrix, the same formula, and the same operation — the difference is only the interpretation of what the operation represents. The active transformation “move the body from B to A” and the passive transformation “change the frame from A to B” both use the same T_A^B and both produce the same numeric result.
The inverse T^{-1} is the fourth reading — “move the body from A to B” (active) or “change the frame from B to A” (passive) — and it is the matrix that causes the sign errors when confused with the other three.
The three readings of T_A^B and the one inverse:
- Pose reading: “
T_A^Bis the pose ofBinA” — a configuration, a static description of whereBis relative toA. - Active reading: “
T_A^BmovesBtoA” — a motion, the transformation that takesB’s points toA’s frame. - Passive reading: “
T_A^BchangesB-coordinates toA-coordinates” — a change of basis, the map from the coordinates a point has inBto the coordinates it has inA. - Inverse reading: “
T_A^Binverted (i.e.,T_B^A) is the pose ofAinB, the active motion ofAtoB, or the passive change fromA-coordinates toB-coordinates.”
Readings 1, 2, and 3 are the same operation with different language; reading 4 is the opposite operation. The canonical sign errors arise from conflating reading 4 with readings 1–3, or from using reading 4 when reading 1–3 is intended (or vice versa).
§L.1.1 — Result: the active and passive readings are the same matrix, and the inverse is the fourth reading; the sign error is a confusion between the pair
Claim. The active transformation “move B to A” and the passive transformation “change B-coordinates to A-coordinates” are the same matrix T_A^B; the pose reading is the same matrix T_A^B; the “opposite” of all three is (T_A^B)^{-1} = T_B^A, and a sign error is any code that uses T_B^A where T_A^B is intended (or vice versa).
Proof sketch. For a point p with B-coordinates p^B and A-coordinates p^A, the pose of B in A satisfies p^A = T_A^B p^B. The passive reading is the map p^B ↦ p^A, and it is T_A^B. The active reading is the map p^B ↦ p^A viewed as “the point moves from the B position to the A position,” and it is the same map T_A^B. The pose reading is the static description “B is at T_A^B relative to A,” which is the same matrix as the map. The inverse T_B^A = (T_A^B)^{-1} satisfies p^B = T_B^A p^A, which is the opposite map — the active motion of A to B, the passive change from A-coordinates to B-coordinates, and the pose of A in B.
Corollary. The “opposite” of a transform is not a different notation for the same thing; it is a different matrix (the inverse), and it produces a different result (the opposite pose). The code that writes T_A^B when T_B^A is intended is not “using a different convention” — it is using the wrong transform.
Corollary. The four readings are not four matrices; they are one matrix and its inverse, read in two ways each. The code’s correctness depends on which of the two is used, and the direction’s correctness cannot be verified from the matrix’s entries alone — it requires knowing the intended reading.
§L.1.2 — Worked-example ladder
WE-L.1.a — Fully worked: the same matrix, four readings, by hand
Input. A transform T_A^B = [[Rz(π/2), (1, 0, 0)], [0, 1]], and a point p^B = (1, 0, 0).
Trace (reading 1: pose).
- The pose of
BinAis(Rz(π/2), (1, 0, 0))—B’s origin is at(1, 0, 0)inA, andB’sx-axis points alongA’sy-axis (a 90° rotation aboutz).
Trace (reading 2: active motion).
- The active motion “move
BtoA” takes a point atB’s origin(0, 0, 0)toA’s coordinates(1, 0, 0), and a point atB’sx-axis(1, 0, 0)toA’s coordinates(1, 1, 0)— the point is moved to the(1, 1, 0)position. - Specifically:
p^A = R p^B + t = Rz(π/2)·(1, 0, 0) + (1, 0, 0) = (0, 1, 0) + (1, 0, 0) = (1, 1, 0).
Trace (reading 3: passive change).
- The passive change “read
B’s coordinates inA” also givesp^A = (1, 1, 0), because it is the same computation as the active motion — the difference is that the body moves (active) vs. the frame changes (passive), but the point’s coordinates inAare the same either way.
Trace (reading 4: inverse).
- The inverse
T_B^A = (T_A^B)^{-1} = (Rz(−π/2), Rz(−π/2)·(−(1, 0, 0))):Rz(−π/2) = [[0, 1, 0], [−1, 0, 0], [0, 0, 1]].−Rᵀt = −Rz(−π/2)·(1, 0, 0) = −(0, −1, 0) = (0, 1, 0).- So
T_B^A = (Rz(−π/2), (0, 1, 0)).
- Applying
T_B^Atop^A = (1, 1, 0):p^B = Rz(−π/2)·(1, 1, 0) + (0, 1, 0) = (1, −1, 0) + (0, 1, 0) = (1, 0, 0). ✓ Recoversp^B. - But applying
T_B^Atop^B = (1, 0, 0)(the wrong direction) givesRz(−π/2)·(1, 0, 0) + (0, 1, 0) = (0, −1, 0) + (0, 1, 0) = (0, 0, 0)— a different point, and the source of the sign error.
Output and interpretation. Readings 1, 2, and 3 are the same matrix T_A^B and produce the same p^A for a given p^B; reading 4 is the inverse T_B^A, and using it on the wrong side produces the wrong point. The (0, 0, 0) from step 7 is the symptom of the wrong-direction error: the point that “should be at (1, 1, 0)” is at the origin, because the transform went backward.
Self-explanation 1. Before reading on: in step 6,
T_B^Aapplied top^A = (1, 1, 0)recoversp^B = (1, 0, 0). What is the geometric meaning, and why is it the inverse ofT_A^B?Answer.
T_A^BmapsB-coordinates toA-coordinates;T_B^AmapsA-coordinates toB-coordinates. The two maps are inverses because they are the same frame change in opposite directions. The geometric meaning ofT_B^A p^A = p^Bis “given a point’s coordinates inA, find its coordinates inB” — the reverse lookup, which is the inverse of “givenB, findA.”
Self-explanation 2. Before reading on: in step 7, applying
T_B^Atop^B(instead ofp^A) gives(0, 0, 0). What is the physical interpretation, and why is it a sign error?Answer. The physical interpretation: the point at
B’s origin is transformed byT_B^A, which moves it to the wrong place —(0, 0, 0), the origin ofB’s frame inB’s coordinates, which is(0, 0, 0)by definition. The transform “went the wrong way” (fromAtoBwhenBwas the source), and the point atB’s origin was treated as if it were already inA. The error is a sign error because it uses the inverse of the intended transform, and the inverse is the “opposite” transform.
WE-L.1.b — Completion problem
Input. The same T_A^B, and a point p^B = (0, 1, 0) (on B’s y-axis).
Trace (final 25% as prompts).
- The pose reading:
B’sy-axis inAis ⟨your turn: computeR_A^B · (0, 1, 0)⟩. - The active motion of
p^B: ⟨your turn: computeT_A^B · [p^B; 1]⟩. - The passive reading: ⟨your turn: is it the same as step 2? Why?⟩
- The inverse’s action on the same
p^B(the wrong direction): ⟨your turn: computeT_B^A · [p^B; 1]and compare to step 2⟩
Answers.
R_A^B · (0, 1, 0) = Rz(π/2) · (0, 1, 0) = (-1, 0, 0)—B’sy-axis points alongA’s−x-axis.R p^B + t = (-1, 0, 0) + (1, 0, 0) = (0, 0, 0).- Yes, the passive reading is the same
(0, 0, 0), because it is the same computation. T_B^A · p^B = Rz(-π/2)·(0, 1, 0) + (0, 1, 0) = (1, 0, 0) + (0, 1, 0) = (1, 1, 0), which is different from step 2’s(0, 0, 0)— the wrong direction.
The lesson. The inverse’s action on p^B is different from the forward action, and the difference is the source of the sign error. Both results are legal points, so the error is silent; the correctness depends on knowing which point p^B was supposed to be in and which transform’s source frame it belongs to.
WE-L.1.c — Faded: the same transform in the running case
Setup. The running case’s camera is mounted on the end-effector, with a calibration T_EE^Cam = [[I, (0, 0, 0.1)], [0, 1]] (the camera is 10 cm along z from the end-effector, with no rotation). A point is observed at p^Cam = (0.5, 0, 1) (in the camera’s frame).
Question. What is the point’s position in the end-effector’s frame? What is it in the base’s frame if T_B^EE = [[Rz(π/2), (0.3, 0.2, 0.1)], [0, 1]]?
You must produce the intermediate steps. The setup and the answer are given.
Answer. p^EE = T_EE^Cam · p^Cam = I·(0.5, 0, 1) + (0, 0, 0.1) = (0.5, 0, 1.1). p^B = T_B^EE · p^EE = Rz(π/2)·(0.5, 0, 1.1) + (0.3, 0.2, 0.1) = (0, 0.5, 1.1) + (0.3, 0.2, 0.1) = (0.3, 0.7, 1.2). The lesson: the composition T_B^Cam = T_B^EE · T_EE^Cam maps Cam to B in two steps, each T_source^target with the correct direction; the composition’s correctness depends on each factor’s direction being right, which is the subject of §L.5.
WE-L.1.d — Contrastive near-miss: the pose reading vs. the “pose of the origin” confusion
Correct solution. The pose of B in A is T_A^B. The origin of B in A’s coordinates is t_A^B (the translation), and the B-frame’s axes in A are the columns of R_A^B.
Near-miss. The learner reads T_A^B as “the pose of A in B” — the inverse — and uses T_A^B where T_B^A is intended. The pose is opposite, and the end-effector is at the inverse location.
Why the near-miss produces a plausible but incorrect number. The T_A^B is a legal SE(3) element, and the code runs; the end-effector’s pose is some pose, but it is the inverse of what was intended. At q = 0, both T_B^EE and T_EE^B are near-identity (if the arm is near its home), so the error is small and the FK looks almost right; at q ≠ 0, the error grows, and the end-effector is at the opposite location. The failure is silent in the q = 0 test and visible in the q ≠ 0 test against the correct FK.
The discrepancy, stated. T_A^B and T_B^A = (T_A^B)^{-1} differ by an inverse, which is (Rᵀ, −Rᵀt) — a different matrix. The pose of B in A is T_A^B, not T_B^A, and the two are equal only when the transform is its own inverse (the identity or a 180° rotation with a translation on the axis). The near-miss’s “pose” is the pose of A in B, and its effect on a point is the opposite of the intended.
Self-explanation 3. Before reading on: in the near-miss, if the transform is the identity (
T = I), is the error visible?Answer. No —
I^{-1} = I, so the pose and its inverse are the same matrix, and the error is invisible at the identity. The error is visible only at non-identity transforms, which is why theq = 0test cannot catch it and theq ≠ 0test can.
§L.1.3 — Comparative table: the four readings of T_A^B
| Reading | Statement | Matrix | Maps | Differs from the pose reading when | Symptom if substituted |
|---|---|---|---|---|---|
| Pose | “B is at T_A^B relative to A” | T_A^B | nothing — it is a description | Never; it is the same matrix | None — but it answers a question about B, not about a point |
| Active | “move the body from B to A” | T_A^B | B-points → A-points | Never; the same computation | None |
| Passive | “change B-coordinates to A-coordinates” | T_A^B | B-coordinates → A-coordinates | Never; the same computation | None |
| Inverse | “the pose of A in B”, or the reverse motion | T_B^A = (T_A^B)^{-1} | A-coordinates → B-coordinates | Always, except at a self-inverse transform | The point lands at the origin or the mirrored location |
Design principle (P8.5). A transform’s correctness is a fact about its direction, not about its entries. All four readings are legal SE(3) elements, so no validity check — det R = 1, RᵀR = I, T·T⁻¹ = I — separates the intended matrix from its inverse. The entries are silent on the one question that matters. Only the intended source frame answers it, which is why the direction has to be written down somewhere the machine can read: the variable’s name, or the effect’s subscript order.
§L.1.4 — Concept check
Concept check (displaces M1). A learner says: “A transform is a matrix, so its meaning is unambiguous —
T @ [p; 1]is a well-defined computation and the result is the answer.” Which model is missing, and what bug class follows?Answer. The missing model is that the matrix is unambiguous and the reading is not. The same
T_A^Bis the pose ofBinA, the active motion ofBtoA, and the passive change fromB- toA-coordinates — three sentences, one matrix, one computation — whileT_B^Ais a fourth sentence and a different matrix. The bug class is a correct computation of the wrong question: the code runs, the arithmetic is right, the result is a legal point, and the point is the inverse image of the one intended. Nothing in the computation can flag it, because nothing in the computation knows which reading was meant.
§L.1.5 — Consolidation checkpoint 1
(a) You should now be able to:
(i) state the active and passive readings and say precisely what stays fixed in each;
(ii) list the four readings of T_A^B and say which three share a matrix;
(iii) state why no entry-wise validity check can detect a direction error.
(b) Transfer probe. A reviewer approves a PR because the test assert T_EECam @ T_CamEE == I passes. What has the test established, and what has it not?
Answer sketch: it has established that the two variables are inverses of each other — an arithmetic identity that holds just as well when the pair is backwards, because the wrong-direction bug supplies the inverse. It has established nothing about which of the two is the camera’s pose in the end-effector. The test that separates them applies the transform to a point with a known answer in both frames, or checks the composition’s name cancellation in a chain where an inverted factor leaves a frame unmatched. A test whose predicate is symmetric under the error it is meant to catch is not a test of that error.
(c) Remediation rule. If you cannot do (a)(ii) or (b), return to §L.1.2 and redo WE-L.1.a steps 5–7, then re-attempt (b) by asking what the two candidate matrices would each do to a point at the end-effector’s origin.
Part B — The Four Canonical Sign Errors
§L.2 — Objects: the four errors and their signatures
The four sign errors are:
- Inverse rotation — using
RᵀwhereRis intended, or vice versa. The rotation goes the wrong way; the translation is unaffected (if the code usestseparately) or also wrong (if the code uses the naive inverse). - Inverse translation — using
−twhere−Rᵀtis intended, ortwhereR tis needed. The position is off byRᵀt − t(or its negative,t − Rᵀt), whose magnitude is‖(R − I)t‖, and the error grows with‖t‖. - Wrong composition order — using
T₂·T₁whereT₁·T₂is intended. The pose is a different arm’s FK, correct atq = 0and drifting atq ≠ 0. - Wrong direction — using
T_A^BwhereT_B^Ais intended. The pose is the inverse; the end-effector is at the “opposite” location.
Each has a distinct signature, and the signature is the diagnostic.
§L.2.1 — Result: each sign error has a distinct signature, and the signature is the discriminator
Claim. The four errors produce four distinguishable symptoms: the inverse-rotation error rotates the object the wrong way (the rotation’s sign is flipped); the inverse-translation error shifts the position by a constant Rᵀt − t (zero at R = I, growing with ‖t‖, and equal in magnitude to ‖(R − I)t‖); the wrong-order error produces a pose correct at q = 0 and drifting at q ≠ 0; and the wrong-direction error produces a pose that is the inverse of the correct one (the origin is at the “wrong” place and the axes are “inside out”).
Proof sketch. Each error substitutes a different matrix into the same code: (1) R → Rᵀ flips the rotation’s sign; (2) t → −t shifts the translation by t + t = 2t in the frame of the naive inverse, which as a vector is Rᵀt − t — equal in norm to ‖(R − I)t‖ (because Rᵀt − t = Rᵀ(t − Rt) and Rᵀ is an isometry), but a different vector: for R = Rz(π/2) and t = (1,0,0), Rᵀt − t = (−1,−1,0) while (R−I)t = (−1,1,0); (3) T₁·T₂ → T₂·T₁ reorders the product, producing a q = 0-correct and q ≠ 0-different result; (4) T_A^B → T_B^A replaces the matrix by its inverse, giving the “opposite” pose. The four errors are independent (they can co-occur), and each has a unique signature.
Corollary. The diagnostic procedure is: check the rotation’s direction (error 1), check the position’s offset (error 2), check the q = 0 vs. q ≠ 0 behavior (error 3), and check the “opposite pose” (error 4). The four tests together isolate the error, and each test is a simple computation.
Corollary. The errors compound if multiple are present: a code with both an inverse-rotation and a wrong-order error has a pose that is wrong in both the rotation’s sign and the order, and the diagnostic must isolate each. The lab’s break-it sequence exercises the four errors one at a time so the diagnostics can be learned separately.
§L.2.2 — Worked-example ladder: the four errors, one at a time
WE-L.2.a — Fully worked: the inverse-rotation error
Correct solution. R = Rz(π/2), and a point p = (1, 0, 0) transforms as R p = (0, 1, 0) (rotated +90° about z).
Near-miss. Rᵀ = Rz(−π/2), and Rᵀ p = (0, -1, 0) — rotated −90° about z.
Signature. The rotation’s sign is flipped: +90° vs. −90°. The position of p in the frame is mirrored across the x-axis. The error is visible whenever the rotation is not its own inverse (i.e., not 0° or 180°).
Discrepancy. R and Rᵀ are both legal SO(3) elements; their actions on p differ by R p − Rᵀ p = (0, 1, 0) − (0, -1, 0) = (0, 2, 0). The error is a reflection across the axis of the rotation (the x-axis), which is the geometric meaning of the transpose.
Self-explanation 4. Before reading on: if the rotation is Rz(π) (a 180° rotation), is the inverse-rotation error visible?
Answer. No —
Rz(π) = Rz(−π)(a 180° rotation is its own inverse), soR = Rᵀand the error is invisible. The error is visible only for rotations whose angle is not0°or180°, which is the vast majority of configurations.
WE-L.2.b — Completion problem: the inverse-translation error
Correct solution. T = (Rz(π/2), (1, 2, 3)), and its inverse T^{-1} = (Rz(−π/2), −Rᵀt) with −Rᵀt = −Rz(−π/2)·(1, 2, 3) = −(2, -1, 3) = (-2, 1, -3).
Near-miss. T^{-1}_naive = (Rᵀ, −t) = (Rz(−π/2), (-1, -2, -3)).
Trace (final 25% as prompts).
T^{-1} · T’s translation: ⟨your turn: computeR^{-1}·R·0 + R^{-1}·t + (−Rᵀt)and state it is0⟩.T^{-1}_naive · T’s translation: ⟨your turn: computeRᵀ·t + (−t)⟩.- The difference between (1) and (2): ⟨your turn⟩.
Answers.
Rᵀ·t + (−Rᵀt) = Rᵀt − Rᵀt = 0. ✓Rᵀ·t + (−t) = (2, -1, 3) + (-1, -2, -3) = (1, -3, 0). The product’s translation is(1, -3, 0), not0.- The difference is
(1, -3, 0) = Rᵀt − t, which is the naive inverse’s error and equals−(t − Rᵀt). The error is the rotation’s action on the translation minus the translation, and it is zero only whentis on the rotation axis ort = 0.
The lesson. The naive inverse’s translation is wrong by Rᵀt − t, and the error is systematic (deterministic, grows with ‖t‖). The T^{-1}_naive · T is a valid SE(3) element with a non-zero translation, but the inverse’s defining property T^{-1} T = I is violated, which is the test.
Self-explanation 5. Before reading on: if t = (0, 0, 5) (on the z-axis) and R = Rz(π/2), does the naive inverse still have the error?
Answer. No —
Rᵀt = Rz(−π/2)·(0, 0, 5) = (0, 0, 5) = t, soRᵀt − t = 0and the naive inverse equals the correct one. The error is zero when the translation is along the rotation axis (the axial component is unchanged by the rotation). For a generaltwith a perpendicular component, the error is non-zero, and its magnitude is the perpendicular component’s change under rotation.
WE-L.2.c — Faded: the wrong-composition-order error
Correct solution. The FK is T₁·T₂·…·T₆, composed in the chain order (joint 1 to joint 6).
Near-miss. The FK is T₆·…·T₂·T₁, composed in the reverse order.
Question. At q = 0, does the near-miss match the correct FK? At q ≠ 0? Why?
You must produce the intermediate steps. The setup and the answer are given.
Answer. At q = 0, each T_i(0) = Trans_x(a) is a pure translation, and translations commute — Trans_x(a)·Trans_x(a) = Trans_x(2a), and the order does not matter. So the near-miss matches at q = 0. At q ≠ 0, the T_i(q_i) contain rotations, which do not commute, so the reversed order is a different product, and the FK drifts. The drift is the q ≠ 0 difference between the two products, which is the BCH correction (Lesson 0.2.3).
The lesson. The wrong-order error is invisible at q = 0 and visible at q ≠ 0, which is why the q = 0 test cannot catch it and the q ≠ 0 test is the discriminator. The error’s magnitude grows with the joint angles, and its direction depends on the twists’ brackets.
Self-explanation 6. Before reading on: in the near-miss, if all the joints are pure rotations about the same axis (a planar arm), does the wrong order give the same FK?
Answer. No, not exactly — but the error is smaller than for mixed axes. If all joints are about the same axis (
z), the rotations commute, and the reversed order is the same product up to the translations’ interleaving. The translations between the rotations do not commute with the rotations (aTrans_x(a)followed byRz(q)is not the same asRz(q)followed byTrans_x(a)), so the reversed order is different even for a planar arm. The error is smaller than for mixed axes (because the rotations themselves commute, so the “main” error is the translation-rotation non-commutativity), but it is still non-zero.
WE-L.2.d — Contrastive near-miss: the wrong-direction error
Correct solution. The camera’s pose in the end-effector is T_EE^Cam, and the object’s position in the base is p^B = T_B^EE · T_EE^Cam · p^Cam.
Near-miss. The learner uses T_Cam^EE = (T_EE^Cam)^{-1} in place of T_EE^Cam. The object’s position is p^B = T_B^EE · T_Cam^EE · p^Cam, which is the inverse of the correct composition.
Why the near-miss produces a plausible but incorrect number. The near-miss’s T_Cam^EE is a legal SE(3) element, and the composition runs; the object’s position is some position, but it is the inverse of the intended. The camera’s calibration T_EE^Cam is usually a small translation (the camera is near the end-effector), so the inverse T_Cam^EE is also a small translation, and the two are close. The error is small and silent, and the object’s position is slightly off — enough to miss a grasp, but not enough to raise an alarm. The sign of the small translation is wrong, so the object appears slightly closer or farther than it is, and the policy is trained on the wrong position.
The discrepancy, stated. T_EE^Cam and T_Cam^EE differ by an inverse, and the inverse of a small translation is a small translation in the opposite direction. The two are close in magnitude (the inverse of a small displacement is a small displacement), so the error is small but systematic (always in the wrong direction). The fix is the naming discipline (§L.7), which makes the direction explicit in the code’s variable name, so the wrong-direction error is caught at the name rather than at the output.
Self-explanation 7. Before reading on: in the near-miss, if T_EE^Cam = (I, (0, 0, 0)) (the camera is at the end-effector’s origin), is the error visible?
Answer. No — the identity is its own inverse, so
T_EE^Cam = T_Cam^EE = I, and the error is invisible. The error is visible only when the transform is not its own inverse, which is the typical case (the camera is displaced from the end-effector). The small-displacement case is the dangerous case, because the error is small enough to escape detection but large enough to cause a miss.
§L.2.3 — Comparative table: the four canonical sign errors
| Error | Wrong matrix | Correct matrix | Symptom | Magnitude | Blind spot where the error vanishes |
|---|---|---|---|---|---|
| Inverse rotation | (Rᵀ, t) | (R, t) | The object rotates the wrong way; the position mirrors across the rotation axis | 2‖t_B‖ sin θ_i (the reach term) | θ = 0 and θ = π |
| Inverse translation | (Rᵀ, −t) | (Rᵀ, −Rᵀt) | The position is off by a constant | ‖(R − I)t‖, exactly 2a for a link offset | t on the rotation axis, and t = 0 |
| Wrong composition order | T_n·…·T_1 | T_1·…·T_n | Correct at q = 0, drifts at q ≠ 0 | The BCH correction, growing with the angles | q = 0, where every factor is a translation and translations commute |
| Wrong direction | T_B^A | T_A^B | The end-effector at the “opposite” location; the object twice as far | 2d on a camera offset of d | A self-inverse transform: I, or a half-turn with an on-axis translation |
Design principle (P8.6). Diagnose by the blind spot, not by the symptom. Each error is invisible on a set of configurations that the others do not share: the inverse-rotation error vanishes at θ = 0 and π, the inverse-translation error vanishes when the translation is axial, the wrong-order error vanishes at q = 0, and the wrong-direction error vanishes at every self-inverse transform. A test suite that lives entirely inside those sets passes on all four. The diagnostic is to pick the configuration where the candidate error is largest — a bent joint, an off-axis translation, a non-zero q, a rotation that is not its own inverse — and to run one test per error there.
§L.2.4 — Concept check
Concept check (displaces M3). A learner says: “The inverse of
(R, t)is(Rᵀ, −t)— transpose the rotation, negate the translation. That is what transpose-and-negate means.” Which model is missing, and what bug class follows?Answer. The missing model is that the two blocks of a pose live in different frames, so they cannot be inverted by the same operation.
Ris a map between frames and inverts by transpose;tis a vector expressed in the parent frame, and the inverse’s translation must be expressed in the child frame, which requires rotating it first:−Rᵀt. The bug class is an inverse that is nearly right:T⁻¹_naive · Thas translationRᵀt − t, so the product is a legalSE(3)element, close to the identity when the translation is small or nearly axial, and the violation ofT⁻¹T = Iis the only witness. In the running case the error is exactly2aper joint — 0.4 m — independent of the joint’s angle and of the chain’s length, so it is large enough to miss every grasp and small enough to look like calibration drift.
§L.2.5 — Consolidation checkpoint 2
(a) You should now be able to:
(i) name the four errors and the matrix substitution each one makes;
(ii) state each error’s magnitude formula and the configurations on which it vanishes;
(iii) run the four discriminating tests — rotation direction, T⁻¹T = I, q = 0 against q ≠ 0, and the “opposite pose” check — and say which error each one isolates.
(b) Transfer probe. The running case’s error sits at joint 3 with q = [0, 0, π/2, 0, 0, 0], and the joint transform is inverted. Predict the end-effector error before computing, then check it against §L.4.1.
Answer sketch: joint 3 is bent (θ₃ = π/2) with a downstream reach t_B = 3a = 0.6 along x, so the reach term is (Rᵀ − R)t_B = (0, 0, 1.2) — the rotation is about y, so the reach swings into z; the offset term is −(I + R)X = (−0.2, 0, 0.2); the sum is (−0.2, 0, 1.4) and the error is 1.4142. The two terms are not orthogonal here — the reach is along z and the offset has an x and a z component — so the magnitudes do not add in quadrature, and the total is neither 1.2 + 0.28 nor √(1.2² + 0.28²). Joint 3 is the expensive one in this configuration even though it is neither first nor last: it is bent, so the reach term fires, while joints 1 and 2 sit at θ = 0 and contribute exactly 2a despite having longer downstream chains.
(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 writing the two terms of (T_i⁻¹ − T_i) separately before combining them.
Part C — Conventions in Code: Naming, Direction, and the Review Checklist
§L.3 — Objects: the naming discipline and the Transform class
The naming discipline is a convention that makes each transform’s direction explicit in its name, so the code’s correctness can be verified from the names alone. The two common conventions are:
- The “from-to” convention:
T_A_Bmeans “the transform fromAtoB” — the matrix that takesA-coordinates and producesB-coordinates. This is the pose ofAinB, equivalentlyT_B^A. - The “pose-in” convention:
T_A_Bmeans “the pose ofBinA” — the matrix that takesB-coordinates and producesA-coordinates. This is the pose ofBinA, equivalentlyT_A^B.
The two conventions are opposite: T_A_B from-to is T_B^A pose-in, and T_A_B pose-in is T_A^B. The naming is ambiguous unless the convention is stated, which is the source of the sign errors in code reviews. The fix is to state the convention in the variable’s name:
T_A_to_BorT_A_from_B— explicitly “from-to” or its reverse.T_B_in_AorT_A_of_B— explicitly “pose-in.”- The best convention: write the effect in the code, so the direction is stated by the variable’s use:
p_W = T_WB @ p_B— theT_WBtakesB-coordinates and producesW-coordinates, and the subscript order matches the input/output order (WfromB,p_B→p_W). This is the from-to convention, with the variable’s name matching thep_W = T_WB @ p_Bpattern.
The Transform class in code is a wrapper that stores (R, t) and provides the operations (compose, inverse, apply_point, apply_vector, from_rotation_and_translation, etc.). The class’s convention (whether it stores T_A^B or T_B^A) is a design choice that must be documented, and the class’s methods should make the direction explicit:
T.compose(U)returnsT ∘ U— the composition T after U, which isT @ U.T.inverse()returnsT^{-1}— the reverse.T.apply_point(p)returnsT @ [p; 1]— the active/passive action on a point.
§L.3.1 — Result: the naming discipline makes the direction explicit, and the discipline’s cost is zero while the sign error’s cost is high
Claim. A codebase with a stated naming convention (from-to or pose-in, but not both) has zero wrong-direction errors by construction, because the direction is stated in every variable’s name and every composition’s order. A codebase without a stated convention has frequent wrong-direction errors, because the direction is inferred from context and context is inconsistent.
Proof sketch. The variable’s name is the code’s only persistent statement of the transform’s direction; the composition’s order is a local statement that can be wrong if the names’ conventions differ. Stating the convention in the names makes the composition’s order derivable from the names: if T_A_to_B and T_B_to_C are composed, the result must be T_A_to_C (the B cancels), and any other order is provably wrong from the names alone. Without the stated convention, the names are ambiguous, and the composition’s correctness is not derivable.
Corollary. The naming discipline’s cost is a few extra characters per variable; its benefit is the elimination of the four sign errors in the code’s structure, before any run. The benefit is high because the errors are silent and their cost (wrong policy, missed grasp, wrong SLAM trajectory) is large.
Corollary. The Transform class’s convention (which of T_A^B or T_B^A it stores) must be documented once, in the class’s docstring, and every method’s direction must be stated in its docstring. The class’s methods should not rely on the caller’s naming convention; they should state the direction in the method’s name (to, from, in, of) or in the docstring.
§L.3.2 — Worked-example ladder
WE-L.3.a — Fully worked: the naming discipline in a four-frame pipeline
Input. The running case’s four frames: W (world), B (base), EE (end-effector), Cam (camera), Obj (object). The transforms are T_WB, T_BEE, T_EECam, T_CamObj — each T_from_to with the from-to convention.
Trace (composition by name).
- The object’s pose in the world is
T_WObj = T_WB · T_BEE · T_EECam · T_CamObj. - The names show the composition:
WfromB,BfromEE,EEfromCam,CamfromObj— each factor’s source is the next factor’s target, so the products cancel the intermediate frames, leavingWfromObj. The composition is correct by construction, and any other order would leave mismatched frames (e.g.,T_WB · T_EECamhasBandEEmismatched, which is a type error in the frame algebra).
Trace (the same in the pose-in convention).
- If the convention is pose-in, the transforms are
T_W^B,T_B^EE,T_EE^Cam,T_Cam^Obj, and the composition isT_W^Obj = T_W^B · T_B^EE · T_EE^Cam · T_Cam^Obj. The names show the composition:BinW,EEinB,CaminEE,ObjinCam— each factor’s target is the next factor’s source, and the products cancel the intermediate frames, leavingObjinW. The composition is correct by construction, and the same numbers result (because the two conventions are the same matrices when the names are read correctly).
Output and interpretation. The from-to and pose-in conventions give the same composition when read correctly, because T_W^Obj = T_WB · T_BEE · … in both cases — the names are different (T_WB vs. T_W^B), but the matrices are the same. The sign error arises when a code mixes the two conventions: a variable named T_WB in one convention is T_W^B in the other, and the composition’s order is wrong if the mixing is not caught. The discipline is to pick one and use it everywhere.
Self-explanation 8. Before reading on: in step 1, the composition’s order is
T_WB · T_BEE · …. What is the rule for the order, and how does it generalize to any chain?Answer. The rule: the source of each factor is the target of the previous factor, so
T_WB(targetB) is followed byT_BEE(sourceB), which is followed byT_EECam(sourceEE), etc. The intermediate frames cancel in pairs, leavingWfromObj. The rule generalizes to any chain: the transforms’ subscripts are read right-to-left in the product (the rightmost is applied first), and the intermediate frames must match between adjacent factors. If they do not, the composition is wrong, and the mismatch is the sign error.
Self-explanation 9. Before reading on: in step 3, the pose-in convention’s composition is the same product as step 1’s, even though the names are different. Why?
Answer. Because the matrices are the same —
T_W^B(pose ofBinW) andT_WB(fromBtoW) are the same matrix, since “fromBtoW” and “pose ofBinW” are the same map (W-coordinates fromB-coordinates). The two conventions are notation, not different mathematics, and the products are the same when the names are read correctly. The sign error is when the name is read in the wrong convention, giving the inverse.
WE-L.3.b — Completion problem
Input. The same pipeline, but the code has T_EE_Cam (from-to) and T_B^EE (pose-in) mixed.
Trace (final 25% as prompts).
- The code writes
T_B_Cam = T_B^EE · T_EE_Cam. ⟨your turn: is the composition correct by the names, and what doesT_B_Cammean in each convention?⟩ - The correct composition is
T_B_Cam = T_B^EE · T_EE^Cam(pose-in) orT_B_Cam = T_BE · T_EECam(from-to). ⟨your turn: areT_EE_CamandT_EE^Camthe same matrix?⟩ - The fix: ⟨your turn: state the naming discipline that would have prevented the mixing⟩
Answers.
T_B^EE(pose-in) isT_B^EE, andT_EE_Cam(from-to) isT_Cam^EE— the inverse of the intended. So the compositionT_B^EE · T_Cam^EEisT_B^EE · (T_EE^Cam)^{-1}, which is not the intendedT_B^EE · T_EE^Cam. The composition is wrong, and the error is the wrong-direction error on the camera’s calibration.T_EE_Cam(from-to) isT_Cam^EE(pose-in) — the inverse ofT_EE^Cam. They are not the same matrix; they are inverses.- The fix is to pick one convention and use it everywhere: either use from-to throughout (
T_WE,T_BE,T_EECam,T_CamObj) or pose-in throughout (T_W^B,T_B^EE,T_EE^Cam,T_Cam^Obj). The mixed convention is the source of the error, and the fix is to eliminate the mixing.
The lesson. The naming discipline’s value is that the convention is stated in the names, so a mixing of conventions is visible in the variable’s type (the subscript format _ vs. ^). A code with a single convention is correct by construction; a code with a mixed convention has the sign errors, and the fix is to homogenize the convention.
WE-L.3.c — Faded: the “effect in the code” pattern
Setup. A codebase uses the “effect in the code” pattern: p_W = T_WB @ p_B — the variable T_WB is the transform that takes p_B and produces p_W.
Question. What is the name’s convention, and what does the variable’s use say about its direction?
You must produce the intermediate steps. The setup and the answer are given.
Answer. The name T_WB with the effect p_W = T_WB @ p_B is the from-to convention: T_WB takes B-coordinates and produces W-coordinates (from B to W). The effect (p_W on the left, p_B on the right) states the direction unambiguously, and the name’s subscripts (W then B) match the effect’s order (W from B). The use is the convention’s statement, and the name is the reminder.
The lesson. The “effect in the code” pattern makes the convention self-documenting: the variable’s name and its use are consistent, and the direction is stated twice (in the name’s subscript order and in the p_to = T_to_from @ p_from pattern). The double statement is the check: if the name and the use disagree, the code is wrong, and the check is mechanical.
WE-L.3.d — Contrastive near-miss: T_A^B and T_B^A written as T_AB
Correct solution. The code uses T_A_to_B and T_B_to_A as distinct variables, and the composition’s order is checked by the names’ subscripts.
Near-miss. The code uses T_AB for both the pose of B in A and the pose of A in B — the same variable name for two different matrices. The composition’s order is checked by the code’s comments (which may be outdated), not by the names, and the wrong-direction error is invisible in the code review.
Why the near-miss produces a plausible but incorrect number. The variable’s value is one of the two matrices, and the composition’s order depends on which one. If the variable is the pose of B in A (i.e., T_A^B), the composition T_AB · p^B = p^A is correct; if the variable is the pose of A in B (i.e., T_B^A), the same composition gives T_B^A · p^B, which is (T_A^B)^{-1} · p^B — the wrong point. The variable’s name does not distinguish the two, so the code reviewer cannot tell which one the variable holds, and the error is silent.
The discrepancy, stated. T_AB with the pose of B in A vs. T_AB with the pose of A in B are different matrices (inverses) with the same name. The name’s ambiguity is the source of the error, and the fix is the naming discipline (T_A_to_B or T_B_to_A, or T_A^B with the ^ superscript convention), which makes the two distinct in the code.
Self-explanation 10. Before reading on: in the near-miss, if the code uses
T_ABfor both the pose ofBinAand the pose ofAinB, what is the only way the code could be correct?Answer. The code could be correct only if
T_ABand(T_AB)^{-1}are the same matrix, which happens only for transforms that are their own inverse (the identity, or a 180° rotation with a translation on the axis). For all other transforms, the two readings are different matrices, and the code is wrong in at least one of the two uses. The same name for two different matrices is the worst naming error, because it is silent, systematic, and only visible at the “coincidentally-inverse” configurations.
§L.3.3 — Comparative table: conventions for naming a transform
| Convention | Name reads | T_A_B means | Composition rule | Ambiguity | Cost |
|---|---|---|---|---|---|
| From-to | “A from B” | the map taking B-coordinates to A-coordinates, i.e. T_A^B | source of each factor is the target of the next | None once stated | A few characters |
| Pose-in | “pose of B in A” | the map taking B-coordinates to A-coordinates, i.e. T_A^B | target of each factor is the source of the next | None once stated | A few characters |
Bare T_AB | nothing stated | undetermined — either T_A^B or T_B^A | must be recovered from comments | Total — the same name for two matrices | Silent, systematic |
| “Effect in the code” | p_W = T_WB @ p_B | the map taking p_B and returning p_W | subscript order matches input → output | None — the use states it | One extra line of code |
| Mixed | depends on the variable | whatever that variable happened to hold | not derivable | The mixing itself | An inverted factor somewhere in the chain |
Design principle (P8.7). Let the name carry the direction, and let the composition check the name. A convention stated once in a README decays; a convention stated in every variable survives refactoring, because the name travels with the code and the reviewer reads it at the call site. The check is mechanical: in a correct chain every intermediate frame appears exactly twice, once as a target and once as a source. A frame that appears once or three times is an error, and the audit finds it before the code runs.
§L.3.4 — Concept check
Concept check (displaces M5). A learner says: “The variable is named
T_base_ee, so I know the convention — it is the end-effector’s pose in the base.” What has the learner assumed, and what bug class does the assumption produce?Answer. The learner has assumed that the subscript order is the pose-in convention, with the first subscript the frame the pose is expressed in. That is one of the two readings, and the other — from-to,
basefromee, which is the same matrix — is equally consistent with the name; worse, a codebase that writesT_base_eefor the from-to map holdsT_ee^base, the inverse, and the name reads identically either way. The bug class is the mixed-convention chain: one factor in a product is inverted relative to its neighbors, the composition still multiplies to a legalSE(3)element, and the object lands at a pose that is off by one inverse. The fix is not to read the name more carefully but to write a name that cannot be read two ways.
§L.3.5 — Consolidation checkpoint 3
(a) You should now be able to:
(i) state the from-to and pose-in conventions and the composition rule each implies;
(ii) read a chain of T_from_to names and cancel the intermediate frames;
(iii) state what a bare T_AB leaves undetermined and what it costs in a review.
(b) Transfer probe. A PR contains T_WB, T_BEE, T_EECam and T_CamObj, and the composed result puts the object behind the robot. The names cancel correctly. Where do you look first, and what is the test?
Answer sketch: first at whether all four names are in the same convention, because a chain of correctly-canceling names is only correct if the names mean what the cancellation assumed. A single factor written in the other convention is T_EE^Cam where T_Cam^EE was intended, and the cancellation still “works” on the letters while the matrices multiply to the wrong product. The test is a point with a known answer in both frames — the camera’s own origin, which must map to T_EECam’s translation — checked once per factor, not once per chain. Cancelling the letters tests the naming; only a known point tests the matrices.
(c) Remediation rule. If you cannot do (a)(i) or (b), return to §L.3.2 and redo WE-L.3.b, then re-attempt (b) by writing each factor’s matrix in both conventions and seeing which one breaks the chain.
Part D — The Compound-Frame Cascade
§L.4 — Objects: how a single sign error propagates
A compound-frame pipeline has a chain of transforms T_1 · T_2 · … · T_n, where each T_i is a local transform (a pose of one frame in the next). A sign error in one factor (using T_i^{-1} instead of T_i) changes the composed transform by an amount that depends on the downstream factors:
and the difference T_{\text{wrong}} · T_{\text{correct}}^{-1} is the error’s propagation — it is the composition of the wrong factor’s error and the downstream factors’ transforms.
§L.4.1 — Result: the error at the end-effector is the joint’s own offset error plus a reach term that the joint’s rotation gates
Claim. Write X = (a, 0, 0) for the link offset, R_i for the joint’s rotation, t_B for the translation of the downstream product T_{i+1}·…·T_n (the vector from joint i’s origin to the end-effector, in joint i’s frame), and R_A = R_1·…·R_{i-1} for the upstream rotation. Then inverting T_i gives the end-effector error
Proof sketch. The correct product is …·$T_i$·B and the wrong one is …·$T_i^{-1}$·B, where B = $T_{i+1}$·…·$T_n$ has translation $t_B$. The end-effector is the translation of the product, so the difference in joint i’s frame is ($T_i^{-1}$ − $T_i$)·[$t_B$; 1]. Blockwise, $T_i$ = [[R, RX],[0,1]] and $T_i^{-1}$ = [[Rᵀ, −RᵀRX],[0,1]], so the translation rows give −RᵀRX − RX for the constant part and (Rᵀ − $R)t_B$ for the part proportional to the reach. Premultiply by $R_A$ to return to the base frame.
Consequences.
- The reach term is gated by
sin $θ_i$.Rᵀ − Rvanishes at$θ_i$ = 0and at$θ_i$ = π, so a joint sitting at its zero pose contributes only−(I + R)X, whose magnitude is2a. Its downstream chain can be arbitrarily long and it contributes nothing extra. This is the point the “first joint is the most expensive” intuition gets wrong: cost is set by the joint’s own angle, not by its position in the chain. - The offset term is
2a cos(θ_i/2), since‖(I + R)X‖ = 2a|cos(θ_i/2)|forXperpendicular to the axis. It is largest at$θ_i$ = 0— exactly where the reach term vanishes — and falls to zero at$θ_i$ = π, where the reach term also vanishes. The two terms trade off. - The reach term is at most
2‖$t_B$‖, attained at$θ_i$ = π/2when$t_B$is perpendicular to the axis. So‖$e_i$‖ ≤ 2(‖$t_B$‖ + a), and the bound is not tight atθ = 0orπbecause both terms vanish together there.
Corollary. A sign error at the last joint has $t_B$ = 0, so it contributes exactly 2a and nothing else. A sign error at a joint at θ = 0 also contributes exactly 2a. The expensive errors are at bent joints with long downstream reach — in the running case’s q = [0, π/2, 0, 0, 0, 0], joint 2 gives 1.8111 while joints 1 and 3–6 each give 0.4000.
Corollary. The sign errors compound if multiple are present. A code with a wrong-direction error at the camera and a wrong-inverse at the arm has an end-effector error that is the composition of the two, and the diagnostic must isolate each.
§L.4.2 — Worked-example ladder
WE-L.4.a — Fully worked: a sign error at joint 2 of the running case
Input. The running case’s FK at q = [0, π/2, 0, 0, 0, 0] — the arm has joint 1 at 0 and joint 2 at π/2. A learner uses $T_2^{-1}$ instead of $T_2$ at joint 2.
Trace (correct FK).
T_2(π/2) = Ry(π/2)·Trans_x(a). Its translation is$t_2$ = Ry(π/2)·(a, 0, 0) = (0, 0, -a), and$T_1(0$) = Trans_x(a)has$t_1$ = (a, 0, 0).- Joints 3–6 sit at
q = 0, so each contributesTrans_x(a). Collapsing them from the end:$t_6$ = (a,0,0), then$t_5$ + $t_6$ = 2a,$t_4$ + 2a = 3a,$t_3$ + 3a = 4a(all alongx, since$R_3$…$R_5$ = I). So the chain downstream of joint 2 reaches4afrom joint 2’s origin. - Fold joint 2 back in:
$t_2$ + $R_2$·(4a, 0, 0) = (0,0,-a) + (0,0,-4a) = (0, 0, -5a). Then$t_1$ + $R_1$·(0,0,-5a) = (a, 0, -5a). - End-effector:
(a, 0, -5a) = (0.2, 0, -1.0)(matches0.2.3’s chain FK).
Trace (wrong FK).
- Invert joint 2:
$R_2^{-1}$ = Ry(-π/2)and$t_2^{-1}$ = $-R_2^{T}$ $t_2$ = -Ry(-π/2)·(0,0,-a) = -(a, 0, 0) = (-a, 0, 0). So$T_2^{-1}$ = [[Ry(-π/2), (-a, 0, 0)], [0, 1]]. - Refold:
$t_2^{-1}$ + $R_2^{-1}$·(4a, 0, 0) = (-a,0,0) + (0,0,4a), and the upstream factors are unchanged, so the end-effector is(a,0,0) + (-a,0,0) + (0,0,4a) = (0, 0, 4a) = (0, 0, 0.8).
Output and interpretation. Correct (a, 0, -5a), wrong (0, 0, 4a) — the error is wrong − correct = (-a, 0, 9a), magnitude a√82 = 1.8111. It splits by §L.4.1 into two terms: the offset term −(I + $R_2)X$ = (−a, 0, a), of magnitude a√2 = 0.2828, and the reach term ($R_2^{T}$ − $R_2)t_B$ with $t_B$ = (4a, 0, 0), giving (0, 0, 8a) of magnitude 1.6. The reach term is 5.7× larger: the joint’s own offset is the small part, and the flipped downstream reach is the large one.
Self-explanation 11. Before reading on: §L.4.1 says the reach term is
(Rᵀ − $R)t_B$. Which joint angles make that term vanish, and what does that imply about which joint in a chain is the most dangerous to invert?Answer.
Rᵀ − R = −2 sin θ · [axis]_×, so the term vanishes atθ = 0and atθ = π— the joint’s own angle, independent of the chain’s shape or the joint’s position. So the most dangerous joint is not the first or the last but the most bent one with the longest downstream reach. In the running case’sq = [0, π/2, 0, 0, 0, 0], joint 2 (bent90°, reach4a) gives1.8111, while joint 1 (straight, reach5a) gives only0.4000— the straight joint’s larger reach buys nothing because its rotation gates the term to zero.
WE-L.4.b — Completion problem
Input. The running case’s error is at the first joint ($T_1^{-1}$ instead of $T_1$), with the other joints at q = 0.
Trace (final 25% as prompts).
- Correct FK at
q = [π/2, 0, 0, 0, 0, 0]: ⟨your turn: compute the end-effector position⟩ - Wrong FK with
$T_1^{-1}$: ⟨your turn: compute the end-effector position⟩ - The error: ⟨your turn: compute the difference and its magnitude⟩
Answers.
T_1(π/2) = Rz(π/2)·Trans_x(a), so$t_1$ = Rz(π/2)·(a,0,0) = (0, a, 0). Joints 2–6 atq = 0contribute5aalongxin joint 1’s frame, so the end-effector is(0, a, 0) + Rz(π/2)·(5a, 0, 0) = (0, a, 0) + (0, 5a, 0) = (0, 6a, 0).$T_1^{-1}$hasR = Rz(-π/2)andt = $-R^{\top}t_1$ = -Rz(-π/2)·(0,a,0) = -(a, 0, 0) = (-a, 0, 0). End-effector:(-a, 0, 0) + Rz(-π/2)·(5a, 0, 0) = (-a, 0, 0) + (0, -5a, 0) = (-a, -5a, 0).- Error:
(-a, -5a, 0) − (0, 6a, 0) = (-a, -11a, 0), magnitudea√122 = 2.2091. Decomposed by §L.4.1’s formula with$R_A$ = I,$θ_1$ = π/2and$t_B$ = (5a, 0, 0): the reach term(Rᵀ − $R)t_B$contributes(0, -10a, 0)and the offset term−(I + R)Xcontributes(-a, -a, 0).
The lesson. Joint 1’s error here is larger than joint 2’s in the previous example, and the reason is not that joint 1 is first — it is that joint 1 is bent ($θ_1$ = π/2, so the reach term fires) while joint 2 in WE-L.4.a was also bent but had a shorter downstream reach (4a against 5a). Both ingredients matter, and the position in the chain enters only through the reach ‖$t_B$‖. A joint at θ = 0 contributes 2a wherever it sits.
WE-L.4.c — Faded: the sign error’s propagation in a multi-frame pipeline
Setup. The pipeline is $T_WObj$ = $T_WB$ · $T_BEE$ · $T_EECam$ · $T_CamObj$. The error is $T_CamObj^{-1}$ instead of $T_CamObj$.
Question. What is the wrong $T_WObj$, and what is the error’s magnitude?
You must produce the intermediate steps. The setup and the answer are given.
Answer. Wrong: $T_WB$ · $T_BEE$ · $T_EECam$ · $T_CamObj^{-1}$. Error: the camera-to-object transform is inverted, so the object’s position is reflected through the camera’s frame. The error’s magnitude: ‖$T_CamObj^{-1}$·p^Obj − $T_CamObj$·p^Obj‖, which is the object’s distance from the camera doubled (because the inverse of a translation (0, 0, d) is (0, 0, -d), and the difference is 2d). The error is large if the object is far from the camera, and small if the object is close.
The lesson. The camera-to-object transform’s error is amplified by the object’s distance from the camera, and the nearest object is the least affected. The pipeline’s correctness depends on the camera calibration’s sign being right, and the error is visible as the object being twice as far (or twice as close) as it should be.
WE-L.4.d — Contrastive near-miss: the “cascade” of one sign error
Correct solution. The pipeline is correct, and the object’s position is (x, y, z) in the base frame.
Near-miss. The pipeline has one sign error (the camera’s calibration), and the object’s position is (x, y, 2d − z) — the z is reflected through the camera’s plane, and the other coordinates are correct.
Why the near-miss produces a plausible but incorrect number. The object’s position is some legal 3D point, and the code runs; the position is close to the correct one if the object is near the camera (small d), and far if the object is far (large d). The near-miss’s position is “plausible” (it is on the ray from the camera to the object, but on the wrong side of the camera), and the policy trained on it is a policy that reaches behind the camera when it should reach in front — a systematic, silent error that is only visible against a ground-truth position.
The discrepancy, stated. The correct and near-miss positions differ by 2d in the camera’s axis direction, which is the object’s distance’s doubling. The error is small for close objects and large for far objects, and the near-miss’s “plausible” position is systematically on the wrong side of the camera. The fix is the naming discipline, which makes the camera-to-object transform’s direction explicit.
Self-explanation 12. Before reading on: in the near-miss, if the object is at the camera’s origin (
d = 0), is the error visible?Answer. No — the object’s position is the camera’s origin, and
$T_CamObj$is the identity, so the inverse is also the identity, and the error is invisible. The error is visible only for non-zerod, which is the typical case, and it is proportional tod— so the far objects are the most affected, and the near objects are the least.
§L.4.3 — Comparative table: where an inversion lands in a chain
| Error site | $t_B$ (downstream reach) | Reach term (Rᵀ − $R)t_B$ | Offset term −(I + R)X | Measured error, q = [0, 0, π/2, 0, 0, 0] | Why |
|---|---|---|---|---|---|
| Last joint | 0 | 0 | 2a cos(θ/2) | 0.4000 | Nothing downstream to flip |
Joint at θ = 0 | any | 0 — Rᵀ − R vanishes | 2a | 0.4000 | A straight joint’s rotation gates its own reach term to zero |
| Bent joint, short reach | 3a | up to 2‖$t_B$‖ | 2a cos(θ/2) | 1.4142 (joint 3) | The reach term fires |
| Bent joint, long reach | 4a | up to 2‖$t_B$‖ | 2a cos(θ/2) | 1.8111 (joint 2, q = [0, π/2, 0, …]) | Both ingredients present |
| First joint, bent, reach folded | 0.8246 | 1.6492 | 0.2828 | 1.5232 (joint 1, q = [π/2, 0, π/2, 0, …]) | Reach is short because joint 3 folds the chain back |
| Camera-to-object factor | the object’s distance d | n/a — an inverse, not a transpose | n/a | 2d | The inverse of a translation d is −d |
Design principle (P8.8). An error’s size is set by the geometry downstream of it, not by its index in the chain. The formula $e_i$ = $R_A[(R_i$ᵀ − $R_i)t_B$ − (I + $R_i)X$] has two terms: one gated by the joint’s own angle through $R_i$ᵀ − $R_i$ = −2 sin $θ_i$ [axis]_×, and one gated by the link offset. Position in the chain enters only through ‖$t_B$‖, and only after the joint is bent. So the review order is “bent joints first, with the longest downstream reach”, and a joint at its zero pose costs exactly 2a wherever it sits — a rule that inverts the intuition that the first joint is the most expensive.
§L.4.4 — Concept check
Concept check (displaces M2). A learner says: “
$T_A^B$and$T_B^A$are inverses, so it doesn’t matter which one a factor holds — the chain still composes and the numbers come out.” What is wrong with the reasoning, and what does the composed pipeline do?Answer. The premise is true and the conclusion does not follow. Two inverses are both legal
SE(3)elements, so the chain does compose and the numbers do come out — they are the numbers of a different pipeline. Substituting$T_i$⁻¹for one factor changes the composed transform by($T_i$⁻¹ − $T_i$)conjugated by the downstream chain, which in the arm is$e_i$ = $R_A[(R_i$ᵀ − $R_i)t_B$ − (I + $R_i)X$]; in the camera chain it is the object reflected to the far side of the camera, at twice its distance. The bug class is an error with a downstream amplifier: it is zero atq = 0, zero at a self-inverse factor, and grows with‖$t_B$‖ord, so it looks like drift rather than like a wrong transform.
§L.4.5 — Consolidation checkpoint 4
(a) You should now be able to:
(i) write $e_i$ = $R_A[(R_i$ᵀ − $R_i)t_B$ − (I + $R_i)X$] and identify each symbol’s frame;
(ii) state which joint angles make the reach term vanish and which make the offset term vanish, and why they trade off;
(iii) predict, for a given configuration, which joint’s inversion is the most expensive.
(b) Transfer probe. A colleague argues that the first joint of a 6-DOF arm is the most dangerous to invert, “because every downstream error passes through it.” Construct the configuration that refutes this, and say what replaces the rule.
Answer sketch: take q = [0, π/2, 0, 0, 0, 0]. Joint 1 is at θ₁ = 0, so R₁ᵀ − R₁ = 0 and its error is exactly 2a = 0.4 despite a downstream reach of 5a; joint 2 is bent at π/2 with a reach of 4a and gives 1.8111. The claim confuses the chain’s geometry with the error’s mechanism: the error is created at the inverted joint, by the mismatch between R and Rᵀ, and then merely carried downstream. What replaces the rule is that a joint contributes 2a when straight and up to 2(‖$t_B$‖ + a) when bent, so the review order is by sin $θ_i$ · ‖$t_B$‖.
(c) Remediation rule. If you cannot do (a)(ii) or (b), return to §L.4.2 and redo WE-L.4.a, then re-attempt (b) by evaluating Rᵀ − R at θ = 0 and at θ = π/2 before looking at the reach.
Part E — Failure Taxonomy, Lab, Cheat Sheet, Exercises, Exit Test
§L.5 — Failure taxonomy
| Symptom | Three most likely causes, ranked | Discriminating test | Fix |
|---|---|---|---|
| The object rotates the wrong way | (1) Inverse-rotation error (Rᵀ vs. R); (2) wrong-direction error (pose of A in B vs. B in A); (3) the convention’s sign is flipped | Compute R·u and Rᵀ·u for a test u; if the result matches the inverse, it is (1) | Use the correct R for the intended direction |
| The position is off by a constant | (1) Inverse-translation error (-t vs. -Rᵀt); (2) the naive inverse’s error, Rᵀt − t, magnitude ‖(R-I)t‖; (3) the frame’s origin is wrong | Compute T·T⁻¹ and check = I; if not, it is (1) or (2) | Use -Rᵀt (the correct inverse’s translation) |
The pose is correct at q = 0 and drifts at q ≠ 0 | (1) Wrong composition order; (2) the FK chain is reversed; (3) a joint’s twist is wrong | Compare the q ≠ 0 FK to the chain FK; if it differs by a non-trivial amount, it is (1) | Compose in the chain order (joint 1 to joint 6) |
| The end-effector is at the “opposite” location | (1) Wrong-direction error ($T_A^B$ vs. $T_B^A$); (2) the pose-of-A-in-B is used; (3) the chain’s first joint is inverted | Compute T·T⁻¹ and check = I; if it holds, the transform is a valid one and the error is which direction it encodes — compare its translation to the expected origin, and test T·T (an involution only if T is its own inverse, so a general T with T·T ≠ I is normal and says nothing) | Use $T_A^B$ for B-in-A, $T_B^A$ = ($T_A^B)^{-1}$ for A-in-B |
| The object appears twice as far as it is | (1) Camera-calibration inverse error; (2) the object’s z is reflected; (3) the depth is 2d instead of d | Compare $T_CamObj$·p and $T_CamObj^{-1}$·p; if the result matches the inverse, it is (1) | Use $T_EECam$ (correct direction), not $T_CamEE$ |
§L.6 — The lab
§L.6.1 — Task
Build a Transform class with a stated from-to convention, implement the four sign errors (one at a time), and use the running case’s pipeline to diagnose each error from its symptom.
§L.6.2 — Conditions
Python 3.10+, PyTorch 2.2+, CPU. Seed 42, dtype = float64. Runtime under 2 minutes.
§L.6.3 — Controls held fixed
Seed, dtype, a = 0.2, the test q = [0, π/2, 0, 0, 0, 0], the camera calibration $T_EECam$ = [[I, (0, 0, 0.1)], [0, 1]], and the object’s p^Cam = (0.5, 0, 1).
§L.6.4 — Metrics table
| Metric | Definition | How measured |
|---|---|---|
fk_correct | End-effector position from the correct FK | $T_BEE$ · (0; 1) |
fk_inv_rot | End-effector position with Rᵀ at joint 2 | The same, with Rᵀ |
fk_inv_trans | End-effector position with -t at joint 2 | The same, with -t |
fk_wrong_order | End-effector position with the reversed product | The same, reversed |
fk_wrong_dir | End-effector position with $T_2^{-1}$ | The same, with the inverse |
$p_W_correct$ | Object’s position in W from the correct pipeline | $T_WObj$ · p^Obj |
$p_W_wrong_dir$ | The same, with $T_EECam^{-1}$ | The same, with the inverse |
§L.6.5 — Pre-registered hypotheses
- H1.
‖fk_inv_rot − fk_correct‖ ≈ 2‖$t_B$‖ sin $θ_2$ = 1.6— replacing$R_2$with$R_2$ᵀflips the sign of the reach term(Rᵀ − $R)t_B$, whose magnitude is2 sin $θ_2$ · ‖$t_B$‖with‖$t_B$‖ = 4a = 0.8and$θ_2$ = π/2, so the prediction is≈ 1.6, i.e.> 1.0. The offset term is untouched by this particular error (onlyRchanges, nott), so the whole error is the reach term. - H2.
‖fk_inv_trans − fk_correct‖ ≈ 2a = 0.4— replacing−Rᵀtwith−tkeepsRand flips the sign of the translation, so the joint’s block changes from+RXto−RX. The end-effector error is$R_A$·(−2RX), of magnitude exactly2a = 0.4— independent of the joint’s angle and of the downstream reach, because only the constant term in the block difference survives. The factor of two is the thing to get right;aalone is the half-answer. - H3.
‖fk_wrong_order − fk_correct‖ > 0.1andfk_wrong_orderatq = 0equalsfk_correctatq = 0— the wrong-order error is zero atq = 0and large atq ≠ 0. The prediction is the pair. - H4.
‖$p_W_wrong_dir$ − $p_W_correct$‖ ≈ 0.2— the wrong-direction error on the camera is the camera’s translation doubled (the inverse of(0, 0, 0.1)is(0, 0, -0.1), and the difference is0.2). The prediction is≈ 0.2.
§L.6.6 — The break-it sequence
- Inverse-rotation (silent). Replace
RwithRᵀat joint 2. Predicted symptom: no exception; the end-effector moves by exactly the reach term(Rᵀ − $R)t_B$, magnitude1.6. Actual: silent. - Inverse-translation (silent). Replace
−Rᵀtwith−tat joint 2. Predicted symptom: no exception; the end-effector is off by$R_A$·(−2RX), magnitude2a = 0.4at every joint angle. Actual: silent. - Wrong order (silent). Reverse the product to
$T_6$·…·$T_1$. Predicted symptom: no exception; the end-effector is correct atq = 0and drifts atq ≠ 0. Actual: silent. - Wrong direction (silent). Use
$T_EECam^{-1}$instead of$T_EECam$. Predicted symptom: no exception; the object is twice as far from the camera. Actual: silent.
All four are silent.
§L.6.7 — Acceptance criteria
| Criterion | Threshold | Artefact it is tested on |
|---|---|---|
| H1’s error | ≈ 2‖$t_B$‖ sin $θ_2$ = 1.6 (measured 1.6000) | The lab’s fk_inv_rot diff |
| H2’s error | ≈ 2a = 0.4 (measured 0.4000) | The lab’s fk_inv_trans diff |
| H3’s pair | fk_wrong_order(q=0) = fk_correct(q=0) and > 0.1 at q ≠ 0 | The lab’s two prints |
| H4’s error | ≈ 0.2 | The lab’s $p_W_wrong_dir$ diff |
| Naming discipline | All variables use $T_from_to$ | The lab’s code review |
| Four break-its predicted | 4/4 timestamped | The prediction file |
§L.6.8 — What good looks like
A strong submission has five properties:
- The correct FK is computed once, and the four errors are computed as deltas. The submission shows the four differences, not four separate end-effector positions, so the errors’ magnitudes are visible.
- The naming discipline is in the code. Every transform is
$T_from_to$(or$T_to_from$), and the composition’s order is derivable from the names alone. - The wrong-order error’s
q = 0agreement is shown. The submission prints bothfk_wrong_order(q=0)andfk_correct(q=0), and the equality is the demonstration that the error is invisible at the identity. - The wrong-direction error’s “doubling” is shown. The submission prints
$p_W_wrong_dir$and$p_W_correct$, and the difference is2d— the object’s distance from the camera’s doubling. - The four break-its are predicted before running, with numeric bounds, and the actuals are compared.
§L.6.9 — Runnable lab code
# lab_0_2_4.py — The four sign errors, one at a time
# 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 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 invert(T):
R, t = T[:3,:3], T[:3,3]
return make_T(R.T, -R.T @ t)
def invert_naive(T):
R, t = T[:3,:3], T[:3,3]
return make_T(R.T, -t)
# --- The running case's chain (alternating z, y) ---
$A_LINK$ = 0.2
AXES = ['z','y','z','y','z','y']
def chain_fk(q, wrong_at=None, error=None):
a = $A_LINK$
axes = AXES
Ts = []
for i, ax in enumerate(axes):
R = rot_z(q[i]) if ax == 'z' else rot_y(q[i])
# $T_i$ = $Rot(q_i$)·Trans_x(a), so the offset is expressed in the ROTATED
# frame: $t_i$ = R·[a,0,0]. Using [a,0,0] here is the bug 0.2.1 names.
off = R @ torch.tensor([a,0.,0.], dtype=dtype, device=device)
T = make_T(R, off)
if wrong_at == i and error == 'inv_rot':
T = make_T(R.T, off)
elif wrong_at == i and error == 'inv_trans':
T = make_T(R, -off)
elif wrong_at == i and error == 'wrong_dir':
T = invert(T)
Ts.append(T)
if error == 'wrong_order':
Ts = list(reversed(Ts))
T = torch.eye(4, dtype=dtype, device=device)
for t in Ts: T = T @ t
return T
q = torch.tensor([0., math.pi/2, 0., 0., 0., 0.], dtype=dtype, device=device)
$T_correct$ = chain_fk(q)
$p_correct$ = $T_correct$[:3, 3]
$p_chain$ = $p_correct$.clone() # H1/H2 compare against the chain's EE, not the camera point
print(f"correct: {p_correct.tolist()}") # (a, 0, -5a) = (0.2, 0, -1.0)
for err in ['inv_rot', 'inv_trans', 'wrong_order', 'wrong_dir']:
$T_wrong$ = chain_fk(q, wrong_at=1, error=err)
$p_wrong$ = $T_wrong$[:3, 3]
print(f"{err}: {p_wrong.tolist()}, diff = {(p_wrong - $p_correct$).norm().item():.4f}")
# --- q = 0 check for wrong_order ---
q0 = torch.zeros(6, dtype=dtype, device=device)
print(f"q=0 correct: {chain_fk(q0)[:3,3].tolist()}")
print(f"q=0 wrong_order: {chain_fk(q0, error='wrong_order')[:3,3].tolist()}")
# --- per-joint error, to test §L.4.1's claim that position alone does not set cost ---
print$"\njoint angle(deg) downstream reach error"$
for i in range(6):
e = (chain_fk(q, wrong_at=i, error='wrong_dir')[:3,3] - $p_correct$).norm().item()
reach = torch.eye(4, dtype=dtype, device=device)
for j in range(i + 1, 6):
Rj = rot_z(q[j]) if AXES[j] == 'z' else rot_y(q[j])
reach = reach @ make_T(Rj, Rj @ torch.$tensor([A_LINK$, 0., 0.], dtype=dtype, device=device))
print(f"{i+1:5d} {math.degrees(q[i].item()):10.1f} {reach[:3,3].norm().item():16.4f} {e:8.4f}")
# --- camera pipeline ---
$T_BEE$ = chain_fk(q)
$T_EECam$ = make_T(torch.eye(3, dtype=dtype, device=device), torch.tensor([0.,0.,0.1], dtype=dtype, device=device))
$T_CamEE$ = $invert(T_EECam$)
$p_Cam$ = torch.tensor([0.5, 0., 1.], dtype=dtype, device=device)
$p_correct$ = ($T_BEE$ @ $T_EECam$ @ torch.$cat([p_Cam$, torch.ones(1, dtype=dtype, device=device)]))[:3]
$p_wrong$ = ($T_BEE$ @ $T_CamEE$ @ torch.$cat([p_Cam$, torch.ones(1, dtype=dtype, device=device)]))[:3]
print(f"$p_W$ correct: {p_correct.tolist()}")
print(f"$p_W$ wrong_dir: {p_wrong.tolist()}, diff = {(p_wrong - $p_correct$).norm().item():.4f}")
print("\n--- summary ---")
print(f"H1 inv_rot diff: {(chain_fk(q, 1, 'inv_rot')[:3,3] - $p_chain$).norm().item():.4f}")
print(f"H2 inv_trans diff: {(chain_fk(q, 1, 'inv_trans')[:3,3] - $p_chain$).norm().item():.4f}")
print(f"H3 wrong_order(q=0) == correct(q=0): {torch.allclose(chain_fk(q0, error='wrong_order')[:3,3], chain_fk(q0)[:3,3])}")
print(f"H4 wrong_dir diff: {(p_wrong - $p_correct$).norm().item():.4f}")
§L.7 — The cheat sheet
| Term or result | Statement or definition | Role |
|---|---|---|
| Active reading | T moves the body; the frame is fixed | Introduced here; consumed by 1.1.1 (FK) |
| Passive reading | T changes the frame; the body is fixed | Introduced here; consumed by 2.4.1 (SLAM) |
| Pose reading | $T_A^B$ is the pose of B in A | Introduced here; consumed by every multi-frame lesson |
| Inverse reading | $T_B^A$ = ($T_A^B)^{-1}$ is the pose of A in B | Introduced here; consumed by 2.1.1 (pose estimation) |
| Inverse-rotation error | Using Rᵀ where R is intended | Introduced here; consumed by 3.1.2 (VLA pose) |
| Inverse-translation error | Using -t where -Rᵀt is intended | Introduced here; consumed by 2.4.2 (SLAM) |
| Wrong-order error | Using T₂·T₁ where T₁·T₂ is intended | Introduced here; consumed by 1.1.1 (FK) |
| Wrong-direction error | Using $T_A^B$ where $T_B^A$ is intended | Introduced here; consumed by 2.1.1 |
| From-to convention | $T_A_to_B$ is the from-to map; $p_B$ = $T_A_to_B$ @ $p_A$ | Introduced here; consumed by every code pipeline |
| Pose-in convention | $T_A^B$ is the pose of B in A | Introduced here; consumed by every robotics paper |
| Naming discipline | Pick one convention; use it everywhere | Introduced here; consumed by every code review |
| Compound-frame cascade | A sign error at joint i propagates through the downstream chain | Introduced here; consumed by 1.1.1 |
| Design principle P8.1 | The same matrix has three readings; the inverse is the fourth | Introduced here; consumed by every multi-frame pipeline |
| Design principle P8.2 | Pick one naming convention and use it everywhere | Introduced here; consumed by every code review |
| Design principle P8.3 | Verify each transform’s direction from its name, not its comment | Introduced here; consumed by 1.1.1, 2.1.1 |
| Design principle P8.4 | Test at q = 0 and q ≠ 0; the sign errors are invisible at the identity | Introduced here; consumed by 1.1.1’s lab |
| Design principle P8.5 | A transform’s correctness is a fact about its direction, not its entries | Introduced here; consumed by every multi-frame pipeline |
| Design principle P8.6 | Diagnose by the blind spot, not by the symptom | Introduced here; consumed by 1.1.1, 2.4.1 |
| Design principle P8.7 | Let the name carry the direction, and let the composition check the name | Introduced here; consumed by every code review |
| Design principle P8.8 | An error’s size is set by the geometry downstream of it, not its index | Introduced here; consumed by 1.1.1’s lab |
§L.8 — Exercises with solutions
E 0.2.4.1 — The three readings (numerical). T = (Rz(π), (1, 0, 0)), p^B = (1, 0, 0). Compute the pose reading, the active reading, the passive reading, and the inverse reading for p^B.
S 0.2.4.1. Pose: B is at (1, 0, 0) with its x-axis along −x of A. Forward reading: p^A = R p^B + t = Rz(π)·(1,0,0) + (1,0,0) = (−1,0,0) + (1,0,0) = (0,0,0). Inverse reading: p^B = Rᵀ(p^A − t) = Rz(−π)·((0,0,0) − (1,0,0)) = Rz(−π)·(−1,0,0) = (1,0,0) ✓ — the round trip returns the original point. The trap: this T is an involution, since Rz(π) = Rz(π)ᵀ and −Rᵀt = −(−1,0,0) = (1,0,0) = t, so T⁻¹ = T. Applying T or T⁻¹ to the same point therefore gives the same answer, and a test run only at Rz(π) cannot tell a correct inverse from one that forgot the Rᵀ on the translation. The lesson: test the inverse at a rotation that is not self-inverse, e.g. Rz(π/2), where the two readings separate.
E 0.2.4.2 — The inverse-translation (numerical). T = (Rz(π/2), (1, 2, 3)). Compute the correct inverse and the naive inverse, and the naive inverse’s error.
S 0.2.4.2. Correct: (Rz(-π/2), -Rᵀt) with Rᵀt = Rz(-π/2)·(1, 2, 3) = (2, -1, 3), so -Rᵀt = (-2, 1, -3). Naive: (Rz(-π/2), (-1, -2, -3)). Error: (-2, 1, -3) - (-1, -2, -3) = (-1, 3, 0), magnitude √10 ≈ 3.16. The lesson: the naive inverse’s translation is off by Rᵀt - t, which is (2, -1, 3) - (1, 2, 3) = (1, -3, 0) — the rotation’s action on the translation minus the translation, non-zero for any t with a component perpendicular to the rotation axis.
E 0.2.4.3 — The wrong-order diagnosis (diagnostic). A learner’s FK matches at q = 0 and differs at q ≠ 0. They have verified that all four joint transforms are correct. What is the most likely cause?
S 0.2.4.3. The wrong composition order — the transforms are correct in isolation, but the product $T_6$·…·$T_1$ is used instead of $T_1$·…·$T_6$. The q = 0 match is because all $T_i(0$) are pure translations, which commute, so the order does not matter at q = 0. The q ≠ 0 mismatch is because the rotations do not commute. The fix is to compose in the chain order (joint 1 to joint 6). The lesson: the q = 0 test cannot distinguish the composition order; the q ≠ 0 test is the discriminator.
E 0.2.4.4 — The wrong-direction diagnosis (diagnostic). A learner’s object position is twice as far from the camera as expected. What is the likely cause, and what is the fix?
S 0.2.4.4. The camera calibration $T_EE^Cam$ is inverted ($T_Cam^EE$ used instead of $T_EE^Cam$), so the object’s depth is doubled (the inverse of a translation (0, 0, d) is (0, 0, -d), and the difference is 2d). The fix is to use the correct direction ($T_EECam$ from-to, or $T_EE^Cam$ pose-in), and the naming discipline (§L.7) prevents the mixing. The lesson: the wrong-direction error on a small translation is a small error, but on a large translation (a far camera), it is large, and the “twice as far” signature is diagnostic.
E 0.2.4.5 — The naming discipline (constructive). A codebase uses $T_A_B$, $T_B_C$, and $T_A_C$ as variables. State the two conventions that make the composition $T_A_C$ = $T_A_B$ · $T_B_C$ correct, and the one convention that makes it wrong.
S 0.2.4.5. The composition is correct if (i) $T_A_B$ is “from B to A” (A from B), $T_B_C$ is “from C to B”, and $T_A_C$ is “from C to A” — the from-to convention with the subscripts matching the input-output order; or (ii) $T_A_B$ is “pose of B in A”, $T_B_C$ is “pose of C in B”, and $T_A_C$ is “pose of C in A” — the pose-in convention with the subscripts matching the target-source order. The composition is wrong if the variables mix conventions (e.g., $T_A_B$ from-to and $T_B_C$ pose-in), because the subscripts’ meanings differ. The lesson: the naming discipline is the statement of the convention, and the composition’s correctness is derivable from the names if the convention is stated once.
E 0.2.4.6 — The cascade (constructive, transfer probe). A 6-joint arm’s sign error is at joint i. What is the end-effector error’s magnitude as a function of i, and where is the worst case?
S 0.2.4.6. By §L.4.1, the error is $e_i$ = $R_A$[$R_i^{\top} − R_i$t_B − (I + $R_i)X$], so its magnitude is not monotone in i. The reach term $R_i^{\top} − R_i$t_B has magnitude 2 sin $θ_i$ · ‖$t_B$‖ (for $t_B$ perpendicular to the axis) and vanishes when joint i is at $θ_i$ = 0 or π; the offset term has magnitude 2a|cos(θ_i/2)|. The worst case is therefore the joint that is most bent with the longest downstream reach — for the running case’s q = [0, π/2, 0, 0, 0, 0] that is joint 2 (1.8111), not joint 1 (0.4000). The best case is the last joint ($t_B$ = 0, so exactly 2a) or any joint at its zero pose (also exactly 2a). Over 4000 random configurations the maximum was 1.8665, attained at joint 1 — but only because joint 1 was bent in those draws, not because it was first. The lesson: the review order is set by $θ_i$ and ‖$t_B$‖, not by position in the chain.
E 0.2.4.7 — The “self-inverse” trap (transfer probe). A learner tests the inverse-rotation error at Rz(π) and finds no error. They conclude the error is not present. What is wrong with their test?
S 0.2.4.7. Rz(π) is its own inverse (Rz(π) = Rz(-π) = Rz(π)ᵀ), so the inverse-rotation error is invisible at Rz(π). The test must use a non-self-inverse rotation (e.g., Rz(π/2), whose inverse is Rz(-π/2) ≠ Rz(π/2)) to distinguish the two. The learner’s test is insufficient — it passes on the identity and the half-turn, and the error is visible only at other rotations. The lesson: the test must use a configuration where the two variants of the transform differ; the self-inverse configurations are the blind spots, and a test that passes on them is not evidence of correctness.
E 0.2.4.8 — The frame audit (constructive). Write the frame audit for the running case’s W → B → EE → Cam → Obj pipeline, with each transform’s direction, convention, and composition order.
S 0.2.4.8.
Frame audit (from-to convention, $T_from_to$):
- $T_WB$: from B to W (pose of B in W) — base's pose in world.
- $T_BEE$: from EE to B (pose of EE in B) — end-effector's pose in base.
- $T_EECam$: from Cam to EE (pose of Cam in EE) — camera's pose in end-effector.
- $T_CamObj$: from Obj to Cam (pose of Obj in Cam) — object's pose in camera.
- Composition: $T_WObj$ = $T_WB$ · $T_BEE$ · $T_EECam$ · $T_CamObj$.
Names: W from B, B from EE, EE from Cam, Cam from Obj -> W from Obj. ✓
Intermediate frames cancel: B·B, EE·EE, Cam·Cam.
The lesson: the audit’s composition is the name cancellation, and the correctness is the cancellation’s completeness (every intermediate frame appears exactly twice, once as a target and once as a source). A frame that appears once or three times is a sign error, and the audit catches it before the code runs.
§L.9 — Exit test
You are ready for Module 0.3 (Probability and Estimation) when you can, without notes:
- State the active, passive, pose, and inverse readings of a transform, and explain why the first three are the same matrix.
- State the four canonical sign errors and their symptoms.
- Diagnose the inverse-rotation error from the “wrong-way rotation” symptom, and name the test that distinguishes it.
- Diagnose the inverse-translation error from the “off by a constant” symptom, and name the test.
- Diagnose the wrong-order error from the “correct at
q=0, drifts atq≠0” symptom, and name the test. - Diagnose the wrong-direction error from the “opposite pose” symptom, and name the test.
- State the from-to and pose-in conventions, and the composition rule for each.
- State the naming discipline and the “effect in the code” pattern, and explain why they eliminate the wrong-direction error.
- State the compound-frame cascade’s error formula
$e_i$ = $R_A$[$R_i^{\top} − R_i$t_B − (I + $R_i)X$]and explain why a joint’s position in the chain does not by itself determine its error’s size. - State the “self-inverse trap” and the configuration that does reveal the inverse-rotation error.
- Write a frame audit for a four-frame pipeline (base, end-effector, camera, object) with the composition’s name cancellation.
- Diagnose the “twice as far” camera error from the wrong-direction signature, and name the fix.
If any item is not answerable without notes, return to the section that introduced it: 1 to §L.1.1, 2 to §L.2.1, 3 to §L.2.2, 4 to §L.2.2, 5 to §L.2.2, 6 to §L.2.2, 7 to §L.3, 8 to §L.3.2, 9 to §L.4.1, 10 to E 0.2.4.7, 11 to E 0.2.4.8, 12 to E 0.2.4.4.
§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.
| Claim | Tier | Status | Where it is reproduced |
|---|---|---|---|
Replacing R with Rᵀ in a transform moves the end-effector by 1.6 — the predicted 2 sin θ₂ · ‖$t_B$‖ | A | R | H1, matching the closed form exactly |
Replacing −Rᵀt with −t moves it by 0.4 — the predicted 2a | A | R | H2 |
The wrong transform order is invisible at q = 0 and large elsewhere | A | R | H3, printed as True for the equality at q = 0 |
A wrong rotation direction on the camera moves the point by 0.2 | A | R | H4, with both positions printed |
What this lesson does not reproduce. Nothing in the four hypotheses. H3’s q ≠ 0 magnitude is printed in the error loop (wrong_order: diff = 0.8485) alongside the q = 0 equality, so both halves of the claim are measured. The lesson’s per-joint table is likewise a run, not an assertion.
The three errors are dimensionally distinct, and that is the lesson. 1.6, 0.4, and 0.2 are not three versions of the same mistake: the first is a rotation-sign error scaled by 2 sin θ₂ · ‖$t_B$‖, the second a translation-sign error scaled by 2a, the third a direction error on the camera’s offset. Each has a closed form the hypothesis derives before the run, which is what makes them diagnosable from the magnitude of a pose error rather than only from its existence. The wrong-order error (0.8485) has no such simple closed form and is the one the lesson diagnoses by its pattern instead — zero at q = 0, non-zero elsewhere.
The q = 0 equality is a trap, and the lesson says so. A wrong transform order produces the correct pose at the zero configuration, so a test suite that checks only the home pose will pass a chain that is wrong everywhere else. This is the same failure shape as the branch-jump lesson (1.1.4): a check that samples one point cannot see a defect whose error vanishes there.
No external sources are cited in this lesson. The frame convention, the transform composition, and the inverse are defined in Parts A–B and executed by the lab. No paper is named.