Tutorial 14: System Identification of a Franka#
Note
The System Identification extension is in beta. Its workflow, run contracts, exported run specifications, and APIs are not guaranteed to stay future compatible.
This tutorial runs a reproducible, free-space system-identification experiment for a fixed-base Franka Panda. To make the optimization behavior visible, it begins with a deliberately over-frictional working asset. The source asset is preserved; only a separate tutorial overlay is degraded. After recovering joint friction, the tutorial diagnoses the remaining held-out residual and runs a separately bounded joint-damping phase. Each phase starts from the previously persisted candidate and must pass its own replay gate.
The example uses recorded MCAP telemetry, explicit command alignment, separate training and validation motions, uncaptured Newton MuJoCo rollouts, and CMA-ES. Optimization, USD mutation, replay, and promotion remain separate operations so that every stage is reviewable and reversible.
The walkthrough follows the same progression: reference excitation and telemetry on the left, the deliberately poor initial model in orange, and the recovered candidate in green.#
After completing this tutorial, you will be able to:
Construct a controlled poor initialization without modifying a source asset.
Validate telemetry mapping, timing, excitation, and effort semantics.
Configure and export a bounded friction-only RunSpec.
Solve, persist, and independently replay one parameter phase.
Diagnose held-out residuals and configure a conservative second phase.
Compare candidates with a predeclared acceptance policy.
Decide when to promote a candidate and when to collect new excitation data.
Interpret predictive improvement without overstating physical identifiability.
For the complete feature reference, see System Identification Extension.
35-50 minute tutorial, excluding optimization time
Reference outcome#
The tutorial intentionally doubles all seven arm-joint friction values in a working overlay before fitting. The independently reloaded friction candidate reduces held-out normalized cost by 42.7%. Position, velocity, and torque RMSE improve by 8.3%, 13.7%, and 24.4%, respectively.
A second phase opens only seven joint-damping scales. Its independently reloaded candidate reduces torque RMSE from 1.6573 N*m to 1.3946 N*m and normalized cost by another 28.9%. Position and velocity RMSE regress slightly relative to the friction candidate, by 2.0% and 1.1%, while remaining better than the poor seed. The final decision therefore demonstrates an explicit application tradeoff instead of selecting the lowest scalar objective without review.
This is a stress-test initialization, not a recommended engineering prior. It demonstrates a staged tuning cycle from a meaningfully poor model while keeping every experiment stable and reviewable.
Prerequisites#
A built NVIDIA Isaac Sim source checkout.
isaacsim.robot_setup.sysid.uienabled. Open Window > Extensions, search for system identification, and enableisaacsim.robot_setup.sysid.ui. The interactive extension automatically enables theisaacsim.robot_setup.sysidbackend.The Franka tutorial assets from the Content Browser at
IsaacSim/Samples/SystemIdentification/, including a fixed-base Franka stage whose articulation resolves to/World/envs/env_0/panda_instanceable, the MCAP recording, topic mapping, and collection manifest.A CUDA-capable GPU for the Newton MuJoCo population rollout.
A local working directory for RunSpecs, reports, checkpoints, and output USDs. Copy Samples inputs into that directory when a headless command needs a filesystem path, and keep writable outputs local. Do not modify the Samples originals.
The reference recording contains 9,439 samples for seven arm joints at approximately 30 Hz. It provides joint position, velocity, command, and link-side torque.
Important
System Identification consumes excitation data; it does not generate or certify safe robot commands. Validate joint, velocity, acceleration, effort, collision, and workspace limits before executing a trajectory on hardware. See Trajectory generation and excitation for external experiment-design resources.
Step 1: Create a deliberately poor working asset#
Copy IsaacSim/Samples/SystemIdentification/franka_poor_seed.usda into a local working directory such as
C:\sysid_tutorial\, or open the nominal Franka stage from the same Samples folder, confirm that the
articulation initializes, and save a separate local overlay with the over-frictional values below. Do not
edit the Samples originals.
The nominal tutorial overlay used 0.5 N*m*s/rad of physical friction on each arm joint. For this experiment, set each of the seven working-copy values to 1.0 N*m*s/rad. Leave stiffness, damping, armature, masses, inertias, and controller settings unchanged. This isolates the injected error to the parameter family that will be optimized.
Property |
Nominal tutorial overlay |
Poor working overlay |
|---|---|---|
Joint 1-7 physical friction |
0.5 N*m*s/rad |
1.0 N*m*s/rad |
Other physical properties |
Unchanged |
Unchanged |
The poor seed remains numerically stable, but its excess friction creates a measurable trajectory and torque mismatch. This controlled construction is preferable to changing several coupled families at once because the cause of the initial error is known.
Warning
The 0.5 N*m*s/rad nominal values are tutorial seed values, not ground-truth measurements of a physical Franka. The recovered result is evaluated as a better predictive model for this recording, not as a certificate of true joint friction.
Step 2: Load and verify telemetry#
With isaacsim.robot_setup.sysid.ui enabled, open Tools > Robotics > Asset Editors >
System Identification. On Data:
Select
/World/envs/env_0/panda_instanceable.Select MCAP and choose the recording and topic mapping from
IsaacSim/Samples/SystemIdentification/.Choose the collection manifest from the same Samples folder if it defines named motion segments.
Click Load Trajectory.
Confirm 9,439 samples, seven joints, approximately 30 Hz, and the expected signal channels.
Confirm that effort mapping explicitly declares
torque_semantics: link_side.
The torque-semantics field matters because motor-side, link-side, commanded, and measured efforts are not interchangeable. Also verify joint order, signs, units, timestamp gaps, clipping, and long flat intervals.
Review the trajectory preview, chunk table, and quality summary together before configuring a solve.#
Step 3: Correct recording alignment#
The telemetry diagnostic estimates a common timing offset between commands and measured response. The reference recording produced:
Diagnostic |
Value |
|---|---|
Common lag |
16.0825 samples |
Common lag |
0.536075 s |
Joint-to-joint residual spread |
0.5336 samples |
Set telemetry.command_alignment_seconds to 0.5360748729665944 and leave the actuator command-delay
parameter disabled for this phase.
Command alignment corrects the recorded series before fitting. Actuator command delay changes the simulated model. Applying both to the same logging offset would double-count the effect.
Step 4: Separate training and validation#
Disable Auto split and retain two non-overlapping motions:
Chunk |
Role |
Excitation |
Duration |
|---|---|---|---|
|
Train |
Sine |
23.721 s |
|
Validation |
Sine |
27.374 s |
The training multisine contributes to the optimizer. The validation multisine is withheld from optimization and is used for model selection and the final replay. Both motions exercise all seven joints, but they do not guarantee that every selected physical parameter is independently identifiable.
Step 5: Configure the rollout#
Use the following Newton settings:
Setting |
Reference value |
|---|---|
Simulation engine |
Newton |
Newton solver |
MuJoCo |
Robot prim |
|
Device |
|
Controller |
|
Feedforward |
|
Effort clamp |
|
Offline stepping |
Enabled |
CUDA graph capture |
Enabled (default) |
Newton MuJoCo supports batched, derivative-free population evaluation. The differentiable Newton Featherstone rollout is a separate headless-only path with a narrower supported parameter set; it is not used by this CMA-ES walkthrough.
Leave CUDA graph capture enabled for repeated, fixed-shape CMA-ES evaluations. The first evaluation warms the kernels and solver storage, the next captures that shape, and later evaluations reuse the graph. Each captured and uncaptured MuJoCo rollout resets rollout-local warm-start, actuator-activation, applied-force, and control buffers before restoring the initial joint state, so a preceding candidate batch does not seed the next one.
In a reference Franka run with 16 candidates and 800 steps, graph replay reduced median evaluation time from 13.59 seconds uncaptured to 0.515 seconds captured, a 26.4x speedup after warmup. The ratio is specific to that workload and hardware. For a new stage or RunSpec, compare a captured result with an uncaptured result using declared numerical tolerances. Small GPU floating-point differences are expected; disable capture if the difference exceeds the acceptance threshold rather than requiring bitwise equality.
Step 6: Select and check the parameter phase#
Select seven joint-friction scale parameters:
Family |
Count |
Initial |
Minimum |
Maximum |
|---|---|---|---|---|
Joint friction |
7 |
1.0 |
0.1 |
1.5 |
The scale value 1.0 represents the deliberately poor 1.0 N*m*s/rad property authored in Step 1. The bounds therefore correspond to 0.1-1.5 N*m*s/rad on this working asset.
Click Run Check and read the current report. The reference friction-only regressor had rank 7 and
condition number 1.024. All seven rows were identifiable. With the bounds above, four analytical seeds
were in bounds and three were left unchanged because their unconstrained estimates were outside the
configured range. A count such as 7 selected is not an identifiability result; use per-parameter
sensitivity, rank, conditioning, and verdicts.
Do not add stiffness, damping, or armature to this first phase simply because the UI permits it. Adding coupled families would make a larger numerical improvement harder to interpret and would no longer match the controlled error introduced in Step 1. The later damping phase starts only after the friction candidate has been persisted, replayed, and accepted as its own baseline.
Step 7: Configure and export#
On Solve, use:
Setting |
Reference value |
|---|---|
Optimizer |
CMA-ES |
Maximum iterations |
48 |
Population and batch size |
16 |
Sigma |
0.2 |
Seed |
20260730 |
Maximum rollout steps |
800 |
Position weight |
1.0 |
Velocity weight |
1.0 |
Torque weight |
0.01 |
Export franka_friction_run_spec.json into your local working directory. Review every input path, output
path, bound, and writeback flag. Point input paths at your local copies of the Samples assets under
IsaacSim/Samples/SystemIdentification/, and keep output paths local. Store the exported file with its
topic mapping, collection manifest, source revision, and input hashes.
Step 8: Establish the poor-seed baseline#
Run the analyzer before optimization. The examples below use C:\sysid_tutorial as the local working
directory that holds copies of Samples inputs plus writable RunSpecs, reports, and candidate USDs:
.\_build\windows-x86_64\release\python.bat `
source\extensions\isaacsim.robot_setup.sysid\tools\headless_sysid_analyze.py `
--run-spec C:\sysid_tutorial\franka_friction_run_spec.json `
--stage C:\sysid_tutorial\franka_poor_seed.usda `
--result-json C:\sysid_tutorial\poor_seed_baseline.json `
--mode baseline
The aligned held-out baseline was:
Metric |
Value |
|---|---|
Normalized cost |
0.008066 |
Position RMSE |
0.004443 rad |
Velocity RMSE |
0.017925 rad/s |
Torque RMSE |
2.1922 N*m |
Keep this report unchanged. A post-solve value without the same-stage, same-RunSpec baseline cannot establish improvement.
Step 9: Solve, then apply to a new USD#
Run the exported specification without writeback:
.\_build\windows-x86_64\release\python.bat `
source\extensions\isaacsim.robot_setup.sysid\tools\headless_sysid_solve.py `
--run-spec C:\sysid_tutorial\franka_friction_run_spec.json `
--result-json C:\sysid_tutorial\franka_friction_result.json `
--checkpoint-json C:\sysid_tutorial\franka_friction_checkpoint.json
The selected candidate was the last accepted candidate at iteration 47. Review selected_status rather
than assuming the final attempted generation is accepted. The selected scales were:
Joint |
Initial scale |
Recovered scale |
Bound status |
|---|---|---|---|
Joint 1 |
1.000 |
0.214 |
Interior |
Joint 2 |
1.000 |
0.105 |
Interior |
Joint 3 |
1.000 |
0.848 |
Interior |
Joint 4 |
1.000 |
0.184 |
Interior |
Joint 5 |
1.000 |
0.172 |
Interior |
Joint 6 |
1.000 |
0.147 |
Interior |
Joint 7 |
1.000 |
0.156 |
Interior |
No selected value is classified as a bound hit. Joint 2 is close to the lower bound, so extending the search range would be a useful sensitivity study before interpreting its value physically.
Author the selected vector onto a new stage:
.\_build\windows-x86_64\release\python.bat `
source\extensions\isaacsim.robot_setup.sysid\tools\headless_sysid_apply_theta_to_stage.py `
--run-spec C:\sysid_tutorial\franka_friction_run_spec.json `
--result-json C:\sysid_tutorial\franka_friction_result.json `
--stage C:\sysid_tutorial\franka_poor_seed.usda `
--output-stage C:\sysid_tutorial\franka_friction_candidate.usda `
--summary-json C:\sysid_tutorial\franka_friction_apply_summary.json
This keeps optimization, asset mutation, and acceptance separately reviewable.
Step 10: Reload and compare held-out metrics#
Reload the persisted candidate in a fresh process and run a no-op baseline analysis:
.\_build\windows-x86_64\release\python.bat `
source\extensions\isaacsim.robot_setup.sysid\tools\headless_sysid_analyze.py `
--run-spec C:\sysid_tutorial\franka_friction_run_spec.json `
--stage C:\sysid_tutorial\franka_friction_candidate.usda `
--result-json C:\sysid_tutorial\franka_candidate_replay.json `
--mode baseline
The independently reloaded candidate produced:
Held-out metric |
Poor seed |
Persisted candidate |
Change |
|---|---|---|---|
Normalized cost |
0.008066 |
0.004620 |
42.7% lower |
Position RMSE |
0.004443 rad |
0.004073 rad |
8.3% lower |
Velocity RMSE |
0.017925 rad/s |
0.015476 rad/s |
13.7% lower |
Torque RMSE |
2.1922 N*m |
1.6573 N*m |
24.4% lower |
The left panel normalizes each error channel by its own poor-seed value. The right panel shows the large scale correction produced by the deliberately over-frictional initialization.#
Inspect time-series agreement as well as aggregate values:
The persisted candidate follows the held-out position motion across all seven joints. Look for phase error, bias, and localized divergence that an aggregate RMSE can hide.#
Torque RMSE improves substantially but remains higher in absolute units than the state-channel RMSE. Residual effort error can include model-form, controller, effort-calibration, unmodeled friction, and timing mismatch; friction-only fitting is not expected to remove all of it.#
Step 11: Diagnose the residual and declare the next gate#
The friction candidate passes the first-phase fit gate because normalized cost and all three aggregate held-out channels improve after the result is persisted and reloaded. The effort overlay still contains structured amplitude and bias error, however. A tuning cycle should use that residual to form a new, testable phase rather than adding every available parameter at once.
For this tutorial, define an illustrative torque-refinement gate before running the second solve:
Requirement |
Gate |
|---|---|
Persisted-stage normalized cost |
Lower than the friction candidate |
Persisted-stage torque RMSE |
At least 10% lower than the friction candidate |
Position and velocity RMSE |
No more than 2.5% higher than the friction candidate |
Parameter bounds |
No selected bound hits |
Evidence source |
Fresh-process replay of the written USD |
These thresholds demonstrate the mechanism; define application-specific tolerances before fitting your own robot. A strict Pareto policy would instead reject any candidate that worsens any channel.
Duplicate the friction RunSpec and change its source stage to
franka_friction_candidate.usda. Replace the friction entries with seven joint-damping scale parameters:
Family |
Count |
Initial |
Minimum |
Maximum |
|---|---|---|---|---|
Joint damping |
7 |
1.0 |
0.5 |
1.5 |
Keep the telemetry, chunks, command alignment, backend, controller, and channel weights unchanged. Use
CMA-ES with population 16, 48 iterations, sigma 0.15, and seed 20260731. Export the new contract as
franka_damping_run_spec.json.
Run a local sensitivity analysis before optimizing:
.\_build\windows-x86_64\release\python.bat `
source\extensions\isaacsim.robot_setup.sysid\tools\headless_sysid_analyze.py `
--run-spec C:\sysid_tutorial\franka_damping_run_spec.json `
--stage C:\sysid_tutorial\franka_friction_candidate.usda `
--result-json C:\sysid_tutorial\damping_baseline_sensitivity.json `
--mode sensitivity
All seven reference perturbations produced non-low sensitivity and matching train/validation gradient signs. The check report also warns that drive damping is not analytically observable from link-side torque at the true trajectory; the optimizer identifies it through simulated position response. Sensitivity supports a conservative trial, but it is not proof of unique physical identification.
Step 12: Solve, persist, and replay the damping phase#
Run the second RunSpec without writeback:
.\_build\windows-x86_64\release\python.bat `
source\extensions\isaacsim.robot_setup.sysid\tools\headless_sysid_solve.py `
--run-spec C:\sysid_tutorial\franka_damping_run_spec.json `
--result-json C:\sysid_tutorial\franka_damping_result.json `
--checkpoint-json C:\sysid_tutorial\franka_damping_checkpoint.json
The reference solve selected the last accepted candidate at iteration 33:
Joint |
Initial scale |
Selected scale |
Bound status |
|---|---|---|---|
Joint 1 |
1.000 |
1.290 |
Interior |
Joint 2 |
1.000 |
0.670 |
Interior |
Joint 3 |
1.000 |
0.923 |
Interior |
Joint 4 |
1.000 |
0.871 |
Interior |
Joint 5 |
1.000 |
0.567 |
Interior |
Joint 6 |
1.000 |
0.836 |
Interior |
Joint 7 |
1.000 |
0.579 |
Interior |
Write the selected vector to another new stage:
.\_build\windows-x86_64\release\python.bat `
source\extensions\isaacsim.robot_setup.sysid\tools\headless_sysid_apply_theta_to_stage.py `
--run-spec C:\sysid_tutorial\franka_damping_run_spec.json `
--result-json C:\sysid_tutorial\franka_damping_result.json `
--stage C:\sysid_tutorial\franka_friction_candidate.usda `
--output-stage C:\sysid_tutorial\franka_friction_damping_candidate.usda `
--summary-json C:\sysid_tutorial\franka_damping_apply_summary.json
Then reload the written stage in a new process:
.\_build\windows-x86_64\release\python.bat `
source\extensions\isaacsim.robot_setup.sysid\tools\headless_sysid_analyze.py `
--run-spec C:\sysid_tutorial\franka_damping_run_spec.json `
--stage C:\sysid_tutorial\franka_friction_damping_candidate.usda `
--result-json C:\sysid_tutorial\franka_damping_candidate_replay.json `
--mode baseline
Use the replay report as the evidence record. Do not substitute the optimizer’s in-process validation value.
Step 13: Compare the stages and choose the candidate#
The independently replayed stages produced:
Held-out metric |
Poor seed |
Friction candidate |
Friction+damping candidate |
|---|---|---|---|
Normalized cost |
0.008066 |
0.004620 |
0.003285 |
Position RMSE |
0.004443 rad |
0.004073 rad |
0.004153 rad |
Velocity RMSE |
0.017925 rad/s |
0.015476 rad/s |
0.015644 rad/s |
Torque RMSE |
2.1922 N*m |
1.6573 N*m |
1.3946 N*m |
The damping phase lowers aggregate cost and torque error, while position and velocity regress slightly relative to the friction-only candidate. All values come from independent persisted-stage replays.#
Relative to the friction candidate, the second phase lowers normalized cost by 28.9% and torque RMSE by 15.9%. Position and velocity RMSE increase by 2.0% and 1.1%. It passes the illustrative torque-refinement gate, but it does not strictly dominate the friction candidate.
Choose according to the policy declared in Step 11:
For torque-sensitive validation with the stated state-error tolerances, advance the friction+damping candidate to task-level testing.
For a strict no-regression policy or position-priority task, retain the friction-only candidate.
Preserve both stages. Promotion never requires overwriting the earlier accepted result.
Step 14: Decide whether another parameter phase is justified#
The second-stage report contains useful stopping evidence:
No damping scale hit a bound.
Parameter-confidence curvature was inconclusive for all seven damping scales.
Validation cost remained approximately 8.15 times the estimated noise floor.
Joint 2 retained the largest torque-residual power in the report.
Training and validation are still two segments of one multisine recording.
Do not respond by opening armature, link mass, center of mass, and inertia together on the same data. These families can explain overlapping acceleration-dependent residuals. Instead, collect independently designed motions: slow or static configurations for gravity terms, acceleration-rich segments for inertial terms, and a separate set of held-out frequencies. Then repeat the same cycle for one supported family at a time:
Establish the persisted-stage baseline.
Run the check and local sensitivity analysis.
Solve without writeback.
Apply the reviewed selected status to a new USD.
Replay the new stage in a fresh process.
Compare against the predeclared gate and retain or reject the stage.
For a separate known-truth algorithm and backend check, run Tutorial 15: Validate System Identification with Sim-to-Sim Recovery. That tutorial uses a simple double pendulum and transfers a Newton-recovered model into an NVIDIA Isaac Sim PhysX stage. It complements this recorded-data walkthrough; it does not turn the Franka fit into a known-truth result.
Step 15: Preserve evidence and decide#
Keep:
Source, poor-seed, and every candidate USD.
Recording, topic mapping, collection manifest, and declared train/validation split.
One exported RunSpec per parameter phase.
Baseline, sensitivity, result, checkpoint, apply summary, and independent replay reports.
Validation figures, acceptance policy, and any failed diagnostic reports.
Source revision, extension version, input hashes, GPU, driver, and optimizer seed.
The selected candidate is suitable for additional task validation only under the policy you declared before its solve. Keep the source USD untouched, and do not treat either tutorial outcome as a hardware-safety or physical-parameter certification.
Summary#
This tutorial covered:
Creating a controlled over-frictional working asset without modifying the source.
Declaring link-side torque semantics and correcting a measured 0.5361 s recording offset.
Separating coupled-multisine training and held-out frequency content.
Solving and independently replaying a bounded seven-friction phase.
Diagnosing the persisted residual before opening a second parameter family.
Solving and independently replaying a bounded seven-damping phase.
Comparing both candidates with an explicit application tradeoff.
Stopping when the existing excitation no longer supports a defensible broader solve.
Separating predictive fit quality from physical identifiability and known-truth validation.