Objectives

Experiment-specific objective schemas.

Schemas for objectives.

class ionworks.pipeline.objectives.BaseObjective(options=None, callbacks=None, custom_parameters=None, cost=None, constraints=None, penalties=None, parameters=None)[source]

Bases: BaseSchema

Shared base for every objective. Not used directly.

Concrete subclasses (Pulse, EIS, CycleAgeing, MSMRHalfCell, DesignObjective, …) inherit from here. An objective tells the pipeline what to fit (or optimize) and which data to compare against — pick a concrete subclass that matches your experiment.

Parameters:
  • options (dict, optional) – Objective-specific settings. The supported keys depend on the subclass — see each subclass’s docstring for the list.

  • callbacks (Callback or list of Callback, optional) – Callback(s) that run at various points during the fit (logging, plotting, early stopping).

  • cost (Cost or dict, optional) – The cost function used to score the fit (e.g. RMSE, MAE, GaussianLogLikelihood). If None, the optimizer’s default cost is used.

  • constraints (list of Constraint, optional) – Equality or inequality constraints that must hold during the fit.

  • penalties (list of Penalty, optional) – Soft penalties added to the cost.

  • parameters (dict or pybamm.ParameterValues, optional) – Parameter overrides applied only to this objective.

options: dict[str, Any] | None
callbacks: Any | None
cost: Annotated[RMSE | MAE | MSE | Max | SSE | Wasserstein | ChiSquare | MultiCost | GaussianLogLikelihood | DesignFunction, FieldInfo(annotation=NoneType, required=True, discriminator='type')] | None
constraints: list[Constraint] | None
penalties: list[Penalty] | None
parameters: Annotated[dict[str, Any] | ParameterValues, BeforeValidator(func=serialize_parameters, json_schema_input_type=PydanticUndefined)] | None
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.objectives.CalendarAgeing(data=None, options=None, callbacks=None, custom_parameters=None, constraints=None, penalties=None, parameters=None, *, cost: Annotated[RMSE | MAE | MSE | Max | SSE | Wasserstein | ChiSquare | MultiCost | GaussianLogLikelihood | DesignFunction, FieldInfo(annotation=NoneType, required=True, discriminator='type')] | None = None, type: Literal['CalendarAgeing'] = 'CalendarAgeing')[source]

Bases: SimulationObjective

Fit degradation parameters (loss of lithium inventory, loss of active material) to calendar-ageing data for a full cell.

Parameters:

Examples

>>> obj = iw.pipeline.objectives.CalendarAgeing(
...     data_input="path/to/calendar.csv",
...     options={"model": "SPM", "modes": ["LLI [%]", "LAM_pe [%]"]},
... )
>>> # slot into a DataFit (parameters omitted for brevity)
type: Literal['CalendarAgeing']
data_input: Annotated[str | DataFrame | DataFrame | Annotated[object, PlainValidator(func=_as_loader, json_schema_input_type=Any)] | TimeSeriesSpec | DataPayloadSpec | MetadataSpec | dict[str, Any], BeforeValidator(func=_route_measurement_input, json_schema_input_type=PydanticUndefined)]
options: CalendarAgeingOptions | None
callbacks: Any | None
constraints: list[Constraint] | None
penalties: list[Penalty] | None
parameters: Annotated[dict[str, Any] | ParameterValues, BeforeValidator(func=serialize_parameters, json_schema_input_type=PydanticUndefined)] | None
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.objectives.CalendarAgeingOptions(*, model: Any | None = None, parameterized_model_id: str | None = None, modes: list[Literal['LLI [%]', 'LAM_ne [%]', 'LAM_pe [%]']] | None = None, simulation_kwargs: dict[str, Any] | None = None)[source]

Bases: _ObjectiveOptions

Options for a CalendarAgeing objective.

model: Any | None
parameterized_model_id: str | None
modes: list[Literal['LLI [%]', 'LAM_ne [%]', 'LAM_pe [%]']] | None
simulation_kwargs: dict[str, Any] | None
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.objectives.CurrentDriven(data=None, options=None, callbacks=None, custom_parameters=None, constraints=None, penalties=None, parameters=None, *, cost: Annotated[RMSE | MAE | MSE | Max | SSE | Wasserstein | ChiSquare | MultiCost | GaussianLogLikelihood | DesignFunction, FieldInfo(annotation=NoneType, required=True, discriminator='type')] | None = None, type: Literal['CurrentDriven'] = 'CurrentDriven')[source]

