Fit ECM parameters#

Identify circuit parameters of an ECM-PS document from measured data with PyBOP, getting back a new, schema-valid document with the fitted constants and full fit provenance. Requires the fit extra:

pip install "battwin[fit]"

Fit against a twin’s linked data#

from battwin import load
from battwin.fit import fit_thevenin, read_bdf

env = load("cell.twin.json")
link = next(d for d in env.data if d.role == "characterization")

result = fit_thevenin(
    env.models[0].inline,          # the ECM-PS document to calibrate
    read_bdf(link.uri),            # BDF columns: test_time_second, current_ampere, voltage_volt
    fit=["R0 [Ohm]"],              # which Circuit parameters to identify
    initial_soc=1.0,
    ambient_celsius=25.0,
    source_data=link.uri,          # recorded in the fit provenance
)

result.fitted              # {"R0 [Ohm]": 0.01865}
result.rmse_volt           # residual RMS voltage error (V)
result.initial_rmse_volt   # the same cost before fitting
result.ecm_ps              # new document: fitted constants + User-defined provenance

The data columns follow BDF naming and sign (positive = charging); fit_thevenin flips to PyBaMM’s load-positive convention internally, and drives the simulation with the measured current profile, so any protocol works as fitting data, not just constant current.

Defaults, and how to override them#

Initial values

taken from the base document (a table’s value at ambient temperature and mid-SoC, with a warning that the fitted constant replaces the table); override per parameter with initial={"R0 [Ohm]": 0.01}.

Bounds

a factor of 10 either side of the initial value; override with bounds={"R0 [Ohm]": (0.001, 0.05)}. A fitted value at a bound is a red flag — see below.

Optimiser

PyBOP’s SciPyMinimize minimising RMS voltage error, capped by max_iterations.

Attach the result to the twin#

The fitted document is an ordinary ECM-PS; give it its own binding beside the original (sections replace wholesale, so carry the existing bindings forward), with a validity window matching the conditions it was fitted under:

from battwin import ModelBinding, ValidityWindow, save

v2 = env.next_version(
    models=list(env.models) + [
        ModelBinding(kind="custom", name="calibrated (bench 1C, 25 degC)",
                     inline=result.ecm_ps,
                     validity=ValidityWindow(temperature_celsius=(20.0, 30.0)))
    ]
)
save(v2, "cell.v2.twin.json")

What is fittable, honestly#

  • Only R/C circuit parameters ("R0 [Ohm]", "R1 [Ohm]", "C1 [F]", …) can be fitted, each as a constant. OCV curves are not fittable here; they come from characterization.

  • Name only what your data can identify. A smooth constant-current discharge determines the effective total resistance well, but cannot separate R0 from the RC branches — fitting all of them on such data drives parameters to their bounds while the cost barely improves. Dynamic data (pulses, GITT, drive cycles) is what separates the time constants.

  • Fit provenance lands in the result document’s User-defined section under "pybop": optimiser, initial and fitted values, residuals, iteration count, conditions, and the source_data URI, so a fitted model always names its evidence.