5#ifndef LINE_UTIL_LSODA_H
6#define LINE_UTIL_LSODA_H
54using LsodaRhs = std::function<void(
double t,
const double* y,
double* dydt)>;
122 std::function<bool(
double,
const std::vector<double>&)>
step_stop;
127 std::vector<double>
t;
128 std::vector<std::vector<double>>
y;
138 if (
y.empty())
throw NumericError(
"LsodaSolution: no state was recorded");
142 if (
t.empty())
throw NumericError(
"LsodaSolution: no state was recorded");
147namespace lsoda_detail {
150inline void rhs_trampoline(
double t,
double* y,
double* dydt,
void* data) {
151 (*
static_cast<const LsodaRhs*
>(data))(t, y, dydt);
175 const std::vector<double>& t_eval,
179 const std::vector<double>& t_eval,
182 throw InputError(
"lsoda_integrate: t_eval is empty; it must carry at least the start time");
184 throw InputError(
"lsoda_integrate: the initial state is empty");
185 for (std::size_t i = 1; i < t_eval.size(); ++i)
186 if (t_eval[i] < t_eval[i - 1])
187 throw InputError(
"lsoda_integrate: t_eval must be non-decreasing");
194 const std::size_t neq = y0.size();
196 out.
t.push_back(t_eval[0]);
199 lsoda_impl::LSODA solver;
202 const bool rvec = !
opt.rtol_vec.empty(), avec = !
opt.atol_vec.empty();
203 if (rvec &&
opt.rtol_vec.size() != neq)
204 throw InputError(
"lsoda_integrate: rtol_vec has one entry per equation or none");
205 if (avec &&
opt.atol_vec.size() != neq)
206 throw InputError(
"lsoda_integrate: atol_vec has one entry per equation or none");
207 std::vector<double> rtol1(neq + 1,
opt.rtol), atol1(neq + 1,
opt.atol);
210 for (std::size_t k = 0; k < neq; ++k) {
211 if (rvec) rtol1[k + 1] =
opt.rtol_vec[k];
212 if (avec) atol1[k + 1] =
opt.atol_vec[k];
214 const int itol = rvec ? (avec ? 4 : 3) : (avec ? 2 : 1);
215 solver.set_tolerances(rtol1, atol1, itol);
216 solver.set_force_stiff(
opt.force_stiff);
218 std::vector<double> y = y0;
219 std::vector<double> yout;
220 double t = t_eval[0];
224 std::array<int, 7> iworks = {{0, 0, 0,
static_cast<int>(
opt.max_steps), 0,
225 opt.max_order_nonstiff,
opt.max_order_stiff}};
228 std::array<double, 4> rworks = {{0.0,
opt.h_init,
opt.h_max,
opt.h_min}};
233 for (std::size_t i = 1; i < t_eval.size(); ++i) {
234 const double tout = t_eval[i];
240 yout.assign(neq + 1, 0.0);
241 for (std::size_t k = 0; k < neq; ++k) yout[k + 1] = y[k];
242 solver.lsoda(lsoda_detail::rhs_trampoline, neq, yout, &t, tout, 1 , &istate,
243 iopt, jt, iworks, rworks,
const_cast<LsodaRhs*
>(fp));
244 for (std::size_t k = 0; k < neq; ++k) y[k] = yout[k + 1];
255 out.
steps = solver.get_nst();
256 out.
f_evals = solver.get_nfe();
258 out.
method = solver.get_mused() == 2 ?
"bdf" :
"adams";
290 : f_(f), y_(y0), t_(t0), t1_(t1), neq_(y0.size()) {
291 if (y0.empty())
throw InputError(
"LsodaStepper: the initial state is empty");
293 throw InputError(
"LsodaStepper: the final time must exceed the initial time");
294 const bool rvec = !
opt.rtol_vec.empty(), avec = !
opt.atol_vec.empty();
295 if (rvec &&
opt.rtol_vec.size() != neq_)
296 throw InputError(
"LsodaStepper: rtol_vec has one entry per equation or none");
297 if (avec &&
opt.atol_vec.size() != neq_)
298 throw InputError(
"LsodaStepper: atol_vec has one entry per equation or none");
299 std::vector<double> rtol1(neq_ + 1,
opt.rtol), atol1(neq_ + 1,
opt.atol);
302 for (std::size_t k = 0; k < neq_; ++k) {
303 if (rvec) rtol1[k + 1] =
opt.rtol_vec[k];
304 if (avec) atol1[k + 1] =
opt.atol_vec[k];
306 solver_.set_tolerances(rtol1, atol1, rvec ? (avec ? 4 : 3) : (avec ? 2 : 1));
307 solver_.set_force_stiff(
opt.force_stiff);
308 iworks_ = {{0, 0, 0,
static_cast<int>(
opt.max_steps), 0,
opt.max_order_nonstiff,
309 opt.max_order_stiff}};
310 rworks_ = {{0.0,
opt.h_init,
opt.h_max,
opt.h_min}};
315 if (done_ || istate_ < 0)
return false;
317 if (istate_ < 0)
return false;
318 if (t_ >= t1_) done_ =
true;
324 if (istate_ < 0 || !(t_ > t1_))
return;
328 double t()
const {
return t_; }
329 double t_end()
const {
return t1_; }
330 const std::vector<double>&
y()
const {
return y_; }
332 bool failed()
const {
return istate_ < 0; }
334 std::size_t
steps()
const {
return solver_.get_nst(); }
335 std::size_t
f_evals()
const {
return solver_.get_nfe(); }
338 void call(
int itask,
double tout) {
339 std::vector<double> yout(neq_ + 1, 0.0);
340 for (std::size_t k = 0; k < neq_; ++k) yout[k + 1] = y_[k];
341 solver_.lsoda(lsoda_detail::rhs_trampoline, neq_, yout, &t_, tout, itask, &istate_, 1 ,
342 2 , iworks_, rworks_, &f_);
343 for (std::size_t k = 0; k < neq_; ++k) y_[k] = yout[k + 1];
344 if (istate_ > 0) istate_ = 2;
348 lsoda_impl::LSODA solver_;
349 std::vector<double> y_;
352 std::size_t neq_ = 0;
355 std::array<int, 7> iworks_ = {{0, 0, 0, 0, 0, 12, 5}};
356 std::array<double, 4> rworks_ = {{0.0, 0.0, 0.0, 0.0}};
380 const std::vector<double>& t_eval,
383 out.
t.push_back(t_eval[0]);
386 std::vector<double> y = y0;
387 double t = t_eval[0];
388 std::size_t nst = 0, nfe = 0;
389 bool stopped =
false;
391 for (std::size_t i = 1; i < t_eval.size(); ++i) {
392 const double tout = t_eval[i];
393 if (stopped || tout == t) {
394 out.
t.push_back(tout);
399 while (stepper.
step()) {
400 if (
opt.step_stop(stepper.
t(), stepper.
y())) {
407 nst += stepper.
steps();
412 out.
t.push_back(stepper.
t());
413 out.
y.push_back(stepper.
y());
421 out.
t.push_back(tout);
437 const std::vector<double> span{t0, t1};
One internal step at a time: ODEPACK's itask = 2.
std::size_t f_evals() const
bool step()
Advance one accepted step.
std::size_t steps() const
void settle_at_end()
Interpolate the state back onto t1 after itask = 2 stepped past it.
LsodaStepper(const LsodaRhs &f, const std::vector< double > &y0, double t0, double t1, const LsodaOptions &opt=LsodaOptions())
const std::vector< double > & y() const
NumericError(const std::string &what)
The exception types the port throws.
std::function< void(double t, const double *y, double *dydt)> LsodaRhs
The right-hand side dy/dt = f(t, y).
std::vector< double > lsoda_final(const LsodaRhs &f, const std::vector< double > &y0, double t0, double t1, const LsodaOptions &opt=LsodaOptions())
Convenience form: integrate from t0 to t1 and report only the end state.
LsodaSolution lsoda_integrate(const LsodaRhs &f, const std::vector< double > &y0, const std::vector< double > &t_eval, const LsodaOptions &opt=LsodaOptions())
LsodaSolution lsoda_integrate_stepwise(const LsodaRhs &f, const std::vector< double > &y0, const std::vector< double > &t_eval, const LsodaOptions &opt)
Integrate dy/dt = f(t, y) from t_eval.front() through every later entry of t_eval,...
std::vector< double > rtol_vec
Per-component tolerances.
std::size_t max_steps
internal steps between output points
int max_order_stiff
BDF order cap (mxords).
double h_init
initial step; 0 lets LSODA choose
double atol
absolute tolerance, applied to every component
int max_order_nonstiff
Adams order cap (mxordn).
double h_min
smallest admissible step; 0 means no bound
double rtol
relative tolerance, applied to every component
double h_max
largest admissible step; 0 means no bound
std::function< bool(double, const std::vector< double > &)> step_stop
A test consulted after every ACCEPTED STEP, ending the integration when it returns true and holding t...
bool force_stiff
Start on BDF and never switch to Adams.
std::vector< double > atol_vec
Result of an integration, mirroring OdeSolution in ode.h.
std::size_t f_evals
right-hand side evaluations (nfe)
bool success
false when LSODA returned istate < 0
const std::vector< double > & final_state() const
int istate
the solver's final istate
std::vector< std::vector< double > > y
y[i] is the state at t[i]
std::size_t jacobians
Jacobian evaluations (nje).
std::vector< double > t
output times, t[0] = t_eval[0]
double final_time() const
std::size_t steps
internal steps taken (nst)
std::string method
The method in force at the end: "adams" (nonstiff) or "bdf" (stiff).