Bases: SimulationObjective

Fit a model against any current-driven experiment — pulse trains, drive cycles, custom load profiles.

Pass in the recorded current/voltage data along with the model and the objective will simulate the same current profile and compare voltages (or any other variable you ask for).

Parameters:

Examples

>>> obj = iw.pipeline.objectives.CurrentDriven(
...     data_input="path/to/drive_cycle.csv",
...     options={"model": "SPM", "objective variables": ["Voltage [V]"]},
... )
>>> # slot into a DataFit (parameters omitted for brevity)
type: Literal['CurrentDriven']
data_input: Annotated[str | DataFrame | DataFrame | Annotated[object, PlainValidator(func=_as_loader, json_schema_input_type=Any)] | TimeSeriesSpec | DataPayloadSpec | MetadataSpec | dict[str, Any], BeforeValidator(func=_route_measurement_input, json_schema_input_type=PydanticUndefined)]
options: CurrentDrivenOptions | None
callbacks: Any | None
constraints: list[Constraint] | None
penalties: list[Penalty] | None
parameters: Annotated[dict[str, Any] | ParameterValues, BeforeValidator(func=serialize_parameters, json_schema_input_type=PydanticUndefined)] | None
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.objectives.CurrentDrivenOptions(*, model: Any | None = None, parameterized_model_id: str | None = None, independent_variable: Literal['time', 'voltage'] = 'time', simulation_kwargs: dict[str, Any] | None = None, objective_variables: list[str] | None = None, interpolant_atol: float | None = None, interpolant_rtol: float | None = None, interpolant_lossless: bool = False)[source]

Bases: _ObjectiveOptions

Options for a CurrentDriven objective.

model: Any | None
parameterized_model_id: str | None
independent_variable: Literal['time', 'voltage']
simulation_kwargs: dict[str, Any] | None
objective_variables: list[str] | None
interpolant_atol: float | None
interpolant_rtol: float | None
interpolant_lossless: 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.

class ionworks.pipeline.objectives.CycleAgeing(data=None, options=None, callbacks=None, custom_parameters=None, constraints=None, penalties=None, parameters=None, *, cost: Annotated[RMSE | MAE | MSE | Max | SSE | Wasserstein | ChiSquare | MultiCost | GaussianLogLikelihood | DesignFunction, FieldInfo(annotation=NoneType, required=True, discriminator='type')] | None = None, type: Literal['CycleAgeing'] = 'CycleAgeing')[source]

Bases: SimulationObjective

Fit summary variables (capacity, resistance, LLI, LAM) against cell-cycling data.

Each row in the data is one summary measurement across a cycle (or a small group of cycles). The objective runs the requested cycling experiment, extracts the same summary variables from the simulation, and compares the two.

Parameters:
  • data_input (DataLoader, DataFrame, str, or dict) – The cycling summary data — see FittingObjective. One row per summary measurement, so a per-cycle table — a measurement’s stored analysis is the usual platform source, and is a different object from the measurement whose steps define the protocol. A row names the cycle its quantity was measured in, so a quantity measured later in the same test occupies its own row. A target column may be sparse: a NaN says there is no measured value at that cycle, and that row is left out of the fit for that variable alone, so only the measured points are scored.

  • options (CycleAgeingOptions or dict, optional) – See CycleAgeingOptions for the available keys.

  • callbacks – Shared with BaseObjective.

  • constraints – Shared with BaseObjective.

  • penalties – Shared with BaseObjective.

  • parameters – Shared with BaseObjective.

Examples

