Skip to content

signal_metadata

Shared metadata model and helpers for tagging SignalManager.post()/.track() calls with their physical quantity, so callers don't need to hand-build Pint dimension/unit strings at every call site.

MetadataOverrides module-attribute

Python
MetadataOverrides = Mapping[str, MetadataLike | None]

Dimension

Bases: StrEnum

Pint dimension expressions for physical quantities whose concrete unit depends on the user's modeling scale (MuJoCo has no inherent unit system). Use with dim(), which builds a metadata={"dimension": ...} entry for SignalManager.post()/.track().

TransformType

Bases: StrEnum

How a signal must be re-expressed under MojoDataFrame.mojo.change_frame().

POINT, VECTOR, and QUATERNION tag channels grouped into a :x/:y/:z (or :w/:x/:y/:z) family (see rotatable_bases/quaternion_bases), which change_frame refuses to touch untagged. SCALAR says the value does not depend on the frame at all. A lone scalar column needs no tag to be left alone, so tagging it is optional and states the intent; the tag matters for a group whose columns happen to be named :x/:y/:z (or :w/:x/:y/:z) without being a vector, which it keeps out of any rotation.

POINT class-attribute instance-attribute

Python
POINT = 'point'

Translate then rotate (positions).

VECTOR class-attribute instance-attribute

Python
VECTOR = 'vector'

Rotate only (velocities, forces, accelerations, momenta, directions).

QUATERNION class-attribute instance-attribute

Python
QUATERNION = 'quaternion'

Compose: q' = q_b^-1 (x) q.

SCALAR class-attribute instance-attribute

Python
SCALAR = 'scalar'

Unchanged by a frame change (energies, distances, angles, magnitudes, joint values).

metadata property

Python
metadata: ColumnMetadata

Builds a ColumnMetadata carrying only this transform_type, tagging how a signal's grouped vector/quaternion components transform under MojoDataFrame.mojo.change_frame().

ColumnMetadata

Bases: BaseModel

Metadata describing one telemetry column, stored per column in the parquet footer.

The declared fields are the ones mujoco-mojo understands and validates. Any other key is kept as-is (extra="allow"), so callers can attach their own labels. Unset fields are omitted when dumping, so the serialized form only carries what was actually tagged.

unit class-attribute instance-attribute

Python
unit: str | None = None

Concrete Pint unit string, e.g. meter / second. When dimension is also set, both must have the same dimensionality.

dimension class-attribute instance-attribute

Python
dimension: str | None = None

Pint dimension expression for the physical quantity, e.g. [length] / [time]. Tags the quantity without committing to a concrete unit, which is the right choice for built-in signals since the modeling unit system isn't knowable.

quantity class-attribute instance-attribute

Python
quantity: str | None = None

Free-form hint that separates quantities Pint cannot tell apart, e.g. torque versus energy.

transform_type class-attribute instance-attribute

Python
transform_type: TransformType | None = None

How this signal's grouped :x/:y/:z or :w/:x/:y/:z components transform under change_frame(). Only set it for channels that are grouped into such a family.

__hash__

Python
__hash__() -> int

Hashes the declared fields only (equal objects still hash equal), so instances can key the per-timestep caches.

Source code in src/mujoco_mojo/utils/signal_metadata.py
Python
def __hash__(self) -> int:
    """Hashes the declared fields only (equal objects still hash equal), so instances can key the per-timestep caches."""
    return hash((self.unit, self.dimension, self.quantity, self.transform_type))

__bool__

Python
__bool__() -> bool

False when nothing is set, so if metadata: skips empty metadata.

Source code in src/mujoco_mojo/utils/signal_metadata.py
Python
def __bool__(self) -> bool:
    """False when nothing is set, so `if metadata:` skips empty metadata."""
    return bool(self._set_values())

__or__

Python
__or__(other: object) -> ColumnMetadata

Merges two metadata objects, with the right side winning where both set a value. Extras are kept. The result is not re-validated (both sides already were, or are validated where they are registered), which keeps per-timestep merges cheap.

Source code in src/mujoco_mojo/utils/signal_metadata.py
Python
def __or__(self, other: object) -> ColumnMetadata:
    """Merges two metadata objects, with the right side winning where both set a value. Extras are kept. The result is not re-validated (both sides already were, or are validated where they are registered), which keeps per-timestep merges cheap."""
    if not isinstance(other, ColumnMetadata):
        return NotImplemented
    return self.model_copy(update=other._set_values())

validated classmethod

Python
validated(value: MetadataLike) -> ColumnMetadata

