Source code for routee.powertrain.estimators.estimator_interface
from __future__ import annotations
from abc import ABC, abstractmethod
from typing import List, Literal, Optional
import pandas as pd
from pydantic import BaseModel, ConfigDict
from routee.powertrain.core.features import DataColumn
from routee.powertrain.core.model_config import ModelConfig
PadStrategy = Literal["zero", "repeat_first"]
[docs]
class ColumnSpec(BaseModel):
"""Identity of one positional column in an estimator's input or output tensor.
Carries the column ``name`` plus its ``units`` and ``dtype`` so a consumer
holding only the serialized binary can both order its inputs correctly and
interpret/convert their values.
"""
model_config = ConfigDict(frozen=True)
name: str
units: Optional[str] = None
dtype: Optional[str] = None
@classmethod
def from_data_column(cls, column: DataColumn) -> "ColumnSpec":
return cls(name=column.name, units=column.units, dtype=column.dtype)
[docs]
class Estimator(ABC):
"""Abstract base class for all estimator backends."""
#: File extension used when serializing this estimator's binary in a ZIP archive.
file_extension: str
#: Backing store for :attr:`input_spec`. A class-level default (the frozen,
#: hence shareable, empty contract) so estimators that never set one still
#: read cleanly.
_input_spec: InputSpec = InputSpec()
@property
def input_spec(self) -> InputSpec:
"""The input/output contract this estimator implements.
Subclasses set the windowing fields at construction; the trainer stamps
the ordered input/output columns via :meth:`bind_io_contract`.
"""
return self._input_spec
@input_spec.setter
def input_spec(self, spec: InputSpec) -> None:
self._input_spec = spec
[docs]
def output_column_specs(self, config: ModelConfig) -> List[ColumnSpec]:
"""Positional output tensor columns this estimator emits.
Default: one column per energy target, in order. Estimators that emit
extra columns (e.g. per-target uncertainty) override this.
"""
return [ColumnSpec.from_data_column(t) for t in config.target.targets]
[docs]
def bind_io_contract(self, config: ModelConfig) -> None:
"""Stamp the input/output contract derived from ``config`` onto ``input_spec``.
Preserves any windowing fields already set by the trainer and records the
ordered input columns (features, plus distance for RAW), the ordered
output columns, the predict method, and the distance column — so the
serialized binary and metadata are self-describing and a consumer never
has to guess the positional order.
"""
self.input_spec = self.input_spec.model_copy(
update={
"input_columns": [
ColumnSpec.from_data_column(c) for c in config.all_features
],
"output_columns": self.output_column_specs(config),
"predict_method": config.predict_method.value,
"distance_column": config.distance.name,
}
)
[docs]
@abstractmethod
def to_bytes(self) -> bytes:
"""
Serialize the estimator to raw bytes (native binary format).
This is the estimator's *only* serialization primitive. Estimators are
not independently persistable artifacts: a model on disk is always an
estimator binary paired with its ``metadata.json`` sidecar, and that
pairing — along with the required input/output contract and the instance
digest — is enforced exclusively at the ``Model`` save/load choke points
(see ``routee.powertrain.io.archive``). Persist via ``Model.to_file`` /
``Model.from_file``, never by writing these bytes directly.
"""
[docs]
@classmethod
@abstractmethod
def from_bytes(cls, data: bytes) -> Estimator:
"""
Deserialize an estimator from raw bytes.
"""
[docs]
@abstractmethod
def predict(
self,
links_df: pd.DataFrame,
config: ModelConfig,
) -> pd.DataFrame:
"""
Predict absolute energy consumption for each link.
Args:
links_df: the input dataframe. Must contain every column in
``config.feature_set`` plus (if ``predict_method == RAW``) the
distance column, and (if ``input_spec.grouping_column`` is set)
the grouping column.
config: the model's ``ModelConfig``. Estimators read ``feature_set``,
``distance``, ``target`` and ``predict_method`` from here.
"""