Simulation

Client for running battery simulations using UCP protocol and PyBaMM.

For usage examples and guides, see Simulate API on docs.ionworks.com.

Simulation client for running battery simulations.

This module provides the SimulationClient for running battery simulations using the Universal Cycler Protocol (UCP) format. It supports single simulations, batch simulations with design of experiments (DOE), and PyBaMM-based modeling.

class ionworks.api.simulation.SimulationResult(time_series: DataFrame | DataFrame, steps: DataFrame | DataFrame, metrics: dict[str, Any])[source]

Bases: object

Typed result returned by SimulationClient.get_result().

time_series: DataFrame | DataFrame

Time-series data with one row per time point. Column names follow the platform convention (e.g. "Time [s]", "Voltage [V]"). Returns a polars DataFrame by default; a pandas DataFrame when set_dataframe_backend("pandas") is active.

steps: DataFrame | DataFrame

Step-level summary with one row per protocol step. Returns the same DataFrame type as time_series.

metrics: dict[str, Any]

Scalar metrics computed over the full simulation (e.g. cycle-level summaries). Not tabular; returned as a plain dict.

class ionworks.api.simulation.QuickModelConfig(*, capacity: float = 1.0, chemistry: str = 'NMC/Graphite')[source]

Bases: BaseModel

Quick model configuration for protocol-based simulations.

A quick model builds a system ECM from a nominal capacity and a chemistry name — it does not take a base model_id (that is what a full parameterized model is for).

capacity: float
chemistry: str
model_config = {}

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

class ionworks.api.simulation.ProtocolExperimentConfig(*, protocol: str, name: str)[source]

Bases: _StrictModel

Protocol experiment configuration.

protocol: str
name: str
model_config = {'extra': 'forbid'}

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

class ionworks.api.simulation.DOERow(*, type: str, name: str, min: float | None = None, max: float | None = None, count: int | None = None, values: list[float] | None = None, mean: float | None = None, std: float | None = None)[source]

Bases: _StrictModel

Design of experiments row configuration.

type: str
name: str
min: float | None
max: float | None
count: int | None
values: list[float] | None
mean: float | None
std: float | None
model_config = {'extra': 'forbid'}

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

class ionworks.api.simulation.DesignParametersDOE(*, sampling: str, rows: list[DOERow], count: int | None = None)[source]

Bases: _StrictModel

Design of experiments configuration.

sampling: str
rows: list[DOERow]
count: int | None
model_config = {'extra': 'forbid'}

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

class ionworks.api.simulation.ParameterTable(*, x: list[float], y: list[float], interpolator: Literal['linear', 'cubic', 'pchip'] = 'linear')[source]

Bases: _StrictModel

A function-valued parameter given as a lookup table, interpolated in x.

Replaces a function with an interpolant in the function’s first input, the others ignored: a table of electrolyte conductivity against concentration replaces a function of concentration and temperature.

x: list[float]
y: list[float]
interpolator: Literal['linear', 'cubic', 'pchip']
model_config = {'extra': 'forbid'}

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

ionworks.api.simulation.DesignParameterValue = float | str | ionworks.api.simulation.ParameterTable

a number, or a table for a function-valued one.

Type:

A design-parameter override

class ionworks.api.simulation.SimulationCondition(*, experiment_parameters: dict[str, float | str]=<factory>, design_parameters: dict[str, float | str | ~ionworks.api.simulation.ParameterTable]=<factory>)[source]

Bases: _StrictModel

The inputs of one simulation in an explicit list of runs.

experiment_parameters: dict[str, float | str]
design_parameters: dict[str, DesignParameterValue]
model_config = {'extra': 'forbid'}

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

class ionworks.api.simulation.ProtocolSimulationBatchRequest(*, parameterized_model: Any, protocol_experiment: ProtocolExperimentConfig | None = None, protocol_id: str | None = None, design_parameters_doe: DesignParametersDOE | None = None, conditions: list[SimulationCondition] | None = None, experiment_parameters: dict[str, float | str] | None = None, max_backward_jumps: int | None = None, study_id: str | None = None, project_id: str | None = None, extra_variables: list[str] | None = None, simulation_settings: dict[str, Any] | None = None)[source]

Bases: _StrictModel

Request model for batch protocol-based simulation.

parameterized_model: Any
protocol_experiment: ProtocolExperimentConfig | None
protocol_id: str | None
design_parameters_doe: DesignParametersDOE | None
conditions: list[SimulationCondition] | None
experiment_parameters: dict[str, float | str] | None
max_backward_jumps: int | None
study_id: str | None
project_id: str | None
extra_variables: list[str] | None
simulation_settings: dict[str, Any] | None
model_config = {'extra': 'forbid'}

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

class ionworks.api.simulation.SimulationResponse(*, simulation_id: str, job_id: str, protocol_id: str | None = None, already_existed: bool = False)[source]

