Skip to content

SysID: PHYSICAL_PARAMS system identification via free-running rollout fit#48

Merged
yichao-liang merged 7 commits into
pr2-dominofrom
pr3-sysid
Jul 7, 2026
Merged

SysID: PHYSICAL_PARAMS system identification via free-running rollout fit#48
yichao-liang merged 7 commits into
pr2-dominofrom
pr3-sysid

Conversation

@yichao-liang

@yichao-liang yichao-liang commented Jul 7, 2026

Copy link
Copy Markdown
Collaborator

Stacked PR - merge bottom-up. This branch targets the PR below it, so its diff shows only its own changes.

  1. Infra: PyBullet env-leak fix, adaptive-thinking SDK, scene-snapshot guard #46 Infra fixes (base: master)
  2. Domino: goal-NL cleanup + relax Toppled threshold to 30 deg #47 Domino tuning (base: Infra: PyBullet env-leak fix, adaptive-thinking SDK, scene-snapshot guard #46)
  3. SysID: PHYSICAL_PARAMS system identification via free-running rollout fit #48 SysID / PHYSICAL_PARAMS (base: Domino: goal-NL cleanup + relax Toppled threshold to 30 deg #47)
  4. Fan env: grid-free agent + GT hybrid (wind) simulator #49 Fan env (base: SysID: PHYSICAL_PARAMS system identification via free-running rollout fit #48)
  5. predicatorv3 configs: thin launchers, exp splits, shared use_gt_helpers #50 predicatorv3 configs (base: Fan env: grid-free agent + GT hybrid (wind) simulator #49)
  6. Add pybullet_bridge glue-construction domain #51 Bridge domain (base: predicatorv3 configs: thin launchers, exp splits, shared use_gt_helpers #50)
    PHYSICAL_PARAMS system identification: fit execution-relevant physical parameters (e.g. domino friction) from free-running PyBullet rollouts, plus the robustness/identifiability follow-ups. Stacked on the domino-tuning PR.

Commits

  • Add PHYSICAL_PARAMS system identification via free-running rollout fit - new code_sim_learning/physical_sysid.py fit stack, wired through the agent SDK tools, agent_sim_learning_approach, and per-env PHYSICAL_PARAMS declarations on base_env / domino.
  • Zero arm joint velocities between rollout evaluations - resets don't clear velocities; history-dependent probes were the result.
  • Discount probe curvature below the noise floor - identifiability guard so flat/noisy probe segments don't masquerade as signal.
  • Truncate settled trajectory tails + grid-seed the LM start - drop the static tail (pure divergence, no physics signal) and coarse-grid-seed the Levenberg-Marquardt start to escape locally-flat SSE.
  • Trim untrustworthy trajectories, guard param application - consistency-factor trimming of chaotic trajectories and safer parameter application.
  • Fit scale-like physical params in log-space - full z = log(theta) fit stack (geomspace grid, log-normal prior, log probe, fit-space ensembles); recovers friction ~0.0987 vs a linear fit's 0.0100 on identical data.

Contains one hand-resolved settings.py conflict (kept num_mcmc_steps here; the default-to-0 change lands in the configs PR).

Teacher-forced single-step fitting cannot see physical parameters of
momentum-driven dynamics (states carry pose but no velocity), so
friction/restitution were invisible to the existing objective. New
code_sim_learning/physical_sysid.py fits agent-declared PHYSICAL_PARAMS
by matching free-running base-sim rollouts of full trajectories, jointly
with rule PARAM_SPECS in one posterior, and reports per-parameter
identifiability (posterior contraction) instead of regularizing it away.

- BaseEnv.get_physical_param_info / apply_physical_param_overrides:
  envs advertise tunable physics and accept sticky in-place overrides;
  implemented for pybullet_domino (friction, restitution, mass,
  rolling/spinning friction).
- Synthesis prompt gains an optional PHYSICAL_PARAMS section built from
  the env's revealed menu; evaluate_step_fit switches to the joint
  rollout fit when the artifact declares PHYSICAL_PARAMS and appends
  the identifiability report.
- Physics-only artifacts (no PROCESS_RULES) are now valid; identified
  values are applied to the planning base env so refinement and test
  planning use calibrated physics.
- _solve_lm grows a diff_step override: simulation residuals need a
  coarser finite-difference step or the Jacobian is identically zero.
- New CFG code_sim_learning_rollout_num_mcmc_steps keeps the costly
  rollout-fit MCMC budget separate (default 0 = LM point fit).
