> ## Documentation Index
> Fetch the complete documentation index at: https://docs.ionworks.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Run protocol simulations & get results

> Submit a single protocol simulation or a DOE batch against a parameterized model, wait for completion, and pull back time-series, step, and metric data.

This quickstart runs **protocol simulations** — you provide a [Universal Cycler Protocol](/simulate/universal-cycler-protocol) and simulate it against a model, optionally sweeping parameters as a design of experiments (DOE). To run a simulation inside a study (as on the Studies page), see [Run a simulation in a study](/quickstarts/run-in-study) — it uses the same call with a `study_id`.

<Note>
  Install and authenticate first: `pip install ionworks-api` and set `IONWORKS_API_KEY` and `IONWORKS_PROJECT_ID`. See the [Python API client](/api-client) page.
</Note>

```python theme={null}
from ionworks import Ionworks

client = Ionworks()

protocol = """
global:
  initial_soc: 1
  temperature: 25
steps:
  - Discharge:
      mode: C-rate
      value: 1
      ends:
        - "Voltage < 2.5"
"""

# --- A single simulation ---
# `parameterized_model` takes a parameterized-model id, or an inline
# quick model like {"capacity": 5.0, "chemistry": "NMC/Graphite"}.
response = client.simulation.protocol({
    "parameterized_model": "your-parameterized-model-id",
    "protocol_experiment": {"protocol": protocol, "name": "1C discharge"},
})
result = client.simulation.wait_for_completion(response.simulation_id, timeout=120)

result = client.simulation.get_result(response.simulation_id)
print(result.time_series)   # DataFrame: one row per time point
print(result.steps)         # DataFrame: one row per protocol step
print(result.metrics)       # dict of scalar metrics

# --- A DOE batch: sweep a parameter across several values ---
responses = client.simulation.protocol_batch({
    "parameterized_model": "your-parameterized-model-id",
    "protocol_experiment": {"protocol": protocol, "name": "thickness sweep"},
    "design_parameters_doe": {
        "sampling": "grid",  # grid | random | latin_hypercube
        "rows": [
            {"type": "range", "name": "Positive electrode thickness [m]",
             "min": 50e-6, "max": 100e-6, "count": 5},
        ],
    },
})
client.simulation.wait_for_completion([r.simulation_id for r in responses], timeout=3600)
results = [client.simulation.get_result(r.simulation_id) for r in responses]
```

**What's happening**

* Protocols are [Universal Cycler Protocol](/simulate/universal-cycler-protocol) (UCP) YAML, passed inline or as a saved protocol id. Every run needs a `parameterized_model` — a parameterized-model id or an inline quick model.
* **Single** = `client.simulation.protocol(...)`; **batch/DOE** = `client.simulation.protocol_batch(...)` with a `design_parameters_doe` block whose `rows` sweep named parameters under a `sampling` strategy.
* `wait_for_completion(...)` accepts one id or a list. `get_result(id)` returns `.time_series` and `.steps` dataframes (joinable on `Step count`) plus a `.metrics` dict.
* Dataframes are [polars](/api-client) by default; call `set_dataframe_backend("pandas")` once at session start if you prefer pandas.

## Learn more

* [Simulations](/simulate/simulations) and [Studies](/simulate/studies)
* [Universal Cycler Protocol](/simulate/universal-cycler-protocol)
* [Simulation Python API](/simulate/api)
