Day 3
Inverse kinematics, Jacobians, singularities
LECTURE & READING
Glossary primer (12 min)
- Movement, Mechanics & Robot BodyInverse kinematics (IK)Calculating the joint values needed to reach a desired pose. — Given a desired EE pose
T_des, find Movement, Mechanics & Robot BodyJointA movable connection between robot parts. anglesqsuch thatFK(q) = T_des. - Jacobian J(q) — A 6×n matrix linking Movement, Mechanics & Robot BodyJointA movable connection between robot parts. velocities to EE velocities:
[ω_ee; v_ee] = J(q) · q̇. - Geometric / spatial Jacobian — Two flavors of Jacobian, expressed in different frames. Spatial frame for PoE in space form; body frame for PoE in body form.
- Singularity — A configuration where
det(J · J.T) = 0; the Core ConceptsRobotA physical system with sensors and actuators that can observe the world and take actions. can't move in some EE direction. - Damped Least Squares (DLS) Movement, Mechanics & Robot BodyInverse kinematics (IK)Calculating the joint values needed to reach a desired pose. — Iterative Movement, Mechanics & Robot BodyInverse kinematics (IK)Calculating the joint values needed to reach a desired pose. that survives singularities:
q̇ = J.T · (J J.T + λ² I)⁻¹ · ẋ_des. - Newton-Raphson Movement, Mechanics & Robot BodyInverse kinematics (IK)Calculating the joint values needed to reach a desired pose. — Iterative Movement, Mechanics & Robot BodyInverse kinematics (IK)Calculating the joint values needed to reach a desired pose. without damping:
q̇ = J⁺ · ẋ_des(pseudoinverse). Fast when far from singularities, explodes near them. - Null-space — Directions in joint-space that don't move the EE. Redundant arms (n > 6) have a non-trivial null-space; you can use it to satisfy secondary objectives like "stay away from Movement, Mechanics & Robot BodyJointA movable connection between robot parts. limits".
- Manipulability ellipsoid — Geometric visualization of how easily the EE can move in different directions; axes are the singular values of
J.
Real-world analogy
The Jacobian is the gear ratio between Movement, Mechanics & Robot BodyJointA movable connection between robot parts. motion and EE motion. A high gear ratio means small Movement, Mechanics & Robot BodyJointA movable connection between robot parts. moves cause large EE moves (the arm is dexterous in that direction). A singular Jacobian is a car with a locked differential — you can press the accelerator (Movement, Mechanics & Robot BodyJointA movable connection between robot parts. Movement, Mechanics & Robot BodyTorqueA rotational force around a joint or axis.) but the wheels (EE) won't move in the desired direction.
Hour 1 — Robot Academy primer
- Movement, Mechanics & Robot BodyVelocityHow fast something moves. Movement, Mechanics & Robot BodyKinematicsThe study of motion without considering forces. in 3D masterclass, focusing on the manipulability-ellipsoid lessons, ~25 min: https://robotacademy.net.au/masterclass/velocity-kinematics-in-3d/
- Movement, Mechanics & Robot BodyInverse kinematics (IK)Calculating the joint values needed to reach a desired pose. and Core ConceptsRobotA physical system with sensors and actuators that can observe the world and take actions. Motion overview lessons, ~10 min: https://robotacademy.net.au/masterclass/inverse-kinematics-and-robot-motion/
The manipulability-ellipsoid animation is one of the things Corke is genuinely best in the world at explaining.
Hour 2 — Math
- Modern Robotics Ch. 5 exact sequence, ~25 min:
- Movement, Mechanics & Robot BodyVelocityHow fast something moves. Movement, Mechanics & Robot BodyKinematicsThe study of motion without considering forces. and Statics
Video
Movement, Mechanics & Robot BodyVelocityHow fast something moves. Movement, Mechanics & Robot BodyKinematicsThe study of motion without considering forces. and Statics
Open source - 5.1.1 Space Jacobian
- 5.1.2 Body Jacobian
- 5.2 Statics of Open Chains
- 5.3 Singularities
- 5.4 Manipulability
- Lynch & Park Ch. 6 (Movement, Mechanics & Robot BodyInverse kinematics (IK)Calculating the joint values needed to reach a desired pose., numerical Newton-Raphson)
- Stanford CS223A Lec 6 "Instantaneous Movement, Mechanics & Robot BodyKinematicsThe study of motion without considering forces."
Video
Stanford CS223A Lec 6 "Instantaneous Movement, Mechanics & Robot BodyKinematicsThe study of motion without considering forces.
Open source
LAB
Hour 3 — Lab: keyboard-teleop a Panda with DLS IK (75 min)
What you're building. A Python program that runs a MuJoCo Simulation & Sim-to-RealSimulationA virtual environment where robots can be trained or tested. of the Franka Panda at 60 Hz, accepts keyboard input (W/A/S/D for X/Y, Q/E for Z, arrow keys for orientation), and uses Damped Least Squares Movement, Mechanics & Robot BodyInverse kinematics (IK)Calculating the joint values needed to reach a desired pose. to drive the EE toward your commanded pose. You will deliberately steer it toward a singularity, observe the Jacobian's condition number spike, and plot it.
What success looks like at the end. You have:
1. A 30-second screen recording videos/day3_teleop.mp4 showing you piloting the Panda's Movement, Mechanics & Robot BodyGripperA common end-effector used to grasp objects. smoothly through space using keyboard.
2. A plot figures/day3_jacobian_condition.png showing condition number cond(J) over time, with one or two clear spikes (>500) marking moments you got close to a singularity.
3. src/day3_dls_ik.py containing the runnable program.
Step 1 — Install keyboard handling and verify viewer works (10 min)
cd ~/robo47
source .venv/bin/activate
uv pip install pynputQuick sanity test that the viewer runs in passive mode (you'll embed it in a Python loop instead of using the standalone viewer):
python -c "
import mujoco, mujoco.viewer, time
m = mujoco.MjModel.from_xml_path('mujoco_menagerie/franka_emika_panda/scene.xml')
d = mujoco.MjData(m)
with mujoco.viewer.launch_passive(m, d) as v:
for _ in range(180): # 3 seconds at 60 fps
mujoco.mj_step(m, d)
v.sync()
time.sleep(1/60)
print('passive viewer ok')
"Expected: Window opens for ~3 s, Panda visible. If you're on Nebius headless, do this lab on your laptop instead — keyboard Imitation & Reinforcement LearningTeleoperation (teleop)A human remotely controlling the robot, often to collect demonstrations. needs a display.
Step 2 — Write the teleop loop (40 min)
Create w1-foundations/src/day3_dls_ik.py:
"""Day 3: Keyboard-teleop the Franka Panda using Damped Least Squares IK.
Press WASD/QE for translation; arrow keys for orientation; ESC to quit.
"""
from __future__ import annotations
import time
import numpy as np
import mujoco
import mujoco.viewer
import matplotlib.pyplot as plt
from pynput import keyboard
# --- Config ------------------------------------------------------------------
DT = 1.0 / 60.0
LIN_SPEED = 0.15 # m/s
ANG_SPEED = 0.5 # rad/s
DAMPING = 0.05 # DLS lambda
TIMEOUT_SEC = 60.0
MODEL_PATH = "../mujoco_menagerie/franka_emika_panda/scene.xml"
# --- Keyboard state (shared across threads) ---------------------------------
keys_held: set = set()
def on_press(key):
try:
keys_held.add(key.char)
except AttributeError:
keys_held.add(key)
def on_release(key):
try:
keys_held.discard(key.char)
except AttributeError:
keys_held.discard(key)
if key == keyboard.Key.esc:
return False # stop listener
# --- DLS IK ------------------------------------------------------------------
def dls_step(jacp: np.ndarray, jacr: np.ndarray,
dx: np.ndarray, dr: np.ndarray, lam: float = DAMPING) -> np.ndarray:
"""One step of damped least-squares IK. Returns delta-q for the 7 arm joints."""
J = np.vstack([jacp, jacr]) # 6 x n_dof
e = np.concatenate([dx, dr]) # 6
JJt = J @ J.T
dq = J.T @ np.linalg.solve(JJt + lam**2 * np.eye(6), e)
return dq
# --- Main --------------------------------------------------------------------
def main():
model = mujoco.MjModel.from_xml_path(MODEL_PATH)
data = mujoco.MjData(model)
# Home pose for the arm
data.qpos[:7] = [0, -0.785, 0, -2.356, 0, 1.571, 0.785]
mujoco.mj_forward(model, data)
ee_site_id = mujoco.mj_name2id(model, mujoco.mjtObj.mjOBJ_SITE, "attachment_site")
listener = keyboard.Listener(on_press=on_press, on_release=on_release)
listener.start()
print("Teleop ready. WASD = X/Y, QE = Z, arrows = orientation, ESC = quit.")
cond_history: list = []
t_history: list = []
t_start = time.time()
with mujoco.viewer.launch_passive(model, data) as viewer:
while viewer.is_running() and (time.time() - t_start) < TIMEOUT_SEC:
t = time.time() - t_start
# --- Build commanded EE velocity from keyboard
v = np.zeros(3)
w = np.zeros(3)
if 'w' in keys_held: v[0] += LIN_SPEED
if 's' in keys_held: v[0] -= LIN_SPEED
if 'a' in keys_held: v[1] += LIN_SPEED
if 'd' in keys_held: v[1] -= LIN_SPEED
if 'q' in keys_held: v[2] += LIN_SPEED
if 'e' in keys_held: v[2] -= LIN_SPEED
if keyboard.Key.up in keys_held: w[0] += ANG_SPEED
if keyboard.Key.down in keys_held: w[0] -= ANG_SPEED
if keyboard.Key.left in keys_held: w[1] += ANG_SPEED
if keyboard.Key.right in keys_held: w[1] -= ANG_SPEED
# Desired EE motion this step
dx = v * DT
dr = w * DT
# --- Jacobian at current state
jacp = np.zeros((3, model.nv))
jacr = np.zeros((3, model.nv))
mujoco.mj_jacSite(model, data, jacp, jacr, ee_site_id)
# Restrict to the 7 arm joints (slice columns)
J_arm = np.vstack([jacp[:, :7], jacr[:, :7]])
# --- DLS step
dq = dls_step(jacp[:, :7], jacr[:, :7], dx, dr)
data.qpos[:7] += dq
# --- Step physics
mujoco.mj_forward(model, data)
viewer.sync()
# --- Log condition number
cond = np.linalg.cond(J_arm)
cond_history.append(cond)
t_history.append(t)
time.sleep(max(0, DT - (time.time() - t_start - t)))
listener.stop()
# --- Save the condition-number plot
cond = np.array(cond_history)
t = np.array(t_history)
fig, ax = plt.subplots(figsize=(10, 4))
ax.plot(t, cond)
ax.axhline(500, color='r', linestyle='--', label='singularity threshold')
ax.set_xlabel("Time (s)")
ax.set_ylabel("cond(J_arm)")
ax.set_title("Jacobian condition number during teleop")
ax.set_yscale('log')
ax.legend()
ax.grid(True, alpha=0.3)
plt.tight_layout()
plt.savefig("../figures/day3_jacobian_condition.png", dpi=120)
print(f"\nWrote figures/day3_jacobian_condition.png")
print(f"Max condition number reached: {cond.max():.1f}")
print(f"Time spent above threshold (cond > 500): {((cond > 500).sum() * DT):.1f} s")
if __name__ == "__main__":
main()Run it:
cd ~/robo47/w1-foundations
python src/day3_dls_ik.py- What you should experience:
- An interactive Panda window opens.
- Press W: arm advances in +X. Press S: retreats in −X. A/D: left/right. Q/E: up/down.
- Move the arm in a smooth circle in front of the Core ConceptsRobotA physical system with sensors and actuators that can observe the world and take actions. for ~10 seconds. The motion should feel responsive.
- Now deliberately push the arm fully extended forward (hold W). At some point the arm will reach the edge of its Manipulation & TasksWorkspaceThe region of space the robot can reach. and the motion will become jittery — that's a singularity.
- Press ESC to exit.
What success looks like in console output:
Teleop ready. WASD = X/Y, QE = Z, arrows = orientation, ESC = quit.
Wrote figures/day3_jacobian_condition.png
Max condition number reached: 1247.3
Time spent above threshold (cond > 500): 2.4 sExpected figure: A line plot of cond(J) over ~30 s on a log y-axis. Most of the trace sits between 10 and 100 (well-conditioned). One or two visible spikes climb above the red cond=500 line — those are your singularity moments. If the plot has no spikes, you didn't push the arm to a singular pose; do another run and deliberately fully extend it.
Step 3 — Record a screen video (10 min)
Use any screen recorder (macOS Cmd-Shift-5; Linux simplescreenrecorder; Windows Game Bar). Capture a 30-second clip showing fluid Imitation & Reinforcement LearningTeleoperation (teleop)A human remotely controlling the robot, often to collect demonstrations. including one deliberate singularity push. Save to videos/day3_teleop.mp4. Trim to ≤30 s.
Step 4 — Common failures and fixes (10 min)
- `pynput` doesn't capture keys — On macOS, you need to grant terminal accessibility permissions in System Settings → Privacy → Accessibility.
- Arm wiggles wildly —
LIN_SPEEDis too high. Reduce to 0.05 and retry. - DLS doesn't help near singularity (still jittery) — Increase
DAMPINGto 0.1. With more damping, the EE responds more sluggishly but never explodes. - No condition-number spike at all — The Panda's home pose is well-conditioned. To force a singularity, hold W long enough that the arm fully extends, then press an orientation key. The fully-stretched arm has a wrist singularity.
- `mj_jacSite` segfault — You used the wrong site name. Print
[mujoco.mj_id2name(model, mujoco.mjtObj.mjOBJ_SITE, i) for i in range(model.nsite)]to list all sites.
Step 5 — Log and commit (5 min)
echo "day3_dls_teleop,$(date -I),3,dls_ik_teleop,$(cd ~/robo47/w1-foundations && git rev-parse --short HEAD),0,Panda,dls_ik,1800,1,max_cond_J,1247,manual_teleop_30s" >> ~/robo47/metrics/metrics.csv
cd ~/robo47/w1-foundations
git add src/day3_dls_ik.py figures/day3_jacobian_condition.png videos/day3_teleop.mp4
git commit -m "Day 3: DLS IK teleop, observed singularity cond ~1200"Deliverable checklist
Why this lab matters
Every modern Core ConceptsPolicyThe rule or model that maps observations or states to actions. (Modern Robot LearningDiffusion policyA robot policy that generates actions using diffusion-model techniques., ACT, π0, GR00T) eventually outputs an EE delta or Movement, Mechanics & Robot BodyVelocityHow fast something moves.. Internally or externally, something is solving Movement, Mechanics & Robot BodyInverse kinematics (IK)Calculating the joint values needed to reach a desired pose. to convert that to Movement, Mechanics & Robot BodyJointA movable connection between robot parts. commands. When a learned Core ConceptsPolicyThe rule or model that maps observations or states to actions. "fails" at a particular pose, 30% of the time it's because that pose is near a singularity and the Movement, Mechanics & Robot BodyInverse kinematics (IK)Calculating the joint values needed to reach a desired pose. is unstable. You now understand what that feels like in your fingers.
Side quest (optional, 30 min)
Add null-space damping: when cond(J) > 200, add a secondary objective q̇_null = -k · (q - q_home) projected onto the null-space (I - J⁺ J). The arm will gracefully bias toward its home pose when far from a Core ConceptsGoalThe desired outcome or target state for a robot task. — a trick used by every modern Manipulation & TasksManipulationUsing a robot arm or hand to move or interact with objects. Control & PlanningControllerThe algorithm or system that turns desired behavior into motor commands..
---