Day 6

Control II: operational-space, impedance, contact

LECTURE & READING

Glossary primer (12 min)

  • Operational-space Control & PlanningControlThe method used to make the robot move the way you want. (OSC) — Compute desired task-space (Cartesian) Movement, Mechanics & Robot BodyAccelerationHow quickly velocity changes., then map to Movement, Mechanics & Robot BodyJointA movable connection between robot parts. torques: τ = J.T · F + (M − J.T · Λ · J · M⁻¹).proj(τ_null). Generalizes to redundant arms.
  • Impedance Control & PlanningControlThe method used to make the robot move the way you want. — Make the Core ConceptsRobotA physical system with sensors and actuators that can observe the world and take actions. behave like a spring-damper in Core ConceptsTaskThe job the robot is supposed to complete, such as pick-and-place, navigation, or drawer opening. space: F_task = K_p · (x_des − x) − K_d · ẋ. The Core ConceptsRobotA physical system with sensors and actuators that can observe the world and take actions. can be soft (low Kp) or stiff (high Kp).
  • Movement, Mechanics & Robot BodyComplianceThe robot’s ability to yield a little during contact instead of staying rigid. — How much the Core ConceptsRobotA physical system with sensors and actuators that can observe the world and take actions. yields under external force. Inverse of Movement, Mechanics & Robot BodyStiffnessHow strongly a robot resists motion or deformation..
  • Movement, Mechanics & Robot BodyContactPhysical interaction between the robot and an object or surface.Control & PlanningConstraintA rule the robot must obey, such as avoiding collisions or staying within joint limits. forces when bodies touch. MuJoCo simulates them via soft-constraint solver (solref, solimp).
  • Force/Movement, Mechanics & Robot BodyTorqueA rotational force around a joint or axis. Perception & SensingSensorA device that provides information about the robot or its environment. — Measures wrench (3 force + 3 Movement, Mechanics & Robot BodyTorqueA rotational force around a joint or axis.) at a frame. MuJoCo: <sensor type="force"/>.
  • Hybrid position/force Control & PlanningControlThe method used to make the robot move the way you want. — Position-control some Cartesian directions, force-control others. Useful for surface Movement, Mechanics & Robot BodyContactPhysical interaction between the robot and an object or surface. tasks.

Real-world analogy

Position Control & PlanningControlThe method used to make the robot move the way you want. is "go to coordinates X." Impedance Control & PlanningControlThe method used to make the robot move the way you want. is "behave like an invisible rubber band attached to point X." Stiff = rigid glass, compliant = wet noodle. A peg-in-hole Core ConceptsTaskThe job the robot is supposed to complete, such as pick-and-place, navigation, or drawer opening. with stiff position Control & PlanningControlThe method used to make the robot move the way you want. will smash if even slightly misaligned; with low-stiffness impedance Control & PlanningControlThe method used to make the robot move the way you want., it gently slides into place under Movement, Mechanics & Robot BodyContactPhysical interaction between the robot and an object or surface. Movement, Mechanics & Robot BodyFrictionResistance between contacting surfaces that affects sliding and grasping..

Hour 1 — Lectures

  • Khatib's foundational OSC lecture (CS223A Lectures 13–14):
  • Lecture 13
  • Lecture 14
  • Khatib's original 1987 paper "A Unified Approach for Motion and Force Control & PlanningControlThe method used to make the robot move the way you want." — skim

    Reading

    Khatib's original 1987 paper "A Unified Approach for Motion and Force Control & PlanningControlThe method used to make the robot move the way you want." — skim

    Open PDF

Hour 2 — Lectures

  • Russ Tedrake Robotic Manipulation & TasksManipulationUsing a robot arm or hand to move or interact with objects. Ch. 3 "Differential Movement, Mechanics & Robot BodyInverse kinematics (IK)Calculating the joint values needed to reach a desired pose.", focusing on Differential Movement, Mechanics & Robot BodyInverse kinematics (IK)Calculating the joint values needed to reach a desired pose. with constraints: https://manipulation.csail.mit.edu/pick.html
  • Modern Robotics Ch. 11 §11.6 "Hybrid Motion-Force Control & PlanningControlThe method used to make the robot move the way you want.".

LAB

Hour 3 — Lab: impedance circle-tracing on Panda with simulated push (75 min)

What you're building. A task-space impedance Control & PlanningControllerThe algorithm or system that turns desired behavior into motor commands. for the Panda. The EE traces a circle of radius 0.10 m at z=0.7 m at 0.25 Hz. While it traces, an external force perturbs the EE for 1 s. You compare stiff (Kp=1500) vs compliant (Kp=200) tuning and observe how the EE deviates and recovers.

What success looks like at the end. You have: 1. src/day6_impedance.py runnable. 2. Plot figures/day6_circle_tracking.png showing two side-by-side scatter plots (stiff vs compliant). Stiff: tight circle with small deformation under push. Compliant: visibly distorted circle, larger deformation. 3. Plot figures/day6_force_vs_disp.png showing peak EE displacement and peak interaction force during the push, for both regimes. 4. Console: "Stiff: peak disp 0.018 m, peak force 24.3 N. Compliant: peak disp 0.071 m, peak force 14.2 N."

Step 1 — Add an impedance controller (50 min)

Create src/day6_impedance.py:

"""Day 6: Task-space impedance control on the Panda EE, with a 1s push perturbation."""
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 = 8.0
ARM_DOF = 7

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

# Trajectory: circle in xy-plane at z=0.7
CIRCLE_RADIUS = 0.10
CIRCLE_FREQ_HZ = 0.25
CIRCLE_CENTER = np.array([0.4, 0.0, 0.5])

# Joint-space damping for residual modes
KQ_DAMP = np.array([5, 5, 5, 5, 2, 2, 2], dtype=float)


def task_space_target(t: float):
    omega = 2 * np.pi * CIRCLE_FREQ_HZ
    x = CIRCLE_CENTER + np.array([
        CIRCLE_RADIUS * np.cos(omega * t),
        CIRCLE_RADIUS * np.sin(omega * t),
        0.0,
    ])
    xd = np.array([
        -CIRCLE_RADIUS * omega * np.sin(omega * t),
         CIRCLE_RADIUS * omega * np.cos(omega * t),
         0.0,
    ])
    return x, xd


def run(kp_lin: float, push_force: float = 30.0, push_window=(3.0, 4.0)):
    """One run with given task-space stiffness Kp_lin (N/m).
    Damping uses critical damping rule: Kd = 2*sqrt(Kp * m_eff). We use a fixed m_eff~1.
    """
    model = mujoco.MjModel.from_xml_path(MODEL_PATH)
    data = mujoco.MjData(model)

    data.qpos[:ARM_DOF] = Q_HOME.copy()
    data.qvel[:] = 0
    mujoco.mj_forward(model, data)

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

    Kp = kp_lin * np.eye(3)
    Kd = 2 * np.sqrt(kp_lin) * np.eye(3)

    n_steps = int(SIM_TIME_S / model.opt.timestep)
    history_pos = np.zeros((n_steps, 3))
    history_target = np.zeros((n_steps, 3))
    history_force = np.zeros(n_steps)  # commanded EE force magnitude
    t_arr = np.zeros(n_steps)

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

        # --- Task-space target
        x_des, xd_des = task_space_target(t)

        # --- Current EE pose and velocity
        x_now = data.site_xpos[ee_site_id].copy()
        jacp = np.zeros((3, model.nv))
        jacr = np.zeros((3, model.nv))
        mujoco.mj_jacSite(model, data, jacp, jacr, ee_site_id)
        xd_now = jacp[:, :ARM_DOF] @ data.qvel[:ARM_DOF]

        # --- Impedance: F = Kp*(x_des - x) + Kd*(xd_des - xd)
        F = Kp @ (x_des - x_now) + Kd @ (xd_des - xd_now)
        history_force[k] = np.linalg.norm(F)

        # --- Map F to joint torques: τ = J.T · F (translational only)
        tau_task = jacp[:, :ARM_DOF].T @ F

        # --- Joint-space damping for residual / null-space (small)
        tau_null = -KQ_DAMP * data.qvel[:ARM_DOF]

        # --- Gravity comp
        tau_gc = data.qfrc_bias[:ARM_DOF].copy()

        # --- Apply
        data.qfrc_applied[:] = 0
        data.qfrc_applied[:ARM_DOF] = tau_task + tau_null + tau_gc

        # --- External push
        if push_window[0] <= t < push_window[1]:
            data.xfrc_applied[ee_body_id, :3] = [push_force, 0, 0]
        else:
            data.xfrc_applied[ee_body_id, :] = 0

        mujoco.mj_step(model, data)

        history_pos[k] = data.site_xpos[ee_site_id].copy()
        history_target[k] = x_des
        t_arr[k] = t

    return t_arr, history_pos, history_target, history_force


