Skip to content

Load

load

Load

Bases: MojoBaseModel, ABC

Abstract base for all load types. Subclass and implement resolve_ids and apply_load.

name instance-attribute

Python
name: str

Unique label used for telemetry column naming and duplicate-registration warnings.

active class-attribute instance-attribute

Python
active: bool = True

When False the load is suppressed.

resolve_ids abstractmethod

Python
resolve_ids(state: MjState) -> None

Cache any integer IDs from the compiled MuJoCo model.

Source code in src/mujoco_mojo/runtime/load.py
Python
@abstractmethod
def resolve_ids(self, state: MjState) -> None:
    """Cache any integer IDs from the compiled MuJoCo model."""

apply_load abstractmethod

Python
apply_load(state: MjState) -> None

Write the load contribution into the MuJoCo force buffers.

Source code in src/mujoco_mojo/runtime/load.py
Python
@abstractmethod
def apply_load(self, state: MjState) -> None:
    """Write the load contribution into the MuJoCo force buffers."""

get_visuals

Python
get_visuals(state: MjState) -> list[ArrowConfig]

Returns a list of arrow configurations for the renderer.

Source code in src/mujoco_mojo/runtime/load.py
Python
def get_visuals(self, state: MjState) -> list[ArrowConfig]:
    """Returns a list of arrow configurations for the renderer."""
    return []

SiteLoad

Bases: Load

Abstract base for Cartesian-space forcing functions applied at a named site. Subclass and implement calculate() to return (force_world, torque_world); the base class handles mj_applyFT, telemetry, and visualization.

action_site instance-attribute

Python
action_site: AnySite

Site where the force is applied. Its parent body receives the generalized force.

rel_to_site class-attribute instance-attribute

Python
rel_to_site: AnySite | None = None

Coordinate frame for force/torque components returned by calculate(). When None, components are in the world frame.

user_data class-attribute instance-attribute

Python
user_data: SerializeAsAny[UserData] | None = None

Optional strongly-typed payload accessible inside calculate(). Passed through unchanged each timestep.

force_length_scale class-attribute instance-attribute

Python
force_length_scale: float | None = None

Length multiplier for this load's force arrow(s), on top of MuJoCo's native scaling. None falls back to VisualizationSettings.force_length_scale.

force_width_scale class-attribute instance-attribute

Python
force_width_scale: float | None = None

Width multiplier for this load's force arrow(s), on top of MuJoCo's native scaling. None falls back to VisualizationSettings.force_width_scale.

torque_length_scale class-attribute instance-attribute

Python
torque_length_scale: float | None = None

Length multiplier for this load's torque arrow, on top of MuJoCo's native scaling. None falls back to VisualizationSettings.torque_length_scale.

torque_width_scale class-attribute instance-attribute

Python
torque_width_scale: float | None = None

Width multiplier for this load's torque arrow, on top of MuJoCo's native scaling. None falls back to VisualizationSettings.torque_width_scale.

name instance-attribute

Python
name: str

Unique label used for telemetry column naming and duplicate-registration warnings.

active class-attribute instance-attribute

Python
active: bool = True

When False the load is suppressed.

resolve_ids

Python
resolve_ids(state: MjState)

Caches the integer IDs from the compiled MuJoCo model.

Source code in src/mujoco_mojo/runtime/load.py
Python
def resolve_ids(self, state: MjState):
    """Caches the integer IDs from the compiled MuJoCo model."""
    self._vis = MujocoMojoSettings().visualization
    self.action_site.get_id(state.model)

    if self.rel_to_site:
        self.rel_to_site.get_id(state.model)

calculate abstractmethod

Python
calculate(state: MjState) -> tuple[ndarray, ndarray]

Calculate the force for the timestep.

Parameters:

Name Type Description Default
state MjState

The paired MuJoCo model and data instance.

required

Returns:

Type Description
tuple[ndarray, ndarray]

tuple[np.ndarray, np.ndarray]: The force and toque vector output.

Source code in src/mujoco_mojo/runtime/load.py
Python
@abstractmethod
def calculate(self, state: MjState) -> tuple[np.ndarray, np.ndarray]:
    """
    Calculate the force for the timestep.

    Args:
        state: The paired MuJoCo model and data instance.

    Returns:
        tuple[np.ndarray, np.ndarray]: The force and toque vector output.

    """

request

Python
request(
    signal_manager: SignalManager | None = None,
    channels: list[Literal["force", "torque"]]
    | dict[
        Literal["force", "torque"], dict[str, Any] | None
    ] = ["force", "torque"],
)

Registers specific channels for logging.

Channel Description Type
force applied force in the world frame xyzm
torque applied torque in the world frame xyzm

Each channel is posted under subgroups=(load_name, channel).

  • An xyzm is a cartesian vector, posted as 4 values (x, y, z, and its magnitude m).

Each signal is tagged with built-in dimension metadata for its channel (force as force, torque as torque).

If signal_manager is omitted, the SignalManager of the active RuntimeManager with block is used. If that RuntimeManager has no SignalManager configured, this is a no-op.

Parameters:

Name Type Description Default
signal_manager SignalManager | None

The signal manager to register the sampler with.

None
channels list[Literal['force', 'torque']] | dict[Literal['force', 'torque'], dict[str, Any] | None]

The load data channels to log. Pass a list to select channels, or a dict mapping channel name to metadata overrides (or None) to select channels and attach per-channel metadata in one step.

['force', 'torque']
Source code in src/mujoco_mojo/runtime/load.py
Python
def request(
    self,
    signal_manager: SignalManager | None = None,
    channels: list[Literal["force", "torque"]]
    | dict[Literal["force", "torque"], dict[str, Any] | None] = ["force", "torque"],
):
    """
    Registers specific channels for logging.

    | Channel  | Description                        | Type |
    |:---------|:-----------------------------------|:-----|
    | `force`  | applied force in the world frame   | xyzm |
    | `torque` | applied torque in the world frame  | xyzm |

    Each channel is posted under `subgroups=(load_name, channel)`.

    * An `xyzm` is a cartesian vector, posted as 4 values (`x`, `y`, `z`, and its magnitude `m`).

    Each signal is tagged with built-in `dimension` metadata for its channel (`force` as force, `torque` as torque).

    If `signal_manager` is omitted, the `SignalManager` of the active `RuntimeManager` `with` block is used. If that `RuntimeManager` has no `SignalManager` configured, this is a no-op.

    Args:
        signal_manager: The signal manager to register the sampler with.
        channels: The load data channels to log. Pass a list to select channels, or a dict mapping channel name to metadata overrides (or `None`) to select channels and attach per-channel metadata in one step.

    """
    from mujoco_mojo.runtime.signal_manager import resolve_signal_manager

    signal_manager = resolve_signal_manager(signal_manager)
    if signal_manager is None:
        return

    if isinstance(channels, dict):
        _meta = cast("dict[str, dict[str, Any] | None]", channels)
        channels = list(channels.keys())
    else:
        _meta = {}

    channel_metadata = {
        "force": dim(Dimension.FORCE),
        "torque": torque_metadata(),
    }

    def sample(state: MjState):
        for channel in channels:
            source = self._last_f if channel == "force" else self._last_t
            meta = merge_signal_metadata(
                channel_metadata.get(channel), channel, _meta, unit_system=state.us
            )

            # iterate through x, y, z, and magnitude (pop. pop.)
            for i, attr in enumerate("xyzm"):
                signal_manager.post(
                    value=float(source[i]) if self.active else 0.0,
                    category=SignalCategory.LOADS,
                    # nest the force/torque under the function name
                    subgroups=(f"{self.name}", channel),
                    attr=attr,
                    metadata=meta,
                )

    signal_manager.register_sampler(sample)

get_visuals

Python
get_visuals(state: MjState) -> list[ArrowConfig]

Returns a list of arrow configurations for the renderer.

Source code in src/mujoco_mojo/runtime/load.py
Python
def get_visuals(self, state: MjState) -> list[ArrowConfig]:
    """Returns a list of arrow configurations for the renderer."""
    if not self.active:
        return []

    visuals: list[ArrowConfig] = []
    action_pos = self.action_site.rt_pos(state)

    if self._last_f[3] > 1e-4 and self._vis.action_force:
        visuals.append(
            ArrowConfig(
                pos=action_pos,
                vec=self._last_f[:3],
                color=Color[self._vis.action_force].rgba,
                is_torque=False,
                length_scale=self.force_length_scale
                if self.force_length_scale is not None
                else self._vis.force_length_scale,
                width_scale=self.force_width_scale
                if self.force_width_scale is not None
                else self._vis.force_width_scale,
            )
        )

    if self._last_t[3] > 1e-4 and self._vis.torque:
        visuals.append(
            ArrowConfig(
                pos=action_pos,
                vec=self._last_t[:3],
                color=Color[self._vis.torque].rgba,
                is_torque=True,
                length_scale=self.torque_length_scale
                if self.torque_length_scale is not None
                else self._vis.torque_length_scale,
                width_scale=self.torque_width_scale
                if self.torque_width_scale is not None
                else self._vis.torque_width_scale,
            )
        )

    return visuals

PointToPointForce

Bases: SiteLoad

Scalar force along the line-of-sight between two sites, with an equal and opposite reaction on xtion_site. Use the class-method factories for standard spring formulations, or supply a custom magnitude_func.

xtion_site instance-attribute

Python
xtion_site: AnySite

Site that receives the equal-and-opposite reaction force. Named xtion to avoid ambiguity with rel_to_site.

magnitude_func instance-attribute

Python
magnitude_func: Callable[[UserData | None, MjState], float]

Callable (user_data, state) -> signed force magnitude (N). Positive pushes sites apart.

name instance-attribute

Python
name: str

Unique label used for telemetry column naming and duplicate-registration warnings.

active class-attribute instance-attribute

Python
active: bool = True

When False the load is suppressed.

action_site instance-attribute

Python
action_site: AnySite

Site where the force is applied. Its parent body receives the generalized force.

rel_to_site class-attribute instance-attribute

Python
rel_to_site: AnySite | None = None

Coordinate frame for force/torque components returned by calculate(). When None, components are in the world frame.

user_data class-attribute instance-attribute

Python
user_data: SerializeAsAny[UserData] | None = None

Optional strongly-typed payload accessible inside calculate(). Passed through unchanged each timestep.

force_length_scale class-attribute instance-attribute

Python
force_length_scale: float | None = None

Length multiplier for this load's force arrow(s), on top of MuJoCo's native scaling. None falls back to VisualizationSettings.force_length_scale.

force_width_scale class-attribute instance-attribute

Python
force_width_scale: float | None = None

Width multiplier for this load's force arrow(s), on top of MuJoCo's native scaling. None falls back to VisualizationSettings.force_width_scale.

torque_length_scale class-attribute instance-attribute

Python
torque_length_scale: float | None = None

Length multiplier for this load's torque arrow, on top of MuJoCo's native scaling. None falls back to VisualizationSettings.torque_length_scale.

torque_width_scale class-attribute instance-attribute

Python
torque_width_scale: float | None = None

Width multiplier for this load's torque arrow, on top of MuJoCo's native scaling. None falls back to VisualizationSettings.torque_width_scale.

resolve_ids

Python
resolve_ids(state: MjState)

Caches the integer IDs from the compiled MuJoCo model.

Source code in src/mujoco_mojo/runtime/load.py
Python
def resolve_ids(self, state: MjState):
    """Caches the integer IDs from the compiled MuJoCo model."""
    super().resolve_ids(state)
    self.xtion_site.get_id(state.model)
    self._r0_mag = self.action_site.rt_dm(self.xtion_site, state)
    on_resolve = getattr(self.magnitude_func, "on_resolve", None)
    if callable(on_resolve):
        on_resolve(self._r0_mag)

get_visuals

Python
get_visuals(state: MjState) -> list[ArrowConfig]

