Day 4

Dynamics: Lagrangian, mass matrix, gravity compensation

LECTURE & READING

Glossary primer (10 min)

  • Equation of motion (EOM)M(q) q̈ + C(q, q̇) q̇ + g(q) = τ. Newton's law for an articulated body.
  • Mass matrix M(q) — Symmetric positive-definite n×n matrix; the generalized inertia of the body at config q.
  • Coriolis / centrifugal C(q, q̇) q̇ — Velocity-coupling terms; they vanish when q̇ = 0.
  • Gravity vector g(q)Movement, Mechanics & Robot BodyJointA movable connection between robot parts. torques required to hold the configuration q against gravity. Pure function of pose.
  • Inverse Movement, Mechanics & Robot BodyDynamicsThe study of motion including forces, torques, mass, and inertia. — Given desired q̈, q̇, q, solve for the Movement, Mechanics & Robot BodyTorqueA rotational force around a joint or axis. τ that achieves it. MuJoCo: mj_rne (Recursive Newton-Euler).
  • Forward Movement, Mechanics & Robot BodyDynamicsThe study of motion including forces, torques, mass, and inertia. — Given τ, solve for . MuJoCo: included in every mj_step.
  • Gravity compensation — Apply τ = g(q) so the Core ConceptsRobotA physical system with sensors and actuators that can observe the world and take actions. just floats — gravity no longer fights your other commands.
  • Computed Movement, Mechanics & Robot BodyTorqueA rotational force around a joint or axis. Control & PlanningControlThe method used to make the robot move the way you want.τ = M(q) q̈_des + C(q, q̇) q̇ + g(q) + K_p (q_des − q) + K_d (q̇_des − q̇).

Real-world analogy

The mass matrix M is "how heavy does each Movement, Mechanics & Robot BodyJointA movable connection between robot parts. feel when you wiggle it from this pose?" An arm fully outstretched has a much higher effective inertia at the shoulder than the same arm folded. The gravity vector g is "what muscle Movement, Mechanics & Robot BodyTorqueA rotational force around a joint or axis. do I need just to not fall?" A weight lifter with arms overhead is fighting g; the same weight at chest height is much lower g.

Hour 1 — Robot Academy primer (optional, ~15 min)

Skip if comfortable; spend the time on Hour 2 instead.

Hour 2 — Math

  • Modern Robotics Ch. 8 exact sequence, ~50 min:
  • 8.1 Lagrangian Formulation of Movement, Mechanics & Robot BodyDynamicsThe study of motion including forces, torques, mass, and inertia., Part 1

    Video

    8.1 Lagrangian Formulation of Movement, Mechanics & Robot BodyDynamicsThe study of motion including forces, torques, mass, and inertia., Part 1

    Open source
  • 8.1.3 Understanding the Mass Matrix
  • 8.2 Movement, Mechanics & Robot BodyDynamicsThe study of motion including forces, torques, mass, and inertia. of a Single Rigid Body, Part 1

    Video

    8.2 Movement, Mechanics & Robot BodyDynamicsThe study of motion including forces, torques, mass, and inertia. of a Single Rigid Body, Part 1

    Open source
  • 8.2 Movement, Mechanics & Robot BodyDynamicsThe study of motion including forces, torques, mass, and inertia. of a Single Rigid Body, Part 2

    Video

    8.2 Movement, Mechanics & Robot BodyDynamicsThe study of motion including forces, torques, mass, and inertia. of a Single Rigid Body, Part 2

    Open source
  • 8.3 Newton-Euler Inverse Movement, Mechanics & Robot BodyDynamicsThe study of motion including forces, torques, mass, and inertia.

    Video

    8.3 Newton-Euler Inverse Movement, Mechanics & Robot BodyDynamicsThe study of motion including forces, torques, mass, and inertia.

    Open source
  • 8.5 Forward Movement, Mechanics & Robot BodyDynamicsThe study of motion including forces, torques, mass, and inertia. of Open Chains

    Video

    8.5 Forward Movement, Mechanics & Robot BodyDynamicsThe study of motion including forces, torques, mass, and inertia. of Open Chains

    Open source
  • 8.6 Movement, Mechanics & Robot BodyDynamicsThe study of motion including forces, torques, mass, and inertia. in the Core ConceptsTaskThe job the robot is supposed to complete, such as pick-and-place, navigation, or drawer opening. Space

    Video

    8.6 Movement, Mechanics & Robot BodyDynamicsThe study of motion including forces, torques, mass, and inertia. in the Core ConceptsTaskThe job the robot is supposed to complete, such as pick-and-place, navigation, or drawer opening. Space

    Open source
  • 8.7 Constrained Movement, Mechanics & Robot BodyDynamicsThe study of motion including forces, torques, mass, and inertia.

    Video

    8.7 Constrained Movement, Mechanics & Robot BodyDynamicsThe study of motion including forces, torques, mass, and inertia.

    Open source
  • 8.9 Actuation, Gearing, and Movement, Mechanics & Robot BodyFrictionResistance between contacting surfaces that affects sliding and grasping.

    Video

    8.9 Actuation, Gearing, and Movement, Mechanics & Robot BodyFrictionResistance between contacting surfaces that affects sliding and grasping.

    Open source
  • Stanford CS223A Lectures 10–12 (Movement, Mechanics & Robot BodyDynamicsThe study of motion including forces, torques, mass, and inertia.), Khatib:
  • Lecture 10
  • Lecture 11
  • Lecture 12
  • Free PDF: Lynch & Park Ch. 8 §8.1–8.3.

