Core

Core helper mixins used across schema classes.

Schemas for core.

class ionworks.pipeline.core.ConfigMixin[source]

Bases: BaseSchema

Mixin that automatically captures __init__ parameters for subclasses.

When a class inherits from this mixin, any subclass that defines its own __init__ method will have its parameters automatically captured and stored in self._init_params. This is useful for serialization/deserialization (e.g., to_config methods for reverse parsing).

Additionally, if the __init__ has an “options” parameter, it captures which option keys were explicitly passed in self._options_keys_passed before calling the original __init__.

The captured parameters exclude self and filter out None values.

This mixin also provides config_schema() classmethod that generates a pydantic schema from the class’s __init__ signature. This is used by ionworksschema generation.

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.

Containers

BaseSchema and the pipeline containers every element is assembled into.

Base classes for ionworks.pipeline.

ionworks.pipeline.base.reject_runtime_object(value: Any, field_name: str, expected: str) → NoReturn[source]

Reject a value that is neither a config dict nor a schema instance.

Centralizes the boundary guard shared by the distribution, sampler, prior, and objective resolvers. The schema contract holds only ionworks.pipeline objects — a config dict or the matching schema instance — so a live runtime object must be converted (via .to_config() or the matching ionworks.pipeline schema class) before it enters a schema, rather than embedded raw. Callers invoke this on the fall-through branch after they have already accepted the dict and schema-instance forms.

Parameters:
  • value (Any) – The rejected value, echoed into the error message via repr.

  • field_name (str) – The schema field being resolved (e.g. "distribution", "sampler", "prior", "objective"), used to phrase the message.

  • expected (str) – User-facing description of the accepted non-dict form, phrased with its article so it reads in the message (e.g. "a Distribution instance", "an ionworks.pipeline objective instance"). Use the public vocabulary, not an internal implementation class name.

Raises:

ValueError – Always. The function never returns normally.

class ionworks.pipeline.base.BaseSchema[source]

Bases: BaseModel

Shared parent of every schema class in this package.

Construct a concrete subclass (iw.pipeline.Pipeline, iw.pipeline.DataFit, …) rather than this. It rejects unknown fields, so a typo fails at construction, and provides .to_config(), the only supported serializer: .model_dump() omits the type discriminator and uses attribute names (data_input) rather than wire names (data).

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].

to_config() → dict[source]

Build the dict you submit through ionworks.api.

Nested schemas convert recursively, None fields are dropped, and each component the server must re-identify gains a "type" key.

Notes

The only supported serializer. .model_dump() (even with by_alias=True) omits the "type" discriminator, so it is not a payload: iw.pipeline.stats.Uniform(lb=0.0, ub=1.0).to_config() gives {"distribution": "Uniform", "lb": 0.0, "ub": 1.0}, whereas .model_dump(exclude_none=True) gives {"lb": 0.0, "ub": 1.0}.

run(*args, **kwargs)[source]
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.base.Pipeline(elements: dict | None = None, output_file: str | None = None, name: str | None = None, description: str | None = None)[source]

Bases: BaseSchema

A sequence of steps that together produce a parameterised cell model.

Each element pulls parameters from a source (DirectEntry), fits them to data (DataFit), derives quantities (Calculation) or checks them against held-out data (Validation), and passes its parameters to the next, so order matters. Submit .to_config() through ionworks.api to run it server-side.

Parameters:
  • elements (dict, optional) – Mapping of element name to pipeline element (DataFit, DirectEntry, Validation, Calculation, …). The name is used to identify the element in the pipeline report. None is treated as an empty pipeline by to_config.

  • output_file (str, optional) – Optional file path for persisting the final parameter values produced by the pipeline. If None, parameters are not written to disk.

  • name (str, optional) – Human-readable name for the pipeline, used in reports and logs.

  • description (str, optional) – Free-text description of what the pipeline does.

Examples

>>> # known parameters (e.g. ambient temperature)
>>> known = iw.pipeline.direct_entries.DirectEntry(
...     parameters={"Ambient temperature [K]": 298.15},
... )
>>> # fit one parameter against an OCP measurement
>>> obj = iw.pipeline.objectives.OCPHalfCell(
...     electrode="positive",
...     data_input="path/to/ocp.csv",
... )
>>> fit = iw.pipeline.DataFit(
...     objectives={"ocp": obj},
...     parameters={"Positive electrode capacity [A.h]": iw.pipeline.Parameter(
...         "Positive electrode capacity [A.h]", initial_value=3.0, bounds=(2.0, 4.0),
...     )},
... )
>>> # validate the fit against held-out cycling data
>>> val_obj = iw.pipeline.objectives.CurrentDriven(
...     data_input="path/to/cycle.csv", options={"model": "SPM"}
... )
>>> val = iw.pipeline.Validation(objectives={"cycle": val_obj})
>>> pipeline = iw.pipeline.Pipeline(
...     {"known": known, "ocp fit": fit, "validate": val}
... )
>>> config = pipeline.to_config()
>>> # then submit `config` via ionworks.api
elements: dict | None
output_file: str | None
name: str | None
description: str | None
to_config() → dict[source]

Build the pipeline payload you submit through ionworks.api.

Walks every element you added to elements, serialises it (each element’s own to_config), and tags it with its kind ("entry", "data_fit", "validation", …) so the server knows how to dispatch it. Pass the returned dict to the Ionworks API to run the full pipeline.

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.base.SimplePipeline(*args: Any, elements: dict | None = None, output_file: str | None = None, name: str | None = None, description: str | None = None)[source]

Bases: Pipeline

A pipeline that permits at most one expensive element.

Warning

Deprecated, and removed in a future release. Use Pipeline, which accepts the same elements.

SimplePipeline is identical to Pipeline except that it rejects configurations containing more than one expensive element (DataFit, ArrayDataFit, or Validation) at construction time, matching the constraint the SimplePipeline API endpoint enforces.

Parameters:
  • elements (dict, optional) – Mapping of element name to pipeline element (DataFit, DirectEntry, Validation, Calculation, …). At most one element may be a DataFit, ArrayDataFit, or Validation.

  • output_file (str, optional) – Optional file path for persisting the final parameter values produced by the pipeline. If None, parameters are not written to disk.

  • name (str, optional) – Human-readable name for the pipeline, used in reports and logs.

  • description (str, optional) – Free-text description of what the pipeline does.

Raises:

ValueError – If elements contains more than one DataFit, ArrayDataFit, or Validation element.

Examples

>>> entry = iw.pipeline.direct_entries.DirectEntry(
...     parameters={"Ambient temperature [K]": 298.15},
... )
>>> obj = iw.pipeline.objectives.OCPHalfCell(
...     electrode="positive",
...     data_input="path/to/ocp.csv",
... )
>>> fit = iw.pipeline.DataFit(
...     objectives={"ocp": obj},
...     parameters={"Positive electrode capacity [A.h]": iw.pipeline.Parameter(
...         "Positive electrode capacity [A.h]",
...         initial_value=3.0,
...         bounds=(2.0, 4.0),
...     )},
... )
>>> pipeline = iw.pipeline.SimplePipeline(
...     elements={"known": entry, "ocp fit": fit},
...     name="Single OCP fit",
... )
>>> config = pipeline.to_config()
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.