Validates a metadata object or a plain dict (always, unlike model_validate on an instance), raising a ValidationError naming the bad field.

Source code in src/mujoco_mojo/utils/signal_metadata.py
Python
@classmethod
def validated(cls, value: MetadataLike) -> ColumnMetadata:
    """Validates a metadata object or a plain dict (always, unlike `model_validate` on an instance), raising a `ValidationError` naming the bad field."""
    return cls.model_validate(
        value.model_dump() if isinstance(value, ColumnMetadata) else dict(value)
    )

unchecked classmethod

Python
unchecked(value: MetadataLike) -> ColumnMetadata

Wraps a plain dict without validating it, for per-timestep merges. The merged result is validated once, where the signal is registered.

Source code in src/mujoco_mojo/utils/signal_metadata.py
Python
@classmethod
def unchecked(cls, value: MetadataLike) -> ColumnMetadata:
    """Wraps a plain dict without validating it, for per-timestep merges. The merged result is validated once, where the signal is registered."""
    return (
        value if isinstance(value, ColumnMetadata) else cls.model_construct(**value)
    )

lenient classmethod

Python
lenient(
    raw: Mapping[str, Any], *, where: str = ""
) -> ColumnMetadata

Validates raw, but falls back to loading it unvalidated (with a warning) if it is invalid, so files written by other versions still open.

Source code in src/mujoco_mojo/utils/signal_metadata.py
Python
@classmethod
def lenient(cls, raw: Mapping[str, Any], *, where: str = "") -> ColumnMetadata:
    """Validates `raw`, but falls back to loading it unvalidated (with a warning) if it is invalid, so files written by other versions still open."""
    try:
        return cls.validated(raw)
    except ValidationError as e:
        logger.warning(f"Loading invalid column metadata{where}: {e}")
        return cls.model_construct(**raw)

resolve_dimension_metadata

Python
resolve_dimension_metadata(
    meta: ColumnMetadata, unit_system: UnitSystem
) -> ColumnMetadata

Adds a concrete unit string to meta derived from its dimension and the given unit_system. The dimension is preserved so callers can still see both the physical quantity type and the concrete unit. Dimensionless signals (dimension="[]") are left unchanged. All other fields (e.g. quantity) are preserved. Cached, since merge_signal_metadata runs every timestep.

Source code in src/mujoco_mojo/utils/signal_metadata.py
Python
def resolve_dimension_metadata(
    meta: ColumnMetadata, unit_system: UnitSystem
) -> ColumnMetadata:
    """Adds a concrete `unit` string to `meta` derived from its `dimension` and the given `unit_system`. The `dimension` is preserved so callers can still see both the physical quantity type and the concrete unit. Dimensionless signals (`dimension="[]"`) are left unchanged. All other fields (e.g. `quantity`) are preserved. Cached, since `merge_signal_metadata` runs every timestep."""
    if meta.dimension is None or meta.dimension == "[]":
        return meta
    return _resolve_dimension(meta, _base_units(unit_system))

dim cached

Python
dim(dimension: Dimension | str) -> ColumnMetadata

Builds a dimension-tagged ColumnMetadata. Accepts either a Dimension enum member or a Pint unit name string (e.g. "inch"), in which case Pint looks up the matching Dimension member by dimensionality.

Source code in src/mujoco_mojo/utils/signal_metadata.py
Python
@cache
def dim(dimension: Dimension | str) -> ColumnMetadata:
    """Builds a `dimension`-tagged `ColumnMetadata`. Accepts either a `Dimension` enum member or a Pint unit name string (e.g. `"inch"`), in which case Pint looks up the matching `Dimension` member by dimensionality."""
    if isinstance(dimension, Dimension):
        # check Dimension first: it's a StrEnum and is also an instance of str,
        # so this must come before the str branch to avoid the Pint-lookup path running on
        # a known Dimension value (which would return the wrong enum member for TORQUE vs ENERGY)
        return ColumnMetadata(dimension=str(dimension))
    from mujoco_mojo.stochas import ureg

    target = ureg.get_dimensionality(dimension)
    for member in Dimension:
        if ureg.get_dimensionality(member.value) == target:
            return ColumnMetadata(dimension=str(member))
    raise ValueError(
        f"No Dimension member matches the Pint dimensionality of unit {dimension!r}"
    )

unit cached

Python
unit(unit: str) -> ColumnMetadata

Builds a unit-tagged ColumnMetadata.

Source code in src/mujoco_mojo/utils/signal_metadata.py
Python
@cache
def unit(unit: str) -> ColumnMetadata:
    """Builds a `unit`-tagged `ColumnMetadata`."""
    return ColumnMetadata(unit=unit)