Returns a list of arrow configurations for the renderer.

Source code in src/mujoco_mojo/runtime/load.py
Python
def get_visuals(self, state: MjState) -> list[ArrowConfig]:
    """Returns a list of arrow configurations for the renderer."""
    if not self.active:
        return []

    visuals = super().get_visuals(state)

    if self._last_f[3] > 1e-4 and self._vis.reaction_force:
        xtion_pos = self.xtion_site.rt_pos(state)
        visuals.append(
            ArrowConfig(
                pos=xtion_pos,
                vec=-self._last_f[:3],
                color=Color[self._vis.reaction_force].rgba,
                is_torque=False,
                length_scale=self.force_length_scale
                if self.force_length_scale is not None
                else self._vis.force_length_scale,
                width_scale=self.force_width_scale
                if self.force_width_scale is not None
                else self._vis.force_width_scale,
            )
        )

    return visuals

ideal_spring classmethod

Python
ideal_spring(
    name: str,
    action_site: AnySite,
    xtion_site: AnySite,
    stiffness: float | NamedValue[float] = 0.0,
    damping: float | NamedValue[float] = 0.0,
    rest_length: float = 0.0,
) -> Self

Bidirectional spring-damper active in both tension and compression. F = -k*(d - d0) - c*v.

Source code in src/mujoco_mojo/runtime/load.py
Python
@classmethod
def ideal_spring(
    cls,
    name: str,
    action_site: AnySite,
    xtion_site: AnySite,
    stiffness: float | NamedValue[float] = 0.0,
    damping: float | NamedValue[float] = 0.0,
    rest_length: float = 0.0,
) -> Self:
    """Bidirectional spring-damper active in both tension and compression. `F = -k*(d - d0) - c*v`."""

    def logic(ud: UserData | None, state: MjState) -> float:
        dr = action_site.rt_displacements(xtion_site, state)
        dist = float(np.linalg.norm(dr))
        unit_vec = dr / dist if dist > 1e-9 else np.zeros(3)
        vel = float(
            np.dot(
                action_site.rt_velocities(xtion_site, state)[3:6],
                unit_vec,
            )
        )
        return _ideal_force_logic(dist, vel, stiffness, damping, rest_length)

    return cls(
        name=name,
        action_site=action_site,
        xtion_site=xtion_site,
        magnitude_func=logic,
    )

stroke_compression_spring classmethod

Python
stroke_compression_spring(
    name: str,
    action_site: AnySite,
    xtion_site: AnySite,
    stiffness: float | NamedValue[float] = 0.0,
    damping: float | NamedValue[float] = 0.0,
    preload: float | NamedValue[float] = 0.0,
    max_stroke: float | NamedValue[float] = 0.1,
) -> Self

Compression spring with a finite stroke; only active while the sites are compressed between rest and (rest + max_stroke). Useful for preloaded gas springs and mechanical end-stops.

Source code in src/mujoco_mojo/runtime/load.py
Python
@classmethod
def stroke_compression_spring(
    cls,
    name: str,
    action_site: AnySite,
    xtion_site: AnySite,
    stiffness: float | NamedValue[float] = 0.0,
    damping: float | NamedValue[float] = 0.0,
    preload: float | NamedValue[float] = 0.0,
    max_stroke: float | NamedValue[float] = 0.1,
) -> Self:
    """Compression spring with a finite stroke; only active while the sites are compressed between rest and (rest + max_stroke). Useful for preloaded gas springs and mechanical end-stops."""

    class _Logic:
        def __init__(self) -> None:
            self._r0: float = 0.0

        def on_resolve(self, r0: float) -> None:
            self._r0 = r0

        def __call__(self, ud: UserData | None, state: MjState) -> float:
            dr = action_site.rt_displacements(xtion_site, state)
            dist = float(np.linalg.norm(dr))
            unit_vec = dr / dist if dist > 1e-9 else np.zeros(3)
            vel = float(
                np.dot(
                    action_site.rt_velocities(xtion_site, state)[3:6],
                    unit_vec,
                )
            )

            k = stiffness.value if isinstance(stiffness, NamedValue) else stiffness
            c = damping.value if isinstance(damping, NamedValue) else damping
            f_0 = preload.value if isinstance(preload, NamedValue) else preload
            d_f = (
                max_stroke.value
                if isinstance(max_stroke, NamedValue)
                else max_stroke
            )

            delta_d = dist - self._r0
            if 0 <= delta_d <= d_f:
                f_mag = f_0 - (k * delta_d) - (c * vel)
                return max(0.0, f_mag)
            return 0.0

    logic = _Logic()

    return cls(
        name=name,
        action_site=action_site,
        xtion_site=xtion_site,
        magnitude_func=logic,
    )

compression_spring classmethod

Python
compression_spring(
    name: str,
    action_site: AnySite,
    xtion_site: AnySite,
    stiffness: float | NamedValue[float] = 0.0,
    damping: float | NamedValue[float] = 0.0,
    rest_length: float = 0.0,
) -> Self

Creates a spring-damper that only acts when compressed (dist < rest_length). Useful for bumpers, feet, push-off springs, or end-stops.

Source code in src/mujoco_mojo/runtime/load.py
Python
@classmethod
def compression_spring(
    cls,
    name: str,
    action_site: AnySite,
    xtion_site: AnySite,
    stiffness: float | NamedValue[float] = 0.0,
    damping: float | NamedValue[float] = 0.0,
    rest_length: float = 0.0,
) -> Self:
    """
    Creates a spring-damper that only acts when compressed (dist < rest_length). Useful for bumpers, feet, push-off springs, or end-stops.
    """

    def logic(ud: UserData | None, state: MjState) -> float:
        dr = action_site.rt_displacements(xtion_site, state)
        dist = float(np.linalg.norm(dr))
        unit_vec = dr / dist if dist > 1e-9 else np.zeros(3)
        vel = float(
            np.dot(
                action_site.rt_velocities(xtion_site, state)[3:6],
                unit_vec,
            )
        )
        if dist < rest_length:
            return _ideal_force_logic(dist, vel, stiffness, damping, rest_length)
        return 0.0

    return cls(
        name=name,
        action_site=action_site,
        xtion_site=xtion_site,
        magnitude_func=logic,
    )

tension_spring classmethod

Python
tension_spring(
    name: str,
    action_site: AnySite,
    xtion_site: AnySite,
    stiffness: float | NamedValue[float] = 0.0,
    damping: float | NamedValue[float] = 0.0,
    rest_length: float = 0.0,
) -> Self

Creates a spring-damper that only acts when extended (dist > rest_length). Useful for cables, bungees, or tendons.

Source code in src/mujoco_mojo/runtime/load.py
Python
@classmethod
def tension_spring(
    cls,
    name: str,
    action_site: AnySite,
    xtion_site: AnySite,
    stiffness: float | NamedValue[float] = 0.0,
    damping: float | NamedValue[float] = 0.0,
    rest_length: float = 0.0,
) -> Self:
    """
    Creates a spring-damper that only acts when extended (dist > rest_length). Useful for cables, bungees, or tendons.
    """

    def logic(ud: UserData | None, state: MjState) -> float:
        dr = action_site.rt_displacements(xtion_site, state)
        dist = float(np.linalg.norm(dr))
        unit_vec = dr / dist if dist > 1e-9 else np.zeros(3)
        vel = float(
            np.dot(
                action_site.rt_velocities(xtion_site, state)[3:6],
                unit_vec,
            )
        )
        if dist > rest_length:
            return _ideal_force_logic(dist, vel, stiffness, damping, rest_length)
        return 0.0

    return cls(
        name=name,
        action_site=action_site,
        xtion_site=xtion_site,
        magnitude_func=logic,
    )

request

Python
request(
    signal_manager: SignalManager | None = None,
    channels: list[Literal["force", "torque"]]
    | dict[
        Literal["force", "torque"], dict[str, Any] | None
    ] = ["force", "torque"],
)

Registers specific channels for logging.

Channel Description Type
force applied force in the world frame xyzm
torque applied torque in the world frame xyzm

Each channel is posted under subgroups=(load_name, channel).

  • An xyzm is a cartesian vector, posted as 4 values (x, y, z, and its magnitude m).

Each signal is tagged with built-in dimension metadata for its channel (force as force, torque as torque).

If signal_manager is omitted, the SignalManager of the active RuntimeManager with block is used. If that RuntimeManager has no SignalManager configured, this is a no-op.

Parameters:

Name Type Description Default
signal_manager SignalManager | None

The signal manager to register the sampler with.

None
channels list[Literal['force', 'torque']] | dict[Literal['force', 'torque'], dict[str, Any] | None]

The load data channels to log. Pass a list to select channels, or a dict mapping channel name to metadata overrides (or None) to select channels and attach per-channel metadata in one step.

['force', 'torque']
Source code in src/mujoco_mojo/runtime/load.py
Python
def request(
    self,
    signal_manager: SignalManager | None = None,
    channels: list[Literal["force", "torque"]]
    | dict[Literal["force", "torque"], dict[str, Any] | None] = ["force", "torque"],
):
    """
    Registers specific channels for logging.

    | Channel  | Description                        | Type |
    |:---------|:-----------------------------------|:-----|
    | `force`  | applied force in the world frame   | xyzm |
    | `torque` | applied torque in the world frame  | xyzm |

    Each channel is posted under `subgroups=(load_name, channel)`.

    * An `xyzm` is a cartesian vector, posted as 4 values (`x`, `y`, `z`, and its magnitude `m`).

    Each signal is tagged with built-in `dimension` metadata for its channel (`force` as force, `torque` as torque).

    If `signal_manager` is omitted, the `SignalManager` of the active `RuntimeManager` `with` block is used. If that `RuntimeManager` has no `SignalManager` configured, this is a no-op.

    Args:
        signal_manager: The signal manager to register the sampler with.
        channels: The load data channels to log. Pass a list to select channels, or a dict mapping channel name to metadata overrides (or `None`) to select channels and attach per-channel metadata in one step.

    """
    from mujoco_mojo.runtime.signal_manager import resolve_signal_manager

    signal_manager = resolve_signal_manager(signal_manager)
    if signal_manager is None:
        return

    if isinstance(channels, dict):
        _meta = cast("dict[str, dict[str, Any] | None]", channels)
        channels = list(channels.keys())
    else:
        _meta = {}

    channel_metadata = {
        "force": dim(Dimension.FORCE),
        "torque": torque_metadata(),
    }

    def sample(state: MjState):
        for channel in channels:
            source = self._last_f if channel == "force" else self._last_t
            meta = merge_signal_metadata(
                channel_metadata.get(channel), channel, _meta, unit_system=state.us
            )

            # iterate through x, y, z, and magnitude (pop. pop.)
            for i, attr in enumerate("xyzm"):
                signal_manager.post(
                    value=float(source[i]) if self.active else 0.0,
                    category=SignalCategory.LOADS,
                    # nest the force/torque under the function name
                    subgroups=(f"{self.name}", channel),
                    attr=attr,
                    metadata=meta,
                )

    signal_manager.register_sampler(sample)

BodyReactionForce

Bases: SiteLoad

Load that also applies an equal and opposite reaction to a second body. If xtion_body is None, only the action site receives the force.

xtion_body class-attribute instance-attribute

Python
xtion_body: Body | None = None

Body that receives the reaction force. When None, no reaction is applied and the force acts on the world.

name instance-attribute

Python
name: str

Unique label used for telemetry column naming and duplicate-registration warnings.

active class-attribute instance-attribute

Python
active: bool = True

When False the load is suppressed.

action_site instance-attribute

Python
action_site: AnySite

Site where the force is applied. Its parent body receives the generalized force.

rel_to_site class-attribute instance-attribute

Python
rel_to_site: AnySite | None = None

Coordinate frame for force/torque components returned by calculate(). When None, components are in the world frame.

