Source code for protomotions.agents.evaluators.config

# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0

"""Configuration classes for evaluators."""

from typing import Dict, Optional, Union
from dataclasses import dataclass, field
from enum import Enum

from protomotions.envs.mdp_component import MdpComponent, static_component_errors
from protomotions.envs.terminations.rule import TerminationRule
from protomotions.agents.evaluators.components import ThresholdFailure, as_failure


[docs] @dataclass class EvaluatorConfig: """Configuration for base evaluator.""" _target_: str = "protomotions.agents.evaluators.base_evaluator.BaseEvaluator" metric_components: Dict[str, MdpComponent] = field( default_factory=dict, metadata={ "help": "Per-environment numeric metrics; log mean/min/max over scored frames." }, ) failure_components: Dict[str, Union[TerminationRule, ThresholdFailure]] = field( default_factory=dict, metadata={ "help": "A TerminationRule (any-frame failure) or ThresholdFailure(rule, " "reduction=...) over sum, mean, min, max or last value per trial." }, ) score_key: Optional[str] = field( default=None, metadata={ "help": "'eval/success_rate' for checkpoint selection, None for metrics only." }, ) max_eval_steps: int = field( default=600, metadata={"help": "Maximum steps per evaluation episode.", "min": 1}, ) eval_metrics_every: Optional[int] = field( default=200, metadata={ "help": "Evaluate metrics every N epochs. None = disabled.", "min": 1, }, )
[docs] def validate(self): if self.score_key not in (None, "eval/success_rate"): raise ValueError("score_key must be 'eval/success_rate' or None") if self.score_key is not None and not self.failure_components: raise ValueError("A success score requires explicit failure_components") failures = {} for name, entry in self.failure_components.items(): # as_failure refuses a non-rule, a ThresholdFailure of a bad type and # a truncation rule; it normalises a bare rule to an any-frame failure. failure = as_failure(name, entry) # Unpickling bypasses dataclass initialization; validate resumed rules too. failure.rule.__post_init__() failures[name] = failure.component for group in (self.metric_components, failures): for name, component in group.items(): if not isinstance(component, MdpComponent): raise TypeError( f"Evaluation component {name!r} must be an MdpComponent" ) errors = static_component_errors( self.metric_components, group="evaluation" ) + static_component_errors(failures, group="evaluation") if errors: raise ValueError("\n".join(errors))
[docs] @dataclass class MotionWeightsRulesConfig: """Configuration for motion weights update rule.""" motion_weights_update_success_discount: float = field( default=0.999, metadata={ "help": "Discount factor for successful motion weights.", "min": 0.0, "max": 1.0, }, ) motion_weights_update_failure_discount: float = field( default=0.999, metadata={ "help": "Discount for failed motions. 0 = set weight straight to 1.", "min": 0.0, "max": 1.0, }, ) min_motion_weight: Union[float, str] = field( default="1/num_motions", metadata={ "help": "Minimum weight for any motion. '1/num_motions' or float value." }, )
[docs] @dataclass class MimicEvaluatorConfig(EvaluatorConfig): """Configuration for Mimic evaluator.""" _target_: str = "protomotions.agents.evaluators.mimic_evaluator.MimicEvaluator" save_predicted_motion_lib_every: Optional[int] = field( default=3, metadata={ "help": "Save pred_motion_lib every M evals. None = disabled.", "min": 1, }, ) motion_weights_rules: MotionWeightsRulesConfig = field( default_factory=MotionWeightsRulesConfig, metadata={"help": "Rules for updating motion sampling weights."}, )
[docs] class MaskedMimicActionSource(Enum): """Student action path used for MaskedMimic evaluation.""" POSTERIOR = "posterior" PRIOR = "prior"
[docs] @dataclass class MaskedMimicEvaluatorConfig(MimicEvaluatorConfig): """Mimic coverage with posterior training evaluation and prior inference.""" _target_: str = "protomotions.agents.evaluators.masked_mimic_evaluator.MaskedMimicEvaluator" action_source: MaskedMimicActionSource = MaskedMimicActionSource.POSTERIOR
[docs] def validate(self): super().validate() if not isinstance(self.action_source, MaskedMimicActionSource): raise ValueError("action_source must be a MaskedMimicActionSource enum member")