Skip to content

Commit faa42d2

Browse files
committed
Catch time overflow during steady-state simulation
* Remove the old and faulty check for finiteness of `x` in `Model::fxdot`. Closes #3076. * The root cause of those NaNs in `x` was not CVODES. The problem is uncaught integer overflow during steady-state simulations. Catch that. * Add a dedicated error code * Add debug-level expectations to all integration functions of `Solver`
1 parent 47e2517 commit faa42d2

File tree

6 files changed

+64
-31
lines changed

6 files changed

+64
-31
lines changed

include/amici/defines.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ constexpr int AMICI_SINGULAR_JACOBIAN= -9987;
9393
constexpr int AMICI_NOT_IMPLEMENTED= -999;
9494
constexpr int AMICI_MAX_TIME_EXCEEDED = -1000;
9595
constexpr int AMICI_NOT_RUN= -1001;
96+
constexpr int AMICI_T_OVERFLOW= -1002;
9697
constexpr int AMICI_SUCCESS= 0;
9798
constexpr int AMICI_DATA_RETURN= 1;
9899
constexpr int AMICI_ROOT_RETURN= 2;

python/tests/test_preequilibration.py

Lines changed: 48 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,18 @@
11
"""Tests for pre- and post-equilibration"""
22

3+
import inspect
34
import itertools
45

