PyBaMM Thermal Models, Measured: Zero Heating, Missing Entropy and a Cold Start at 25 °C
PyBaMM Thermal Models, Measured: Zero Heating, Missing Entropy and a Cold Start at 25 °C
Search
Ask the AI

PyBaMM Thermal Models, Measured: Zero Heating, Missing Entropy and a Cold Start at 25 °C

Turning on heat in PyBaMM is one option string: DFN({"thermal": "lumped"}). The simulation runs, a temperature curve appears, and it looks reasonable. The problems below are the ones that do not look wrong: a heating variable that is zero by construction, a heat source that four parameter sets leave out, a cell that cannot warm up because of how its geometry is written, and a cold-start study that quietly begins at 25 °C. All numbers were measured with PyBaMM 26.5.0 on the eight built-in lithium-ion parameter sets that carry thermal parameters.

PyBaMM battery modelling series · Silent failures, measured (part 3 of 7): Previous: PyBaMM Experiments That Finish and Are Wrong: Five Traps, Measured · Next: What PyBaMM’s Capacity Numbers Measure: “80% capacity”, Capacity [A.h] and Loss of Capacity to SEI · Series hub: Data pipeline (4) + Silent failures, measured (7) · RSS feed.

1. The default model is isothermal, and its heating variables are zero

pybamm.lithium_ion.DFN() uses "thermal": "isothermal". The cell temperature is set to the ambient temperature at every point and every instant; no energy balance is solved. That is a reasonable default. What is less obvious is what the heating variables contain:

Chen2020, 1C discharge Peak "Total heating [W]"
DFN() 0.0 W at every time point
DFN({"calculate heat source for isothermal models": "true"}) 1.04 W

In the isothermal submodel (pybamm/models/submodels/thermal/isothermal.py), unless that option is set, the Ohmic, irreversible, reversible and total heating and the surface cooling are all assigned the scalar 0. The variables exist, sol["Total heating [W]"] returns an array of the right length, and it is all zeros. A heat-generation map or a “how much heat does this protocol produce” table built on the default model reports a cell that produces none.

The second consequence: the isothermal model ignores "Initial temperature [K]". Its temperature is the ambient temperature, full stop. Setting only the initial temperature to 263.15 K gave a 1C capacity identical to the 25 °C value in all eight sets, to three decimals. Setting only the ambient temperature reproduced the full cold result.

2. Isothermal versus lumped is not a small correction

It is tempting to treat the lumped model as a refinement that nudges the answer. Here is a 1C and a 3C discharge in both models:

Set 1C: isothermal → lumped Rise 3C: isothermal → lumped Rise
Chen2020 4.992 → 5.001 A·h (+0.2%) 13.8 K 2.326 → 2.418 A·h (+4.0%) 58.0 K
OKane2022 4.955 → 5.005 A·h (+1.0%) 12.9 K 2.333 → 5.000 A·h (+114%) 49.6 K
ORegan2022 4.422 → 4.736 A·h (+7.1%) 15.6 K 1.110 → 3.506 A·h (+216%) 43.3 K
Ai2020 2.413 → 2.414 A·h (0.0%) 3.6 K 2.308 → 2.319 A·h (+0.5%) 13.8 K
Mohtat2020 4.836 → 4.852 A·h (+0.3%) 3.0 K 4.589 → 4.713 A·h (+2.7%) 9.3 K
Marquis2019 0.854 → 0.854 A·h (0.0%) 0.4 K 0.821 → 0.823 A·h (+0.2%) 1.2 K
Ecker2015 0.170 → 0.170 A·h (0.0%) 0.1 K 0.168 → 0.168 A·h (+0.1%) 0.6 K
NCA_Kim2011 0.476 → 0.476 A·h (0.0%) 0.0 K 0.461 → 0.461 A·h (0.0%) 0.1 K

Three things are going on in this table, and each gets its own section below.

  • For the three LG M50 sets at 3C, the lumped model heats the cell by 43–58 K, and whether that matters depends on the electrochemistry. OKane2022 and ORegan2022 gain 114% and 216% as the cell warms. Chen2020 heats by 58 K and gains 4%, because its electrolyte and particle transport do not depend on temperature (see the temperature table in the parameter-set article). The thermal model runs; the electrochemistry barely listens.
  • The size of the rise is set by a heat-transfer coefficient that nobody measured for your rig (section 3).
  • Four sets barely warm at all, for a reason that has nothing to do with chemistry (section 4).

