Skip to content

Commit df53205

Browse files
authored
Update IDAKLUSolver error handling (#5291)
* raise `SolverError` at failure to init sundials * Update simulation.py * Update idaklu_solver.py * reuse `pybammsolvers` error messages * Update test_idaklu_solver.py * bump `pybammsolvers` * Update CHANGELOG.md * Update CHANGELOG.md Update CHANGELOG.md
1 parent 593d2aa commit df53205

File tree

5 files changed

+20
-51
lines changed

5 files changed

+20
-51
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
## Bug fixes
88

9+
- Fixed a bug where `IDAKLUSolver` errors were not raised correctly. ([#5291](https://github.com/pybamm-team/PyBaMM/pull/5291))
910
- Fix a bug with serialising `InputParameter`s. ([#5289](https://github.com/pybamm-team/PyBaMM/pull/5289))
1011

1112
# [v25.10.1](https://github.com/pybamm-team/PyBaMM/tree/v25.10.1) - 2025-11-14

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ classifiers = [
2525
"Topic :: Scientific/Engineering",
2626
]
2727
dependencies = [
28-
"pybammsolvers>=0.3.0,<0.4.0",
28+
"pybammsolvers>=0.3.3,<0.4.0",
2929
"black",
3030
"numpy",
3131
"scipy>=1.11.4",

src/pybamm/simulation.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -822,7 +822,7 @@ def solve(
822822
feasible = False
823823
# If none of the cycles worked, raise an error
824824
if cycle_num == 1 and step_num == 1:
825-
raise error
825+
raise error from error
826826
# Otherwise, just stop this cycle
827827
break
828828

src/pybamm/solvers/idaklu_solver.py

Lines changed: 14 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -684,13 +684,17 @@ def _integrate(
684684
atol = self._check_atol_type(atol, y0full.size)
685685

686686
timer = pybamm.Timer()
687-
solns = self._setup["solver"].solve(
688-
t_eval,
689-
t_interp,
690-
y0full,
691-
ydot0full,
692-
inputs,
693-
)
687+
try:
688+
solns = self._setup["solver"].solve(
689+
t_eval,
690+
t_interp,
691+
y0full,
692+
ydot0full,
693+
inputs,
694+
)
695+
except ValueError as e:
696+
# Return from None to replace the C++ runtime error
697+
raise pybamm.SolverError(str(e)) from None
694698
integration_time = timer.time()
695699

696700
return [
@@ -737,14 +741,15 @@ def _post_process_solution(self, sol, model, integration_time, inputs_dict):
737741
termination = "final time"
738742
elif sol.flag < 0:
739743
termination = "failure"
744+
msg = idaklu.sundials_error_message(sol.flag)
740745
match self._on_failure:
741746
case "warn":
742747
warnings.warn(
743-
f"FAILURE {self._solver_flag(sol.flag)}, returning a partial solution.",
748+
msg + ", returning a partial solution.",
744749
stacklevel=2,
745750
)
746751
case "raise":
747-
raise pybamm.SolverError(f"FAILURE {self._solver_flag(sol.flag)}")
752+
raise pybamm.SolverError(msg)
748753

749754
if sol.yp.size > 0:
750755
yp = sol.yp.reshape((number_of_timesteps, number_of_states)).T
@@ -1005,40 +1010,3 @@ def jaxify(
10051010
t_interp=t_interp,
10061011
)
10071012
return obj
1008-
1009-
@staticmethod
1010-
def _solver_flag(flag):
1011-
flags = {
1012-
99: "IDA_WARNING: IDASolve succeeded but an unusual situation occurred.",
1013-
2: "IDA_ROOT_RETURN: IDASolve succeeded and found one or more roots.",
1014-
1: "IDA_TSTOP_RETURN: IDASolve succeeded by reaching the specified stopping point.",
1015-
0: "IDA_SUCCESS: Successful function return.",
1016-
-1: "IDA_TOO_MUCH_WORK: The solver took mxstep internal steps but could not reach tout.",
1017-
-2: "IDA_TOO_MUCH_ACC: The solver could not satisfy the accuracy demanded by the user for some internal step.",
1018-
-3: "IDA_ERR_FAIL: Error test failures occurred too many times during one internal time step or minimum step size was reached.",
1019-
-4: "IDA_CONV_FAIL: Convergence test failures occurred too many times during one internal time step or minimum step size was reached.",
1020-
-5: "IDA_LINIT_FAIL: The linear solver's initialization function failed.",
1021-
-6: "IDA_LSETUP_FAIL: The linear solver's setup function failed in an unrecoverable manner.",
1022-
-7: "IDA_LSOLVE_FAIL: The linear solver's solve function failed in an unrecoverable manner.",
1023-
-8: "IDA_RES_FAIL: The user-provided residual function failed in an unrecoverable manner.",
1024-
-9: "IDA_REP_RES_FAIL: The user-provided residual function repeatedly returned a recoverable error flag, but the solver was unable to recover.",
1025-
-10: "IDA_RTFUNC_FAIL: The rootfinding function failed in an unrecoverable manner.",
1026-
-11: "IDA_CONSTR_FAIL: The inequality constraints were violated and the solver was unable to recover.",
1027-
-12: "IDA_FIRST_RES_FAIL: The user-provided residual function failed recoverably on the first call.",
1028-
-13: "IDA_LINESEARCH_FAIL: The line search failed.",
1029-
-14: "IDA_NO_RECOVERY: The residual function, linear solver setup function, or linear solver solve function had a recoverable failure, but IDACalcIC could not recover.",
1030-
-15: "IDA_NLS_INIT_FAIL: The nonlinear solver's init routine failed.",
1031-
-16: "IDA_NLS_SETUP_FAIL: The nonlinear solver's setup routine failed.",
1032-
-20: "IDA_MEM_NULL: The ida mem argument was NULL.",
1033-
-21: "IDA_MEM_FAIL: A memory allocation failed.",
1034-
-22: "IDA_ILL_INPUT: One of the function inputs is illegal.",
1035-
-23: "IDA_NO_MALLOC: The ida memory was not allocated by a call to IDAInit.",
1036-
-24: "IDA_BAD_EWT: Zero value of some error weight component.",
1037-
-25: "IDA_BAD_K: The k-th derivative is not available.",
1038-
-26: "IDA_BAD_T: The time t is outside the last step taken.",
1039-
-27: "IDA_BAD_DKY: The vector argument where derivative should be stored is NULL.",
1040-
}
1041-
1042-
flag_unknown = "Unknown IDA flag."
1043-
1044-
return flags.get(flag, flag_unknown)

tests/unit/test_solvers/test_idaklu_solver.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -583,7 +583,7 @@ def test_failures(self):
583583
solver = pybamm.IDAKLUSolver()
584584

585585
t_eval = [0, 3]
586-
with pytest.raises(ValueError):
586+
with pytest.raises(pybamm.SolverError):
587587
solver.solve(model, t_eval)
588588

589589
def test_dae_solver_algebraic_model(self):
@@ -779,7 +779,7 @@ def test_solver_options(self):
779779
options = {option: options_fail[option]}
780780
solver = pybamm.IDAKLUSolver(options=options)
781781

782-
with pytest.raises(ValueError):
782+
with pytest.raises(pybamm.SolverError):
783783
solver.solve(model, t_eval)
784784

785785
def test_with_output_variables(self):
@@ -1487,7 +1487,7 @@ def test_on_failure_option(self):
14871487
model, t_eval=t_eval, t_interp=t_interp, inputs=input_parameters
14881488
)
14891489
assert len(w) > 0
1490-
assert "FAILURE" in str(w[0].message)
1490+
assert "_FAIL" in str(w[0].message)
14911491

14921492
def test_no_progress_early_termination(self):
14931493
# SPM at rest

0 commit comments

Comments
 (0)