.. _metrics: Metrics ======= The metrics manager logs per-step scalar values as episode averages. Unlike rewards, metrics carry no weight and are not scaled by the step duration. They exist purely for diagnostics: tracking quantities such as tracking error, contact forces, or energy consumption alongside reward curves without influencing the optimization. Metrics are computed every environment step, accumulated per environment, and averaged over the episode length when the environment resets. The resulting averages are written to the training logger (TensorBoard or Weights & Biases) under the ``Episode_Metrics/`` prefix. If the ``metrics`` dictionary on ``ManagerBasedRlEnvCfg`` is empty, the environment substitutes a lightweight no-op manager with zero overhead. Registration ------------ Each metric term is registered by name in the ``metrics`` dictionary of ``ManagerBasedRlEnvCfg``. The configuration is minimal: a callable and an optional ``params`` dictionary. .. code-block:: python from mjlab.managers.metrics_manager import MetricsTermCfg metrics = { "base_height": MetricsTermCfg( func=base_height, params={"asset_cfg": SceneEntityCfg("robot")}, ), } The callable receives ``env`` as its first argument and any entries in ``params`` as keyword arguments. It must return a tensor of shape ``[num_envs]``, one scalar per environment per step. How metrics are computed ------------------------- The manager maintains a running sum and a step counter for each environment. On every call to ``compute()``: 1. The step counter increments for all environments. 2. Each term function is called with the current environment state. 3. The returned per-environment values are added to the running sums. When an environment resets, the manager reduces each term's accumulated values to a scalar, averages the result across all resetting environments, and returns it under the key ``Episode_Metrics/``. The sums and counters are then zeroed for the reset environments. The reduction is controlled by the ``reduce`` field on ``MetricsTermCfg``: - ``"mean"`` (default): divides the accumulated sum by the step count for each environment. Division is per-environment, so environments that terminated early are not diluted by longer-running ones. - ``"last"``: reports the value from the final step of the episode. This is useful for binary success metrics (such as whether the robot is standing) that should not be averaged over time. - ``"max"``: reports the highest value seen during the episode, useful for peak quantities such as maximum power draw or contact force. - ``"sum"``: reports the accumulated total over the episode without dividing by the step count. Use it for quantities that are inherently cumulative, such as episodic reward or total distance traveled. Note that the value grows with episode length, so it is not comparable across episodes of differing duration. These scalars flow through ``env.extras["log"]`` into the training runner, which writes them to the configured logger. In a typical training run they appear as: .. code-block:: text Episode_Metrics/base_height Episode_Metrics/contact_force alongside the ``Episode_Reward/`` entries produced by the reward manager. Writing custom metric functions -------------------------------- A metric function follows the same pattern as reward and observation functions. It takes the environment as its first argument, reads whatever state it needs, and returns a ``[num_envs]`` tensor. .. code-block:: python import torch from mjlab.envs import ManagerBasedRlEnv from mjlab.managers.scene_entity_config import SceneEntityCfg def base_height( env: ManagerBasedRlEnv, asset_cfg: SceneEntityCfg = SceneEntityCfg("robot"), ) -> torch.Tensor: robot = env.scene[asset_cfg.name] return robot.data.root_link_pos_w[:, 2] For metrics that require cached setup or per-episode state, implement the term as a class with ``__init__(self, cfg, env)`` and a ``__call__`` method. If the class defines a ``reset(env_ids)`` method, the manager calls it automatically on episode resets.