Creating Cells#
examples/cells/01_create.py#
"""
Example: Creating cell specifications, instances, and measurements.
This example demonstrates the new nested cell specification API with:
- Structured ratings (capacity, voltage limits with units)
- Nested component/material definitions
- Quantity format for numeric properties (value + unit)
- Measured properties on cell instances
"""
import os
from ionworks import Ionworks
import pandas as pd
client = Ionworks()
# Step 1: Create a new cell spec with nested component/material data
# The new API uses:
# - `ratings` object with Quantity format for electrical specs
# - Nested `anode`, `cathode`, etc. with `properties` and `material` objects
# - `Quantity` format: {"value": <number>, "unit": "<unit>"} for numeric properties
cell_spec = client.cell_spec.create_or_get(
{
"name": "NCM622/Graphite Coin Cell",
"form_factor": "R2032",
"manufacturer": "Custom Cells",
# Electrical ratings with units
"ratings": {
"capacity": {"value": 0.002, "unit": "A*h"},
"voltage_min": {"value": 2.5, "unit": "V"},
"voltage_max": {"value": 4.2, "unit": "V"},
"nominal_voltage": {"value": 3.6, "unit": "V"},
"energy": {"value": 7.2, "unit": "mW*h"},
"max_discharge_rate": {"value": 2, "unit": "C"},
"max_charge_rate": {"value": 1, "unit": "C"},
},
# Nested anode component with material
"anode": {
"properties": {
"diameter": {"value": 15, "unit": "mm"},
"thickness": {"value": 45, "unit": "um"},
"loading": {"value": 6.5, "unit": "mg/cm**2"},
"porosity": {"value": 35, "unit": "percent"},
"binder": "PVDF",
"conductive_additive": "Carbon Black",
},
"material": {
"name": "Graphite",
"manufacturer": "Customcells",
"definition": {
"type": "graphite",
"particle_size_d50": {"value": 15, "unit": "um"},
},
},
},
# Nested cathode component with material
"cathode": {
"properties": {
"diameter": {"value": 14, "unit": "mm"},
"thickness": {"value": 52, "unit": "um"},
"loading": {"value": 12.3, "unit": "mg/cm**2"},
"porosity": {"value": 30, "unit": "percent"},
"binder": "PVDF",
"conductive_additive": "Carbon Black",
},
"material": {
"name": "NCM622",
"manufacturer": "BASF",
"definition": {
"formula": "LiNi0.6Co0.2Mn0.2O2",
"type": "layered_oxide",
"specific_capacity": {"value": 180, "unit": "mA*h/g"},
},
},
},
# Nested electrolyte component with material
"electrolyte": {
"properties": {
"volume": {"value": 40, "unit": "uL"},
},
"material": {
"name": "LP57",
"manufacturer": "Gotion",
"definition": {
"type": "liquid",
"salt": "LiPF6",
"salt_concentration": {"value": 1.0, "unit": "mol/L"},
"solvents": ["EC", "EMC"],
"solvent_ratio": "3:7 w/w",
},
},
},
# Nested separator component with material
"separator": {
"properties": {
"diameter": {"value": 16, "unit": "mm"},
"thickness": {"value": 25, "unit": "um"},
"porosity": {"value": 40, "unit": "percent"},
},
"material": {
"name": "Celgard 2325",
"manufacturer": "Celgard",
"definition": {
"type": "trilayer",
"composition": "PP/PE/PP",
},
},
},
# Source/provenance information
"source": {
"creator_name": "Battery Lab",
"creator_orcid": "0000-0001-2345-6789",
"publication_date": "2024-01-15",
"license": "CC-BY-4.0",
},
# Other design properties
"properties": {
"assembly_method": "dry room",
"formation_cycles": {"value": 3, "unit": "dimensionless"},
},
"notes": "High-performance coin cell for rate capability testing",
"project_id": os.getenv("PROJECT_ID"),
}
)
print(f"Created cell spec: {cell_spec.name} (id: {cell_spec.id})")
print(f" Form factor: {cell_spec.form_factor}")
cap = cell_spec.ratings["capacity"]
print(f" Capacity: {cap['value']} {cap['unit']}")
v_min = cell_spec.ratings["voltage_min"]
v_max = cell_spec.ratings["voltage_max"]
print(f" Voltage range: {v_min['value']}-{v_max['value']} V")
# Step 2: Create a cell instance with measured properties
# measured_properties uses the same Quantity format, organized by component
cell_instance = client.cell_instance.create_or_get(
cell_spec.id,
{
"name": "NCM622-GR-001",
"batch": "BATCH-2024-001",
"date_manufactured": "2024-01-20",
# Measured properties per component (as-built values)
"measured_properties": {
"cathode": {
"loading": {"value": 12.1, "unit": "mg/cm**2"},
"thickness": {"value": 51, "unit": "um"},
},
"anode": {
"loading": {"value": 6.4, "unit": "mg/cm**2"},
"thickness": {"value": 44, "unit": "um"},
},
"cell": {
"initial_ocv": {"value": 3.02, "unit": "V"},
"weight": {"value": 3.2, "unit": "g"},
},
},
"notes": "First cell from batch, passed QC inspection",
},
)
print(f"\nCreated cell instance: {cell_instance.name} (id: {cell_instance.id})")
print(f" Batch: {cell_instance.batch}")
print(f" Date manufactured: {cell_instance.date_manufactured}")
# Step 3: Upload measurement with time series data
# Note: positive current = discharge (voltage decreases), negative current = charge
time_series = pd.DataFrame(
{
"Time [s]": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
"Voltage [V]": [3.0, 3.2, 3.5, 3.8, 4.0, 4.1, 4.15, 4.18, 4.19, 4.2],
"Current [A]": [-0.002] * 10, # 1C charge (negative = charge)
"Step count": [0] * 5 + [1] * 5,
"Cycle count": [0] * 10,
"Step from cycler": [1] * 5 + [2] * 5,
"Cycle from cycler": [0] * 10,
# Cumulative values reset at each step and only increase within step
"Discharge capacity [A.h]": [0.0] * 10, # No discharge during charge
"Charge capacity [A.h]": [
*[0.0, 0.001, 0.002, 0.003, 0.004],
*[0.0, 0.001, 0.002, 0.003, 0.004],
],
}
)
measurement_data = {
"measurement": {
"name": "Formation Cycle 1",
"protocol": {
"name": "CC-CV charge at C/10 to 4.2V, CC discharge at C/10 to 2.5V",
"ambient_temperature_degc": 25,
},
"test_setup": {
"cycler": "Biologic VMP3",
"operator": "Jane Smith",
"lab": "Battery Research Lab",
},
"notes": "Formation cycle - first charge",
},
"time_series": time_series,
}
try:
measurement_bundle = client.cell_measurement.create(
cell_instance.id, measurement_data
)
except Exception as e:
if "duplicate" in str(e).lower() or "409" in str(e):
# Find and delete existing measurement, then retry
measurements = client.cell_measurement.list(cell_instance.id)
for m in measurements:
if m.name == measurement_data["measurement"]["name"]:
client.cell_measurement.delete(m.id)
print(f"Deleted existing measurement: {m.name}")
break
measurement_bundle = client.cell_measurement.create(
cell_instance.id, measurement_data
)
else:
raise
print(f"\nCreated measurement: {measurement_bundle.measurement.name}")
if measurement_bundle.measurement.protocol:
print(f" Protocol: {measurement_bundle.measurement.protocol.get('name')}")
if measurement_bundle.measurement.test_setup:
print(f" Cycler: {measurement_bundle.measurement.test_setup.get('cycler')}")
print(f" Steps created: {measurement_bundle.steps_created}")
# Create another measurement for rate capability testing
# Step 0: discharge (positive current, voltage decreases)
# Step 1: charge (negative current, voltage increases)
rate_test_time_series = pd.DataFrame(
{
"Time [s]": list(range(20)),
"Voltage [V]": (
[4.2 - 0.06 * i for i in range(10)] # Discharge: voltage decreases
+ [3.6 + 0.06 * i for i in range(10)] # Charge: voltage increases
),
"Current [A]": [0.004] * 10 + [-0.004] * 10, # Discharge then charge
"Step count": [0] * 10 + [1] * 10,
"Cycle count": [0] * 20,
"Step from cycler": [1] * 10 + [2] * 10,
"Cycle from cycler": [0] * 20,
# Cumulative values reset at each step
"Discharge capacity [A.h]": [0.0 + 0.001 * i for i in range(10)]
+ [0.0] * 10, # Only during discharge
"Charge capacity [A.h]": [0.0] * 10
+ [0.0 + 0.001 * i for i in range(10)], # Only during charge
}
)
rate_test_data = {
"measurement": {
"name": "Rate Capability 2C",
"protocol": {
"name": "CC charge/discharge at 2C",
"ambient_temperature_degc": 25,
},
"test_setup": {
"cycler": "Biologic VMP3",
"operator": "Jane Smith",
"lab": "Battery Research Lab",
},
"notes": "Rate capability test at 2C",
},
"time_series": rate_test_time_series,
}
try:
rate_test_bundle = client.cell_measurement.create(cell_instance.id, rate_test_data)
except Exception as e:
if "duplicate" in str(e).lower() or "409" in str(e):
# Find and delete existing measurement, then retry
measurements = client.cell_measurement.list(cell_instance.id)
for m in measurements:
if m.name == rate_test_data["measurement"]["name"]:
client.cell_measurement.delete(m.id)
print(f"Deleted existing measurement: {m.name}")
break
rate_test_bundle = client.cell_measurement.create(
cell_instance.id, rate_test_data
)
else:
raise
print(f"\nCreated measurement: {rate_test_bundle.measurement.name}")
print(f" Steps created: {rate_test_bundle.steps_created}")
print("\n=== Summary ===")
print(f"Cell Specification: {cell_spec.name}")
print(f"Cell Instance: {cell_instance.name}")
meas1 = measurement_bundle.measurement.name
meas2 = rate_test_bundle.measurement.name
print(f"Measurements: {meas1}, {meas2}")