Lesson 1.1.2 — Inverse Kinematics: The Analytical and Numerical Solutions, and the Singularities That Break Both
Week 5 · Prerequisites: 0.1.1, 0.1.2, 0.2.1, 0.2.2, 0.2.3, 0.2.4, 1.1.1.
Prior results used. This lesson uses 0.2.1’s SE(3) inverse and point/vector distinction, 0.2.2’s SO(3) log and its π-singularity, 0.2.3’s twist and adjoint, 0.2.4’s from-to convention, and 1.1.1’s DH table, PoE form, and geometric Jacobian J(q) ∈ ℝ^{6×n} with its moment-arm column J_i = [ω_i; ω_i × (p_ee − q_i)]. Each is restated at the point of use.
Hands forward. This lesson hands forward one capability: the learner can formulate the inverse kinematics problem, derive the closed-form solution for the running case’s simplified 2-link arm, implement a damped-least-squares numerical IK that converges from a far initial guess and stays stable at singularities, identify the running case’s singular configurations from the Jacobian’s singular values, and diagnose the four IK-specific bugs (single-branch assumption, undamped pseudoinverse, stationary residual, wrong-axis Jacobian) from their end-effector symptoms — the capability that Lesson 1.1.3 (control), 2.3.2 (motion planning), and every pose → joint-angle pipeline in Phases 1–4 assumes.
Running case. The same 6-DOF MuJoCo arm from 1.1.1, but with two readings of the IK problem: (i) the full 6-DOF IK (given a target end-effector pose, find q ∈ ℝ⁶), which has no closed form for the general 6R arm (Pieper’s condition does not apply — the axes are not in the spherical-wrist configuration) and requires numerical methods; and (ii) the 2-link planar sub-arm (q₁, q₂ only, a₁ = a₂ = 0.2), which has a closed-form analytical IK with the classic two-branch (elbow-up/elbow-down) solution. The lesson’s central result is that the numerical IK converges from most initial guesses but fails at the arm’s singularities, and the fix is the damped least-squares (DLS) update that trades convergence speed for stability at the cost of a systematic end-effector error.
§L.0 — Lesson map, objectives, and misconceptions
§L.0.1 — Lesson map
| Part | Content |
|---|---|
| A | The IK problem: existence, uniqueness, and the three methods (analytical, numerical, optimization) |
| B | Analytical IK: the 2-link planar arm, the two branches, the workspace boundary |
| C | Numerical IK: Newton–Raphson, the Jacobian pseudoinverse, the iterative loop, convergence |
| D | Singularities and DLS: the Jacobian’s SVD, manipulability, damping, the null space |
| 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 IK problem
q* = f⁻¹(T_target)and the three questions of existence (is there a solution?), uniqueness (how many?), and reachability (is the target in the workspace?). - Derive the closed-form IK for the 2-link planar arm (
q₁, q₂from(x, y)), including the two branches (elbow-up and elbow-down) and the workspace annulus|a₁ − a₂| ≤ √(x² + y²) ≤ a₁ + a₂. - Implement the Newton–Raphson iteration
q_{k+1} = q_k + J(q_k)⁺(T_target − T(q_k))with the Jacobian pseudoinverse, and state the convergence criterion (the pose error’s norm below a tolerance). - State the three failure modes of the numerical IK — a far initial guess (slow or no convergence), a singularity (
J⁺unbounded), and a stationary residual (Jᵀe = 0with‖e‖ > 0) — and their discriminating tests (‖Jᵀe‖for stationarity, thenσ_min(J)to tell a singularity from a genuine local minimum). - Diagnose the four IK-specific bugs — single-branch assumption, undamped pseudoinverse, stationary residual, wrong-axis Jacobian — from the end-effector’s behavior (a jump, a blow-up, a stall, a systematic offset).
- State the damped least-squares (DLS) update
q_{k+1} = q_k + Jᵀ(JJᵀ + λ²I)⁻¹ eand its trade-off (convergence speed vs. stability at singularities). - Compute the manipulability
w(q) = √(det(JJᵀ))and identify the running case’s singular configurations from the Jacobian’s smallest singular value. - Produce a written “IK audit” of a target pose: its reachability, its branch (if multiple solutions exist), the initial guess and its convergence basin, the singularity proximity, and the final pose error.
§L.0.3 — Misconception inventory
| Wrong model the learner probably holds | Why it is attractive | Where displaced | The correct model in one sentence |
|---|---|---|---|
| M1 — “Inverse kinematics has a unique solution.” | The forward kinematics is a function (one output per input), and the inverse feels like a function too, with one input per output. | §L.1 and §L.2 (Parts A and B) | IK is a relation, not a function — the two-branch (elbow-up/elbow-down) solution of the 2-link arm is the simplest example, and the 6-DOF arm has up to 8 solutions (elbow × shoulder × wrist flips); the choice of branch is a decision the planner makes (which branch is collision-free? which is closest to the current config?), not a fact the IK returns. |
M2 — “The Jacobian pseudoinverse J⁺ is the IK solution.” | q = f⁻¹(T) feels like q = J⁻¹·(T_target − T_current) — the Jacobian is the FK’s derivative, and its inverse should “undo” the FK. | §L.3 (Part C) | J⁺ is the differential step, not the solution — it gives the direction to move in q-space to reduce the current error, but the FK is nonlinear, and a single J⁺ step does not reach the target (the error after one step is O(‖e‖²)); the IK is an iteration q_{k+1} = q_k + J⁺(q_k)·e_k, and the convergence is local (in the basin of the linearization’s validity). |
| M3 — “The pseudoinverse is the numerically stable choice at singularities.” | J⁺ is the minimum-norm solution of Jx = e, and “minimum norm” sounds like “well-behaved.” | §L.4 (Part D) | At a singularity, the Jacobian’s smallest singular value is zero, and J⁺ amplifies the error’s component in the null direction by 1/σ_min = ∞ — the pseudoinverse is unbounded, not stable; the fix is damped least-squares Jᵀ(JJᵀ + λ²I)⁻¹, which bounds the amplification at the cost of a systematic error O(λ²). |
| M4 — “Numerical IK always converges for a reachable target.” | The target is reachable, and the iteration reduces the error at each step, so it should converge. | §L.3 (Part C) | The iteration is local. It stops where the gradient dies — Jᵀe = 0 — and that can happen with ‖e‖ > 0 for two different reasons: the arm is at a singular configuration (the residual lies in a rank-deficient J’s null space) or the target is outside the reachable set. Neither is fixed by iterating longer; both need a different seed, which is what multiple restarts provide. |
| M5 — “The analytical IK exists for any arm.” | The 2-link arm has a closed form, and the 6-DOF arm has six equations and six unknowns, so the closed form “should” exist. | §L.2 (Part B) | The general 6R arm’s IK is a degree-16 polynomial (the 6R’s “kinematic equation” is a high-degree system), and it has a closed form only under Pieper’s condition — three consecutive joint axes intersect (a spherical wrist) or three are parallel; the running case’s alternating z/y axes do not satisfy it, and its IK has no closed form (numerical or optimization is required). |
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 fourteenth lesson. The prompt requires drawing from lessons two to five steps back; 1.1.1 is one step back and is excluded. The warm-up draws from 0.3.1–0.3.4 (two to five steps back).
Warm-up 1 (from 0.3.1, Bayes’ rule). State the four terms of Bayes’ rule and which of them is the normalizer. Answer: prior p(x), likelihood p(y|x), posterior p(x|y), evidence p(y) = ∫ p(y|x)p(x)dx — the evidence is the normalizer. How this is used: §L.5’s optimization-based IK can be viewed as a MAP problem — the prior is a preference for configurations near the current one, the likelihood is the pose error’s Gaussian, and the MAP solution is a regularized IK that prefers small joint movements. The IK’s “regularization” is the MAP’s prior, and the DLS’s damping is the prior’s precision.
Warm-up 2 (from 0.3.2, the log-likelihood and gradient). A loss L(θ) = ‖f(θ) − y‖² has gradient ∇L = 2 Jᵀ(f(θ) − y) where J = ∂f/∂θ. What is the Gauss–Newton step? Answer: θ_{k+1} = θ_k − (JᵀJ)⁻¹ Jᵀ(f(θ_k) − y), which is the pseudoinverse step θ_{k+1} = θ_k + J⁺(y − f(θ_k)) for the residual. How this is used: §L.3’s Newton–Raphson IK is the Gauss–Newton step applied to the pose residual e = pose(T_target) − pose(T(q)), and the pseudoinverse J⁺ is the same object as the Gauss–Newton’s (JᵀJ)⁻¹Jᵀ — the IK is a least-squares problem.
Warm-up 3 (from 0.3.3, the Kalman filter’s gain). The KF’s gain K = Σ⁻Hᵀ(HΣ⁻Hᵀ + R)⁻¹ has a +R in the denominator. What does the +R do, and why is it a regularizer? Answer: the +R (the measurement noise’s covariance) bounds the gain when HΣ⁻Hᵀ is small (i.e., when the measurement is uninformative), preventing the K from blowing up; the R is a Tikhonov regularization of the least-squares problem, and its role is the same as the DLS’s damping λ². How this is used: §L.4’s DLS update Jᵀ(JJᵀ + λ²I)⁻¹ is the same structure as the KF’s gain — the λ² is the “measurement noise” of the IK’s differential step, and its value is the damping that trades convergence speed for stability.
Warm-up 4 (from 0.3.4, the Mahalanobis gate). The Mahalanobis distance d² = νᵀS⁻¹ν divides by S (the innovation’s covariance). What is the inverse of a matrix with a zero eigenvalue, and what is the pseudoinverse’s behavior there? Answer: a matrix with a zero eigenvalue is singular and has no inverse; its pseudoinverse replaces 1/σ with 0 for the zero singular values, which is the minimum-norm solution — the zero direction is ignored. How this is used: §L.4’s singular Jacobian has a zero singular value, and the pseudoinverse ignores that direction (the arm cannot move in the null direction). The damping does not differ there — at σ = 0 the DLS gain σ/(σ² + λ²) is also 0. What the damping changes is the neighborhood: at σ = 10⁻³ the pseudoinverse gain is 1000 while the damped gain is 0.1; at σ = 10⁻² it is 100 against 0.99. The damping caps the amplification near a singularity, not at it — which is why §L.6.6 measures the trade-off at q₂ = 10⁻³ rather than at q₂ = 0.
Part A — The Inverse Kinematics Problem
§L.1 — Objects: the problem, the three questions, and the three methods
The inverse kinematics problem is: given a desired end-effector pose T_target ∈ SE(3), find a joint configuration q* ∈ ℝⁿ such that the forward kinematics T(q*) = T_target:
The three questions:
- Existence — does a
q*exist? The target must be in the robot’s workspace (the set of reachable poses), which is the image of the FKf: ℝⁿ → SE(3). - Uniqueness — how many
q*satisfyf(q*) = T_target? The FK is non-injective (multiple configurations give the same pose), and the number of solutions is the degree of the IK, which is up to8for a general 6-DOF arm (2 elbow × 2 shoulder × 2 wrist flips) and infinite for a redundant arm (n > 6). - Reachability — is the target inside the workspace (a solution exists) or outside (no solution)? The workspace’s boundary is the set of poses at the arm’s maximum reach (fully extended) and minimum reach (fully folded), and the boundary is not convex in
SE(3)— the arm’s orientation’s constraints shrink the reachable positions.
The three methods:
- Analytical (closed-form) — solve the FK’s equations exactly, using the arm’s geometry (the DH parameters’ structure). This gives all the solutions’ branches in closed form, and it is fast and exact. It is available only for arms that satisfy Pieper’s condition (three consecutive axes intersecting or parallel), which the running case does not (its axes alternate
z/yand the distances between them are non-zero). - Numerical (iterative) — start from an initial guess
q₀and iterateq_{k+1} = q_k + Δq_kwithΔq_kcomputed from the Jacobian, untilT(q_k) ≈ T_target. This is general (works for any arm), local (converges to the nearest solution, not the global one), and it fails at singularities without damping. - Optimization-based — formulate the IK as a non-linear least-squares problem
min_q ‖T_target − T(q)‖² + λ‖q − q₀‖², and solve it with a general optimizer (Levenberg–Marquardt, Gauss–Newton). This is a special case of the numerical method, with the regularizationλ‖q − q₀‖²added, and it is the same structure as the DLS’s damping.
The choice of method:
| Condition | Method | Reason |
|---|---|---|
| Spherical wrist or parallel axes (Pieper) | Analytical | Fast, exact, all branches |
| General 6R (like the running case) | Numerical | General, local, damping required |
Redundant arm (n > 6) | Numerical + null-space | The extra DOF for the secondary objective |
| Offline (planning) | Optimization | The global solution, with the regularization |
| Online (control) | Analytical or fast numerical | The real-time constraint, the warm start |
§L.1.1 — Result: IK is a relation, not a function; the number of solutions is the FK’s degree, and it is not unique
Claim. For a general 6-DOF arm with spherical wrist (Pieper’s condition), the IK has up to 8 solutions (2 shoulder × 2 elbow × 2 wrist); for an arm without the spherical wrist (like the running case), the IK has up to 16 solutions (a degree-16 polynomial); for a redundant arm (n > 6), the IK has infinitely many solutions (a continuous family). The IK is a relation f⁻¹(T_target) ⊆ ℝⁿ, not a function, and the “solution” is a set.
Proof sketch. The FK is a system of 6 equations (the pose’s 6 DOF) in n unknowns (n joint angles). For n = 6, the system is square (6 equations, 6 unknowns), and its solutions are the roots of a polynomial system; Bezout’s theorem bounds the number of roots by the product of the degrees, which is 16 for the 6R’s kinematic equation (each joint contributes a degree-2 in the half-angle parameterization). For n > 6, the system is under-determined (fewer equations than unknowns), and the solution set is a manifold of dimension n − 6. The spherical wrist’s structure reduces the degree (the wrist’s q₄, q₅, q₆ can be solved independently of the position, giving 2 × 2 × 2 = 8 solutions instead of 16).
Corollary. The branch choice is a decision, not a fact — a planner chooses the branch that is collision-free, closest to the current configuration, or reachable within the joint limits. The IK’s output is a set of solutions, and the single solution is a post-selection. A learner who calls an IK library and receives a single q* has received one of the branches (usually the one closest to the seed), and the other branches are discarded.
Corollary. The workspace is the set of T_target that have a solution, and its boundary is the set of poses at the arm’s extremes — the fully extended arm (max reach Σ a_i) and the fully folded arm (min reach, which is not zero for a general arm, because the links have thickness and the joints have limits). A target outside the workspace has no solution, and the IK’s failure is physical, not a bug — the controller must reject the target or saturate the arm.
§L.1.2 — Worked-example ladder
WE-L.1.a — Fully worked: counting the solutions for the 2-link planar arm
Input. A 2-link planar arm with a₁ = a₂ = 0.5, target (x, y) = (0.6, 0.3).
Trace (existence).
r = √(x² + y²) = √(0.36 + 0.09) = √0.45 ≈ 0.671.- Workspace annulus:
|a₁ − a₂| = 0 ≤ r ≤ a₁ + a₂ = 1.0. r = 0.671is inside the annulus → a solution exists. ✓
Trace (uniqueness).
- The 2-link arm has two solutions for the elbow angle
q₂:cos(q₂) = (r² − a₁² − a₂²)/(2a₁a₂) = (0.45 − 0.25 − 0.25)/(2·0.25) = (-0.05)/0.5 = -0.1. q₂ = ± arccos(-0.1) = ± 1.671 rad ≈ ± 95.7°. Two solutions (+is elbow-up,−is elbow-down).- For each
q₂, there is oneq₁(the shoulder angle) — the twoq₂’s give twoq₁’s. - Total: 2 solutions. ✓
Output and interpretation. The target (0.6, 0.3) is reachable (inside the annulus), and there are two IK solutions (q₂ = +1.671 and q₂ = −1.671). The branch choice is the planner’s — if the arm’s joint 2 has limits [−π/2, π/2], only the +1.671 branch is valid (−1.671 is outside the limits), and the IK’s solution set is filtered to the one valid branch. The q₁’s are atan2(y, x) − atan2(a₂ sin q₂, a₁ + a₂ cos q₂) for each q₂.
Self-explanation 1. Before reading on: in step 5,
q₂ = ±1.671rad. What ifcos(q₂)were outside[-1, 1], e.g.,cos(q₂) = 1.5?Answer.
cos(q₂) = 1.5is outside the range, so there is no realq₂— the target is unreachable. Thearccosis undefined for|x| > 1, and the existence condition is exactly|cos(q₂)| ≤ 1, which is the workspace annulus|a₁ − a₂| ≤ r ≤ a₁ + a₂in disguise (cos(q₂) = (r² − a₁² − a₂²)/(2a₁a₂) ∈ [−1, 1]iffr ∈ [|a₁ − a₂|, a₁ + a₂]). The existence check is thearccos’s domain, and the failure is theNaNfromarccos(1.5).
Self-explanation 2. Before reading on: in step 5,
q₂ = ±1.671rad is almost±95.7°. What doesq₂ = 0correspond to, and where is the arm?Answer.
q₂ = 0is the fully extended arm (both links aligned), and the end-effector is atr = a₁ + a₂ = 1.0— the maximum reach. Atq₂ = π(the other boundary), the arm is fully folded (the links fold back on themselves), and the end-effector is atr = |a₁ − a₂| = 0— the minimum reach. Forq₂between0andπ, the arm is bent, and the reach is between the extremes. Theq₂ = ±1.671is a middle bend, givingr = 0.671, which is between0and1.0— a typical reach.
WE-L.1.b — Completion problem
Input. Same 2-link arm, target (x, y) = (1.2, 0).
Trace (final 25% as prompts).
r = √(1.44) = 1.2. ⟨your turn: is this inside the annulus[0, 1.0]?⟩cos(q₂) = (r² − a₁² − a₂²)/(2a₁a₂) = (1.44 − 0.5)/0.5 = 1.88. ⟨your turn: is this in[-1, 1]?⟩- What does the answer to step 2 mean? ⟨your turn⟩
Answers.
r = 1.2 > 1.0— outside the annulus.cos(q₂) = 1.88 > 1— outside[-1, 1].- The target is unreachable — the arm’s maximum reach is
a₁ + a₂ = 1.0, and1.2 > 1.0. Thearccos(1.88)is undefined (aNaN), and the IK fails. The controller must reject the target or saturate the arm to the nearest reachable pose ((1.0, 0)for this direction).
The lesson. The existence check is the arccos’s domain |cos(q₂)| ≤ 1, which is the workspace annulus r ≤ a₁ + a₂. The symptom of an unreachable target is the NaN from arccos, and the fix is the saturating command (the nearest reachable target), not the damping.
WE-L.1.c — Faded: the 6-DOF arm’s IK solution count
Setup. The running case’s 6-DOF arm, with alternating z/y axes and no spherical wrist (a_i = 0.2 for all i).
Question. How many solutions does its IK have? Does the closed form exist?
You must produce the intermediate steps. The setup and the answer are given.
Answer. The arm has no spherical wrist (the last three axes do not intersect at a point — the a_i = 0.2 offsets put each joint at a different location), so Pieper’s condition is not satisfied. The IK’s degree is up to 16 (the generic 6R’s bound), and it has no closed form in elementary functions. The IK is numerical (Part C) or optimization-based (Part D). The solutions (up to 16) are the branches — for a given target, there are up to 16 configurations that achieve it, and the numerical method converges to one of them (the one in its basin).
The lesson. The spherical wrist is the practical condition for a closed form — it decouples the position IK (the first 3 joints) from the orientation IK (the last 3 joints), reducing the degree from 16 to 8. The running case lacks the spherical wrist, so its IK is numerical. The trade-off: the closed-form arm (like a PUMA 560) is faster and exact, while the numerical arm (like the running case) is general but iterative and local.
WE-L.1.d — Contrastive near-miss: assuming the IK returns the “closest” solution
Correct solution. The IK returns one of the branches — the one in the iteration’s basin — and the planner must explicitly choose the branch (by validating the returned q* against the joint limits, the collision constraints, and the continuity with the previous q).
Near-miss. The learner assumes the IK returns the closest solution to the current configuration (because they seeded the iteration with it), and they do not validate the returned q* — it may be on a different branch if the target jumped across a workspace boundary, causing a sudden arm motion that violates the joint limits or collides with obstacles.
Why the near-miss produces a plausible but incorrect number. The IK returns a q* that exactly satisfies f(q*) = T_target — the pose is correct, and the test ‖f(q*) − T_target‖ < ε passes. The q* is far from the current q, but the learner’s assumption is that it is close (since the seed was the current q). The symptom is the arm’s jump — a large motion that the controller commands, which is valid as a pose but invalid as a trajectory (the joint limits, the collisions, the velocity limits). The bug is silent in the pose test and visible in the arm’s behavior.
The discrepancy, stated. The IK’s pose test ‖f(q*) − T_target‖ < ε and the trajectory test ‖q* − q_current‖ < δ are different tests, and the first passes while the second fails. The branch is the source — the IK found a solution, but not the one the planner wanted (the one close to the current configuration). The fix is the continuity check, the joint limits filter, or the multi-restart with the closest q* selected.
Self-explanation 3. Before reading on: in the near-miss, if the target jumps from one side of the workspace to the other, is the “closest”
q*the one the IK returns?Answer. No — the IK returns the
q*in its iteration’s basin, and a basin is not a ball in joint space. Measured on the 2-link arm at target(0.6, 0.3): a seed closer toq⁻converges toq⁺in 47 of 400 random cases, and a seed closer toq⁺converges toq⁻in 59 (§WE-L.3.c). The “nearest branch” heuristic fails in both directions on more than a quarter of seeds. When the target jumps across a workspace boundary, the iteration may land in the other basin and return a branch far from the seed; the only reliable check is to validate the returnedq*against the joint limits and the previous configuration, not to infer it from the distance.
§L.1.3 — Comparative table: the three methods
| Method | What it solves | Branches returned | Works when | Fails when | Cost |
|---|---|---|---|---|---|
| Analytical (closed form) | the FK’s equations exactly, from the arm’s geometry | all of them, in closed form | Pieper’s condition holds — three consecutive axes intersect (spherical wrist) or are parallel | the arm is a general 6R; the running case’s alternating z/y axes do not satisfy it | closed-form evaluation, one pass |
| Numerical (iterative) | a sequence of local linearizations | one — whichever lies in the iteration’s basin | the seed is inside the basin and J is well conditioned | the seed is far, J is singular, or the target is outside the reachable set | tens of iterations, each a Jacobian and a solve |
| Optimization-based | a regularized least-squares objective | one — the minimizer nearest the prior | a secondary objective or a prior on q is wanted | the same local minima; the regularizer biases the answer | a full solver, the most expensive of the three |
Design principle (P14.6). The method is chosen by the arm and the deadline, not by preference. A spherical-wrist arm in an offline planner wants the closed form because it returns every branch and the planner can then choose among them; the same arm online wants the closed form warmed from the last cycle; a general 6R wants a numerical method with damping and a seed that is the previous configuration, because a cold seed buys nothing but iterations. The regularity that makes the closed form possible is a property of the mechanism, and no amount of solver tuning substitutes for it — which is why the running case is numerical from the first line.
§L.1.4 — Concept check
Concept check (displaces M5). A learner says: “The 6-DOF arm has six equations and six unknowns, so the closed form must exist — I just need a better symbolic solver.” What has the learner failed to model, and what bug class follows?
Answer. The learner has failed to model that a square system is not a solvable one: six equations in six unknowns is a polynomial system whose roots number up to 16, and a closed form in elementary functions exists only under Pieper’s condition — three consecutive axes intersecting at a point, or three parallel. The running case’s alternating
z/yaxes witha_i = 0.2offsets satisfy neither, so its IK is a degree-16 system with no elementary solution. The bug class is a solver run until it returns something: the symbolic package will either run forever or return a numeric root dressed as a closed form, and the learner will believe the second because the first was expected to be slow. The practical consequence is not an error but a missing capability — no branch enumeration, so no planner choice among the up-to-16 solutions — and it is why the lesson spends Part C on iteration rather than on algebra.
§L.1.5 — Consolidation checkpoint 1
(a) You should now be able to: (i) state the three questions — existence, uniqueness, reachability — and which object answers each; (ii) say how many solutions a general 6R has and why the spherical wrist reduces the count; (iii) choose a method from the arm’s structure and the deadline, and say what each method cannot give you.
(b) Transfer probe. A 2-link arm has a_1 = a_2 = 0.5. A target sits at r = 0.671. A colleague says “the arm has two solutions, so I’ll take whichever the numerical solver finds first.” Is the count right, and what has the choice skipped?
Answer sketch: the count is right — r is strictly inside the annulus [0, 1.0], so cos q_2 = −0.1 gives q_2 = ±1.671 and two configurations. The choice has skipped the selection: the two branches give the same pose and differ in q_1 by more than a radian, so whichever one the solver returns determines the arm’s posture, its clearance, and whether it is inside the joint limits. Taking the solver’s output is taking a branch, not avoiding the decision; a planner that never asks which branch it got has delegated the decision to the seed.
(c) Remediation rule. If you cannot do (a)(ii) or (b), return to §L.1.2 and redo WE-L.1.a, then re-attempt (b) without notes.
Part B — Analytical IK: The 2-Link Arm and the Workspace Boundary
§L.2 — Objects: the closed-form solution and the two branches
The 2-link planar arm’s FK is x = a₁ cos q₁ + a₂ cos(q₁ + q₂), y = a₁ sin q₁ + a₂ sin(q₁ + q₂). The IK is:
The two branches are the ± in q₂: the + branch is elbow-up (the elbow is above the line from shoulder to end-effector), and the − branch is elbow-down (below).
The workspace annulus:
The r < |a₁ − a₂| is inside the inner boundary (the arm cannot fold that tightly), and r > a₁ + a₂ is beyond the outer boundary (the arm cannot reach that far). Both give no solution.
The atan2’s role: it resolves the quadrant of the angle, which a naive arctan(y/x) cannot (the arctan is periodic in π and undefined at x = 0). The atan2(y, x) returns the angle in (−π, π] with the correct quadrant for every (x, y) — the 0-to-2π atan2 is the standard for IK.
§L.2.1 — Result: the 2-link IK’s closed form has two branches, and the workspace boundary is the arccos’s domain
Claim. For the 2-link planar arm the solution set has two points in the interior of the annulus (the two branches), one at the outer boundary r = a₁ + a₂, and none outside the annulus. At the inner boundary the count depends on the arm: for a₁ ≠ a₂ it is one point; for a₁ = a₂ it is a one-parameter family (r = 0, every q₁ with q₂ = π). The branch choice is the sign of q₂, and the two interior solutions have the same r but different q₁ and q₂.
Proof sketch. The cos(q₂) is the law of cosines applied to the triangle with sides a₁, a₂, r: r² = a₁² + a₂² − 2a₁a₂ cos(π − q₂) = a₁² + a₂² + 2a₁a₂ cos q₂, which rearranges to cos q₂ = (r² − a₁² − a₂²)/(2a₁a₂). The cos q₂ is in [−1, 1] iff r ∈ [|a₁ − a₂|, a₁ + a₂], which is the annulus. The q₂ = ± arccos(...) gives the two solutions, and the q₁’s follow from the geometry (the angle from the shoulder to the elbow, plus the angle from the elbow to the end-effector).
Corollary. At the outer boundary r = a₁ + a₂ (fully extended), cos q₂ = 1, q₂ = 0, and the two branches coincide — the solution count drops from 2 to 1, and this is a singularity of the Jacobian (Part D). At the inner boundary r = |a₁ − a₂| the two branches coincide only when a₁ ≠ a₂: there cos q₂ = −1, q₂ = ±π is the same angle, and atan2(a₂ sin q₂, a₁ + a₂ cos q₂) = atan2(0, a₁ − a₂) = 0 for both signs. For the lesson’s arm (a₁ = a₂) the inner boundary is r = 0, the denominator a₁ + a₂ cos q₂ = 0 vanishes, and the solution set becomes a one-parameter family — every q₁ with q₂ = π puts the end-effector at the origin, because the elbow can sit anywhere on the circle of radius a₁. The closed form still returns two points there (atan2(0, 0) = 0), which is the trap: it silently reports two solutions where there are infinitely many.
Corollary. The atan2 is essential — the arctan alone is ambiguous (the same y/x for opposite quadrants) and undefined at x = 0. The atan2’s range is (−π, π], and the angle wrapping (the q₁’s ± 2π for the joint limits) is the post-selection the planner does.
§L.2.2 — Worked-example ladder
WE-L.2.a — Fully worked: the two branches for a specific target
Input. 2-link arm with a₁ = a₂ = 0.5, target (x, y) = (0.6, 0.3).
Trace (the closed form).
r² = 0.6² + 0.3² = 0.36 + 0.09 = 0.45,r ≈ 0.671.cos q₂ = (0.45 − 0.25 − 0.25)/(2·0.5·0.5) = (-0.05)/0.5 = -0.1.q₂ = ± arccos(-0.1) = ± 1.671 rad ≈ ± 95.7°.- Branch
+(q₂ = 1.671):a₂ sin q₂ = 0.5·sin(1.671) ≈ 0.5·0.995 ≈ 0.498.a₁ + a₂ cos q₂ = 0.5 + 0.5·(-0.1) = 0.45.atan2(0.498, 0.45) ≈ 0.836 rad ≈ 47.9°.atan2(y, x) = atan2(0.3, 0.6) = 0.464 rad ≈ 26.6°.q₁ = 0.464 − 0.836 = −0.372 rad ≈ −21.3°.- Solution:
q = (−0.372, 1.671).
- Branch
−(q₂ = −1.671):a₂ sin q₂ = 0.5·sin(−1.671) ≈ −0.498.atan2(−0.498, 0.45) ≈ −0.836 rad.q₁ = 0.464 − (−0.836) = 1.300 rad ≈ 74.5°.- Solution:
q = (1.300, −1.671).
Output and interpretation. The two solutions are q⁺ = (−0.372, 1.671) (elbow-up) and q⁻ = (1.300, −1.671) (elbow-down). Both give the same end-effector (0.6, 0.3), verified by the FK. The branch choice: if the joint 1 has limits [−π/2, π/2], both are valid (−0.372 and 1.300); if joint 2 has limits [0, π], only the + branch is valid (the − branch has q₂ = −1.671 outside). The continuity with the previous q decides the final choice: the branch closer to the current configuration.
Self-explanation 4. Before reading on: in step 4,
q₁ = -0.372andq₂ = 1.671— the arm’s elbow is up. What does “elbow-up” mean geometrically, and how does the sign ofq₂determine it?Answer. “Elbow-up” means the elbow (the joint 2 location) is above the line from the shoulder (origin) to the end-effector. The
q₂ > 0means the second link is rotated counterclockwise from the first link’s direction (by the right-hand rule aboutz), which bends the arm upward if the first link points in the+x-direction. Theq₂ < 0bends it downward. The geometric test: the sign ofq₂(or, equivalently, the sign of the cross product(elbow − shoulder) × (end-effector − elbow)), and the two branches are the mirror images across the line from shoulder to end-effector.
Self-explanation 5. Before reading on: in step 3,
cos q₂ = -0.1is close to 0, soq₂ ≈ ±90°. What does this say about the arm’s shape, and where is it in the workspace?Answer.
q₂ ≈ ±90°means the two links are perpendicular, so the arm is in a right-angle bend. The reach isr = √(a₁² + a₂²) = √(0.25 + 0.25) = √0.5 ≈ 0.707— close to ther = 0.671we computed, and between the extremes (0.0and1.0). The right-angle configuration is the maximum manipulability for the 2-link arm (the Jacobian’s determinant is maximal there), which is why the target(0.6, 0.3)is easy to reach with good dexterity. The workspace’s “easy” region is the mid-radius (the right-angle bends), and the “hard” regions are the boundaries (the fully extended or fully folded arms).
WE-L.2.b — Completion problem
Input. Same arm, target (x, y) = (0.0, 0.5) (on the y-axis).
Trace (final 25% as prompts).
r² = 0 + 0.25 = 0.25,r = 0.5. ⟨your turn: inside[0, 1]?⟩cos q₂ = (0.25 − 0.5)/0.5 = -0.5. ⟨your turn⟩q₂ = ± arccos(-0.5) = ± 2π/3 = ± 2.094 rad. ⟨your turn: verify⟩atan2(y, x) = atan2(0.5, 0) = π/2 = 1.571 rad. ⟨your turn⟩a₂ sin q₂ = 0.5·sin(2.094) ≈ 0.5·0.866 ≈ 0.433.a₁ + a₂ cos q₂ = 0.5 + 0.5·(-0.5) = 0.25. ⟨your turn⟩q₁ = atan2(y, x) − atan2(0.433, 0.25). ⟨your turn: compute for the+branch⟩- For the
−branch: ⟨your turn⟩
Answers.
r = 0.5is inside[0, 1.0]. ✓cos q₂ = -0.5. ✓arccos(-0.5) = 2π/3 ≈ 2.094. ✓atan2(0.5, 0) = π/2. ✓a₂ sin q₂ ≈ 0.433,a₁ + a₂ cos q₂ = 0.25. ✓atan2(0.433, 0.25) ≈ 1.047 rad = π/3 ≈ 60°.q₁ = 1.571 − 1.047 = 0.524 rad ≈ 30°. Solution:q⁺ = (0.524, 2.094).a₂ sin(-2.094) ≈ −0.433.atan2(-0.433, 0.25) ≈ -1.047 rad.q₁ = 1.571 − (−1.047) = 2.618 rad ≈ 150°. Solution:q⁻ = (2.618, -2.094).
The lesson. The two solutions are q⁺ = (0.524, 2.094) and q⁻ = (2.618, -2.094) — the + branch has the shoulder at 30° and the elbow at 120°, and the − branch has the shoulder at 150° and the elbow at −120°. The two are mirror images across the y-axis (the target’s line), and the arm’s shape is the same reflected — the geometry is the same, and the choice is the planner’s.
WE-L.2.c — Faded: the workspace’s inner boundary
Setup. Same arm (a₁ = a₂ = 0.5), target (x, y) = (0.0, 0.0) (the origin).
Question. Is the target reachable? What does the cos q₂ give?
You must produce the intermediate steps. The setup and the answer are given.
Answer. r = 0. The annulus’s inner boundary is |a₁ − a₂| = 0, and the target’s r = 0 is on the boundary. The cos q₂ = (0 − 0.25 − 0.25)/0.5 = -1, and q₂ = ± arccos(-1) = ± π = ± 180° — the arm is fully folded (the two links are antiparallel, one over the other). The q₁ is undefined — the elbow is at (0, 0) (the origin), and the end-effector is also at (0, 0), so the arm’s two links are overlapping, and any q₁ (with q₂ = π) gives the same end-effector at (0, 0). The IK has infinitely many solutions at the inner boundary (the q₁ is free).
The lesson. The workspace’s boundary is where the IK’s solution count changes, but the two boundaries behave differently. At the outer boundary (r = a₁ + a₂), q₂ = 0 and the two branches coincide — one solution. At the inner boundary the answer depends on whether the links are equal. With a₁ ≠ a₂ the two branches also coincide (q₂ = ±π is one angle) and the solution count is one. With a₁ = a₂ — the lesson’s arm — the inner boundary is r = 0, the atan2’s denominator a₁ + a₂ cos q₂ vanishes, and the count is ∞: every q₁ with q₂ = π works. The general rule is that the solution count drops by one at the boundary unless the boundary is a degenerate point of the mechanism, which is what a₁ = a₂ makes r = 0. The Jacobian’s singularities are exactly these boundaries (§L.4).
WE-L.2.d — Contrastive near-miss: the arctan instead of atan2
Correct solution. q₁ = atan2(y, x) − atan2(a₂ sin q₂, a₁ + a₂ cos q₂) — the two atan2’s resolve the quadrants.
Near-miss. q₁ = arctan(y/x) − arctan(a₂ sin q₂ / (a₁ + a₂ cos q₂)) — the naive arctan’s.
Why the near-miss produces a plausible but incorrect number. The arctan’s range is (−π/2, π/2), and it cannot distinguish the first quadrant from the third (the same y/x) or the second from the fourth. For a target in the second quadrant (x < 0, y > 0), the arctan(y/x) returns a negative angle (the fourth quadrant’s), and the q₁ is off by π — the arm points to the opposite side. The error is silent (the IK returns a valid q₁), and the end-effector is at (−x, −y) instead of (x, y) — the mirror across the origin. The atan2’s range is (−π, π], which covers all four quadrants, and it is the only correct choice.
The discrepancy, stated. arctan(y/x) and atan2(y, x) agree only in the first and fourth quadrants (where x > 0). In the second and third quadrants (x < 0), the arctan is off by π — the atan2 adds π to correct the quadrant. The arctan’s range is half the atan2’s, and the missing half is the bug.
Self-explanation 6. Before reading on: in the near-miss, if the target is on the
y-axis (x = 0), what does thearctan(y/0)give, and what does theatan2(y, 0)give?Answer.
arctan(y/0)is undefined (aZeroDivisionErroror∞), so the naive IK crashes atx = 0.atan2(y, 0)is defined: it returnsπ/2fory > 0,−π/2fory < 0, and0fory = 0(withx = 0, the origin). Theatan2’s definition handles thex = 0case gracefully, and the naivearctanfails. Theatan2is not a convenience — it is the correct function for the polar angle.
§L.2.3 — Comparative table: the candidates for the shoulder angle
| Candidate | Expression | Correct quadrant | Defined at x = 0 | What it returns in the second quadrant (x < 0, y > 0) |
|---|---|---|---|---|
arctan(y/x) | the naive single-argument arctangent | no — range is (−π/2, π/2) | no — divides by zero | a negative angle, off by π, pointing the arm to the opposite side |
atan2(y, x) | the two-argument arctangent | yes — range is (−π, π] | yes — returns ±π/2 for y ≠ 0 | the correct obtuse angle |
atan2(y, x) − atan2(a₂ sin q₂, a₁ + a₂ cos q₂) | the closed-form q₁ | yes, when both terms are atan2 | yes, except at the inner boundary where both arguments vanish | the correct q₁ for that branch |
±arccos(cos q₂) | the elbow angle | n/a | n/a | the two branches, which must be carried, not collapsed to one |
Design principle (P14.7). A closed form is only as correct as its domain. The 2-link IK’s arithmetic is exact, and every failure in it is a domain failure: the arccos outside [−1, 1] is an unreachable target, the arctan outside the first and fourth quadrants is a reflected arm, and the atan2(0, 0) at the inner boundary is a free parameter reported as a value. Check the domain of each inverse function before trusting the value it returns, and treat the checks as part of the solution rather than as defensive code around it — the reachability test and the branch test are the same test as the arccos’s domain.
§L.2.4 — Concept check
Concept check (displaces M1). A learner says: “My IK function returns a
qthat puts the end-effector exactly on the target, so the problem is solved — there’s nothing left to decide.” What has the learner failed to model, and what bug class follows?Answer. The learner has failed to model that IK is a relation: the pose test
‖f(q) − T_target‖ < εpasses for every branch, and a pass on that test says only that some configuration was found. The bug class is a valid pose delivered as an invalid trajectory: the target jumps across a workspace boundary, the solver lands in the other basin, and the returnedqis correct as a pose and far from the current configuration as a motion — a commanded jump that violates the joint limits or passes through an obstacle. Nothing in the pose residual can see this, because the residual is measured in pose space and the defect is in joint space. The fix is a second test on the returnedq— joint limits, continuity with the previous configuration, collision — and it must be a separate test, because a branch is a decision and the solver never made it.
§L.2.5 — Consolidation checkpoint 2
(a) You should now be able to:
(i) derive cos q₂ from the law of cosines and read the workspace annulus off its domain;
(ii) carry both branches through the q₁ formula and say what distinguishes them geometrically;
(iii) state the solution count at each boundary, including the degenerate case a₁ = a₂ at r = 0.
(b) Transfer probe. A 2-link arm with a₁ = a₂ = 0.5 is asked for the target (0, 0). A colleague’s solver returns q = (0, π) and reports success. Is the answer wrong, and what did the solver not say?
Answer sketch: q = (0, π) is a genuine solution — the links fold back and the tip sits at the origin — so the pose test passes. What the solver did not say is that the solution set is a one-parameter family: r = 0 is the inner boundary, a₁ + a₂ cos q₂ = 0 vanishes, and every q₁ with q₂ = π puts the tip at the origin. Reporting one point as “the” answer is the closed form’s silent failure at a degenerate boundary. The learner who takes it as the answer has a valid pose, no information about the family it belongs to, and no way to choose among them by any criterion the solver supplied.
(c) Remediation rule. If you cannot do (a)(i) or (b), return to §L.2.2 and redo WE-L.2.a, then re-attempt (b) without notes.
Part C — Numerical IK: The Jacobian, the Pseudoinverse, and the Iteration
§L.3 — Objects: the Newton–Raphson iteration, the pose error, and the pseudoinverse
The numerical IK starts from an initial guess q₀ and iterates:
where:
e_k = pose_error(T_{target}, T(q_k))is the pose error — a 6-vector, typically the twist that takesT(q_k)toT_target:e_k = log(T(q_k)^{-1} · T_target) ∈ ℝ⁶(using0.2.3’slogon SE(3)), or the position + orientation error[p_target − p(q); log(R(q)ᵀ R_target)].J(q_k) ∈ ℝ^{6×n}is the geometric Jacobian from1.1.1.J⁺is the Moore–Penrose pseudoinverse, which for a square invertibleJisJ⁻¹, for a wideJ(n > 6, more joints than task dimensions) isJᵀ(JJᵀ)⁻¹(the right inverse, and the minimum-norm solution), and for a tallJ(n < 6) is(JᵀJ)⁻¹Jᵀ(the left inverse, the least-squares solution). The label follows the shape ofJ, which is6×nbecause the task is six-dimensional:n > 6is wide,n < 6is tall. Getting this backwards is not a cosmetic error — forn > 6the productJᵀJisn×nof rank 6 and(JᵀJ)⁻¹does not exist, so the formula fails on exactly the case it was chosen for. The rule of thumb: more joints than tasks → the minimum-norm solution → theJJᵀform, becauseJJᵀis the6×6matrix that is invertible.
The convergence criterion: ‖e_k‖ < ε (the pose error’s norm below a tolerance, e.g., 1e-4 m and 1e-3 rad), or a maximum iteration count (e.g., 100), whichever comes first.
The three failure modes:
- Far initial guess — the error’s basin of convergence is local (the linearization is valid only in a neighborhood of
q_k), and a farq₀may not converge (the error increases or oscillates). - Singularity — the Jacobian’s smallest singular value
σ_minis zero (or near zero), and theJ⁺’s component1/σ_minblows up — the stepJ⁺eis large, and the iteration jumps. The behavior at exactlyσ_min = 0depends on whichJ⁺you computed, and the two spellings give opposite symptoms: the mathematical Moore–Penrose inverse is defined at a singularJ(the zero singular value is truncated,1/0 → 0), so the step along the missing direction is zero and the iteration stalls — silently. The closed form(JᵀJ)⁻¹Jᵀraises instead. See §L.3.1 for which of the two the lesson’s near-miss actually produces. - Local minimum — the iteration reaches a stationary point of
½‖e‖²that is not a solution:Jᵀe = 0while‖e‖ > 0. The test is‖Jᵀe‖, not‖e‖— a residual that stops shrinking means nothing on its own, because a singular configuration produces exactly the same symptom. The two are told apart byσ_min(J): at a singularity the residual lies in the null space of a rank-deficientJ, and the fix is a different starting configuration; at a true local minimumJis full rank, soJᵀe = 0forcese = 0, and a non-zero stationary residual means the target is outside the reachable set or a joint limit is active. §L.6.6’s break-it #3 measures both quantities on the same stalled configuration and findsσ_min = 5·10⁻¹¹, rank5/6,‖Jᵀe‖ = 1.5·10⁻¹¹— the singularity case.
§L.3.1 — Result: the Newton–Raphson iteration converges quadratically near a solution and fails at a singularity; which failure it fails with depends on which pseudoinverse you wrote
Claim. If q_k is sufficiently close to a solution q*, the iteration q_{k+1} = q_k + J⁺ e_k converges quadratically — the error after k+1 steps is O(‖e_k‖²). If J is near-singular, the J⁺’s norm is 1/σ_min, which is large, and the step overshoots; the iteration can fail to converge from a seed that would converge from a well-conditioned one.
Proof sketch. The iteration is the Newton–Raphson method applied to f(q) = T(q), with the “derivative” being the Jacobian. The convergence is quadratic if J is full rank and the initial guess is close — the standard Newton–Raphson theorem for nonlinear systems. The near-singular case: the J⁺’s norm is ‖J⁺‖ = 1/σ_min, and the error’s component along the near-singular direction is amplified by 1/σ_min. The step is large, the linearization that justified it is invalid over a large step, and the next iterate can be further from the solution than the last.
Proof sketch, the exact-singular case. The claim above is about near-singularity, and it must not be extended to σ_min = 0, where it is false. The Moore–Penrose inverse is continuous in J only on the set of fixed rank; at a rank drop it is not. Concretely: J⁺ = V Σ⁺ Uᵀ with the reciprocal of each zero singular value set to zero, so ‖J⁺‖ = 1/σ_r where σ_r is the smallest non-zero singular value — finite. The step is then the minimum-norm solution of J Δq = e, which moves the end-effector only in the directions J can reach. If e lies entirely in the unreachable subspace, the step is exactly zero and the iteration stalls at the seed, printing no error and making no progress. That is the silent version of the failure, and it is the one a lab using pinv will see. The closed form (JᵀJ)⁻¹Jᵀ is the one that raises LinAlgError, because JᵀJ is genuinely singular. Neither behavior is “divergence”.
Corollary. The convergence basin is the neighborhood of q* where the linearization is valid, which is ‖q − q*‖ = O(1/L) where L is the FK’s second derivative’s bound. The basin’s size depends on the arm’s configuration — it is larger near the mid-workspace (the well-conditioned region) and smaller near the singularities (where L is large).
Corollary. The iteration q_{k+1} = q_k + J⁺ e_k with the full J⁺ is the Newton–Raphson step, and it is not the same as q = J⁺ e (a single step) — the single step is the linear approximation, and the iteration is the nonlinear correction. A learner who calls q = J⁺ e once and stops has done one Newton step, which is not the solution.
§L.3.2 — Worked-example ladder
WE-L.3.a — Fully worked: one Newton–Raphson step for the 2-link arm
Input. 2-link arm (a₁ = a₂ = 0.5), current q = (0.0, 0.0) (fully extended along +x, end-effector at (1.0, 0.0)), target (0.6, 0.3).
Trace.
T(q) · [0;0;0;1] = (1.0, 0.0, 0.0)— the current end-effector.e = T_target · [0;0;0;1] − T(q) · [0;0;0;1] = (0.6, 0.3, 0.0) − (1.0, 0.0, 0.0) = (-0.4, 0.3, 0.0).- The Jacobian at
q = (0, 0): from1.1.1’sJ = [ω_1 × (p_ee − q_1); ω_1; ω_2 × (p_ee − q_2); ω_2](withω_1 = ω_2 = (0,0,1),q_1 = (0,0,0),q_2 = (0.5, 0, 0),p_ee = (1.0, 0, 0)):J_1 = [ω_1 × (p_ee − q_1); ω_1] = [(0,0,1) × (1,0,0); (0,0,1)] = [(0, 1, 0); (0,0,1)](linear =(0, 1, 0), angular =(0,0,1)).J_2 = [ω_2 × (p_ee − q_2); ω_2] = [(0,0,1) × (0.5, 0, 0); (0,0,1)] = [(0, 0.5, 0); (0,0,1)].- The full
6×2Jacobian is[[0, 0], [1, 0.5], [0, 0], [0, 0], [0, 0], [1, 1]](in[v; ω]convention). - For the planar arm, the position Jacobian
J_v = [[0, 0], [1, 0.5], [0, 0]]and the angularJ_ω = [[0, 0], [0, 0], [1, 1]].
- The position residual
e_v = (-0.4, 0.3)(thezand angular parts are zero, since the arm is planar and the target has no orientation change). - The pseudoinverse
J_v⁺ = (J_vᵀ J_v)⁻¹ J_vᵀ:J_vᵀ J_v = [[0, 1, 0], [0, 0.5, 0]] · [[0, 0], [1, 0.5], [0, 0]] = [[1, 0.5], [0.5, 0.25]].(J_vᵀ J_v)⁻¹ = (1/(1·0.25 − 0.25)) [[0.25, -0.5], [-0.5, 1]]— det = 0, singular!
- The
q = (0, 0)is a singularity (the arm is fully extended, and the Jacobian’s determinant is zero). TheJ_v⁺does not exist (the inverse is undefined). - The fix: use the DLS
J_vᵀ(J_v J_vᵀ + λ²I)⁻¹, or use a non-singular startingq(e.g.,q = (π/4, π/4)).
Output and interpretation. The q = (0, 0) (fully extended) is a singularity — the J_v’s determinant is zero, and the pseudoinverse does not exist. The numerical IK fails at this configuration, and the fix is to start from a non-singular q, or to use the DLS (Part D) that handles the singularity gracefully. The example is deliberately chosen at the singularity, to show the failure mode clearly.
Self-explanation 7. Before reading on: in step 5, the
det(J_vᵀ J_v) = 0— the singularity is exactly the fully extended arm (q₂ = 0). What is the physical meaning, and why does the IK fail there?Answer. At the fully extended arm, the two joints’ contributions to the end-effector’s radial velocity are parallel — both push the end-effector outward along the
x-axis, with no way to move it tangentially without both joints moving. The arm cannot move radially inward (it is at the outer boundary of the workspace), and a target inside the workspace requires a radial inward motion — which the fully extended arm cannot produce with a finite joint velocity. Thedet = 0is the geometric fact, and theJ_v⁺’s blow-up is the algebraic symptom.
Self-explanation 8. Before reading on: in step 7,
q = (π/4, π/4)is a non-singular starting point. What is thedet(J_v)there, and why does the iteration converge?Answer.
q = (π/4, π/4)givesq₂ = π/4, anddet(J_v) = a₁ a₂ sin(q₂) = 0.5·0.5·sin(π/4) ≈ 0.177— non-zero. TheJ_vis invertible, and the Newton stepJ_v⁺ eis finite. The iteration converges (quadratically, once it is near a solution) to the solution in its basin — and the basin is not the joint-space neighborhood. Fromq = (π/4, π/4)the iteration reachesq⁻ = (1.299, -1.671), the−branch (q₂ < 0), even though that seed is closer to the+branch:‖q₀ − q⁺‖ = 1.457against‖q₀ − q⁻‖ = 2.510. The nearest branch in joint space is not the one the iteration finds.
WE-L.3.b — Completion problem
Input. Same arm, start at q = (π/4, π/4), target (0.6, 0.3). Compute two Newton steps.
Trace (final 25% as prompts).
T(q₀)atq₀ = (π/4, π/4):p_ee = Rz(π/4) · (0.5, 0) + Rz(π/2) · (0.5, 0) = (0.354, 0.354) + (0, 0.5) = (0.354, 0.854). ⟨your turn: verify⟩e₀ = (0.6, 0.3) − (0.354, 0.854) = (0.246, −0.554). ⟨your turn⟩J_vatq₀:J_v = [ω × (p_ee − q_1); ω × (p_ee − q_2)].q_1 = (0,0),q_2 = (0.354, 0.354),p_ee = (0.354, 0.854).J_1 = (0,0,1) × (0.354, 0.854, 0) = (-0.854, 0.354, 0)(thez-component zero),J_2 = (0,0,1) × (0, 0.5, 0) = (-0.5, 0, 0). SoJ_v = [[-0.854, -0.5], [0.354, 0]]. ⟨your turn: verify⟩J_v⁺ e₀: the2×2J_v’s inverse timese₀.det(J_v) = (-0.854)(0) − (-0.5)(0.354) = 0.177.J_v⁻¹ = (1/0.177) [[0, 0.5], [-0.354, -0.854]] = [[0, 2.825], [-2.0, -4.825]].Δq = J_v⁻¹ · (0.246, -0.554) = (2.825·(-0.554), -2.0·0.246 + (-4.825)·(-0.554)) = (-1.565, -0.492 + 2.673) = (-1.565, 2.181). ⟨your turn⟩q₁ = q₀ + Δq = (0.785 − 1.565, 0.785 + 2.181) = (-0.780, 2.966). ⟨your turn⟩T(q₁)— is it closer to(0.6, 0.3)thanq₀’s? ⟨your turn: computep_eeatq₁⟩
Answers.
p_ee ≈ (0.354, 0.854). ✓e₀ ≈ (0.246, −0.554). ✓J_v ≈ [[-0.854, -0.5], [0.354, 0]]. ✓Δq ≈ (-1.565, 2.181). ✓q₁ ≈ (-0.780, 2.966). ✓p_eeatq₁:Rz(-0.780)·(0.5, 0) = (0.355, -0.352).Rz(-0.780 + 2.966)·(0.5, 0) = Rz(2.186)·(0.5, 0) = (-0.290, 0.407).p_ee ≈ (0.067, 0.057). The new error is(0.6 − 0.067, 0.3 − 0.057) = (0.533, 0.243), which has norm0.586vs. the old0.606— slightly smaller, but not much smaller.
The lesson. The first Newton step overshoots (the Δq is (-1.565, 2.181), which is large), and the new error is smaller but not by the quadratic factor (the linearization is poor for a large e₀, and the step overshoots the linearization’s validity). The second step would be smaller (the linearization is better near the solution), and the convergence is eventual (after several steps) but not in one step. The e₀’s large magnitude is the far initial guess — the iteration converges but slowly (the basin is large here, but the linearization’s error dominates the first steps).
WE-L.3.c — Faded: which branch the iteration finds
Setup. 2-link arm, target (0.6, 0.3), whose two analytical solutions are q⁺ = (-0.372, 1.671) (elbow-up) and q⁻ = (1.299, -1.671) (elbow-down). The iteration starts at q₀ = (2.5, -1.0).
Question. Which branch does the iteration converge to, and is it the branch closest to q₀?
You must produce the intermediate steps. The setup and the answer are given.
Answer. The iteration converges to q⁺ = (-0.372, 1.671) — the farther branch. In joint space q₀ sits 1.376 from q⁻ and 3.922 from q⁺, so a “nearest branch” rule picks the wrong one. The basin is not a joint-space ball: the Newton step is J_v⁻¹ e, and J_v⁻¹ is large and badly conditioned away from the solution, so the early iterates are thrown around the joint space (q₀ = (2.5, -1.0) → (3.518, -4.565) → (2.739, -1.572) → …, with the error going up before it comes down: 1.086 → 1.226 → 0.934 → 0.791 → 1.219 → …) before settling at step 9 with ‖e‖ = 1.7·10⁻¹⁴.
The seed-level result generalizes, and the numbers below are what four independent generators give for 400 seeds uniform in [-3, 3]² with pinv and angles wrapped to (-π, π] before the distance is taken:
| generator seed | converged | closer to q⁺, got q⁻ | closer to q⁻, got q⁺ | rule fails |
|---|---|---|---|---|
| 0 | 294 / 400 | 41 | 41 | 28% |
| 1 | 310 / 400 | 61 | 43 | 34% |
| 7 | 306 / 400 | 57 | 40 | 32% |
| 42 | 304 / 400 | 67 | 44 | 37% |
Between a quarter and a third of converging seeds land on the branch that is not the nearest one, in both directions. The exact pair of counts moves with the generator; the fraction does not. Quote the fraction, or quote a table like this one — a single pair of integers from an unstated generator is not a reproducible claim, and it is exactly the kind of number a reader will try to reproduce and fail to.
The lesson. A basin is a region of error space defined by where the linearization points, not a ball in joint space. Seeding with “the current configuration” makes the answer look close to the seed, but nothing in the iteration enforces that, and ‖q* − q_seed‖ cannot be used to decide which branch you got. The planner must check the returned branch against joint limits and collisions; it cannot infer it from the seed.
WE-L.3.d — Contrastive near-miss: the single-step “solution”
Correct solution. Iterate q_{k+1} = q_k + J⁺ e_k until ‖e_k‖ < ε, with multiple steps.
Near-miss. q* = q₀ + J⁺(q₀) · e₀ — one Newton step, called the “solution”.
Why the near-miss produces a plausible but incorrect number. The single step reduces the error by O(‖e₀‖²) (the Newton step’s quadratic convergence is for the full iteration, and the first step’s reduction is first-order in ‖e₀‖ if the linearization is good, and worse if it is poor). For a small e₀, the single step is a good approximation (the quadratic reduction is small for small e₀), and the learner who tests with a small error sees a plausible result. For a large e₀, the single step overshoots, and the new error is larger than the old (the linearization is invalid for the large step). The error is silent in the small-error test and visible in the large-error test, and the symptom is the overshoot — the arm jumps past the target.
The discrepancy, stated. q* = q₀ + J⁺ e₀ is the linear solution, and q* = q₀ + J⁺ e₀ + O(‖e₀‖²) is the nonlinear solution. The two differ by the second-order term, which is small for a small e₀ and large for a large e₀. The iteration q_{k+1} = q_k + J⁺ e_k reduces the error at each step, and the convergence is to the true solution — the single step is not.
Self-explanation 9. Before reading on: in the near-miss, if
e₀is exactly zero (the seed is the solution), what does the single step give?Answer.
q* = q₀ + J⁺ · 0 = q₀— the seed is the solution, and the single step leaves it unchanged. The single step is correct at the solution and incorrect elsewhere, and the iteration is the same at the solution (no step needed). Thee₀ = 0case is degenerate (no iteration needed), and the single step coincides with the true solution only there.
§L.3.3 — Comparative table: the candidate step rules
| Step rule | Shape it is valid for | What it returns | At n > 6 | At a rank drop |
|---|---|---|---|---|
(JᵀJ)⁻¹Jᵀ | tall J, n ≤ 6, full column rank | the least-squares solution | does not exist — JᵀJ is n×n of rank at most 6, so it is singular for exactly the case the rule was reached for | raises LinAlgError, which is at least loud |
Jᵀ(JJᵀ)⁻¹ | wide J, n ≥ 6, full row rank | the minimum-norm solution | exists and is the right one — JJᵀ is 6×6 and invertible | JJᵀ becomes singular and the rule fails |
V Σ⁺ Uᵀ (Moore–Penrose, via SVD) | any shape, any rank | the minimum-norm least-squares solution | exists | truncates 1/0 → 0: finite, and the step along the missing direction is zero |
Jᵀ(JJᵀ + λ²I)⁻¹ (DLS) | any shape, any rank | a bounded approximation to the above | exists | bounded at 1/(2λ), with a systematic bias O(λ²) |
| one application of any of the above | — | a single Newton step, O(‖e‖²) from the answer | — | — |
| the same rule applied in a loop | — | the solution, when the iteration converges | — | — |
Design principle (P14.8). Read the shape of J before choosing the formula. J is 6×n because the task is six-dimensional, so n > 6 makes it wide and n < 6 makes it tall — the opposite of the intuition that more joints means a bigger square matrix. The consequence is not cosmetic: for a redundant arm JᵀJ is n×n of rank 6 and (JᵀJ)⁻¹ does not exist, so the tall formula fails on precisely the arms it is most often written for. More joints than tasks means the minimum-norm solution, which is the JJᵀ form, because JJᵀ is the 6×6 matrix that stays invertible. Assert the branch on n in the code rather than in a comment.
§L.3.4 — Concept check
Concept check (displaces M2). A learner says: “IK is
q = J⁺(T_target − T(q))— I invert the Jacobian, apply it to the error, and that’s the joint configuration.” What has the learner failed to model, and what bug class follows?Answer. The learner has failed to model that
J⁺is a differential step, not a solution. The Jacobian is the FK’s derivative at one configuration, soJ⁺eis exact only for the linearization it came from; the FK is nonlinear, and the error after one step isO(‖e‖²), which is negligible only wheneis already small. The bug class is a solution that is accurate at the scale it was tested and wrong at the scale it is used: a unit test seeded close to the target passes with a single step, and the same code seeded at a realistic distance overshoots, because the linearization that justified the step is invalid over the step’s own length. The fix is the loop —q_{k+1} = q_k + J⁺(q_k)e_kuntil‖e_k‖ < ε— and the loop is the object; the step is a line of it.
§L.3.5 — Consolidation checkpoint 3
(a) You should now be able to:
(i) write the pose error and say which of its two forms the iteration consumes;
(ii) pick the pseudoinverse’s spelling from the shape of J and say why the tall form fails for n > 6;
(iii) state the three failure modes and the test that separates a stationary residual from a genuine local minimum.
(b) Transfer probe. The 2-link arm sits at q = (0, 0), fully extended, and the target is (0.6, 0.3). A colleague runs an iteration built on torch.linalg.pinv, sees q unchanged after 100 iterations, and reports “converged — the pose error must be below tolerance.” What actually happened, and what single number settles it?
Answer sketch: the configuration is exactly singular — both linear columns are (0, 1, 0) and (0, 0.5, 0), so the rank is 1 and the residual’s inward component lies in the unreachable subspace. The Moore–Penrose inverse is finite there, with the zero singular value truncated to zero, so the step along that direction is exactly zero: the iteration stalls at the seed, prints no error, and makes no progress. It did not diverge and it did not converge. The number that settles it is σ_min(J) — zero here, which identifies the stall as a singularity rather than a local minimum — together with ‖e‖, which is still 0.5 and was never checked against the tolerance the colleague claimed.
(c) Remediation rule. If you cannot do (a)(ii) or (b), return to §L.3.2 and redo WE-L.3.a, then re-attempt (b) without notes.
Part D — Singularities, Damping, and the Null Space
§L.4 — Objects: the singular values, manipulability, DLS, and the null-space projection
The Jacobian’s singular value decomposition (SVD) is J = U Σ Vᵀ, with Σ a diagonal matrix of singular values σ₁ ≥ σ₂ ≥ … ≥ σ_min ≥ 0. The rank is the number of non-zero σ_i, and the smallest σ_min is the measure of proximity to a singularity:
σ_min = 0— a singularity (the rank is less thannor6).σ_minsmall — near-singular (theJ⁺’s norm1/σ_minis large).σ_min = O(1)— well-conditioned (theJ⁺’s norm isO(1)).
The manipulability is w(q) = √(det(JJᵀ)) = ∏ σ_i — the product of the singular values, which is zero at a singularity and maximal at the most dexterous configuration. For the 2-link arm, w(q) = a₁ a₂ |sin q₂|, maximized at q₂ = ±π/2 (the right angle).
The damped least-squares (DLS) update is:
The damping λ bounds the J⁺’s amplification — the effective 1/σ_i is replaced by σ_i/(σ_i² + λ²), which is bounded by 1/(2λ) and tends to 1/σ_i for $σ_i$ ≫ λ. The trade-off:
λ → 0: the DLS becomes the pseudoinverse (the unbounded case).λ → ∞: the DLS becomes the gradient descent$q_{k+1}$ = $q_k$ + (1/λ²) Jᵀ $e_k$(the slow but stable case).- Intermediate
λ: the DLS balances convergence speed and stability, and the systematic error (the DLS’s step’s deviation from the pseudoinverse’s step) isO(λ²).
The null-space projection is the redundancy exploitation: for a redundant arm (n > 6), the Jacobian’s n − 6 null directions correspond to joint motions that do not move the end-effector, and the null-space joint velocity (I − J⁺J) q̇_0 can be used for a secondary objective (collision avoidance, joint-limit avoidance) without affecting the primary pose. The projection I − J⁺J is the orthogonal complement of the row space of J, and the secondary velocity is invisible to the pose.
§L.4.1 — Result: the pseudoinverse’s norm is 1/σ_min, and the DLS’s damping bounds it at 1/(2λ); the systematic error is O(λ²), and the choice of λ is the trade-off
Claim. The pseudoinverse’s norm is ‖J⁺‖ = 1/σ_min, which is unbounded as $σ_min$ → 0. The DLS’s effective inverse has the effective singular values σ_i/(σ_i² + λ²), bounded by 1/(2λ), and the DLS’s step deviates from the pseudoinverse’s by a bounded amount that is systematic — the DLS biases the solution toward the null direction, and the bias shrinks as O(λ²).
Proof sketch. The SVD gives J = U Σ Vᵀ, and J⁺ = V Σ⁺ Uᵀ with Σ⁺ the reciprocals of the singular values (zero for $σ_i$ = 0). The norm ‖J⁺‖ = 1/σ_min. The DLS’s effective $Σ_DLS$ has entries σ_i/(σ_i² + λ²), which is bounded by 1/(2λ) (the maximum of x/(x² + λ²) is 1/(2λ) at x = λ). The difference is exact, not asymptotic:
1/σ − σ/(σ² + λ²) = λ²/(σ(σ² + λ²)).
For σ ≫ λ this is O(λ²/σ³), and for σ ≪ λ it tends to 1/σ — the pseudoinverse’s own value, because the DLS’s contribution has gone to zero. So the deviation is largest exactly where the pseudoinverse is largest; the damping does not add error there, it removes the whole term.
Corollary. The damping choice is the trade-off: a small λ converges fast but is unstable near singularities; a large λ is stable but slow and biased. The adaptive choice λ(q) = $λ_0$ · (1 − σ_min/σ_threshold) (or λ = 0 when $σ_min$ > $σ_threshold$) is a common fix, which damps only near singularities.
Corollary. The null-space projection I − J⁺J is PSD (a projection), and it is exactly zero for a non-redundant arm (n = 6, full-rank J). For a redundant arm (n > 6), the null-space dimension is n − 6, and the secondary velocity is non-zero in the null directions.
§L.4.2 — Worked-example ladder
WE-L.4.a — Fully worked: the 2-link arm’s manipulability at three configurations
Input. 2-link arm (a₁ = a₂ = 0.5), at q = (0, 0), (π/4, π/2), and (0, π/2).
Trace.
q = (0, 0):q₂ = 0,w = 0.5·0.5·|sin 0| = 0— singular (fully extended).q = (π/4, π/2):q₂ = π/2,w = 0.5·0.5·|sin(π/2)| = 0.25— maximal manipulability (the right angle).q = (0, π/2):q₂ = π/2(same as above),w = 0.25— the same manipulability (the shoulder doesn’t affectwfor the 2-link planar arm).
Output and interpretation. The manipulability is zero at the fully extended arm and maximal at the right angle. The shoulder angle q₁ does not affect w (the arm’s shape is the same, only its global orientation changes), and the elbow angle q₂ determines w. The w = 0 at q₂ = 0 and q₂ = π is the workspace boundary, and the w = 0.25 at q₂ = π/2 is the most dexterous configuration.
Self-explanation 10. Before reading on: in step 1,
q = (0, 0)givesw = 0— the manipulability is zero. What is the physical meaning, and why is it a singularity?Answer. The manipulability is the “ease of motion” — the volume of the ellipsoid that the end-effector can sweep with a unit joint-velocity ball. At
q = (0, 0)(fully extended), the arm’s two joints both push the end-effector radially (along+x), and the tangential direction has zero velocity — the end-effector can only move outward (or not at all), and any tangential motion requires infinite joint velocity. Thew = 0is the degenerate ellipsoid (a line, not a volume), and thew = 0is the singularity — the arm’s velocity constraints make some task-space directions unreachable.
WE-L.4.b — Completion problem
Input. Same arm, target at q₂ = π/2 (the right angle), and the DLS with λ = 0.1.
Trace (final 25% as prompts).
- The singular values of
$J_v$atq = (π/4, π/2): the2×2$J_v$ = [[−a₁ sin q₁ − a₂ sin(q₁+q₂), −a₂ sin(q₁+q₂)], [a₁ cos q₁ + a₂ cos(q₁+q₂), a₂ cos(q₁+q₂)]]. Atq₁ = π/4, q₂ = π/2:−a₁ sin(π/4) − a₂ sin(3π/4) = −0.354 − 0.354 = −0.707;−a₂ sin(3π/4) = −0.354;a₁ cos(π/4) + a₂ cos(3π/4) = 0.354 − 0.354 = 0;a₂ cos(3π/4) = −0.354. So$J_v$ = [[−0.707, −0.354], [0, −0.354]]. ⟨your turn: verify⟩ - Singular values:
$J_v$ᵀ $J_v$ = [[0.5, 0.25], [0.25, 0.25]]. Its eigenvalues are0.6545and0.0955, so the singular values are their square roots:σ₁ ≈ 0.809,σ₂ ≈ 0.309. (Do not read the eigenvalues as singular values — that is a factor-of-√σerror, and it is the usual one here.) ⟨your turn: verify⟩ J⁺’s norm:1/σ_min ≈ 3.236. The arm is moderately conditioned — the condition numberσ₁/σ₂ = 2.618. ⟨your turn⟩- DLS’s effective
1/σ_min:σ_min/(σ_min² + λ²) = 0.309/(0.0955 + 0.01) = 0.309/0.1055 ≈ 2.929, against the pseudoinverse’s3.236— the damping shrinks the inverse by 9.5%. ⟨your turn⟩ - At a near-singular
q = (0, 0.1):$σ_min$ ≈ 0.0224. Pseudoinverse1/σ_min = 44.74, DLS0.0224/(0.0005 + 0.01) ≈ 2.129— the DLS is 21× smaller than the pseudoinverse. ⟨your turn⟩
Answers.
$J_v$ ≈ [[−0.707, −0.354], [0, −0.354]]. ✓- Singular values
≈ (0.809, 0.309). ✓ ‖J⁺‖ ≈ 3.236. ✓- DLS effective
≈ 2.929— the damping’s effect is small here (the condition number is moderate). - At
q = (0, 0.1), the DLS caps the amplification at2.13vs. the pseudoinverse’s44.7— a 21× reduction, which is the stability the damping provides.
The lesson. The damping’s effect is proportional to the condition number — at a well-conditioned configuration, the DLS is almost the pseudoinverse; at a near-singular configuration, the DLS is much smaller. The choice of λ is the trade-off: λ = 0.1 is a good default, but the adaptive λ (large near singularities) is better for a general arm.
WE-L.4.c — Faded: the null-space projection for a redundant arm
Setup. A 7-DOF redundant arm (n = 7), with the Jacobian J ∈ ℝ^{6×7} and a secondary objective g(q) = ‖q − $q_nominal$‖² (a preference for the nominal configuration).
Question. Write the null-space update that moves the arm toward $q_nominal$ without changing the end-effector’s pose.
You must produce the intermediate steps. The setup and the answer are given.
Answer. The primary step is J⁺ e, and the secondary step is (I − J⁺J) q̇_secondary where q̇_secondary = −∇g = −2(q − $q_nominal$). The combined step is $q_{k+1}$ = $q_k$ + J⁺ e + α (I − J⁺J) q̇_secondary where α is the secondary gain. The projection I − J⁺J removes the primary-space component of q̇_secondary, so the secondary motion is null (the end-effector does not move), and the arm drifts toward $q_nominal$ without affecting the pose.
The lesson. The null-space projection is the redundant arm’s feature — the n − 6 extra DOF can be used for a secondary objective without sacrificing the primary pose. The α is the secondary gain, and its choice is the trade-off between the primary convergence and the secondary progress.
WE-L.4.d — Contrastive near-miss: the undamped pseudoinverse at the singularity
Correct solution. J⁺ with damping Jᵀ(JJᵀ + λ²I)⁻¹ — the bounded step.
Near-miss. J⁺ = (JᵀJ)⁻¹Jᵀ — the undamped pseudoinverse, which at a singularity has infinite norm.
Why the near-miss produces a plausible but incorrect number. At a well-conditioned q (the normal case), the undamped J⁺ is finite and correct, and the iteration converges to the solution — the bug is invisible. At a near-singular q, the J⁺’s norm 1/σ_min is large (e.g., 40 for $σ_min$ = 0.025), and the step J⁺ e is huge — the arm jumps to a far configuration, which is not the solution (the linearization is invalid for the large step). The error is silent at well-conditioned configurations and catastrophic near singularities, and the symptom is the arm’s jump (a large, discontinuous motion).
The discrepancy, stated. J⁺ and the DLS’s Jᵀ(JJᵀ + λ²I)⁻¹ have effective singular values 1/σ and σ/(σ² + λ²), which differ by λ²/(σ(σ² + λ²)). For σ ≫ λ that is O(λ²/σ³) — negligible. For σ ≪ λ the DLS’s value tends to σ/λ² → 0 while the pseudoinverse’s 1/σ → ∞: the damping does not perturb a large number, it replaces it. The near-singular case is therefore the failure — the pseudoinverse’s huge step overshoots, and the iteration fails to converge. The DLS’s damping bounds the step at 1/(2λ), and the iteration converges (slowly) but does not fail. Measured on the 2-link arm at q = (π/4, π/2) with e = (0.1, 0.2): the steps differ by 0.0527 at λ = 0.1, 0.0142 at λ = 0.05, 0.0036 at λ = 0.025. The successive ratios are 3.72 and 3.92, and the ratio gap/λ² is 5.27, 5.68, 5.79 — drifting upward toward its σ ≪ λ limit rather than sitting at a constant. Quoting “a factor of 3.7 for each halving” is right for the first halving and wrong for the second; the honest statement is that the gap is O(λ²) over this window with a coefficient that has not yet converged, and the coefficient approaches 1/σ_min from below as λ → 0.
Self-explanation 11. Before reading on: in the near-miss, if the
qis exactly singular ($σ_min$ = 0), what does the undampedJ⁺give?Answer. It depends on how you compute it, and the difference is the whole point. The mathematical Moore–Penrose pseudoinverse is defined at a singular
J— it isV Σ⁺ Uᵀwith1/0replaced by0— so the answer is notNaN; it is a finite matrix that simply cannot move the end-effector along the missing direction.(JᵀJ)⁻¹Jᵀ, the formula the lesson uses, is the one that crashes:torch.linalg.invraises_LinAlgErrorandnumpy.linalg.invraisesLinAlgError. Buttorch.linalg.pinvdoes not raise — it truncates the zero singular value and returns[[0.0, 0.8], [0.0, 0.4]]for the 2-link arm atq = (0,0). So “the singularity gives a loud error” is true only for the(JᵀJ)⁻¹spelling, and a lab that usespinvsees the silent version: a finite step in the wrong direction, or no step at all. The near-singular case ($σ_min$small but non-zero) is the same failure without the crash:J⁺is finite but huge, and the iteration jumps.
§L.4.3 — Comparative table: the update rules near a singularity
| Rule | Effective gain on $σ_i$ | Bound | Behavior at $σ_min$ = 0 | Behavior at $σ_min$ = 10⁻³ with λ = 0.1 | Cost |
|---|---|---|---|---|---|
1/σ_i (undamped pseudoinverse) | 1/σ_i | none — ‖J⁺‖ = 1/σ_min | 1/0 → 0 under the SVD definition; (JᵀJ)⁻¹Jᵀ raises | 1000 — the step jumps | one SVD or one solve |
σ_i/(σ_i² + λ²) (DLS) | bounded | 1/(2λ) | 0 — same as the pseudoinverse | 0.1 — 10,000× smaller | one solve, λ² added to the diagonal |
σ_i/(σ_i² + λ²) with λ → ∞ | σ_i/λ² | — | 0 | gradient descent: slow, stable | — |
adaptive λ(q) | bounded, small away from singularity | 1/(2λ(q)) | 0 | pseudoinverse-like where $σ_min$ is healthy | a check per step |
(I − J⁺J) q̇_0 (null-space term) | n/a — acts only in the null space | — | the projection survives, the primary step does not | the secondary motion is invisible to the pose | one projection |
Design principle (P14.9). Damping trades a bounded error for an unbounded one. The pseudoinverse is exact wherever it is finite and unbounded wherever it matters; the DLS is bounded everywhere and exact nowhere. The trade is not a compromise between two estimates of the same quantity — the damping replaces the term rather than perturbing it, since 1/σ − σ/(σ²+λ²) = λ²/(σ(σ²+λ²)) tends to 1/σ as σ → 0, which is to say the DLS’s own contribution vanishes and the whole of the pseudoinverse’s is gone. So state the bias as part of the answer: a DLS-driven arm converges to a pose that is wrong by O(λ²), and a controller that needs the last millimeter must remove the damping near convergence rather than reduce the tolerance.
§L.4.4 — Concept check
Concept check (displaces M3). A learner says: “I’ll use the pseudoinverse at singularities because it’s the minimum-norm solution — minimum norm is the numerically stable choice.” What has the learner failed to model, and what bug class follows?
Answer. The learner has failed to model what the norm is the minimum of:
J⁺eis the smallestΔqthat achieves the stepe, and “smallest” is a statement about the step, not about the gain that produces it. The gain is1/σ_min, so the norm is minimal precisely by being the limit of an unbounded amplification — the two facts are the same fact. The bug class is a step that is minimal in joint space and enormous in joint space: near a singularity$σ_min$is small,1/σ_minis large, and the minimum-norm step is still a huge one, so the arm jumps to a far configuration whose linearization is invalid, and the next error is larger than the last. At an exact singularity the behavior changes character rather than severity — the Moore–Penrose step is finite and zero along the missing direction, so the iteration stalls silently instead of blowing up, and the two symptoms are easy to mistake for each other. The fix is damping, which bounds the gain at1/(2λ), and the diagnosis is$σ_min$, which is the only number that tells the two cases apart.
§L.4.5 — Consolidation checkpoint 4
(a) You should now be able to:
(i) read $σ_min$ as the proximity to a singularity and 1/σ_min as the pseudoinverse’s gain;
(ii) compute the DLS’s effective gain and its bound 1/(2λ) at a given λ;
(iii) distinguish the exact-singularity behavior (finite, zero, silent stall) from the near-singular one (finite but huge) and from the (JᵀJ)⁻¹ spelling (raises).
(b) Transfer probe. A 2-link arm is at q = (π/4, π/2), where $σ_min$ ≈ 0.309, and the DLS runs with λ = 0.1. A colleague halves λ three times to speed convergence and reports that each halving roughly doubles the gain. Is the claim right, and what does it miss?
Answer sketch: at $σ_min$ = 0.309 ≫ λ, the DLS gain is already within 9.5% of 1/σ_min — 2.929 against 3.236 — so halving λ changes almost nothing; the reported doubling would require the arm to be in the regime σ ≪ λ, which it is not. The claim is a statement about the wrong regime, and it is the usual one: the damping’s effect scales with the condition number, so at a well-conditioned configuration the DLS is the pseudoinverse and the dial does nothing. The number to check before tuning is σ_min/λ; only when it is small does the gain move, and then it moves as σ/(σ²+λ²) rather than as a constant factor.
(c) Remediation rule. If you cannot do (a)(iii) or (b), return to §L.4.2 and redo WE-L.4.b, then re-attempt (b) without notes.
Part E — Failure Taxonomy, Lab, Cheat Sheet, Exercises, Exit Test
§L.5 — Failure taxonomy
| Symptom | Three most likely causes, ranked | Discriminating test | Fix |
|---|---|---|---|
| The IK returns a different branch than expected | (1) The seed is in a different basin; (2) the target is near a branch boundary; (3) the arm’s geometry has more branches than expected | Not ‖q* − $q_seed$‖ — the basin is not a joint-space ball, and the nearer branch is the wrong one on 106 of 400 random seeds (§WE-L.3.c). Check the sign of q₂ and the branch’s joint-limit validity | Validate the branch against the limits; use the current q as the seed |
| The IK diverges near a target | (1) Undamped J⁺ at a singularity; (2) the target is unreachable; (3) the step is too large | Check $σ_min(J$); if small, it is (1) | Use the DLS |
| The IK stalls at a non-zero error | (1) A singular configuration reached from this seed; (2) a genuine local minimum (J full rank); (3) the target is outside the reachable set | ‖Jᵀe‖ separates “stationary” from “still moving”; $σ_min(J$) then separates (1) from (2) — rank-deficient J means the residual is unreachable from here. Measured: $σ_min$ = 5·10⁻¹¹, rank 5/6 (§L.6.6) | Multiple restarts from a different seed; DLS alone does not escape |
The IK crashes with a NaN | (1) arccos of an out-of-range value; (2) (JᵀJ)⁻¹ of a singular matrix; (3) division by $σ_min$ = 0 | Check the operation; the arccos is (1), the inverse is (2) | Saturate the target; DLS |
| The IK returns a pose far from the target | (1) The kinematic model is wrong (the DH table or the PoE); (2) the Jacobian is wrong (missing moment arm); (3) the convergence criterion is too loose | Compare the returned T(q*) to $T_target$; if the norm is large, the model is wrong | Check the DH/PoE cross-check |
| The IK returns a solution outside the joint limits | (1) The IK does not filter the limits; (2) the target is only reachable outside the limits; (3) the branch choice ignores the limits | Check q* against the limits; if outside, it is (1) | Filter the solutions; multi-restart with limit-checked seeds |
§L.6 — The lab
§L.6.1 — Task
Implement the analytical IK for the 2-link arm, the numerical IK (Newton–Raphson with DLS) for the 6-DOF arm, and verify the convergence, the branch behavior, and the singularity handling; then break the pipeline three ways.
§L.6.2 — Conditions
Python 3.10+, PyTorch 2.2+, CPU. Seed 42, dtype = float64. Runtime under 3 minutes. The 2-link arm’s targets: (0.6, 0.3) and (0.0, 0.5). The 6-DOF arm’s target: a reachable pose from the running case’s q = [0.3, 0.5, −0.2, 0.1, 0.4, −0.3] (the target is $T(q_target$) for a known $q_target$, so the solution is known).
§L.6.3 — Controls held fixed
The seed, dtype, the arm’s parameters, the target poses, and the iteration’s parameters (max_iter = 100, tol = 1e-6, λ = 0.1) are fixed.
§L.6.4 — Metrics table
| Metric | Definition | How measured |
|---|---|---|
ik_2link_error | ‖$p_ee(q*$) − $p_target$‖ for the analytical 2-link | The closed-form’s output |
ik_nr_converged | Whether the NR iteration converges | The error’s final value vs. the tolerance |
ik_nr_iterations | The number of iterations to converge | The iteration counter |
ik_dls_error | The DLS’s final pose error | The error’s final value |
branch_jump | ‖q⁺ − q⁻‖ — the joint-space distance between the two analytical branches at the same target | The difference between the two branches |
singularity_ratio | ‖J⁺e‖ / ‖Jᵀ(JJᵀ + λ²I)⁻¹e‖ just off the singularity | The q₂ sweep |
§L.6.5 — Pre-registered hypotheses
- H1. The 2-link analytical IK’s
ik_2link_error < 1e-10(the closed form is exact). The prediction is the bound. - H2. The DLS IK for the 6-DOF arm converges from a near seed (
‖$q_seed$ − $q_target$‖ < 0.5), withconverged = Trueand‖$q_seed$ − $q_target$‖ < 0.5— the seed’s premise and the convergence together. The iteration count is reported, not gated: atλ = 0.1the damped step is small enough that convergence takes ~56 iterations, so a< 20bound would be a threshold chosen to fail. The prediction is the pair. - H3. The damping’s systematic error shrinks with
λ: atλ = 0.1the converged position error is< 1e-4, and loweringλto0.01lowers it by roughly two orders of magnitude. The prediction is the pair (both< 1e-4, the smallerλstrictly smaller). - H4. The
branch_jump—‖q⁺ − q⁻‖between the two analytical branches at(0.5, 0.5)— is > 1.0 (a full branch switch). The prediction is the bound. - H5. Near the singularity, the undamped step’s norm exceeds the damped step’s by more than 100×: at
q = (0, 1e-3)the pseudoinverse step is4472while the damped step is0.022. Exactly at the singularity both are zero — the requested direction is in the null space and neither method can move there — so the prediction is stated for the near-singular regime, which is where the trade-off lives. The prediction is the ratio.
§L.6.6 — The break-it sequence
-
Undamped pseudoinverse at a singularity (silent). Use the undamped
J⁺atq = (0, 0). Predicted symptom: no exception.torch.linalg.pinvtruncates the zero singular value and returns a finite matrix, so the bug is not the crash but the enormous step it produces just off the singularity —4472atq₂ = 1e-3against the damped0.022. Actual: silent. -
Single-branch assumption (silent). Return only the
+branch for the 2-link arm, ignoring the−. Predicted symptom: the+branch is valid for some targets (the ones where the−is outside the joint limits), and invalid for others (where the+is outside). Actual: as predicted — silent (the returned branch is a valid solution for that target, just not the closest one). -
Far seed (silent). Seed the DLS with
q = (2.5, -1.0, 0.5, -0.5, 1.0, -1.0), far from$q_target$. Predicted symptom: no exception and no error message — the loop returns whatever it has whenmax_iterruns out, and the caller sees a pose that is close but wrong, or a joint vector that differs from$q_target$. Actual: silent, and worse than the prediction, in a way that changes what the test must measure. The fixed seed converges to the same configuration:‖$q_ik$ − $q_target$‖ = 6.2832, which is exactly2π, so the raw joint-space distance reports a jump where the configuration is identical. Measured over 60 far seeds ($q_target$ + 1.2·randn): 16 converge to the same configuration (wrapped distance0.0000), 16 converge to a different one (0.4813), and 28 stall atmax_iter = 300with a position error of4.16e-02— 4 cm on a 1.2 m arm, which passes a1e-1check and fails a1e-6one. So the joint-space distance must be measured modulo2π, and the stall is the failure mode a pose test alone will miss.The stall is a singularity, not a local minimum — and the test that separates them is
$σ_min$, not the residual. Every stalled seed above lands on the same configuration, with$σ_min(J$) = 5·10⁻¹¹, rank5/6, and‖Jᵀe‖ = 1.5·10⁻¹¹. That last number is the discriminator: at a true local minimum of the pose error,Jᵀe ≠ 0and the residual points somewhere the arm can still move. HereJᵀe = 0to eleven digits while‖e‖ = 0.0475, so the residual lies entirely in the Jacobian’s null space — the arm is at a singular configuration and the remaining error is in a direction no joint can produce. Iterating longer does not help: 2000 iterations leave the error at4.16e-02. The lesson’s three failure modes are not three independent mechanisms; this one is mode 2 (singularity) wearing mode 3’s clothes, and‖Jᵀe‖is what tells you which you are looking at.
§L.6.7 — Acceptance criteria
| Criterion | Threshold | Artefact it is tested on |
|---|---|---|
H1’s ik_2link_error | < 1e-10 | The analytical IK’s print |
| H2’s convergence | True with ‖$q_seed$ − $q_target$‖ < 0.5 | The DLS’s print |
| H3’s damping error | both < 1e-4; λ = 0.01 strictly below λ = 0.1 | The λ sweep |
| H4’s branch jump | > 1.0 | The branch comparison |
| H5’s undamped vs. damped step | undamped / damped > 100 near the singularity | The q2 sweep |
| Break-it #3’s stall diagnosis | ‖Jᵀe‖ < 1e-8 with $σ_min$ < 1e-8 on the stalled config | The stalled-config print |
| Three break-its predicted | 3/3 timestamped | The prediction file |
The atan2 is used, not arctan | The code uses atan2 | The code review |
§L.6.8 — What good looks like
A strong submission has five properties:
- The analytical 2-link IK is implemented with
atan2, and the two branches are both returned (not just one). - The NR iteration is implemented with the DLS, and the
λ’s effect is demonstrated (the DLS’s systematic error vs. the NR’s exactness). - The branch choice is validated against the joint limits, and the near-boundary case’s jump is shown.
- The near-singular blow-up is shown for the undamped step and the bounded damped step beside it, with the numerical values and the
q₂sweep that locates the regime. - The break-its are predicted with the symptom (silent pseudoinverse truncation, silent branch switch, silent far-seed stall) stated before running, and the stall is diagnosed rather than labeled —
‖Jᵀe‖for stationarity,$σ_min$for which kind.
§L.6.9 — Runnable lab code
# lab_1_1_2.py — Analytical and numerical IK, branches, singularities, DLS
# Requires: Python 3.10+, PyTorch 2.2+
import torch, math
torch.manual_seed(42); dtype, device = torch.float64, torch.device("cpu")
# --- Step 1: the 2-link analytical IK --------------------------------
def ik_2link(x, y, a1=0.5, a2=0.5):
"""Return both branches (q1, q2) for the 2-link planar arm."""
r2 = x*x + y*y
cos_q2 = (r2 - a1*a1 - a2*a2) / (2*a1*a2)
if abs(cos_q2) > 1:
return None # unreachable
q2_plus = math.acos(cos_q2)
q2_minus = -q2_plus
def q1_for(q2):
return math.atan2(y, x) - math.atan2(a2*math.sin(q2), a1 + a2*math.cos(q2))
return [(q1_for(q2_plus), q2_plus), (q1_for(q2_minus), q2_minus)]
def fk_2link(q, a1=0.5, a2=0.5):
x = a1*math.cos(q[0]) + a2*math.cos(q[0]+q[1])
y = a1*math.sin(q[0]) + a2*math.sin(q[0]+q[1])
return x, y
# Test on the WE-L.2.a target (0.6, 0.3)
target = (0.6, 0.3)
branches = ik_2link(*target)
ik_2link_err = 0.0
for i, q in enumerate(branches):
x, y = fk_2link(q)
err = math.sqrt((x - target[0])**2 + (y - target[1])**2)
ik_2link_err = max(ik_2link_err, err)
print(f"branch {i}: q = ({q[0]:.4f}, {q[1]:.4f}), fk = ({x:.4f}, {y:.4f}), err = {err:.2e}")
# Test on the boundary (0.0, 0.0) — fully folded
print$f"\ntarget (0,0): {ik_2link(0, 0)}"$
# Test on unreachable (1.5, 0.0)
print(f"target (1.5, 0): {ik_2link(1.5, 0)}")
# --- Step 2: numerical IK with DLS for the 6-DOF arm ----------------
def fk_6(q):
"""PoE FK (from 0.2.3) for the running case."""
a = 0.2
twists = [
torch.tensor([0., 0., 1., 0., 0., 0.], dtype=dtype, device=device),
torch.tensor([0., 1., 0., 0., 0., a], dtype=dtype, device=device),
torch.tensor([0., 0., 1., 0., -2*a, 0.], dtype=dtype, device=device),
torch.tensor([0., 1., 0., 0., 0., 3*a], dtype=dtype, device=device),
torch.tensor([0., 0., 1., 0., -4*a, 0.], dtype=dtype, device=device),
torch.tensor([0., 1., 0., 0., 0., 5*a], dtype=dtype, device=device),
]
T = torch.eye(4, dtype=dtype, device=device)
for xi, qi in zip(twists, q):
w, v = xi[:3], xi[3:]
th = qi
W = torch.tensor([[0, -w[2], w[1]], [w[2], 0, -w[0]], [-w[1], w[0], 0]], dtype=dtype, device=device)
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)
$T_i$ = torch.eye(4, dtype=dtype, device=device)
$T_i$[:3, :3] = R; $T_i$[:3, 3] = J @ v
T = T @ $T_i$
$T_end$ = torch.eye(4, dtype=dtype, device=device)
$T_end$[:3, 3] = torch.tensor([6*a, 0., 0.], dtype=dtype, device=device)
return T @ $T_end$
def jacobian_6(q):
"""Numerical 6x6 Jacobian via finite differences (for the lab)."""
J = torch.zeros(6, 6, dtype=dtype, device=device)
delta = 1e-6
T0 = fk_6(q)
for i in range(6):
$q_p$ = q.clone(); $q_p[i$] += delta
$T_p$ = $fk_6(q_p$)
J[:3, i] = ($T_p$[:3, 3] - T0[:3, 3]) / delta
dR = ($T_p$[:3, :3] - T0[:3, :3]) @ T0[:3, :3].T / delta
J[3:, i] = torch.tensor([dR[2, 1], dR[0, 2], dR[1, 0]], dtype=dtype, device=device)
return J
def numerical_ik(target_T, $q_seed$, lam=0.1, max_iter=100, tol=1e-6):
q = $q_seed$.clone()
for k in range(max_iter):
T = fk_6(q)
# Position error
$e_p$ = target_T[:3, 3] - T[:3, 3]
# Orientation error (log of $R_err$)
$R_err$ = T[:3, :3].T @ target_T[:3, :3]
# Simplified: use the skew-symmetric part
$e_o$ = torch.$tensor([R_err[2$, 1] - $R_err[1$, 2], $R_err[0$, 2] - $R_err[2$, 0], $R_err[1$, 0] - $R_err[0$, 1]], dtype=dtype) / 2
e = torch.$cat([e_p$, $e_o$])
if e.norm() < tol:
return q, k, True
J = jacobian_6(q)
# DLS update
JJT = J @ J.T
delta_q = J.T @ torch.linalg.solve(JJT + lam**2 * torch.eye(6, dtype=dtype), e)
q = q + delta_q
return q, max_iter, False
# Target from a known q
$q_target$ = torch.tensor([0.3, 0.5, -0.2, 0.1, 0.4, -0.3], dtype=dtype, device=device)
$T_target$ = $fk_6(q_target$)
# H2's premise is $||q_seed$ - $q_target||$ < 0.5, so the perturbation must be
# small enough to satisfy it; 0.3*randn gives ~0.98 and tests a different claim.
$q_seed$ = $q_target$ + 0.1 * torch.randn(6, dtype=dtype)
$q_ik$, iters, converged = $numerical_ik(T_target$, $q_seed$)
err = ($fk_6(q_ik$)[:3, 3] - $T_target$[:3, 3]).norm().item()
print$f"\nDLS IK: converged={converged}, iters={iters}, pos_error={err:.2e}"$
print(f" $q_ik$ = {q_ik.tolist()}")
print(f" $q_target$ = {q_target.tolist()}")
print(f" ‖$q_ik$ − $q_target$‖ = {(q_ik - $q_target$).norm().item():.4f}")
# H3: the damping's error is O(lambda^2), so a smaller lambda converges tighter.
err_by_lam = {}
for lam in (0.1, 0.01):
$q_l$, it_l, $c_l$ = $numerical_ik(T_target$, $q_seed$.clone(), lam=lam)
err_by_lam[lam] = ($fk_6(q_l$)[:3, 3] - $T_target$[:3, 3]).norm().item()
print(f" lambda={lam}: $converged={c_l}$ in {it_l} iters, pos_error={err_by_lam[lam]:.2e}")
# --- Step 3: undamped pseudoinverse at singularity (SILENT) ----------
# torch.linalg.pinv does not raise at a singular J: it truncates the zero
# singular value and returns a finite matrix. The failure is the huge step
# just OFF the singularity, not a crash.
def jacobian_2link(q, a1=0.5, a2=0.5):
J = torch.zeros(2, 2, dtype=dtype, device=device)
J[0, 0] = -a1*math.sin(q[0]) - a2*math.sin(q[0]+q[1])
J[0, 1] = -a2*math.sin(q[0]+q[1])
J[1, 0] = a1*math.cos(q[0]) + a2*math.cos(q[0]+q[1])
J[1, 1] = a2*math.cos(q[0]+q[1])
return J
q0 = torch.tensor([0., 0.], dtype=dtype, device=device)
J0 = jacobian_2link(q0)
det_J0 = torch.linalg.det(J0).item()
print(f"\n[break-it #1] det(J) at q=(0,0): {det_J0:.2e} (singular, but pinv will NOT raise)")
# --- Step 4: branch jump, 2-link (silent) ---------------------------
# Not a far seed: this is the single-branch trap. Both branches are valid
# solutions for the same target, so a solver that returns one is not wrong —
# it just may not be the one the joint limits allow.
target_boundary = (0.5, 0.5)
branches_b = ik_2link(*target_boundary)
print(f"\n[break-it #2] branches at (0.5, 0.5): {branches_b}")
if branches_b:
jump = math.sqrt((branches_b[0][0] - branches_b[1][0])**2 + (branches_b[0][1] - branches_b[1][1])**2)
print(f" branch jump: {jump:.4f} (predicted > 1.0 for a full branch switch)")
# --- Step 5: undamped vs. damped step NEAR the singularity ---------
# Exactly at q=(0,0) the requested direction lies in the null space, and
# NEITHER step moves: pinv returns zero and the damped solve returns zero
# too. The damping's benefit appears just OFF the singularity, where the
# undamped step blows up and the damped one stays bounded. That is the
# regime the lesson is about, so measure there.
$e_sing$ = torch.tensor([1.0, 0.0], dtype=dtype, device=device) # +x is the weak direction
print(f"\n[break-it #1] det(J) at q=(0,0) = {det_J0:.2e}; pinv = {torch.linalg.pinv(J0).tolist()}")
print("q2 sigma_min |dq_undamped| |dq_damped|")
for q2 in (1e-4, 1e-3, 1e-2, 1e-1):
Jq = jacobian_2link(torch.tensor([0., q2], dtype=dtype, device=device))
sv = torch.linalg.svdvals(Jq).min().item()
du = (torch.linalg.pinv(Jq) @ $e_sing$).norm().item()
dd = (Jq.T @ torch.linalg.solve(Jq @ Jq.T + 0.1**2 * torch.eye(2, dtype=dtype, device=device),
$e_sing$)).norm().item()
print(f"{q2:8.4f} {sv:.3e} {du:12.4f} {dd:10.4f}")
$J_near$ = jacobian_2link(torch.tensor([0., 1e-3], dtype=dtype, device=device))
dq_undamped = torch.linalg.$pinv(J_near$) @ $e_sing$
dq_damped = $J_near$.T @ torch.linalg.solve(
$J_near$ @ $J_near$.T + 0.1**2 * torch.eye(2, dtype=dtype, device=device), $e_sing$)
# --- Step 6: far seed (silent) --------------------------------------
# A far seed does NOT raise: the loop simply runs to max_iter and returns
# whatever it has. Two distinct silent failures come out of it, and they need
# different tests.
# (a) a 2*pi-wrapped joint vector, where the CONFIGURATION is identical and
# only the raw distance looks like a branch jump;
# (b) a stall, where the pose is close but not converged.
# So measure the distance modulo 2*pi, and check `converged` as well.
def wrap_dist(dq):
"""Joint-space distance modulo 2*pi — the physically meaningful one."""
return (torch.remainder(dq + math.pi, 2 * math.pi) - math.pi).norm().item()
$q_far$ = torch.tensor([2.5, -1.0, 0.5, -0.5, 1.0, -1.0], dtype=dtype, device=device)
$q_ik_far$, it_far, conv_far = $numerical_ik(T_target$, $q_far$, max_iter=300)
far_pos_err = ($fk_6(q_ik_far$)[:3, 3] - $T_target$[:3, 3]).norm().item()
print(f"\n[break-it #3] fixed far seed: converged={conv_far} in {it_far} iters, "
f"pos_error={far_pos_err:.2e}")
print(f"[break-it #3] raw $||q_ik$ - $q_target||$ = {(q_ik_far - $q_target$).norm().item():.4f}"
f" wrapped = {wrap_dist(q_ik_far - $q_target$):.4f}"
f" (near-seed run: raw {(q_ik - $q_target$).norm().item():.4f})")
# Sweep the seed to see all three outcomes. The counts below are the finding:
# a far seed splits into same-configuration, different-configuration, and stall.
same = other = stalled = 0
for s in range(60):
torch.manual_seed(s)
$q_s$ = $q_target$ + 1.2 * torch.randn(6, dtype=dtype)
$q_s_ik$, it_s, conv_s = $numerical_ik(T_target$, $q_s$, max_iter=300)
if not conv_s:
stalled += 1
elif $wrap_dist(q_s_ik$ - $q_target$) < 1e-3:
same += 1
else:
other += 1
print(f"[break-it #3] 60 far seeds: same-config={same} other-config={other} "
f"stalled={stalled} (predicted: all converge to a different branch)")
# The stalled seeds all land on the same configuration. Is that a local minimum
# of the pose error, or a singularity? The residual alone cannot tell you; the
# discriminator is ||J^T e||, which is zero at BOTH. What separates them is
# sigma_min: a rank-deficient J means the residual is unreachable.
torch.manual_seed(0)
$q_stall$, _, conv_stall = $numerical_ik(T_target$, $q_target$ + 1.2*torch.randn(6, dtype=dtype),
max_iter=300)
$T_s$ = $fk_6(q_stall$)
$e_p_s$ = $T_target$[:3, 3] - $T_s$[:3, 3]
$R_s$ = $T_s$[:3, :3].T @ $T_target$[:3, :3]
$e_o_s$ = torch.$tensor([R_s[2$, 1] - $R_s[1$, 2], $R_s[0$, 2] - $R_s[2$, 0],
$R_s[1$, 0] - $R_s[0$, 1]], dtype=dtype) / 2
$e_s$ = torch.$cat([e_p_s$, $e_o_s$])
$J_s$ = $jacobian_6(q_stall$)
sv_s = torch.linalg.$svdvals(J_s$)
rank_s = int((sv_s > 1e-8 * sv_s.max()).sum())
print(f"[break-it #3] stalled config: ||e||={e_s.norm().item():.4f} "
f"||J^T e||={(J_s.T @ $e_s$).norm().item():.2e} "
f"sigma_min={sv_s.min().item():.2e} rank={rank_s}/6")
# --- Step 7: summary ------------------------------------------------
print("\n--- lab 1.1.2 summary ---")
print(f"H1: 2-link analytical error = {ik_2link_err:.2e} (predicted < 1e-10)")
print(f"H2: DLS IK converged = {converged}, iters = {iters}, "
f"$||q_seed-q_target||$ = {(q_seed - $q_target$).norm().item():.3f} (predicted converged, < 0.5)")
print(f"H3: DLS pos error at lambda=0.1 = {err_by_lam[0.1]:.2e}, "
f"at lambda=0.01 = {err_by_lam[0.01]:.2e} (predicted both < 1e-4, the smaller strictly smaller)")
print(f"H4: branch jump at (0.5, 0.5) = {jump if branches_b else 'N/A'}")
print(f"H5: near the singularity (q2 = 1e-3) the undamped step is "
f"{dq_undamped.norm().item():.1f} and the damped step is {dq_damped.norm().item():.3f} "
f"(predicted: undamped >> damped, i.e. a ratio > 100)")
§L.7 — The cheat sheet
| Term or result | Statement or definition | Role |
|---|---|---|
| Inverse kinematics | q* = f⁻¹($T_target$); solve for joint angles from pose | Introduced here; consumed by 1.1.3 (control), 2.3.2 (planning) |
| Workspace | The set of reachable poses; the annulus `[ | a₁−a₂ |
| Branch | One of the multiple solutions; up to 8 for 6-DOF spherical, up to 16 for general | Introduced here; consumed by the planner’s selection |
| Pieper’s condition | 3 consecutive axes intersect (spherical wrist) or parallel → closed form | Introduced here; consumed by 2.3.2 (arm selection) |
| 2-link closed form | cos q₂ = (r²−a₁²−a₂²)/(2a₁a₂), q₁ = atan2(y,x) − atan2(a₂ sin q₂, a₁+a₂ cos q₂) | Introduced here; consumed by 2.3.2 |
atan2(y, x) | The polar angle in (−π, π] with the correct quadrant | Introduced here; consumed by every IK |
| Newton–Raphson IK | $q_{k+1}$ = $q_k$ + J⁺ $e_k$, iterate until ‖e‖ < ε | Introduced here; consumed by 1.1.3 |
| Pose error | log(T(q)^{-1} $T_target$) or [$p_target$ − p; log(R(q)ᵀ $R_target$)] | Introduced here; consumed by 1.1.3 |
Pseudoinverse J⁺ | J⁻¹ (square), (JᵀJ)⁻¹Jᵀ (tall), Jᵀ(JJᵀ)⁻¹ (wide) | Introduced here; consumed by 1.1.3 |
| Singularity | $σ_min(J$) = 0 (rank drop); the pseudoinverse blows up | Introduced in 1.1.1 as rank(J) < n; restated here in singular-value form, which is what the DLS damping needs |
| Manipulability | w(q) = √det(JJᵀ) = ∏ $σ_i$; zero at singularity | Introduced here; consumed by 2.3.2 (path planning) |
| DLS | Jᵀ(JJᵀ + λ²I)⁻¹; bounds the inverse at 1/(2λ) | Introduced here; consumed by 1.1.3 (robust control) |
| Damping trade-off | λ small → fast/unstable; λ large → slow/stable; error O(λ²) | Introduced here; consumed by 1.1.3 |
| Null-space projection | I − J⁺J; secondary objectives for redundant arms | Introduced here; consumed by 2.3.2 (redundant planning) |
| Local minimum | The error stalls at a non-zero value; the iteration doesn’t converge | Introduced here; consumed by 2.3.2 (multi-restart). Caution: a stall is more often a singularity than a local minimum — check $σ_min$ before believing this label |
| Design principle P14.1 | IK is a relation, not a function; the branch is the planner’s decision | Introduced here; consumed by 2.3.2 |
| Design principle P14.2 | atan2, not arctan | Introduced here; consumed by every IK |
| Design principle P14.3 | The pseudoinverse is a step, not a solution; iterate | Introduced here; consumed by 1.1.3 |
| Design principle P14.4 | The DLS is the bounded pseudoinverse; λ’s choice is the trade-off | Introduced here; consumed by 1.1.3 |
| Design principle P14.5 | Validate the branch against the joint limits and the continuity | Introduced here; consumed by 2.3.2 |
| Design principle P14.6 | The method is chosen by the arm and the deadline | Introduced here; consumed by 2.3.2 (arm selection) |
| Design principle P14.7 | A closed form is only as correct as its domain | Introduced here; consumed by every closed-form IK |
| Design principle P14.8 | Read the shape of J before choosing the formula | Introduced here; consumed by 1.1.3 |
| Design principle P14.9 | Damping trades a bounded error for an unbounded one | Introduced here; consumed by 1.1.3 (robust control) |
§L.8 — Exercises with solutions
E 1.1.2.1 — The 2-link IK (numerical). a₁ = a₂ = 0.4, target (0.5, 0.2). Compute both branches.
S 1.1.2.1. r² = 0.25 + 0.04 = 0.29. cos q₂ = (0.29 − 0.16 − 0.16)/(2·0.4·0.4) = (-0.03)/0.32 = -0.094. q₂ = ± arccos(-0.094) ≈ ± 1.665 rad ≈ ± 95.4°. For q₂ = 1.665: a₂ sin q₂ ≈ 0.4·0.996 ≈ 0.398; a₁ + a₂ cos q₂ ≈ 0.4 − 0.0375 ≈ 0.362; atan2(0.398, 0.362) ≈ 0.833; atan2(0.2, 0.5) ≈ 0.381; q₁ ≈ -0.452. For q₂ = -1.665: q₁ ≈ 0.381 − (−0.833) = 1.214. Solutions: q ≈ (-0.452, 1.665) and (1.214, -1.665). The lesson: the closed form gives the two branches, and the choice is the planner’s.
E 1.1.2.2 — The workspace test (diagnostic). Target (1.0, 0) for a₁ = a₂ = 0.4. Is it reachable?
S 1.1.2.2. r = 1.0 > a₁ + a₂ = 0.8. Unreachable — the arm’s max reach is 0.8, and 1.0 is beyond. The arccos((1.0 − 0.16 − 0.16)/0.32) = arccos(2.125) is undefined (NaN). The controller must saturate to (0.8, 0). The lesson: the arccos’s domain [-1, 1] is the reachability check.
E 1.1.2.3 — The Jacobian step (numerical). 2-link (a₁ = a₂ = 0.5) at q = (π/4, π/2), e = (0.1, 0.2). Compute $J_v$ and $J_v$⁺e.
S 1.1.2.3. $J_v[0$, 0] = -0.5 sin(π/4) − 0.5 sin(3π/4) = -0.354 − 0.354 = -0.707. $J_v[0$, 1] = -0.5 sin(3π/4) = -0.354. $J_v[1$, 0] = 0.5 cos(π/4) + 0.5 cos(3π/4) = 0.354 − 0.354 = 0. $J_v[1$, 1] = 0.5 cos(3π/4) = -0.354. det = (-0.707)(-0.354) − (-0.354)(0) = 0.25. $J_v$⁻¹ = (1/0.25) [[-0.354, 0.354], [0, -0.707]] = [[-1.414, 1.414], [0, -2.828]]. $J_v$⁺ e = $J_v$⁻¹ · (0.1, 0.2) = (-0.141 + 0.283, 0 - 0.566) = (0.141, -0.566). The lesson: the step is finite at the well-conditioned configuration, and the direction is the descent direction of the error.
E 1.1.2.4 — The manipulability (numerical). 2-link (a₁ = a₂ = 0.5) at q₂ = π/3. Compute the manipulability.
S 1.1.2.4. w = a₁ a₂ |sin q₂| = 0.5·0.5·sin(π/3) = 0.25·0.866 = 0.2165. The lesson: the manipulability is proportional to |sin q₂|, and it is maximal at π/2 (w = 0.25) and zero at 0 and π (the boundaries).
E 1.1.2.5 — The DLS’s bound (numerical). At $σ_min$ = 0.01, λ = 0.1, what is the effective inverse σ_min/(σ_min² + λ²)?
S 1.1.2.5. 0.01/(0.0001 + 0.01) = 0.01/0.0101 ≈ 0.990. The effective inverse is 0.99 vs. the pseudoinverse’s 1/0.01 = 100 — a 100× reduction. The lesson: the DLS’s bound 1/(2λ) = 5 caps the amplification, and at $σ_min$ ≪ λ the effective inverse is ≈ σ_min/λ² ≈ 0.01/0.01 = 1, which is the bounded value.
E 1.1.2.6 — The branch jump (diagnostic). A learner’s IK returns q = (0.5, 0.3) for target A, and q = (2.1, −1.4) for a target B very close to A. What is the likely cause?
S 1.1.2.6. The IK jumped branches — the two targets are close in the task space (they are near a branch boundary), and the IK’s single-branch assumption (or the iteration’s basin) returned the + branch for A and the − branch for B. ‖$q_A$ − $q_B$‖ = √(1.6² + 1.7²) = 2.33, even though the task-space distance is small — the motion the IK commands is huge, and the arm jumps across the workspace. The fix is to seed the IK with the previous q (continuity), and to validate the returned q against the joint limits and the motion constraints. The lesson: the IK’s pose test passes for both, but the trajectory test fails — the branch choice is the planner’s.
E 1.1.2.7 — The analytical vs. numerical (diagnostic). A 6-DOF arm with a spherical wrist has a closed-form IK, but a learner uses the numerical IK and gets slower convergence and occasional local minima. What is the cause?
S 1.1.2.7. The spherical wrist satisfies Pieper’s condition, so the closed form exists and is exact and fast. The numerical IK ignores this structure — it treats the arm as general, uses the iterative method, and pays the cost (slower, local minima). The fix is to use the closed form (the analytical IK) for the spherical-wrist arm — the structure is the advantage, and the numerical method discards it. The lesson: the choice of method depends on the arm’s structure — the closed form for Pieper, the numerical for general.
E 1.1.2.8 — The null-space (constructive, transfer probe). A 7-DOF redundant arm has the primary task (end-effector pose) and a secondary task (joint-limit avoidance). Write the combined update.
S 1.1.2.8.
delta_q = J.T @ inv(J @ J.T + lambda^2 * I) @ e # primary
$q_secondary$ = -grad_g(q) # e.g., -2(q - $q_center$)
null_proj = I - pinv(J) @ J
delta_q_secondary = null_proj @ $q_secondary$
$q_next$ = q + delta_q + alpha * delta_q_secondary
The primary step is the DLS’s Jᵀ(JJᵀ + λ²I)⁻¹ e, and the secondary step is the null-space projection (I − J⁺J) q̇_secondary, which does not affect the end-effector’s pose. The α is the secondary gain. The lesson: the redundancy is used for the secondary objective without sacrificing the primary, and the null-space projection is the mechanism.
§L.9 — Exit test
You are ready for Lesson 1.1.3 when you can, without notes:
- State the IK problem and the three questions (existence, uniqueness, reachability).
- State the 2-link arm’s closed-form IK and both branches.
- State the workspace annulus and the
arccos’s domain. - State why
atan2is required andarctanis insufficient. - State the Newton–Raphson iteration and the convergence criterion.
- State the three failure modes (far seed, singularity, stationary residual) and the test that separates the last two.
- State the pseudoinverse’s behavior at singularities (
1/σ_minunbounded). - State the DLS update and the trade-off (speed vs. stability,
O(λ²)error). - State the manipulability
w = ∏ $σ_i$and the 2-link arm’sw = a₁a₂|sin q₂|. - State the null-space projection and its use for redundant arms.
- State Pieper’s condition and why the running case lacks the closed form.
- Diagnose the branch jump, the singularity divergence, the stationary residual, and the
arctanquadrant bug from their symptoms.
If any item is not answerable without notes, return to the section that introduced it: 1 to §L.1, 2 to §L.2, 3 to §L.2.1, 4 to WE-L.2.d, 5 to §L.3, 6 to §L.3.1, 7 to §L.4.1, 8 to §L.4, 9 to §L.4, 10 to §L.4, 11 to §L.1.1, 12 to §L.5.
End of Lesson 1.1.2. The learner has now completed the inverse kinematics foundation: the analytical closed form (for the 2-link), the numerical iteration (for the 6-DOF), the singularities and the DLS’s damping, and the redundancy’s null-space. The next lesson, 1.1.3 — PID Control and Trajectory Tracking, uses this IK foundation to close the loop — the controller’s job is to drive the actual pose to the commanded pose, using the Jacobian’s pseudo-inverse as the feedforward and the PID as the feedback.
§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 |
|---|---|---|---|
The 2-link closed-form IK is exact: error 1.24e-16 | A | R | H1 |
Damped least squares converges from a near seed: True in 56 iterations | A | R | H2, with the iteration count reported, not gated |
Lowering λ from 0.1 to 0.01 lowers the residual from 9.02e-7 to 5.15e-9 | A | R | H3, both residuals printed |
The analytical branch switch at (0.5, 0.5) is 3.512 rad | A | R | H4 |
Near the singularity (q₂ = 1e-3) the undamped step is 4472.1 against the damped 0.022, a ratio over 10⁵ — the damping bounds the step where the pseudoinverse does not | A | R | H5’s paired print |
| From 60 far seeds, 16 reach the same configuration, 16 a different one, and 28 stall | A | R | Break-it #3’s three-way print |
A stalled solve is a rank deficiency, not a local minimum: $σ_min$ = 4.89e-11, rank 5/6 | A | R | Break-it #3’s diagnostic line |
What this lesson does not reproduce. The claim that exactly at the singularity both the damped and undamped steps are zero — because the requested direction lies in the null space — is stated in H5 and is not printed by the lab, which evaluates near the singularity (q₂ = 1e-3). It is tier B, status S: a limiting argument the reader can check by setting q₂ = 0 and observing that the column collapses, but not a number the shipped run produces. The distinction matters, because “near” and “at” differ by a factor of 10⁵ in the step norm, which is the whole point of the hypothesis.
The 28/60 stall result is a finding about the solver, not about the arm. A stalled run has ‖Jᵀe‖ = 1.49e-11 and $σ_min$ = 4.89e-11, so the gradient is zero because the Jacobian has lost a rank direction — not because the pose is unreachable. A reader who diagnoses a stall as “bad seed” will retry the same seed forever; the diagnostic that separates the two is the singular value, which the lab prints.
No external sources are cited in this lesson. The closed form is derived from the law of cosines in §L.1; damped least squares is derived in §L.3 from the regularized normal equations. No paper is named, and no citation is implied by the names “Levenberg” or “Marquardt,” which this lesson does not use.