Neither model is “the right one”. Isothermal means perfect cooling — a cell clamped to a large thermal mass. Lumped with the default coefficient means roughly a cell in still air. Pick the one that matches the test you are trying to reproduce, and know that at high rate they can disagree by a factor of three.

3. One unmeasured number decides the high-rate answer

The lumped model cools the cell through "Total heat transfer coefficient [W.m-2.K-1]" times "Cell cooling surface area [m2]". The built-in sets default it to 10 (Chen2020, OKane2022, ORegan2022, Marquis2019, Ecker2015), 5 (Mohtat2020), 25 (NCA_Kim2011) or 35 (Ai2020). Sweeping it on OKane2022 at 3C:

Heat-transfer coefficient Temperature rise 3C capacity
isothermal (infinite) 0 K 2.333 A·h
1000 W/m²·K 1.9 K 2.598 A·h
100 W/m²·K 15.1 K 4.647 A·h
25 W/m²·K 31.8 K 4.934 A·h
10 W/m²·K (default) 49.6 K 5.000 A·h
5 W/m²·K 62.3 K 5.025 A·h
1 W/m²·K 78.2 K 5.047 A·h

The 3C capacity ranges from 2.3 to 5.0 A·h depending on a single boundary condition. Note what the default row actually says: the cell delivers its full capacity at 3C because it peaks near 75 °C. A “3C is fine” conclusion from this model is a statement about a hot cell, with everything that implies for ageing and safety, not about the cell at room temperature.

The coefficient is not a cell property. It describes the fixture, the airflow and the neighbours. If you are comparing with measurements, it has to come from your setup — the cooling curve of a thermocouple on the can after a known heat pulse is enough to fit it — not from the parameter set.

4. Four of the eight sets describe a single sheet cooled on both faces

Whether a lumped cell can warm up is set by its thermal time constant, τ = heat capacity ÷ (h × cooling area). Computing it for each set:

Set Cooling area ÷ volume Heat capacity h τ Rise at 3C
NCA_Kim2011 12,143 m⁻¹ 9.4 J/K 25 7 s 0.1 K
Ecker2015 11,316 m⁻¹ 4.2 J/K 10 24 s 0.6 K
Marquis2019 7,295 m⁻¹ 14.1 J/K 10 25 s 1.2 K
Mohtat2020 10,459 m⁻¹ 121.1 J/K 5 59 s 9.3 K
Ai2020 394 m⁻¹ 41.3 J/K 35 195 s 13.8 K
Chen2020, OKane2022 219 m⁻¹ 42.8 J/K 10 806 s 58.0 / 49.6 K
ORegan2022 219 m⁻¹ 60.6 J/K 10 1141 s 43.3 K

The first four stand out. In each of them "Cell cooling surface area [m2]" equals twice the electrode height times the electrode width (to within 0.5%), and "Cell volume [m3]" equals the volume of the single modelled electrode stack. In other words the “cell” is one electrode sheet, cooled over both of its faces. That geometry sheds heat so fast that the lumped temperature follows the ambient within seconds to a minute. Switching these sets to "lumped" changes almost nothing — not because the thermal model is inactive, but because the cell it describes cannot hold heat. Mohtat2020 still reaches 9.3 K at 3C only because its reversible heat is unusually large (next section).

The LG M50 sets, by contrast, describe a real 21700 can: 0.00531 m² is the side plus both ends of a 21 × 70 mm cylinder, and the electrode stack fills 85% of the can volume. If you model a packaged cell with one of the first four sets, replace the geometry with the real can or pouch dimensions before trusting any temperature.

5. Four sets have no reversible heat

The heat a cell generates has three main parts: Ohmic heat in the electrolyte and solid, irreversible heat from reaction overpotentials, and reversible (entropic) heat, proportional to the temperature derivative of the open-circuit potential (Bernardi, Pawlikowski and Newman, 1985). In PyBaMM that derivative is "… electrode OCP entropic change [V.K-1]". In Chen2020, OKane2022, Ecker2015 and NCA_Kim2011 it is exactly 0.0 for both electrodes. The simulation runs with a whole heat source switched off, and nothing reports it.

How much that heat source matters, in the four sets that do include it (net share of total heat, lumped model):

Set C/5 1C 3C
Mohtat2020 96.4% 85.7% 73.6%
Ai2020 78.5% 46.8% 30.7%
ORegan2022 28.9% 19.3% 26.8%
Marquis2019 20.8% 5.5% 2.8%
Chen2020, OKane2022, Ecker2015, NCA_Kim2011 0% by construction