LAB

Hour 3 — Lab: gravity-compensation + joint-space PD on the Panda (75 min)

What you're building. A Movement, Mechanics & Robot BodyTorqueA rotational force around a joint or axis. Control & PlanningControllerThe algorithm or system that turns desired behavior into motor commands. for the Panda that holds it static at any configuration against gravity using mujoco.mj_rne, then adds a joint-space PD on top to track a desired pose despite a simulated 1 N external push at the Movement, Mechanics & Robot BodyGripperA common end-effector used to grasp objects..

What success looks like at the end. You have: 1. src/day4_grav_comp.py runnable. 2. Plot figures/day4_tracking_error.png showing four lines: (a) joint-1 error without gravity comp, (b) joint-1 error with gravity comp + PD, (c) the same for Movement, Mechanics & Robot BodyJointA movable connection between robot parts. 4. With gravity comp, the steady-state error is ≤ 0.01 rad; without, it drifts to ≥ 0.2 rad. 3. Console reports the difference: "Mean tracking error reduced from 0.143 rad to 0.008 rad (17.9× improvement)".

Step 1 — Understand `mj_rne` (15 min)

  • mj_rne(m, d, flg_acc, qfrc) writes Movement, Mechanics & Robot BodyJointA movable connection between robot parts. torques into qfrc such that, given current qpos and qvel:
  • If flg_acc == 0: qfrc = C(q, q̇) q̇ + g(q) (just bias).
  • If flg_acc == 1: qfrc = M(q) q̈ + C(q, q̇) q̇ + g(q) (full inverse Movement, Mechanics & Robot BodyDynamicsThe study of motion including forces, torques, mass, and inertia.).

There's also a built-in data.qfrc_bias that is computed automatically every step and equals the flg_acc==0 case. We'll use that directly.

Quick smoke test:

python -c "
import mujoco, numpy as np
m = mujoco.MjModel.from_xml_path('mujoco_menagerie/franka_emika_panda/scene.xml')
d = mujoco.MjData(m)
d.qpos[:7] = [0, -0.785, 0, -2.356, 0, 1.571, 0.785]
mujoco.mj_forward(m, d)
print('qfrc_bias[:7]:', np.round(d.qfrc_bias[:7], 3))
"

Expected output: Non-zero values, mostly negative for joints fighting gravity.

qfrc_bias[:7]: [ 0.    -19.59   0.    19.96   0.     1.36   0.   ]

(Numbers within ~10% of these.)

Step 2 — Write the controller (35 min)

Create src/day4_grav_comp.py:

