Run an ECM simulation#

Turn a model binding’s ECM Parameter Set into a PyBaMM Thevenin model, run experiments, and get BDF-named results ready to attach to the envelope. Requires the sim extra:

pip install "battwin[sim]"

Build and run#

import json
from battwin.sim import build_thevenin, run_experiment

ecm_ps = json.load(open("cell.ecm-ps.json"))
build = build_thevenin(ecm_ps, initial_soc=1.0, ambient_celsius=25.0)
for w in build.warnings:
    print("warning:", w)

columns = run_experiment(build, ["Discharge at 1C until 2.5 V"], period_s=10.0)
# columns: test_time_second, voltage_volt, current_ampere,
#          state_of_charge, surface_temperature_celsius

instructions are ordinary PyBaMM experiment strings, so multi-step protocols ("Charge at C/2 until 4.2 V", "Hold at 4.2 V until C/50", …) work as-is.

Behavior that matters#

Sign convention

Returned currents follow BDF (positive = charging); PyBaMM’s load-positive sign is flipped for you.

Values become interpolants

Constants pass straight through; 1-D tables become SoC interpolants; 2-D tables become (temperature, SoC) interpolants, with the document’s Kelvin axis converted to the Celsius axis PyBaMM’s ECM callbacks use. Expression strings are not part of ECM-PS and are rejected with a clear error.

Hysteresis is projected

PyBaMM’s basic Thevenin has a single OCV, so when an ECM-PS carries charge and discharge branches, their mean is used (2-D branches are read at the temperature nearest ambient); the branches stay untouched in the document. This and any similar simplification is surfaced in TheveninBuild.warnings.

Consistency is enforced at build time

build_thevenin raises a clear ValueError when the Circuit is missing an R{i}/C{i} pair implied by Number of RC elements, or when the OCV branches disagree about their SoC grid.

Write the results back#

Simulation output goes back into the envelope as ordinary spec objects, keeping all runtime activity inside the hash-chained document:

from datetime import datetime, timezone
from battwin import DataLink, StateSnapshot, load, save

v1 = load("cell.twin.json")
v2 = v1.next_version(
    data=list(v1.data) + [DataLink(kind="bdf", uri="sim/discharge_1c.bdf.csv", role="simulation")],
    state=StateSnapshot(
        as_of=datetime.now(timezone.utc),
        state_of_charge=columns["state_of_charge"][-1],
        method="ecm_simulation",
        source_data="sim/discharge_1c.bdf.csv",
    ),
)
save(v2, "cell.v2.twin.json")

PyBaMM telemetry

PyBaMM includes opt-out usage telemetry (via posthog). To keep a local run from reporting usage, set PYBAMM_DISABLE_TELEMETRY=true before importing PyBaMM; see the PyBaMM documentation for the current mechanism.