Data fit

DataFit / ArrayDataFit schemas for parameter estimation from data.

Schemas for data_fit.

class ionworks.pipeline.data_fit.ArrayDataFit(objectives, source='', parameters=None, cost=None, initial_guesses=None, optimizer=None, cost_logger=None, multistarts=None, objective_parallelism='auto', initial_guess_sampler=None, priors=None, options=None)[source]

Bases: DataFit

Fit the same model separately at each value of an independent variable.

Use this when you have one experiment repeated at different conditions — typically temperatures, C-rates, or pulse indices — and you want one fitted parameter set per condition rather than one global fit. objectives is keyed by the independent variable value ({298.15: ..., 313.15: ...}); each entry is fitted independently and the results can be post-processed to extract how parameters depend on the variable.

All other fields behave the same as DataFit.

objectives: Annotated[dict[Any, Annotated[CalendarAgeing | CurrentDriven | CycleAgeing | EIS | ElectrodeBalancing | ElectrodeBalancingHalfCell | MSMRFullCell | MSMRHalfCell | OCPHalfCell | Objective | Pulse | Resistance, FieldInfo(annotation=NoneType, required=True, discriminator='type'), WrapValidator(func=_resolve_objective, json_schema_input_type=PydanticUndefined)]] | Annotated[CalendarAgeing | CurrentDriven | CycleAgeing | EIS | ElectrodeBalancing | ElectrodeBalancingHalfCell | MSMRFullCell | MSMRHalfCell | OCPHalfCell | Objective | Pulse | Resistance, FieldInfo(annotation=NoneType, required=True, discriminator='type'), WrapValidator(func=_resolve_objective, json_schema_input_type=PydanticUndefined)], FieldInfo(annotation=NoneType, required=True, metadata=[_PydanticGeneralMetadata(union_mode='left_to_right')])]
model_config = {'arbitrary_types_allowed': True, 'extra': 'forbid', 'populate_by_name': True, 'validate_assignment': True}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

model_post_init(context: Any, /) → None

This function is meant to behave like a BaseModel method to initialize private attributes.

It takes context as an argument since that’s what pydantic-core passes when calling it.

Args:

self: The BaseModel instance. context: The context.

class ionworks.pipeline.data_fit.DataFit(objectives, source='', parameters=None, cost=None, initial_guesses=None, optimizer=None, cost_logger=None, multistarts=None, objective_parallelism='auto', initial_guess_sampler=None, priors=None, options=None)[source]

Bases: BaseSchema

Fit a model’s parameters to measured experimental data.

objectives name the experiments and measured curves to match, parameters the values free to move, and optional priors what you already believe about them. The other fields (cost, optimizer, initial_guesses, multistarts, …) tune how the fit runs; their defaults suit most fits.

Parameters:
  • objectives (objective or dict, or a mapping of name to either) – What to fit against. Either a single objective (a CurrentDriven, MSMRHalfCell, … from iw.pipeline.objectives) or a dict of named objectives if the fit spans multiple experiments.

  • source (str, optional) – Free-text label for the data source (paper, dataset name, instrument). Shown in reports and provenance records.

  • parameters (dict[str, Parameter | pybamm.Symbol | callable] | None, optional) –

    Which parameters are being fitted, and (optionally) how they relate to each other through pybamm expressions. At least one of parameters or priors must be set. Each value can be:

    • an iw.pipeline.Parameter object, e.g. iw.pipeline.Parameter("x")

    • a pybamm expression, in which case other referenced parameters must also be supplied as iw.pipeline.Parameter objects via pybamm.Parameter wrapping. For example:

      {
          "param": 2 * pybamm.Parameter("half-param"),
          "half-param": iw.pipeline.Parameter("half-param"),
      }
      

      works, but {"param": 2 * iw.pipeline.Parameter("half-param")} does not.

    • a function that constructs a pybamm expression referencing other parameters, which must again be explicitly supplied as iw.pipeline.Parameter objects:

      {
          "main parameter": lambda x: (
              pybamm.Parameter("other parameter") * x**2
          ),
          "other parameter": iw.pipeline.Parameter("other parameter"),
      }
      

    The dict key does not need to match the underlying pybamm parameter name — DataFit figures out which variable to fit from the iw.pipeline.Parameter reference.

  • cost (Cost or dict or None, optional) – How disagreement between model and data is summed up into a single number (e.g. sum-of-squares, log-likelihood). Give a cost schema instance (e.g. iw.pipeline.costs.SSE()) or a config dict ({"type": "SSE"}). Leave unset for a sensible default.

  • initial_guesses (dict[str, float] or list[dict[str, float]] or None, optional) – Starting point(s) for the optimiser. One dict applies to every restart; a list of dicts provides one starting point per restart.

  • optimizer (Optimizer or dict or None, optional) – Which optimisation algorithm to use (e.g. CMAES, PSO, ScipyMinimize). Leave unset for the default.

  • cost_logger (BaseSchema or dict or None, optional) – Optional logger that records the cost trajectory and parameter values across the fit, for later inspection.

  • multistarts (int | None, optional) – Number of independent restarts from different initial guesses. More restarts is more robust but takes longer.

  • objective_parallelism ({"auto", "on", "off"}, optional) – Whether to evaluate objectives in parallel. "auto" (default) lets the engine decide; "on"/"off" force or disable it. A debugging escape hatch.

  • initial_guess_sampler (DistributionSampler or dict or None, optional) – How to spread the multistart guesses across the parameter space (LatinHypercube by default).

  • priors (Prior or list[Prior] or dict or None, optional) – What you already believe about the parameter values. Acts as a regulariser on the fit. May be supplied alone (the prior names become the fit parameters) or alongside parameters (priors regularise the listed fit parameters).

  • options (DataFitOptions or dict or None, optional) – Runtime options for the fit — seed, low_memory, max_iterations, maxtime, validate, and skip_objective_callbacks. Pass a DataFitOptions or a plain dict; unknown keys are rejected. See DataFitOptions for each option’s meaning and default.

Examples

>>> # build the schema with the fields you care about
>>> obj = iw.pipeline.objectives.OCPHalfCell(
...     electrode="positive",
...     data_input="path/to/ocp.csv",
... )
>>> fit = iw.pipeline.DataFit(
...     objectives={"ocp": obj},
...     parameters={"Q_pe": iw.pipeline.Parameter(
...         "Positive electrode capacity [A.h]", initial_value=3.0, bounds=(2.0, 4.0),
...     )},
...     priors={"Q_pe": iw.pipeline.priors.Prior("Q_pe", iw.pipeline.stats.Normal(3.0, 0.2))},
... )
>>> config = iw.pipeline.Pipeline({"ocp fit": fit}).to_config()
>>> # then submit `config` via ionworks.api
objectives: Annotated[dict[str, Annotated[CalendarAgeing | CurrentDriven | CycleAgeing | EIS | ElectrodeBalancing | ElectrodeBalancingHalfCell | MSMRFullCell | MSMRHalfCell | OCPHalfCell | Objective | Pulse | Resistance, FieldInfo(annotation=NoneType, required=True, discriminator='type'), WrapValidator(func=_resolve_objective, json_schema_input_type=PydanticUndefined)]] | Annotated[CalendarAgeing | CurrentDriven | CycleAgeing | EIS | ElectrodeBalancing | ElectrodeBalancingHalfCell | MSMRFullCell | MSMRHalfCell | OCPHalfCell | Objective | Pulse | Resistance, FieldInfo(annotation=NoneType, required=True, discriminator='type'), WrapValidator(func=_resolve_objective, json_schema_input_type=PydanticUndefined)], FieldInfo(annotation=NoneType, required=True, metadata=[_PydanticGeneralMetadata(union_mode='left_to_right')])]
source: str
parameters: dict[str, Any] | None
cost: Annotated[RMSE | MAE | MSE | Max | SSE | Wasserstein | ChiSquare | MultiCost | GaussianLogLikelihood | DesignFunction, FieldInfo(annotation=NoneType, required=True, discriminator='type')] | None
initial_guesses: dict[str, int | float] | list[dict[str, int | float]] | None
optimizer: Annotated[DifferentialEvolution | CMAES | PSO | XNES | BayesianOptimization | SOBER | TuRBO | AskTellOptimizer | Nested | ScipyDifferentialEvolution | ScipyMinimize | ScipyLeastSquares | ScipyLsqLinear | ScipyBasinhopping | ScipyDualAnnealing | ScipyShgo | GridSearch | PointEstimateOptimizer | PointEstimateSampler | DummyOptimizer | DummySampler | PintsSampler, FieldInfo(annotation=NoneType, required=True, discriminator='type')] | None
cost_logger: Annotated[dict[str, Any] | BaseSchema | Any, FieldInfo(annotation=NoneType, required=True, metadata=[_PydanticGeneralMetadata(union_mode='left_to_right')])] | None
multistarts: int | None
objective_parallelism: Literal['auto', 'on', 'off']
initial_guess_sampler: Annotated[LatinHypercube | Uniform, FieldInfo(annotation=NoneType, required=True, discriminator='type'), WrapValidator(func=_resolve_sampler, json_schema_input_type=PydanticUndefined)] | None
priors: Annotated[dict[str, Any] | Prior | Any, FieldInfo(annotation=NoneType, required=True, metadata=[_PydanticGeneralMetadata(union_mode='left_to_right')])] | list[Annotated[dict[str, Any] | Prior | Any, FieldInfo(annotation=NoneType, required=True, metadata=[_PydanticGeneralMetadata(union_mode='left_to_right')])]] | None
options: DataFitOptions | None
wrap_bare_objective()[source]

