How to perform adaptive step size using Runge-Kutta fourth order (Matlab)? - matlab

For me, it seems like the estimated hstep takes quite a long time and long iteration to converge.
I tried it with this first ODE.
Basically, you perform the difference between RK4 with stepsize of h with h/2.Please note that to reach the same timestep value, you will have to use the y value after two timestep of h/2 so that it reaches h also.
frhs=#(x,y) x.^2*y;
Is my code correct?
clear all;close all;clc
c=[]; i=1; U_saved=[]; y_array=[]; y_array_alt=[];
y_arr=1; y_arr_2=1;
frhs=#(x,y) 20*cos(x);
tol=0.001;
y_ini= 1;
y_ini_2= 1;
c=abs(y_ini-y_ini_2)
hc=1
all_y_values=[];
for m=1:500
if (c>tol || m==1)
fprintf('More')
y_arr
[Unew]=vpa(Runge_Kutta(0,y_arr,frhs,hc))
if (m>1)
y_array(m)=vpa(Unew);
y_array=y_array(logical(y_array));
end
[Unew_alt]=Runge_Kutta(0,y_arr_2,frhs,hc/2);
[Unew_alt]=vpa(Runge_Kutta(hc/2,Unew_alt,frhs,hc/2))
if (m>1)
y_array_alt(m)=vpa(Unew_alt);
y_array_alt=y_array_alt(logical(y_array_alt));
end
fprintf('More')
%y_array_alt(m)=vpa(Unew_alt);
c=vpa(abs(Unew_alt-Unew) )
hc=abs(tol/c)^0.25*hc
if (c<tol)
fprintf('Less')
y_arr=vpa(y_array(end) )
y_arr_2=vpa(y_array_alt(end) )
[Unew]=Runge_Kutta(0,y_arr,frhs,hc)
all_y_values(m)=Unew;
[Unew_alt]=Runge_Kutta(0,y_arr_2,frhs,hc/2);
[Unew_alt]=Runge_Kutta(hc/2,Unew_alt,frhs,hc/2)
c=vpa( abs(Unew_alt-Unew) )
hc=abs(tol/c)^0.2*hc
end
end
end
all_y_values

A better structure for the time loop has only one place where the time step is computed.
x_array = [x0]; y_array = [y0]; h = h_init;
x = x0; y = y0;
while x < x_end
[y_new, err] = RK4_step_w_error(x,y,rhs,h);
factor = abs(tol/err)^0.2;
if factor >= 1
y_array(end+1) = y = y_new;
x_array(end+1) = x = x+h;
end
h = factor*h;
end
For the data given in the code
rhs = #(x,y) 20*cos(x);
x0 = 0; y0 = 1; x_end = 6.5; tol = 1e-3; h_init = 1;
this gives the result against the exact solution
The computed points lie exactly on the exact solution, for the segments between them one would need to use a "dense output" interpolation. Or as a first improvement, just include the middle value from the half-step computation.
function [ y_next, err] = RK4_step_w_error(x,y,rhs,h)
y2 = RK4_step(x,y,rhs,h);
y1 = RK4_step(x,y,rhs,h/2);
y1 = RK4_step(x+h/2,y1,rhs,h/2);
y_next = y1;
err = (y2-y1)/15;
end
function y_next = RK4_step(x,y,rhs,h)
k1 = h*rhs(x,y);
k2 = h*rhs(x+h/2,y+k1);
k3 = h*rhs(x+h/2,y+k2);
k4 = h*rhs(x+h,y+k3);
y_next = y + (k1+2*k2+2*k3+k4)/6;
end
Revision 1
The error returned is the actual step error. The error that is required for the step size control however is the unit step error or error density, which is the step error with divided by h
function [ y_next, err] = RK4_step_w_error(x,y,rhs,h)
y2 = RK4_step(x,y,rhs,h);
y1 = RK4_step(x,y,rhs,h/2);
y1 = RK4_step(x+h/2,y1,rhs,h/2);
y_next = y1;
err = (y2-y1)/15/h;
end
Changing the example to a simple bi-stable model oscillating between two branches of stable equilibria
rhs = #(x,y) 3*y-y^3 + 3*cos(x);
x0 = 0; y0 = 1; x_end = 13.5; tol = 5e-3; h_init = 5e-2;
gives plots of solution, error (against an ode45 integration) and step sizes
Red crosses are the step sizes of rejected steps.
Revision 2
The error in the function values can be used as an error guidance for the extrapolation value which is of 5th order, making the method a 5th order method in extrapolation mode. As it uses the 4th order error to predict the 5th order optimal step size, a caution factor is recommended, the code changes in the appropriate places to
factor = 0.75*abs(tol/err)^0.2;
...
function [ y_next, err] = RK4_step_w_error(x,y,rhs,h)
y2 = RK4_step(x,y,rhs,h);
y1 = RK4_step(x,y,rhs,h/2);
y1 = RK4_step(x+h/2,y1,rhs,h/2);
y_next = y1+(y1-y2)/15;
err = (y1-y2)/15;
end
In the plots the step size is appropriately larger, but the error shows sharper and larger spikes, this version of the method is apparently less stable.

