odeRK(func, t0, t1, dt, x0, butcher, adapt_set, unit_hint)

import almafa.engine.func-1

Calculates the solution of an ODE $ \dot x(t) = f(t,x) $ for a given Butcher parametrisation.

funcThe function $f$ defining the ODE $ \dot x(t) = f(t,x). $
t0The start of the evaluation interval
t1The end of the evaluation interval
dtIntitial iteration step (might be adjusted later, c.f. above).
butcherThe butcher tables to parametrise the Runge-Kutta method.
adapt_setSetting of the step adaptive steps.
unit_hintThe method tries to guess all the right quantities from the parameters, but zero might trick it. You can give here the right units [t_units, [x0_unit, x1_unit, ...]]
return valueEither the value of the solution in format [t0, x00, x10, ...; t1, x01, x11, ...], or the coefficents for the interpolation when interpolation is calculated.

Our example in this documentation will be the autmonomous $ \ddot x=x $.

Fix step iteration.

In this case, the iteration goes through with the predefined t-step between $t_0$ and $t_1$. The stucture 'butcher' requires only a 'matrix', a 'steps', and a 'coeff' component. They are coefficients of the previous function evaluations for the new x in the new evaluation, the time steps for the new evaluations, and the final coefficients for the eventual step respectively. See the return values of 'odeRungeKutta3()' and 'odeRungeKutta4()' as examples.

import almafa.engine.func-1;
soln = odeRK(@(t,x) [x[1],-x[0]/1s^2], 0, 5s, .01s, [1e-21m, 1m/s], odeRungeKutta4()); soln.size() plot(soln[...;0]/1s, soln[...;1]/1m)
Adaptive size steps.

Often times one needs an error control on the t-steps. There are embedded RK methods which estimate errors based on a different order method derived from the same function evaluation matrix. The parameter 'butcher' will require the corresponding final coefficents in the component 'corr', and also a 'corr_fn' which returns the error estimation from the last two (hence allowing simple control system methods) difference vector of the two different order results. See the return values of 'odeDormandPrince54()' and 'odeDormandPrince853' as examples.

To enable the adaptive version, one need to define the parameter 'adapt_set'. It takes the following components:

All of these take default values, hence an empty structure '{}' will switch the method on.
import almafa.engine.func-1;
soln = odeRK(@(t,x) [x[1],-x[0]/1s^2], 0, 5s, .01s, [1e-21m, 1m/s], odeDormandPrince853(), {}); soln.size() plot(soln[...;0]/1s, soln[...;1]/1m)
Interpolated adaptive steps.

The clear advantage of high-order adaptive methods are that they can make huge steps, wherever it is possible. Sometimes one needs the values inbeetween the iteration steps. Standard interpolation techniques apply here too. The stucture 'butcher' will need additional fields 'interp', 'ip_coeff', 'ip_steps'. They will correspond to the 'matrix', 'coeff', and 'steps' of the base method. The first four coefficients of the interpolation will be calculated from the iteration step, and the additional coefficients will be the evaluations of 'ip_coeff' for the Horner like polynomial evaluation: $$ p(t) = c_0 + t(c_1 + (1-t)*(c_2 + t*(c_3 + \dots))), $$ where $ t $ is the fraction of the interpolation step. This evaluation is performed by the function 'odeRKEvalInterp()'.

The method is switched on by setting the component 'interp' of the parameter 'adapt_set' true.

import almafa.engine.func-1;
f = @(t,x) [x[1],-x[0]/1s^2]; t0 = 0; t1 = 5s; dt = .01s; x0 = [1e-21m, 1m/s]; B = almafa.odeDormandPrince853(); sparse = almafa.odeRK(f, t0, t1, dt, x0, B, {}); interp = almafa.odeRK(f, t0, t1, dt, x0, B, {interp:true}); j0 = 21; j1 = 29; t = sparse[j0;0]..1ms..sparse[j1;0] x = (@(_) almafa.odeRKEvalInterp(interp,_)[0])(#t); plot(sparse[j0..j1;0]/1s,sparse[j0..j1;1]/1m).curve(t/1s,x/1m)