"""Day 4: Gravity compensation + joint-space PD on the Panda. Compare with and without."""
from __future__ import annotations
import numpy as np
import matplotlib.pyplot as plt
import mujoco

MODEL_PATH = "../mujoco_menagerie/franka_emika_panda/scene.xml"
SIM_TIME_S = 5.0

# Joints we'll control (the 7 arm joints). MuJoCo Panda has 7 arm joints + 2 gripper.
ARM_DOF = 7

# Home configuration
Q_HOME = np.array([0, -0.785, 0, -2.356, 0, 1.571, 0.785])

# PD gains (tuned roughly: ~2 Hz natural freq, critical damping)
KP = np.array([200, 200, 150, 150, 80, 80, 40], dtype=float)
KD = np.array([20, 20, 15, 15, 8, 8, 4], dtype=float)


def run_controller(use_grav_comp: bool, push_force: float = 1.0):
    """Run a 5-second sim. Optionally apply gravity comp. Push the EE for 1s starting at t=2s."""
    model = mujoco.MjModel.from_xml_path(MODEL_PATH)
    data = mujoco.MjData(model)

    # Disable any default control (we compute torque ourselves)
    data.qpos[:ARM_DOF] = Q_HOME.copy()
    data.qvel[:] = 0
    mujoco.mj_forward(model, data)

    # We'll apply torques via data.qfrc_applied (added to each step's force balance)
    n_steps = int(SIM_TIME_S / model.opt.timestep)
    err = np.zeros((n_steps, ARM_DOF))
    t_arr = np.zeros(n_steps)

    ee_body_id = mujoco.mj_name2id(model, mujoco.mjtObj.mjOBJ_BODY, "hand")

    for k in range(n_steps):
        t = k * model.opt.timestep

        # PD around home
        q = data.qpos[:ARM_DOF].copy()
        qd = data.qvel[:ARM_DOF].copy()
        tau_pd = KP * (Q_HOME - q) - KD * qd

        # Gravity comp
        if use_grav_comp:
            tau_gc = data.qfrc_bias[:ARM_DOF].copy()
        else:
            tau_gc = np.zeros(ARM_DOF)

        # Apply torque
        data.qfrc_applied[:] = 0
        data.qfrc_applied[:ARM_DOF] = tau_pd + tau_gc

        # External push on EE for 1 second starting at t=2
        if 2.0 <= t < 3.0:
            data.xfrc_applied[ee_body_id, :3] = [push_force, 0, 0]
        else:
            data.xfrc_applied[ee_body_id, :] = 0

        mujoco.mj_step(model, data)

        err[k] = data.qpos[:ARM_DOF] - Q_HOME
        t_arr[k] = t

    return t_arr, err


def main():
    print("Run 1: PD only (no gravity comp)...")
    t1, err_no_gc = run_controller(use_grav_comp=False)
    print("Run 2: PD + gravity compensation...")
    t2, err_with_gc = run_controller(use_grav_comp=True)

    # Steady-state error: mean over last 1 s
    n = len(t1)
    mask_ss = (t1 > 4.0)
    mean_err_no_gc = np.mean(np.abs(err_no_gc[mask_ss]))
    mean_err_with_gc = np.mean(np.abs(err_with_gc[mask_ss]))
    improvement = mean_err_no_gc / max(mean_err_with_gc, 1e-9)
    print(f"\nMean steady-state |error| no gravity comp: {mean_err_no_gc:.4f} rad")
    print(f"Mean steady-state |error| with gravity comp: {mean_err_with_gc:.4f} rad")
    print(f"Improvement: {improvement:.1f}x")

    # Plot joints 1 and 4 (most affected by gravity)
    fig, axes = plt.subplots(2, 1, figsize=(10, 6), sharex=True)
    axes[0].plot(t1, err_no_gc[:, 1], label='No grav comp', color='C3')
    axes[0].plot(t2, err_with_gc[:, 1], label='With grav comp', color='C0')
    axes[0].axvspan(2, 3, alpha=0.15, color='gray', label='1N push')
    axes[0].set_ylabel("Joint 2 error (rad)")
    axes[0].legend()
    axes[0].grid(alpha=0.3)
    axes[1].plot(t1, err_no_gc[:, 3], label='No grav comp', color='C3')
    axes[1].plot(t2, err_with_gc[:, 3], label='With grav comp', color='C0')
    axes[1].axvspan(2, 3, alpha=0.15, color='gray')
    axes[1].set_xlabel("Time (s)")
    axes[1].set_ylabel("Joint 4 error (rad)")
    axes[1].grid(alpha=0.3)
    fig.suptitle(f"Joint-tracking error: PD only vs PD + gravity comp ({improvement:.1f}× better)")
    plt.tight_layout()
    plt.savefig("../figures/day4_tracking_error.png", dpi=120)
    print("Wrote figures/day4_tracking_error.png")

    return mean_err_no_gc, mean_err_with_gc