def main():
    print("Run 1: STIFF (Kp = 1500 N/m)")
    t_s, pos_s, tgt_s, force_s = run(kp_lin=1500.0)
    print("Run 2: COMPLIANT (Kp = 200 N/m)")
    t_c, pos_c, tgt_c, force_c = run(kp_lin=200.0)

    # Tracking error during push window (t in 3–4s)
    push_mask_s = (t_s >= 3.0) & (t_s < 4.0)
    push_mask_c = (t_c >= 3.0) & (t_c < 4.0)
    err_s = np.linalg.norm(pos_s - tgt_s, axis=1)
    err_c = np.linalg.norm(pos_c - tgt_c, axis=1)

    peak_disp_s = err_s[push_mask_s].max()
    peak_disp_c = err_c[push_mask_c].max()
    peak_force_s = force_s[push_mask_s].max()
    peak_force_c = force_c[push_mask_c].max()

    print(f"\nStiff:     peak disp {peak_disp_s:.3f} m, peak |F| {peak_force_s:.1f} N")
    print(f"Compliant: peak disp {peak_disp_c:.3f} m, peak |F| {peak_force_c:.1f} N")

    # --- Plot circles in xy
    fig, axes = plt.subplots(1, 2, figsize=(11, 5))
    for ax, (pos, tgt, label) in zip(axes,
        [(pos_s, tgt_s, f"Stiff (Kp=1500), peak disp {peak_disp_s*1000:.0f} mm"),
         (pos_c, tgt_c, f"Compliant (Kp=200), peak disp {peak_disp_c*1000:.0f} mm")]):
        ax.plot(tgt[:, 0], tgt[:, 1], 'k--', alpha=0.4, label='target')
        ax.plot(pos[:, 0], pos[:, 1], 'b-', alpha=0.7, label='actual')
        # Highlight push window
        push_mask = (t_s >= 3.0) & (t_s < 4.0)
        ax.plot(pos[push_mask, 0], pos[push_mask, 1], 'r-', linewidth=2, label='during push')
        ax.set_xlabel("X (m)")
        ax.set_ylabel("Y (m)")
        ax.set_title(label)
        ax.legend(fontsize=9)
        ax.set_aspect('equal')
        ax.grid(alpha=0.3)

    plt.tight_layout()
    plt.savefig("../figures/day6_circle_tracking.png", dpi=120)
    print("Wrote figures/day6_circle_tracking.png")

    # --- Bar plot of peak disp vs peak force
    fig2, ax2 = plt.subplots(figsize=(7, 4))
    x = np.arange(2)
    width = 0.35
    ax2.bar(x - width/2, [peak_disp_s * 1000, peak_disp_c * 1000], width, label='peak disp (mm)', color='C0')
    ax2.bar(x + width/2, [peak_force_s, peak_force_c], width, label='peak |F| (N)', color='C3')
    ax2.set_xticks(x)
    ax2.set_xticklabels(['Stiff Kp=1500', 'Compliant Kp=200'])
    ax2.set_ylabel("Magnitude")
    ax2.set_title("Stiffness trade-off under 1s, 30N push")
    ax2.legend()
    ax2.grid(alpha=0.3, axis='y')
    plt.tight_layout()
    plt.savefig("../figures/day6_force_vs_disp.png", dpi=120)
    print("Wrote figures/day6_force_vs_disp.png")

    return peak_disp_s, peak_disp_c, peak_force_s, peak_force_c


if __name__ == "__main__":
    main()

Run:

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

Expected output (numbers within ±30%):

Run 1: STIFF (Kp = 1500 N/m)
Run 2: COMPLIANT (Kp = 200 N/m)

