ionworks-api 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ionworks_api-0.1.0/.gitignore +10 -0
- ionworks_api-0.1.0/PKG-INFO +318 -0
- ionworks_api-0.1.0/README.md +297 -0
- ionworks_api-0.1.0/ionworks/__init__.py +3 -0
- ionworks_api-0.1.0/ionworks/cell_instance.py +201 -0
- ionworks_api-0.1.0/ionworks/cell_measurement.py +339 -0
- ionworks_api-0.1.0/ionworks/cell_specification.py +108 -0
- ionworks_api-0.1.0/ionworks/client.py +126 -0
- ionworks_api-0.1.0/ionworks/errors.py +35 -0
- ionworks_api-0.1.0/ionworks/job.py +111 -0
- ionworks_api-0.1.0/ionworks/models.py +185 -0
- ionworks_api-0.1.0/ionworks/pipeline.py +256 -0
- ionworks_api-0.1.0/ionworks/simulation.py +371 -0
- ionworks_api-0.1.0/ionworks/validators.py +171 -0
- ionworks_api-0.1.0/pyproject.toml +34 -0
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: ionworks-api
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Python client for interacting with the Ionworks API
|
|
5
|
+
Requires-Python: >=3.10
|
|
6
|
+
Requires-Dist: black
|
|
7
|
+
Requires-Dist: iwutil
|
|
8
|
+
Requires-Dist: numpy
|
|
9
|
+
Requires-Dist: pandas
|
|
10
|
+
Requires-Dist: polars
|
|
11
|
+
Requires-Dist: pyarrow
|
|
12
|
+
Requires-Dist: pybamm>=25.10.0
|
|
13
|
+
Requires-Dist: pydantic>=2.6.0
|
|
14
|
+
Requires-Dist: python-dotenv==1.2.1
|
|
15
|
+
Requires-Dist: requests==2.32.5
|
|
16
|
+
Requires-Dist: supabase
|
|
17
|
+
Requires-Dist: types-requests>=2.31.0
|
|
18
|
+
Provides-Extra: dev
|
|
19
|
+
Requires-Dist: pytest>=7.0.0; extra == 'dev'
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
|
|
22
|
+
# Ionworks API Client
|
|
23
|
+
|
|
24
|
+
⚠️ **Warning**: This client is under active development and the API may change without notice.
|
|
25
|
+
|
|
26
|
+
A Python client for interacting with the Ionworks API.
|
|
27
|
+
|
|
28
|
+
## Installation
|
|
29
|
+
|
|
30
|
+
1. Clone this repository
|
|
31
|
+
2. Install the package in editable mode:
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
pip install -e .
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
3. Get your API key from the [Ionworks account settings](https://app.ionworks.com/dashboard/account) and set it as the `IONWORKS_API_KEY` environment variable (or in a `.env` file).
|
|
38
|
+
|
|
39
|
+
## Usage
|
|
40
|
+
|
|
41
|
+
Basic usage example:
|
|
42
|
+
|
|
43
|
+
```python
|
|
44
|
+
from ionworks import Ionworks
|
|
45
|
+
|
|
46
|
+
# Initialize client (uses IONWORKS_API_KEY from environment/.env file)
|
|
47
|
+
client = Ionworks()
|
|
48
|
+
|
|
49
|
+
# or provide credentials directly
|
|
50
|
+
client = Ionworks(api_key="your_key")
|
|
51
|
+
|
|
52
|
+
# Check API health
|
|
53
|
+
health = client.health_check()
|
|
54
|
+
print(health)
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### Uploading data to the Ionworks app
|
|
58
|
+
|
|
59
|
+
Uploading data to the Ionworks app follows a three-step process:
|
|
60
|
+
|
|
61
|
+
1. Create a cell spec (or get an existing one)
|
|
62
|
+
2. Create a cell instance with the spec id
|
|
63
|
+
3. Upload measurement(s) with time series data using the instance id
|
|
64
|
+
|
|
65
|
+
Given time series data, the data can be uploaded as follows:
|
|
66
|
+
|
|
67
|
+
```python
|
|
68
|
+
from ionworks import Ionworks
|
|
69
|
+
import pandas as pd
|
|
70
|
+
|
|
71
|
+
client = Ionworks()
|
|
72
|
+
|
|
73
|
+
# Step 1: Create or get a cell specification
|
|
74
|
+
cell_spec = client.cell_spec.create_or_get(
|
|
75
|
+
{
|
|
76
|
+
"name": "NCM622/Graphite Coin Cell",
|
|
77
|
+
"form_factor": "R2032",
|
|
78
|
+
"manufacturer": "Custom Cells",
|
|
79
|
+
"ratings": {
|
|
80
|
+
"capacity": {"value": 0.002, "unit": "A*h"},
|
|
81
|
+
"voltage_min": {"value": 2.5, "unit": "V"},
|
|
82
|
+
"voltage_max": {"value": 4.2, "unit": "V"},
|
|
83
|
+
},
|
|
84
|
+
"cathode": {
|
|
85
|
+
"properties": {"loading": {"value": 12.3, "unit": "mg/cm**2"}},
|
|
86
|
+
"material": {"name": "NCM622", "manufacturer": "BASF"},
|
|
87
|
+
},
|
|
88
|
+
"anode": {
|
|
89
|
+
"properties": {"loading": {"value": 6.5, "unit": "mg/cm**2"}},
|
|
90
|
+
"material": {"name": "Graphite", "manufacturer": "Customcells"},
|
|
91
|
+
},
|
|
92
|
+
}
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
# Step 2: Create or get a cell instance
|
|
96
|
+
cell_instance = client.cell_instance.create_or_get(
|
|
97
|
+
cell_spec.id,
|
|
98
|
+
{
|
|
99
|
+
"name": "NCM622-GR-001",
|
|
100
|
+
"batch": "BATCH-2024-001",
|
|
101
|
+
"date_manufactured": "2024-01-20",
|
|
102
|
+
"measured_properties": {
|
|
103
|
+
"cathode": {"loading": {"value": 12.1, "unit": "mg/cm**2"}},
|
|
104
|
+
"anode": {"loading": {"value": 6.4, "unit": "mg/cm**2"}},
|
|
105
|
+
},
|
|
106
|
+
},
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
# Step 3: Upload measurement with time series data
|
|
110
|
+
time_series = pd.DataFrame(
|
|
111
|
+
{
|
|
112
|
+
"Time [s]": [0, 1, 2, 3, 4, 5],
|
|
113
|
+
"Voltage [V]": [3.0, 3.2, 3.5, 3.8, 4.0, 4.2],
|
|
114
|
+
"Current [A]": [0.002, 0.002, 0.002, 0.002, 0.002, 0.002],
|
|
115
|
+
"Step count": [0, 0, 0, 1, 1, 1],
|
|
116
|
+
"Cycle count": [0, 0, 0, 0, 0, 0],
|
|
117
|
+
"Step from cycler": [1, 1, 1, 2, 2, 2],
|
|
118
|
+
"Cycle from cycler": [0, 0, 0, 0, 0, 0],
|
|
119
|
+
}
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
measurement_data = {
|
|
123
|
+
"measurement": {
|
|
124
|
+
"name": "Formation Cycle 1",
|
|
125
|
+
"protocol": {
|
|
126
|
+
"name": "CC-CV charge at C/10 to 4.2V",
|
|
127
|
+
"ambient_temperature_degc": 25,
|
|
128
|
+
},
|
|
129
|
+
"test_setup": {
|
|
130
|
+
"cycler": "Biologic VMP3",
|
|
131
|
+
"operator": "Jane Smith",
|
|
132
|
+
},
|
|
133
|
+
"notes": "Formation cycle - first charge",
|
|
134
|
+
},
|
|
135
|
+
"time_series": time_series,
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
measurement_bundle = client.cell_measurement.create(
|
|
139
|
+
cell_instance.id, measurement_data
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
print(f"Created measurement: {measurement_bundle.measurement.name}")
|
|
143
|
+
print(f"Steps created: {measurement_bundle.steps_created}")
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
### Reading cell data
|
|
147
|
+
|
|
148
|
+
```python
|
|
149
|
+
from ionworks import Ionworks
|
|
150
|
+
|
|
151
|
+
client = Ionworks()
|
|
152
|
+
|
|
153
|
+
# List all cell specifications
|
|
154
|
+
specs = client.cell_spec.list()
|
|
155
|
+
for spec in specs[:5]:
|
|
156
|
+
print(f" - {spec.name} (form_factor: {spec.form_factor})")
|
|
157
|
+
|
|
158
|
+
# Get a specific cell spec with full nested data
|
|
159
|
+
full_spec = client.cell_spec.get(spec_id)
|
|
160
|
+
print(f"Capacity: {full_spec.ratings['capacity']['value']} "
|
|
161
|
+
f"{full_spec.ratings['capacity']['unit']}")
|
|
162
|
+
|
|
163
|
+
# Get cell instance by slug
|
|
164
|
+
instance = client.cell_instance.get_by_slug("ncm622-gr-001")
|
|
165
|
+
|
|
166
|
+
# List measurements for an instance
|
|
167
|
+
measurements = client.cell_measurement.list(instance.id)
|
|
168
|
+
|
|
169
|
+
# Get measurement detail with time series
|
|
170
|
+
measurement_detail = client.cell_measurement.detail(measurement_id)
|
|
171
|
+
print(f"Time series shape: {measurement_detail.time_series.shape}")
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
### Running pipelines
|
|
175
|
+
|
|
176
|
+
Pipelines allow you to run complex workflows combining data fitting, calculations, and validations. A pipeline consists of named elements, where each element has an `element_type` and configuration specific to that type.
|
|
177
|
+
|
|
178
|
+
**Recommended workflow:** First upload your experimental data using the cell measurement API (see "Uploading data to the Ionworks app" above), then reference it in your pipeline using the `db:<measurement_id>` format. This ensures your data is stored and versioned in the database.
|
|
179
|
+
|
|
180
|
+
Available element types:
|
|
181
|
+
|
|
182
|
+
- `entry`: Provide initial parameter values
|
|
183
|
+
- `data_fit`: Fit model parameters to experimental data
|
|
184
|
+
- `calculation`: Run calculations (e.g., OCP fitting)
|
|
185
|
+
- `validation`: Validate model against data
|
|
186
|
+
|
|
187
|
+
```python
|
|
188
|
+
import time
|
|
189
|
+
from ionworks import Ionworks
|
|
190
|
+
|
|
191
|
+
client = Ionworks()
|
|
192
|
+
|
|
193
|
+
# First, upload your data (see "Uploading data to the Ionworks app" section)
|
|
194
|
+
# Then get the measurement ID to reference in the pipeline
|
|
195
|
+
measurements = client.cell_measurement.list(cell_instance_id)
|
|
196
|
+
measurement_id = measurements[0].id # or find the specific measurement you need
|
|
197
|
+
|
|
198
|
+
# Define entry configuration with initial parameter values
|
|
199
|
+
entry_config = {
|
|
200
|
+
"values": {
|
|
201
|
+
"Negative particle diffusivity [m2.s-1]": 3.3e-14,
|
|
202
|
+
"Positive particle diffusivity [m2.s-1]": 4e-15,
|
|
203
|
+
# ... other parameters
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
# Define datafit configuration - reference uploaded data with db:<measurement_id>
|
|
208
|
+
datafit_config = {
|
|
209
|
+
"objectives": {
|
|
210
|
+
"test_1C": {
|
|
211
|
+
"objective": "CurrentDriven",
|
|
212
|
+
"model": {"type": "SPMe"},
|
|
213
|
+
"data": f"db:{measurement_id}", # Reference data from database
|
|
214
|
+
"custom_parameters": {"Ambient temperature [K]": "initial_temperature"},
|
|
215
|
+
},
|
|
216
|
+
},
|
|
217
|
+
"parameters": {
|
|
218
|
+
"Negative particle diffusivity [m2.s-1]": {
|
|
219
|
+
"bounds": [1e-14, 1e-13],
|
|
220
|
+
"initial_value": 2e-14,
|
|
221
|
+
},
|
|
222
|
+
"Positive particle diffusivity [m2.s-1]": {
|
|
223
|
+
"bounds": [1e-15, 1e-14],
|
|
224
|
+
"initial_value": 2e-15,
|
|
225
|
+
},
|
|
226
|
+
},
|
|
227
|
+
"cost": {"type": "RMSE"},
|
|
228
|
+
"optimizer": {"type": "ScipyDifferentialEvolution"},
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
# Create pipeline config with named elements
|
|
232
|
+
pipeline_config = {
|
|
233
|
+
"elements": {
|
|
234
|
+
"entry": {**entry_config, "element_type": "entry"},
|
|
235
|
+
"fit data": {**datafit_config, "element_type": "data_fit"},
|
|
236
|
+
},
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
# Submit pipeline
|
|
240
|
+
pipeline = client.pipeline.create(pipeline_config)
|
|
241
|
+
print(f"Pipeline submitted: {pipeline.id}")
|
|
242
|
+
|
|
243
|
+
# Poll for completion
|
|
244
|
+
while True:
|
|
245
|
+
pipeline = client.pipeline.get(pipeline.id)
|
|
246
|
+
print(f"Status: {pipeline.status}")
|
|
247
|
+
if pipeline.status == "completed":
|
|
248
|
+
result = client.pipeline.result(pipeline.id)
|
|
249
|
+
print("Fitted parameters:", result.element_results["fit data"])
|
|
250
|
+
break
|
|
251
|
+
elif pipeline.status == "failed":
|
|
252
|
+
print("Pipeline failed:", pipeline.error)
|
|
253
|
+
break
|
|
254
|
+
time.sleep(2)
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
### Running simulations
|
|
258
|
+
|
|
259
|
+
The client supports running battery simulations using the Universal Cycler Protocol (UCP) format:
|
|
260
|
+
|
|
261
|
+
```python
|
|
262
|
+
from ionworks import Ionworks
|
|
263
|
+
|
|
264
|
+
client = Ionworks()
|
|
265
|
+
|
|
266
|
+
# Define a charge/discharge protocol in YAML format
|
|
267
|
+
protocol_yaml = """global:
|
|
268
|
+
initial_state_type: soc_percentage
|
|
269
|
+
initial_state_value: 50
|
|
270
|
+
initial_temperature: 25.0
|
|
271
|
+
steps:
|
|
272
|
+
- Charge:
|
|
273
|
+
mode: C-rate
|
|
274
|
+
value: "0.6"
|
|
275
|
+
ends:
|
|
276
|
+
- Voltage > 4.2
|
|
277
|
+
- Rest:
|
|
278
|
+
duration: 3600
|
|
279
|
+
- Discharge:
|
|
280
|
+
mode: C-rate
|
|
281
|
+
value: "0.5"
|
|
282
|
+
ends:
|
|
283
|
+
- Voltage < 2.5
|
|
284
|
+
"""
|
|
285
|
+
|
|
286
|
+
# Create simulation with quick model
|
|
287
|
+
config = {
|
|
288
|
+
"parameterized_model": {
|
|
289
|
+
"quick_model": {"capacity": 1.0, "chemistry": "NMC/Graphite"}
|
|
290
|
+
},
|
|
291
|
+
"protocol_experiment": {
|
|
292
|
+
"protocol": protocol_yaml,
|
|
293
|
+
"name": "NMC Charge Discharge Protocol",
|
|
294
|
+
},
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
result = client.simulation.protocol(config)
|
|
298
|
+
print(f"Simulation ID: {result.simulation_id}")
|
|
299
|
+
|
|
300
|
+
# Wait for completion
|
|
301
|
+
simulation = client.simulation.wait_for_completion(
|
|
302
|
+
result.simulation_id, timeout=60, poll_interval=2
|
|
303
|
+
)
|
|
304
|
+
|
|
305
|
+
# Get results
|
|
306
|
+
simulation_data = client.simulation.get_result(result.simulation_id)
|
|
307
|
+
time_series = simulation_data.get("time_series", {})
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
## Error Handling
|
|
311
|
+
|
|
312
|
+
The client will raise exceptions in the following cases:
|
|
313
|
+
|
|
314
|
+
- Missing API credentials
|
|
315
|
+
- Invalid API credentials
|
|
316
|
+
- API request errors (will raise `IonworksError` with details)
|
|
317
|
+
|
|
318
|
+
Make sure to handle these exceptions appropriately in your code.
|
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
# Ionworks API Client
|
|
2
|
+
|
|
3
|
+
⚠️ **Warning**: This client is under active development and the API may change without notice.
|
|
4
|
+
|
|
5
|
+
A Python client for interacting with the Ionworks API.
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
1. Clone this repository
|
|
10
|
+
2. Install the package in editable mode:
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
pip install -e .
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
3. Get your API key from the [Ionworks account settings](https://app.ionworks.com/dashboard/account) and set it as the `IONWORKS_API_KEY` environment variable (or in a `.env` file).
|
|
17
|
+
|
|
18
|
+
## Usage
|
|
19
|
+
|
|
20
|
+
Basic usage example:
|
|
21
|
+
|
|
22
|
+
```python
|
|
23
|
+
from ionworks import Ionworks
|
|
24
|
+
|
|
25
|
+
# Initialize client (uses IONWORKS_API_KEY from environment/.env file)
|
|
26
|
+
client = Ionworks()
|
|
27
|
+
|
|
28
|
+
# or provide credentials directly
|
|
29
|
+
client = Ionworks(api_key="your_key")
|
|
30
|
+
|
|
31
|
+
# Check API health
|
|
32
|
+
health = client.health_check()
|
|
33
|
+
print(health)
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### Uploading data to the Ionworks app
|
|
37
|
+
|
|
38
|
+
Uploading data to the Ionworks app follows a three-step process:
|
|
39
|
+
|
|
40
|
+
1. Create a cell spec (or get an existing one)
|
|
41
|
+
2. Create a cell instance with the spec id
|
|
42
|
+
3. Upload measurement(s) with time series data using the instance id
|
|
43
|
+
|
|
44
|
+
Given time series data, the data can be uploaded as follows:
|
|
45
|
+
|
|
46
|
+
```python
|
|
47
|
+
from ionworks import Ionworks
|
|
48
|
+
import pandas as pd
|
|
49
|
+
|
|
50
|
+
client = Ionworks()
|
|
51
|
+
|
|
52
|
+
# Step 1: Create or get a cell specification
|
|
53
|
+
cell_spec = client.cell_spec.create_or_get(
|
|
54
|
+
{
|
|
55
|
+
"name": "NCM622/Graphite Coin Cell",
|
|
56
|
+
"form_factor": "R2032",
|
|
57
|
+
"manufacturer": "Custom Cells",
|
|
58
|
+
"ratings": {
|
|
59
|
+
"capacity": {"value": 0.002, "unit": "A*h"},
|
|
60
|
+
"voltage_min": {"value": 2.5, "unit": "V"},
|
|
61
|
+
"voltage_max": {"value": 4.2, "unit": "V"},
|
|
62
|
+
},
|
|
63
|
+
"cathode": {
|
|
64
|
+
"properties": {"loading": {"value": 12.3, "unit": "mg/cm**2"}},
|
|
65
|
+
"material": {"name": "NCM622", "manufacturer": "BASF"},
|
|
66
|
+
},
|
|
67
|
+
"anode": {
|
|
68
|
+
"properties": {"loading": {"value": 6.5, "unit": "mg/cm**2"}},
|
|
69
|
+
"material": {"name": "Graphite", "manufacturer": "Customcells"},
|
|
70
|
+
},
|
|
71
|
+
}
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
# Step 2: Create or get a cell instance
|
|
75
|
+
cell_instance = client.cell_instance.create_or_get(
|
|
76
|
+
cell_spec.id,
|
|
77
|
+
{
|
|
78
|
+
"name": "NCM622-GR-001",
|
|
79
|
+
"batch": "BATCH-2024-001",
|
|
80
|
+
"date_manufactured": "2024-01-20",
|
|
81
|
+
"measured_properties": {
|
|
82
|
+
"cathode": {"loading": {"value": 12.1, "unit": "mg/cm**2"}},
|
|
83
|
+
"anode": {"loading": {"value": 6.4, "unit": "mg/cm**2"}},
|
|
84
|
+
},
|
|
85
|
+
},
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
# Step 3: Upload measurement with time series data
|
|
89
|
+
time_series = pd.DataFrame(
|
|
90
|
+
{
|
|
91
|
+
"Time [s]": [0, 1, 2, 3, 4, 5],
|
|
92
|
+
"Voltage [V]": [3.0, 3.2, 3.5, 3.8, 4.0, 4.2],
|
|
93
|
+
"Current [A]": [0.002, 0.002, 0.002, 0.002, 0.002, 0.002],
|
|
94
|
+
"Step count": [0, 0, 0, 1, 1, 1],
|
|
95
|
+
"Cycle count": [0, 0, 0, 0, 0, 0],
|
|
96
|
+
"Step from cycler": [1, 1, 1, 2, 2, 2],
|
|
97
|
+
"Cycle from cycler": [0, 0, 0, 0, 0, 0],
|
|
98
|
+
}
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
measurement_data = {
|
|
102
|
+
"measurement": {
|
|
103
|
+
"name": "Formation Cycle 1",
|
|
104
|
+
"protocol": {
|
|
105
|
+
"name": "CC-CV charge at C/10 to 4.2V",
|
|
106
|
+
"ambient_temperature_degc": 25,
|
|
107
|
+
},
|
|
108
|
+
"test_setup": {
|
|
109
|
+
"cycler": "Biologic VMP3",
|
|
110
|
+
"operator": "Jane Smith",
|
|
111
|
+
},
|
|
112
|
+
"notes": "Formation cycle - first charge",
|
|
113
|
+
},
|
|
114
|
+
"time_series": time_series,
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
measurement_bundle = client.cell_measurement.create(
|
|
118
|
+
cell_instance.id, measurement_data
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
print(f"Created measurement: {measurement_bundle.measurement.name}")
|
|
122
|
+
print(f"Steps created: {measurement_bundle.steps_created}")
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### Reading cell data
|
|
126
|
+
|
|
127
|
+
```python
|
|
128
|
+
from ionworks import Ionworks
|
|
129
|
+
|
|
130
|
+
client = Ionworks()
|
|
131
|
+
|
|
132
|
+
# List all cell specifications
|
|
133
|
+
specs = client.cell_spec.list()
|
|
134
|
+
for spec in specs[:5]:
|
|
135
|
+
print(f" - {spec.name} (form_factor: {spec.form_factor})")
|
|
136
|
+
|
|
137
|
+
# Get a specific cell spec with full nested data
|
|
138
|
+
full_spec = client.cell_spec.get(spec_id)
|
|
139
|
+
print(f"Capacity: {full_spec.ratings['capacity']['value']} "
|
|
140
|
+
f"{full_spec.ratings['capacity']['unit']}")
|
|
141
|
+
|
|
142
|
+
# Get cell instance by slug
|
|
143
|
+
instance = client.cell_instance.get_by_slug("ncm622-gr-001")
|
|
144
|
+
|
|
145
|
+
# List measurements for an instance
|
|
146
|
+
measurements = client.cell_measurement.list(instance.id)
|
|
147
|
+
|
|
148
|
+
# Get measurement detail with time series
|
|
149
|
+
measurement_detail = client.cell_measurement.detail(measurement_id)
|
|
150
|
+
print(f"Time series shape: {measurement_detail.time_series.shape}")
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
### Running pipelines
|
|
154
|
+
|
|
155
|
+
Pipelines allow you to run complex workflows combining data fitting, calculations, and validations. A pipeline consists of named elements, where each element has an `element_type` and configuration specific to that type.
|
|
156
|
+
|
|
157
|
+
**Recommended workflow:** First upload your experimental data using the cell measurement API (see "Uploading data to the Ionworks app" above), then reference it in your pipeline using the `db:<measurement_id>` format. This ensures your data is stored and versioned in the database.
|
|
158
|
+
|
|
159
|
+
Available element types:
|
|
160
|
+
|
|
161
|
+
- `entry`: Provide initial parameter values
|
|
162
|
+
- `data_fit`: Fit model parameters to experimental data
|
|
163
|
+
- `calculation`: Run calculations (e.g., OCP fitting)
|
|
164
|
+
- `validation`: Validate model against data
|
|
165
|
+
|
|
166
|
+
```python
|
|
167
|
+
import time
|
|
168
|
+
from ionworks import Ionworks
|
|
169
|
+
|
|
170
|
+
client = Ionworks()
|
|
171
|
+
|
|
172
|
+
# First, upload your data (see "Uploading data to the Ionworks app" section)
|
|
173
|
+
# Then get the measurement ID to reference in the pipeline
|
|
174
|
+
measurements = client.cell_measurement.list(cell_instance_id)
|
|
175
|
+
measurement_id = measurements[0].id # or find the specific measurement you need
|
|
176
|
+
|
|
177
|
+
# Define entry configuration with initial parameter values
|
|
178
|
+
entry_config = {
|
|
179
|
+
"values": {
|
|
180
|
+
"Negative particle diffusivity [m2.s-1]": 3.3e-14,
|
|
181
|
+
"Positive particle diffusivity [m2.s-1]": 4e-15,
|
|
182
|
+
# ... other parameters
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
# Define datafit configuration - reference uploaded data with db:<measurement_id>
|
|
187
|
+
datafit_config = {
|
|
188
|
+
"objectives": {
|
|
189
|
+
"test_1C": {
|
|
190
|
+
"objective": "CurrentDriven",
|
|
191
|
+
"model": {"type": "SPMe"},
|
|
192
|
+
"data": f"db:{measurement_id}", # Reference data from database
|
|
193
|
+
"custom_parameters": {"Ambient temperature [K]": "initial_temperature"},
|
|
194
|
+
},
|
|
195
|
+
},
|
|
196
|
+
"parameters": {
|
|
197
|
+
"Negative particle diffusivity [m2.s-1]": {
|
|
198
|
+
"bounds": [1e-14, 1e-13],
|
|
199
|
+
"initial_value": 2e-14,
|
|
200
|
+
},
|
|
201
|
+
"Positive particle diffusivity [m2.s-1]": {
|
|
202
|
+
"bounds": [1e-15, 1e-14],
|
|
203
|
+
"initial_value": 2e-15,
|
|
204
|
+
},
|
|
205
|
+
},
|
|
206
|
+
"cost": {"type": "RMSE"},
|
|
207
|
+
"optimizer": {"type": "ScipyDifferentialEvolution"},
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
# Create pipeline config with named elements
|
|
211
|
+
pipeline_config = {
|
|
212
|
+
"elements": {
|
|
213
|
+
"entry": {**entry_config, "element_type": "entry"},
|
|
214
|
+
"fit data": {**datafit_config, "element_type": "data_fit"},
|
|
215
|
+
},
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
# Submit pipeline
|
|
219
|
+
pipeline = client.pipeline.create(pipeline_config)
|
|
220
|
+
print(f"Pipeline submitted: {pipeline.id}")
|
|
221
|
+
|
|
222
|
+
# Poll for completion
|
|
223
|
+
while True:
|
|
224
|
+
pipeline = client.pipeline.get(pipeline.id)
|
|
225
|
+
print(f"Status: {pipeline.status}")
|
|
226
|
+
if pipeline.status == "completed":
|
|
227
|
+
result = client.pipeline.result(pipeline.id)
|
|
228
|
+
print("Fitted parameters:", result.element_results["fit data"])
|
|
229
|
+
break
|
|
230
|
+
elif pipeline.status == "failed":
|
|
231
|
+
print("Pipeline failed:", pipeline.error)
|
|
232
|
+
break
|
|
233
|
+
time.sleep(2)
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
### Running simulations
|
|
237
|
+
|
|
238
|
+
The client supports running battery simulations using the Universal Cycler Protocol (UCP) format:
|
|
239
|
+
|
|
240
|
+
```python
|
|
241
|
+
from ionworks import Ionworks
|
|
242
|
+
|
|
243
|
+
client = Ionworks()
|
|
244
|
+
|
|
245
|
+
# Define a charge/discharge protocol in YAML format
|
|
246
|
+
protocol_yaml = """global:
|
|
247
|
+
initial_state_type: soc_percentage
|
|
248
|
+
initial_state_value: 50
|
|
249
|
+
initial_temperature: 25.0
|
|
250
|
+
steps:
|
|
251
|
+
- Charge:
|
|
252
|
+
mode: C-rate
|
|
253
|
+
value: "0.6"
|
|
254
|
+
ends:
|
|
255
|
+
- Voltage > 4.2
|
|
256
|
+
- Rest:
|
|
257
|
+
duration: 3600
|
|
258
|
+
- Discharge:
|
|
259
|
+
mode: C-rate
|
|
260
|
+
value: "0.5"
|
|
261
|
+
ends:
|
|
262
|
+
- Voltage < 2.5
|
|
263
|
+
"""
|
|
264
|
+
|
|
265
|
+
# Create simulation with quick model
|
|
266
|
+
config = {
|
|
267
|
+
"parameterized_model": {
|
|
268
|
+
"quick_model": {"capacity": 1.0, "chemistry": "NMC/Graphite"}
|
|
269
|
+
},
|
|
270
|
+
"protocol_experiment": {
|
|
271
|
+
"protocol": protocol_yaml,
|
|
272
|
+
"name": "NMC Charge Discharge Protocol",
|
|
273
|
+
},
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
result = client.simulation.protocol(config)
|
|
277
|
+
print(f"Simulation ID: {result.simulation_id}")
|
|
278
|
+
|
|
279
|
+
# Wait for completion
|
|
280
|
+
simulation = client.simulation.wait_for_completion(
|
|
281
|
+
result.simulation_id, timeout=60, poll_interval=2
|
|
282
|
+
)
|
|
283
|
+
|
|
284
|
+
# Get results
|
|
285
|
+
simulation_data = client.simulation.get_result(result.simulation_id)
|
|
286
|
+
time_series = simulation_data.get("time_series", {})
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
## Error Handling
|
|
290
|
+
|
|
291
|
+
The client will raise exceptions in the following cases:
|
|
292
|
+
|
|
293
|
+
- Missing API credentials
|
|
294
|
+
- Invalid API credentials
|
|
295
|
+
- API request errors (will raise `IonworksError` with details)
|
|
296
|
+
|
|
297
|
+
Make sure to handle these exceptions appropriately in your code.
|