Automatic Differentiation
 
Loading...
Searching...
No Matches
arkode_integrator.hpp
Go to the documentation of this file.
1#ifndef STAN_MATH_PRIM_FUNCTOR_ARKODE_INTEGRATOR_HPP
2#define STAN_MATH_PRIM_FUNCTOR_ARKODE_INTEGRATOR_HPP
3
11#include <sundials/sundials_context.h>
12#include <arkode/arkode_erkstep.h>
13#include <nvector/nvector_serial.h>
14#include <algorithm>
15#include <cmath>
16#include <memory>
17#include <ostream>
18#include <stdexcept>
19#include <tuple>
20#include <vector>
21
22namespace stan {
23namespace math {
24
42template <ARKODE_ERKTableID Table, bool DenseOutput, typename F, typename T_y0,
43 typename T_t0, typename T_ts, typename... Args>
45 using T_Return = return_type_t<T_y0, T_t0, T_ts, Args...>;
47
48 const char* function_name_;
49 sundials::Context sundials_context_;
50 const F& f_;
51 const Eigen::Matrix<T_y0_t0, Eigen::Dynamic, 1> y0_;
52 const T_t0 t0_;
53 const std::vector<T_ts>& ts_;
54 std::tuple<const Args&...> args_tuple_;
55 std::ostream* msgs_;
58 long int max_num_steps_; // NOLINT(runtime/int)
59
61 std::vector<double> coupled_state_;
62 std::vector<double> z_;
63 std::vector<double> dz_dt_;
64 N_Vector nv_state_;
65
66 // odeint controls the max of the weighted errors, not their RMS
67 static realtype max_norm(N_Vector x, N_Vector w) {
68 Eigen::Map<const Eigen::ArrayXd> x_map(NV_DATA_S(x), NV_LENGTH_S(x));
69 Eigen::Map<const Eigen::ArrayXd> w_map(NV_DATA_S(w), NV_LENGTH_S(w));
70 return (x_map * w_map).abs().maxCoeff();
71 }
72
73 static int erk_rhs(realtype t, N_Vector y, N_Vector ydot, void* user_data) {
74 arkode_integrator* integrator = static_cast<arkode_integrator*>(user_data);
75 integrator->rhs(t, NV_DATA_S(y), NV_DATA_S(ydot));
76 return 0;
77 }
78
79 inline void rhs(double t, const double y[], double dy_dt[]) {
80 z_.assign(y, y + coupled_state_.size());
82 std::copy(dz_dt_.begin(), dz_dt_.end(), dy_dt);
83 }
84
85 public:
104 arkode_integrator(const char* function_name, const F& f, const T_y0& y0,
105 T_t0 t0, const std::vector<T_ts>& ts,
106 double relative_tolerance, double absolute_tolerance,
107 long int max_num_steps, // NOLINT(runtime/int)
108 std::ostream* msgs, const Args&... args)
109 : function_name_(function_name),
111 f_(f),
112 y0_(y0.template cast<T_y0_t0>()),
113 t0_(t0),
114 ts_(ts),
115 args_tuple_(args...),
116 msgs_(msgs),
117 relative_tolerance_(relative_tolerance),
118 absolute_tolerance_(absolute_tolerance),
119 max_num_steps_(max_num_steps),
120 coupled_system_(f, y0_, msgs, args...),
121 coupled_state_(coupled_system_.initial_state()) {
122 check_finite(function_name, "initial state", y0_);
123 check_finite(function_name, "initial time", t0_);
124 check_finite(function_name, "times", ts_);
125
127 [&](const auto&... args_ref) {
128 (check_finite(function_name, "ode parameters and data", args_ref),
129 ...);
130 },
132
133 check_nonzero_size(function_name, "initial state", y0_);
134 check_nonzero_size(function_name, "times", ts_);
135 check_sorted(function_name, "times", ts_);
136 check_less(function_name, "initial time", t0_, ts_[0]);
137 check_positive_finite(function_name, "relative_tolerance",
139 check_positive_finite(function_name, "absolute_tolerance",
141 check_positive(function_name, "max_num_steps", max_num_steps_);
142
143 nv_state_ = N_VMake_Serial(coupled_state_.size(), coupled_state_.data(),
145 nv_state_->ops->nvwrmsnorm = &arkode_integrator::max_norm;
146 }
147
148 ~arkode_integrator() { N_VDestroy_Serial(nv_state_); }
149
158 std::vector<Eigen::Matrix<T_Return, Eigen::Dynamic, 1>> operator()() {
159 std::vector<Eigen::Matrix<T_Return, Eigen::Dynamic, 1>> y;
160 y.reserve(ts_.size());
161
162 auto free_mem = [](void* mem) { ERKStepFree(&mem); };
163 std::unique_ptr<void, decltype(free_mem)> mem_ptr(
166 free_mem);
167 void* arkode_mem = mem_ptr.get();
168 if (arkode_mem == nullptr) {
169 throw std::runtime_error("ERKStepCreate failed to allocate memory");
170 }
171
173 ERKStepSetUserData(arkode_mem, reinterpret_cast<void*>(this)));
174 CHECK_ARKODE_CALL(ERKStepSetTableNum(arkode_mem, Table));
175 CHECK_ARKODE_CALL(ERKStepSStolerances(arkode_mem, relative_tolerance_,
177 CHECK_ARKODE_CALL(ERKStepSetMaxNumSteps(arkode_mem, max_num_steps_));
178
179 // odeint: h *= 0.9 * err^(-1/5) limited to [0.2, 4.5], h kept if err >= 0.5
180 realtype adapt_params[3] = {1.0, 0.0, 0.0};
181 CHECK_ARKODE_CALL(ERKStepSetInitStep(arkode_mem, 0.1));
182 CHECK_ARKODE_CALL(ERKStepSetAdaptivityMethod(arkode_mem, ARK_ADAPT_I, 0, 1,
183 adapt_params));
184 CHECK_ARKODE_CALL(ERKStepSetSafetyFactor(arkode_mem, 0.9));
185 CHECK_ARKODE_CALL(ERKStepSetErrorBias(arkode_mem, 1.0));
186 CHECK_ARKODE_CALL(ERKStepSetMaxGrowth(arkode_mem, 4.5));
187 CHECK_ARKODE_CALL(ERKStepSetMaxFirstGrowth(arkode_mem, 4.5));
188 CHECK_ARKODE_CALL(ERKStepSetMinReduction(arkode_mem, 0.2));
190 ERKStepSetFixedStepBounds(arkode_mem, 0.9, 0.9 * std::pow(0.5, -0.2)));
191 if (!DenseOutput) {
192 // never evaluated, but avoids the Hermite interpolant's extra RHS calls
194 ERKStepSetInterpolantType(arkode_mem, ARK_INTERP_LAGRANGE));
195 }
196
197 double t_init = value_of(t0_);
198 for (size_t n = 0; n < ts_.size(); ++n) {
199 double t_final = value_of(ts_[n]);
200
201 if (t_final != t_init) {
202 if (!DenseOutput) {
203 CHECK_ARKODE_CALL(ERKStepSetStopTime(arkode_mem, t_final));
204 }
205 int flag = ERKStepEvolve(arkode_mem, t_final, nv_state_, &t_init,
206 ARK_NORMAL);
207 if (flag == ARK_TOO_MUCH_WORK) {
209 "Failed to integrate to next output time (",
210 ") in less than max_num_steps steps");
211 }
212 arkode_check(flag, "ERKStepEvolve");
213 }
214
215 y.emplace_back(math::apply(
216 [&](const auto&... args_ref) {
218 msgs_, args_ref...);
219 },
220 args_tuple_));
221
222 t_init = t_final;
223 }
224
225 return y;
226 }
227}; // arkode_integrator
228
229} // namespace math
230} // namespace stan
231#endif
#define CHECK_ARKODE_CALL(call)
const Eigen::Matrix< T_y0_t0, Eigen::Dynamic, 1 > y0_
static int erk_rhs(realtype t, N_Vector y, N_Vector ydot, void *user_data)
coupled_ode_system< F, T_y0_t0, Args... > coupled_system_
return_type_t< T_y0, T_t0, T_ts, Args... > T_Return
std::vector< Eigen::Matrix< T_Return, Eigen::Dynamic, 1 > > operator()()
Solve the ODE initial value problem y' = f(t, y), y(t0) = y0 at a set of times, { t1,...
std::tuple< const Args &... > args_tuple_
return_type_t< T_y0, T_t0 > T_y0_t0
std::vector< double > coupled_state_
static realtype max_norm(N_Vector x, N_Vector w)
const std::vector< T_ts > & ts_
arkode_integrator(const char *function_name, const F &f, const T_y0 &y0, T_t0 t0, const std::vector< T_ts > &ts, double relative_tolerance, double absolute_tolerance, long int max_num_steps, std::ostream *msgs, const Args &... args)
Construct arkode_integrator object.
void rhs(double t, const double y[], double dy_dt[])
Integrator interface for ARKODE's ERKStep explicit adaptive Runge-Kutta solver, using one of ERKStep'...
auto cast(T &&a)
Typecast a kernel generator expression scalar.
Definition cast.hpp:80
typename return_type< Ts... >::type return_type_t
Convenience type for the return type of the specified template parameters.
T value_of(const fvar< T > &v)
Return the value of the specified variable.
Definition value_of.hpp:18
void arkode_check(int flag, const char *func_name)
void throw_domain_error(const char *function, const char *name, const T &y, const char *msg1, const char *msg2)
Throw a domain error with a consistently formatted message.
void check_finite(const char *function, const char *name, const T_y &y)
Return true if all values in y are finite.
Eigen::VectorXd ode_store_sensitivities(const F &f, const std::vector< double > &coupled_state, const Eigen::Matrix< T_y0_t0, Eigen::Dynamic, 1 > &y0, T_t0 t0, T_t t, std::ostream *msgs, const Args &... args)
When all arguments are arithmetic, there are no sensitivities to store, so the function just returns ...
void check_nonzero_size(const char *function, const char *name, const T_y &y)
Check if the specified matrix/vector is of non-zero size.
void check_positive(const char *function, const char *name, const T_y &y)
Check if y is positive.
void check_sorted(const char *function, const char *name, const EigVec &y)
Check if the specified vector is sorted into increasing order (repeated values are okay).
void check_less(const char *function, const char *name, const T_y &y, const T_high &high, Idxs... idxs)
Throw an exception if y is not strictly less than high.
constexpr decltype(auto) apply(F &&f, Tuple &&t, PreArgs &&... pre_args)
Definition apply.hpp:51
void check_positive_finite(const char *function, const char *name, const T_y &y)
Check if y is positive and finite.
The lgamma implementation in stan-math is based on either the reentrant safe lgamma_r implementation ...