Kiru Lab / Embodied Control: Kinematics and the Inverse Jacobian / The Jacobian
The Velocity Jacobian
The same object you met in track two, now with physical units: how fast the hand moves per unit of joint rate.
Code Walkthrough · about 40 minutes
The Jacobian of the forward kinematics map answers: if I turn joint j at one radian per second, how does the end effector move? Each column is the end-effector velocity contributed by one joint. Each row is the sensitivity of one task-space direction to all joints.
import numpy as np
def jacobian_analytic(q, lengths):
"""d(x, y) / d(q) for a planar revolute chain. Shape (2, n)."""
angles = np.cumsum(q)
n = len(q)
J = np.zeros((2, n))
for j in range(n):
# Joint j rotates every link from j onward.
J[0, j] = -np.sum(lengths[j:] * np.sin(angles[j:]))
J[1, j] = np.sum(lengths[j:] * np.cos(angles[j:]))
return J
def jacobian_numeric(q, lengths, eps=1e-6):
"""Finite differences — slower, and the way you check the analytic version."""
J = np.zeros((2, len(q)))
for j in range(len(q)):
step = np.zeros_like(q); step[j] = eps
J[:, j] = (forward_kinematics(q + step, lengths)
- forward_kinematics(q - step, lengths)) / (2 * eps)
return JThis is literally the numerical-gradient helper from track two with joint angles as inputs and hand position as output. Autograd computes the Jacobian of a network the same way it is computed here. Nothing new has been introduced — only a different meaning attached to the numbers.
Reading the columns
Print the Jacobian at a configuration and read the columns as directions. A column near zero means that joint currently does almost nothing for the end effector. Two nearly parallel columns mean two joints are doing nearly the same thing — redundancy in that pose, and a conditioning problem for the solver.
Hold on to
- The Jacobian maps joint velocity to end-effector velocity
- Columns are per-joint contributions; rank is how many directions are achievable
- It is the same Jacobian autograd computes for a network
Work through
Try each one before opening the solution. Getting it wrong first is most of where the learning happens.
-
Verify `jacobian_analytic` against `jacobian_numeric` to 1e-6 at ten random configurations.
Hint
Compare at random configurations, not just at zero — many bugs vanish at the origin.
Solution
They agree to about 1e-9 with eps=1e-6. Testing at the zero configuration alone is the classic mistake: many sign and indexing errors happen to cancel when all angles are zero, so a broken Jacobian passes. Always test at random poses.
import numpy as np rng = np.random.default_rng(0) lengths = np.array([1.0, 0.8, 0.6]) worst = 0.0 for _ in range(10): q = rng.uniform(-np.pi, np.pi, size=3) worst = max(worst, np.abs(jacobian_analytic(q, lengths) - jacobian_numeric(q, lengths)).max()) print(worst) # < 1e-6Check your work
Paste this after your own code. If it runs without raising, you have it.
import numpy as np assert worst < 1e-6 print("ok") -
Find a configuration where one column is near zero and explain physically what that joint is doing.
Hint
A column is near zero when moving that joint barely moves the end effector.
Solution
The last joint's column has magnitude equal to the last link length, so it is never zero for a nonzero link — but a column shrinks toward zero as the links beyond that joint fold back on themselves and their contributions cancel. Physically: turning that joint spins the remaining links about a configuration where their motions oppose, so the hand barely moves. That joint has, temporarily, almost no authority over the end effector.
import numpy as np lengths = np.array([1.0, 1.0]) q = np.array([0.0, np.pi]) # second link folded back on the first J = jacobian_analytic(q, lengths) print(np.linalg.norm(J, axis=0)) # first column is small: the links cancel
Sign in to track your progress through the lab.