Skip to content

Commit ac8ab87

Browse files
author
Yi Zhang
committed
use more informative error msg in cvodes
1 parent 5b3362a commit ac8ab87

4 files changed

Lines changed: 119 additions & 67 deletions

File tree

stan/math/prim/err/check_flag_sundials.hpp

Lines changed: 116 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -12,74 +12,128 @@ namespace math {
1212
#define CHECK_CVODES_CALL(call) cvodes_check(call, #call)
1313
#define CHECK_KINSOL_CALL(call) kinsol_check(call, #call)
1414

15-
/**
16-
* Throws a std::runtime_error exception when a Sundial function fails
17-
* (i.e. returns a negative flag)
18-
*
19-
* @param flag Error flag
20-
* @param func_name Name of the function that returned the flag
21-
* @throw <code>std::runtime_error</code> if the flag is negative
22-
*/
23-
inline void cvodes_check(int flag, const char* func_name) {
24-
if (flag < 0) {
25-
std::ostringstream ss;
26-
ss << func_name << " failed with error flag " << flag << ": "
27-
<< CVodeGetReturnFlagName(flag) << ".";
28-
if (flag == -1 || flag == -4) {
29-
throw std::domain_error(ss.str());
30-
} else {
31-
throw std::runtime_error(ss.str());
15+
/**
16+
* Map cvodes error flag to acutally error msg. The most frequent
17+
* errors are put at the top. An alternative would be to use std::map
18+
* but in our case the difference would be negligible. Note that we
19+
* don't use CVGetReturnFlagName function to retrieve the constant
20+
* because sanitizer indicates it contains mem leak.
21+
*
22+
* @param flag
23+
*
24+
* @return error msg string constant and actuall informative msg
25+
*/
26+
inline std::array<std::string, 2> cvodes_flag_msg(int flag) {
27+
std::array<std::string, 2> msg;
28+
switch (flag) {
29+
case -1 : msg = {"CV_TOO_MUCH_WORK" , "The solver took mxstep internal steps but could not reach tout"}; break; // NOLINT
30+
case -2 : msg = {"CV_TOO_MUCH_ACC" , "The solver could not satisfy the accuracy demanded by the user for some internal step"}; break; // NOLINT
31+
case -3 : msg = {"CV_ERR_FAILURE" , "Error test failures occurred too many times during one internal time step or minimum step size was reached"}; break; // NOLINT
32+
case -4 : msg = {"CV_CONV_FAILURE" , "Convergence test failures occurred too many times during one internal time step or minimum step size was reached"}; break; // NOLINT
33+
case -8 : msg = {"CV_RHSFUNC_FAIL" , "The right-hand side function failed in an unrecoverable manner"}; break; // NOLINT
34+
case -9 : msg = {"CV_FIRST_RHSFUNC_ERR", "The right-hand side function failed at the first call"}; break; // NOLINT
35+
case -10 : msg = {"CV_REPTD_RHSFUNC_ERR", "The right-hand side function had repetead recoverable errors"}; break; // NOLINT
36+
case -11 : msg = {"CV_UNREC_RHSFUNC_ERR", "The right-hand side function had a recoverable error, but no recovery is possible"}; break; // NOLINT
37+
case -27 : msg = {"CV_TOO_CLOSE" , "The output and initial times are too close to each other"}; break; // NOLINT
38+
default:
39+
switch (flag) {
40+
case -5 : msg = {"CV_LINIT_FAIL" , "The linear solver's initialization function failed"}; break; // NOLINT
41+
case -6 : msg = {"CV_LSETUP_FAIL" , "The linear solver's setup function failed in an unrecoverable manner"}; break; // NOLINT
42+
case -7 : msg = {"CV_LSOLVE_FAIL" , "The linear solver's solve function failed in an unrecoverable manner"}; break; // NOLINT
43+
case -20 : msg = {"CV_MEM_FAIL" , "A memory allocation failed"}; break; // NOLINT
44+
case -21 : msg = {"CV_MEM_NULL" , "The cvode_mem argument was NULL"}; break; // NOLINT
45+
case -22 : msg = {"CV_ILL_INPUT" , "One of the function inputs is illegal"}; break; // NOLINT
46+
case -23 : msg = {"CV_NO_MALLOC" , "The CVODE memory block was not allocated by a call to CVodeMalloc"}; break; // NOLINT
47+
case -24 : msg = {"CV_BAD_K" , "The derivative order k is larger than the order used"}; break; // NOLINT
48+
case -25 : msg = {"CV_BAD_T" , "The time t s outside the last step taken"}; break; // NOLINT
49+
case -26 : msg = {"CV_BAD_DKY" , "The output derivative vector is NULL"}; break; // NOLINT
50+
case -40 : msg = {"CV_BAD_IS" , "The sensitivity index is larger than the number of sensitivities computed"}; break; // NOLINT
51+
case -41 : msg = {"CV_NO_SENS" , "Forward sensitivity integration was not activated"}; break; // NOLINT
52+
case -42 : msg = {"CV_SRHSFUNC_FAIL" , "The sensitivity right-hand side function failed in an unrecoverable manner"}; break; // NOLINT
53+
case -43 : msg = {"CV_FIRST_SRHSFUNC_ER", "The sensitivity right-hand side function failed at the first call"}; break; // NOLINT
54+
case -44 : msg = {"CV_REPTD_SRHSFUNC_ER", "The sensitivity ight-hand side function had repetead recoverable errors"}; break; // NOLINT
55+
case -45 : msg = {"CV_UNREC_SRHSFUNC_ER", "The sensitivity right-hand side function had a recoverable error, but no recovery is possible"}; break; // NOLINT
56+
case -101: msg = {"CV_ADJMEM_NULL" , "The cvadj_mem argument was NULL"}; break; // NOLINT
57+
case -103: msg = {"CV_BAD_TB0" , "The final time for the adjoint problem is outside the interval over which the forward problem was solved"}; break; // NOLINT
58+
case -104: msg = {"CV_BCKMEM_NULL" , "The cvodes memory for the backward problem was not created"}; break; // NOLINT
59+
case -105: msg = {"CV_REIFWD_FAIL" , "Reinitialization of the forward problem failed at the first checkpoint"}; break; // NOLINT
60+
case -106: msg = {"CV_FWD_FAIL" , "An error occured during the integration of the forward problem"}; break; // NOLINT
61+
case -107: msg = {"CV_BAD_ITASK" , "Wrong task for backward integration"}; break; // NOLINT
62+
case -108: msg = {"CV_BAD_TBOUT" , "The desired output time is outside the interval over which the forward problem was solved"}; break; // NOLINT
63+
case -109: msg = {"CV_GETY_BADT" , "Wrong time in interpolation function"}; break; // NOLINT
64+
}
65+
}
66+
return msg;
67+
}
68+
69+
/**
70+
* Throws a std::runtime_error exception when a Sundial function fails
71+
* (i.e. returns a negative flag)
72+
*
73+
* @param flag Error flag
74+
* @param func_name Name of the function that returned the flag
75+
* @throw <code>std::runtime_error</code> if the flag is negative
76+
*/
77+
inline void cvodes_check(int flag, const char* func_name) {
78+
if (flag < 0) {
79+
std::ostringstream ss;
80+
ss << func_name << " failed with error flag " << flag << ": \n"
81+
<< cvodes_flag_msg(flag).at(1) << ".";
82+
if (flag == -1 || flag == -4) {
83+
throw std::domain_error(ss.str());
84+
} else {
85+
throw std::runtime_error(ss.str());
86+
}
3287
}
3388
}
34-
}
3589

36-
/**
37-
* Throws an exception message when the functions in KINSOL
38-
* fails. When the exception is caused
39-
* by a tuning parameter the user controls, gives a specific
40-
* error. "KINGetReturnFlagName()" from sundials has a mem leak bug so
41-
* until it's fixed we cannot use it to extract flag error string.
42-
*
43-
* @param flag Error flag
44-
* @param func_name calling function name
45-
* @throw <code>std::runtime_error</code> if the flag is negative.
46-
*/
47-
inline void kinsol_check(int flag, const char* func_name) {
48-
std::ostringstream ss;
49-
if (flag < 0) {
50-
ss << "algebra_solver failed with error flag " << flag << ".";
51-
throw std::runtime_error(ss.str());
90+
/**
91+
* Throws an exception message when the functions in KINSOL
92+
* fails. When the exception is caused
93+
* by a tuning parameter the user controls, gives a specific
94+
* error. "KINGetReturnFlagName()" from sundials has a mem leak bug so
95+
* until it's fixed we cannot use it to extract flag error string.
96+
*
97+
* @param flag Error flag
98+
* @param func_name calling function name
99+
* @throw <code>std::runtime_error</code> if the flag is negative.
100+
*/
101+
inline void kinsol_check(int flag, const char* func_name) {
102+
std::ostringstream ss;
103+
if (flag < 0) {
104+
ss << "algebra_solver failed with error flag " << flag << ".";
105+
throw std::runtime_error(ss.str());
106+
}
52107
}
53-
}
54108

55-
/**
56-
* Throws an exception message when the KINSol() call fails.
57-
* When the exception is caused
58-
* by a tuning parameter the user controls, gives a specific
59-
* error.
60-
*
61-
* @param flag Error flag
62-
* @param func_name calling function name
63-
* @param max_num_steps max number of nonlinear iters
64-
* @throw <code>std::runtime_error</code> if the flag is negative.
65-
* @throw <code>std::domain_error</code> if the flag indicates max
66-
* number of steps is exceeded..
67-
*/
68-
inline void kinsol_check(int flag, const char* func_name,
69-
long int max_num_steps) { // NOLINT(runtime/int)
70-
std::ostringstream ss;
71-
if (flag == -6) {
72-
domain_error("algebra_solver", "maximum number of iterations",
73-
max_num_steps, "(", ") was exceeded in the solve.");
74-
} else if (flag == -11) {
75-
ss << "The linear solver’s setup function failed in an unrecoverable "
76-
"manner.";
77-
throw std::runtime_error(ss.str());
78-
} else if (flag < 0) {
79-
ss << "algebra_solver failed with error flag " << flag << ".";
80-
throw std::runtime_error(ss.str());
109+
/**
110+
* Throws an exception message when the KINSol() call fails.
111+
* When the exception is caused
112+
* by a tuning parameter the user controls, gives a specific
113+
* error.
114+
*
115+
* @param flag Error flag
116+
* @param func_name calling function name
117+
* @param max_num_steps max number of nonlinear iters
118+
* @throw <code>std::runtime_error</code> if the flag is negative.
119+
* @throw <code>std::domain_error</code> if the flag indicates max
120+
* number of steps is exceeded..
121+
*/
122+
inline void kinsol_check(int flag, const char* func_name,
123+
long int max_num_steps) { // NOLINT(runtime/int)
124+
std::ostringstream ss;
125+
if (flag == -6) {
126+
domain_error("algebra_solver", "maximum number of iterations",
127+
max_num_steps, "(", ") was exceeded in the solve.");
128+
} else if (flag == -11) {
129+
ss << "The linear solver’s setup function failed "
130+
<< "in an unrecoverable manner.";
131+
throw std::runtime_error(ss.str());
132+
} else if (flag < 0) {
133+
ss << "algebra_solver failed with error flag " << flag << ".";
134+
throw std::runtime_error(ss.str());
135+
}
81136
}
82-
}
83137

84138
} // namespace math
85139
} // namespace stan

test/unit/math/rev/functor/algebra_solver_newton_test.cpp

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -99,8 +99,7 @@ TEST_F(degenerate_eq_test, newton_guess_saddle_point_dbl) {
9999
// a saddle point.
100100
using stan::math::algebra_solver_newton;
101101
std::stringstream err_msg;
102-
err_msg
103-
<< "The linear solver’s setup function failed in an unrecoverable manner";
102+
err_msg << "The linear solver’s setup function failed in an unrecoverable manner"; // NOLINT
104103
std::string msg = err_msg.str();
105104

106105
EXPECT_THROW_MSG(algebra_solver_newton(degenerate_eq_functor(), x_guess_3,

test/unit/math/rev/functor/cvodes_utils_test.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ TEST(StanMath, cvodes_error_handler) {
2020
int t0 = 0;
2121
std::vector<double> ts = {0.45, 1.1};
2222

23-
std::string msg = "CV_CONV_FAILURE";
23+
std::string msg = "Convergence test failures occurred too many times";
2424

2525
EXPECT_THROW_MSG(stan::math::ode_bdf(Inverse(), y0, t0, ts, nullptr),
2626
std::domain_error, msg);

test/unit/math/rev/functor/util_algebra_solver.hpp

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -426,8 +426,7 @@ void inline unsolvable_flag_test(Eigen::Matrix<T, Eigen::Dynamic, 1>& y,
426426
std::vector<double> dat;
427427
std::vector<int> dat_int;
428428
std::stringstream err_msg;
429-
err_msg << "The linear solver’s setup function failed in an unrecoverable "
430-
"manner.";
429+
err_msg << "The linear solver’s setup function failed in an unrecoverable manner."; // NOLINT
431430
std::string msg = err_msg.str();
432431
EXPECT_THROW_MSG(general_algebra_solver(is_newton, unsolvable_eq_functor(), x,
433432
y, dat, dat_int),

0 commit comments

Comments
 (0)