2222
2323#include " memilio/utils/compiler_diagnostics.h"
2424#include " memilio/math/integrator.h"
25+ #include " memilio/utils/logging.h"
2526
2627GCC_CLANG_DIAGNOSTIC (push)
2728GCC_CLANG_DIAGNOSTIC(ignored " -Wshadow" )
@@ -32,7 +33,7 @@ MSVC_WARNING_DISABLE_PUSH(4127)
3233#include " boost/numeric/odeint/stepper/runge_kutta4.hpp"
3334#include " boost/numeric/odeint/stepper/runge_kutta_fehlberg78.hpp"
3435#include " boost/numeric/odeint/stepper/runge_kutta_cash_karp54.hpp"
35- #include " boost/numeric/odeint/stepper/runge_kutta_dopri5.hpp"
36+ // #include "boost/numeric/odeint/stepper/runge_kutta_dopri5.hpp" // TODO: reenable once boost bug is fixed
3637MSVC_WARNING_POP ()
3738GCC_CLANG_DIAGNOSTIC(pop)
3839
@@ -50,6 +51,12 @@ template <template <class State = Eigen::VectorXd, class Value = double, class D
5051 class ControlledStepper >
5152class ControlledStepperWrapper : public mio ::IntegratorCore
5253{
54+ using Stepper = boost::numeric::odeint::controlled_runge_kutta<ControlledStepper<>>;
55+ static constexpr bool is_fsal_stepper = std::is_same_v<typename Stepper::stepper_type::stepper_category,
56+ boost::numeric::odeint::explicit_error_stepper_fsal_tag>;
57+ static_assert (!is_fsal_stepper,
58+ " FSAL steppers cannot be used until https://github.com/boostorg/odeint/issues/72 is resolved." );
59+
5360public:
5461 /* *
5562 * @brief Set up the integrator
@@ -70,32 +77,67 @@ class ControlledStepperWrapper : public mio::IntegratorCore
7077 }
7178
7279 /* *
73- * @brief Make a single integration step of a system of ODEs and adapt step width
74- * @param[in] yt value of y at t, y(t)
75- * @param[in,out] t current time step h=dt
76- * @param[in,out] dt current time step h=dt
77- * @param[out] ytp1 approximated value y(t+1)
80+ * @brief Make a single integration step on a system of ODEs and adapt the step size dt.
81+
82+ * @param[in] yt Value of y at t_{k}, y(t_{k}).
83+ * @param[in,out] t Current time step t_{k} for some k. Will be set to t_{k+1} in [t_{k} + dt_min, t_{k} + dt].
84+ * @param[in,out] dt Current time step size h=dt. Overwritten by an estimated optimal step size for the next step.
85+ * @param[out] ytp1 The approximated value of y(t_{k+1}).
7886 */
7987 bool step (const mio::DerivFunction& f, Eigen::Ref<Eigen::VectorXd const > yt, double & t, double & dt,
8088 Eigen::Ref<Eigen::VectorXd> ytp1) const override
8189 {
82- // copy y(t) to dydt, to retrieve the VectorXd from the Ref
83- dydt = yt;
84- const double t_old = t; // t is updated by try_step on a successfull step
85- do {
86- // we use the scheme try_step(sys, inout, t, dt) with sys=f, inout=y(t) for
87- // in-place computation. This is similiar to do_step, but it can update t and dt
88- m_stepper.try_step (
89- // reorder arguments of the DerivFunction f for the stepper
90- [&](const Eigen::VectorXd& x, Eigen::VectorXd& dxds, double s) {
91- dxds.resizeLike (x); // try_step calls sys with a vector of size 0 for some reason
92- f (x, s, dxds);
93- },
94- dydt, t, dt);
95- // stop on a successfull step or a failed step size adaption (w.r.t. the minimal step size)
96- } while (t == t_old && dt > m_dt_min);
97- ytp1 = dydt; // output new y(t)
98- return dt > m_dt_min;
90+ using boost::numeric::odeint::fail;
91+ assert (0 <= m_dt_min);
92+ assert (m_dt_min <= m_dt_max);
93+
94+ if (dt < m_dt_min || dt > m_dt_max) {
95+ mio::log_warning (" IntegratorCore: Restricting given step size dt = {} to [{}, {}]." , dt, m_dt_min,
96+ m_dt_max);
97+ }
98+ // set initial values for exit conditions
99+ auto step_result = fail;
100+ bool is_dt_valid = true ;
101+ // copy vectors from the references, since the stepper cannot (trivially) handle Eigen::Ref
102+ m_ytp1 = ytp1;
103+ m_yt = yt;
104+ // make a integration step, adapting dt to a possibly larger value on success,
105+ // or a strictly smaller value on fail.
106+ // stop only on a successful step or a failed step size adaption (w.r.t. the minimal step size m_dt_min)
107+ while (step_result == fail && is_dt_valid) {
108+ if (dt < m_dt_min) {
109+ is_dt_valid = false ;
110+ dt = m_dt_min;
111+ }
112+ // we use the scheme try_step(sys, in, t, out, dt) with sys=f, in=y(t_{k}), out=y(t_{k+1}).
113+ // this is similiar to do_step, but it can adapt the step size dt. If successful, it also updates t.
114+
115+ if constexpr (!is_fsal_stepper) { // prevent compile time errors with fsal steppers
116+ step_result = m_stepper.try_step (
117+ // reorder arguments of the DerivFunction f for the stepper
118+ [&](const Eigen::VectorXd& x, Eigen::VectorXd& dxds, double s) {
119+ dxds.resizeLike (x); // boost resizers cannot resize Eigen::Vector, hence we need to do that here
120+ f (x, s, dxds);
121+ },
122+ m_yt, t, m_ytp1, dt);
123+ }
124+ }
125+ // output the new value by copying it back to the reference
126+ ytp1 = m_ytp1;
127+ // bound dt from below
128+ // the last adaptive step (successful or not) may have calculated a new step size smaller than m_dt_min
129+
130+ dt = std::max (dt, m_dt_min);
131+ // check whether the last step failed (which means that m_dt_min was still too large to suffice tolerances)
132+ if (step_result == fail) {
133+ // adaptive stepping failed, but we still return the result of the last attempt
134+ t += m_dt_min;
135+ return false ;
136+ }
137+ else {
138+ // successfully made an integration step
139+ return true ;
140+ }
99141 }
100142
101143 // / @param tol the required absolute tolerance for comparison of the iterative approximation
@@ -126,21 +168,18 @@ class ControlledStepperWrapper : public mio::IntegratorCore
126168 }
127169
128170private:
129- boost::numeric::odeint::controlled_runge_kutta<ControlledStepper<>> create_stepper ()
171+ // / @brief (Re)initialize the internal stepper.
172+ Stepper create_stepper ()
130173 {
131174 // for more options see: boost/boost/numeric/odeint/stepper/controlled_runge_kutta.hpp
132- return boost::numeric::odeint::controlled_runge_kutta<ControlledStepper<>>(
133- boost::numeric::odeint::default_error_checker<typename ControlledStepper<>::value_type,
134- typename ControlledStepper<>::algebra_type,
135- typename ControlledStepper<>::operations_type>(m_abs_tol,
136- m_rel_tol),
137- boost::numeric::odeint::default_step_adjuster<typename ControlledStepper<>::value_type,
138- typename ControlledStepper<>::time_type>(m_dt_max));
175+ return Stepper (typename Stepper::error_checker_type (m_abs_tol, m_rel_tol),
176+ typename Stepper::step_adjuster_type (m_dt_max));
139177 }
140178
141- double m_abs_tol, m_rel_tol, m_dt_min, m_dt_max; // integrator parameters
142- mutable Eigen::VectorXd dydt;
143- mutable boost::numeric::odeint::controlled_runge_kutta<ControlledStepper<>> m_stepper;
179+ double m_abs_tol, m_rel_tol; // /< Absolute and relative tolerances for integration.
180+ double m_dt_min, m_dt_max; // /< Lower and upper bound to the step size dt.
181+ mutable Eigen::VectorXd m_ytp1, m_yt; // /< Temporary storage to avoid allocations in step function.
182+ mutable Stepper m_stepper; // /< A stepper instance used for integration.
144183};
145184
146185} // namespace mio
0 commit comments