Skip to content

sysid: independent + cross-model + external-hardware validation for the actuator recipes#2

Closed
machinavitalis wants to merge 4 commits into
mainfrom
jax-sysid-independent-oracle
Closed

sysid: independent + cross-model + external-hardware validation for the actuator recipes#2
machinavitalis wants to merge 4 commits into
mainfrom
jax-sysid-independent-oracle

Conversation

@machinavitalis

Copy link
Copy Markdown
Owner

Why

The sysid.actuator LM recipes (Servo / BLDC / SeriesElastic) were only ever checked by round-tripping telemetry generated with the same fixed-step RK4 the fitter uses, so a shared discretization or sign error passed silently — the fit recovered the bug, not the physics. This PR builds a validation ladder of increasing independence, ending in real hardware.

The ladder

  1. Integrator-independent (Servo/BLDC/SEA). Frozen reference traces from SciPy's implicit Radau (a different integrator than the fitter's RK4), applied to a standalone transcription of each device ODE. Rules out a discretization/sign bug shared between the fitter and its own data generator.
  2. Model-form independent (SEA, BLDC). Ground truth built from jaxonomy's own physics, not a transcription of the device ODE: SEA from acausal Inertia/Spring/Damper; BLDC from an acausal IdealMotor (its own R/L/back-EMF/torque law, with the current-loop P folded into an effective resistance so no feedback crosses the acausal boundary). A sign/coupling error in jaxterity's own equations fails to recover here even if copied into the transcription.
  3. Implementation independent (Servo). Its nonlinear saturation feedback can't be a passive acausal network, so its oracle is a jaxonomy causal block diagram on the BDF solver — a distinct engine, labeled honestly as the weaker level (not independent physics).
  4. External hardware (DC-motor recipe). Validated against the EMPS positioning-rig benchmark (Janot/Gautier/Brunot 2019) — real 1 kHz lab data, ground truth outside the stack. Recovers the rig's mass + viscous/Coulomb friction to within 0.2% of the paper's own IDIM-LS identification and predicts a held-out trace (~13.6% RMS). Fills the one recipe with no in-stack oracle.

Notes for review

  • New nightly tests (test/sysid/test_actuator_recipe_independent.py) load frozen .npz oracles; SciPy/jaxonomy are only needed to regenerate them (data/independent/generate*.py), not to run the tests.
  • EMPS is opt-in, not vendored. The data has an unclear license, so fetch_emps.py downloads it on demand (sha256-pinned) into a gitignored dir; the test is marked external_data — skips without the data, excluded from scripts/check.sh and CI. Numbers recorded in test/sysid/external/RESULTS.md.
  • Docs reconciled: KNOWN_GAPS.md now tracks the remaining sysid gaps explicitly (backlash, BLDC R/L/Ke electrical split, and external validation for the BLDC/SEA/Servo LM recipes — which have no open real dataset and need a bench capture).
  • Gate green: ruff, format, mypy, and the nightly recovery suite.

🤖 Generated with Claude Code

The Servo/BLDC/SeriesElastic LM fitters were only checked against telemetry
generated by the same fixed-step RK4 they use, so a shared discretization or
sign error could pass silently. Add frozen reference traces produced by an
independent integrator (SciPy Radau) over a standalone transcription of each
device ODE, plus a recovery test that fits them through the recipe. SciPy is
only needed to regenerate the traces, not to run the test.

Also document the remaining sysid gaps in KNOWN_GAPS.md (external-ground-truth
validation, transmission backlash, the BLDC R/L/Ke electrical split), which
were previously only noted in source comments.
The Radau oracle rules out a shared integrator but still transcribes
jaxterity's own device ODEs, so a physics model-form error copied into both
the fitter and the transcription would survive. Close that for the
series-elastic actuator with a cross-model oracle: ground truth built from
jaxonomy's acausal rotational primitives (Inertia/Spring/Damper/TorqueSource),
not from _sea_rhs at all. A sign or coupling error in jaxterity's SEA equations
now fails to recover even if copied identically into the transcription.

BLDC/Servo cross-model oracles (they embed controllers) and truly
external-to-stack validation (third-party log / real bench) remain tracked in
KNOWN_GAPS.md.
BLDC gets a genuine independent-physics oracle: jaxonomy's acausal IdealMotor
(its own R/L/back-EMF/torque equations) driven open-loop, with the current-loop
P controller folded exactly into an effective winding resistance (R+Kp_i) so no
causal feedback crosses the acausal boundary (which otherwise recurses). Recovers
Kt/J to ~0.3%.

Servo gets an independent-implementation oracle: its nonlinear saturation
feedback can't be a passive acausal network and jaxonomy's acausal sensor
outputs can't sit in a causal feedback loop, so it is a jaxonomy causal block
diagram (Integrator/Gain/Adder/tanh) on the BDF solver — a distinct engine but a
transcription of the same equations, labeled honestly as the weaker level.

generate_jaxonomy.py now emits all three oracles; two new nightly recovery
tests. Docs (KNOWN_GAPS/CHANGELOG/README) updated; the only remaining sysid
validation gap is ground truth external to the jax*y stack.
Adds the first sysid check whose ground truth is a real physical rig outside
the jax*y stack: the EMPS positioning-rig benchmark (Janot/Gautier/Brunot 2019).
The DC-motor recipe (V = Ks*sgn(v) + Kv*v + Ka*a) matches the rig's
F = M*a + Fv*v + Fc*sgn(v) form; on real 1 kHz data it recovers mass and
viscous/Coulomb friction to within 0.2% of the paper's own IDIM-LS
identification and predicts a held-out validation trace (~13.6% RMS). This fills
the one recipe with no in-stack oracle.

Mode B (fetched, not vendored): the data has an unclear license, so fetch_emps.py
downloads it on demand (sha256-pinned) into a gitignored dir and the test is
marked external_data — skips without the data, excluded from check.sh/CI. Numbers
recorded in RESULTS.md.

Remaining gap: the BLDC/SEA/Servo LM recipes have no open real dataset and still
need our own bench capture (tracked in KNOWN_GAPS).
@machinavitalis

Copy link
Copy Markdown
Owner Author

Closing: this work now lives on local main and will be pushed only on an explicit publish instruction. Removing the public branch/PR for now — no history is being carried publicly.

@machinavitalis
machinavitalis deleted the jax-sysid-independent-oracle branch July 20, 2026 00:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant