Using BAM in MuJoCo Warp via mjlab (GPU)#
This page explains how to integrate BAM friction models into an mjlab
pipeline running on GPU with MuJoCo Warp. The entry point is
bam.mjlab.BamActuatorCfg.
Installation#
BAM is available on PyPI. Install it with the mjlab extra, which pulls in
mjlab together with mujoco, mujoco-warp and torch:
pip install better-actuator-models[mjlab]
BAM is compatible with mjlab 1.3.
Overview#
BamActuatorCfg is a dataclass that plugs into mjlab’s
actuator system. When an Entity is built, it instantiates a
BamActuator that runs the full BAM pipeline — voltage
control law, DC motor torque, and BAM friction budget — fully vectorized
over all parallel environments via PyTorch tensors.
Instantiating the config#
Two approaches are available, mutually exclusive:
Bundled motor:
from bam.mjlab import BamActuatorCfg
actuator_cfg = BamActuatorCfg(
motor_name="xl330",
model="m6",
target_names_expr=(r".*",),
)
Custom JSON (parameters produced by bam.fit):
actuator_cfg = BamActuatorCfg(
json_path="params/my_motor/m6.json",
target_names_expr=(r".*",),
)
The target_names_expr field is a tuple of regex patterns that select
which actuated joints this config controls.
Supported bundled motors: "xl330", "xl320", "mx106", "mx64",
"erob80:50", "erob80:100".
Supported model variants: "m1" through "m6" (see Friction Models (M1-M6)).
Voltage and P-gain overrides#
By default, the supply voltage and firmware P-gain are read from the parameter JSON. They can be overridden at config level:
actuator_cfg = BamActuatorCfg(
motor_name="xl330",
model="m6",
target_names_expr=(r".*",),
vin=7.5, # supply voltage [V]
kp_fw=125.0, # firmware P-gain
)
Domain randomization#
BamActuatorCfg supports per-environment randomization
of two physical quantities that are naturally variable across hardware units
or charge states.
Battery voltage — sample a different supply voltage for each environment at startup:
actuator_cfg = BamActuatorCfg(
motor_name="xl330",
model="m6",
target_names_expr=(r".*",),
vin_range=(7.0, 8.0), # sampled uniformly at startup [V]
)
vin_range takes precedence over vin when both are set.
Voltage drop gain — model battery + cable resistance with a per-env internal-resistance gain:
where \(g_\text{drop} \approx R / K_t\). Randomizing this gain captures variability in cable length or connector quality across units:
actuator_cfg = BamActuatorCfg(
motor_name="xl330",
model="m6",
target_names_expr=(r".*",),
vin_range=(7.0, 8.0),
vin_drop_gain_range=(0.3, 0.7), # [V/Nm]
vin_min=6.0, # hard lower bound [V]
)
Both ranges are sampled once at initialization and held constant across episode resets.
Current clipping#
Servo firmwares can cap the motor current to protect the hardware. BAM reproduces
this saturation with the max_current field: the motor current
\(I = \tau / K_t\) is clipped to [-max_current, max_current], which is
equivalent to clipping the motor torque to \(\pm\,\texttt{max\_current}\cdot K_t\).
actuator_cfg = BamActuatorCfg(
motor_name="xl330",
model="m6",
target_names_expr=(r".*",),
max_current=1.75, # firmware current limit [A]
)
Leave it at None (default) to disable current clipping.
Command delay#
BAM inherits mjlab’s command delay system, which models the latency between policy output and motor response (e.g. communication bus latency, firmware scheduling). The lag is expressed in simulation steps and can be randomized per environment:
actuator_cfg = BamActuatorCfg(
motor_name="xl330",
model="m6",
target_names_expr=(r".*",),
delay_min_lag=1, # always at least 1 step of delay
delay_max_lag=3, # up to 3 steps, randomized per env
)
Setting delay_min_lag == delay_max_lag gives a fixed, deterministic delay.
Leave both at 0 (default) to disable delay entirely.
Passing the config to an Entity#
Pass the config to the actuator_cfgs argument of an mjlab Entity:
import mjlab
actuator_cfg = BamActuatorCfg(
motor_name="xl330",
model="m6",
target_names_expr=(r".*",),
kp_fw=125,
vin_range=(7.0, 8.0),
vin_drop_gain_range=(0.3, 0.7),
vin_min=6.0,
delay_min_lag=1,
delay_max_lag=3,
)
entity = mjlab.Entity(
xml_path="robot.xml",
actuator_cfgs=(actuator_cfg,),
)
mjlab calls build() internally to create the
BamActuator and wire it into the simulation graph.