Switching on SEI growth and lithium plating in PyBaMM takes two option strings, and the simulation always produces a fade curve. What decides the shape of that curve is a set of choices that never raise an error: how long the protocol takes, which SEI law you pick, what temperature the rates see, whether pores are allowed to fill, and how the plating law behaves at rest. This article measures each of them in PyBaMM 26.5.0, mostly with the OKane2022 (LG M50) parameter set — the only built-in full-cell set that ships parameters for SEI, lithium plating, particle cracking and stress-driven loss of active material together, although four SEI variants still need parameters it lacks (section 2). Its companion, what PyBaMM’s capacity numbers mean, covers how to read the result.
PyBaMM battery modelling series · Silent failures, measured (part 5 of 7): Previous: What PyBaMM’s Capacity Numbers Measure: “80% capacity”, Capacity [A.h] and Loss of Capacity to SEI · Next: PyBaMM Solver Convergence Failures: Numerics or Physics · Series hub: Data pipeline (4) + Silent failures, measured (7) · RSS feed.
1. SEI is a clock
O’Kane et al. used the solvent-diffusion-limited SEI law ("SEI": "solvent-diffusion limited"), which is also the option PyBaMM’s coupled-degradation example pairs with OKane2022. Its growth rate is −D_sol · c_sol · F / L_sei times one Arrhenius factor (sei_growth.py, 185–187 and 216–220): it depends on the SEI thickness and the temperature, and on nothing else — not current, not electrode potential, not state of charge. Three consequences follow. The first has come up repeatedly on PyBaMM’s forums as a suspected bug; the other two follow from the same law.
Faster charging gives less SEI per cycle
100 cycles of 1C discharge with different charge rates (SPM, SEI only):
| Charge rate | Simulated time | SEI lithium |
|---|---|---|
| C/3 | 18.1 days | 0.00926 A·h |
| 1C | 10.9 days | 0.00656 A·h |
| 2C | 9.6 days | 0.00598 A·h (−35%) |
The SEI per cycle simply tracks how long the cycle takes. Adding partially reversible plating reverses the trend, though only weakly at 25 °C: read after the discharge, LLI is 0.200 / 0.202 / 0.215% for C/3 / 1C / 2C in the SPM (0.198 / 0.204 / 0.232% in the DFN), against 0.122 / 0.086 / 0.079% for SEI alone, and plating makes up 39–64% of it. Read at the end of the cycle (after charging) the same runs report 0.39–0.47%, because about half of that is reversibly plated lithium — see the read-point section of the companion article.
Rests change how many cycles fit, not when the cell dies
DFN, SEI only (no plating), SEI solvent diffusivity ×300 to reach end of life in a feasible run, pore clogging on, 1C discharge / C/3 CC-CV:
| Rest after each half-cycle | Measured 1C capacity reaches 90% of its own cycle 2 | Day it crosses the same 4.376 A·h |
|---|---|---|
| none | cycle 382, day 64.9 | 64.9 |
| 10 hours | cycle 72, day 70.4 | 65.0 |
Against the same absolute threshold both schedules cross on the same day, and their capacity-versus-time curves differ by less than 0.001 A·h over 68 days. (The 5.5-day gap in the middle column is an artefact of the reference: the rested run’s cycle 2 comes almost a day later, so its 90% threshold is lower.) The rest schedule changes the cycle count to end of life 5.3-fold and the calendar date not at all. A study that shortens or drops rests to save compute is changing its cycle-count answer, not just its run time.
Storage state of charge cannot matter
With no potential term in the rate, 1000 hours of storage at 10%, 50% and 100% SOC give the same 0.0158801 A·h of SEI in both SPM and DFN (at tight tolerance), equal to the closed form L² = L₀² + 2·V̄·D_sol·c_sol·t to seven significant figures. This is an identity of the law, not a measurement about cells. If you need SOC-dependent calendar ageing or a charge-rate effect on SEI, you need a potential-dependent law — “reaction limited” with the default exchange current grows 149 times more SEI at 100% than at 10% SOC in 100 hours — and data at two or more SOCs to calibrate it (section 2).
Box: a start_time bug that silently removes calendar time. To simulate “one cycle per day”, you give the first step of each cycle a start_time, and PyBaMM pads the gap with a rest. In 26.5 this breaks whenever the same step appears more than once anywhere in the experiment. Experiment deduplicates steps by their repr — the text plus any tags, description and start_time (experiment.py, line 79) — then writes each step’s schedule onto those shared objects (lines 262–276), so every repeat inherits the schedule of its first occurrence and the padding rests never happen. With two "Rest for 5 minutes" per cycle it fails silently: five “daily” cycles ran back to back in 21.7 hours instead of 100.3, and the SEI came out 3.8 times too low (0.00085 vs 0.00320 A·h), with nothing logged. With one rest per cycle the run instead stopped after two cycles with an ERROR, “Step time must be >0”; with ten cycles of the first variant it ended early with a misleading “default duration was reached” warning. Giving every step a per-cycle tag, pybamm.step.string(text, tags=[f"c{i}s{j}"]), restores the schedule exactly; building new step objects with the same arguments does not. PyBaMM’s own unit test for repeated steps with start_time uses the failing pattern and passes, because it counts steps rather than simulated time. We reported it upstream as issue #5809 on 27 September 2026; a fix is open as PR #5817 and is still under review (not merged as of 4 October 2026).
2. The SEI rate constants are placeholders
OKane2022’s SEI rate constants — exchange current, solvent diffusivity and concentration, electron conductivity, interstitial diffusivity, EC rate constant and diffusivity — are identical to Chen2020’s generic example values, and to most other shipped sets. Only the activation energy (38 kJ/mol instead of 0) and the lithium-per-SEI ratio (1 instead of 2) differ. The O’Kane et al. paper used only the solvent-diffusion model, called D_sol = 2.5 × 10⁻²² m²/s “PyBaMM’s default value”, and varied it from 2.5 × 10⁻²² to 1.25 × 10⁻²⁰ m²/s as a sensitivity study rather than fitting it. (The parameter-set article flagged the same for Chen2020, Marquis2019, Mohtat2020 and Ai2020; it applies to OKane2022 too.)
So switching the SEI option does not switch between fitted models. It switches between generic constants of unrelated sizes. Same cell, same protocol, only the "SEI" option changed (DFN, pore clogging on, 1C / C/3 CC-CV, 25 °C; default mesh and tolerance, with the “reaction limited” and “ec reaction limited” rows also checked on finer meshes):
| SEI option | LLI after 300 cycles |
|---|---|
| “reaction limited” | 2.99% |
| “solvent-diffusion limited” (O’Kane’s choice) | 0.25% |
| “electron-migration limited” | 0.13% |
| “interstitial-diffusion limited” | 0.0067% |
| “ec reaction limited” | 1C capacity below 80% at cycle 16 (7.4% LLI); later steps skipped without a message |
| “constant” | 0 — it means constant thickness, not constant rate |
About 450× between the working options, and one that kills the cell in sixteen cycles. With pore clogging off (the default), “ec reaction limited” in the SPM instead reports 48% LLI after 1000 cycles, which would need 3.9 µm of SEI in pores that are full at 0.65 µm. Four more options — both “(asymmetric)” variants, “tunnelling limited” and “VonKolzenberg2020” — stop with a KeyError naming a parameter OKane2022 does not define. That is the good kind of failure.
Calibrating does not make the choice go away
To make every law give the same 1.00% LLI at cycle 100 (SPM, converged tolerance, pore clogging on), its one rate constant has to be scaled by ×1.06 (SEI exchange current), ×38 (solvent diffusivity), ×108 (electron conductivity), ×3990 (interstitial diffusivity), ×3.6 × 10⁻⁴ (EC rate constant) or ×2.9 × 10⁻³ (EC diffusivity). After that, the laws agree at cycle 100 by construction but spread 1.6× by cycle 300 and 1.9× by cycle 500: diffusion-type laws grow roughly as √t, reaction-type laws roughly linearly. One early data point cannot choose between them.
And the DFN adds something the SPM cannot show. The calibrated reaction-limited law, run in the DFN, delivers 88% of its cycle-2 capacity at cycle 526–527 and 4% one cycle later, at only 5.6% LLI, because the SEI grows faster next to the separator (641–647 nm locally against a 453–457 nm average) and fills the pores there first. Nothing is logged. The SPM with the same parameter keeps running to cycle 1000 and reports 7.7% LLI, but by then its SEI fills 96% of the pores and its negative-electrode electrolyte is 26 times more concentrated than at the start (section 7): it is not describing a cell either.
3. Temperature: one Arrhenius factor, and it is zero almost everywhere
Every SEI law shares a single Arrhenius factor, exp(E_sei/R · (1/T_ref − 1/T)), and every SEI rate constant is a plain scalar. "SEI growth activation energy [J.mol-1]" is 0 in every built-in set that ships SEI parameters except OKane2022 (and its graphite/SiOx half-cell variant, both 38 kJ/mol). What that means depends on the law (SPM, 100 cycles of 1C discharge and C/2 CC-CV, both ambient and initial temperature set):
- Solvent-diffusion SEI becomes temperature-independent: at equal elapsed time, 0, 25 and 45 °C differ by less than 0.02% in Chen2020, Marquis2019, Mohtat2020, Ai2020 and Ecker2015.
- Reaction-limited SEI grows faster when it is colder. SEI lithium at 0 °C is 1.67× (Ai2020), 2.48× (Marquis2019), 2.70× (Mohtat2020), 4.44× (Chen2020) and 8.56× (Ecker2015) the 25 °C value, and 0.47–0.71× at 45 °C. In the per-cycle rate at cycle 2, the explicit F/RT in the exponent multiplies the rate by 1.7–2.1 at 0 °C, and the lower anode potential when cold by a further 1.0–6.4 (13.5× in total for Ecker2015; the 100-cycle ratios above are smaller than these first-cycle factors).
- OKane2022’s 38 kJ/mol makes its solvent-diffusion SEI behave as expected (0.38× at 0 °C, 1.83× at 45 °C), but reaction-limited SEI with the same activation energy is U-shaped: 1.18× at 0 °C and 1.16× at 45 °C, so 25 °C is the lowest of the three temperatures tested.
This is the “hotter cell degrades less” result users report. With Chen2020 (activation energy 0) and reaction-limited SEI, switching an SPM from isothermal 25 °C to the lumped thermal model (h = 10 W/m²·K, mean cell temperature 28.8 °C) lowers 100-cycle SEI lithium by 10% (67.9 → 60.9 mAh). With OKane2022’s 38 kJ/mol the same switch changes reaction-limited SEI by +0.3%.
Plating kinetics ignore temperature
In OKane2022 (and Ecker2015, which uses the same functions) the plating and stripping exchange currents are F·k·c_e and F·k·c_Li. The temperature argument is accepted and never used. Cold plating then comes only from the 1/T in the Butler–Volmer exponent and from the other kinetics and transport slowing down. How much an activation energy would matter, at 0 °C (isothermal DFN, one 2C CC-CV charge from empty):
| Activation energy on plating and stripping | Peak plated lithium |
|---|---|
| none (as shipped) | 173 mAh |
| 20 kJ/mol | 110 mAh |
| 35 kJ/mol | 79 mAh |
| 50 kJ/mol | 56 mAh |
Over 20 cycles at 0 °C with 2C charging, 35 kJ/mol cuts irreversible lithium loss from 19.2 to 11.6 mAh (−39%). It is not a uniform reduction: after the single 2C charge in the table and a 1-hour rest, the lithium still plated rises from 5.7 to 20.4 mAh, because stripping slows as well. The energies are illustrative; OKane2022 ships none, and until you choose one every sub-zero plating result describes kinetics that do not know the temperature.
Isothermal versus lumped is a choice, not a correction
OKane2022 DFN with SEI, partially reversible plating and pore clogging, 1C discharge / 2C CC-CV at 25 °C ambient. The lumped cell averages 37.8 °C (peak 54.6 °C) at h = 10 W/m²·K and 29.3 °C at h = 30. Irreversible lithium loss (SEI + dead lithium, read after discharge):
| Cycle | Isothermal | Lumped, h = 10 W/m²·K | Lumped, h = 30 W/m²·K |
|---|---|---|---|
| 100 | 17.65 mAh | 12.33 mAh (isothermal +43%) | 14.20 mAh (+24%) |
| 500 | 50.2 mAh | 33.5 mAh (+50%) | 39.8 mAh (+26%) |
The relative difference levels off (1.43× at cycle 100, 1.50× at cycle 500 for h = 10) instead of compounding, because OKane2022’s dead-lithium formation rate scales with L₀/L and slows the way SEI does. The mechanism split differs too: at cycle 500 the isothermal run has 31.8 mAh of dead lithium and 18.4 of SEI; the h = 10 run has 8.5 and 25.0. OKane2022’s degradation parameters were not fitted to ageing data, and O’Kane et al. included no thermal model, holding the temperature constant. Neither boundary condition is validated for these parameters, so this measures the choice, not an error in one option. The thermal-model article covers why the heat-transfer coefficient deserves as much care as any degradation parameter.
4. Pore clogging, knees and the mesh
The option documentation lists "SEI porosity change" and "lithium plating porosity change" as two switches. The code has one: if either is "true", PyBaMM installs a single porosity model that subtracts SEI, plated and dead lithium and SEI on cracks together (base_lithium_ion_model.py, 457–472). Runs with only one flag, the other, or both are bitwise identical.
The knee is a state, not a cycle number
With pore clogging on, OKane2022’s SEI + plating fade collapses when SEI has filled about 80–83% of the initial negative-electrode pore volume: plated lithium closes the rest next to the separator during each charge. With the shipped geometry that is 0.49 A·h of SEI lithium, whether the SEI solvent diffusivity is multiplied by 1000 or 10,000; doubling the lithium each unit of SEI volume holds (two lithium per SEI molecule instead of one, so the same pores hold twice the SEI lithium) moves it to 0.99–1.02 A·h. At the shipped SEI rate, SEI reaches only about 0.5% LLI in 1000 cycles, far below this threshold; the knee O’Kane et al. report appears only in their 30–50× cracking-rate cases. In their 50× cracking case (1C discharge / 0.3C CC-CV to C/100) the 1C capacity reaches 80% at cycle 615 ± 2 on every mesh from x_n = 20 down to a 0.067 µm cell at the separator; x_n = 10 is 2% late. SEI-driven clogging converges at PyBaMM’s default mesh, and so does the SEI + plating knee at 25 °C (cycle 145 on the default mesh, 142–143 on every finer one).
A cold plating clog does not
One condition — DFN, OKane2022 with its temperature-independent plating kinetics, SEI + partially reversible plating, pore clogging on, isothermal −10 °C, 2C CC-CV from empty, 1-hour rest, 1C discharge:
| Width of the negative-electrode cell at the separator | 1st discharge | 5th discharge |
|---|---|---|
| 4.26 µm (default, 20 cells) | 3.12 A·h | 3.06 A·h |
| 1.07 µm (80 cells) | 0.75 A·h | 3.09 A·h |
| 0.27 µm (320 cells) | 0.62 A·h | 1.33 A·h |
| 0.067 µm (graded) | 0.59 A·h | 1.22 A·h |
| 0.008 µm (graded) | 0.59 A·h | 1.00 A·h |
| pore clogging off, any mesh | 3.12–3.15 A·h | 3.07–3.09 A·h |
| clogging on, plating Arrhenius 35 kJ/mol, 20 vs 640 cells | 3.154 vs 3.178 A·h | — |
During the 4.2 V hold, reversibly plated lithium fills the pores of the cell next to the separator; the hold current reaches C/20 after 9–12 minutes instead of about 186, and the charge stops at 1.2–1.4 A·h. How thick that blocking layer is depends on how thick the mesh cell is, so the first discharge converges only once that cell is 0.07 µm or thinner — two orders of magnitude below the particle radius — and later cycles do not converge at all. A 100× tighter solver tolerance changes nothing. The default mesh overstates the first discharge 5.3-fold, with no warning and a normal termination. The problem disappears with clogging off, or when the plating kinetics get a temperature dependence (last row).
Mesh advice: report the width of the negative-electrode cell at the separator, not the cell count. pybamm.Exponential1DSubMesh with side="right" and 40 or more cells reproduces a uniform mesh with the same separator cell to within 0.7% (a 20-cell graded mesh was 2% off), in up to 28 times less run time. Set its stretch, or raise the point count, until the separator-side cell is as thin as the case needs: the default stretch with 20 points gives only 1.16 µm. Never quote a minimum porosity as a number: it did not converge in any case we ran.
How clogged runs end
After a clogging cliff PyBaMM may stop within a few cycles or keep running silently. In the 50× cracking case it stopped one to five cycles later with an ERROR-level IDA_ERR_FAIL or IDA_BAD_K, or a WARNING that the “Zero negative electrode porosity cut-off” fired, depending on the mesh. The SEI + plating knee, on the default mesh, ran on to cycle 300 with no message while its electrolyte concentration went negative from cycle 163 (see the companion article). When it does stop, it returns a truncated solution without raising an exception, so check sol.termination, the log and the electrolyte concentration. Separately, a step whose experiment event is already violated at its start is replaced by an empty solution with no message, whatever its skip_ok setting (skip_ok is consulted only when every step of a cycle is skipped; simulation.py, 970–978 and 1081–1082). That is how the “ec reaction limited” run above kept “running” after cycle 17.
5. The default solver tolerance biases the SPM
PyBaMM’s default IDAKLUSolver uses rtol = 10⁻⁴, atol = 10⁻⁶. Per-cycle SEI growth is a tiny fraction of the SEI state, and in the SPM the integrator takes steps of up to 40 minutes through a charge. For potential-dependent laws, whose rate changes by orders of magnitude within such a step, that shows up as bias:
| SPM, OKane2022 | Default tolerance | Converged (rtol 10⁻⁷–10⁻⁸) |
|---|---|---|
| “interstitial-diffusion limited”, LLI after 1000 cycles | 0.037% | 0.018% (default doubles it) |
| “reaction limited”, LLI after 200 cycles | 1.952% | 1.849% (+5.6%) |
| “reaction limited”, measured 1C fade after 200 cycles | +6.3% at default tolerance | |
| “solvent-diffusion limited”, LLI after 1000 cycles | 0.489% | 0.489% |
(The interstitial default-tolerance value comes from a run with pore clogging off; that SEI uses 0.23% of the pore volume, so the flag makes no difference.) The DFN, which takes much smaller steps for other reasons, agreed with rtol 10⁻⁶ to within 10⁻⁵ relative in every case we checked. Fix: for SPM or SPMe with a potential-dependent SEI law, pass solver=pybamm.IDAKLUSolver(rtol=1e-6, atol=1e-8), which brings reaction-limited LLI within 0.4% of converged; use rtol 10⁻⁷ if the absolute interstitial value matters (it is still 5.5% high at 10⁻⁶). Tightening atol alone does not help. Splitting a long run into blocks with starting_solution is safe (see the companion article).
6. “irreversible” plating deposits lithium at rest
PyBaMM’s "lithium plating": "irreversible" is its reversible Butler–Volmer plating law with the stripping term deleted (plating.py, 94–97). There is no threshold at 0 V versus lithium. With OKane2022’s exchange current (F·k·c_e = 0.0965 A/m²) it deposits lithium whenever the graphite is lithiated, current or no current:
- A fresh DFN cell (pore clogging off) resting 24 hours at 100% SOC and 25 °C books 0.761 A·h — 10% of the lithium in the particles — as dead lithium (0.789 A·h with clogging on), while the plating overpotential never falls below +0.086 V: the graphite is nowhere near lithium-metal potential. Integrating the Tafel rate by hand, outside PyBaMM, gives 0.7612 A·h.
- After a full C/3 recharge, the C/3 capacity is 16.4% below the same cell without plating (4.162 vs 4.977 A·h).
- In ordinary 25 °C C/3 cycling, where the anode never drops below +0.037 V, one discharge, rest and recharge books 0.131 A·h.
This is by design rather than an accident. The model’s author has said plating continues, more slowly, above 0 V versus lithium (discussion #2031), and a maintainer called “irreversible” unrealistic but kept it because it is common in the literature (discussion #3447). PyBaMM’s own lithium-plating example notebook shows the effect in a one-hour rest after charging at −5 °C and calls the roughly 40 mAh “unrealistic”; re-running it in 26.5 gives 39.0–39.8 mAh at every charge rate from C/8 to 2C. The option’s API documentation says nothing about it, and no warning is raised. Neither O’Kane et al. 2020 nor 2022 defines an irreversible model; the irreversible Tafel law the 2022 paper cites (Yang et al. 2017) starts plating only below 0 V versus lithium and uses an exchange current 96 times smaller.
Fix: use "partially reversible", which is what O’Kane et al. used. It has the same unthresholded plating term, but most of what it plates at rest strips again: 0.0093 A·h in the same 24-hour rest, against 0.761 A·h. It is not free of the effect — issue #3605, still open, reports plating loss at zero current in PyBaMM’s coupled-degradation example, which uses it. If you need an irreversible law, write a thresholded one.
7. The SPM: two more things to know
- SEI on cracks leaks lithium back. In the SPM or SPMe with
"SEI on cracks"and the default surface form, 98% of the SEI-on-cracks lithium never leaves the particles (issue #5263, open). Setting"surface form": "algebraic"closes the ledger. - With pore clogging on, the SPM concentrates its electrolyte. It holds porosity × concentration fixed in each electrode with no exchange with the separator, so as pores fill, the negative-electrode electrolyte concentration rises as ε₀·c₀/ε. After 24 hours of “irreversible” plating at rest it reaches 1,239 mol/m³ against the DFN’s 1,078, which inflates the SPM’s deposit by about 11% (0.864 vs 0.780 A·h with clogging off); with shipped reaction-limited SEI it heads to physically impossible concentrations as the SEI approaches the pore-fill limit. The DFN also keeps salt in shrinking pores (1,139 mol/m³ at 30% pore fill in our aged-cell runs) but lets it diffuse into the separator and positive electrode. And because the SPM averages porosity through the electrode, it cannot represent clogging next to the separator at all.
8. A pre-flight check
This takes an unsolved pybamm.Simulation and flags the set-ups above that run without an error:
import pybamm
POTENTIAL_DEPENDENT = ("reaction limited", "ec reaction limited",
"interstitial-diffusion limited", "electron-migration limited",
"tunnelling limited", "VonKolzenberg2020")
def sei_preflight(sim):
"""Warnings for an unsolved degradation Simulation that will run but mislead."""
opts, pv = sim.model.options, sim.parameter_values
out = []
# options can be per-electrode tuples: (negative, positive)
sei, plating = (o[0] if isinstance(o, tuple) else o
for o in (opts["SEI"], opts["lithium plating"]))
if sei not in ("none", "constant"):
if pv["SEI growth activation energy [J.mol-1]"] == 0:
out.append("SEI activation energy is 0: SEI growth has no Arrhenius "
"temperature dependence")
if (opts["SEI porosity change"] == "false"
and opts["lithium plating porosity change"] == "false"):
out.append("pore clogging is off (both porosity flags 'false'): "
"SEI can grow past full pores")
if (sei.split(" (")[0] in POTENTIAL_DEPENDENT
and isinstance(sim.model, pybamm.lithium_ion.SPM)
and getattr(sim.solver, "rtol", 0) > 1e-6):
out.append(f"'{sei}' in an SPM/SPMe at rtol={sim.solver.rtol:g}: "
"use rtol<=1e-6, atol<=1e-8")
if plating != "none":
j0 = pv["Exchange-current density for plating [A.m-2]"]
cold, warm = (pv.evaluate(j0(pybamm.Scalar(1000.0), pybamm.Scalar(0.0),
pybamm.Scalar(T))) for T in (263.15, 298.15))
if cold == warm:
out.append("plating exchange current does not depend on temperature")
if plating == "irreversible":
out.append("'irreversible' plating deposits lithium at any electrode "
"potential, including at rest")
exp = getattr(sim, "experiment", None)
if exp is not None and exp.initial_start_time is not None:
if len({id(s) for s in exp.steps}) < len(exp.steps):
out.append("start_time with repeated identical steps: give every step "
"a unique tag, or the scheduled rests are skipped")
return out
Output on seven configurations (the SPMe is a subclass of the SPM in PyBaMM, so the tolerance check covers both):
P1 DFN, OKane2022, SEI + partially reversible plating
pore clogging is off (both porosity flags 'false'): SEI can grow past full pores
plating exchange current does not depend on temperature
P2 SPM, Chen2020, reaction-limited SEI, default solver
SEI activation energy is 0: SEI growth has no Arrhenius temperature dependence
pore clogging is off (both porosity flags 'false'): SEI can grow past full pores
'reaction limited' in an SPM/SPMe at rtol=0.0001: use rtol<=1e-6, atol<=1e-8
P3 DFN, OKane2022, irreversible plating, pore clogging on
plating exchange current does not depend on temperature
'irreversible' plating deposits lithium at any electrode potential, including at rest
P4 SPM, OKane2022, 'one cycle per day' with start_time
start_time with repeated identical steps: give every step a unique tag, or the scheduled rests are skipped
P4b same, every step tagged per cycle
(no warnings)
P5 DFN, OKane2022, SEI + partially reversible plating, clogging on, plating Arrhenius 35 kJ/mol
(no warnings)
P6 DFN, OKane2022, SEI + plating, only 'lithium plating porosity change' set
plating exchange current does not depend on temperature
What it cannot check: whether the SEI rate constant was fitted to your cell (section 2), and whether the mesh resolves a plating clog (section 4). For the first you need ageing data; for the second, run the case again with a finer separator-side cell and compare. A pass also says nothing about how you read the result — that is the companion article’s fade_audit.
9. How this was measured
- PyBaMM 26.5.0, Python 3.13, Apple M3,
IDAKLUSolver. OKane2022 unless stated; 25 °C isothermal unless stated; cycles of 1C discharge to 2.5 V and a CC-CV charge to 4.2 V with CV to C/20. - Accelerated runs name the multiplied parameter. SEI results are kept only while the SEI fits in the negative-electrode pores (651 nm of growth, 0.612 A·h of lithium for OKane2022); results past that appear only as labelled counterexamples. Lithium loss and plating are read after the discharge step.
- Most headline numbers were checked at finer meshes (x_n 40 and 80, and meshes graded toward the separator) and at rtol 10⁻⁶–10⁻⁷. The exceptions are the DFN solvent-diffusion, electron-migration and interstitial rows in section 2 and the DFN 1C-charge value in section 1, run at x_n = 20 and default tolerance only, and the cases a section says do not converge.
- The work ran in two rounds — independent measurements, then an adversarial re-verification against fixed standards (pore-fill limit, read point, tolerance, mesh). Several first-round results did not survive and are not reported.
References
- S. E. J. O’Kane et al., Lithium-ion battery degradation: how to model it, Phys. Chem. Chem. Phys. 24 (2022) 7909–7922.
- S. E. J. O’Kane, I. D. Campbell, M. W. J. Marzook, G. J. Offer, M. Marinescu, Physical origin of the differential voltage minimum associated with lithium plating in Li-ion batteries, J. Electrochem. Soc. 167 (2020) 090540.
- X.-G. Yang, Y. Leng, G. Zhang, S. Ge, C.-Y. Wang, Modeling of lithium plating induced aging of lithium-ion batteries: transition from linear to nonlinear aging, J. Power Sources 360 (2017) 28–40.
- PyBaMM examples: lithium plating, coupled degradation
- PyBaMM issue #3605 (plating at rest), issue #5263 (SPM SEI on cracks), discussions #2031 and #3447 (irreversible plating), #2992 (SEI and temperature)
Related: What PyBaMM’s capacity numbers actually measure; PyBaMM thermal model pitfalls; PyBaMM Experiment pitfalls; and PyBaMM solver convergence failures.
Companion resources
Download the companion scripts
Battery Modeling for AI / GUIDE
PyBaMM silent-failure checks README
Pins pybamm==26.5.0 (verified 2026-09-26), says what each file does and how to run the two test scripts, and links both articles.
Battery Modeling for AI / CODE
sei_preflight.py: SEI and plating preflight
Checks a Simulation before it is solved: zero SEI activation energy, pore clogging off, solver tolerance for potential-dependent SEI in an SPM, temperature-independent plating exchange current, irreversible plating, and start_time with repeated steps.
Battery Modeling for AI / CODE
test_preflight.py: sei_preflight cases
Builds the seven Simulation configurations from the SEI article (P1–P6 and P4b) and prints the sei_preflight warnings for each, without solving.
Battery Modeling for AI / CODE
fade_audit.py: capacity-reading audit
Checks a solved degradation run: cycles that end after a charge, "% capacity" termination comparing the eSOH capacity, negative electrolyte concentration, and SEI past half of what would fill the negative pores with clogging off.
Battery Modeling for AI / CODE
test_audit.py: four fade_audit cases
Runs four degradation cases on OKane2022, one per argument T1 to T4, and prints the discharge capacity and fade_audit warnings.