dimensionless_metadata

Python
dimensionless_metadata() -> ColumnMetadata

Tags a signal as known to have no units (e.g. quaternions, rotation matrices, axis vectors, enum/index values), distinguishing "known to be unitless" from "nobody tagged this column".

Source code in src/mujoco_mojo/utils/signal_metadata.py
Python
def dimensionless_metadata() -> ColumnMetadata:
    """Tags a signal as known to have no units (e.g. quaternions, rotation matrices, axis vectors, enum/index values), distinguishing "known to be unitless" from "nobody tagged this column"."""
    return dim(Dimension.DIMENSIONLESS)

angle_metadata

Python
angle_metadata() -> ColumnMetadata

Tags a signal as an angle in radians. MuJoCo's compiled model always stores angles in radians regardless of the XML angle= attribute, so this is a concrete unit rather than a scale-ambiguous dimension.

Source code in src/mujoco_mojo/utils/signal_metadata.py
Python
def angle_metadata() -> ColumnMetadata:
    """Tags a signal as an angle in radians. MuJoCo's compiled model always stores angles in radians regardless of the XML `angle=` attribute, so this is a concrete unit rather than a scale-ambiguous dimension."""
    return unit(str(Angle.RADIAN))

angular_rate_metadata

Python
angular_rate_metadata(
    per: str = "second",
) -> ColumnMetadata

Tags a signal as an angular rate (e.g. angular velocity, angular acceleration) in radians per per.

Source code in src/mujoco_mojo/utils/signal_metadata.py
Python
def angular_rate_metadata(per: str = "second") -> ColumnMetadata:
    """Tags a signal as an angular rate (e.g. angular velocity, angular acceleration) in radians per `per`."""
    return unit(f"{Angle.RADIAN} / {per}")

torque_metadata

Python
torque_metadata() -> ColumnMetadata

Tags a signal as torque. Includes an extra quantity hint since torque and energy share the same Pint dimensionality and can't be told apart by dimension alone.

Source code in src/mujoco_mojo/utils/signal_metadata.py
Python
def torque_metadata() -> ColumnMetadata:
    """Tags a signal as torque. Includes an extra `quantity` hint since torque and energy share the same Pint dimensionality and can't be told apart by dimension alone."""
    return _torque_metadata()

force_or_torque cached

Python
force_or_torque(jnt_type: int) -> ColumnMetadata

Returns force metadata for a slide joint's generalized force, or torque metadata for any rotational joint (hinge/ball/free).

Source code in src/mujoco_mojo/utils/signal_metadata.py
Python
@cache
def force_or_torque(jnt_type: int) -> ColumnMetadata:
    """Returns force metadata for a slide joint's generalized force, or torque metadata for any rotational joint (hinge/ball/free)."""
    import mujoco

    if jnt_type == mujoco.mjtJoint.mjJNT_SLIDE:
        return dim(Dimension.FORCE)
    return torque_metadata()

merge_signal_metadata

Python
merge_signal_metadata(
    builtin: ColumnMetadata | None,
    channel: str,
    user_metadata: MetadataOverrides | None,
    *,
    unit_system: UnitSystem | None = None,
) -> ColumnMetadata

Merges a built-in default with the caller-supplied override/extension for channel, with user-supplied fields winning on conflict. If unit_system is provided and builtin has a dimension, it is resolved to a concrete unit. Returns an empty ColumnMetadata if both are empty. Nothing here is validated: the result is validated when it is used to build a Column.

Source code in src/mujoco_mojo/utils/signal_metadata.py
Python
def merge_signal_metadata(
    builtin: ColumnMetadata | None,
    channel: str,
    user_metadata: MetadataOverrides | None,
    *,
    unit_system: UnitSystem | None = None,
) -> ColumnMetadata:
    """Merges a built-in default with the caller-supplied override/extension for `channel`, with user-supplied fields winning on conflict. If `unit_system` is provided and `builtin` has a `dimension`, it is resolved to a concrete unit. Returns an empty `ColumnMetadata` if both are empty. Nothing here is validated: the result is validated when it is used to build a `Column`."""
    if builtin is not None and unit_system is not None:
        builtin = resolve_dimension_metadata(builtin, unit_system)
    override = (user_metadata or {}).get(channel)
    if override is None:
        return builtin if builtin is not None else ColumnMetadata()
    override_meta = ColumnMetadata.unchecked(override)
    return override_meta if builtin is None else builtin | override_meta