56
import amici
67
import numpy as np
78
import pytest
89
from amici.debugging import get_model_for_preeq
10+
from amici.importers.antimony import antimony2amici
911
from amici.sim.sundials import (
1012
AMICI_ERROR,
1113
AMICI_SUCCESS,
1214
ExpData,
1315
LogSeverity,
14-
LogSeverity_debug,
1516
ParameterScaling,
1617
SensitivityMethod,
1718
SensitivityOrder,
@@ -592,7 +593,7 @@ def test_simulation_errors(preeq_fixture):
592593
for e in [edata, edata_preeq]:
593594
rdata = run_simulation(model, solver, e)
594595
assert rdata["status"] != AMICI_SUCCESS
595-
assert rdata._swigptr.messages[0].severity == LogSeverity_debug
596+
assert rdata._swigptr.messages[0].severity == LogSeverity.debug
596597
assert rdata._swigptr.messages[0].identifier == "EQUILIBRATION_FAILURE"
597598
assert (
598599
"exceeded maximum number of integration steps"
@@ -601,26 +602,51 @@ def test_simulation_errors(preeq_fixture):
601602
assert rdata._swigptr.messages[1].severity == LogSeverity.error
602603
assert rdata._swigptr.messages[1].identifier == "OTHER"
603604

604-
# too long simulations
605-
solver.set_max_steps(int(1e4))
606-
solver.set_relative_tolerance_steady_state(0.0)
607-
solver.set_absolute_tolerance_steady_state(0.0)
608-
# preeq & posteq
609-
for e in [edata_preeq, edata]:
610-
rdata = run_simulation(model, solver, e)
611-
assert rdata["status"] != AMICI_SUCCESS
612-
messages = []
613-
# remove repeated RHSFUNC_FAIL messages
614-
for message in rdata._swigptr.messages:
615-
if not messages or message.message != messages[-1].message:
616-
messages.append(message)
617-
assert messages[0].severity == LogSeverity.debug
618-
assert messages[0].identifier.endswith(":RHSFUNC_FAIL")
619-
assert messages[1].severity == LogSeverity.debug
620-
assert messages[1].identifier == "EQUILIBRATION_FAILURE"
621-
assert "exceedingly long simulation time" in messages[1].message
622-
assert messages[2].severity == LogSeverity.error
623-
assert messages[2].identifier == "OTHER"
605+
606+
def test_t_overflow():
607+
"""Test that equilibration fails with an informative error message
608+
upon time overflow."""
609+
module_name = inspect.stack()[0].function
610+
ant_str = f"""
611+
model {module_name}
612+
# Constant increase so the solver will take large steps;
613+
# small enough to let `t` to overflow before `x`.
614+
dxx_dt = 1e-16
615+
xx' = dxx_dt
616+
xx = 0
617+
end
618+
"""
619+
with TemporaryDirectory(prefix=module_name) as outdir:
620+
model = antimony2amici(
621+
ant_str,
622+
model_name=module_name,
623+
output_dir=outdir,
624+
fixed_parameters=["dxx_dt"],
625+
)
626+
model.set_steady_state_computation_mode(
627+
SteadyStateComputationMode.integrationOnly
628+
)
629+
630+
# require simulation until forever
631+
solver = model.create_solver()
632+
solver.set_relative_tolerance_steady_state(0)
633+
solver.set_absolute_tolerance_steady_state(0)
634+
635+
edata_preeq = ExpData(model)
636+
edata_preeq.set_timepoints([np.inf])
637+
edata_preeq.fixed_parameters_pre_equilibration = [1e-16]
638+
edata_posteq = ExpData(model)
639+
edata_posteq.set_timepoints([float("inf")])
640+
641+
for edata in (edata_preeq, edata_posteq):
642+
rdata = run_simulation(model, solver, edata=edata)
643+
assert rdata.status == AMICI_ERROR
644+
for msg in rdata.messages:
645+
if "exceedingly long simulation time" in msg.message:
646+
assert msg.identifier == "EQUILIBRATION_FAILURE"
647+
break
648+
else:
649+
assert False, list(rdata.messages)
624650

625651

626652
def test_get_model_for_preeq(preeq_fixture):

src/amici.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ std::map<int, std::string> simulation_status_to_str_map = {
5959
{AMICI_UNREC_SRHSFUNC_ERR, "AMICI_UNREC_SRHSFUNC_ERR"},
6060
{AMICI_RTFUNC_FAIL, "AMICI_RTFUNC_FAIL"},
6161
{AMICI_LINESEARCH_FAIL, "AMICI_LINESEARCH_FAIL"},
62+
{AMICI_T_OVERFLOW, "AMICI_T_OVERFLOW"},
6263
};
6364

6465
std::unique_ptr<ReturnData> run_simulation(

src/forwardproblem.cpp

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,12 @@ void EventHandlingSimulator::run_steady_state(
197197
auto tout = std::isfinite(next_t_event)
198198
? next_t_event
199199
: std::max(ws_->sol.t, 1.0) * 10;
200+
201+
if(!std::isfinite(tout)) {
202+
// tout overflowed
203+
throw IntegrationFailure(AMICI_T_OVERFLOW, tout);
204+
}
205+
200206
auto status = solver_->step(tout);
201207
solver_->write_solution(ws_->sol);
202208

@@ -914,7 +920,7 @@ SteadyStateStatus SteadyStateProblem::find_steady_state_by_simulation(
914920
ex.time
915921
);
916922
return SteadyStateStatus::failed_convergence;
917-
case AMICI_RHSFUNC_FAIL:
923+
case amici::AMICI_T_OVERFLOW:
918924
if (model_->get_logger())
919925
model_->get_logger()->log(
920926
LogSeverity::debug, "EQUILIBRATION_FAILURE",

src/solver.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
#include "amici/model.h"
66

77
#include <sundials/sundials_context.h>
8+
#include <gsl/gsl-lite.hpp>
89

910
#include <cstdio>
1011
#include <filesystem>
@@ -138,6 +139,8 @@ void Solver::apply_max_num_steps_B() const {
138139
}
139140

140141
int Solver::run(realtype const tout) const {
142+
gsl_ExpectsDebug(std::isfinite(tout));
143+
141144
set_stop_time(tout);
142145
CpuTimer const cpu_timer;
143146
int status = AMICI_SUCCESS;
@@ -157,6 +160,8 @@ int Solver::run(realtype const tout) const {
157160
}
158161

159162
int Solver::step(realtype const tout) const {
163+
gsl_ExpectsDebug(std::isfinite(tout));
164+
160165
int status = AMICI_SUCCESS;
161166

162167
apply_max_num_steps();
@@ -173,6 +178,8 @@ int Solver::step(realtype const tout) const {
173178
}
174179

175180
void Solver::run_b(realtype const tout) const {
181+
gsl_ExpectsDebug(std::isfinite(tout));
182+
176183
CpuTimer cpu_timer;
177184

178185
apply_max_num_steps_B();

src/solver_cvodes.cpp

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1178,14 +1178,6 @@ static int fxdot(realtype t, N_Vector x, N_Vector xdot, void* user_data) {
11781178
return AMICI_MAX_TIME_EXCEEDED;
11791179
}
11801180

1181-
if (t > 1e200
1182-
&& !model->check_finite(gsl::make_span(x), ModelQuantity::xdot, t)) {
1183-
/* when t is large (typically ~1e300), CVODES may pass all NaN x
1184-
to fxdot from which we typically cannot recover. To save time
1185-
on normal execution, we do not always want to check finiteness
1186-
of x, but only do so when t is large and we expect problems. */
1187-
return AMICI_UNRECOVERABLE_ERROR;
1188-
}
11891181

11901182
model->fxdot(t, x, xdot);
11911183
return model->check_finite(gsl::make_span(xdot), ModelQuantity::xdot, t);

0 commit comments

Comments
 (0)