Fitting#

The fitting step optimizes the friction model parameters so that the BAM simulator reproduces the recorded trajectories as closely as possible. The objective is the mean absolute error (MAE) between simulated and measured joint positions, averaged across all logs.

Running the fit#

uv run python -m bam.fit \
    --actuator xl330 \
    --model m6 \
    --logdir data_processed \
    --output params/xl330/m6.json

--actuator must match the motor name used during recording. --model selects the friction model variant (m1 through m6).

The optimizer writes params.json every few seconds as it runs, so progress can be monitored by inspecting the output file or running uv run python -m bam.plot in parallel.

Optimization options#

Argument

Default

Description

--method

cmaes

Optimization algorithm: cmaes (CMA-ES with BIPOP restart), random, or nsgaii.

--trials

100 000

Number of evaluations. Increase for better convergence on complex models (M5, M6).

--workers

1

Number of parallel workers. Uses a shared SQLite study database when greater than 1.

--load-study

Path to an existing Optuna study to resume optimization.

--reset_period

Re-synchronize the simulator state to the log at this interval [seconds]. Useful when accumulated error destabilizes long rollouts.

Validation split#

To detect overfitting, hold out the logs recorded at one P-gain value and use them as a validation set:

uv run python -m bam.fit \
    --actuator xl330 \
    --model m6 \
    --logdir data_processed \
    --validation_kp 8 \
    --output params/xl330/m6.json

Logs recorded with --kp 8 are excluded from training and evaluated separately. The best validation MAE is reported alongside the training score.

Fitting all models#

It is recommended to fit all six models and compare their validation error:

for model in m1 m2 m3 m4 m5 m6; do
    uv run python -m bam.fit \
        --actuator xl330 \
        --model $model \
        --logdir data_processed \
        --validation_kp 8 \
        --output params/xl330/$model.json \
        --trials 100000
done

Simpler models (M1, M2) train faster; richer models (M5, M6) may need more trials to converge but can capture directional and load-dependent effects that simpler models miss. The model with the best validation MAE and acceptable parameter count is typically the right choice.

Output file#

The optimizer writes a JSON file containing the identified parameters and metadata:

{
  "model": "m6",
  "actuator": "xl330",
  "kt": 2.21,
  "R": 2.03,
  "armature": 0.026,
  "friction_base": 1.0e-05,
  "friction_viscous": 0.051,
  "friction_stribeck": 0.122,
  "dtheta_stribeck": 1.75,
  "alpha": 1.14,
  "load_friction_motor": 0.198,
  "load_friction_external": 0.022,
  "load_friction_motor_stribeck": 0.199,
  "load_friction_external_stribeck": 0.087,
  "load_friction_motor_quad": 0.010,
  "load_friction_external_quad": 7.3e-05,
  "q_offset": 0.0
}

The file can be passed directly to bam.model.load_model() or used as json_path in bam.mjlab.BamActuatorCfg.

Evaluating and visualizing results#

To evaluate a parameter file on the recorded logs:

uv run python -m bam.fit \
    --actuator xl330 \
    --model m6 \
    --logdir data_processed \
    --eval \
    --output params/xl330/m6.json

To plot measured versus simulated trajectories:

uv run python -m bam.plot \
    --actuator xl330 \
    --logdir data_processed \
    --sim \
    --params params/xl330/m6.json

Several --params files can be given to overlay multiple models on the same plot, which is useful for comparing M1 through M6 side by side.

Weights & Biases logging#

Pass --wandb to stream training and validation metrics to a W&B project:

uv run python -m bam.fit \
    --actuator xl330 \
    --model m6 \
    --logdir data_processed \
    --wandb \
    --output params/xl330/m6.json