Skip to content

Commit dd116d7

Browse files
reneSchmMaxBetzDLR
andauthored
1365 Potential use of wrong dt_max in ControlledStepperWrapper (#1366)
- Changed how dt_max of the wrapped stepper is set, so it always uses the same value as IntegratorCore::get_dt_max(). - Change the step method so that it computes its result in place, avoiding two copies. Co-authored-by: MaxBetz <104758467+MaxBetzDLR@users.noreply.github.com>
1 parent 75ce311 commit dd116d7

File tree

1 file changed

+54
-29
lines changed

1 file changed

+54
-29
lines changed

cpp/memilio/math/stepper_wrapper.h

Lines changed: 54 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@
1717
* See the License for the specific language governing permissions and
1818
* limitations under the License.
1919
*/
20-
#ifndef STEPPER_WRAPPER_H_
21-
#define STEPPER_WRAPPER_H_
20+
#ifndef MIO_MATH_STEPPER_WRAPPER_H
21+
#define MIO_MATH_STEPPER_WRAPPER_H
2222

2323
#include "memilio/math/integrator.h"
2424
#include "memilio/utils/logging.h"
@@ -28,48 +28,77 @@
2828
#include "boost/numeric/odeint/stepper/controlled_runge_kutta.hpp"
2929
#include "boost/numeric/odeint/stepper/runge_kutta_fehlberg78.hpp"
3030
#include "boost/numeric/odeint/stepper/runge_kutta_cash_karp54.hpp"
31+
#include <boost/core/ref.hpp>
3132
// #include "boost/numeric/odeint/stepper/runge_kutta_dopri5.hpp" // TODO: reenable once boost bug is fixed
3233

3334
namespace mio
3435
{
3536

37+
namespace details
38+
{
39+
40+
/// @brief Extends the default_step_adjuster with a setter for dt_max.
41+
template <class Value, class Time>
42+
struct step_adjuster : public boost::numeric::odeint::default_step_adjuster<Value, Time> {
43+
using boost::numeric::odeint::default_step_adjuster<Value, Time>::default_step_adjuster;
44+
void set_dt_max(const Time& dt_max)
45+
{
46+
this->m_max_dt = dt_max;
47+
}
48+
};
49+
50+
} // namespace details
51+
3652
/**
3753
* @brief This is an adaptive IntegratorCore. It creates and manages an instance of a
3854
* boost::numeric::odeint::controlled_runge_kutta integrator, wrapped as mio::IntegratorCore.
3955
*/
40-
template <typename FP, template <class State, class Value, class Deriv, class Time, class Algebra, class Operations,
41-
class Resizer> class ControlledStepper>
56+
template <typename FP,
57+
template <class State, class Value, class Deriv, class Time, class Algebra, class Operations, class Resizer>
58+
class ControlledStepper>
4259
class ControlledStepperWrapper : public mio::OdeIntegratorCore<FP>
4360
{
61+
using Algebra = boost::numeric::odeint::vector_space_algebra;
62+
using Operations = typename boost::numeric::odeint::operations_dispatcher<Eigen::VectorX<FP>>::operations_type;
63+
using Resizer = boost::numeric::odeint::initially_resizer;
64+
using ErrorChecker = boost::numeric::odeint::default_error_checker<FP, Algebra, Operations>;
65+
using StepAdjuster = details::step_adjuster<FP, FP>;
66+
// Note: use a reference_wrapper so we can both update dt_max, and replace the stepper to change tolerances
4467
using Stepper = boost::numeric::odeint::controlled_runge_kutta<
45-
ControlledStepper<Eigen::VectorX<FP>, FP, Eigen::VectorX<FP>, FP, boost::numeric::odeint::vector_space_algebra,
46-
typename boost::numeric::odeint::operations_dispatcher<Eigen::VectorX<FP>>::operations_type,
47-
boost::numeric::odeint::initially_resizer>>;
68+
ControlledStepper<Eigen::VectorX<FP>, FP, Eigen::VectorX<FP>, FP, Algebra, Operations, Resizer>, ErrorChecker,
69+
boost::reference_wrapper<StepAdjuster>>;
4870
static constexpr bool is_fsal_stepper = std::is_same_v<typename Stepper::stepper_type::stepper_category,
4971
boost::numeric::odeint::explicit_error_stepper_fsal_tag>;
5072
static_assert(!is_fsal_stepper,
5173
"FSAL steppers cannot be used until https://github.com/boostorg/odeint/issues/72 is resolved.");
5274

5375
public:
5476
/**
55-
* @brief Set up the integrator
56-
* @param abs_tol absolute tolerance
57-
* @param rel_tol relative tolerance
58-
* @param dt_min lower bound for time step dt
59-
* @param dt_max upper bound for time step dt
77+
* @brief Set up the integrator.
78+
* @param[in] abs_tol Absolute tolerance for convergence.
79+
* @param[in] rel_tol Relative tolerance for convergence.
80+
* @param[in] dt_min Lower bound for time step dt.
81+
* @param[in] dt_max Upper bound for time step dt.
6082
*/
6183
ControlledStepperWrapper(FP abs_tol = 1e-10, FP rel_tol = 1e-5, FP dt_min = std::numeric_limits<FP>::min(),
6284
FP dt_max = std::numeric_limits<FP>::max())
6385
: OdeIntegratorCore<FP>(dt_min, dt_max)
6486
, m_abs_tol(abs_tol)
6587
, m_rel_tol(rel_tol)
88+
, m_step_adjuster(StepAdjuster{this->get_dt_max()})
6689
, m_stepper(create_stepper())
6790
{
6891
}
6992

93+
// if needed, make sure to create a new m_stepper
94+
ControlledStepperWrapper(ControlledStepperWrapper&& other) = delete;
95+
ControlledStepperWrapper(const ControlledStepperWrapper& other) = delete;
96+
ControlledStepperWrapper& operator=(ControlledStepperWrapper&& other) = delete;
97+
ControlledStepperWrapper& operator=(const ControlledStepperWrapper& other) = delete;
98+
7099
std::unique_ptr<OdeIntegratorCore<FP>> clone() const override
71100
{
72-
return std::make_unique<ControlledStepperWrapper>(*this);
101+
return std::make_unique<ControlledStepperWrapper>(m_abs_tol, m_rel_tol, this->get_dt_min(), this->get_dt_max());
73102
}
74103

75104
/**
@@ -80,24 +109,23 @@ class ControlledStepperWrapper : public mio::OdeIntegratorCore<FP>
80109
* @param[in,out] dt Current time step size h=dt. Overwritten by an estimated optimal step size for the next step.
81110
* @param[out] ytp1 The approximated value of y(t').
82111
*/
83-
bool step(const DerivFunction<FP>& f, Eigen::Ref<Eigen::VectorX<FP> const> yt, FP& t, FP& dt,
112+
bool step(const mio::DerivFunction<FP>& f, Eigen::Ref<const Eigen::VectorX<FP>> yt, FP& t, FP& dt,
84113
Eigen::Ref<Eigen::VectorX<FP>> ytp1) const override
85114
{
86115
using boost::numeric::odeint::fail;
87116
using std::max;
88117
assert(0 <= this->get_dt_min());
89118
assert(this->get_dt_min() <= this->get_dt_max());
90-
119+
// synchronise dt_max of the base class with the stepper
120+
m_step_adjuster.set_dt_max(this->get_dt_max());
121+
// warn about (upcoming) restrictions to dt
91122
if (dt < this->get_dt_min() || dt > this->get_dt_max()) {
92123
mio::log_warning("IntegratorCore: Restricting given step size dt = {} to [{}, {}].", dt, this->get_dt_min(),
93124
this->get_dt_max());
94125
}
95126
// set initial values for exit conditions
96127
auto step_result = fail;
97128
bool is_dt_valid = true;
98-
// copy vectors from the references, since the stepper cannot (trivially) handle Eigen::Ref
99-
m_ytp1 = ytp1; // y(t')
100-
m_yt = yt; // y(t)
101129
// make a integration step, adapting dt to a possibly larger value on success,
102130
// or a strictly smaller value on fail.
103131
// stop only on a successful step or a failed step size adaption (w.r.t. the minimal step size dt_min)
@@ -115,11 +143,9 @@ class ControlledStepperWrapper : public mio::OdeIntegratorCore<FP>
115143
[&](const Eigen::VectorX<FP>& x, Eigen::VectorX<FP>& dxds, FP s) {
116144
f(x, s, dxds);
117145
},
118-
m_yt, t, m_ytp1, dt);
146+
yt, t, ytp1, dt);
119147
}
120148
}
121-
// output the new value by copying it back to the reference
122-
ytp1 = m_ytp1;
123149
// bound dt from below
124150
// the last adaptive step (successful or not) may have calculated a new step size smaller than m_dt_min
125151

@@ -160,29 +186,28 @@ class ControlledStepperWrapper : public mio::OdeIntegratorCore<FP>
160186
void set_dt_max(FP dt_max)
161187
{
162188
this->get_dt_max() = dt_max;
163-
m_stepper = create_stepper();
164189
}
165190

166191
private:
167192
/// @brief (Re)initialize the internal stepper.
168193
Stepper create_stepper()
169194
{
170195
// for more options see: boost/boost/numeric/odeint/stepper/controlled_runge_kutta.hpp
171-
return Stepper(typename Stepper::error_checker_type(m_abs_tol, m_rel_tol),
172-
typename Stepper::step_adjuster_type(this->get_dt_max()));
196+
return Stepper(ErrorChecker(m_abs_tol, m_rel_tol), boost::ref(m_step_adjuster));
173197
}
174198

175199
FP m_abs_tol, m_rel_tol; ///< Absolute and relative tolerances for integration.
176-
mutable Eigen::VectorX<FP> m_ytp1, m_yt; ///< Temporary storage to avoid allocations in step function.
200+
mutable StepAdjuster m_step_adjuster; ///< Defines step sizing. Holds a copy of dt_max that has to be updated.
177201
mutable Stepper m_stepper; ///< A stepper instance used for integration.
178202
};
179203