>>> obj = iw.pipeline.objectives.CycleAgeing(
...     data_input="path/to/cycling_summary.csv",
...     options={
...         "model": "SPM",
...         "objective variables": ["Discharge capacity [A.h]"],
...     },
... )
>>> # slot into a DataFit (parameters omitted for brevity)
type: Literal['CycleAgeing']
data_input: Annotated[str | DataFrame | DataFrame | Annotated[object, PlainValidator(func=_as_loader, json_schema_input_type=Any)] | TimeSeriesSpec | DataPayloadSpec | MetadataSpec | dict[str, Any], BeforeValidator(func=_route_measurement_input, json_schema_input_type=PydanticUndefined)]
options: CycleAgeingOptions | None
callbacks: Any | None
constraints: list[Constraint] | None
penalties: list[Penalty] | None
parameters: Annotated[dict[str, Any] | ParameterValues, BeforeValidator(func=serialize_parameters, json_schema_input_type=PydanticUndefined)] | None
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.objectives.CycleAgeingOptions(*, model: Any | None = None, parameterized_model_id: str | None = None, experiment: Any | None = None, termination: Literal['duration', 'events'] | None = None, use_cv: bool | None = None, split_cc_cv: bool | None = None, objective_variables: list[str] | None = None, metrics: dict[str, Any] | None = None, simulation_kwargs: dict[str, Any] | None = None)[source]

Bases: _ObjectiveOptions

Options for a CycleAgeing objective.

model: Any | None
parameterized_model_id: str | None
experiment: Any | None
termination: Literal['duration', 'events'] | None
use_cv: bool | None
split_cc_cv: bool | None
objective_variables: list[str] | None
metrics: dict[str, Any] | None
simulation_kwargs: dict[str, Any] | None
classmethod wrap_dataloader_experiment(value)[source]

Put a DataLoader experiment in the from data form consumers read.

A DataLoader serializes to a bare data config, but every consumer dispatches on a "from data" key: an unwrapped reference is resolved by nobody and then rejected by the parser.

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.objectives.DesignObjective(actions, constraints=None, options=None, callbacks=None, custom_parameters=None, cost=None, validate_against_experiment_steps=True, output_variables_full=None, save_at_cycles=None, penalties=None, parameters=None)[source]

Bases: BaseObjective

Objective for design optimisation — maximise (or minimise) one or more cell metrics by adjusting design parameters.

You describe each design target as an Action wrapping a Metric (e.g. maximise energy density, hit a target fast-charge time) and pass them in via actions. The optimiser then searches the parameter space to find the design that best satisfies all of them, subject to any constraints you specify.

The objective handles simulation failures and edge-case parameter combinations automatically — failed simulations receive large penalties so the optimiser steers away from those regions.

Parameters:
  • actions (dict[str, Any]) – Mapping of action name to an Action-wrapped Metric defining each design target.

  • constraints (dict[str, BaseAction] or list of Constraint, optional) – Hard limits on the design. For design optimization this may be either a dict of Action-wrapped metrics (e.g. minimum capacity) or a list of Constraint terms on pybamm symbols.

  • options (dict, optional) – Settings for the design simulation (model, experiment, simulation kwargs).

  • callbacks (Callback or list of Callback, optional) – Callback(s) invoked at various points during optimisation.

  • cost (Cost or dict, optional) – Cost function used to combine the actions into a single design score. If None, the optimizer’s default design cost is used.

  • penalties (list of Penalty, optional) – Soft penalties added to the cost.

  • parameters (dict or pybamm.ParameterValues, optional) – Parameter overrides applied only to this objective.

actions: dict[str, Any]
constraints: dict[str, Any] | list[Constraint] | None
options: dict[str, Any] | None
callbacks: Any | None
cost: Annotated[RMSE | MAE | MSE | Max | SSE | Wasserstein | ChiSquare | MultiCost | GaussianLogLikelihood | DesignFunction, FieldInfo(annotation=NoneType, required=True, discriminator='type')] | None
validate_against_experiment_steps: bool
output_variables_full: list[str] | None
save_at_cycles: list[int] | None
penalties: list[Penalty] | None
parameters: Annotated[dict[str, Any] | ParameterValues, BeforeValidator(func=serialize_parameters, json_schema_input_type=PydanticUndefined)] | None
to_config() → dict[source]

Build the dict you submit through ionworks.api.

Same as the base to_config, with actions folded under the "options" key so the payload matches the structure the Ionworks API expects for cycling objectives.

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.objectives.EIS(data=None, options=None, callbacks=None, custom_parameters=None, constraints=None, penalties=None, parameters=None, *, cost: Annotated[RMSE | MAE | MSE | Max | SSE | Wasserstein | ChiSquare | MultiCost | GaussianLogLikelihood | DesignFunction, FieldInfo(annotation=NoneType, required=True, discriminator='type')] | None = None, type: Literal['EIS'] = 'EIS')[source]

