In this first post I want to explain the organization of the code that I'm going to implement. The main idea is to have a structured organization and to subdivide the code according to the most important operations which must be executed. So that should be easier to optimize the bottlenecks and to extend the code with new functionalities.
We will have two main actors: the steppers and the integrate functions.
Steppers
A stepper will be a function and will represent the numerical method used for the integration. Its job is to execute just one integration step and its signature will be:
[x_next,err] = stepper(f,x,t,dt)
x_next
is the solution at the next step, err
is an estimation of the error (obtainable for example with Richardson Extrapolation), f
is a function handle representing the equations to be integrated, x
is the solution at the current time t
and finally dt
is the time step.
Inside this function I'll check if err
is really requested by means of nargout
keyword. The estimation of the error will be useful to determine the optimal dt
in adaptive integrators.
To set the parameters for both the numerical method and the solver I'll use odeset
and odeget
which are not embedded into Octave but are functions of Odepkg. My intent is to edit these functions in order to make them suitable for my parameters setting.
Integrate Functions
An integrate function will be the function executing the algorithm of integration on more steps. We will have different integrate functions depending on how we want to obtain the solution. For example we can have:
integrate_const(stepper,f,x0,t0,t1,dt)
integrating from t0 to t<=t1 with a fixeddt
;integrate_n_steps(stepper,f,x0,t0,dt,n)
integrating from t0 for n integration steps with fixeddt
;integrate_adaptive(stepper,p,f,x0,t0,t1,dt)
integrating from t0 to t1 with an adaptive timestep, where p is the order of the stepper.
A simple example: Forward Euler
In this part I'll show you an example of two simple steppers (fe_richardson
and fe_heun
). Both the steppers make use of the Forward Euler method to find the new solution:
$$x_{k+1} = x_{k} + h f(t_{k},x_{k})\ .$$
They differ in the way the error is estimated: fe_richardson
makes use of Richardson Extrapolation while fe_heun
makes use of Heun-Euler method.
This is the implementation of the fe_richardson
stepper:
function [t_next,x_next,err] = fwe_richardson(f,t,x,dt,options)
x_next = x + dt.*f(t,x);
t_next = t+dt;
if(nargout == 3)
x1 = x + (.5*dt).*f(t,x);
x_est = x1 + (.5*dt).*f(t+ .5*dt,x1);
err = norm(x_next - x_est,2);
end
end
The Heun-Euler method combines the Heun method, which is of order 2, with the Euler method, which is of order 1. This combination allows to get an estimation of the local error.
We can write the Heun method as
$$\begin{aligned}
\tilde{x}_{k+1} &= x_{k} + h f(t,x_{k}) \\
x_{k+1}^{*} &= x_{k} + \frac{h}{2} (f(t,x_{k}) + f(t+h,\tilde{x}_{k+1}))
\end{aligned}$$
So that the error estimation is given by:
$$ e_{k+1} = \tilde{x}_{k+1} - x^*_{k+1}=\frac{h}{2}(f(t+h,\tilde{x}_{k+1}) - f(t,x_{k}))$$
This is the implementation of the fe_heun
method:
function [t_next,x_next,err]=fwe_heun(f,t,x,dt,options)
x_next = x + dt.*f(t,x);
t_next = t+dt;
if(nargout == 3)
err = .5 * dt * norm(f(t+dt,x_next) - f(t,x),2);
end
end
The adaptive integrator
Finally I'll describe how integrate_adaptive
is implemented. As i said this function does the integration on the total interval [t0,t1]
adapting the timestep at each iteration, by means of the error estimation returned by the stepper. This function takes as input:
stepper
: the stepper;p
: the order of the stepper;f
: the function to be integrated;x0
: the initial condition;t0, t1
: the extremes of the time interval;dt
: the first timestep.
t1
, the function substitutes the last element of the solution with a linear interpolation calculated in t1
between the last two elements of the solution.
function [t,x] = integrate_adaptive(stepper,order, ...
func,tspan,x0,options)
t = tspan(1);
x = x0(:);
dt = odeget(options,'InitialStep',[],'fast');
tau = 1.e-6;
counter = 2;
while( t(end) < (tspan(end)-1.e-13) )
[s,y,err] = stepper(func,t(end),x(:,end),dt,options);
if( s(end) > (tspan(counter)+1.e-13) )
z = [t(end);s];
u = [x(:,end),y];
i = 1;
while( i<=length(z) )
if( abs(z(i)-tspan(counter)) < 1.e-13 )
x = [x,y];
t = [t;s];
i_old = i;
i = length(z)+1;
elseif( z(i) > (tspan(counter)+1.e-13) )
x = [x(:,1:end-1),u(:,1:i-1),u(:,i-1)+ ...
((tspan(counter)-z(i-1))/(z(i)-z(i-1)))* ...
(u(:,i)-u(:,i-1)),u(:,i:end)];
t = [t(1:end-1);z(1:i-1);tspan(counter);z(i:end)];
i_old = i;
i = length(z)+1;
end
i++;
end
counter++;
else
x = [x,y];
t = [t;s];
end
err += eps;
dt = dt.*(tau/abs(err))^(1.0/(order+1));
end
if( counter > max(size(tspan)) )
k = (length(z)-i_old)+1;
x = x(:,1:end-k);
t = t(1:end-k);
end
x = x';
end
The method used to calculate the next timestep is taken from the book of Shampine, Gladwell and Thomson. tau
is a parameter which now is fixed but in the future will be setted by means of odeset
and odeget
functions.
How it will be at the end
At the end, functions like ode45
will be just aliases. For example, we define a function called odefwe
which does the integration by means of Forward Euler method, adapting the timestep with Richardson Extrapolation. The call to this function will just hide a call to integrate_adaptive
function in this way:
odefwe(f,x0,t0,t1,dt) = ...
integrate_adaptive(fe_richardson,1,f,x0,t0,t1,dt)
In the same way we'll define a function called ode12
which does the integration in an adaptive way using the Heun-Euler method. So that, now we will have:
ode12(f,x0,t0,t1,dt) = integrate_adaptive(fe_heun,1,f,x0,t0,t1,dt)
Nessun commento:
Posta un commento