user_data class-attribute instance-attribute

Python
user_data: SerializeAsAny[UserData] | None = None

Optional strongly-typed payload accessible inside calculate(). Passed through unchanged each timestep.

force_length_scale class-attribute instance-attribute

Python
force_length_scale: float | None = None

Length multiplier for this load's force arrow(s), on top of MuJoCo's native scaling. None falls back to VisualizationSettings.force_length_scale.

force_width_scale class-attribute instance-attribute

Python
force_width_scale: float | None = None

Width multiplier for this load's force arrow(s), on top of MuJoCo's native scaling. None falls back to VisualizationSettings.force_width_scale.

torque_length_scale class-attribute instance-attribute

Python
torque_length_scale: float | None = None

Length multiplier for this load's torque arrow, on top of MuJoCo's native scaling. None falls back to VisualizationSettings.torque_length_scale.

torque_width_scale class-attribute instance-attribute

Python
torque_width_scale: float | None = None

Width multiplier for this load's torque arrow, on top of MuJoCo's native scaling. None falls back to VisualizationSettings.torque_width_scale.

get_visuals

Python
get_visuals(state: MjState) -> list[ArrowConfig]

Returns a list of arrow configurations for the renderer.

Source code in src/mujoco_mojo/runtime/load.py
Python
def get_visuals(self, state: MjState) -> list[ArrowConfig]:
    """Returns a list of arrow configurations for the renderer."""
    if not self.active:
        return []

    visuals: list[ArrowConfig] = []
    action_pos = self.action_site.rt_pos(state)

    if self._last_f[3] > 1e-4 and self._vis.action_force:
        visuals.append(
            ArrowConfig(
                pos=action_pos,
                vec=self._last_f[:3],
                color=Color[self._vis.action_force].rgba,
                is_torque=False,
                length_scale=self.force_length_scale
                if self.force_length_scale is not None
                else self._vis.force_length_scale,
                width_scale=self.force_width_scale
                if self.force_width_scale is not None
                else self._vis.force_width_scale,
            )
        )

    if self._last_t[3] > 1e-4 and self._vis.torque:
        visuals.append(
            ArrowConfig(
                pos=action_pos,
                vec=self._last_t[:3],
                color=Color[self._vis.torque].rgba,
                is_torque=True,
                length_scale=self.torque_length_scale
                if self.torque_length_scale is not None
                else self._vis.torque_length_scale,
                width_scale=self.torque_width_scale
                if self.torque_width_scale is not None
                else self._vis.torque_width_scale,
            )
        )

    return visuals

calculate abstractmethod

Python
calculate(state: MjState) -> tuple[ndarray, ndarray]

Calculate the force for the timestep.

Parameters:

Name Type Description Default
state MjState

The paired MuJoCo model and data instance.

required

Returns:

Type Description
tuple[ndarray, ndarray]

tuple[np.ndarray, np.ndarray]: The force and toque vector output.

Source code in src/mujoco_mojo/runtime/load.py
Python
@abstractmethod
def calculate(self, state: MjState) -> tuple[np.ndarray, np.ndarray]:
    """
    Calculate the force for the timestep.

    Args:
        state: The paired MuJoCo model and data instance.

    Returns:
        tuple[np.ndarray, np.ndarray]: The force and toque vector output.

    """

request

Python
request(
    signal_manager: SignalManager | None = None,
    channels: list[Literal["force", "torque"]]
    | dict[
        Literal["force", "torque"], dict[str, Any] | None
    ] = ["force", "torque"],
)

Registers specific channels for logging.

Channel Description Type
force applied force in the world frame xyzm
torque applied torque in the world frame xyzm

Each channel is posted under subgroups=(load_name, channel).

  • An xyzm is a cartesian vector, posted as 4 values (x, y, z, and its magnitude m).

Each signal is tagged with built-in dimension metadata for its channel (force as force, torque as torque).

If signal_manager is omitted, the SignalManager of the active RuntimeManager with block is used. If that RuntimeManager has no SignalManager configured, this is a no-op.

Parameters:

Name Type Description Default
signal_manager SignalManager | None

The signal manager to register the sampler with.

None
channels list[Literal['force', 'torque']] | dict[Literal['force', 'torque'], dict[str, Any] | None]

The load data channels to log. Pass a list to select channels, or a dict mapping channel name to metadata overrides (or None) to select channels and attach per-channel metadata in one step.

['force', 'torque']
Source code in src/mujoco_mojo/runtime/load.py
Python
def request(
    self,
    signal_manager: SignalManager | None = None,
    channels: list[Literal["force", "torque"]]
    | dict[Literal["force", "torque"], dict[str, Any] | None] = ["force", "torque"],
):
    """
    Registers specific channels for logging.

    | Channel  | Description                        | Type |
    |:---------|:-----------------------------------|:-----|
    | `force`  | applied force in the world frame   | xyzm |
    | `torque` | applied torque in the world frame  | xyzm |

    Each channel is posted under `subgroups=(load_name, channel)`.

    * An `xyzm` is a cartesian vector, posted as 4 values (`x`, `y`, `z`, and its magnitude `m`).

    Each signal is tagged with built-in `dimension` metadata for its channel (`force` as force, `torque` as torque).

    If `signal_manager` is omitted, the `SignalManager` of the active `RuntimeManager` `with` block is used. If that `RuntimeManager` has no `SignalManager` configured, this is a no-op.

    Args:
        signal_manager: The signal manager to register the sampler with.
        channels: The load data channels to log. Pass a list to select channels, or a dict mapping channel name to metadata overrides (or `None`) to select channels and attach per-channel metadata in one step.

    """
    from mujoco_mojo.runtime.signal_manager import resolve_signal_manager

    signal_manager = resolve_signal_manager(signal_manager)
    if signal_manager is None:
        return

    if isinstance(channels, dict):
        _meta = cast("dict[str, dict[str, Any] | None]", channels)
        channels = list(channels.keys())
    else:
        _meta = {}

    channel_metadata = {
        "force": dim(Dimension.FORCE),
        "torque": torque_metadata(),
    }

    def sample(state: MjState):
        for channel in channels:
            source = self._last_f if channel == "force" else self._last_t
            meta = merge_signal_metadata(
                channel_metadata.get(channel), channel, _meta, unit_system=state.us
            )

            # iterate through x, y, z, and magnitude (pop. pop.)
            for i, attr in enumerate("xyzm"):
                signal_manager.post(
                    value=float(source[i]) if self.active else 0.0,
                    category=SignalCategory.LOADS,
                    # nest the force/torque under the function name
                    subgroups=(f"{self.name}", channel),
                    attr=attr,
                    metadata=meta,
                )

    signal_manager.register_sampler(sample)

ScalarForce

Bases: BodyReactionForce

Force along the local X-axis of action_site, scaled by scalar_func each timestep.

scalar_func class-attribute instance-attribute

Python
scalar_func: Callable[[UserData | None, MjState], float] = (
    lambda ud, s: 0.0
)

Callable (user_data, state) -> signed force magnitude (N). Positive is along the site's +X axis.

name instance-attribute

Python
name: str

Unique label used for telemetry column naming and duplicate-registration warnings.

active class-attribute instance-attribute

Python
active: bool = True

When False the load is suppressed.

action_site instance-attribute

Python
action_site: AnySite

Site where the force is applied. Its parent body receives the generalized force.

rel_to_site class-attribute instance-attribute

Python
rel_to_site: AnySite | None = None

Coordinate frame for force/torque components returned by calculate(). When None, components are in the world frame.

user_data class-attribute instance-attribute

Python
user_data: SerializeAsAny[UserData] | None = None

Optional strongly-typed payload accessible inside calculate(). Passed through unchanged each timestep.

force_length_scale class-attribute instance-attribute

Python
force_length_scale: float | None = None

Length multiplier for this load's force arrow(s), on top of MuJoCo's native scaling. None falls back to VisualizationSettings.force_length_scale.

force_width_scale class-attribute instance-attribute

Python
force_width_scale: float | None = None

Width multiplier for this load's force arrow(s), on top of MuJoCo's native scaling. None falls back to VisualizationSettings.force_width_scale.

torque_length_scale class-attribute instance-attribute

Python
torque_length_scale: float | None = None

Length multiplier for this load's torque arrow, on top of MuJoCo's native scaling. None falls back to VisualizationSettings.torque_length_scale.

torque_width_scale class-attribute instance-attribute

Python
torque_width_scale: float | None = None

Width multiplier for this load's torque arrow, on top of MuJoCo's native scaling. None falls back to VisualizationSettings.torque_width_scale.

xtion_body class-attribute instance-attribute

Python
xtion_body: Body | None = None

Body that receives the reaction force. When None, no reaction is applied and the force acts on the world.

get_visuals

Python
get_visuals(state: MjState) -> list[ArrowConfig]

Returns a list of arrow configurations for the renderer.

Source code in src/mujoco_mojo/runtime/load.py
Python
def get_visuals(self, state: MjState) -> list[ArrowConfig]:
    """Returns a list of arrow configurations for the renderer."""
    if not self.active:
        return []

    visuals: list[ArrowConfig] = []
    action_pos = self.action_site.rt_pos(state)

    if self._last_f[3] > 1e-4 and self._vis.action_force:
        visuals.append(
            ArrowConfig(
                pos=action_pos,
                vec=self._last_f[:3],
                color=Color[self._vis.action_force].rgba,
                is_torque=False,
                length_scale=self.force_length_scale
                if self.force_length_scale is not None
                else self._vis.force_length_scale,
                width_scale=self.force_width_scale
                if self.force_width_scale is not None
                else self._vis.force_width_scale,
            )
        )

    if self._last_t[3] > 1e-4 and self._vis.torque:
        visuals.append(
            ArrowConfig(
                pos=action_pos,
                vec=self._last_t[:3],
                color=Color[self._vis.torque].rgba,
                is_torque=True,
                length_scale=self.torque_length_scale
                if self.torque_length_scale is not None
                else self._vis.torque_length_scale,
                width_scale=self.torque_width_scale
                if self.torque_width_scale is not None
                else self._vis.torque_width_scale,
            )
        )

    return visuals

request

Python
request(
    signal_manager: SignalManager | None = None,
    channels: list[Literal["force", "torque"]]
    | dict[
        Literal["force", "torque"], dict[str, Any] | None
    ] = ["force", "torque"],
)

Registers specific channels for logging.

Channel Description Type
force applied force in the world frame xyzm
torque applied torque in the world frame xyzm

Each channel is posted under subgroups=(load_name, channel).

  • An xyzm is a cartesian vector, posted as 4 values (x, y, z, and its magnitude m).

Each signal is tagged with built-in dimension metadata for its channel (force as force, torque as torque).

If signal_manager is omitted, the SignalManager of the active RuntimeManager with block is used. If that RuntimeManager has no SignalManager configured, this is a no-op.

Parameters:

Name Type Description Default
signal_manager SignalManager | None

The signal manager to register the sampler with.

None
channels list[Literal['force', 'torque']] | dict[Literal['force', 'torque'], dict[str, Any] | None]

The load data channels to log. Pass a list to select channels, or a dict mapping channel name to metadata overrides (or None) to select channels and attach per-channel metadata in one step.