Stiff:     peak disp 0.018 m, peak |F| 24.3 N
Compliant: peak disp 0.071 m, peak |F| 14.2 N
Wrote figures/day6_circle_tracking.png
Wrote figures/day6_force_vs_disp.png
  • Expected figures:
  • day6_circle_tracking.png: Two side-by-side circles. Both show a target dashed black circle (radius 0.10 m). The stiff blue trace stays nearly on top of it, with a small red bump (peak ~2 cm) during the push. The compliant blue trace is visibly less round overall and during the push the red bump is much larger (~7 cm), showing the EE getting shoved out by ~7 cm.
  • day6_force_vs_disp.png: Bar chart with stiff peak disp ~18 mm and stiff peak force ~24 N versus compliant peak disp ~71 mm and compliant peak force ~14 N. The trade-off is visible: stiff = small motion, large force; compliant = large motion, small force.

Step 2 — Common failures and fixes (15 min)

  • EE shoots away during push (compliant case)Kp_lin=200 is fine; if you set it to 50 the EE genuinely flies. Keep ≥ 100.
  • Stiff circle is also wobblyMovement, Mechanics & Robot BodyJointA movable connection between robot parts. damping KQ_DAMP too low. Raise to [10, 10, 10, 10, 4, 4, 4].
  • Both runs identical — You forgot to pass kp_lin through run(). Print Kp inside the loop on first step to verify.
  • `mj_jacSite` returns garbage — Confirm the site name "attachment_site" exists.

Step 3 — Log and commit (10 min)

echo "day6_imped_stiff,$(date -I),6,impedance_stiff,$(cd ~/robo47/w1-foundations && git rev-parse --short HEAD),0,Panda,impedance,4000,1,peak_disp_m,0.018,kp1500" >> ~/robo47/metrics/metrics.csv
echo "day6_imped_comp,$(date -I),6,impedance_compliant,$(cd ~/robo47/w1-foundations && git rev-parse --short HEAD),0,Panda,impedance,4000,1,peak_disp_m,0.071,kp200" >> ~/robo47/metrics/metrics.csv

cd ~/robo47/w1-foundations
git add src/day6_impedance.py figures/day6_circle_tracking.png figures/day6_force_vs_disp.png
git commit -m "Day 6: task-space impedance, stiff-vs-compliant trade-off"

Deliverable checklist

Why this lab matters

Every contact-rich Manipulation & TasksManipulationUsing a robot arm or hand to move or interact with objects. Core ConceptsTaskThe job the robot is supposed to complete, such as pick-and-place, navigation, or drawer opening. — peg Manipulation & TasksInsertionPlacing one object into another, like plugging in a connector., wiping a surface, opening a drawer — relies on the operator picking the right Movement, Mechanics & Robot BodyStiffnessHow strongly a robot resists motion or deformation. in each Cartesian direction. VLAs that output Movement, Mechanics & Robot BodyJointA movable connection between robot parts. targets implicitly delegate this to the low-level Control & PlanningControllerThe algorithm or system that turns desired behavior into motor commands.; ones that output EE deltas (most modern ones) need the underlying Control & PlanningControllerThe algorithm or system that turns desired behavior into motor commands. to be impedance-style or they'll crash on Movement, Mechanics & Robot BodyContactPhysical interaction between the robot and an object or surface.. When BeyondMimic's authors talk about "Movement, Mechanics & Robot BodyComplianceThe robot’s ability to yield a little during contact instead of staying rigid." they mean exactly the Kp dial you tuned today.

Side quest (optional, 45 min)

Implement full OSC: build the task-space inertia matrix Λ = (J · M⁻¹ · J.T)⁻¹ and the dynamically-consistent Movement, Mechanics & Robot BodyTorqueA rotational force around a joint or axis. map τ = J.T · Λ · (ẍ_des - J̇ q̇) + null-space term. Compare tracking accuracy against today's simpler J.T · F Navigation & LocomotionMappingBuilding a representation of the environment.. The full version is what Khatib advocates and what high-end controllers (Cartesio, Drake's IIWA stack) actually run.

---

Deliverable checklist

Optional: Submit your repo