Bases: SimulationObjective

Fit a model against electrochemical impedance spectroscopy (EIS) data.

Simulates the model response at the supplied frequencies and compares the predicted impedance to the measured spectrum. Uses pybamm’s frequency-domain EIS simulator under the hood.

Parameters:

Examples

>>> obj = iw.pipeline.objectives.EIS(data_input="path/to/eis.csv", options={"model": "SPM"})
>>> # slot into a DataFit (parameters omitted for brevity)
type: Literal['EIS']
data_input: Annotated[str | DataFrame | DataFrame | Annotated[object, PlainValidator(func=_as_loader, json_schema_input_type=Any)] | TimeSeriesSpec | DataPayloadSpec | MetadataSpec | dict[str, Any], BeforeValidator(func=_route_measurement_input, json_schema_input_type=PydanticUndefined)]
options: EISOptions | None
callbacks: Any | None
constraints: list[Constraint] | None
penalties: list[Penalty] | None
parameters: Annotated[dict[str, Any] | ParameterValues, BeforeValidator(func=serialize_parameters, json_schema_input_type=PydanticUndefined)] | None
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.objectives.EISOptions(*, model: Any | None = None, parameterized_model_id: str | None = None, simulation_kwargs: dict[str, Any] | None = None)[source]

Bases: _ObjectiveOptions

Options for an EIS objective.

model: Any | None
parameterized_model_id: str | None
simulation_kwargs: dict[str, Any] | None
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.objectives.ElectrodeBalancing(data=None, options=None, callbacks=None, custom_parameters=None, constraints=None, penalties=None, parameters=None, *, cost: Annotated[RMSE | MAE | MSE | Max | SSE | Wasserstein | ChiSquare | MultiCost | GaussianLogLikelihood | DesignFunction, FieldInfo(annotation=NoneType, required=True, discriminator='type')] | None = None, type: Literal['ElectrodeBalancing'] = 'ElectrodeBalancing')[source]

Bases: FittingObjective

Find the electrode capacities and stoichiometry windows that best reconstruct a full-cell OCV curve from the underlying half-cell OCPs.

Parameters:

Examples

>>> obj = iw.pipeline.objectives.ElectrodeBalancing(
...     data_input="path/to/full_cell_ocv.csv",
...     options={"direction": "discharge"},
... )
>>> # slot into a DataFit (parameters omitted for brevity)
type: Literal['ElectrodeBalancing']
data_input: Annotated[str | DataFrame | DataFrame | Annotated[object, PlainValidator(func=_as_loader, json_schema_input_type=Any)] | TimeSeriesSpec | DataPayloadSpec | MetadataSpec | dict[str, Any], BeforeValidator(func=_route_measurement_input, json_schema_input_type=PydanticUndefined)]
options: ElectrodeBalancingOptions | None
callbacks: Any | None
constraints: list[Constraint] | None
penalties: list[Penalty] | None
parameters: Annotated[dict[str, Any] | ParameterValues, BeforeValidator(func=serialize_parameters, json_schema_input_type=PydanticUndefined)] | None
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.objectives.ElectrodeBalancingHalfCell(electrode=None, data=None, options=None, callbacks=None, custom_parameters=None, constraints=None, penalties=None, parameters=None, *, cost: Annotated[RMSE | MAE | MSE | Max | SSE | Wasserstein | ChiSquare | MultiCost | GaussianLogLikelihood | DesignFunction, FieldInfo(annotation=NoneType, required=True, discriminator='type')] | None = None, type: Literal['ElectrodeBalancingHalfCell'] = 'ElectrodeBalancingHalfCell')[source]

Bases: FittingObjective

Find the starting capacity and total capacity of one electrode, given a known OCV function for that electrode.

Useful for aligning a fresh experiment (for example a GITT scan) to a previously measured OCV curve — the fit slides and scales the data so it lines up with the OCV in stoichiometry.

Parameters:

Examples