['force', 'torque']
Source code in src/mujoco_mojo/runtime/load.py
Python
def request(
    self,
    signal_manager: SignalManager | None = None,
    channels: list[Literal["force", "torque"]]
    | dict[Literal["force", "torque"], dict[str, Any] | None] = ["force", "torque"],
):
    """
    Registers specific channels for logging.

    | Channel  | Description                        | Type |
    |:---------|:-----------------------------------|:-----|
    | `force`  | applied force in the world frame   | xyzm |
    | `torque` | applied torque in the world frame  | xyzm |

    Each channel is posted under `subgroups=(load_name, channel)`.

    * An `xyzm` is a cartesian vector, posted as 4 values (`x`, `y`, `z`, and its magnitude `m`).

    Each signal is tagged with built-in `dimension` metadata for its channel (`force` as force, `torque` as torque).

    If `signal_manager` is omitted, the `SignalManager` of the active `RuntimeManager` `with` block is used. If that `RuntimeManager` has no `SignalManager` configured, this is a no-op.

    Args:
        signal_manager: The signal manager to register the sampler with.
        channels: The load data channels to log. Pass a list to select channels, or a dict mapping channel name to metadata overrides (or `None`) to select channels and attach per-channel metadata in one step.

    """
    from mujoco_mojo.runtime.signal_manager import resolve_signal_manager

    signal_manager = resolve_signal_manager(signal_manager)
    if signal_manager is None:
        return

    if isinstance(channels, dict):
        _meta = cast("dict[str, dict[str, Any] | None]", channels)
        channels = list(channels.keys())
    else:
        _meta = {}

    channel_metadata = {
        "force": dim(Dimension.FORCE),
        "torque": torque_metadata(),
    }

    def sample(state: MjState):
        for channel in channels:
            source = self._last_f if channel == "force" else self._last_t
            meta = merge_signal_metadata(
                channel_metadata.get(channel), channel, _meta, unit_system=state.us
            )

            # iterate through x, y, z, and magnitude (pop. pop.)
            for i, attr in enumerate("xyzm"):
                signal_manager.post(
                    value=float(source[i]) if self.active else 0.0,
                    category=SignalCategory.LOADS,
                    # nest the force/torque under the function name
                    subgroups=(f"{self.name}", channel),
                    attr=attr,
                    metadata=meta,
                )

    signal_manager.register_sampler(sample)

ScalarTorque

Bases: BodyReactionForce

Torque about the local X-axis of action_site, scaled by scalar_func each timestep.

scalar_func class-attribute instance-attribute

Python
scalar_func: Callable[[UserData | None, MjState], float] = (
    lambda ud, s: 0.0
)

Callable (user_data, state) -> signed torque magnitude. Positive follows the right-hand rule about the site's +X axis.

name instance-attribute

Python
name: str

Unique label used for telemetry column naming and duplicate-registration warnings.

active class-attribute instance-attribute

Python
active: bool = True

When False the load is suppressed.

action_site instance-attribute

Python
action_site: AnySite

Site where the force is applied. Its parent body receives the generalized force.

rel_to_site class-attribute instance-attribute

Python
rel_to_site: AnySite | None = None

Coordinate frame for force/torque components returned by calculate(). When None, components are in the world frame.

user_data class-attribute instance-attribute

Python
user_data: SerializeAsAny[UserData] | None = None

Optional strongly-typed payload accessible inside calculate(). Passed through unchanged each timestep.

force_length_scale class-attribute instance-attribute

Python
force_length_scale: float | None = None

Length multiplier for this load's force arrow(s), on top of MuJoCo's native scaling. None falls back to VisualizationSettings.force_length_scale.

force_width_scale class-attribute instance-attribute

Python
force_width_scale: float | None = None

Width multiplier for this load's force arrow(s), on top of MuJoCo's native scaling. None falls back to VisualizationSettings.force_width_scale.

torque_length_scale class-attribute instance-attribute

Python
torque_length_scale: float | None = None

Length multiplier for this load's torque arrow, on top of MuJoCo's native scaling. None falls back to VisualizationSettings.torque_length_scale.

torque_width_scale class-attribute instance-attribute

Python
torque_width_scale: float | None = None

Width multiplier for this load's torque arrow, on top of MuJoCo's native scaling. None falls back to VisualizationSettings.torque_width_scale.

xtion_body class-attribute instance-attribute

Python
xtion_body: Body | None = None

Body that receives the reaction force. When None, no reaction is applied and the force acts on the world.

get_visuals

Python
get_visuals(state: MjState) -> list[ArrowConfig]

Returns a list of arrow configurations for the renderer.

Source code in src/mujoco_mojo/runtime/load.py
Python
def get_visuals(self, state: MjState) -> list[ArrowConfig]:
    """Returns a list of arrow configurations for the renderer."""
    if not self.active:
        return []

    visuals: list[ArrowConfig] = []
    action_pos = self.action_site.rt_pos(state)

    if self._last_f[3] > 1e-4 and self._vis.action_force:
        visuals.append(
            ArrowConfig(
                pos=action_pos,
                vec=self._last_f[:3],
                color=Color[self._vis.action_force].rgba,
                is_torque=False,
                length_scale=self.force_length_scale
                if self.force_length_scale is not None
                else self._vis.force_length_scale,
                width_scale=self.force_width_scale
                if self.force_width_scale is not None
                else self._vis.force_width_scale,
            )
        )

    if self._last_t[3] > 1e-4 and self._vis.torque:
        visuals.append(
            ArrowConfig(
                pos=action_pos,
                vec=self._last_t[:3],
                color=Color[self._vis.torque].rgba,
                is_torque=True,
                length_scale=self.torque_length_scale
                if self.torque_length_scale is not None
                else self._vis.torque_length_scale,
                width_scale=self.torque_width_scale
                if self.torque_width_scale is not None
                else self._vis.torque_width_scale,
            )
        )

    return visuals

request

Python
request(
    signal_manager: SignalManager | None = None,
    channels: list[Literal["force", "torque"]]
    | dict[
        Literal["force", "torque"], dict[str, Any] | None
    ] = ["force", "torque"],
)

Registers specific channels for logging.

Channel Description Type
force applied force in the world frame xyzm
torque applied torque in the world frame xyzm

Each channel is posted under subgroups=(load_name, channel).

  • An xyzm is a cartesian vector, posted as 4 values (x, y, z, and its magnitude m).

Each signal is tagged with built-in dimension metadata for its channel (force as force, torque as torque).

If signal_manager is omitted, the SignalManager of the active RuntimeManager with block is used. If that RuntimeManager has no SignalManager configured, this is a no-op.

Parameters:

Name Type Description Default
signal_manager SignalManager | None

The signal manager to register the sampler with.

None
channels list[Literal['force', 'torque']] | dict[Literal['force', 'torque'], dict[str, Any] | None]

The load data channels to log. Pass a list to select channels, or a dict mapping channel name to metadata overrides (or None) to select channels and attach per-channel metadata in one step.

['force', 'torque']
Source code in src/mujoco_mojo/runtime/load.py
Python
def request(
    self,
    signal_manager: SignalManager | None = None,
    channels: list[Literal["force", "torque"]]
    | dict[Literal["force", "torque"], dict[str, Any] | None] = ["force", "torque"],
):
    """
    Registers specific channels for logging.

    | Channel  | Description                        | Type |
    |:---------|:-----------------------------------|:-----|
    | `force`  | applied force in the world frame   | xyzm |
    | `torque` | applied torque in the world frame  | xyzm |

    Each channel is posted under `subgroups=(load_name, channel)`.

    * An `xyzm` is a cartesian vector, posted as 4 values (`x`, `y`, `z`, and its magnitude `m`).

    Each signal is tagged with built-in `dimension` metadata for its channel (`force` as force, `torque` as torque).

    If `signal_manager` is omitted, the `SignalManager` of the active `RuntimeManager` `with` block is used. If that `RuntimeManager` has no `SignalManager` configured, this is a no-op.

    Args:
        signal_manager: The signal manager to register the sampler with.
        channels: The load data channels to log. Pass a list to select channels, or a dict mapping channel name to metadata overrides (or `None`) to select channels and attach per-channel metadata in one step.

    """
    from mujoco_mojo.runtime.signal_manager import resolve_signal_manager

    signal_manager = resolve_signal_manager(signal_manager)
    if signal_manager is None:
        return

    if isinstance(channels, dict):
        _meta = cast("dict[str, dict[str, Any] | None]", channels)
        channels = list(channels.keys())
    else:
        _meta = {}

    channel_metadata = {
        "force": dim(Dimension.FORCE),
        "torque": torque_metadata(),
    }

    def sample(state: MjState):
        for channel in channels:
            source = self._last_f if channel == "force" else self._last_t
            meta = merge_signal_metadata(
                channel_metadata.get(channel), channel, _meta, unit_system=state.us
            )

            # iterate through x, y, z, and magnitude (pop. pop.)
            for i, attr in enumerate("xyzm"):
                signal_manager.post(
                    value=float(source[i]) if self.active else 0.0,
                    category=SignalCategory.LOADS,
                    # nest the force/torque under the function name
                    subgroups=(f"{self.name}", channel),
                    attr=attr,
                    metadata=meta,
                )

    signal_manager.register_sampler(sample)

VectorForce

Bases: BodyReactionForce

3-axis force with independent per-axis callables. Components are expressed in rel_to_site frame, or world frame if None.

fx class-attribute instance-attribute

Python
fx: Callable[[UserData | None, MjState], float] = (
    lambda ud, s: 0.0
)

Callable (user_data, state) -> X-axis force component (N).

fy class-attribute instance-attribute

Python
fy: Callable[[UserData | None, MjState], float] = (
    lambda ud, s: 0.0
)

Callable (user_data, state) -> Y-axis force component (N).

fz class-attribute instance-attribute

Python
fz: Callable[[UserData | None, MjState], float] = (
    lambda ud, s: 0.0
)

Callable (user_data, state) -> Z-axis force component (N).

name instance-attribute

Python
name: str

Unique label used for telemetry column naming and duplicate-registration warnings.

active class-attribute instance-attribute

Python
active: bool = True

When False the load is suppressed.

action_site instance-attribute

Python
action_site: AnySite

Site where the force is applied. Its parent body receives the generalized force.

rel_to_site class-attribute instance-attribute

Python
rel_to_site: AnySite | None = None

Coordinate frame for force/torque components returned by calculate(). When None, components are in the world frame.

user_data class-attribute instance-attribute

Python
user_data: SerializeAsAny[UserData] | None = None

Optional strongly-typed payload accessible inside calculate(). Passed through unchanged each timestep.

force_length_scale class-attribute instance-attribute

Python
force_length_scale: float | None = None

Length multiplier for this load's force arrow(s), on top of MuJoCo's native scaling. None falls back to VisualizationSettings.force_length_scale.

force_width_scale class-attribute instance-attribute

Python
force_width_scale: float | None = None

Width multiplier for this load's force arrow(s), on top of MuJoCo's native scaling. None falls back to VisualizationSettings.force_width_scale.

torque_length_scale class-attribute instance-attribute

Python
torque_length_scale: float | None = None

Length multiplier for this load's torque arrow, on top of MuJoCo's native scaling. None falls back to VisualizationSettings.torque_length_scale.

torque_width_scale class-attribute instance-attribute

Python
torque_width_scale: float | None = None

Width multiplier for this load's torque arrow, on top of MuJoCo's native scaling. None falls back to VisualizationSettings.torque_width_scale.

xtion_body class-attribute instance-attribute

Python
xtion_body: Body | None = None

Body that receives the reaction force. When None, no reaction is applied and the force acts on the world.

get_visuals

Python
get_visuals(state: MjState) -> list[ArrowConfig]

Returns a list of arrow configurations for the renderer.

Source code in src/mujoco_mojo/runtime/load.py
Python
def get_visuals(self, state: MjState) -> list[ArrowConfig]:
    """Returns a list of arrow configurations for the renderer."""
    if not self.active:
        return []

    visuals: list[ArrowConfig] = []
    action_pos = self.action_site.rt_pos(state)

    if self._last_f[3] > 1e-4 and self._vis.action_force:
        visuals.append(
            ArrowConfig(
                pos=action_pos,
                vec=self._last_f[:3],
                color=Color[self._vis.action_force].rgba,
                is_torque=False,
                length_scale=self.force_length_scale
                if self.force_length_scale is not None
                else self._vis.force_length_scale,
                width_scale=self.force_width_scale
                if self.force_width_scale is not None
                else self._vis.force_width_scale,
            )
        )

    if self._last_t[3] > 1e-4 and self._vis.torque:
        visuals.append(
            ArrowConfig(
                pos=action_pos,
                vec=self._last_t[:3],
                color=Color[self._vis.torque].rgba,
                is_torque=True,
                length_scale=self.torque_length_scale
                if self.torque_length_scale is not None
                else self._vis.torque_length_scale,
                width_scale=self.torque_width_scale
                if self.torque_width_scale is not None
                else self._vis.torque_width_scale,
            )
        )

    return visuals