And what removing it does to the predicted temperature rise, in the same four sets:

Set C/5 rise: original → zero entropy 1C rise: original → zero entropy
ORegan2022 2.46 → 0.80 K (−68%) 15.6 → 11.0 K (−30%)
Ai2020 0.53 → 0.13 K (−75%) 3.58 → 1.70 K (−53%)
Mohtat2020 0.63 → 0.02 K (−97%) 3.04 → 0.25 K (−92%)
Marquis2019 0.069 → 0.011 K (−85%) 0.37 → 0.16 K (−56%)

The pattern is the expected one: reversible heat scales with current, irreversible and Ohmic heat roughly with current squared, so the missing term matters most at low rate — exactly the regime of slow cycling, drive-cycle averages and calendar-plus-cycling studies. The spread between sets (6% to 86% at 1C) is itself a warning: the entropic coefficient is chemistry- and state-of-charge-specific, and borrowing it from another set is not much better than leaving it at zero.

The LG M50 is the one cell where this can be checked directly. Chen2020 and OKane2022 describe it with zero entropic change and 700 J/kg·K for both electrodes and the separator; ORegan2022 carries the measured thermal parameters for the same cell (O’Regan et al., 2022), including entropic coefficients and a 42% larger heat capacity (60.6 vs 42.8 J/K). If you need temperatures for an LG M50, ORegan2022 is the set to start from.

6. The cold start that starts at 25 °C

This is the most expensive one. To simulate a cold discharge, the natural move is:

pv.update({"Ambient temperature [K]": 253.15})

In the isothermal model that is correct. In the lumped model it is not: the cell’s temperature starts from "Initial temperature [K]" — 298.15 K in every built-in set — and then relaxes toward the cold ambient with the time constant from section 4. For an LG M50 that is 13 to 19 minutes, about the length of a 3C discharge. So the simulated cell does most of its work warm.

Lumped model, discharge to cut-off Only ambient set (cell starts at 25 °C) Ambient and initial set
OKane2022, −20 °C, 3C 4.932 A·h in 19.7 min 0.275 A·h in 1.1 min
ORegan2022, −10 °C, 3C 3.170 A·h in 12.7 min 0.232 A·h in 0.9 min
ORegan2022, −20 °C, 2C 2.207 A·h in 13.2 min 0.212 A·h in 1.3 min
ORegan2022, −20 °C, 3C 3.082 A·h in 12.3 min 0.125 A·h in 0.5 min

A genuinely cold cell hits the voltage cut-off within a minute, before self-heating can rescue it. The same simulation with one parameter missing reports 10 to 25 times more charge and a cell that “handles −20 °C at 3C”.

When the cell does survive the first minute, the end capacity is almost identical either way — self-heating dominates by the end of discharge, and the capacity is decided there. The error moves to the power prediction instead:

Lumped model, 1C Voltage at 10 s, starts at 25 °C Voltage at 10 s, starts cold Capacity (both)
OKane2022, −10 °C 4.006 V 3.886 V 4.868 A·h
Mohtat2020, −20 °C 4.105 V 3.917 V 2.909 / 2.906 A·h
ORegan2022, −20 °C 4.108 V 3.888 V 3.414 / 3.384 A·h

A 0.12–0.22 V optimistic voltage in the first seconds is exactly the quantity a cold-cranking or power-capability study is after.

The experiment API handles this better than a bare parameter update, if you use it the way it wants. In 26.5, the temperature of the first step is copied into "Initial temperature [K]" (see _set_up_and_parameterise_experiment in simulation.py). So either of these starts the cell cold, and both reproduced the 0.275 A·h result above:

step = pybamm.step.string("Discharge at 3C until 2.5 V", temperature="-20oC")
experiment = pybamm.Experiment([step])

experiment = pybamm.Experiment(["Discharge at 3C until 2.5 V"], temperature=253.15)

Writing the temperature inside the string — "Discharge at 3C until 2.5 V at -20oC" — raises ValueError: Temperature must be specified as a keyword argument instead of in the string, which at least fails loudly. The silent case is the one above: the ambient temperature changed in ParameterValues, the initial temperature left alone.

7. A check to run after every thermal simulation

This takes a solved pybamm.Simulation and returns warnings for each set-up above that runs without error:

import numpy as np
import pybamm


