Source code for emflow.problems.problem

"""Problem: the complete, self-describing benchmark bundle.

``load_problem("heftcom2024:forecasting")`` returns one of these. It carries
everything an :class:`~emflow.run.experiment.Experiment` needs โ€” dataset,
environment factory, objective, schedule, splits โ€” plus the published
reference scores that let a result be ranked against the historical field.
"""

from __future__ import annotations

from dataclasses import dataclass, field as _dc_field
import typing as t

import pandas as pd

from ..data import Dataset
from .objective import Objective
from .schedule import IssueSchedule

if t.TYPE_CHECKING:  # pragma: no cover
    import gymnasium as gym


[docs] @dataclass(frozen=True) class Splits: """Temporal splits, in *target time*. ``train_end`` Training data is everything strictly before this timestamp. ``validation`` Half-open ``[start, end)`` period whose origins are for model iteration โ€” what agents hillclimb on. ``holdout`` Half-open ``[start, end)`` period scored once by the Verifier. Never iterate against it. """ train_end: pd.Timestamp validation: t.Tuple[pd.Timestamp, pd.Timestamp] holdout: t.Tuple[pd.Timestamp, pd.Timestamp] def __post_init__(self): object.__setattr__(self, "train_end", pd.Timestamp(self.train_end)) for attr in ("validation", "holdout"): start, end = getattr(self, attr) start, end = pd.Timestamp(start), pd.Timestamp(end) if end <= start: raise ValueError(f"{attr}: end must be after start") if start < self.train_end: raise ValueError(f"{attr} starts before train_end โ€” splits must not overlap training data") object.__setattr__(self, attr, (start, end))
[docs] def period(self, split: str) -> t.Tuple[pd.Timestamp, pd.Timestamp]: if split not in ("validation", "holdout"): raise ValueError(f"split must be 'validation' or 'holdout', got {split!r}") return getattr(self, split)
[docs] @dataclass(frozen=True) class RefScore: """One row of a competition's published final leaderboard.""" rank: int team: str score: float
[docs] @dataclass class Problem: """A benchmark problem. Environments are created fresh per run via :meth:`env` โ€” never share a live env between runs.""" name: str dataset: t.Union[Dataset, str, t.Callable[[], Dataset]] make_env: t.Callable[["Problem", str], "gym.Env"] objective: Objective schedule: IssueSchedule splits: Splits description: t.Optional[str] = None reference_scores: t.List[RefScore] = _dc_field(default_factory=list) _dataset_cache: t.Optional[Dataset] = _dc_field(default=None, repr=False, compare=False)
[docs] def load_dataset(self) -> Dataset: """Resolve the dataset (lazily, cached): a Dataset instance, an ``rb://`` manifest reference, or a zero-arg loader callable.""" if self._dataset_cache is None: ds = self.dataset if isinstance(ds, str): ds = Dataset.from_manifest(ds) elif callable(ds) and not isinstance(ds, Dataset): ds = ds() if not isinstance(ds, Dataset): raise TypeError(f"problem {self.name!r}: dataset resolved to {type(ds).__name__}") self._dataset_cache = ds return self._dataset_cache
[docs] def env(self, split: str = "validation") -> "gym.Env": """Build a fresh environment over the given split's origins.""" return self.make_env(self, split)
[docs] def origins(self, split: str = "validation"): start, end = self.splits.period(split) return self.schedule.origins(start, end)
[docs] def rank_of(self, score: float) -> t.Optional[int]: """1-based position ``score`` would have taken on the published leaderboard (None if no reference scores).""" if not self.reference_scores: return None beaten = sum(1 for r in self.reference_scores if self.objective.is_better(score, r.score)) return len(self.reference_scores) - beaten + 1
def __repr__(self): return f"Problem({self.name!r}, objective={self.objective.name})"