request

Python
request(
    signal_manager: SignalManager | None = None,
    channels: list[Literal["force", "torque"]]
    | dict[
        Literal["force", "torque"], dict[str, Any] | None
    ] = ["force", "torque"],
)

Registers specific channels for logging.

Channel Description Type
force applied force in the world frame xyzm
torque applied torque in the world frame xyzm

Each channel is posted under subgroups=(load_name, channel).

  • An xyzm is a cartesian vector, posted as 4 values (x, y, z, and its magnitude m).

Each signal is tagged with built-in dimension metadata for its channel (force as force, torque as torque).

If signal_manager is omitted, the SignalManager of the active RuntimeManager with block is used. If that RuntimeManager has no SignalManager configured, this is a no-op.

Parameters:

Name Type Description Default
signal_manager SignalManager | None

The signal manager to register the sampler with.

None
channels list[Literal['force', 'torque']] | dict[Literal['force', 'torque'], dict[str, Any] | None]

The load data channels to log. Pass a list to select channels, or a dict mapping channel name to metadata overrides (or None) to select channels and attach per-channel metadata in one step.

['force', 'torque']
Source code in src/mujoco_mojo/runtime/load.py
Python
def request(
    self,
    signal_manager: SignalManager | None = None,
    channels: list[Literal["force", "torque"]]
    | dict[Literal["force", "torque"], dict[str, Any] | None] = ["force", "torque"],
):
    """
    Registers specific channels for logging.

    | Channel  | Description                        | Type |
    |:---------|:-----------------------------------|:-----|
    | `force`  | applied force in the world frame   | xyzm |
    | `torque` | applied torque in the world frame  | xyzm |

    Each channel is posted under `subgroups=(load_name, channel)`.

    * An `xyzm` is a cartesian vector, posted as 4 values (`x`, `y`, `z`, and its magnitude `m`).

    Each signal is tagged with built-in `dimension` metadata for its channel (`force` as force, `torque` as torque).

    If `signal_manager` is omitted, the `SignalManager` of the active `RuntimeManager` `with` block is used. If that `RuntimeManager` has no `SignalManager` configured, this is a no-op.

    Args:
        signal_manager: The signal manager to register the sampler with.
        channels: The load data channels to log. Pass a list to select channels, or a dict mapping channel name to metadata overrides (or `None`) to select channels and attach per-channel metadata in one step.

    """
    from mujoco_mojo.runtime.signal_manager import resolve_signal_manager

    signal_manager = resolve_signal_manager(signal_manager)
    if signal_manager is None:
        return

    if isinstance(channels, dict):
        _meta = cast("dict[str, dict[str, Any] | None]", channels)
        channels = list(channels.keys())
    else:
        _meta = {}

    channel_metadata = {
        "force": dim(Dimension.FORCE),
        "torque": torque_metadata(),
    }

    def sample(state: MjState):
        for channel in channels:
            source = self._last_f if channel == "force" else self._last_t
            meta = merge_signal_metadata(
                channel_metadata.get(channel), channel, _meta, unit_system=state.us
            )

            # iterate through x, y, z, and magnitude (pop. pop.)
            for i, attr in enumerate("xyzm"):
                signal_manager.post(
                    value=float(source[i]) if self.active else 0.0,
                    category=SignalCategory.LOADS,
                    # nest the force/torque under the function name
                    subgroups=(f"{self.name}", channel),
                    attr=attr,
                    metadata=meta,
                )

    signal_manager.register_sampler(sample)

VectorTorque

Bases: BodyReactionForce

3-axis torque with independent per-axis callables. Components are expressed in rel_to_site frame, or world frame if None.

tx class-attribute instance-attribute

Python
tx: Callable[[UserData | None, MjState], float] = (
    lambda ud, s: 0.0
)

Callable (user_data, state) -> X-axis torque component.

ty class-attribute instance-attribute

Python
ty: Callable[[UserData | None, MjState], float] = (
    lambda ud, s: 0.0
)

Callable (user_data, state) -> Y-axis torque component.

tz class-attribute instance-attribute

Python
tz: Callable[[UserData | None, MjState], float] = (
    lambda ud, s: 0.0
)

Callable (user_data, state) -> Z-axis torque component.

name instance-attribute

Python
name: str

Unique label used for telemetry column naming and duplicate-registration warnings.

active class-attribute instance-attribute

Python
active: bool = True

When False the load is suppressed.

action_site instance-attribute

Python
action_site: AnySite

Site where the force is applied. Its parent body receives the generalized force.

rel_to_site class-attribute instance-attribute

Python
rel_to_site: AnySite | None = None

Coordinate frame for force/torque components returned by calculate(). When None, components are in the world frame.

user_data class-attribute instance-attribute

Python
user_data: SerializeAsAny[UserData] | None = None

Optional strongly-typed payload accessible inside calculate(). Passed through unchanged each timestep.

force_length_scale class-attribute instance-attribute

Python
force_length_scale: float | None = None

Length multiplier for this load's force arrow(s), on top of MuJoCo's native scaling. None falls back to VisualizationSettings.force_length_scale.

force_width_scale class-attribute instance-attribute

Python
force_width_scale: float | None = None

Width multiplier for this load's force arrow(s), on top of MuJoCo's native scaling. None falls back to VisualizationSettings.force_width_scale.

torque_length_scale class-attribute instance-attribute

Python
torque_length_scale: float | None = None

Length multiplier for this load's torque arrow, on top of MuJoCo's native scaling. None falls back to VisualizationSettings.torque_length_scale.

torque_width_scale class-attribute instance-attribute

Python
torque_width_scale: float | None = None

Width multiplier for this load's torque arrow, on top of MuJoCo's native scaling. None falls back to VisualizationSettings.torque_width_scale.

xtion_body class-attribute instance-attribute

Python
xtion_body: Body | None = None

Body that receives the reaction force. When None, no reaction is applied and the force acts on the world.

get_visuals

Python
get_visuals(state: MjState) -> list[ArrowConfig]

Returns a list of arrow configurations for the renderer.

Source code in src/mujoco_mojo/runtime/load.py
Python
def get_visuals(self, state: MjState) -> list[ArrowConfig]:
    """Returns a list of arrow configurations for the renderer."""
    if not self.active:
        return []

    visuals: list[ArrowConfig] = []
    action_pos = self.action_site.rt_pos(state)

    if self._last_f[3] > 1e-4 and self._vis.action_force:
        visuals.append(
            ArrowConfig(
                pos=action_pos,
                vec=self._last_f[:3],
                color=Color[self._vis.action_force].rgba,
                is_torque=False,
                length_scale=self.force_length_scale
                if self.force_length_scale is not None
                else self._vis.force_length_scale,
                width_scale=self.force_width_scale
                if self.force_width_scale is not None
                else self._vis.force_width_scale,
            )
        )

    if self._last_t[3] > 1e-4 and self._vis.torque:
        visuals.append(
            ArrowConfig(
                pos=action_pos,
                vec=self._last_t[:3],
                color=Color[self._vis.torque].rgba,
                is_torque=True,
                length_scale=self.torque_length_scale
                if self.torque_length_scale is not None
                else self._vis.torque_length_scale,
                width_scale=self.torque_width_scale
                if self.torque_width_scale is not None
                else self._vis.torque_width_scale,
            )
        )

    return visuals

request

Python
request(
    signal_manager: SignalManager | None = None,
    channels: list[Literal["force", "torque"]]
    | dict[
        Literal["force", "torque"], dict[str, Any] | None
    ] = ["force", "torque"],
)

Registers specific channels for logging.

Channel Description Type
force applied force in the world frame xyzm
torque applied torque in the world frame xyzm

Each channel is posted under subgroups=(load_name, channel).

  • An xyzm is a cartesian vector, posted as 4 values (x, y, z, and its magnitude m).

Each signal is tagged with built-in dimension metadata for its channel (force as force, torque as torque).

If signal_manager is omitted, the SignalManager of the active RuntimeManager with block is used. If that RuntimeManager has no SignalManager configured, this is a no-op.

Parameters:

Name Type Description Default
signal_manager SignalManager | None

The signal manager to register the sampler with.

None
channels list[Literal['force', 'torque']] | dict[Literal['force', 'torque'], dict[str, Any] | None]

The load data channels to log. Pass a list to select channels, or a dict mapping channel name to metadata overrides (or None) to select channels and attach per-channel metadata in one step.

['force', 'torque']
Source code in src/mujoco_mojo/runtime/load.py
Python
def request(
    self,
    signal_manager: SignalManager | None = None,
    channels: list[Literal["force", "torque"]]
    | dict[Literal["force", "torque"], dict[str, Any] | None] = ["force", "torque"],
):
    """
    Registers specific channels for logging.

    | Channel  | Description                        | Type |
    |:---------|:-----------------------------------|:-----|
    | `force`  | applied force in the world frame   | xyzm |
    | `torque` | applied torque in the world frame  | xyzm |

    Each channel is posted under `subgroups=(load_name, channel)`.

    * An `xyzm` is a cartesian vector, posted as 4 values (`x`, `y`, `z`, and its magnitude `m`).

    Each signal is tagged with built-in `dimension` metadata for its channel (`force` as force, `torque` as torque).

    If `signal_manager` is omitted, the `SignalManager` of the active `RuntimeManager` `with` block is used. If that `RuntimeManager` has no `SignalManager` configured, this is a no-op.

    Args:
        signal_manager: The signal manager to register the sampler with.
        channels: The load data channels to log. Pass a list to select channels, or a dict mapping channel name to metadata overrides (or `None`) to select channels and attach per-channel metadata in one step.

    """
    from mujoco_mojo.runtime.signal_manager import resolve_signal_manager

    signal_manager = resolve_signal_manager(signal_manager)
    if signal_manager is None:
        return

    if isinstance(channels, dict):
        _meta = cast("dict[str, dict[str, Any] | None]", channels)
        channels = list(channels.keys())
    else:
        _meta = {}

    channel_metadata = {
        "force": dim(Dimension.FORCE),
        "torque": torque_metadata(),
    }

    def sample(state: MjState):
        for channel in channels:
            source = self._last_f if channel == "force" else self._last_t
            meta = merge_signal_metadata(
                channel_metadata.get(channel), channel, _meta, unit_system=state.us
            )

            # iterate through x, y, z, and magnitude (pop. pop.)
            for i, attr in enumerate("xyzm"):
                signal_manager.post(
                    value=float(source[i]) if self.active else 0.0,
                    category=SignalCategory.LOADS,
                    # nest the force/torque under the function name
                    subgroups=(f"{self.name}", channel),
                    attr=attr,
                    metadata=meta,
                )

    signal_manager.register_sampler(sample)

GeneralLoad

Bases: VectorForce, VectorTorque

Full 6-DOF force and torque with independent callables for each component. Combines VectorForce and VectorTorque into a single load.

name instance-attribute

Python
name: str

Unique label used for telemetry column naming and duplicate-registration warnings.

active class-attribute instance-attribute

Python
active: bool = True

When False the load is suppressed.

action_site instance-attribute

Python
action_site: AnySite

Site where the force is applied. Its parent body receives the generalized force.

rel_to_site class-attribute instance-attribute

Python
rel_to_site: AnySite | None = None