>>> obj = iw.pipeline.objectives.ElectrodeBalancingHalfCell(
...     electrode="positive",
...     data_input="path/to/half_cell.csv",
... )
>>> # slot into a DataFit (parameters omitted for brevity)
type: Literal['ElectrodeBalancingHalfCell']
electrode: Literal['positive', 'negative']
data_input: Annotated[str | DataFrame | DataFrame | Annotated[object, PlainValidator(func=_as_loader, json_schema_input_type=Any)] | TimeSeriesSpec | DataPayloadSpec | MetadataSpec | dict[str, Any], BeforeValidator(func=_route_measurement_input, json_schema_input_type=PydanticUndefined)]
options: ElectrodeBalancingHalfCellOptions | None
callbacks: Any | None
constraints: list[Constraint] | None
penalties: list[Penalty] | None
parameters: Annotated[dict[str, Any] | ParameterValues, BeforeValidator(func=serialize_parameters, json_schema_input_type=PydanticUndefined)] | None
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.objectives.ElectrodeBalancingHalfCellOptions(*, direction: Literal['lithiation', 'delithiation'] | None = None, GITT: bool = False, dudq_cutoff: float | None = None, objective_variables: list[str] | None = None)[source]

Bases: _ObjectiveOptions

Options for an ElectrodeBalancingHalfCell objective.

direction: Literal['lithiation', 'delithiation'] | None
gitt: bool
dudq_cutoff: float | None
objective_variables: list[str] | None
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.objectives.ElectrodeBalancingOptions(*, dudq_cutoff: float | None = None, dqdu_cutoff: float | None = None, direction: Literal['charge', 'discharge'] | None = None, GITT: bool = False, dqdu_model_axis: bool = False, objective_variables: list[str] | None = None)[source]

Bases: _ObjectiveOptions

Options for an ElectrodeBalancing objective.

dudq_cutoff: float | None
dqdu_cutoff: float | None
direction: Literal['charge', 'discharge'] | None
gitt: bool
dqdu_model_axis: bool
objective_variables: list[str] | None
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.objectives.FittingObjective(data=None, options=None, callbacks=None, custom_parameters=None, constraints=None, penalties=None, parameters=None, *, cost: Annotated[RMSE | MAE | MSE | Max | SSE | Wasserstein | ChiSquare | MultiCost | GaussianLogLikelihood | DesignFunction, FieldInfo(annotation=NoneType, required=True, discriminator='type')] | None = None)[source]

Bases: BaseObjective

Shared base for objectives that fit a model to measured data.

On top of BaseObjective this adds data_input — the experimental data the model is compared against. Pick a concrete subclass that matches your experiment (Pulse, EIS, OCPHalfCell, CycleAgeing, …).

Parameters:
  • data_input (DataLoader, DataFrame, str, or dict) – The measured data. Accepts an ionworks.data.DataLoader (constructed directly from DataFrames, or via DataLoader.from_local(path) / DataLoader.from_db(measurement_id)), a path to a data file, a pandas or polars DataFrame, or a dict with keys data (DataFrame of raw values) and metadata (dict describing the experiment).

  • options (dict, optional) – Objective-specific settings — see each subclass for supported keys.

  • callbacks (Callback or list of Callback, optional) – Callback(s) invoked at various points during the fit.

  • cost (Cost or dict, optional) – Cost function used to score the fit. If None, the optimizer’s default cost is used.

  • constraints (list of Constraint, optional) – Equality or inequality constraints that must hold during the fit.

  • penalties (list of Penalty, optional) – Soft penalties added to the cost.

  • parameters (dict or pybamm.ParameterValues, optional) – Parameter overrides applied only to this objective.

data_input: Annotated[str | DataFrame | DataFrame | Annotated[object, PlainValidator(func=_as_loader, json_schema_input_type=Any)] | TimeSeriesSpec | DataPayloadSpec | MetadataSpec | dict[str, Any], BeforeValidator(func=_route_measurement_input, json_schema_input_type=PydanticUndefined)]
options: dict[str, Any] | None
callbacks: Any | None
cost: Annotated[RMSE | MAE | MSE | Max | SSE | Wasserstein | ChiSquare | MultiCost | GaussianLogLikelihood | DesignFunction, FieldInfo(annotation=NoneType, required=True, discriminator='type')] | None
constraints: list[Constraint] | None
penalties: list[Penalty] | None
parameters: Annotated[dict[str, Any] | ParameterValues, BeforeValidator(func=serialize_parameters, json_schema_input_type=PydanticUndefined)] | None
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.objectives.MSMRFullCell(data=None, options=None, callbacks=None, custom_parameters=None, constraints=None, penalties=None, parameters=None, *, cost: Annotated[RMSE | MAE | MSE | Max | SSE | Wasserstein | ChiSquare | MultiCost | GaussianLogLikelihood | DesignFunction, FieldInfo(annotation=NoneType, required=True, discriminator='type')] | None = None, type: Literal['MSMRFullCell'] = 'MSMRFullCell')[source]

