Search code examples
pythonc++boostodeint

Specify interpolation times in boost::odeint


I have started using boost::odeint in my C++ code, and I think I'm missing a simple feature available in other integrators, namely Scipy's odeint.

scipy.odeint lets the user specify the times at which the state must be added to the output state history.scipy.odeint is a variable timestep integrator whose one-liner call looks like this (the state is integrated from the initial condition X0 and interpolated/stored at the times specified in times)

X = scipy.odeint(dxdt,X0,times,atol = 1e-13,rtol = 1e-13) 

where X is a matrix that has as many rows as there are elements in times

Basically, I am looking for a similar feature in boost::odeint in order to do two things:

  1. Propagate a state from t0 to tf, but only retrieve the final value of the state. I think I could write an observer only storing the state if the internal time satisfies t == tf, but this looks like a rather ugly hack. If I want to let the integrator choose the proper internal timestep to meet the tolerance values, storing intermediate states is a unnecessary burden.
  2. Propagate a state from t0 to tf but store the state at times specified in advance, that are not necessarily evenly distributed, in a similar fashion to the call to scipy.odeint hereabove, while also letting the integrator choose the proper internal timestep.

The closest I've been to achieving that is with the following

size_t steps = integrate_adaptive( make_controlled< error_stepper_type >( 1.0e-10 , 1.0e-16 ) , 
    dynamics , x , 0.0 , 10.0 , 1. , push_back_state_and_time( x_vec , times ));

The tolerances are met, but all the states are stored into x_vec by the observer, without letting me specify what the storage times should be.

How should I proceed?


Solution

  • It seems you are looking for the integrate_times function:

    It lets you specify a range of exact times for which the observer will be invoked, adjusting the step size to reach every time step exactly, if necessary.

    Especially for adaptive methods, this is quite useful as it computes the solution at the exact times you specified while still controlling the time step size to not exceed the error bounds.

    So your current call could be modified to something like

    auto stepper = make_controlled<error_stepper_type>( 1.0e-10 , 1.0e-16 );
    // std::vector<time> times;
    // std::vector<state> x_vec;
    // time tstep;
    auto tbegin = times.begin();
    auto tend = times.end();
    integrate_times(stepper, dynamics, x, tbegin, tend, tstep, push_back_state(x_vec));