Coordinate frame for force/torque components returned by calculate(). When None, components are in the world frame.

user_data class-attribute instance-attribute

Python
user_data: SerializeAsAny[UserData] | None = None

Optional strongly-typed payload accessible inside calculate(). Passed through unchanged each timestep.

force_length_scale class-attribute instance-attribute

Python
force_length_scale: float | None = None

Length multiplier for this load's force arrow(s), on top of MuJoCo's native scaling. None falls back to VisualizationSettings.force_length_scale.

force_width_scale class-attribute instance-attribute

Python
force_width_scale: float | None = None

Width multiplier for this load's force arrow(s), on top of MuJoCo's native scaling. None falls back to VisualizationSettings.force_width_scale.

torque_length_scale class-attribute instance-attribute

Python
torque_length_scale: float | None = None

Length multiplier for this load's torque arrow, on top of MuJoCo's native scaling. None falls back to VisualizationSettings.torque_length_scale.

torque_width_scale class-attribute instance-attribute

Python
torque_width_scale: float | None = None

Width multiplier for this load's torque arrow, on top of MuJoCo's native scaling. None falls back to VisualizationSettings.torque_width_scale.

xtion_body class-attribute instance-attribute

Python
xtion_body: Body | None = None

Body that receives the reaction force. When None, no reaction is applied and the force acts on the world.

tx class-attribute instance-attribute

Python
tx: Callable[[UserData | None, MjState], float] = (
    lambda ud, s: 0.0
)

Callable (user_data, state) -> X-axis torque component.

ty class-attribute instance-attribute

Python
ty: Callable[[UserData | None, MjState], float] = (
    lambda ud, s: 0.0
)

Callable (user_data, state) -> Y-axis torque component.

tz class-attribute instance-attribute

Python
tz: Callable[[UserData | None, MjState], float] = (
    lambda ud, s: 0.0
)

Callable (user_data, state) -> Z-axis torque component.

fx class-attribute instance-attribute

Python
fx: Callable[[UserData | None, MjState], float] = (
    lambda ud, s: 0.0
)

Callable (user_data, state) -> X-axis force component (N).

fy class-attribute instance-attribute

Python
fy: Callable[[UserData | None, MjState], float] = (
    lambda ud, s: 0.0
)

Callable (user_data, state) -> Y-axis force component (N).

fz class-attribute instance-attribute

Python
fz: Callable[[UserData | None, MjState], float] = (
    lambda ud, s: 0.0
)

Callable (user_data, state) -> Z-axis force component (N).

get_visuals

Python
get_visuals(state: MjState) -> list[ArrowConfig]

Returns a list of arrow configurations for the renderer.

Source code in src/mujoco_mojo/runtime/load.py
Python
def get_visuals(self, state: MjState) -> list[ArrowConfig]:
    """Returns a list of arrow configurations for the renderer."""
    if not self.active:
        return []

    visuals: list[ArrowConfig] = []
    action_pos = self.action_site.rt_pos(state)

    if self._last_f[3] > 1e-4 and self._vis.action_force:
        visuals.append(
            ArrowConfig(
                pos=action_pos,
                vec=self._last_f[:3],
                color=Color[self._vis.action_force].rgba,
                is_torque=False,
                length_scale=self.force_length_scale
                if self.force_length_scale is not None
                else self._vis.force_length_scale,
                width_scale=self.force_width_scale
                if self.force_width_scale is not None
                else self._vis.force_width_scale,
            )
        )

    if self._last_t[3] > 1e-4 and self._vis.torque:
        visuals.append(
            ArrowConfig(
                pos=action_pos,
                vec=self._last_t[:3],
                color=Color[self._vis.torque].rgba,
                is_torque=True,
                length_scale=self.torque_length_scale
                if self.torque_length_scale is not None
                else self._vis.torque_length_scale,
                width_scale=self.torque_width_scale
                if self.torque_width_scale is not None
                else self._vis.torque_width_scale,
            )
        )

    return visuals

request

Python
request(
    signal_manager: SignalManager | None = None,
    channels: list[Literal["force", "torque"]]
    | dict[
        Literal["force", "torque"], dict[str, Any] | None
    ] = ["force", "torque"],
)

Registers specific channels for logging.

Channel Description Type
force applied force in the world frame xyzm
torque applied torque in the world frame xyzm

Each channel is posted under subgroups=(load_name, channel).

  • An xyzm is a cartesian vector, posted as 4 values (x, y, z, and its magnitude m).

Each signal is tagged with built-in dimension metadata for its channel (force as force, torque as torque).

If signal_manager is omitted, the SignalManager of the active RuntimeManager with block is used. If that RuntimeManager has no SignalManager configured, this is a no-op.

Parameters:

Name Type Description Default
signal_manager SignalManager | None

The signal manager to register the sampler with.

None
channels list[Literal['force', 'torque']] | dict[Literal['force', 'torque'], dict[str, Any] | None]

The load data channels to log. Pass a list to select channels, or a dict mapping channel name to metadata overrides (or None) to select channels and attach per-channel metadata in one step.

['force', 'torque']
Source code in src/mujoco_mojo/runtime/load.py
Python
def request(
    self,
    signal_manager: SignalManager | None = None,
    channels: list[Literal["force", "torque"]]
    | dict[Literal["force", "torque"], dict[str, Any] | None] = ["force", "torque"],
):
    """
    Registers specific channels for logging.

    | Channel  | Description                        | Type |
    |:---------|:-----------------------------------|:-----|
    | `force`  | applied force in the world frame   | xyzm |
    | `torque` | applied torque in the world frame  | xyzm |

    Each channel is posted under `subgroups=(load_name, channel)`.

    * An `xyzm` is a cartesian vector, posted as 4 values (`x`, `y`, `z`, and its magnitude `m`).

    Each signal is tagged with built-in `dimension` metadata for its channel (`force` as force, `torque` as torque).

    If `signal_manager` is omitted, the `SignalManager` of the active `RuntimeManager` `with` block is used. If that `RuntimeManager` has no `SignalManager` configured, this is a no-op.

    Args:
        signal_manager: The signal manager to register the sampler with.
        channels: The load data channels to log. Pass a list to select channels, or a dict mapping channel name to metadata overrides (or `None`) to select channels and attach per-channel metadata in one step.

    """
    from mujoco_mojo.runtime.signal_manager import resolve_signal_manager

    signal_manager = resolve_signal_manager(signal_manager)
    if signal_manager is None:
        return

    if isinstance(channels, dict):
        _meta = cast("dict[str, dict[str, Any] | None]", channels)
        channels = list(channels.keys())
    else:
        _meta = {}

    channel_metadata = {
        "force": dim(Dimension.FORCE),
        "torque": torque_metadata(),
    }

    def sample(state: MjState):
        for channel in channels:
            source = self._last_f if channel == "force" else self._last_t
            meta = merge_signal_metadata(
                channel_metadata.get(channel), channel, _meta, unit_system=state.us
            )

            # iterate through x, y, z, and magnitude (pop. pop.)
            for i, attr in enumerate("xyzm"):
                signal_manager.post(
                    value=float(source[i]) if self.active else 0.0,
                    category=SignalCategory.LOADS,
                    # nest the force/torque under the function name
                    subgroups=(f"{self.name}", channel),
                    attr=attr,
                    metadata=meta,
                )

    signal_manager.register_sampler(sample)

JointLoad

Bases: Load

Abstract base for loads applied directly in generalized-coordinate (joint) space via qfrc_applied. Subclass and implement apply_load().

joint instance-attribute

Python
joint: Joint

The MJCF joint this load acts on.

name instance-attribute

Python
name: str

Unique label used for telemetry column naming and duplicate-registration warnings.

active class-attribute instance-attribute

Python
active: bool = True

When False the load is suppressed.

resolve_ids

Python
resolve_ids(state: MjState) -> None

Caches the joint ID, DOF address, and DOF count from the compiled MuJoCo model.

Source code in src/mujoco_mojo/runtime/load.py
Python
def resolve_ids(self, state: MjState) -> None:
    """Caches the joint ID, DOF address, and DOF count from the compiled MuJoCo model."""
    self._jid = self.joint.get_id(state.model)
    jnt_type = state.model.jnt_type[self._jid]
    match jnt_type:
        case mujoco.mjtJoint.mjJNT_HINGE | mujoco.mjtJoint.mjJNT_SLIDE:
            self._nv = 1
        case mujoco.mjtJoint.mjJNT_BALL:
            self._nv = 3
        case _:
            msg = f"JointLoad '{self.name}': joint '{self.joint.name}' must be hinge, slide, or ball (got type {jnt_type})"
            logger.error(msg)
            raise ValueError(msg)
    self._dof_adr = int(state.model.jnt_dofadr[self._jid])
    logger.debug(
        f"resolved joint '{self.joint.name}' to dof_adr={self._dof_adr} nv={self._nv}",
    )

apply_load abstractmethod

Python
apply_load(state: MjState) -> None

Writes the load contribution into state.data.qfrc_applied.

Source code in src/mujoco_mojo/runtime/load.py
Python
@abstractmethod
def apply_load(self, state: MjState) -> None:
    """Writes the load contribution into `state.data.qfrc_applied`."""

get_visuals

Python
get_visuals(state: MjState) -> list[ArrowConfig]

Returns a list of arrow configurations for the renderer.

Source code in src/mujoco_mojo/runtime/load.py
Python
def get_visuals(self, state: MjState) -> list[ArrowConfig]:
    """Returns a list of arrow configurations for the renderer."""
    return []

JointFriction

Bases: JointLoad

Joint friction with a pluggable formulation; use the class-method factories to construct.

Works for hinge, slide, and ball joints. The friction_func receives the full generalized velocity vector for the joint's DOFs and returns a generalized force vector of the same length. All built-in formulations use the velocity magnitude as the scalar speed and apply the resulting force magnitude along -vel/|vel|, so they generalize naturally to multi-DOF joints. Use a NamedValue inside the closure to make parameters mutable at runtime.

friction_func instance-attribute

Python
friction_func: Callable[[ndarray, MjState], ndarray]

(vel, state) -> friction force/torque vector in generalized coordinates. Constructed by the named class-method factories.

name instance-attribute

Python
name: str

Unique label used for telemetry column naming and duplicate-registration warnings.

active class-attribute instance-attribute

Python
active: bool = True

When False the load is suppressed.

joint instance-attribute

Python
joint: Joint

The MJCF joint this load acts on.

apply_load

Python
apply_load(state: MjState) -> None

Evaluates friction_func at the current joint velocity vector and accumulates the result into qfrc_applied.

Source code in src/mujoco_mojo/runtime/load.py
Python
def apply_load(self, state: MjState) -> None:
    """Evaluates `friction_func` at the current joint velocity vector and accumulates the result into `qfrc_applied`."""
    if not self.active or self._dof_adr < 0:
        self._last_force = np.zeros(3)
        return

    vel = np.array(state.data.qvel[self._dof_adr : self._dof_adr + self._nv])
    frc = self.friction_func(vel, state)
    state.data.qfrc_applied[self._dof_adr : self._dof_adr + self._nv] += frc
    self._last_force = self._to_world_frc(frc, state)

request

Python
request(
    signal_manager: SignalManager | None = None,
    metadata: dict[str, dict[str, Any]] | None = None,
) -> None

Registers specific channels for logging.

Channel Description Type
friction applied friction force/torque in the world frame xyzm

Each channel is posted under subgroups=(load_name, channel).

  • An xyzm is a cartesian vector, posted as 4 values (x, y, z, and its magnitude m).

friction is tagged with built-in dimension metadata resolved from the joint's type (force for slide, torque for hinge/ball).

Parameters:

Name Type Description Default
signal_manager SignalManager

Manager to register the sampler with. If omitted, the SignalManager of the active RuntimeManager with block is used. If that RuntimeManager has no SignalManager configured, this is a no-op.

