Import upstream snapshot 57d13ead53206a6bf0db3d66f86506ae8c2ce01a

Upstream: https://github.com/pollen-robotics/bam
Upstream-Commit: 57d13ead53206a6bf0db3d66f86506ae8c2ce01a
Upstream-Branch: mjlab_frictionloss
This commit is contained in:
Upstream Snapshot
2026-08-28 15:42:08 +08:00
commit 8be13016bc
131 changed files with 12114 additions and 0 deletions
+90
View File
@@ -0,0 +1,90 @@
Identified actuators
====================
Each motor below ships with pre-identified friction parameters. Click a motor
to browse its JSON parameters, or use the **Use in MuJoCo** / **Use in mjlab**
buttons to jump to the integration guide with the code examples pre-filled for
that motor.
.. list-table::
:header-rows: 1
:widths: 14 14 18 28 26
:class: actuators-table
* - Actuator
- Image
- Name
- Description
- Use in simulation
* - **MX-64**
- .. image:: ../_static/actuator_mx64.png
:width: 120px
- ``mx64``
- ROBOTIS Dynamixel MX-64 servo-actuator
(`Parameters <https://github.com/Rhoban/bam/tree/main/bam/params/mx64>`__,
`model <https://github.com/Rhoban/bam/blob/main/bam/dynamixel/actuator.py>`__,
`raw data <https://huggingface.co/buckets/Gregwar/bam_data/resolve/mx64_raw.tgz?download=true>`__)
- .. raw:: html
<a class="sd-btn sd-btn-outline-primary motor-btn" href="mujoco_cpu.html?motor=mx64">Use in MuJoCo</a>
<a class="sd-btn sd-btn-outline-primary motor-btn" href="mjlab_gpu.html?motor=mx64">Use in mjlab</a>
* - **MX-106**
- .. image:: ../_static/actuator_mx106.png
:width: 120px
- ``mx106``
- ROBOTIS Dynamixel MX-106 servo-actuator
(`Parameters <https://github.com/Rhoban/bam/tree/main/bam/params/mx106>`__,
`model <https://github.com/Rhoban/bam/blob/main/bam/dynamixel/actuator.py>`__,
`raw data <https://huggingface.co/buckets/Gregwar/bam_data/resolve/mx106_raw.tgz?download=true>`__)
- .. raw:: html
<a class="sd-btn sd-btn-outline-primary motor-btn" href="mujoco_cpu.html?motor=mx106">Use in MuJoCo</a>
<a class="sd-btn sd-btn-outline-primary motor-btn" href="mjlab_gpu.html?motor=mx106">Use in mjlab</a>
* - **XL-320**
- .. image:: ../_static/actuator_xl320.png
:width: 120px
- ``xl320``
- ROBOTIS Dynamixel XL-320 servo-actuator
(`Parameters <https://github.com/Rhoban/bam/tree/main/bam/params/xl320>`__,
`model <https://github.com/Rhoban/bam/blob/main/bam/dynamixel/actuator.py>`__)
- .. raw:: html
<a class="sd-btn sd-btn-outline-primary motor-btn" href="mujoco_cpu.html?motor=xl320">Use in MuJoCo</a>
<a class="sd-btn sd-btn-outline-primary motor-btn" href="mjlab_gpu.html?motor=xl320">Use in mjlab</a>
* - **XL-330**
- .. image:: ../_static/actuator_xl330.png
:width: 120px
- ``xl330``
- ROBOTIS Dynamixel XL-330 servo-actuator
(`Parameters <https://github.com/Rhoban/bam/tree/main/bam/params/xl330>`__,
`model <https://github.com/Rhoban/bam/blob/main/bam/dynamixel/actuator.py>`__)
- .. raw:: html
<a class="sd-btn sd-btn-outline-primary motor-btn" href="mujoco_cpu.html?motor=xl330">Use in MuJoCo</a>
<a class="sd-btn sd-btn-outline-primary motor-btn" href="mjlab_gpu.html?motor=xl330">Use in mjlab</a>
* - **STS3215 (7.4V)**
- .. image:: ../_static/actuator_feetech_sts3215_7_4V.png
:width: 120px
- ``feetech_sts3215_7_4V``
- Feetech STS3215 servo-actuator (7.4V version)
(`Parameters <https://github.com/Rhoban/bam/tree/main/bam/params/feetech_sts3215_7_4V>`__,
`model <https://github.com/Rhoban/bam/blob/main/bam/feetech/actuator.py>`__,
`raw data <https://huggingface.co/buckets/Gregwar/bam_data/resolve/feetech_sts3215_raw.zip?download=true>`__)
.. raw:: html
<p class="mini-warning">⚠ Only the <code>m1</code> model was identified for this actuator.</p>
- .. raw:: html
<a class="sd-btn sd-btn-outline-primary motor-btn" href="mujoco_cpu.html?motor=feetech_sts3215_7_4V">Use in MuJoCo</a>
<a class="sd-btn sd-btn-outline-primary motor-btn" href="mjlab_gpu.html?motor=feetech_sts3215_7_4V">Use in mjlab</a>
+15
View File
@@ -0,0 +1,15 @@
Examples
========
BAM is used in real open-source robotics projects. If you are looking for
inspiration on how to wire it into a full pipeline, these are good places to
start:
- **mjlab (GPU)** — `MjLab Microban <https://github.com/MarcDcls/mjlab_microban>`_ is an
open-source project, in which the fully open-source 30 cm humanoid Microban
learns to walk. It shows how to plug :class:`bam.mjlab.BamActuatorCfg` into
a vectorized MuJoCo Warp training pipeline.
- **MuJoCo (CPU)** — `Microban <https://github.com/MarcDcls/microban>`_ is
the repository containing the CAD and codebase for the Microban humanoid.
It shows how to use :class:`bam.mujoco.MujocoController` in the MuJoCo
simulation of the robot run by ``make sim``.
+60
View File
@@ -0,0 +1,60 @@
Usage
=====
This section explains how to use identified models in simulation stacks.
.. grid:: 1 2 2 2
:gutter: 3
:class-container: bam-getting-started
.. grid-item-card:: 📇 Identified actuators
:link: actuators
:link-type: doc
:class-card: bam-card
Browse the bundled library of pre-identified servo-actuators and the
friction parameters shipped for each of them.
+++
:doc:`Browse actuators → <actuators>`
.. grid-item-card:: 🖥️ MuJoCo (CPU)
:link: mujoco_cpu
:link-type: doc
:class-card: bam-card
Plug BAM friction models into a standard MuJoCo simulation running on
CPU with :class:`bam.mujoco.MujocoController`.
+++
:doc:`MuJoCo CPU guide → <mujoco_cpu>`
.. grid-item-card:: ⚡ mjlab (MuJoCo GPU)
:link: mjlab_gpu
:link-type: doc
:class-card: bam-card
Integrate BAM into an mjlab pipeline running on GPU with MuJoCo Warp
via :class:`bam.mjlab.BamActuatorCfg`.
+++
:doc:`mjlab GPU guide → <mjlab_gpu>`
.. grid-item-card:: 🤖 Examples
:link: examples
:link-type: doc
:class-card: bam-card
See BAM wired into real open-source robotics projects for end-to-end
inspiration.
+++
:doc:`Browse examples → <examples>`
.. toctree::
:hidden:
actuators
mujoco_cpu
mjlab_gpu
examples
+212
View File
@@ -0,0 +1,212 @@
mjlab (MuJoCo GPU)
==================
This page explains how to integrate BAM friction models into an mjlab
pipeline running on GPU with MuJoCo Warp. The entry point is
:class:`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:
.. code-block:: text
pip install better-actuator-models[mjlab]
Or, with `uv <https://docs.astral.sh/uv/>`_:
.. code-block:: text
uv add "better-actuator-models[mjlab]"
BAM is compatible with mjlab 1.3.
Registering the BAM init event
------------------------------
First, be sure to register the ``bam_init`` function as a startup event in your environment:
.. code-block:: python
from bam.mjlab import bam_init
# cfg is your mjlab task configuration
cfg.events["bam_init"] = EventTermCfg(func=bam_init, mode="startup")
Instantiating the config
------------------------
:class:`~bam.mjlab.BamActuatorCfg` is a dataclass that plugs into mjlab's
actuator system. When an ``Entity`` is built, it instantiates a
:class:`~bam.mjlab.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.
Two approaches are available, mutually exclusive:
**Bundled motor:**
.. code-block:: python
from bam.mjlab import BamActuatorCfg
actuator_cfg = BamActuatorCfg(
motor_name="{actuator}",
model="m6",
target_names_expr=(r".*",),
)
**Custom JSON** (parameters produced by ``bam.fit``):
.. code-block:: python
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: see the :doc:`list of identified actuators <actuators>`.
- Supported model variants: ``"m1"`` through ``"m6"`` (see :doc:`../theory/models`).
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:
.. code-block:: python
actuator_cfg = BamActuatorCfg(
motor_name="{actuator}",
model="m6",
target_names_expr=(r".*",),
vin=7.5, # supply voltage [V]
kp_fw=125.0, # firmware P-gain
)
Domain randomization
--------------------
:class:`~bam.mjlab.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:
.. code-block:: python
actuator_cfg = BamActuatorCfg(
motor_name="{actuator}",
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 resistance** — model battery + cable resistance with a per-env
equivalent resistor between the battery and the motors:
.. math::
V_\text{eff} = V_\text{in} - R_\text{drop} \, I,
\qquad
I = \frac{1}{K_t} \sum_i |\tau_i|
where :math:`R_\text{drop}` (``vin_drop_resistance_range``) is the combined
battery + wire resistance in ohms, and the current :math:`I` is estimated from
the actuator torques using the torque constant :math:`K_t`. Randomizing this
resistance captures variability in cable length or connector quality across
units:
.. code-block:: python
actuator_cfg = BamActuatorCfg(
motor_name="{actuator}",
model="m6",
target_names_expr=(r".*",),
vin_range=(7.0, 8.0),
vin_drop_resistance_range=(0.05, 0.15), # [Ohm] ~100 mOhms of wire & battery resistance
vin_min=6.0, # hard lower bound [V]
)
Both ranges are sampled once at initialization and held constant across
episode resets.
.. warning::
The voltage drop is computed independently by each
:class:`~bam.mjlab.BamActuator` from its own joints' current draw. If several
actuator configs share the same physical battery, their currents are **not**
summed together, so the modeled drop underestimates the real one. Group all
joints powered by the same battery under a single :class:`~bam.mjlab.BamActuatorCfg`
if you need the shared-supply behavior. In the current implementation, only one model
can be used per actuator config, voltage drop will not function properly if different motors
are mixed in the same config.
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:
.. code-block:: python
actuator_cfg = BamActuatorCfg(
motor_name="{actuator}",
model="m6",
target_names_expr=(r".*",),
delay_min_lag=1, # always at least 1 step of delay
# = 5ms with mjlab's default 200Hz control loop
delay_max_lag=3, # up to 3 steps, randomized per env
# = 15ms with mjlab's default 200Hz control loop
)
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``:
.. code-block:: python
import mjlab
actuator_cfg = BamActuatorCfg(
motor_name="{actuator}",
model="m6",
target_names_expr=(r".*",),
kp_fw=125,
vin_range=(7.0, 8.0),
vin_drop_resistance_range=(0.05, 0.15), # [Ohm]
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 :meth:`~bam.mjlab.BamActuatorCfg.build` internally to create the
:class:`~bam.mjlab.BamActuator` and wire it into the simulation graph.
API reference
-------------
- :class:`bam.mjlab.BamActuatorCfg`
- :class:`bam.mjlab.BamActuator`
- :func:`bam.model.load_model`
+218
View File
@@ -0,0 +1,218 @@
MuJoCo (CPU)
============
This page explains how to plug BAM friction models into a standard MuJoCo
simulation running on CPU. The entry point is :class:`bam.mujoco.MujocoController`.
Installation
------------
BAM is available on PyPI. Install it with the ``mujoco`` extra to pull in the
MuJoCo dependency:
.. code-block:: text
pip install better-actuator-models[mujoco]
Or, with `uv <https://docs.astral.sh/uv/>`_:
.. code-block:: text
uv add "better-actuator-models[mujoco]"
Overview
--------
At each simulation step, :class:`~bam.mujoco.MujocoController` does three things:
1. Optionally lowers the supply voltage by a drop proportional to the previous
step's load, to model battery + cable resistance.
2. Computes the motor torque from a firmware-like P-controller — optionally
clipping it to the firmware current limit — and applies it via
``mj_data.ctrl``.
3. Evaluates the BAM friction model and writes the result into
``mj_model.dof_frictionloss`` and ``mj_model.dof_damping``.
Loading a model
---------------
Use :func:`bam.model.load_model` to obtain a :class:`~bam.model.Model` object.
Two approaches are available.
**Bundled motor** — the library ships identified parameters for a set of common
servos:
.. code-block:: python
from bam.model import load_model
model = load_model(motor_name="{actuator}", model="m6")
- Supported motor names: see the :doc:`list of identified actuators <actuators>`.
- Supported model variants: ``"m1"`` through ``"m6"`` (see :doc:`../theory/models`).
**Custom JSON** — parameters produced by your own identification run:
.. code-block:: python
model = load_model("path/to/params.json")
XML setup
---------
Each actuator must be declared as a ``motor`` in the MJCF file (not
``position`` or ``velocity``). BAM overwrites ``frictionloss``, ``damping``,
and ``armature`` at runtime, so any value set in the XML will be ignored.
.. code-block:: xml
<actuator>
<motor name="joint_1" joint="joint_1" gear="1"/>
...
<motor name="joint_n" joint="joint_n" gear="1"/>
</actuator>
Instantiating the controller
-----------------------------
.. code-block:: python
import mujoco
from bam.mujoco import MujocoController
mj_model = mujoco.MjModel.from_xml_file("robot.xml")
mj_data = mujoco.MjData(mj_model)
controller = MujocoController(
model=model,
actuator=["joint_1", ..., "joint_n"], # must match the motor name in the XML
mujoco_model=mj_model,
mujoco_data=mj_data,
)
The ``actuator`` argument can take a single string or a list of strings, which
allows the same motor model to drive multiple joints. Each string must
match the ``name`` attribute of the ``<motor>``.
Simulation loop
---------------
Inside the loop, call :meth:`~bam.mujoco.MujocoController.set_q_target` to
provide the desired joint angle, then :meth:`~bam.mujoco.MujocoController.update`
before every ``mj_step``:
.. code-block:: python
mujoco.mj_resetData(mj_model, mj_data)
joint_names = ["joint_1", ..., "joint_n"]
target_angles = [...]
while True:
for joint_name, target_angle in zip(joint_names, target_angles):
controller.set_q_target(joint_name, target_angle)
controller.update()
mujoco.mj_step(mj_model, mj_data)
Voltage drop (optional)
-----------------------
Real batteries and cables have an internal resistance that causes a voltage
drop under load. BAM models this as an equivalent resistor placed between the
battery and the motors:
.. math::
V_\text{eff} = V_\text{in} - R_\text{drop} \, I,
\qquad
I = \frac{1}{K_t} \sum_i |\tau_i|
where ``vin_drop_resistance`` is :math:`R_\text{drop}` (the combined battery +
wire resistance, in ohms), and the current :math:`I` is estimated from the
actuator torques using the torque constant :math:`K_t`, summed over all
controlled joints. A hard lower bound ``vin_min`` can be set to prevent the
effective voltage from collapsing under heavy load:
.. code-block:: python
controller = MujocoController(
model=model,
actuator=["joint_1", ..., "joint_n"],
mujoco_model=mj_model,
mujoco_data=mj_data,
vin_drop_resistance=0.1, # 100 mOhms of wire & battery resistance
vin_min=6.0, # [V]
)
.. warning::
The voltage drop is computed independently by each
:class:`~bam.mujoco.MujocoController` from its own joints' current draw.
If several controllers share the same physical battery, their currents are
**not** summed together, so the modeled drop underestimates the real one.
Group all joints powered by the same battery under a single controller if you
need the shared-supply behavior.
Multi-actuator config file
--------------------------
For robots with many joints, :func:`bam.mujoco.load_config` loads a JSON
configuration file that maps each group of joints to a model:
.. code-block:: python
from bam.mujoco import load_config
controllers, dof_to_controller = load_config(
path="config.json",
mujoco_model=mj_model,
mujoco_data=mj_data,
kp=125.0,
vin=7.5,
)
The config file has the following structure:
.. code-block:: json
{
"arm": {
"dofs": ["shoulder", "elbow"],
"model": {
"kt": 1.6224667906987444,
"R": 3.949433673232461,
"armature": 0.011951238325312509,
"friction_base": 0.09038677246291783,
"friction_viscous": 0.011691602145974832,
"model": "m1",
"actuator": "mx64"
},
"error_gain": 1.0,
"max_pwm": 885
},
"leg": {
"dofs": ["hip", "knee", "ankle"],
"model": {
"kt": 2.1913757006745245,
"R": 2.9649903987776804,
"armature": 0.026609234235148084,
"friction_base": 0.10352026623606064,
"friction_viscous": 0.03520238029013507,
"model": "m1",
"actuator": "mx106"
},
"error_gain": 1.0,
"max_pwm": 885
}
}
``controllers`` is a dict keyed by group name; ``dof_to_controller`` maps each
DOF name back to its group.
API reference
-------------
- :class:`bam.mujoco.MujocoController`
- :func:`bam.mujoco.load_config`
- :func:`bam.model.load_model`