Source code for tropt.tracker.base
from __future__ import annotations
import logging
from abc import ABC, abstractmethod
from typing import Optional
logger = logging.getLogger(__name__)
DEFAULT_EXPERIMENT_NAME = "tropt_experiment"
[docs]
class BaseTracker(ABC):
"""Interface for experiment trackers.
Lifecycle (managed by ``BaseOptimizer``'s wrapper)::
__init__() # by the user: stores backend config, no run started
init(config) # by the optimizer wrapper: opens a run (now stateful)
log(data) # per optimization step
finish(summary) # by the optimizer wrapper: closes the run (back to stateless)
The tracker can be reused across multiple runs: after ``finish()``,
a subsequent ``init()`` opens a fresh run on the same backend.
Subclasses must implement ``_init``, ``_log``, and ``_finish``.
The base class manages their guards.
"""
def __init__(
self,
experiment_name: str = DEFAULT_EXPERIMENT_NAME,
experiment_config: Optional[dict] = None,
):
self.experiment_name = experiment_name
self.experiment_config = experiment_config
self._active = False
# ── Public interface (guarded) ──────────────────────────────────────
[docs]
def init(self, config: Optional[dict] = None):
"""Open a new run. Called by the optimizer wrapper before optimization.
Merges the user's ``experiment_config`` (from ``__init__``) with the
optimizer-generated ``config`` and forwards to ``_init``.
"""
if self._active:
logger.warning(
"init() called on already-active tracker (previous run not finished). "
"Auto-finishing the previous run."
)
self.finish()
self._active = True
merged = {**(self.experiment_config or {}), **(config or {})} or None
self._init(merged)
[docs]
def log(self, data: dict):
"""Log per-step data. Must be called between ``init`` and ``finish``."""
assert self._active, "log() called on inactive tracker (missing init()?)"
self._log(data)
[docs]
def finish(self, summary: Optional[dict] = None):
"""Close the current run, optionally logging a final summary."""
if not self._active:
logger.warning("finish() called on inactive tracker — ignoring.")
return
self._finish(summary)
self._active = False
# ── Subclass hooks ──────────────────────────────────────────────────
@abstractmethod
def _init(self, config: Optional[dict] = None):
"""Open a run on the backend. ``config`` contains run metadata/hparams."""
...
@abstractmethod
def _log(self, data: dict):
"""Write one step's data to the backend."""
...
@abstractmethod
def _finish(self, summary: Optional[dict] = None):
"""Close the run, writing ``summary`` if provided."""
...