None
metadata dict[str, dict[str, Any]] | None

Metadata overriding or extending the built-in default for the friction channel.

None

Raises:

Type Description
ValueError

If the joint has no name.

Source code in src/mujoco_mojo/runtime/load.py
Python
def request(
    self,
    signal_manager: SignalManager | None = None,
    metadata: dict[str, dict[str, Any]] | None = None,
) -> None:
    """
    Registers specific channels for logging.

    | Channel    | Description                                       | Type |
    |:-----------|:--------------------------------------------------|:-----|
    | `friction` | applied friction force/torque in the world frame  | xyzm |

    Each channel is posted under `subgroups=(load_name, channel)`.

    * An `xyzm` is a cartesian vector, posted as 4 values (`x`, `y`, `z`, and its magnitude `m`).

    `friction` is tagged with built-in `dimension` metadata resolved from the joint's type (force for slide, torque for hinge/ball).

    Args:
        signal_manager (SignalManager): Manager to register the sampler with. If omitted, the `SignalManager` of the active `RuntimeManager` `with` block is used. If that `RuntimeManager` has no `SignalManager` configured, this is a no-op.
        metadata: Metadata overriding or extending the built-in default for the `friction` channel.

    Raises:
        ValueError: If the joint has no name.

    """
    from mujoco_mojo.runtime.signal_manager import resolve_signal_manager

    signal_manager = resolve_signal_manager(signal_manager)
    if signal_manager is None:
        return

    if self.joint.name is None:
        msg = f"Cannot request telemetry for JointFriction '{self.name}': joint has no name."
        logger.error(msg)
        raise ValueError(msg)

    def sample(state: MjState) -> None:
        jnt_id = self.joint.get_id(state.model)
        jnt_type = int(state.model.jnt_type[jnt_id])
        meta = merge_signal_metadata(
            force_or_torque(jnt_type), "friction", metadata, unit_system=state.us
        )

        frc = self._last_force if self.active else np.zeros(3)
        for v, attr in zip(frc, ("x", "y", "z")):
            signal_manager.post(
                value=float(v),
                category=SignalCategory.LOADS,
                subgroups=(self.name, "friction"),
                attr=attr,
                metadata=meta,
            )
        signal_manager.post(
            value=float(np.linalg.norm(frc)),
            category=SignalCategory.LOADS,
            subgroups=(self.name, "friction"),
            attr="m",
            metadata=meta,
        )

    signal_manager.register_sampler(sample)

coulomb_simple classmethod

Python
coulomb_simple(
    name: str,
    joint: Joint,
    magnitude: float | NamedValue[float],
) -> Self

Constant-magnitude Coulomb (dry) friction opposing motion. Zero force at standstill. Good for brake pads, dry contacts, and cable friction.

"Simple" because the magnitude is a fixed number you choose, not derived from any actual load on the joint. See karnopp for friction that responds to the joint's real bearing/pin reaction load.

Parameters:

Name Type Description Default
name str

Load name used for telemetry column labeling.

required
joint Joint

The MJCF joint to act on (slide, hinge, or ball).

required
magnitude float | NamedValue[float]

Friction force or torque magnitude. Accepts NamedValue[float] for runtime mutation.

required
Source code in src/mujoco_mojo/runtime/load.py
Python
@classmethod
def coulomb_simple(
    cls,
    name: str,
    joint: Joint,
    magnitude: float | NamedValue[float],
) -> Self:
    """
    Constant-magnitude Coulomb (dry) friction opposing motion. Zero force at standstill. Good for brake pads, dry contacts, and cable friction.

    "Simple" because the magnitude is a fixed number you choose, not derived from any actual load on the joint. See `karnopp` for friction that responds to the joint's real bearing/pin reaction load.

    Args:
        name (str): Load name used for telemetry column labeling.
        joint (Joint): The MJCF joint to act on (slide, hinge, or ball).
        magnitude (float | NamedValue[float]): Friction force or torque magnitude. Accepts `NamedValue[float]` for runtime mutation.

    """

    def func(vel: np.ndarray, state: MjState) -> np.ndarray:
        speed = float(np.linalg.norm(vel))
        return (
            -float(magnitude) * vel / speed if speed > 1e-9 else np.zeros_like(vel)
        )

    return cls(name=name, joint=joint, friction_func=func)

viscous_simple classmethod

Python
viscous_simple(
    name: str,
    joint: Joint,
    damping: float | NamedValue[float],
) -> Self

Velocity-proportional viscous damping. Force is continuous through zero with no discontinuity at standstill. Good for grease-lubricated joints and fluid drag.

Parameters:

Name Type Description Default
name str

Load name used for telemetry column labeling.

required
joint Joint

The MJCF joint to act on (slide, hinge, or ball).

required
damping float | NamedValue[float]

Damping coefficient (force per velocity, or torque per angular velocity). Accepts NamedValue[float] for runtime mutation.

required
Source code in src/mujoco_mojo/runtime/load.py
Python
@classmethod
def viscous_simple(
    cls,
    name: str,
    joint: Joint,
    damping: float | NamedValue[float],
) -> Self:
    """
    Velocity-proportional viscous damping. Force is continuous through zero with no discontinuity at standstill. Good for grease-lubricated joints and fluid drag.

    Args:
        name (str): Load name used for telemetry column labeling.
        joint (Joint): The MJCF joint to act on (slide, hinge, or ball).
        damping (float | NamedValue[float]): Damping coefficient (force per velocity, or torque per angular velocity). Accepts `NamedValue[float]` for runtime mutation.

    """

    def func(vel: np.ndarray, state: MjState) -> np.ndarray:
        return -float(damping) * vel

    return cls(name=name, joint=joint, friction_func=func)

coulomb_viscous_simple classmethod

Python
coulomb_viscous_simple(
    name: str,
    joint: Joint,
    coulomb: float | NamedValue[float],
    viscous: float | NamedValue[float],
) -> Self

Coulomb and viscous friction combined, both at fixed magnitudes you choose. Constant sliding friction plus a velocity-proportional drag term.

"Simple" because neither term is derived from any actual load on the joint. See karnopp for friction that responds to the joint's real bearing/pin reaction load.

Parameters:

Name Type Description Default
name str

Load name used for telemetry column labeling.

required
joint Joint

The MJCF joint to act on (slide, hinge, or ball).

required
coulomb float | NamedValue[float]

Coulomb friction force or torque magnitude. Accepts NamedValue[float] for runtime mutation.

required
viscous float | NamedValue[float]

Viscous damping coefficient (force per velocity, or torque per angular velocity). Accepts NamedValue[float] for runtime mutation.

required
Source code in src/mujoco_mojo/runtime/load.py
Python
@classmethod
def coulomb_viscous_simple(
    cls,
    name: str,
    joint: Joint,
    coulomb: float | NamedValue[float],
    viscous: float | NamedValue[float],
) -> Self:
    """
    Coulomb and viscous friction combined, both at fixed magnitudes you choose. Constant sliding friction plus a velocity-proportional drag term.

    "Simple" because neither term is derived from any actual load on the joint. See `karnopp` for friction that responds to the joint's real bearing/pin reaction load.

    Args:
        name (str): Load name used for telemetry column labeling.
        joint (Joint): The MJCF joint to act on (slide, hinge, or ball).
        coulomb (float | NamedValue[float]): Coulomb friction force or torque magnitude. Accepts `NamedValue[float]` for runtime mutation.
        viscous (float | NamedValue[float]): Viscous damping coefficient (force per velocity, or torque per angular velocity). Accepts `NamedValue[float]` for runtime mutation.

    """

    def func(vel: np.ndarray, state: MjState) -> np.ndarray:
        speed = float(np.linalg.norm(vel))
        coulomb_term = (
            -float(coulomb) * vel / speed if speed > 1e-9 else np.zeros_like(vel)
        )
        return coulomb_term - float(viscous) * vel

    return cls(name=name, joint=joint, friction_func=func)

stribeck_simple classmethod

Python
stribeck_simple(
    name: str,
    joint: Joint,
    coulomb: float | NamedValue[float],
    static: float | NamedValue[float],
    stribeck_velocity: float | NamedValue[float],
    viscous: float | NamedValue[float] = 0.0,
) -> Self

Full Stribeck friction model, at fixed magnitudes you choose. Friction peaks at standstill, drops to the kinetic level as motion begins, then rises with speed. Best for brake and clutch models where stick-slip matters.

F = -(coulomb + (static - coulomb) * exp(-|v| / stribeck_velocity) + viscous * |v|) * v / |v|

Parameters:

Name Type Description Default
name str

Load name used for telemetry column labeling.

required
joint Joint

The MJCF joint to act on (slide, hinge, or ball).

required
coulomb float | NamedValue[float]

Kinetic friction force or torque magnitude. Accepts NamedValue[float].

required
static float | NamedValue[float]

Peak static friction force or torque at zero velocity. Must be >= coulomb. Accepts NamedValue[float].

required
stribeck_velocity float | NamedValue[float]

Characteristic velocity at which friction transitions from static to kinetic. Smaller values give a sharper transition. Accepts NamedValue[float].

required
viscous float | NamedValue[float]

Velocity-proportional damping (force per velocity, or torque per angular velocity). Defaults to 0. Accepts NamedValue[float].

0.0
Source code in src/mujoco_mojo/runtime/load.py
Python
@classmethod
def stribeck_simple(
    cls,
    name: str,
    joint: Joint,
    coulomb: float | NamedValue[float],
    static: float | NamedValue[float],
    stribeck_velocity: float | NamedValue[float],
    viscous: float | NamedValue[float] = 0.0,
) -> Self:
    """
    Full Stribeck friction model, at fixed magnitudes you choose. Friction peaks at standstill, drops to the kinetic level as motion begins, then rises with speed. Best for brake and clutch models where stick-slip matters.

    F = -(coulomb + (static - coulomb) * exp(-|v| / stribeck_velocity) + viscous * |v|) * v / |v|

    Args:
        name (str): Load name used for telemetry column labeling.
        joint (Joint): The MJCF joint to act on (slide, hinge, or ball).
        coulomb (float | NamedValue[float]): Kinetic friction force or torque magnitude. Accepts `NamedValue[float]`.
        static (float | NamedValue[float]): Peak static friction force or torque at zero velocity. Must be >= `coulomb`. Accepts `NamedValue[float]`.
        stribeck_velocity (float | NamedValue[float]): Characteristic velocity at which friction transitions from static to kinetic. Smaller values give a sharper transition. Accepts `NamedValue[float]`.
        viscous (float | NamedValue[float], optional): Velocity-proportional damping (force per velocity, or torque per angular velocity). Defaults to 0. Accepts `NamedValue[float]`.

    """

    def func(vel: np.ndarray, state: MjState) -> np.ndarray:
        speed = float(np.linalg.norm(vel))
        if speed < 1e-9:
            return np.zeros_like(vel)
        fc, fs = float(coulomb), float(static)
        vs, fv = float(stribeck_velocity), float(viscous)
        mag = fc + (fs - fc) * np.exp(-speed / max(vs, 1e-9)) + fv * speed
        return -mag * vel / speed

    return cls(name=name, joint=joint, friction_func=func)

karnopp classmethod

Python
karnopp(
    name: str,
    joint: Joint,
    mu_kinetic: float | NamedValue[float],
    mu_static: float | NamedValue[float],
    velocity_threshold: float | NamedValue[float],
    viscous: float | NamedValue[float] = 0.0,
) -> Self

