"""MuJoCo built-in actuators.
This module provides actuators that use MuJoCo's native actuator implementations,
created programmatically via the MjSpec API.
"""
from __future__ import annotations
from dataclasses import dataclass
from typing import TYPE_CHECKING
import mujoco
import torch
from mjlab.actuator.actuator import (
Actuator,
ActuatorCfg,
ActuatorCmd,
TransmissionType,
)
from mjlab.utils.spec import (
create_motor_actuator,
create_muscle_actuator,
create_position_actuator,
create_velocity_actuator,
)
if TYPE_CHECKING:
from mjlab.entity import Entity
[docs]
@dataclass(kw_only=True)
class BuiltinPositionActuatorCfg(ActuatorCfg):
"""Configuration for MuJoCo built-in position actuator.
Under the hood, this creates a <position> actuator for each target and sets
the stiffness, damping and effort limits accordingly. It also modifies the target's
properties, namely armature and frictionloss.
"""
stiffness: float
"""PD proportional gain."""
damping: float
"""PD derivative gain."""
effort_limit: float | None = None
"""Maximum actuator force/torque. If None, no limit is applied."""
def __post_init__(self) -> None:
super().__post_init__()
if self.transmission_type == TransmissionType.SITE:
raise ValueError(
"BuiltinPositionActuatorCfg does not support SITE transmission. "
"Use BuiltinMotorActuatorCfg for site transmission."
)
[docs]
def build(
self, entity: Entity, target_ids: list[int], target_names: list[str]
) -> BuiltinPositionActuator:
return BuiltinPositionActuator(self, entity, target_ids, target_names)
[docs]
class BuiltinPositionActuator(Actuator[BuiltinPositionActuatorCfg]):
"""MuJoCo built-in position actuator."""
[docs]
def __init__(
self,
cfg: BuiltinPositionActuatorCfg,
entity: Entity,
target_ids: list[int],
target_names: list[str],
) -> None:
super().__init__(cfg, entity, target_ids, target_names)
[docs]
def edit_spec(self, spec: mujoco.MjSpec, target_names: list[str]) -> None:
# Add <position> actuator to spec, one per target.
for target_name in target_names:
actuator = create_position_actuator(
spec,
target_name,
stiffness=self.cfg.stiffness,
damping=self.cfg.damping,
effort_limit=self.cfg.effort_limit,
armature=self.cfg.armature,
frictionloss=self.cfg.frictionloss,
transmission_type=self.cfg.transmission_type,
)
self._mjs_actuators.append(actuator)
[docs]
def compute(self, cmd: ActuatorCmd) -> torch.Tensor:
return cmd.position_target
[docs]
@dataclass(kw_only=True)
class BuiltinMotorActuatorCfg(ActuatorCfg):
"""Configuration for MuJoCo built-in motor actuator.
Under the hood, this creates a <motor> actuator for each target and sets
its effort limit and gear ratio accordingly. It also modifies the target's
properties, namely armature and frictionloss.
"""
effort_limit: float
"""Maximum actuator effort."""
gear: float = 1.0
"""Actuator gear ratio."""
[docs]
def build(
self, entity: Entity, target_ids: list[int], target_names: list[str]
) -> BuiltinMotorActuator:
return BuiltinMotorActuator(self, entity, target_ids, target_names)
[docs]
class BuiltinMotorActuator(Actuator[BuiltinMotorActuatorCfg]):
"""MuJoCo built-in motor actuator."""
[docs]
def __init__(
self,
cfg: BuiltinMotorActuatorCfg,
entity: Entity,
target_ids: list[int],
target_names: list[str],
) -> None:
super().__init__(cfg, entity, target_ids, target_names)
[docs]
def edit_spec(self, spec: mujoco.MjSpec, target_names: list[str]) -> None:
# Add <motor> actuator to spec, one per target.
for target_name in target_names:
actuator = create_motor_actuator(
spec,
target_name,
effort_limit=self.cfg.effort_limit,
gear=self.cfg.gear,
armature=self.cfg.armature,
frictionloss=self.cfg.frictionloss,
transmission_type=self.cfg.transmission_type,
)
self._mjs_actuators.append(actuator)
[docs]
def compute(self, cmd: ActuatorCmd) -> torch.Tensor:
return cmd.effort_target
[docs]
@dataclass(kw_only=True)
class BuiltinVelocityActuatorCfg(ActuatorCfg):
"""Configuration for MuJoCo built-in velocity actuator.
Under the hood, this creates a <velocity> actuator for each target and sets the
damping gain. It also modifies the target's properties, namely armature and
frictionloss.
"""
damping: float
"""Damping gain."""
effort_limit: float | None = None
"""Maximum actuator force/torque. If None, no limit is applied."""
def __post_init__(self) -> None:
super().__post_init__()
if self.transmission_type == TransmissionType.SITE:
raise ValueError(
"BuiltinVelocityActuatorCfg does not support SITE transmission. "
"Use BuiltinMotorActuatorCfg for site transmission."
)
[docs]
def build(
self, entity: Entity, target_ids: list[int], target_names: list[str]
) -> BuiltinVelocityActuator:
return BuiltinVelocityActuator(self, entity, target_ids, target_names)
[docs]
class BuiltinVelocityActuator(Actuator[BuiltinVelocityActuatorCfg]):
"""MuJoCo built-in velocity actuator."""
[docs]
def __init__(
self,
cfg: BuiltinVelocityActuatorCfg,
entity: Entity,
target_ids: list[int],
target_names: list[str],
) -> None:
super().__init__(cfg, entity, target_ids, target_names)
[docs]
def edit_spec(self, spec: mujoco.MjSpec, target_names: list[str]) -> None:
# Add <velocity> actuator to spec, one per target.
for target_name in target_names:
actuator = create_velocity_actuator(
spec,
target_name,
damping=self.cfg.damping,
effort_limit=self.cfg.effort_limit,
armature=self.cfg.armature,
frictionloss=self.cfg.frictionloss,
transmission_type=self.cfg.transmission_type,
)
self._mjs_actuators.append(actuator)
[docs]
def compute(self, cmd: ActuatorCmd) -> torch.Tensor:
return cmd.velocity_target
[docs]
@dataclass(kw_only=True)
class BuiltinMuscleActuatorCfg(ActuatorCfg):
"""Configuration for MuJoCo built-in muscle actuator."""
length_range: tuple[float, float] = (0.0, 0.0)
"""Length range for muscle actuator."""
gear: float = 1.0
"""Gear ratio."""
timeconst: tuple[float, float] = (0.01, 0.04)
"""Activation and deactivation time constants."""
tausmooth: float = 0.0
"""Smoothing time constant."""
range: tuple[float, float] = (0.75, 1.05)
"""Operating range of normalized muscle length."""
force: float = -1.0
"""Peak force (if -1, defaults to scale * FLV)."""
scale: float = 200.0
"""Force scaling factor."""
lmin: float = 0.5
"""Minimum normalized muscle length."""
lmax: float = 1.6
"""Maximum normalized muscle length."""
vmax: float = 1.5
"""Maximum normalized muscle velocity."""
fpmax: float = 1.3
"""Passive force at lmax."""
fvmax: float = 1.2
"""Active force at -vmax."""
def __post_init__(self) -> None:
super().__post_init__()
if self.transmission_type == TransmissionType.SITE:
raise ValueError(
"BuiltinMuscleActuatorCfg does not support SITE transmission. "
"Use BuiltinMotorActuatorCfg for site transmission."
)
[docs]
def build(
self, entity: Entity, target_ids: list[int], target_names: list[str]
) -> BuiltinMuscleActuator:
return BuiltinMuscleActuator(self, entity, target_ids, target_names)
[docs]
class BuiltinMuscleActuator(Actuator[BuiltinMuscleActuatorCfg]):
"""MuJoCo built-in muscle actuator."""
[docs]
def __init__(
self,
cfg: BuiltinMuscleActuatorCfg,
entity: Entity,
target_ids: list[int],
target_names: list[str],
) -> None:
super().__init__(cfg, entity, target_ids, target_names)
[docs]
def edit_spec(self, spec: mujoco.MjSpec, target_names: list[str]) -> None:
# Add <muscle> actuator to spec, one per target.
for target_name in target_names:
actuator = create_muscle_actuator(
spec,
target_name,
length_range=self.cfg.length_range,
gear=self.cfg.gear,
timeconst=self.cfg.timeconst,
tausmooth=self.cfg.tausmooth,
range=self.cfg.range,
force=self.cfg.force,
scale=self.cfg.scale,
lmin=self.cfg.lmin,
lmax=self.cfg.lmax,
vmax=self.cfg.vmax,
fpmax=self.cfg.fpmax,
fvmax=self.cfg.fvmax,
transmission_type=self.cfg.transmission_type,
)
self._mjs_actuators.append(actuator)
[docs]
def compute(self, cmd: ActuatorCmd) -> torch.Tensor:
return cmd.effort_target