180204
/**
181205
* @brief This is a non-adaptive IntegratorCore. It creates and manages an instance of an explicit stepper from
182206
* boost::numeric::odeint, wrapped as mio::IntegratorCore.
183207
*/
184-
template <typename FP, template <class State, class Value, class Deriv, class Time, class Algebra, class Operations,
185-
class Resizer> class ExplicitStepper>
208+
template <typename FP,
209+
template <class State, class Value, class Deriv, class Time, class Algebra, class Operations, class Resizer>
210+
class ExplicitStepper>
186211
class ExplicitStepperWrapper : public mio::OdeIntegratorCore<FP>
187212
{
188213
public:
@@ -212,7 +237,7 @@ class ExplicitStepperWrapper : public mio::OdeIntegratorCore<FP>
212237
* @param[in] dt Current time step size h=dt.
213238
* @param[out] ytp1 The approximated value of y(t+dt).
214239
*/
215-
bool step(const mio::DerivFunction<FP>& f, Eigen::Ref<Eigen::VectorX<FP> const> yt, FP& t, FP& dt,
240+
bool step(const mio::DerivFunction<FP>& f, Eigen::Ref<const Eigen::VectorX<FP>> yt, FP& t, FP& dt,
216241
Eigen::Ref<Eigen::VectorX<FP>> ytp1) const override
217242
{
218243
// copy the values from y(t) to ytp1, since we use the scheme do_step(sys, inout, t, dt) with
@@ -235,4 +260,4 @@ class ExplicitStepperWrapper : public mio::OdeIntegratorCore<FP>
235260

236261
} // namespace mio
237262

238-
#endif
263+
#endif // MIO_MATH_STEPPER_WRAPPER_H

0 commit comments

Comments
 (0)