Karnopp friction whose normal force is the joint's actual bearing/pin reaction load (Joint.rt_bearing_load), not a fixed magnitude. Responds to ordinary side loads (e.g. a body's weight pressing on a hinge pin, or a side load on a slider) not just a number you pick. Properly distinguishes "stuck" from "sliding" instead of picking a coefficient based on speed alone:

  • Sliding (|v| >= velocity_threshold): ordinary kinetic Coulomb friction, F = -mu_kinetic * bearing_load * v/|v|, plus an optional viscous term.
  • Stuck (|v| < velocity_threshold): rather than picking a direction from a near-zero (and possibly noisy) velocity, friction is set to exactly cancel whatever other smooth force is currently acting on the joint (qfrc_smooth: passive + actuator + applied + bias), clamped to the static limit mu_static * bearing_load. If the driving force exceeds that limit the joint breaks away and friction saturates at the limit, opposing the driving force.

This avoids the chattering a pure velocity-direction switch can produce right at standstill, since the held force no longer depends on the sign of a near-zero, noisy velocity.

The bearing load comes from Joint.rt_cfrc_int, which refreshes itself on demand via state.ensure_rne_post_constraint(), so no extra wiring is needed regardless of how the simulation is driven. Note that qfrc_smooth reflects the previous step's solve, since this step's values are not yet known when loads are applied; this mirrors the existing one-step lag already inherent in using vel to set this step's force.

Parameters:

Name Type Description Default
name str

Load name used for telemetry column labeling.

required
joint Joint

The MJCF joint to act on (slide, hinge, or ball).

required
mu_kinetic float | NamedValue[float]

Friction coefficient applied to the bearing load while sliding. Accepts NamedValue[float].

required
mu_static float | NamedValue[float]

Friction coefficient applied to the bearing load that the joint can hold against before breaking away. Accepts NamedValue[float].

required
velocity_threshold float | NamedValue[float]

Speed below which the joint is considered stuck rather than sliding. Accepts NamedValue[float].

required
viscous float | NamedValue[float]

Velocity-proportional damping added on top of the kinetic term while sliding. Defaults to 0. Accepts NamedValue[float].

0.0
Source code in src/mujoco_mojo/runtime/load.py
Python
@classmethod
def karnopp(
    cls,
    name: str,
    joint: Joint,
    mu_kinetic: float | NamedValue[float],
    mu_static: float | NamedValue[float],
    velocity_threshold: float | NamedValue[float],
    viscous: float | NamedValue[float] = 0.0,
) -> Self:
    """
    Karnopp friction whose normal force is the joint's actual bearing/pin reaction load (`Joint.rt_bearing_load`), not a fixed magnitude. Responds to ordinary side loads (e.g. a body's weight pressing on a hinge pin, or a side load on a slider) not just a number you pick. Properly distinguishes "stuck" from "sliding" instead of picking a coefficient based on speed alone:

    - Sliding (`|v| >= velocity_threshold`): ordinary kinetic Coulomb friction, `F = -mu_kinetic * bearing_load * v/|v|`, plus an optional `viscous` term.
    - Stuck (`|v| < velocity_threshold`): rather than picking a direction from a near-zero (and possibly noisy) velocity, friction is set to exactly cancel whatever other smooth force is currently acting on the joint (`qfrc_smooth`: passive + actuator + applied + bias), clamped to the static limit `mu_static * bearing_load`. If the driving force exceeds that limit the joint breaks away and friction saturates at the limit, opposing the driving force.

    This avoids the chattering a pure velocity-direction switch can produce right at standstill, since the held force no longer depends on the sign of a near-zero, noisy velocity.

    The bearing load comes from `Joint.rt_cfrc_int`, which refreshes itself on demand via `state.ensure_rne_post_constraint()`, so no extra wiring is needed regardless of how the simulation is driven. Note that `qfrc_smooth` reflects the previous step's solve, since this step's values are not yet known when loads are applied; this mirrors the existing one-step lag already inherent in using `vel` to set this step's force.

    Args:
        name (str): Load name used for telemetry column labeling.
        joint (Joint): The MJCF joint to act on (slide, hinge, or ball).
        mu_kinetic (float | NamedValue[float]): Friction coefficient applied to the bearing load while sliding. Accepts `NamedValue[float]`.
        mu_static (float | NamedValue[float]): Friction coefficient applied to the bearing load that the joint can hold against before breaking away. Accepts `NamedValue[float]`.
        velocity_threshold (float | NamedValue[float]): Speed below which the joint is considered stuck rather than sliding. Accepts `NamedValue[float]`.
        viscous (float | NamedValue[float], optional): Velocity-proportional damping added on top of the kinetic term while sliding. Defaults to 0. Accepts `NamedValue[float]`.

    """

    def func(vel: np.ndarray, state: MjState) -> np.ndarray:
        speed = float(np.linalg.norm(vel))
        normal = joint.rt_bearing_load(state)

        if speed < float(velocity_threshold):
            driving = np.array(joint.rt_qfrc_smooth(state))
            drive_mag = float(np.linalg.norm(driving))
            if drive_mag < 1e-9:
                return np.zeros_like(vel)
            max_static = float(mu_static) * normal
            return -min(drive_mag, max_static) * driving / drive_mag

        if speed < 1e-9:
            return np.zeros_like(vel)
        coulomb_term = -float(mu_kinetic) * normal * vel / speed
        return coulomb_term - float(viscous) * vel

    return cls(name=name, joint=joint, friction_func=func)

resolve_ids

Python
resolve_ids(state: MjState) -> None

Caches the joint ID, DOF address, and DOF count from the compiled MuJoCo model.

Source code in src/mujoco_mojo/runtime/load.py
Python
def resolve_ids(self, state: MjState) -> None:
    """Caches the joint ID, DOF address, and DOF count from the compiled MuJoCo model."""
    self._jid = self.joint.get_id(state.model)
    jnt_type = state.model.jnt_type[self._jid]
    match jnt_type:
        case mujoco.mjtJoint.mjJNT_HINGE | mujoco.mjtJoint.mjJNT_SLIDE:
            self._nv = 1
        case mujoco.mjtJoint.mjJNT_BALL:
            self._nv = 3
        case _:
            msg = f"JointLoad '{self.name}': joint '{self.joint.name}' must be hinge, slide, or ball (got type {jnt_type})"
            logger.error(msg)
            raise ValueError(msg)
    self._dof_adr = int(state.model.jnt_dofadr[self._jid])
    logger.debug(
        f"resolved joint '{self.joint.name}' to dof_adr={self._dof_adr} nv={self._nv}",
    )

get_visuals

Python
get_visuals(state: MjState) -> list[ArrowConfig]

Returns a list of arrow configurations for the renderer.

Source code in src/mujoco_mojo/runtime/load.py
Python
def get_visuals(self, state: MjState) -> list[ArrowConfig]:
    """Returns a list of arrow configurations for the renderer."""
    return []

ActuatorControl

Bases: Load

Drives an actuator's control input each timestep, writing into mjData.ctrl. Use control_func for an arbitrary control law, or the constant factory for a simple, runtime-mutable set point.

actuator instance-attribute

Python
actuator: SerializeAsAny[ActuatorBase]

The MJCF actuator this load drives.

control_func class-attribute instance-attribute

Python
control_func: Callable[
    [UserData | None, MjState], float
] = lambda ud, s: 0.0

Callable (user_data, state) -> control value written into mjData.ctrl for this actuator.

user_data class-attribute instance-attribute

Python
user_data: SerializeAsAny[UserData] | None = None

Optional strongly-typed payload accessible inside control_func. Passed through unchanged each timestep.

name instance-attribute

Python
name: str

Unique label used for telemetry column naming and duplicate-registration warnings.

active class-attribute instance-attribute

Python
active: bool = True

When False the load is suppressed.

resolve_ids

Python
resolve_ids(state: MjState) -> None

Caches the actuator ID from the compiled MuJoCo model.

Source code in src/mujoco_mojo/runtime/load.py
Python
def resolve_ids(self, state: MjState) -> None:
    """Caches the actuator ID from the compiled MuJoCo model."""
    self._aid = self.actuator.get_id(state.model)

apply_load

Python
apply_load(state: MjState) -> None

Evaluates control_func and writes the result into mjData.ctrl[self._aid].

Source code in src/mujoco_mojo/runtime/load.py
Python
def apply_load(self, state: MjState) -> None:
    """Evaluates `control_func` and writes the result into `mjData.ctrl[self._aid]`."""
    if not self.active:
        self._last_ctrl = 0.0
        return

    self._last_ctrl = float(self.control_func(self.user_data, state))
    state.data.ctrl[self._aid] = self._last_ctrl

request

Python
request(
    signal_manager: SignalManager | None = None,
    metadata: dict[str, dict[str, Any]] | None = None,
) -> None

Registers the applied control value for logging under Loads/<name>:ctrl.

ctrl's units depend on the driven actuator's transmission and gear/dyntype (the same ambiguity as ActuatorBase.request()'s ctrl channel), so no built-in metadata default is applied. Supply metadata={"ctrl": {...}} yourself if you know it.

Parameters:

Name Type Description Default
signal_manager SignalManager

Manager to register the sampler with. If omitted, the SignalManager of the active RuntimeManager with block is used. If that RuntimeManager has no SignalManager configured, this is a no-op.

None
metadata dict[str, dict[str, Any]] | None

Metadata for the ctrl channel.

None
Source code in src/mujoco_mojo/runtime/load.py
Python
def request(
    self,
    signal_manager: SignalManager | None = None,
    metadata: dict[str, dict[str, Any]] | None = None,
) -> None:
    """
    Registers the applied control value for logging under `Loads/<name>:ctrl`.

    `ctrl`'s units depend on the driven actuator's transmission and `gear`/`dyntype` (the same ambiguity as `ActuatorBase.request()`'s `ctrl` channel), so no built-in metadata default is applied. Supply `metadata={"ctrl": {...}}` yourself if you know it.

    Args:
        signal_manager (SignalManager): Manager to register the sampler with. If omitted, the `SignalManager` of the active `RuntimeManager` `with` block is used. If that `RuntimeManager` has no `SignalManager` configured, this is a no-op.
        metadata: Metadata for the `ctrl` channel.

    """
    from mujoco_mojo.runtime.signal_manager import resolve_signal_manager

    signal_manager = resolve_signal_manager(signal_manager)
    if signal_manager is None:
        return

    def sample(state: MjState) -> None:
        signal_manager.post(
            value=self._last_ctrl if self.active else 0.0,
            category=SignalCategory.LOADS,
            subgroups=(self.name,),
            attr="ctrl",
            metadata=merge_signal_metadata(None, "ctrl", metadata),
        )

    signal_manager.register_sampler(sample)

constant classmethod

Python
constant(
    name: str,
    actuator: ActuatorBase,
    value: float | NamedValue[float],
) -> Self

Drives the actuator with a fixed set point.

Parameters:

Name Type Description Default
name str

Load name used for telemetry column labeling.

required
actuator ActuatorBase

The MJCF actuator to drive.

required
value float | NamedValue[float]

Control value written to mjData.ctrl every timestep. Accepts NamedValue[float] for runtime mutation.

required
Source code in src/mujoco_mojo/runtime/load.py
Python
@classmethod
def constant(
    cls,
    name: str,
    actuator: ActuatorBase,
    value: float | NamedValue[float],
) -> Self:
    """
    Drives the actuator with a fixed set point.

    Args:
        name (str): Load name used for telemetry column labeling.
        actuator (ActuatorBase): The MJCF actuator to drive.
        value (float | NamedValue[float]): Control value written to `mjData.ctrl` every timestep. Accepts `NamedValue[float]` for runtime mutation.

    """

    def func(ud: UserData | None, state: MjState) -> float:
        return value.value if isinstance(value, NamedValue) else value

    return cls(name=name, actuator=actuator, control_func=func)

get_visuals

Python
get_visuals(state: MjState) -> list[ArrowConfig]

Returns a list of arrow configurations for the renderer.

Source code in src/mujoco_mojo/runtime/load.py
Python
def get_visuals(self, state: MjState) -> list[ArrowConfig]:
    """Returns a list of arrow configurations for the renderer."""
    return []