Bases: BaseModel

Response model for simulation creation.

simulation_id: str
job_id: str
protocol_id: str | None
already_existed: bool
model_config = {}

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

ionworks.api.simulation.expected_doe_simulation_count(doe: DesignParametersDOE) → int[source]

Return the number of simulations a DOE will produce.

For grid sampling, this is the product of the per-row sample counts (len(values) for discrete rows, count for range rows). For random and latin_hypercube sampling, it is the top-level count (defaulting to 1 when unset). Used by SimulationClient.protocol() to refuse multi-simulation DOEs before billing them.

class ionworks.api.simulation.SimulationClient(client: Any)[source]

Bases: object

Client for running simulations.

__init__(client: Any) → None[source]

Initialize the SimulationClient.

Parameters:

client (Any) – The HTTP client instance for making API requests.

protocol(config: dict[str, Any]) → SimulationResponse[source]

Create a single protocol-based simulation.

Delegates to protocol_batch() and returns the single result. Pass the same config fields as protocol_batch(), plus the single-simulation convenience field design_parameters — a flat mapping of numbers, sent as a one-row discrete DOE, or with any ParameterTable value, sent as a single condition.

Parameters:

config (dict[str, Any]) –

Configuration dictionary containing:

  • parameterized_model: one of
    • a quick-model dict {"capacity": <Ah>, "chemistry": <name>} (builds a system ECM; no base model_id),

    • a QuickModelConfig,

    • a full model dict {"model_id": ..., "parameters": {...}}, or

    • a parameterized-model ID string.

  • protocol_experiment: ProtocolExperimentConfig dict with protocol and name fields

  • protocol_id: id of a saved protocol, in place of protocol_experiment (provide exactly one)

  • experiment_parameters: Optional dict with initial_soc and initial_temperature

  • design_parameters: Optional dict[str, float | ParameterTable] — design parameter overrides for this single simulation. A function-valued parameter takes a table {"x": [...], "y": [...]} (or a ParameterTable).

  • max_backward_jumps: Optional int

  • study_id: Optional str

  • extra_variables: Optional list[str] — extra variables to include in simulation output

  • simulation_settings: Optional dict or SimulationSettings — per-run numerics (solver / var_pts / submesh_types / spatial_methods / discretisation_kwargs) overriding the parameterized model’s own settings key by key, for this call’s runs only. Send only the keys you mean to change: restating the model’s own settings still counts as different numerics, so the run is not deduplicated against one that sent none.

Returns:

Response containing simulation_id and job_id.

Return type:

SimulationResponse

Raises:

ValueError – If the configuration is invalid, both design_parameters and design_parameters_doe are supplied, or a multi-row design_parameters_doe is supplied (use protocol_batch() for multi-simulation DOE).

protocol_batch(config: dict[str, Any]) → list[SimulationResponse][source]

Create multiple protocol-based simulations using DOE.

Uses a two-step flow: first parses the protocol and creates an experiment template (POST /protocols/parse-to-template), then runs the batch (POST /simulations/with-template/batch). A saved protocol_id is already a template, so it skips the parse step.

Parameters:

config (dict[str, Any]) –

Configuration dictionary containing:

  • parameterized_model: one of
    • a quick-model dict {"capacity": <Ah>, "chemistry": <name>} (builds a system ECM; no base model_id),

    • a QuickModelConfig,

    • a full model dict {"model_id": ..., "parameters": {...}}, or

    • a parameterized-model ID string.

  • protocol_experiment: ProtocolExperimentConfig dict with protocol and name fields

  • protocol_id: id of a saved protocol, in place of protocol_experiment (provide exactly one)

  • design_parameters_doe: DesignParametersDOE dict

  • conditions: Optional list of {"experiment_parameters": {...}, "design_parameters": {...}} dicts — one simulation per entry, in place of design_parameters_doe. Top-level experiment_parameters fill in whatever a condition omits.

  • experiment_parameters: Optional dict

  • max_backward_jumps: Optional int

  • study_id: Optional str

  • extra_variables: Optional list[str] — extra variables to include in simulation output

  • simulation_settings: Optional dict or SimulationSettings — per-run numerics (solver / var_pts / submesh_types / spatial_methods / discretisation_kwargs) overriding the parameterized model’s own settings key by key, for this call’s runs only. Send only the keys you mean to change: restating the model’s own settings still counts as different numerics, so the run is not deduplicated against one that sent none.

Returns:

List of responses, each containing simulation_id and job_id.

Return type:

list[SimulationResponse]

Raises:

ValueError – If the configuration is invalid.

list(parameterized_model_id: str | None = None, study_id: str | None = None, *, protocol_id: str | None = None, state: Literal['queued', 'running', 'completed', 'failed'] | None = None, summary: bool = False, limit: int | None = None, offset: int | None = None) → list[dict[str, Any]] | PaginatedList[dict[str, Any]][source]