Bases: FittingObjective

Fit an MSMR model to full-cell open-circuit voltage data.

Compares both the capacity-vs-voltage curve and the differential voltage dU/dQ of the full cell — fitting both together gives much tighter constraints on the underlying half-cell MSMR parameters than fitting the OCV alone [1].

Parameters:

References

Examples

>>> obj = iw.pipeline.objectives.MSMRFullCell(
...     data_input="path/to/full_cell_ocv.csv",
...     options={
...         "negative voltage limits": (0.005, 1.5),
...         "positive voltage limits": (3.0, 4.3),
...     },
... )
>>> # slot into a DataFit (parameters omitted for brevity)
type: Literal['MSMRFullCell']
data_input: Annotated[str | DataFrame | DataFrame | Annotated[object, PlainValidator(func=_as_loader, json_schema_input_type=Any)] | TimeSeriesSpec | DataPayloadSpec | MetadataSpec | dict[str, Any], BeforeValidator(func=_route_measurement_input, json_schema_input_type=PydanticUndefined)]
options: MSMRFullCellOptions | None
callbacks: Any | None
constraints: list[Constraint] | None
penalties: list[Penalty] | None
parameters: Annotated[dict[str, Any] | ParameterValues, BeforeValidator(func=serialize_parameters, json_schema_input_type=PydanticUndefined)] | None
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.objectives.MSMRFullCellOptions(*, model: Any | None = None, dudq_cutoff: Literal['none'] | float | None = None, dqdu_cutoff: Literal['none'] | float | None = None, negative_voltage_limits: tuple[float, float] | None = None, positive_voltage_limits: tuple[float, float] | None = None, objective_variables: list[str] | None = None)[source]

Bases: _ObjectiveOptions

Options for an MSMRFullCell objective.

model: Any | None
dudq_cutoff: Literal['none'] | float | None
dqdu_cutoff: Literal['none'] | float | None
negative_voltage_limits: tuple[float, float] | None
positive_voltage_limits: tuple[float, float] | None
objective_variables: list[str] | None
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.objectives.MSMRHalfCell(data=None, options=None, callbacks=None, custom_parameters=None, constraints=None, penalties=None, parameters=None, *, cost: Annotated[RMSE | MAE | MSE | Max | SSE | Wasserstein | ChiSquare | MultiCost | GaussianLogLikelihood | DesignFunction, FieldInfo(annotation=NoneType, required=True, discriminator='type')] | None = None, type: Literal['MSMRHalfCell'] = 'MSMRHalfCell')[source]

Bases: FittingObjective

Fit an MSMR model to half-cell open-circuit potential data.

Compares the capacity-vs-voltage curve and (optionally) the differential voltage dU/dQ of one electrode to a measured half-cell OCP.

Capacity [A.h] is used as given: its origin is carried by {Electrode} electrode lower excess capacity [A.h], not re-zeroed away. Seed that parameter from the data (-min(q)) and bound it of order the capacity span; a library value is in another frame.

Parameters:

Examples

>>> obj = iw.pipeline.objectives.MSMRHalfCell(
...     data_input="path/to/half_cell.csv",
...     options={"model": iw.pipeline.models.MSMRHalfCellModel(electrode="positive")},
... )
>>> # slot into a DataFit (parameters omitted for brevity)
type: Literal['MSMRHalfCell']
data_input: Annotated[str | DataFrame | DataFrame | Annotated[object, PlainValidator(func=_as_loader, json_schema_input_type=Any)] | TimeSeriesSpec | DataPayloadSpec | MetadataSpec | dict[str, Any], BeforeValidator(func=_route_measurement_input, json_schema_input_type=PydanticUndefined)]
options: MSMRHalfCellOptions | None
callbacks: Any | None
constraints: list[Constraint] | None
penalties: list[Penalty] | None
parameters: Annotated[dict[str, Any] | ParameterValues, BeforeValidator(func=serialize_parameters, json_schema_input_type=PydanticUndefined)] | None
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.objectives.MSMRHalfCellOptions(*, model: Any | None = None, dudq_cutoff: Literal['none'] | float | None = None, dqdu_cutoff: Literal['none'] | float | None = None, voltage_limits: tuple[float, float] | None = None, GITT: bool = False, objective_variables: list[str] | None = None, constrain_xj_method: Literal['project', 'explicit', 'reformulate'] = 'project', penalize_xj_complement_bounds: bool = False, preserve_u0j_order: bool = False)[source]

