| name | run-simulations |
| description | Run electrochemical simulations with UCP protocols, and convert UCP protocols to vendor-native files (Maccor, Arbin, Neware, BioLogic, Novonix). Use when the user wants to simulate battery behavior, run protocol-based simulations, batch simulations with DOE, validate UCP protocols, retrieve simulation results, or export a UCP to a cycler's protocol format. Triggers: "simulation", "simulate", "protocol", "UCP", "DOE", "batch simulation", "design of experiments", "convert protocol", "Maccor", "Arbin", "Neware", "BioLogic", "Novonix". |
Simulations
Guide for running battery simulations using the
Universal Cycler Protocol (UCP) format.
Prerequisites
- Run the discover-api skill first
- Fetch the protocol schema:
protocol_schema = client.schema("protocol")
UCP YAML Format
Each entry under steps: is a single-key dict keyed
by step type. The same applies to nested steps inside a
step_block:.
global:
initial_state_type: voltage
initial_state_value: "4.2"
initial_temperature: "25"
steps:
- Charge:
mode: C-rate
value: "1"
ends:
- Voltage > 4.2
- Rest:
- Rest:
duration: 600
- step_block:
name: cycling
repeat: 100
steps:
- Discharge:
mode: C-rate
value: "1"
ends:
- Voltage < 2.5
- Rest:
duration: 600
- Charge:
mode: C-rate
value: "1"
ends:
- Voltage > 4.2
- Rest:
duration: 600
- Increment cycle number
Ends accept three forms (see
protocol_schema["end_condition_forms"]):
- string:
"Voltage > 4.2", "Capacity > 1.0" (Ah),
"Duration > 600" (seconds), "LoopNumber >= 100" —
the right-hand side is a numeric expression; units
are implied, not written
- single-key dict with goto:
{"Voltage > 4.2": {"goto": "cv_charge"}}
- typed dict:
{type: Voltage, operator: ">", value: "4.2"}
Validate before submitting. Either route works:
import jsonschema, yaml
proto = yaml.safe_load(protocol_yaml)
jsonschema.validate(proto, protocol_schema["input_schema"])
result = client.protocol.validate(protocol_yaml)
Single Simulation
from ionworks import Ionworks
client = Ionworks()
config = {
"parameterized_model": "pm-id-here",
"protocol_experiment": {
"protocol": "...",
"name": "1C discharge",
},
}
config = {
"parameterized_model": {
"capacity": 5.0,
"chemistry": "NMC",
},
"protocol_experiment": {
"protocol": "...",
"name": "1C discharge",
},
}
config = {
"parameterized_model": {
"model_id": "model-id",
"parameters": { ... },
},
"protocol_experiment": {
"protocol": "...",
"name": "custom sim",
},
}
sim = client.simulation.protocol(config)
result = client.simulation.wait_for_completion(
sim.simulation_id,
timeout=60,
poll_interval=2,
verbose=True,
)
Optional Parameters
config = {
"parameterized_model": "pm-id",
"protocol_experiment": {
"protocol": "...",
"name": "sim name",
},
"experiment_parameters": { ... },
"design_parameters": { ... },
"max_backward_jumps": 100,
"study_id": "study-id",
"extra_variables": ["variable_name"],
}
Batch Simulation (Design of Experiments)
Run multiple simulations with parameter variations:
config = {
"parameterized_model": "pm-id",
"protocol_experiment": {
"protocol": "...",
"name": "batch sim",
},
"design_parameters_doe": {
"sampling": "grid",
"rows": [
{
"type": "range",
"name": "Positive electrode thickness [m]",
"min": 50e-6,
"max": 100e-6,
"count": 5,
},
{
"type": "discrete",
"name": "Current function [A]",
"values": [1.0, 2.0, 5.0],
},
{
"type": "normal",
"name": "Negative electrode porosity",
"mean": 0.3,
"std": 0.05,
"count": 10,
},
],
"count": 50,
},
}
sims = client.simulation.protocol_batch(config)
results = client.simulation.wait_for_completion(
[s.simulation_id for s in sims],
timeout=3600,
)
Listing and Retrieving Results
sims = client.simulation.list(parameterized_model_id="pm-id")
sims = client.simulation.list(study_id="study-id")
sim = client.simulation.get("simulation-id")
result = client.simulation.get_result("simulation-id")
Protocol Validation
Validate a UCP protocol string before simulation:
validation = client.protocol.validate(protocol_yaml_string)
refs = client.protocol.find_input_references(protocol_yaml_string)
Convert UCP to Vendor Protocol Files
Take a UCP and emit a protocol file you can load directly into a
cycler's software (Maccor, Arbin, Neware, BioLogic BT-Test, Novonix):
result = client.protocol.convert(protocol, target="maccor")
result.primary_bytes
result.primary_filename
result.media_type
result.assets
result.text()
result.save("./out")
protocol accepts a dict or a YAML string. target is one of
maccor, arbin, neware, biologic_bttest, novonix.
Pass drive_cycles={"name": [[t, i], ...]} when the protocol uses
DriveCycle steps. Pass filename_stem="my_run" to override the
default protocol.<ext> name.
The endpoint returns bytes always — text() and save() are opt-in
helpers. If the protocol fails validation, the call raises a 400.
Error Handling
from ionworks import IonworksError
try:
result = client.simulation.wait_for_completion(
sim.simulation_id,
timeout=120,
raise_on_failure=True,
)
except TimeoutError:
print("Simulation timed out")
except IonworksError as e:
print(f"Simulation failed: {e.message}")
Pass raise_on_failure=False to get the result dict
even on failure (check result["status"]).