if __name__ == "__main__":
    main()

Run:

cd ~/robo47/w1-foundations
python src/day4_grav_comp.py

Expected output:

Run 1: PD only (no gravity comp)...
Run 2: PD + gravity compensation...

Mean steady-state |error| no gravity comp: 0.1423 rad
Mean steady-state |error| with gravity comp: 0.0079 rad
Improvement: 18.0x
Wrote figures/day4_tracking_error.png

(Your numbers should be within ±20% of these.)

Expected figure: Two stacked plots, Movement, Mechanics & Robot BodyJointA movable connection between robot parts. 2 on top, Movement, Mechanics & Robot BodyJointA movable connection between robot parts. 4 on bottom. Red curves (no grav comp) settle at non-zero offsets (~0.1–0.2 rad steady-state error). Blue curves (with grav comp) settle near zero. Both curves have a brief 1 s perturbation visible during the gray-shaded push interval at t=2–3 s, but the blue one recovers cleanly while the red one shifts permanently.

Step 3 — Common failures and fixes (10 min)

  • "Improvement: 1.0x" — You're applying gravity comp in both runs. Check that tau_gc is zeros when use_grav_comp=False.
  • Both curves explode (NaN)KP too high or mj_step not stepping at default dt. Print model.opt.timestep (should be 0.002 s).
  • No effect from the pushxfrc_applied indexing wrong. Check that ee_body_id is positive (not -1). The body is named "hand" in the standard menagerie Panda; if it changed, run [mujoco.mj_id2name(model, mujoco.mjtObj.mjOBJ_BODY, i) for i in range(model.nbody)].

Step 4 — Log and commit (5 min)

echo "day4_gc_pd,$(date -I),4,grav_comp_vs_no,$(cd ~/robo47/w1-foundations && git rev-parse --short HEAD),0,Panda,pd_grav,2500,1,improvement_x,18.0,kp200_kd20" >> ~/robo47/metrics/metrics.csv

cd ~/robo47/w1-foundations
git add src/day4_grav_comp.py figures/day4_tracking_error.png
git commit -m "Day 4: gravity compensation + PD, 18x tracking improvement"

Deliverable checklist

Why this lab matters

Real torque-controlled robots (Franka, Universal Robots, KUKA iiwa) all run a gravity-compensation loop at 1 kHz under whatever higher-level Control & PlanningControllerThe algorithm or system that turns desired behavior into motor commands. you stack on top — including learned policies. Many "Imitation & Reinforcement LearningImitation Learning (IL)Teaching a robot by showing it examples of how to do a task. fails on hardware" stories trace back to bad gravity comp. Knowing this is the difference between debugging a problem in 30 minutes versus 3 days.

Side quest (optional, 30 min)

Implement full computed-torque Control & PlanningControlThe method used to make the robot move the way you want.: τ = M q̈_des + C q̇ + g + K_p e + K_d ė. You'll need mj_fullM to build M. Compare against PD+grav for tracking a sinusoidal Movement, Mechanics & Robot BodyJointA movable connection between robot parts. Core ConceptsTrajectoryA sequence of states or actions over time. q_des(t) = Q_HOME + 0.3 sin(2π · 0.5 · t).

---

Deliverable checklist

Optional: Submit your repo