Skip to main content

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.

Before uploading, raw data from your cycler needs to be converted into the Ionworks data format. The ionworksdata library reads files from common battery cyclers, auto-detects formats, and normalizes units, timestamps, and column names.
pip install ionworksdata

Supported cyclers

ionworksdata auto-detects the file format when possible and produces a polars DataFrame with the standard columns.
CyclerFile types
Arbin.csv, .xlsx, .res
BaSyTec.csv
BioLogic.mpt, .mpr, .txt
Maccor.txt, .csv, .xls, .xlsx
Neware.csv, .xls, .xlsx (multi-sheet supported; automatic Latin-1 fallback for CSVs)
Novonix.csv
Repower.csv
Generic CSV.csv (any CSV with recognized column headers or custom mappings)
BDF (Battery Data Format).bdf, .bdf.gz, .bdf.parquet, .csv
import ionworksdata as iwd

df = iwd.read.time_series("my_test.mpt", "biologic")

Custom column mappings

If your CSV uses non-standard column names, map them to the standard names with extra_column_mappings. The reader skips auto-detection for any column you explicitly map, so values are used as-is without rescaling.
import ionworksdata as iwd

df = iwd.read.time_series(
    "my_data.csv",
    "csv",
    extra_column_mappings={
        "vCell": "Voltage [V]",
        "iCell": "Current [A]",
        "t": "Time [s]",
        "Tcell": "Temperature [degC]",
    },
)
Partial mappings work too — any standard column you don’t map is still auto-detected:
df = iwd.read.time_series(
    "my_data.csv",
    "csv",
    extra_column_mappings={"vCell": "Voltage [V]"},
)
Use extra_column_mappings when your CSV comes from a custom test setup or proprietary cycler. Mapped values are preserved exactly — no unit conversion or scaling is applied. Ensure your data is already in the expected standard units (V, A, s, °C) before using this parameter.

Battery Data Format (BDF)

ionworksdata can read and write files in the Battery Data Format (BDF) defined by the Battery Data Alliance. CSV, gzipped CSV, and parquet variants are all supported. Files are auto-detected by their header or extension (.bdf, .bdf.gz, .bdf.parquet).
import ionworksdata as iwd

# Read
df = iwd.read.bdf("cell.bdf")
df = iwd.read.time_series("cell.bdf.parquet")  # via the generic entrypoint

# Write
iwd.write.bdf(df, "out.bdf")                  # CSV with preferred labels
iwd.write.bdf(df, "out.bdf.gz")               # gzipped CSV
iwd.write.bdf(df, "out.bdf.parquet")          # parquet
iwd.write.bdf(df, "out.bdf", use_machine_readable_names=True)
BDF does not mandate a current sign convention. The reader normalises current to the Ionworks convention (positive = discharge) on load, so third-party BDF files that follow the opposite IEC convention are flipped automatically. The writer emits whatever convention is in the input DataFrame — pass the data through transform.set_positive_current_for_discharge first if you need to guarantee discharge-positive output.

EIS and impedance data

InstrumentFile types
BioLogic.mpt, .mpr, .txt (files containing impedance columns)
Gamry.dta (ZCURVE table)
Impedance data is read into columns Frequency [Hz], Z_Re [Ohm], Z_Im [Ohm], Z_Mod [Ohm], and Z_Phase [deg].
import ionworksdata as iwd

df = iwd.read.gamry("eis_measurement.dta")
See the data format page for the full column spec and sign convention.

Troubleshooting

Incorrect current sign convention

Problem: When uploading measurement data, you receive an error like:
Current sign convention error: positive current appears to be charge, not discharge.
Solution: Ionworks expects positive current = discharge and negative current = charge. If your cycler uses the opposite convention, convert the data before uploading:
import ionworksdata as iwd

data = iwd.transform.set_positive_current_for_discharge(data)

Ambiguous current sign convention

Problem: When uploading measurement data, you receive an error like:
Current sign convention error: the sign convention is ambiguous.
This happens when all current values have the same sign, so the validator cannot determine whether positive means charge or discharge. Solution: Use the same transform — it uses voltage-response analysis (fitting an OCV-R equivalent circuit model under both sign conventions) to infer charge vs. discharge direction even when all currents share the same sign:
import ionworksdata as iwd

data = iwd.transform.set_positive_current_for_discharge(data)
If the automatic approach does not work for your data (for example, flat voltage profiles), you can manually apply a sign based on the step type column from your cycler:
import polars as pl

# Replace "Step type" and "charge" with your cycler's column name
# and step-type label (e.g. "CC_Charge", "Charge", "C", etc.)
data = data.with_columns(
    pl.when(pl.col("Step type") == "charge")
    .then(-pl.col("Current [A]").abs())
    .otherwise(pl.col("Current [A]").abs())
    .alias("Current [A]")
)

Time not cumulative

Problem: Time resets to 0 for each cycle. Solution: Track a cumulative time offset:
cumulative_time_offset = 0.0
for cycle_num, cycle_df in df.group_by("Cycle from cycler"):
    cycle_df = cycle_df.with_columns(
        (pl.col("Time [s]") + cumulative_time_offset).alias("Time [s]")
    )
    cumulative_time_offset = cycle_df["Time [s]"].max()

Step count not cumulative

Problem: Step count resets for each cycle. Solution: Track a step count offset across cycles:
step_offset = 0
for cycle_num, cycle_df in df.group_by("Cycle count"):
    cycle_df = cycle_df.with_columns(
        (pl.col("Step from cycler") + step_offset).alias("Step count")
    )
    step_offset = cycle_df["Step count"].max() + 1

Missing capacity columns

Problem: Capacity calculation fails. Solution: Ensure you have Time [s], Current [A], and Voltage [V] columns before calculating capacity.

Non-UTF-8 CSV files (e.g. Neware)

Problem: Reading a Neware CSV file fails with an encoding error. Solution: The Neware reader automatically falls back to Latin-1 if UTF-8 decoding fails, so no action is needed in most cases:
import ionworksdata as iwd

df = iwd.read.time_series("neware_data.csv", "neware")
To explicitly control the encoding:
df = iwd.read.time_series(
    "neware_data.csv",
    "neware",
    options={"file_encoding": "latin-1"},
)

Next steps

Data format

Full reference for recognized columns, units, and sign conventions.

Uploading data

Upload prepared data as cell specs, instances, and measurements.

ionworksdata API reference

Complete reference for read, write, transform, steps, and load.

ionworksdata on GitHub

Report issues or browse the source.