= 0.006, 'guard: arm_thick below M2 minimum 0.006 m'\nassert arm_len <= 0.300, 'guard: arm_len exceeds M2 envelope 0.300 m'\nprint('params loaded — guards pass')","label":"1 · Parametric model + M2 guards"},{"code":"def rollout(actuator_kv, actuator_kp, spring_k, stop_angle):\n np.random.seed(0xD2C1) # determinism: same params -> same telemetry\n dt = 0.002; T = 3.0; n = int(T/dt); t = np.arange(n)*dt\n tau_max = 2.0\n # effective rotational inertia about the hinge (folds in arm + hub/gear),\n # calibrated so kp/spring map to a clean single-DOF 2nd-order response.\n I = 0.06\n theta_ref = np.radians(stop_angle)\n theta = 0.0; omega = 0.0\n TH = np.zeros(n); TAU = np.zeros(n)\n for i in range(n):\n err = theta_ref - theta\n tau_ctrl = actuator_kp*err - actuator_kv*omega # PD controller\n tau_spring = -spring_k*(theta - theta_ref) # spring-return toward latch\n tau_dist = 0.15 if (2.0 <= t[i] < 2.05) else 0.0 # S4 disturbance impulse\n tau_act = np.clip(tau_ctrl, -tau_max, tau_max) # actuator saturates\n alpha = (tau_act + tau_spring + tau_dist) / I\n omega += alpha*dt; theta += omega*dt # semi-implicit Euler\n TH[i] = theta; TAU[i] = tau_act\n return t, np.degrees(TH), TAU, n, T, tau_max\n\nt, theta_deg, TAU, n, T, tau_max = rollout(actuator_kv, actuator_kp, spring_k, stop_angle)\nprint(f'rolled out {n} steps to {T}s')","label":"2 · Deterministic rollout (MuJoCo-WASM stand-in)"},{"code":"def metrics(t, theta_deg, TAU, n, T, tau_max, stop_angle):\n stop = stop_angle; travel = stop_angle - 0.0\n band = 2.0; within = np.abs(theta_deg - stop) <= band\n settle = np.inf # S1: first lasting entry into +-2 deg\n for i in range(n):\n if within[i] and np.all(within[i:]):\n settle = t[i]; break\n overshoot = max(0.0, (np.max(theta_deg) - stop)/travel*100.0) # S2\n last = t >= (T - 0.5); steady_error = np.mean(np.abs(theta_deg[last] - stop)) # S3\n post = t >= 2.0; hold = np.max(np.abs(theta_deg[post] - np.mean(theta_deg[last]))) # S4\n sat = np.mean(np.abs(TAU) >= tau_max - 1e-9) # S5\n return settle, overshoot, steady_error, hold, sat\n\nsettle, OS, e_ss, hold, sat = metrics(t, theta_deg, TAU, n, T, tau_max, stop_angle)\nprint(f'RUN seed=0xD2C1 actuator_kv={actuator_kv}')\nprint(f' settle_time = {settle:.2f} s overshoot = {OS:.1f} % steady_err = {e_ss:.2f} deg')\nprint(f' hold_delta = {hold:.2f} deg saturation = {sat*100:.0f} %')","label":"3 · Metrics from telemetry"},{"code":"def grade(settle, OS, e_ss, hold, sat):\n fails = []\n if not (settle <= 0.80):\n fails.append(f'FAIL S1: settle_time {settle:.2f}s > 0.80s -- arm rings before resting. Raise actuator_kv (damping).')\n if not (OS <= 5.0):\n fails.append(f'FAIL S2: overshoot {OS:.1f}% > 5.0% -- PUSH beats WHOA. Raise actuator_kv; do NOT raise actuator_kp (that grows overshoot).')\n if not (e_ss <= 1.0):\n fails.append(f'FAIL S3: steady_error {e_ss:.2f}deg > 1.0deg -- final aim is off. Raise actuator_kp or lower spring_k.')\n if not (hold <= 3.0):\n fails.append(f'FAIL S4: hold_under_load {hold:.2f}deg > 3deg -- the disturbance knocks it off latch. Stiffen control: raise actuator_kp.')\n if not (sat <= 0.05):\n fails.append(f'FAIL S5: saturation {sat*100:.0f}% at tau_max -- commanding more torque than the actuator delivers. Lower actuator_kp or raise actuator_kv.')\n if fails:\n for f in fails: print(f)\n else:\n print('PASS: latch-arm-v1 IN SPEC (5 of 5). Defensible: one knob, driven by the data.')\n\ngrade(settle, OS, e_ss, hold, sat)\nprint('\\n# Tune above: set actuator_kv = 0.50 and re-run cells 2-4. Result:')\ngrade(*metrics(*rollout(0.50, actuator_kp, spring_k, stop_angle), stop_angle))","label":"4 · Autograder — task-focused PASS / FAIL"}],"intro":"latch-arm-v1: a single-DOF revolute hinge driven by a torsion-spring + position actuator, modelled parametrically in numpy (in the full Bench this is CadQuery geometry + MuJoCo-WASM dynamics). Deterministic seed 0xD2C1, 3.0s rollout. Tune ONE knob — actuator_kv (damping) — and Re-Sim until all five spec checks PASS. Cranking actuator_kp makes overshoot worse; the data points at kv.","key":"design-simulation/improvement-loop-measure-tune-resim","kind":"python","title":"The Improvement Loop: Measure → Tune → Re-Sim"}">
PYTHON · NUMPY · IN-BROWSER

The Improvement Loop: Measure → Tune → Re-Sim

latch-arm-v1: a single-DOF revolute hinge driven by a torsion-spring + position actuator, modelled parametrically in numpy (in the full Bench this is CadQuery geometry + MuJoCo-WASM dynamics). Deterministic seed 0xD2C1, 3.0s rollout. Tune ONE knob — actuator_kv (damping) — and Re-Sim until all five spec checks PASS. Cranking actuator_kp makes overshoot worse; the data points at kv.