Skip to content

[Bug]: CC→CV step transition passes algebraically-inconsistent state to rootfinder (regression in #5380) #5449

@TomTranter

Description

@TomTranter

PyBaMM Version

26.3

Python Version

3.13

Describe the bug

Since commit 4d0c6cd (PR #5380, perf: precalculate state mapper for mapping between experimental steps, merged Feb 2026), the state vector passed as the rootfinder's initial guess at an experiment step transition is no longer algebraically consistent when the previous and next steps use different built-model instances (as is the case for CC→CV in any pybamm.Experiment).

Specifically, when the target model has an algebraic state variable (e.g. Current [A] in a CV hold) that was an input rather than a state in the previous step, build_initial_state_mapper cannot find a y-slice match, falls back to the target model's original initial condition (typically 0), and hands that to calculate_consistent_state. The Newton rootfinder then has to reduce a residual of ~1.5 from a seed that assumes zero current when the CC step actually ended at full charge current.

pybamm.SolverError: Could not find consistent states: Could not find acceptable solution:
solver terminated unsuccessfully and maximum solution error (0.24956) above tolerance (1e-06)

Steps to Reproduce


import sys
import numpy as np
import casadi
import pybamm


_residual_log = []


def _install_probe():
    orig = pybamm.solvers.base_solver.BaseSolver.calculate_consistent_state

    def patched(self, model, time=0, inputs=None):
        try:
            y0 = model.y0_list[0] if hasattr(model, "y0_list") else model.y0
            inp_dict = inputs[0] if isinstance(inputs, list) else (inputs or {})
            inp_vec = casadi.vertcat(*[v for v in inp_dict.values()])
            fun = model.casadi_algebraic(time, y0, inp_vec)
            r = float(np.max(np.abs(np.asarray(fun).ravel())))
            _residual_log.append(r)
            print(f"  [consistent-init] t={time:9.3f}  ||F(y0)||_inf={r:.6e}")
        except Exception as e:
            print(f"  [consistent-init] probe failed: {e}")
        return orig(self, model, time=time, inputs=inputs)

    pybamm.solvers.base_solver.BaseSolver.calculate_consistent_state = patched


def main():
    _install_probe()

    experiment = pybamm.Experiment(
        [
            (
                "Charge at 3 C until 4.2 V",
                "Hold at 4.2 V for 1800 seconds or until C/20",
                "Discharge at 1 C until 2.5 V",
            ),
        ],
        period="1s",
    )

    params = pybamm.ParameterValues("Chen2020")
    model = pybamm.lithium_ion.DFN()
    solver = pybamm.CasadiSolver()

    sim = pybamm.Simulation(
        model, parameter_values=params, experiment=experiment, solver=solver
    )
    print(f"pybamm {pybamm.__version__}")
    sim.solve(initial_soc=0.05)

    cv_entry_residual = _residual_log[1]
    print(f"\nCV-entry initial-guess residual: {cv_entry_residual:.3e}")
    if cv_entry_residual > 1e-3:
        print("REGRESSION: CV-entry initial guess is not algebraically consistent.")
        sys.exit(1)
    print("OK: CV-entry initial guess is consistent.")


if __name__ == "__main__":
    main()

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions