How FrictionDRBamActuator Modifies the BAM Actuator Model in Microduck RL

FrictionDRBamActuator extends the base BamActuator by introducing per-environment friction scaling through a friction_scale tensor and an overridden _compute_friction_budget() method, enabling non-accumulating domain randomization for sim-to-real transfer.

The Microduck RL repository from Pollen Robotics builds upon the voltage-controlled XL330 actuator defined in the external bam.mjlab.BamActuator class. To support robust domain randomization, FrictionDRBamActuator modifies the BAM actuator model to expose friction magnitude as a per-environment parameter without altering the underlying voltage-control logic or friction physics.

Extending the BAM Actuator with Per-Environment Friction Scaling

The subclass is defined in src/mjlab_microduck/actuator/friction_dr_bam.py and inherits directly from bam.mjlab.BamActuator. It introduces three core architectural modifications that transform static friction parameters into randomized, environment-specific variables.

Initialization of Friction State Tensors

In the initialize() method, the class allocates self.friction_scale as a PyTorch tensor of shape (num_envs, 1), creating an independent scalar multiplier for each parallel simulation environment. It also stores default_friction_scale to preserve the baseline 1.0 multiplier. This approach mirrors the existing per-environment infrastructure used for parameters like kp_scale, ensuring compatibility with batched simulation environments.

Overriding the Friction Budget Computation

The subclass overrides _compute_friction_budget(), the method the base BAM actuator uses to calculate velocity-independent friction components including Coulomb, Stribeck, and load-dependent terms. The implementation first calls super()._compute_friction_budget(...) to retrieve the base friction calculation, then multiplies this value by the per-environment friction_scale (base * fs). This operation scales the dominant friction terms differently for each environment while preserving the original physics model's structure.

Domain Randomization API Methods

Two public methods expose friction control to external randomization logic:

  • set_friction_scale(env_ids, friction_scale): Writes new scalar values into specific environment indices of the friction_scale tensor.
  • reset_friction_scale(env_ids): Restores the default 1.0 multiplier to specified environments.

These methods allow training event handlers to sample new friction magnitudes at episode boundaries without requiring direct access to internal tensor buffers.

Backlash-Aware Variant: BacklashEncoderBamActuator

A secondary subclass, BacklashEncoderBamActuator, inherits from FrictionDRBamActuator to handle mechanical backlash in encoder feedback. During initialize(), it discovers joints named passive_<joint>_backlash and constructs indexing masks (self._backlash_joint_ids) and multipliers (self._backback_mask).

The get_command() method modifies the position command to account for encoder readings that pass through the backlash joint:

cmd.pos + data.joint_pos[:, self._backlash_joint_ids] * self._backback_mask

When no backlash joints exist in the model, this class behaves identically to FrictionDRBamActuator, making the upgrade path transparent.

Wiring Randomization into Training Episodes

The environment startup event randomize_bam_friction (implemented in src/mjlab_microduck/tasks/mdp.py at lines 3212-3241) orchestrates the domain randomization. The function executes the following steps for each training episode:

  1. Identifies all FrictionDRBamActuator instances attached to the robot entity.
  2. Resets the friction scale to 1.0 via reset_friction_scale(env_ids) to prevent parameter accumulation across episodes.
  3. Samples new values uniformly from a configurable scale_range using torch.rand(len(env_ids), 1) * (hi - lo) + lo.
  4. Applies the sampled scalars via set_friction_scale(env_ids, new_scales).

This non-accumulating approach ensures that friction variability mimics real-world hardware variability without causing drift over long training runs.

Architectural Benefits of the Subclass Design

The inheritance chain (BamActuator → FrictionDRBamActuator → BacklashEncoderBamActuator) provides specific engineering advantages:

  • Modular extension: Each layer addresses a distinct concern—voltage control, friction randomization, and backlash handling—without cross-contamination.
  • Zero-accumulation guarantee: The explicit reset_friction_scale() call at episode start eliminates the compounding randomization bug common in other implementations.
  • Transparent integration: Policy networks, reward functions, and other simulator components continue using standard actuator APIs; they receive scaled friction values automatically.
  • Parallel environment support: By leveraging tensors of shape (num_envs, 1), the implementation utilizes existing parallel simulation infrastructure without introducing Python-level loops.

Configuration and Usage Examples

To use the friction-randomized actuator, import the configuration class and specify it in your robot configuration:


# Example: creating a robot config that uses the friction‑DR actuator

from mjlab_microduck.actuator.friction_dr_bam import FrictionDRBamActuatorCfg

# In a task config (e.g., microduck_velocity_env_cfg.py):

class MyRobotCfg:
    # …

    actuator_cfg = FrictionDRBamActuatorCfg(
        kp_scale=1.0,
        kd_scale=0.1,
        # other BAM parameters …

    )

The randomization event logic typically follows this pattern:


# Example: how the randomization event works (simplified)

def randomize_bam_friction(env, env_ids, scale_range):
    for act in env.scene["robot"].actuators:
        if isinstance(act, FrictionDRBamActuator):
            act.reset_friction_scale(env_ids)
            new_scales = torch.rand(len(env_ids), 1) * (scale_range[1] - scale_range[0]) + scale_range[0]
            act.set_friction_scale(env_ids, new_scales)

For robots with mechanical backlash, use the backlash-aware variant:


# Example: using the backlash‑aware actuator

from mjlab_microduck.actuator.friction_dr_bam import BacklashEncoderBamActuatorCfg

class MyBacklashRobotCfg:
    actuator_cfg = BacklashEncoderBamActuatorCfg(
        # same BAM params as above …

    )

Summary

  • FrictionDRBamActuator modifies the BAM model by overriding _compute_friction_budget() to apply a per-environment friction_scale multiplier.
  • The subclass maintains per-environment tensors of shape (num_envs, 1) to support parallel training without loops.
  • Domain randomization is wired through the randomize_bam_friction event in tasks/mdp.py, which uses reset_friction_scale() and set_friction_scale() to prevent accumulation.
  • BacklashEncoderBamActuator extends the friction model further to account for encoder feedback through backlash joints.
  • All modifications preserve the base BamActuator voltage-control semantics while exposing friction magnitude as a randomized parameter.

Frequently Asked Questions

What is the difference between FrictionDRBamActuator and the standard BamActuator?

FrictionDRBamActuator adds per-environment friction scaling via the friction_scale tensor and overrides _compute_friction_budget() to apply this scaling dynamically. The standard BamActuator uses fixed friction parameters across all environments, making it unsuitable for domain randomization without modification.

How does the friction randomization reset between episodes?

The randomize_bam_friction event explicitly calls reset_friction_scale(env_ids) to restore the default 1.0 multiplier before sampling new values. This non-accumulating design prevents friction magnitudes from compounding across long training runs, ensuring each episode starts from a known baseline.

When should I use BacklashEncoderBamActuator instead of FrictionDRBamActuator?

Use BacklashEncoderBamActuator when your robot model contains passive_<joint>_backlash joints that simulate mechanical play between gears. This subclass adjusts encoder position commands to account for backlash, matching real hardware behavior. If your model lacks backlash joints, FrictionDRBamActuator provides sufficient functionality with lower computational overhead.

Where is the domain randomization logic implemented?

The randomization logic resides in src/mjlab_microduck/tasks/mdp.py within the randomize_bam_friction function (lines 3212-3241). Task configurations register this function as a startup event and specify the scale_range parameter (e.g., [0.8, 1.2]) to control the magnitude of friction variation.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →