# How FrictionDRBamActuator Modifies the BAM Actuator Model in Microduck RL

> Learn how FrictionDRBamActuator modifies the BAM actuator model in Microduck RL. Discover non-accumulating domain randomization for sim-to-real transfer.

- Repository: [Pollen Robotics/microduck_rl](https://github.com/pollen-robotics/microduck_rl)
- Tags: deep-dive
- Published: 2026-09-01

---

**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`](https://github.com/pollen-robotics/microduck_rl/blob/main/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:

```python
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`](https://github.com/pollen-robotics/microduck_rl/blob/main/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:

```python

# 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:

```python

# 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:

```python

# 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`](https://github.com/pollen-robotics/microduck_rl/blob/main/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`](https://github.com/pollen-robotics/microduck_rl/blob/main/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.