Examples › BAM rig › PID tuning results
PID tuning results
pidtuna's four-rung tuning ladder (factory → analytic → tuned → oracle) on nine simulated robots, as one dashboard: how close the tuned gains get to the oracle per robot, gains against the error they buy, the error per rung, every joint and motor, and the one real check (the sim plant against the rack's servos).
The .base file and its data
Every tab above is one entry of this file; the views read these rows. ⋯ in the toolbar copies the current view as YAML.
# PID tuning results as one dashboard: pidtuna's four-rung ladder (factory → analytic → tuned → oracle) on nine
# simulated robots, gains against the error they buy, and the one real comparison (the sim plant vs the rack).
# Data: source/build.py (pidtuna-website's exported JSON, sim and illustrative; sim_fidelity.json, real).
# Grammar: docs/peps/0004-dashboards.md.
data: gains.json
facets:
- robot
- { property: rung, order: [factory, analytic, tuned, oracle] }
- { property: joint, searchable: true, limit: 6 }
properties:
robot: { displayName: Robot }
rung: { displayName: Rung }
joint: { displayName: Joint }
kp: { displayName: Kp }
kd: { displayName: Kd }
tracking_rms: { displayName: Tracking RMS }
stop_p90: { displayName: Stop p90 }
max_error: { displayName: Max error }
ringing: { displayName: Ringing }
improvement: { displayName: × better than factory }
tuned_tracking_rms: { displayName: Tuned tracking RMS }
oracle_tracking_rms: { displayName: Oracle tracking RMS }
tuned_vs_oracle: { displayName: Tuned ÷ oracle }
servo: { displayName: Servo }
ff_fraction: { displayName: Coulomb feed-forward }
datasets:
gains: gains.json
robots: robots.json
fidelity:
data: fidelity.json
label: The rack (real)
facets: [servo, ff_fraction]
links:
- { from: gains.robot_id, to: robots.robot_id }
layout:
panels:
- text: |
## How close the tuner gets to the oracle
Each robot's joints are tuned four ways: the **factory** gains, the **analytic** first guess, the **tuned**
result and the **oracle** (the best any gains can do against the hidden true plant). These are pidtuna's
**simulated** robots with illustrative parameters (hollow marks: estimates), so read the ladder, not the
absolute numbers. Errors are mrad for joints and mm for the belt axes (conveyor, gantry). The data has no
overshoot or settling time: pidtuna scores *stop p90*, *tracking RMS*, *max error* and *ringing*.
w: 12
- { view: Tuned against the oracle, w: 12, h: 3 }
- { view: Gain vs error, w: 7, h: 5 }
- { view: Error by rung, w: 5, h: 5 }
- { views: [Every joint, Robot × rung], title: Per joint and motor, w: 12, h: 5 }
- text: |
### Does the sim plant match the real rack?
The one **real** comparison: the tuner's sim plant against the rack's servos (CAN 4, 11-14), stop p90 with and
without Coulomb feed-forward. Points on the diagonal agree; this dataset has no link to the robots above, so
their rail leaves it alone.
w: 12
- { view: Sim vs real stop p90, w: 6, h: 4 }
- { view: Fidelity by servo, w: 6, h: 4 }
views:
- type: kpi
name: Tuned against the oracle
from: robots
select: robot_id
groupBy: { property: robot }
kpi:
value: { property: tuned_tracking_rms }
compare: { against: oracle_tracking_rms, better: lower, neutral: 0.05 }
- type: chart
name: Gain vs error
select: robot_id
chart:
mark: { type: point, size: 70 }
encoding:
x: { field: kp, scale: { type: log }, title: Kp }
y: { field: tracking_rms, scale: { type: log }, title: Tracking RMS (mrad; mm for belt axes) }
color: { field: rung, title: Rung, sort: [factory, analytic, tuned, oracle] }
shape: { field: robot, title: Robot }
hover: { title: joint, fields: [robot, rung, kp, kd, tracking_rms, stop_p90, feedforward] }
- type: chart
name: Error by rung
select: rung
chart:
mark: bar
encoding:
x: { field: rung, sort: [factory, analytic, tuned, oracle], title: null }
y: { field: tracking_rms, aggregate: median, scale: { type: log }, title: Median tracking RMS }
color: { field: rung, sort: [factory, analytic, tuned, oracle], legend: null }
- type: table
name: Every joint
order: [robot, joint, rung, kp, kd, feedforward, tracking_rms, stop_p90, max_error, ringing, improvement]
sort:
- { property: robot_id, direction: ASC }
- { property: joint, direction: ASC }
- { property: rung_order, direction: ASC }
- type: pivot
name: Robot × rung
groupBy: { property: robot }
splitBy: [rung]
value: { property: tracking_rms, summary: Median }
- type: chart
name: Sim vs real stop p90
from: fidelity
chart:
mark: { type: point, filled: true, size: 90 }
encoding:
x: { field: real_p90, title: Real stop p90 (mrad) }
y: { field: sim_p90, title: Sim stop p90 (mrad) }
color: { field: servo, title: Servo }
shape: { field: ff_fraction, type: nominal, title: Coulomb ff }
guides:
- { indexLine: { slope: 1, through: 1 }, label: "sim = real" }
hover: { title: servo, fields: [ff_fraction, real_p90, sim_p90, real_tracking_rms, sim_tracking_rms, gap_p90] }
- type: table
name: Fidelity by servo
from: fidelity
order: [servo, ff_fraction, real_p90, sim_p90, gap_p90, real_tracking_rms, sim_tracking_rms, real_coulomb_nm, truth_coulomb_nm]
sort:
- { property: can_id, direction: ASC }
// gains.json: 132 rows, 115 KB; the first 3
{
"id": "pid-tuning-gains",
"properties": {
"complete": {
"note": "Whether the run finished the whole tape.",
"type": "boolean",
"width": 80
},
"feedforward": {
"note": "Which feed-forward terms the rung used, as the file words them.",
"type": "text",
"width": 240
},
"improvement": {
"note": "factory tracking_rms divided by this rung's, same joint: how many times better than shipped gains. Factory is 1×.",
"type": "number",
"width": 100
},
"joint": {
"note": "Joint name from the robot file; the five-bar lists only its two driven joints.",
"type": "text",
"width": 190
},
"kd": {
"note": "Derivative gain as written in the result file.",
"type": "number",
"width": 90
},
"kp": {
"note": "Proportional gain as written in the result file (its own units; not converted).",
"type": "number",
"width": 90
},
"max_error": {
"note": "Worst absolute tracking error. Unit is the robot's (mrad or mm).",
"type": "number",
"width": 120
},
"ringing": {
"note": "Oscillation after the stop. Unit is the robot's (mrad or mm).",
"type": "number",
"width": 100
},
"robot": {
"display": "badge",
"note": "Demo robot (index.json title). Sim, illustrative parameters.",
"type": "text",
"width": 190
},
"robot_id": {
"note": "pidtuna's robot key; joins to robots.json.",
"type": "text",
"width": 90
},
"rung": {
"display": "badge",
"note": "Tuning method: factory (as-shipped gains), analytic (from the description), tuned (auto-tuner), oracle (tuned against the hidden truth).",
"type": "text",
"width": 90
},
"rung_order": {
"note": "0 factory, 1 analytic, 2 tuned, 3 oracle: sorts the ladder.",
"type": "number",
"width": 70
},
"saturation": {
"note": "Fraction of samples at the torque limit, 0-1.",
"type": "number",
"width": 90
},
"stop_p90": {
"note": "90th percentile settling error at the stops. Unit is the robot's (mrad or mm); the score key says mrad even for mm robots.",
"type": "number",
"width": 120
},
"tracking_rms": {
"note": "RMS tracking error over the benchmark tapes. Unit is the robot's (mrad or mm).",
"type": "number",
"width": 120
}
},
"rows": [
{
"complete": true,
"feedforward": "none",
"id": "conveyor/conveyor/factory",
"improvement": {
"basis": "derived",
"source": "conveyor.json: factory tracking_rms / factory tracking_rms, same joint",
"unit": "×",
"value": 1
},
"joint": "conveyor",
"kd": 61.4,
"kp": 368.4,
"max_error": {
"basis": "estimate",
"source": "conveyor.json:rungs.factory.conveyor.score.max_error_mrad",
"unit": "mm",
"value": 176.0826
},
"ringing": {
"basis": "estimate",
"source": "conveyor.json:rungs.factory.conveyor.score.ringing_mrad",
"unit": "mm",
"value": 0.2431
},
"robot": "Conveyor belt",
"robot_id": "conveyor",
"rung": "factory",
"rung_order": 0,
"saturation": 0,
"stop_p90": {
"basis": "estimate",
"source": "conveyor.json:rungs.factory.conveyor.score.stop_p90_mrad",
"unit": "mm",
"value": 101.5043
},
"tracking_rms": {
"basis": "estimate",
"source": "conveyor.json:rungs.factory.conveyor.score.tracking_rms_mrad",
"unit": "mm",
"value": 90.5411
}
},
{
"complete": true,
"feedforward": "Coulomb + viscous + inertia",
"id": "conveyor/conveyor/analytic",
"improvement": {
"basis": "derived",
"source": "conveyor.json: factory tracking_rms / analytic tracking_rms, same joint",
"unit": "×",
"value": 68.426
},
"joint": "conveyor",
"kd": 633.156782,
"kp": 16286.748307,
"max_error": {
"basis": "estimate",
"source": "conveyor.json:rungs.analytic.conveyor.score.max_error_mrad",
"unit": "mm",
"value": 5.2516
},
"ringing": {
"basis": "estimate",
"source": "conveyor.json:rungs.analytic.conveyor.score.ringing_mrad",
"unit": "mm",
"value": 0.0006
},
"robot": "Conveyor belt",
"robot_id": "conveyor",
"rung": "analytic",
"rung_order": 1,
"saturation": 0,
"stop_p90": {
"basis": "estimate",
"source": "conveyor.json:rungs.analytic.conveyor.score.stop_p90_mrad",
"unit": "mm",
"value": 2.499
},
"tracking_rms": {
"basis": "estimate",
"source": "conveyor.json:rungs.analytic.conveyor.score.tracking_rms_mrad",
"unit": "mm",
"value": 1.3232
}
},
{
"complete": true,
"feedforward": "Coulomb + viscous + inertia + gravity",
"id": "conveyor/conveyor/tuned",
"improvement": {
"basis": "derived",
"source": "conveyor.json: factory tracking_rms / tuned tracking_rms, same joint",
"unit": "×",
"value": 284.541
},
"joint": "conveyor",
"kd": 2130.75072,
"kp": 70030.037766,
"max_error": {
"basis": "estimate",
"source": "conveyor.json:rungs.tuned.conveyor.score.max_error_mrad",
"unit": "mm",
"value": 1.9194
},
"ringing": {
"basis": "estimate",
"source": "conveyor.json:rungs.tuned.conveyor.score.ringing_mrad",
"unit": "mm",
"value": 0.0009
},
"robot": "Conveyor belt",
"robot_id": "conveyor",
"rung": "tuned",
"rung_order": 2,
"saturation": 0.0004,
"stop_p90": {
"basis": "estimate",
"source": "conveyor.json:rungs.tuned.conveyor.score.stop_p90_mrad",
"unit": "mm",
"value": 0.5911
},
"tracking_rms": {
"basis": "estimate",
"source": "conveyor.json:rungs.tuned.conveyor.score.tracking_rms_mrad",
"unit": "mm",
"value": 0.3182
}
}
],
"source": "~/pidtuna-website/site/public/data/index.json + ~/pidtuna-website/site/public/data/<robot>.json (pidtuna-website 1414ae6; sim results, demo parameters illustrative per pidtuna 63c33ba), read_on 2026-10-26. Simulated against a hidden truth plant: not hardware.",
"title": "PID tuning ladder per joint (sim, illustrative)",
"titleProperty": "joint"
}README.md
# PID tuning results
pidtuna's four-rung tuning ladder (factory → analytic → tuned → oracle) on nine simulated robots, as one dashboard:
how close the tuned gains get to the oracle per robot, gains against the error they buy, the error per rung, every
joint and motor, and the one real check (the sim plant against the rack's servos).
- **App:** BAM rig
- **Replaces:** pidtuna.com's per-robot result tables and the rack tuner's printed summaries
(`bam_pid_tunning/docs/build_report.py`); the MuJoCo replays stay on pidtuna.com
(`~/pidtuna-website/site`) and the design rationale in `~/bam_ws/reports/pid-tuner-design-rationale-2026-10-26.html`.
- **Shows:** three datasets, two linked (`gains.robot_id → robots.robot_id`) and one deliberately not (the rack's
fidelity data, which the robots' rail leaves alone); KPI cards whose delta is against a property of the same row
(`compare.against: oracle_tracking_rms`); a log-log gain-vs-error scatter whose click selects a robot; bars of the
median error per rung whose click selects a rung; a per-joint table and a robot × rung pivot as tabs; a sim-vs-real
scatter with a y = x guide.
- **Data:** `gains.json` (132 joint × rung rows), `robots.json` (9) from pidtuna-website's exported
`site/public/data/*.json` at 1414ae6: **simulated, illustrative parameters** (commit 63c33ba), scores drawn as
estimates; `fidelity.json` (10 rows) from bam_ws `bam_pid_tunning/docs/sim_fidelity.json` (branch
`claude/pid-auto-tuner-stack-e956da`, d572a55f): **real** rack measurements. Written by `source/build.py`, read-only.
No overshoot or settling time exists in either source.
The original UI it replaces
pidtuna.com's per-robot result tables and the rack tuner's printed summaries (bam_pid_tunning/docs/build_report.py); the MuJoCo replays stay on pidtuna.com (~/pidtuna-website/site) and the design rationale in ~/bam_ws/reports/pid-tuner-design-rationale-2026-10-26.html.
What it shows
three datasets, two linked (gains.robot_id → robots.robot_id) and one deliberately not (the rack's fidelity data, which the robots' rail leaves alone); KPI cards whose delta is against a property of the same row (compare.against: oracle_tracking_rms); a log-log gain-vs-error scatter whose click selects a robot; bars of the median error per rung whose click selects a rung; a per-joint table and a robot × rung pivot as tabs; a sim-vs-real scatter with a y = x guide.
Data
gains.json (132 joint × rung rows), robots.json (9) from pidtuna-website's exported site/public/data/*.json at 1414ae6: simulated, illustrative parameters (commit 63c33ba), scores drawn as estimates; fidelity.json (10 rows) from bam_ws bam_pid_tunning/docs/sim_fidelity.json (branch claude/pid-auto-tuner-stack-e956da, d572a55f): real rack measurements. Written by source/build.py, read-only. No overshoot or settling time exists in either source.