resetBaseVelocity alone leaves articulated joint velocities behind, and
_set_state's per-component diff skips joints whose positions already
match, so consecutive rollouts on the shared fit env inherited up to
~1.8 rad/s of residual arm momentum. Position control overrides it
within a step or two, so this is hygiene rather than the (engine
internal) source of the observed same-theta SSE jitter, but rollouts
genuinely must start at rest.
The prior-scale curvature probe measured the SSE increase against a
single MAP evaluation, so on chaotic data the same-theta evaluation
jitter itself read as curvature and every parameter was declared
identified (run_20260705_203314: ~5k d_sse inside a ~8k jitter,
contraction 0.00 on all params, false confidence in friction 0.5).
Evaluate the MAP SSE three times, take the median, and subtract the
observed spread from every perturbation response before it counts as
curvature; a flat-after-discount direction reports NOT identified.
Two measured blockers on real domino data (true friction 0.1, planning
0.5):

- A free-running rollout diverges chaotically from the recording over
  hundreds of contact steps, and the long settled tail re-scores that
  accumulated divergence every step: on full 500-step trajectories the
  SSE at the TRUE friction exceeded the SSE at the wrong one. Cutting
  each trajectory after its last observed scored-feature motion (plus a
  settle margin) restores the signal: a clean top-edge push goes from
  ratio 0.9 to ~5e4 wrong/true SSE.

- The SSE landscape is flat above friction ~0.5 (topple reach
  saturates), so LM's finite differences see no gradient from the
  declared init and the fit stalls even on clean data. A coarse
  per-parameter grid sweep relocates the LM starting point into the
  best-scoring basin; the Gaussian prior stays centered on the declared
  init.

Together these recover friction 0.07-0.08 from a single clean recorded
push (init 0.5); previously the fit returned the init unchanged.
…ation

The fit consumed whatever exploration recorded, and applied whatever it
fitted. Both trusted too much (run_20260706_111805: a chaotic scraping
push dragged the pooled friction fit to 0.34, and rolling/spinning
friction were applied at arbitrary values their own identifiability
report called NOT identified). Three layers, each verified against
real recorded episodes:

- Explainability trimming (min_explainable_rms): judge each trajectory
  by its best-achievable RMS over a candidate param grid — judged
  against its own best params, not the pooled fit, which chaos poisons
  — and drop recordings no parameters can explain before fitting.
- Consistency loop: at the joint fit every survivor must fit nearly as
  well as its own best; on disagreement (a recording can be
  accidentally explainable at WRONG params, e.g. a quiet shove at
  friction ~1.34 vs the clean topple's 0.1) drop the survivor with the
  largest best-RMS and refit, anchoring on the cleanest data.
- select_trustworthy_params: apply a fitted physical value to the
  planning env only when its identifiability verdict is at least
  weakly identified; keep the declared init otherwise. If nothing
  survives trimming, the result is pinned at the declared inits so a
  false-positive verdict on chaotic data cannot leak arbitrary values.

Callers compute post-SSE and the identifiability probe on the
SURVIVING trajectories only. E2E on real recordings: chaotic-only data
keeps the declared inits with honest NOT-identified verdicts; chaotic
pooled with one clean topple trims the chaos and recovers friction
0.0706 (true 0.1) plus rolling_friction 0.0078 (true 0.006).
run_20260706_171526 fit friction 0.0114 for a true 0.1: the linear
grid over [0.01, 2.0] has no candidate between 0.01 and 0.29, the
sweep jumped to the 0.01 endpoint, LM saw no gradient across the flat
low-friction basin, and the linear curvature probe blessed the
bound-hugging MAP as identified. The believed sim ended up 9x too
slippery and drop-place plans verified in sim but failed in the real
env (the 2/5 final-test failures).

ParamSpec gains scale="log" for positive scale-like parameters. The
optimizer runs in z = log(theta): geometric grid sweeps (a candidate
lands at ~0.098), relative LM finite-difference steps, a log-normal
prior (x2 up and x2 down equally plausible), log-space identifiability
contraction, and fit-space Laplace/perturbation ensembles. Everything
simulator- and caller-facing stays in linear units; FitResult samples
stay external while jacobian/prior_sigma live in fit space (scales
records the mapping).

The domino env registry marks friction, spinning_friction, and mass as
log-scale (restitution and rolling_friction keep lo=0, stay linear),
and the approach/tool paths stamp the registry scale onto the
agent-declared PHYSICAL_PARAMS so agents need not know about it.

E2E-verified on a same-engine cascade at true friction 0.1 from init
0.5: log-scale recovers 0.0987 (SSE 0.705 -> 0.0005); the old linear
path reproduces the 0.0100 endpoint failure on identical data.
@yichao-liang yichao-liang merged commit 900f94f into pr2-domino Jul 7, 2026
14 checks passed
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