Wrap a bare objective in a dict for a consistent internal shape.

Only applies to DataFit, not ArrayDataFit (which requires a dict keyed by independent variable values).

validate_parameters_or_priors()[source]

At least one of parameters or priors must be supplied.

The runtime accepts both together — priors then act as regularizers on the listed fit parameters — so we mirror the runtime here rather than enforce a stricter mutual exclusion at the schema boundary.

model_config = {'arbitrary_types_allowed': True, 'extra': 'forbid', 'populate_by_name': True, 'validate_assignment': True}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

model_post_init(context: Any, /) → None

This function is meant to behave like a BaseModel method to initialize private attributes.

It takes context as an argument since that’s what pydantic-core passes when calling it.

Args:

self: The BaseModel instance. context: The context.

class ionworks.pipeline.data_fit.DataFitOptions(*, seed: Annotated[int | None, Ge(ge=0), Le(le=4294967295)] = None, low_memory: bool | None = None, max_iterations: Annotated[int | None, Gt(gt=0)] = None, maxtime: Annotated[float | None, Gt(gt=0), AllowInfNan(allow_inf_nan=False)] = None, validate: bool = True, skip_objective_callbacks: bool = False)[source]

Bases: BaseSchema

Runtime options for a DataFit.

Tunes how a fit executes rather than what it fits. Every field is optional; unknown keys are rejected so a misplaced option (for example multistarts, which is a DataFit field rather than an option) fails when you build the config instead of part-way through the job.

Parameters:
  • seed (int, optional) – Random seed, for reproducibility. Must be in [0, 2**32 - 1], the range numpy accepts. When unset the engine generates one from the current time.

  • low_memory (bool, optional) – Reduce log size by appending an entry only when the cost improves the best-so-far by at least 0.1%. When unset the engine enables it for deterministic optimizers and disables it for probabilistic ones.

  • max_iterations (int, optional) – Maximum iterations per optimization job. Must be positive. Only takes effect when the model’s convert_to_format is "casadi".

  • maxtime (float, optional) – Maximum wall time in seconds per optimization job. Must be positive and finite. With multistarts the total may exceed this, since many jobs run. Only takes effect when the model’s convert_to_format is "casadi".

  • validate (bool, optional) – Check, before the fit starts, that every fit parameter is actually used by at least one objective’s model. Catches a misspelt or orphaned parameter name up front rather than after a fit that could never move it. Defaults to True.

  • skip_objective_callbacks (bool, optional) – Skip the per-objective callbacks that capture the initial and final fit results. Improves performance when those results are not needed. Defaults to False.

Examples

>>> options = iw.pipeline.DataFitOptions(seed=42, max_iterations=500)
>>> options.to_config()
{'seed': 42, 'max_iterations': 500}
seed: int | None
low_memory: bool | None
max_iterations: int | None
maxtime: float | None
validate_: bool
skip_objective_callbacks: bool
model_config = {'arbitrary_types_allowed': True, 'extra': 'forbid', 'populate_by_name': True, 'validate_assignment': True}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

model_post_init(context: Any, /) → None

This function is meant to behave like a BaseModel method to initialize private attributes.

It takes context as an argument since that’s what pydantic-core passes when calling it.

Args:

self: The BaseModel instance. context: The context.