Related

debug code, not sure why im not getting the right value for x1

function [x1] = tutorial1(x0,nMax,tol)
% Calculate the root of the function f(x) = x^3 - 3^x + 1
% using the Newton Method of root-finding.
% Inputs:
% - x0 initial guess
% - nMax number of iterations
% - tol solution accuracy tolerance
% Output:
% - x1 converged root of the equation
% Initialisation of the values x0, nMax, to
x0 = 1.5;
nMax = 15;
tol = 1e-4;
% for loop acting continously for 15 iterations
for i = 1:nMax
fx0= (x0).^3-3.^(x0)+1;
differentialx0=3.*(x0.^2) - 3.^x0.*log(3);
%function f(x0)respectively
x1 = x0 -fx0./differentialx0;
if abs(x1-x0)<tol
break
end
x0 = x1;
fprintf('Iteration = %d, x0 = %.4f, x1 = %.4f, fx1 = %.4f\n',i,x0,x1);
end
% Sample output code for monitoring (this should be included in your loop structure.
return
You are getting the right value actually. With this kind of method, you are numerically solving an equation. So you will converge to the solution with the precision you want (if it works) set by the tolerance criteria.
Execute the code below. It is the same with more digits for the number printing, and a more strict tolerance, and a print of the error (and I added fx1 computation to print it too).
x0 = 1.5;
nMax = 15;
tol = 1e-10; %1e-4
% for loop acting continously for 15 iterations
for i = 1:nMax
fx0= (x0).^3-3.^(x0)+1;
differentialx0=3.*(x0.^2) - 3.^x0.*log(3);
%function f(x0)respectively
x1 = x0 -fx0./differentialx0;
fx1= (x0).^3-3.^(x0)+1;
error = abs(x1-x0) ;
if error<tol
break
end
x0 = x1;
fprintf('Iteration = %i, x0 = %.6f, x1 = %.6f, fx1 = %.6f, error = %.6f \n',i,x0,x1,fx1,error);
end
% Sample output code for monitoring (this should be included in your loop structure.
You will get this output in the command :
Iteration = 1, x0 = 2.288476, x1 = 2.288476, fx1 = -0.821152, error = 0.788476
Iteration = 2, x0 = 1.994137, x1 = 1.994137, fx1 = 0.628953, error = 0.294339
Iteration = 3, x0 = 2.000009, x1 = 2.000009, fx1 = -0.012367, error = 0.005873
Iteration = 4, x0 = 2.000000, x1 = 2.000000, fx1 = 0.000020, error = 0.000009
It gives a solution closer to 2 (yet if you add more digits to fprintf, you will see that it is not a perfect 2 but something like 2.000000000001).
Keep in mind that, if the algorithm converges, it will give you a numerical solution close to theoretical solution with an error which depends on the tolerance you provide

Trouble using Runge-Kutta 2nd-order shooting method in Matlab

I'm having some issues getting my RK2 algorithm to work for a certain second-order linear differential equation. I have posted my current code (with the provided parameters) below. For some reason, the value of y1 deviates from the true value by a wider margin each iteration. Any input would be greatly appreciated. Thanks!
Code:
f = #(x,y1,y2) [y2; (1+y2)/x];
a = 1;
b = 2;
alpha = 0;
beta = 1;
n = 21;
h = (b-a)/(n-1);
yexact = #(x) 2*log(x)/log(2) - x +1;
ye = yexact((a:h:b)');
s = (beta - alpha)/(b - a);
y0 = [alpha;s];
[y1, y2] = RungeKuttaTwo2D(f, a, b, h, y0);
error = abs(ye - y1);
function [y1, y2] = RungeKuttaTwo2D(f, a, b, h, y0)
n = floor((b-a)/h);
y1 = zeros(n+1,1); y2 = y1;
y1(1) = y0(1); y2(1) = y0(2);
for i=1:n-1
ti = a+(i-1)*h;
fvalue1 = f(ti,y1(i),y2(i));
k1 = h*fvalue1;
fvalue2 = f(ti+h/2,y1(i)+k1(1)/2,y2(i)+k1(2)/2);
k2 = h*fvalue2;
y1(i+1) = y1(i) + k2(1);
y2(i+1) = y2(i) + k2(2);
end
end
Your exact solution is wrong. It is possible that your differential equation is missing a minus sign.
y2'=(1+y2)/x has as its solution y2(x)=C*x-1 and as y1'=y2 then y1(x)=0.5*C*x^2-x+D.
If the sign in the y2 equation were flipped, y2'=-(1+y2)/x, one would get y2(x)=C/x-1 with integral y1(x)=C*log(x)-x+D, which contains the given exact solution.
0=y1(1) = -1+D ==> D=1
1=y1(2) = C*log(2)-1 == C=1/log(2)
Additionally, the arrays in the integration loop have length n+1, so that the loop has to be from i=1 to n. Else the last element remains zero, which gives wrong residuals for the second boundary condition.
Correcting that and enlarging the computation to one secant step finds the correct solution for the discretization, as the ODE is linear. The error to the exact solution is bounded by 0.000285, which is reasonable for a second order method with step size 0.05.

Cannot debug 'subscripted assignments mismatch' error on Matlab loop

Below I provide the parameters which essentially set my problem up:
%% Parameters
L = 5; % size of domain
T = 5; % measurement time
dx = 1e-2; % position step
dt = 1e-3; % time step
x0 = 0;
%% More Parameters
t = 0:dt:T; % time vector
x = (0:dx:L)'; % position vector
nt = length(t);
nx = length(x);
mu = dt/dx;
eta = dx/dx;
Lx = (1/dx^2)*spdiags(ones(nx,1)*[1 -2 1],-1:1,nx,nx); % discrete Laplace operator
B = spdiags(ones(nt,1)*[-eta 1+eta 0],0:1,nt-1,nt);
phi = #(x) (x>0).*exp(-1./x.^2);
R = #(x) phi(x).*phi(1-x);
r = R(x-2);
%% Get Data
u = zeros(nx,nt); % preallocate memory
% initial conditions
u(:,1) = sinc((x-x0)/dx);
u(:,2) = sinc((x-x0)/dx);
for k = 2:nt-1
u(:,k+1) = 2*u(:,k) - u(:,k-1) + dt^2*Lx*u(:,k) - dt^2*r.*u(:,k);
end
data = u(x==x0,:);
Okay, so now that we have what we need, I can describe my problem. Below I am trying to compute a loop which will get us v, a 5000x501 matrix, as one can see in the preallocation in the memory. However, the problem is that when I run the loop below, I immediately get the 'subscripted assignments mismatch' error.
%% Solve
v = zeros(nx,nt-1); % preallocate memory
v(1,:) = 2*gradient(data); % initial condition
for l = 1:nx
v(l+1,:) = B*v(l,:);
end
I have computed size(v) = 501 5000, size(B) = 5000 50001, size(v(l,:)) = size(v(1,:)) = 1 5000, thus, since nx = 500, it should all work; but for some reason it doesn't.
Your error is occurring before the loop, on the following line:
v(1,:) = 2*gradient(data); % initial condition
The left hand side is 1-by-5000, but the right hand side is 1-by-5001.
Even if you fix that, you're going to run into a problem in the loop due to the B*v(l,:) operation:
>> B*v(l,:)
Error using *
Inner matrix dimensions must agree.
This is because matrix multiplication requires that the second dimension of B (which is 5001) has to be equal to the first dimension of v(l,:) (which is 1). You'll also have to make sure that the result is a row vector since it's being assigned to v(l+1,:).

Frank - Wolfe Algorithm in matlab

I'm trying to solve the following question :
maximize x^2-5x+y^2-3y
x+y <= 8
x<=2
x,y>= 0
By using Frank Wolf algorithm ( according to http://web.mit.edu/15.053/www/AMP-Chapter-13.pdf ).
But after running of the following program:
syms x y t;
f = x^2-5*x+y^2-3*y;
fdx = diff(f,1,x); % f'x
fdy = diff(f,1,y); % y'x
x0 = [0 0]; %initial point
A = [1 1;1 0]; %constrains matrix
b = [8;2];
lb = zeros(1,2);
eps = 0.00001;
i = 1;
X = [inf inf];
Z = zeros(2,200); %result for end points (x1,x2)
rr = zeros(1,200);
options = optimset('Display','none');
while( all(abs(X-x0)>[eps,eps]) && i < 200)
%f'x(x0)
c1 = subs(fdx,x,x0(1));
c1 = subs(c1,y,x0(2));
%f'y(x0)
c2 = subs(fdy,x,x0(1));
c2 = subs(c2,y,x0(2));
%optimization point of linear taylor function
ys = linprog((-[c1;c2]),A,b,[],[],lb,[],[],options);
%parametric representation of line
xt = (1-t)*x0(1)+t*ys(1,1);
yt = (1-t)*x0(2)+t*ys(2,1);
%f(x=xt,y=yt)
ft = subs(f,x,xt);
ft = subs(ft,y,yt);
%f't(x=xt,y=yt)
ftd = diff(ft,t,1);
%f't(x=xt,y=yt)=0 -> for max point
[t1] = solve(ftd); % (t==theta)
X = double(x0);%%%%%%%%%%%%%%%%%
% [ xt(t=t1) yt(t=t1)]
xnext(1) = subs(xt,t,t1) ;
xnext(2) = subs(yt,t,t1) ;
x0 = double(xnext);
Z(1,i) = x0(1);
Z(2,i) = x0(2);
i = i + 1;
end
x_point = Z(1,:);
y_point = Z(2,:);
% Draw result
scatter(x_point,y_point);
hold on;
% Print results
fprintf('The answer is:\n');
fprintf('x = %.3f \n',x0(1));
fprintf('y = %.3f \n',x0(2));
res = x0(1)^2 - 5*x0(1) + x0(2)^2 - 3*x0(2);
fprintf('f(x0) = %.3f\n',res);
I get the following result:
x = 3.020
y = 0.571
f(x0) = -7.367
And this no matter how many iterations I running this program (1,50 or 200).
Even if I choose a different starting point (For example, x0=(1,6) ), I get a negative answer to most.
I know that is an approximation, but the result should be positive (for x0 final, in this case).
My question is : what's wrong with my implementation?
Thanks in advance.
i changed a few things, it still doesn't look right but hopefully this is getting you in the right direction. It looks like the intial x0 points make a difference to how the algorithm converges.
Also make sure to check what i is after running the program, to determine if it ran to completion or exceeded the maximum iterations
lb = zeros(1,2);
ub = [2,8]; %if x+y<=8 and x,y>0 than both x,y < 8
eps = 0.00001;
i_max = 100;
i = 1;
X = [inf inf];
Z = zeros(2,i_max); %result for end points (x1,x2)
rr = zeros(1,200);
options = optimset('Display','none');
while( all(abs(X-x0)>[eps,eps]) && i < i_max)
%f'x(x0)
c1 = subs(fdx,x,x0(1));
c1 = subs(c1,y,x0(2));
%f'y(x0)
c2 = subs(fdy,x,x0(1));
c2 = subs(c2,y,x0(2));
%optimization point of linear taylor function
[ys, ~ , exit_flag] = linprog((-[c1;c2]),A,b,[],[],lb,ub,x0,options);
so here is the explanation of the changes
ub, uses our upper bound. After i added a ub, the result immediately changed
x0, start this iteration from the previous point
exit_flag this allows you to check exit_flag after execution (it always seems to be 1 indicating it solved the problem correctly)

integral2Calc>integral2t/tensor & Warning: Reached the maximum number of function evaluations (10000)

I am trying to do a set of integrations for a particular value of psi and theta. These integrations are then combined into a formula to give P. The idea is then to do this for a set of psi and theta from 0 to pi/2 and then plot the results of P against a function of theta and psi.
I get two errors, firstly
integral2Calc>integral2t/tensor
not sure how to change the integral to make it the right dimensions to psi no longer being a scalar.
I also get Warning: Reached the maximum number of function evaluations (10000) for i3, is this a serious error, (as in has the integration not been computed) I tried changing the type of integration or changing the error tolerance and this seemed to have no effect on removing the actual error..
eta = input('Enter Dielectric Constant 1.5-4: ');
sdev = input('Enter STD DEV (roughness) maybe 0.1: ');
psi = [0:0.01:pi/2];
theta = [0:0.01:pi/2];
r = sqrt((sin(psi)).^2+(sin(theta).*(cos(psi))).^2);
calpha = (cos(theta+dtheta)).*(cos(psi+dpsi));
rp01 = calpha-sqrt(eta-1+((calpha).^2));
rp02 = calpha+sqrt(eta-1+((calpha).^2));
rperp = (rp01./rp02).^2;
rp11 = ((eta.*calpha)-sqrt(eta-1+((calpha).^2)));
rp12 = ((eta.*calpha)+sqrt(eta-1+((calpha).^2)));
rpar = (rp11./rp12).^2;
qthingy1 = ((sin(psi+dpsi)).^2)-((sin(theta+dtheta)).^2).*((cos(psi+dpsi)).^2);
qthingy2 = ((sin(psi+dpsi)).^2)+((sin(theta+dtheta)).^2).*((cos(psi+dpsi)).^2);
qthingy = qthingy1./qthingy2;
wthingy1 = (sin(2*(psi+dpsi))).*(sin(theta+dtheta));
wthingy2 = ((sin(psi+dpsi)).^2)+(sin((theta+dtheta)).^2).*((sin(psi+dpsi)).^2);
wthingy = wthingy1./wthingy2;
roughness = (cos(psi)./(2*pi*(sdev.^2))).*exp(-((cos(psi).*dtheta).^2+(dpsi).^2)/(2*sdev.^2));
fun = matlabFunction((rpar+rperp).*roughness,'vars',{dtheta,dpsi});
fun2 = matlabFunction(roughness,'vars',{dtheta,dpsi});
fun3 = matlabFunction((rpar+rperp).*roughness.*qthingy,'vars',{dtheta,dpsi});
fun4 = matlabFunction((rpar+rperp).*roughness.*wthingy,'vars',{dtheta,dpsi});
thetamax = (pi/2) - theta;
psimax = (pi/2) - psi;
A = integral2(fun2,-pi/2,pi/2,-pi/2,pi/2);
i1 = (integral2(fun,-pi/2,thetamax,-pi/2,psimax))./A;
q = 2 - i1;
i2 = (integral2(fun3,-pi/2,thetamax,-pi/2,psimax))./A;
i3 = (integral2(fun4,-pi/2,thetamax,-pi/2,psimax))./A;
P = sqrt(i2.^2+i3.^2)./i1
plot(P, r);