Source code for ionworks.pipeline.validation

"""Schema for Validation pipeline element."""

from pydantic import Field

from .base import BaseSchema
from .objective_functions.objective_functions import CostUnion
from .objectives.objectives import ObjectiveUnion

# Each objective is validated by the discriminated ``ObjectiveUnion`` — dispatched
# on its ``type`` to the concrete leaf; unknown/base types and bogus keys reject.
# Summary stats are cost functions discriminated by ``type``; the trailing
# ``str`` arm mirrors the ``cost`` fields (bare-name shorthand parity).
_SummaryStatLike = CostUnion | str


[docs] class Validation(BaseSchema): """Check a fitted model against held-out experimental data. A ``Validation`` step takes the parameters produced earlier in the pipeline, simulates the experiments listed in ``objectives``, and compares those simulations to the measured data. The result tells you how well the model generalises beyond the data you fit on. Each ``objective`` describes one comparison (e.g. "current vs. time for this discharge"). The ``summary_stats`` list controls which scalar error metrics — RMSE, MAE, max error, … — get reported alongside the full time-series comparison. Parameters ---------- objectives : dict of name to objective or dict One entry per experiment you want to compare against. The key is a human-readable label (used in the report); the value is the objective describing what to simulate and what to compare. summary_stats : list[Cost | dict], optional Which scalar error metrics to report (e.g. ``RMSE()``, ``MAE()``, ``Max()``). If you leave this unset, sensible defaults are filled in for fitting-style objectives so the report carries the same physical units as the measurements. Examples -------- >>> obj1 = iw.pipeline.objectives.CurrentDriven( ... data_input="path/to/cycle_1C.csv", options={"model": "SPM"} ... ) >>> obj2 = iw.pipeline.objectives.CurrentDriven( ... data_input="path/to/cycle_C2.csv", options={"model": "SPM"} ... ) >>> val = iw.pipeline.Validation( ... objectives={"1C": obj1, "C/2": obj2}, ... summary_stats=[iw.pipeline.costs.RMSE(), iw.pipeline.costs.MAE()], ... ) >>> config = iw.pipeline.Pipeline({"validate": val}).to_config() >>> # then submit `config` via ionworks.api """ objectives: dict[str, ObjectiveUnion] = Field( ..., description=( "One entry per experiment you want to validate against. " "The key is the label that appears in the validation " "report; the value is an ``iw.pipeline.objectives.*`` instance " "(e.g. ``CurrentDriven``, ``MSMRHalfCell``) describing " "what to simulate and which measured curve to compare to." ), ) summary_stats: list[_SummaryStatLike] | None = Field( default=None, description=( "Scalar error metrics (e.g. ``RMSE()``, ``MAE()``, " "``Max()``) to report alongside the time-series comparison. " "Leave unset to get sensible defaults for fitting " "objectives — the metrics will then carry the same physical " "units as the underlying measurements." ), ) def __init__( self, objectives: dict[str, ObjectiveUnion], summary_stats: list[_SummaryStatLike] | None = None, ): super().__init__(objectives=objectives, summary_stats=summary_stats)