def thermal_audit(sim):
    """Warnings for a solved pybamm.Simulation whose thermal set-up runs but misleads."""
    opts, pv, sol = sim.model.options, sim.parameter_values, sim.solution
    out = []
    if opts["thermal"] == "isothermal":
        if opts["calculate heat source for isothermal models"] == "false":
            out.append("isothermal: every heating variable is 0 unless "
                       "'calculate heat source for isothermal models' is 'true'")
        return out

    def first(name):
        return float(np.ravel(sol[name].entries)[0])

    T0 = first("Volume-averaged cell temperature [K]")
    T_amb = first("Volume-averaged ambient temperature [K]")
    if abs(T0 - T_amb) > 0.5:
        out.append(f"cell starts at {T0 - 273.15:.1f} °C but ambient is "
                   f"{T_amb - 273.15:.1f} °C: set 'Initial temperature [K]' too")

    for electrode in ("Negative", "Positive"):
        dUdT = pv[f"{electrode} electrode OCP entropic change [V.K-1]"]
        if isinstance(dUdT, (int, float)) and dUdT == 0:
            out.append(f"{electrode.lower()} electrode entropic change is 0: "
                       "no reversible heat from it")

    heat_capacity = (first("Volume-averaged effective heat capacity [J.K-1.m-3]")
                     * pv["Cell volume [m3]"])
    tau = heat_capacity / (pv["Total heat transfer coefficient [W.m-2.K-1]"]
                           * pv["Cell cooling surface area [m2]"])
    if tau < 120:
        out.append(f"thermal time constant is {tau:.0f} s: "
                   "this lumped cell behaves almost isothermally")
    return out

Output on a 1C discharge in each configuration:

Chen2020, default DFN
    isothermal: every heating variable is 0 unless 'calculate heat source for isothermal models' is 'true'
Chen2020, isothermal + heat source
    (no warnings)
Chen2020, lumped
    negative electrode entropic change is 0: no reversible heat from it
    positive electrode entropic change is 0: no reversible heat from it
OKane2022, lumped, only ambient -20 C
    cell starts at 25.0 °C but ambient is -20.0 °C: set 'Initial temperature [K]' too
    negative electrode entropic change is 0: no reversible heat from it
    positive electrode entropic change is 0: no reversible heat from it
OKane2022, lumped, step temperature -20 C
    negative electrode entropic change is 0: no reversible heat from it
    positive electrode entropic change is 0: no reversible heat from it
Marquis2019, lumped
    thermal time constant is 25 s: this lumped cell behaves almost isothermally
ORegan2022, lumped, both -10 C
    (no warnings)
Ai2020, lumped
    (no warnings)

What it cannot check: whether the heat-transfer coefficient matches your rig (section 3), and whether the electrochemistry responds to the temperature it is given (Chen2020 in section 2). Both need a comparison against measurement, not a code check. The entropic test only flags a constant zero; a set that supplies an entropic function is assumed to mean it.

8. How this was measured

  • PyBaMM 26.5.0, Python 3.13, Apple M3, default IDAKLUSolver. pybamm.lithium_ion.DFN with {"thermal": "isothermal"} or {"thermal": "lumped"}.
  • Every discharge starts at initial_soc=1.0 and runs through an Experiment to the set’s own Lower voltage cut-off [V].
  • Temperature rise is the maximum of "Volume-averaged cell temperature [K]" minus its initial value. Heat is the time integral, by the trapezoid rule over the solution’s time points, of "Total heating [W]" and its reversible, irreversible and Ohmic components.
  • τ uses "Volume-averaged effective heat capacity [J.K-1.m-3]" at t = 0 times "Cell volume [m3]", divided by h times the cooling area — the same terms the lumped submodel uses (lumped.py, base_thermal.py).
  • “Zero entropy” runs set both entropic-change parameters to 0 and change nothing else.
  • The eight sets are the built-in lithium-ion sets that define the lumped thermal parameters. Prada2013 and Ramadass2004 raise KeyError in lumped mode (missing current-collector thickness and cell volume respectively); that failure is loud and is not covered here.
  • Each set ran in its own process with a timeout. None hung.

References

Related: PyBaMM’s built-in parameter sets, measured, for why Chen2020 barely responds to temperature; PyBaMM Experiment pitfalls, for the other ways an experiment finishes and still returns the wrong thing; and PyBaMM solver convergence failures, for when a cold, high-rate run does not finish at all. How temperature enters SEI growth and lithium plating is measured in PyBaMM SEI and lithium plating pitfalls.

Leave a Reply

Scroll down