Bases: _ObjectiveOptions

Options for an MSMRHalfCell objective.

model: Any | None
dudq_cutoff: Literal['none'] | float | None
dqdu_cutoff: Literal['none'] | float | None
voltage_limits: tuple[float, float] | None
gitt: bool
objective_variables: list[str] | None
constrain_xj_method: Literal['project', 'explicit', 'reformulate']
penalize_xj_complement_bounds: bool
preserve_u0j_order: 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.

class ionworks.pipeline.objectives.OCPHalfCell(electrode=None, data=None, options=None, callbacks=None, custom_parameters=None, constraints=None, penalties=None, parameters=None, *, cost: Annotated[RMSE | MAE | MSE | Max | SSE | Wasserstein | ChiSquare | MultiCost | GaussianLogLikelihood | DesignFunction, FieldInfo(annotation=NoneType, required=True, discriminator='type')] | None = None, type: Literal['OCPHalfCell'] = 'OCPHalfCell')[source]

Bases: FittingObjective

Fit a smooth open-circuit-potential function to half-cell OCP data.

Parameters:

Examples

>>> obj = iw.pipeline.objectives.OCPHalfCell(
...     electrode="positive",
...     data_input="path/to/ocp.csv",
...     options={"stoichiometry limits": (0.05, 0.95)},
... )
>>> # slot into a DataFit (parameters omitted for brevity)
type: Literal['OCPHalfCell']
electrode: Literal['positive', 'negative']
data_input: Annotated[str | DataFrame | DataFrame | Annotated[object, PlainValidator(func=_as_loader, json_schema_input_type=Any)] | TimeSeriesSpec | DataPayloadSpec | MetadataSpec | dict[str, Any], BeforeValidator(func=_route_measurement_input, json_schema_input_type=PydanticUndefined)]
options: OCPHalfCellOptions | None
callbacks: Any | None
constraints: list[Constraint] | None
penalties: list[Penalty] | None
parameters: Annotated[dict[str, Any] | ParameterValues, BeforeValidator(func=serialize_parameters, json_schema_input_type=PydanticUndefined)] | None
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.objectives.Objective(data=None, options=None, callbacks=None, custom_parameters=None, constraints=None, penalties=None, parameters=None, *, cost: Annotated[RMSE | MAE | MSE | Max | SSE | Wasserstein | ChiSquare | MultiCost | GaussianLogLikelihood | DesignFunction, FieldInfo(annotation=NoneType, required=True, discriminator='type')] | None = None, type: Literal['Objective'] = 'Objective')[source]

Bases: FittingObjective

Deprecated alias for FittingObjective.

type: Literal['Objective']
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.objectives.OCPHalfCellOptions(*, theta_ref: float | None = None, stoichiometry_limits: tuple[float, float] | None = None, voltage_limits: tuple[float, float] | None = None, dudq_cutoff: float | None = None, dqdu_cutoff: float | None = None, direction: Literal['lithiation', 'delithiation'] | None = None)[source]

Bases: _ObjectiveOptions

Options for an OCPHalfCell objective.

theta_ref: float | None
stoichiometry_limits: tuple[float, float] | None
voltage_limits: tuple[float, float] | None
dudq_cutoff: float | None
dqdu_cutoff: float | None
direction: Literal['lithiation', 'delithiation'] | None
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.objectives.Pulse(data=None, options=None, callbacks=None, custom_parameters=None, constraints=None, penalties=None, parameters=None, *, cost: Annotated[RMSE | MAE | MSE | Max | SSE | Wasserstein | ChiSquare | MultiCost | GaussianLogLikelihood | DesignFunction, FieldInfo(annotation=NoneType, required=True, discriminator='type')] | None = None, type: Literal['Pulse'] = 'Pulse')[source]

