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
SciPyMinimizeminimising RMS voltage error, capped bymax_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-definedsection under"pybop": optimiser, initial and fitted values, residuals, iteration count, conditions, and thesource_dataURI, so a fitted model always names its evidence.