Skip to content

Commit c6a421b

Browse files
committed
add stiffness detect (HLAMB), step adjust (facold, beta stabilization, upper/lower limits)
1 parent 343a187 commit c6a421b

File tree

1 file changed

+80
-10
lines changed

1 file changed

+80
-10
lines changed

include/integrators/ode/dop853.hpp

Lines changed: 80 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,28 @@ class DOP853Integrator : public AdaptiveIntegrator<S, T> {
5656
using value_type = typename base_type::value_type;
5757
using system_function = typename base_type::system_function;
5858

59+
// Step size control parameters (modern C++ style, with defaults)
60+
time_type safety_factor_ = static_cast<time_type>(0.9); // Fortran SAFE
61+
time_type fac1_ = static_cast<time_type>(0.333); // Fortran FAC1 (min step size factor)
62+
time_type fac2_ = static_cast<time_type>(6.0); // Fortran FAC2 (max step size factor)
63+
time_type beta_ = static_cast<time_type>(0.0); // Fortran BETA (step size stabilization)
64+
time_type dt_max_ = static_cast<time_type>(1e100); // Fortran HMAX (max step size)
65+
time_type dt_min_ = static_cast<time_type>(1e-16); // Min step size (not in Fortran, but practical)
66+
int nmax_ = 100000; // Fortran NMAX (max steps)
67+
int nstiff_ = 1000; // Fortran NSTIFF (stiffness test interval)
68+
69+
// Stiffness detection state
70+
int iastiff_ = 0;
71+
int nonsti_ = 0;
72+
time_type hlamb_ = 0;
73+
time_type facold_ = static_cast<time_type>(1e-4);
74+
75+
// For statistics (optional)
76+
int nstep_ = 0;
77+
int naccpt_ = 0;
78+
int nrejct_ = 0;
79+
int nfcn_ = 0;
80+
5981
private:
6082
// Compute a good initial step size (HINIT from Fortran)
6183
time_type compute_initial_step(const state_type& y, time_type t, const system_function& sys, time_type t_end) const {
@@ -111,8 +133,18 @@ class DOP853Integrator : public AdaptiveIntegrator<S, T> {
111133
public:
112134
explicit DOP853Integrator(system_function sys,
113135
time_type rtol = static_cast<time_type>(1e-8),
114-
time_type atol = static_cast<time_type>(1e-10))
115-
: base_type(std::move(sys), rtol, atol) {}
136+
time_type atol = static_cast<time_type>(1e-10),
137+
time_type safety = static_cast<time_type>(0.9),
138+
time_type fac1 = static_cast<time_type>(0.333),
139+
time_type fac2 = static_cast<time_type>(6.0),
140+
time_type beta = static_cast<time_type>(0.0),
141+
time_type dt_max = static_cast<time_type>(1e100),
142+
time_type dt_min = static_cast<time_type>(1e-16),
143+
int nmax = 100000,
144+
int nstiff = 1000)
145+
: base_type(std::move(sys), rtol, atol),
146+
safety_factor_(safety), fac1_(fac1), fac2_(fac2), beta_(beta),
147+
dt_max_(dt_max), dt_min_(dt_min), nmax_(nmax), nstiff_(nstiff) {}
116148

117149
void step(state_type& state, time_type dt) override {
118150
adaptive_step(state, dt);
@@ -132,25 +164,63 @@ class DOP853Integrator : public AdaptiveIntegrator<S, T> {
132164
// Use the system function and current state to estimate initial step
133165
current_dt = compute_initial_step(state, t, this->sys_, t_end);
134166
// Clamp to allowed min/max
135-
current_dt = std::max(this->dt_min_, std::min(this->dt_max_, current_dt));
167+
current_dt = std::max(dt_min_, std::min(dt_max_, current_dt));
136168
}
137-
const int max_attempts = 10;
138-
for (int attempt = 0; attempt < max_attempts; ++attempt) {
169+
int attempt = 0;
170+
for (; attempt < nmax_; ++attempt) {
139171
state_type y_new = StateCreator<state_type>::create(state);
140172
state_type error = StateCreator<state_type>::create(state);
141173
dop853_step(state, y_new, error, current_dt);
174+
nfcn_ += 12; // 12 stages per step
142175
time_type err_norm = this->error_norm(error, y_new);
176+
177+
// Fortran: FAC11 = ERR**EXPO1, FAC = FAC11 / FACOLD**BETA
178+
time_type expo1 = 1.0 / 8.0 - beta_ * 0.2;
179+
time_type fac11 = std::pow(std::max(err_norm, 1e-16), expo1);
180+
time_type fac = fac11 / std::pow(facold_, beta_);
181+
fac = std::max(fac2_, std::min(fac1_, fac / safety_factor_));
182+
time_type next_dt = current_dt / fac;
183+
143184
if (err_norm <= 1.0) {
185+
facold_ = std::max(err_norm, static_cast<time_type>(1e-4));
186+
naccpt_++;
187+
nstep_++;
144188
state = y_new;
145189
this->advance_time(current_dt);
146-
time_type next_dt = this->suggest_step_size(current_dt, err_norm, 8);
147-
return std::max(this->dt_min_, std::min(this->dt_max_, next_dt));
190+
// stiffness detection (Fortran HLAMB)
191+
if (nstiff_ > 0 && (naccpt_ % nstiff_ == 0 || iastiff_ > 0)) {
192+
// Compute HLAMB = |h| * sqrt(stnum / stden)
193+
time_type stnum = 0, stden = 0;
194+
for (std::size_t i = 0; i < state.size(); ++i) {
195+
// Use error and state difference as proxy (not exact Fortran, but close)
196+
stnum += (error[i]) * (error[i]);
197+
stden += (y_new[i] - state[i]) * (y_new[i] - state[i]);
198+
}
199+
if (stden > 0) hlamb_ = std::abs(current_dt) * std::sqrt(stnum / stden);
200+
if (hlamb_ > 6.1) {
201+
nonsti_ = 0;
202+
iastiff_++;
203+
if (iastiff_ == 15) {
204+
throw std::runtime_error("DOP853: Problem seems to become stiff");
205+
}
206+
} else {
207+
nonsti_++;
208+
if (nonsti_ == 6) iastiff_ = 0;
209+
}
210+
}
211+
// Clamp next step size
212+
next_dt = std::max(dt_min_, std::min(dt_max_, next_dt));
213+
// Accept and return next step size
214+
return next_dt;
148215
} else {
149-
current_dt *= std::max(this->safety_factor_ * std::pow(err_norm, -1.0/8.0), static_cast<time_type>(0.1));
150-
current_dt = std::max(current_dt, this->dt_min_);
216+
// Step rejected
217+
nrejct_++;
218+
nstep_++;
219+
next_dt = current_dt / std::min(fac1_, fac11 / safety_factor_);
220+
current_dt = std::max(dt_min_, next_dt);
151221
}
152222
}
153-
throw std::runtime_error("DOP853: Maximum number of step size reductions exceeded");
223+
throw std::runtime_error("DOP853: Maximum number of step size reductions or steps exceeded");
154224
}
155225

156226
private:

0 commit comments

Comments
 (0)