Bases: SimulationObjective

Fit a model against pulse experiments — GITT, HPPC, ICI, and similar.

By default the fit compares simulated voltage against measured voltage, but you can swap in a different objective variables function to compare overpotentials, resistances, or ICI/GITT features instead.

Parameters:

Examples

>>> obj = iw.pipeline.objectives.Pulse(
...     data_input="path/to/gitt.csv", options={"model": "SPM"}
... )
>>> # slot into a DataFit (parameters omitted for brevity)
type: Literal['Pulse']
data_input: Annotated[str | DataFrame | DataFrame | Annotated[object, PlainValidator(func=_as_loader, json_schema_input_type=Any)] | TimeSeriesSpec | DataPayloadSpec | MetadataSpec | dict[str, Any], BeforeValidator(func=_route_measurement_input, json_schema_input_type=PydanticUndefined)]
options: PulseOptions | None
callbacks: Any | None
constraints: list[Constraint] | None
penalties: list[Penalty] | None
parameters: Annotated[dict[str, Any] | ParameterValues, BeforeValidator(func=serialize_parameters, json_schema_input_type=PydanticUndefined)] | None
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.objectives.PulseOptions(*, model: Any | None = None, parameterized_model_id: str | None = None, simulation_kwargs: dict[str, Any] | None = None, objective_variables: Callable | None = None, interpolant_atol: float | None = None, interpolant_rtol: float | None = None, interpolant_lossless: bool = False)[source]

Bases: _ObjectiveOptions

Options for a Pulse objective.

model: Any | None
parameterized_model_id: str | None
simulation_kwargs: dict[str, Any] | None
objective_variables: Callable | None
interpolant_atol: float | None
interpolant_rtol: float | None
interpolant_lossless: 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.

class ionworks.pipeline.objectives.Resistance(data=None, options=None, callbacks=None, custom_parameters=None, constraints=None, penalties=None, parameters=None, *, cost: Annotated[RMSE | MAE | MSE | Max | SSE | Wasserstein | ChiSquare | MultiCost | GaussianLogLikelihood | DesignFunction, FieldInfo(annotation=NoneType, required=True, discriminator='type')] | None = None, type: Literal['Resistance'] = 'Resistance')[source]

Bases: FittingObjective

Fit a cell-resistance curve against measured resistance vs SOC.

Parameters:

Examples

>>> obj = iw.pipeline.objectives.Resistance(data_input="path/to/resistance.csv")
>>> # slot into a DataFit (parameters omitted for brevity)
type: Literal['Resistance']
data_input: Annotated[str | DataFrame | DataFrame | Annotated[object, PlainValidator(func=_as_loader, json_schema_input_type=Any)] | TimeSeriesSpec | DataPayloadSpec | MetadataSpec | dict[str, Any], BeforeValidator(func=_route_measurement_input, json_schema_input_type=PydanticUndefined)]
options: ResistanceOptions | None
callbacks: Any | None
constraints: list[Constraint] | None
penalties: list[Penalty] | None
parameters: Annotated[dict[str, Any] | ParameterValues, BeforeValidator(func=serialize_parameters, json_schema_input_type=PydanticUndefined)] | None
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.objectives.ResistanceOptions(*, model: Any | None = None)[source]

Bases: _ObjectiveOptions

Options for a Resistance objective.

model: Any | None
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.objectives.SimulationObjective(data=None, options=None, callbacks=None, custom_parameters=None, constraints=None, penalties=None, parameters=None, *, cost: Annotated[RMSE | MAE | MSE | Max | SSE | Wasserstein | ChiSquare | MultiCost | GaussianLogLikelihood | DesignFunction, FieldInfo(annotation=NoneType, required=True, discriminator='type')] | None = None)[source]

Bases: FittingObjective

Shared base for objectives that run a pybamm simulation and compare the output to measured data.

Subclasses such as Pulse, CurrentDriven, CalendarAgeing, CycleAgeing, and EIS inherit from here. The arguments are the same as FittingObjective — pick a concrete subclass that matches your experiment.

Every subclass needs a model to fit against, given either as options={"model": ...} or, for a model stored on the platform, as options={"parameterized_model_id": ...}. Construction fails without one.

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.