List simulations of a parameterized model, a study, or a protocol.

Scope by at most one of parameterized_model_id or study_id, optionally narrowed to one saved protocol with protocol_id; or by protocol_id alone. Newest first.

Parameters:
  • parameterized_model_id (str, optional) – Filter simulations belonging to this parameterized model.

  • study_id (str, optional) – Filter simulations assigned to this study.

  • protocol_id (str, optional) – Filter simulations that ran this saved protocol. Combines with either of the other two, e.g. every run of one model on one protocol.

  • state ({"queued", "running", "completed", "failed"}, optional) – Only runs in this state. failed includes canceled runs.

  • summary (bool, optional) – Leave out each run’s metrics, which are most of a listing’s size. Defaults to False.

  • limit (int, optional) – Maximum simulations to return (1-1000). The server defaults to 100.

  • offset (int, optional) – Number of simulations to skip.

Returns:

Simulation summaries. A PaginatedList carrying .total when limit or offset is given, otherwise a plain list.

Return type:

list[dict[str, Any]] | PaginatedList[dict[str, Any]]

Raises:

ValueError – If both parameterized_model_id and study_id are given, or none of the three scopes is.

get(simulation_id: str, summary: bool = False) → dict[str, Any][source]

Get a specific simulation by ID.

Parameters:
  • simulation_id (str) – The UUID of the simulation to retrieve.

  • summary (bool, optional) – If True, return the run’s status, model, protocol and inputs without its resolved parameters or simulation data. Defaults to False.

Returns:

Simulation object with full joined data including model, experiment, and simulation_data (null if not completed).

Return type:

dict[str, Any]

delete(simulation_id: str) → None[source]

Delete a simulation and its stored result data.

Parameters:

simulation_id (str) – The UUID of the simulation to delete.

get_statuses(simulation_ids: list[str]) → list[dict[str, Any]][source]

Get the job status of many simulations in one lightweight request.

Much cheaper than calling get() per simulation when polling a batch: only the status of each simulation’s job is returned, not the joined simulation record.

Parameters:

simulation_ids (list[str]) – Ids of the simulations to check.

Returns:

One {"simulation_id", "status", "error", "error_code", "updated_at"} item per simulation that exists, is visible to the caller, and has a job. Ids that match none are omitted, so compare against the ids you asked for. status is the job status (pending, waiting, processing, completed, failed or canceled).

Return type:

list[dict[str, Any]]

get_result(simulation_id: str) → SimulationResult[source]

Get simulation data/result for a completed simulation.

Downloads time_series.parquet and steps.parquet via signed-URL redirects, and loads metrics from the simulation record. Callers see the same SimulationResult shape as before.

Parameters:

simulation_id (str) – The UUID of the simulation.

Returns:

Typed result with time_series and steps as DataFrames and metrics as a plain dict. DataFrame type (polars or pandas) follows the active backend set via set_dataframe_backend().

Return type:

SimulationResult

Raises:

IonworksError – If the API request fails. A 404 typically means the result is not yet available (simulation still running, queued, or legacy data not yet upgraded to parquet). Other status codes indicate authentication failures, server errors, or a missing simulation ID.

get_steps(simulation_id: str) → DataFrame | DataFrame[source]

Get a completed simulation’s step-level summary, one row per step.

The steps frame of get_result() without downloading the time series, which is far larger.

Parameters:

simulation_id (str) – The UUID of the simulation.

Returns:

Step summary with Delta capacity [A.h] and Delta energy [W.h] added, in the active dataframe backend.

Return type:

DataFrame

Raises:

IonworksError – If the request fails; a 404 means the result is not ready yet.

wait_for_completion(simulation_id: str | list[str], timeout: int = 60, poll_interval: int = 2, verbose: bool = True, raise_on_failure: bool = True) → dict[str, Any] | list[dict[str, Any]][source]

Wait for simulation(s) to complete by polling until done or timeout.

Parameters:
  • simulation_id (str | list[str]) – Single simulation ID or list of simulation IDs to wait for. Can also be a SimulationResponse or list of them (the job_id will be extracted automatically for failure detection).

  • timeout (int) – Maximum time to wait in seconds (default: 60).

  • poll_interval (int) – Time between polls in seconds (default: 2).

  • verbose (bool) – Whether to print status updates (default: True).

  • raise_on_failure (bool) – Whether to raise IonworksError when a simulation’s job fails or is canceled (default: True).

Returns:

Completed simulation(s). Returns single dict if single ID provided, list of dicts if list of IDs provided. Only returns completed simulations if timeout is reached.

Return type:

dict[str, Any] | list[dict[str, Any]]

Raises:
  • TimeoutError – If timeout is reached before all simulations complete.

  • IonworksError – If a simulation fails and raise_on_failure is True.