Using BAM in 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:: bash pip install 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="xl330", model="m6") Supported motor names: ``"xl320"``, ``"xl330"``, ``"mx106"``, ``"mx64"``, ``"erob80:50"``, ``"erob80:100"``. 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 ... 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 ````. 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) controller.reset(mj_data.qpos) 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) :meth:`~bam.mujoco.MujocoController.reset` should be called after every ``mj_resetData`` to clear the internal velocity and torque state. Voltage drop (optional) ----------------------- Real batteries and cables introduce a voltage drop proportional to the total current draw. BAM models this as: .. math:: V_\text{eff} = V_\text{in} - g_\text{drop} \sum_i |\tau_i| where :math:`g_\text{drop}` is ``vin_drop_gain`` (approximately :math:`R / K_t`) and the sum runs 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_gain=0.5, # [V/Nm] vin_min=6.0, # [V] ) Current clipping (optional) --------------------------- Servo firmwares can cap the motor current to protect the hardware. BAM reproduces this saturation with the ``max_current`` parameter: the motor current :math:`I = \tau / K_t` is clipped to ``[-max_current, max_current]``, which is equivalent to clipping the motor torque to :math:`\pm\,\texttt{max\_current}\cdot K_t`. .. code-block:: python controller = MujocoController( model=model, actuator=["joint_1", ..., "joint_n"], mujoco_model=mj_model, mujoco_data=mj_data, max_current=1.75, # firmware current limit [A] ) Leave it at ``None`` (default) to disable current clipping. 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`