The IBVP is
u_t+2u_x=0 , 0<x<1, t>0
u(x,0)=sin(pi*x)
u(0,t)=g(t)=sin(-2*pi*t)
I have to first implement the scheme using 4th-order central difference SBP operator in space and RK4 in time with spacing xi=i*h, i=0,1,...,N and update g(t) at every RK stage. Also, using the code compute the convergence rate on a sequence of grids.
Below I have shown my working which is not helping me find the convergence rate so can anyone help me with finding my mistakes.
%Parameters
nstp = 16; %number of grid points in space
t0 = 0; %initial time
tend = 1; %end time
x0 = 0; %left boundary
xN = 1; %right boundary
x = linspace(0,1,nstp); %points in space
h = xN-x0/nstp-1; %grid size
cfl = 4;
k = cfl*h; %length of time steps
N = ceil(tend/k); %number of steps in time
k = tend/N; %length of time steps
u0 = sin(pi*x); %initial data
e = zeros(nstp);
e(1) = 1;
e0 = e(:,1);
m = zeros(nstp);
m(1) = sin(-2*pi*tend);
g = m(:,1);
%4th order central SBP operator in space
m=10; %points
H=diag(ones(m,1),0);
H(1:4,1:4)=diag([17/48 59/48 43/48 49/48]);
H(m-3:m,m-3:m)=fliplr(flipud(diag([17/48 59/48 43/48 49/48])));
H=H*h;
HI=inv(H);
D1=(-1/12*diag(ones(m-2,1),2)+8/12*diag(ones(m-1,1),1)- ...
8/12*diag(ones(m-1,1),-1)+1/12*diag(ones(m-2,1),-2));
D1(1:4,1:6)=[-24/17,59/34,-4/17,-3/34,0,0; -1/2,0,1/2,0,0,0;
4/43,-59/86,0,59/86,-4/43,0; 3/98,0,-59/98,0,32/49,-4/49];
D1(m-3:m,m-5:m)=flipud( fliplr(-D1(1:4,1:6)));
D1=D1/h;
%SBP-SAT scheme
u = -2*D1*x(1:N-1)'-(2*HI*(u0-g)*e);
%Runge Kutta for ODE
for i=1:nstp %calculation loop
t=(i-1)*k;
k1=D*u;
k2=D*(u+k*k1/2);
k3=D*(u+k*k2/2);
k4=D*(u+k*k3);
u=u+(h*(k1+k2+k3+k4))/6; %main equation
figure(1)
plot(x(1:N-1),u); %plot
drawnow
end
%error calculcation
ucorrect = sin(pi*(x-2*tend)); %correct solution
ucomp = u(:,end); %computed solution
errornorm = sqrt((ucomp-ucorrect)'*H*(ucomp-ucorrect)); %norm of error**
One egregious error is
h = xN-x0/nstp-1; %grid size
You need parentheses here, else you compute h as xN-(x0/nstp)-1=1-(0/nstp)-1=0.
Or avoid this all and use the actual grid step. Also, if nstp is the number of steps or segments in the subdivision, the number of nodes is one larger. And if you define x0,xN then you should also actually use them.
x = linspace(x0,xN,nstp+1); %points in space
h = x(2)-x(1); %grid size
As to the SBP-SAT approach, the space discretization gives
u_t(t) + 2*D_1*u(t) = - H_inv * (dot(e_0,u(t)) - g(t))*e_0
which should give the MoL ODE function as
F = #(u,t) -2*D1*u - HI*e0*(u(1)-g(t))
The matrix construction has to be adapted to have the correct extends for these changed operations.
The implement RK4 as
k1 = F(u,t)
k2 = F(u+0.5*h*k1, t+0.5*h)
...
In total this gives a modified script below. Stable solutions only occur for cfl=1 or below. With that the errors seemingly behave like 4th order. The factor for the initial condition also works best in the range 0.5 to 1, larger factors tend to increase the error at x=0, at least in the initial time steps. With the smaller factors the error is uniform over the interval.
%Parameters
xstp = 16; %number of grid points in space
x0 = 0; %left boundary
xM = 1; %right boundary
x = linspace(x0,xM,xstp+1)'; %points in space
h = x(2)-x(1); %grid size
cfl = 1;
t0 = 0; %initial time
tend = 1; %end time
k = cfl*h; %length of time steps
N = ceil(tend/k); %number of steps in time
k = tend/N; %length of time steps
u = sin(pi*x); %initial data
e0 = zeros(xstp+1,1);
e0(1) = 1;
g = #(t) sin(-2*pi*t);
%4th order central SBP operator in space
M=xstp+1; %points
H=diag(ones(M,1),0); % or eye(M)
H(1:4,1:4)=diag([17/48 59/48 43/48 49/48]);
H(M-3:M,M-3:M)=fliplr(flipud(diag([17/48 59/48 43/48 49/48])));
H=H*h;
HI=inv(H);
D1=(-1/12*diag(ones(M-2,1),2)+8/12*diag(ones(M-1,1),1)- ...
8/12*diag(ones(M-1,1),-1)+1/12*diag(ones(M-2,1),-2));
D1(1:4,1:6) = [ -24/17, 59/34, -4/17, -3/34, 0, 0;
-1/2, 0, 1/2, 0, 0, 0;
4/43, -59/86, 0, 59/86, -4/43, 0;
3/98, 0, -59/98, 0, 32/49, -4/49 ];
D1(M-3:M,M-5:M) = flipud( fliplr(-D1(1:4,1:6)));
D1=D1/h;
%SBP-SAT scheme
F = #(u,t) -2*D1*u - 0.5*HI*(u(1)-g(t))*e0;
clf;
%Runge Kutta for ODE
for i=1:N %calculation loop
t=(i-1)*k;
k1=F(u,t);
k2=F(u+0.5*k*k1, t+0.5*k);
k3=F(u+0.5*k*k2, t+0.5*k);
k4=F(u+k*k3, t+k);
u=u+(k/6)*(k1+2*k2+2*k3+k4); %main equation
figure(1)
subplot(211);
plot(x,u,x,sin(pi*(x-2*(t+k)))); %plot
ylim([-1.1,1.1]);
grid on;
legend(["computed";"exact"])
drawnow
subplot(212);
plot(x,u-sin(pi*(x-2*(t+k)))); %plot
grid on;
hold on;
drawnow
end
%error calculcation
ucorrect = sin(pi*(x-2*tend)); %correct solution
ucomp = u; %computed solution
errornorm = sqrt((ucomp-ucorrect)'*H*(ucomp-ucorrect)); %norm of error**
Related
Consider the following problem:
I am now in the third part of this question. I wrote the vectorial loop equations (q=teta2, x=teta3 and y=teta4):
fval(1,1) = r2*cos(q)+r3*cos(x)-r4*cos(y)-r1;
fval(2,1) = r2*sin(q)+r3*sin(x)-r4*sin(y);
I have these 2 functions, and all variables except x and y are given. I found the roots with help of this video.
Now I need to plot graphs of q versus x and q versus y when q is at [0,2pi] with delta q of 2.5 degree. What should I do to plot the graphs?
Below is my attempt so far:
function [fval,jac] = lorenzSystem(X)
%Define variables
x = X(1);
y = X(2);
q = pi/2;
r2 = 15
r3 = 50
r4 = 45
r1 = 40
%Define f(x)
fval(1,1)=r2*cos(q)+r3*cos(x)-r4*cos(y)-r1;
fval(2,1)=r2*sin(q)+r3*sin(x)-r4*sin(y);
%Define Jacobian
jac = [-r3*sin(X(1)), r4*sin(X(2));
r3*cos(X(1)), -r4*cos(X(2))];
%% Multivariate NR
%Initial conditions:
X0 = [0.5;1];
maxIter = 50;
tolX = 1e-6;
X = X0;
Xold = X0;
for i = 1:maxIter
[f,j] = lorenzSystem(X);
X = X - inv(j)*f;
err(:,i) = abs(X-Xold);
Xold = X;
if (err(:,i)<tolX)
break;
end
end
Please take a look at my solution below, and study how it differs from your own.
function [th2,th3,th4] = q65270276()
[th2,th3,th4] = lorenzSystem();
hF = figure(); hAx = axes(hF);
plot(hAx, deg2rad(th2), deg2rad(th3), deg2rad(th2), deg2rad(th4));
xlabel(hAx, '\theta_2')
xticks(hAx, 0:pi/3:2*pi);
xticklabels(hAx, {'$0$','$\frac{\pi}{3}$','$\frac{2\pi}{3}$','$\pi$','$\frac{4\pi}{3}$','$\frac{5\pi}{3}$','$2\pi$'});
hAx.TickLabelInterpreter = 'latex';
yticks(hAx, 0:pi/6:pi);
yticklabels(hAx, {'$0$','$\frac{\pi}{6}$','$\frac{\pi}{3}$','$\frac{\pi}{2}$','$\frac{2\pi}{3}$','$\frac{5\pi}{6}$','$\pi$'});
set(hAx, 'XLim', [0 2*pi], 'YLim', [0 pi], 'FontSize', 16);
grid(hAx, 'on');
legend(hAx, '\theta_3', '\theta_4')
end
function [th2,th3,th4] = lorenzSystem()
th2 = (0:2.5:360).';
[th3,th4] = deal(zeros(size(th2)));
% Define geometry:
r1 = 40;
r2 = 15;
r3 = 50;
r4 = 45;
% Define the residual:
res = #(q,X)[r2*cosd(q)+r3*cosd(X(1))-r4*cosd(X(2))-r1; ... Δx=0
r2*sind(q)+r3*sind(X(1))-r4*sind(X(2))]; % Δy=0
% Define the Jacobian:
J = #(X)[-r3*sind(X(1)), r4*sind(X(2));
r3*cosd(X(1)), -r4*cosd(X(2))];
X0 = [acosd((45^2-25^2-50^2)/(-2*25*50)); 180-acosd((50^2-25^2-45^2)/(-2*25*45))]; % Accurate guess
maxIter = 500;
tolX = 1e-6;
for idx = 1:numel(th2)
X = X0;
Xold = X0;
err = zeros(maxIter, 1); % Preallocation
for it = 1:maxIter
% Update the guess
f = res( th2(idx), Xold );
X = Xold - J(Xold) \ f;
% X = X - pinv(J(X)) * res( q(idx), X ); % May help when J(X) is close to singular
% Determine convergence
err(it) = (X-Xold).' * (X-Xold);
if err(it) < tolX
break
end
% Update history
Xold = X;
end
% Unpack and store θ₃, θ₄
th3(idx) = X(1);
th4(idx) = X(2);
% Update X0 for faster convergence of the next case:
X0 = X;
end
end
Several notes:
All computations are performed in degrees.
The specific plotting code I used is less interesting, what matters is that I defined all θ₂ in advance, then looped over them to find θ₃ and θ₄ (without recursion, as was done in your own implementation).
The initial guess (actually, analytical solution) for the very first case (θ₂=0) can be found by solving the problem manually (i.e. "on paper") using the law of cosines. The solver also works for other guesses, but you might need to increase maxIter. Also, for certain guesses (e.g. X(1)==X(2)), the Jacobian is ill-conditioned, in which case you can use pinv.
If my computation is correct, this is the result:
I have a basic exercise for telecommunications with matlab, and i must plot a triangle pulse with (-c,0) to (c,0) with c = 6 and Amplitude = 1 in a for loop for M pulses and approach the periodic pulse using N Fourier series terms. I can't find something on the internet that can help me so far.
A similar code for rect pulse that I made and works is this:
a = 1;
b = 3;
N = 1000;
t = linspace(a-2*a,b+2*b,N);
A = 1;
y = rect_pulse(A,a,b,t);
plot(t,y);
grid on
axis([a-2*a b+2*b 0 2*A]);
M = 5;
T=7;
t_new = linspace(a-2*a,b+(M-1)*T+2*b,N);
y_new = zeros(1,N);
for index = 1:1:M
temp_y = rect_pulse(A,a+(index-1)*T,b+(index-1)*T,t_new);
y_new = y_new + temp_y;
end
figure;
plot(t_new,y_new);
grid on;
axis([a-2*a b+(M-1)*T+2*b 0 2*A]);
Where rect_pulse is this:
function y = rect_pulse (A,a,b,t)
N=length(t);
y = zeros(1,N);
for index = 1:1:N
if(t(1,index)>=a) && (t(1,index)<=b)
y(1,index) = A;
end
end
And fourier series is this:
function y_fourier = fourier_series_rect_pulse(a,b,To,N,t)
y_fourier = 0;
wo = (2*pi)/To;
for n = -N:1:N
f_real = #(x) cos(n*wo*x);
f_imag = #(x) sin(n*wo*x);
cn = (1/To)*(quad(f_real,a,b)) - j*quad(f_imag,a,b));
y_fourier = y_fourier + cn*exp(j*n*wo*t);
end
y_fourier = real(y_fourier);
Any ideas how to make this in to triangle pulse?
This probably deviates significantly from your approach but if you're curious here is a script I came up with to generate a triangular pulse train that can be adjusted. This method, unfortunately, uses the fft() function which may or may not be off-limits in your case. Most of the script uses indexing and manipulating vectors. Additional spectral components may be seen due to the DC offset of the alternating triangular wave and the limited number of cycles available in the vector representation of the triangular wave.
Triangular Pulses and Fourier Transforms:
Triangular Pulse with Duty-Off Period:
Higher frequency spectral components present due to the abrupt corners that occur at the transition states of the triangle pulse and duty-off period.
%******************************************************%
%PROPERTIES THAT CAN BE CHANGED%
%******************************************************%
Plotting_Interval = 0.01; %0.01 seconds%
Pulse_Width = 1/20; %6 seconds%
Period = 1/20; %10 seconds (should be at least the pulse width)%
Start_Time = 0;
End_Time = Pulse_Width*1000; %(1000 pulses)%
%******************************************************%
if(Period < Pulse_Width)
Period = Pulse_Width;
end
Time_Vector = (Start_Time: Plotting_Interval: End_Time);
Points_Per_Unit_Time = 1/Plotting_Interval;
Half_Pulse = Pulse_Width/2;
Number_Of_Points = Pulse_Width/Plotting_Interval;
Rising_Slope = linspace(0,1,floor(Number_Of_Points/2) + 1);
Falling_Slope = 1 - Rising_Slope;
Triangular_Pulse = [Rising_Slope Falling_Slope(2:end)];
t = (0: Plotting_Interval: Pulse_Width);
Periodic_Triangular_Pulse = zeros(1,length(Time_Vector));
for Cycle = 1: +Period/Plotting_Interval: End_Time/Plotting_Interval
Periodic_Triangular_Pulse(1,Cycle:Cycle+length(Triangular_Pulse)-1) = Triangular_Pulse(1,1:end);
end
Periodic_Triangular_Pulse = Periodic_Triangular_Pulse(1,1:length(Time_Vector));
subplot(1,2,1); plot(Time_Vector,Periodic_Triangular_Pulse);
Triangle_Frequency = 1/Period;
title("Triangular Pulse Train " + num2str(Triangle_Frequency) + "Hz (first 10 cycles)");
axis([0 Period*10 0 1]);
xlabel("Time (s)"); ylabel("Amplitude");
Signal_Length = length(Periodic_Triangular_Pulse);
Fourier_Transform = fft(Periodic_Triangular_Pulse);
Fs = 1/Plotting_Interval;
P2 = abs(Fourier_Transform/Signal_Length);
P1 = P2(1:floor(Signal_Length/2)+1);
P1(2:end-1) = 2*P1(2:end-1);
f = Fs*(0:(Signal_Length/2))/Signal_Length;
subplot(1,2,2); plot(f,P1)
title("Single-Sided Fourier Transform");
xlabel("Frequency (Hz)"); ylabel("Magnitude");
Ran using MATLAB R2019b
I am trying to approximate and plot the solution to u"(x) = exp(x) in the interval 0-3, with boundary conditions x(0)=1,x(3)=3. I am able to plot the approximate solution vs the exact, but the plot looks a bit off:
% Interval
a=0;
b=3;
n=10;
% Boundary vals
alpha=1;
beta=3;
%grid size
h=(b-a)/(n+1);
%Matrix generation
m = -2;
u = 1;
l = 1;
% Obtained from (y(i-1) -2y(i) + y(i+1)) = h^2 exp(x(i)
M = (1/h^2).*(diag(m*ones(1,n)) + diag(u*ones(1,n-1),1) + diag(l*ones(1,n-1),-1));
B=[];
xjj=[];
for j=1:n
xjj=[xjj,j*h];
if j==1
B=[B,f(j*h)-(alpha/h^2)];
continue
end
if j==n
B=[B,f(j*h)-(beta/h^2)];
continue
else
B=[B,f(j*h)];
end
end
X=M\B';
x=linspace(0,3,101);
plot(xjj',X,'r*')
hold on
plot(x,exp(x),'b-')
I appreciate all the advice and explanation. This is the scheme I am following: http://web.mit.edu/10.001/Web/Course_Notes/Differential_Equations_Notes/node9.html
You could shorten the big loop to simply
x=linspace(a,b,n+2);
B = f(x(2:end-1));
B(1)-=alpha/h^2;
B(n)-=beta/h^2;
The exact solution is u(x)=C*x+D+exp(x), the boundary conditions give D=0 and 3*C+exp(3)=3 <=> C=1-exp(3)/3.
Plotting this exact solution against the numerical solution gives a quite good fit for this large step size:
f=#(x)exp(x)
a=0; b=3;
n=10;
% Boundary vals
alpha=1; beta=3;
%grid
x=linspace(a,b,n+2);
h=x(2)-x(1);
% M*u=B obtained from (u(i-1) -2u(i) + u(i+1)) = h^2 exp(x(i))
M = (1/h^2).*(diag(-2*ones(1,n)) + diag(1*ones(1,n-1),1) + diag(1*ones(1,n-1),-1));
B = f(x(2:end-1));
B(1)-=alpha/h^2; B(n)-=beta/h^2;
U=M\B';
U = [ alpha; U; beta ];
clf;
plot(x',U,'r*')
hold on
x=linspace(0,3,101);
C = 1-exp(3)/3
plot(x,exp(x)+C*x,'b-')
Here is the code which is trying to solve a coupled PDEs using finite difference method,
clear;
Lmax = 1.0; % Maximum length
Wmax = 1.0; % Maximum wedth
Tmax = 2.; % Maximum time
% Parameters needed to solve the equation
K = 30; % Number of time steps
n = 3; % Number of space steps
m =30; % Number of space steps
M = 2;
N = 1;
Pr = 1;
Re = 1;
Gr = 5;
maxn=20; % The wave-front: intermediate point from which u=0
maxm = 20;
maxk = 20;
dt = Tmax/K;
dx = Lmax/n;
dy = Wmax/m;
%M = a*B1^2*l/(p*U)
b =1/(1+M*dt);
c =dt/(1+M*dt);
d = dt/((1+M*dt)*dy);
%Gr = gB*(T-T1)*l/U^2;
% Initial value of the function u (amplitude of the wave)
for i = 1:n
if i < maxn
u(i,1)=1.;
else
u(i,1)=0.;
end
x(i) =(i-1)*dx;
end
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
for j = 1:m
if j < maxm
v(j,1)=1.;
else
v(j,1)=0.;
end
y(j) =(j-1)*dy;
end
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
for k = 1:K
if k < maxk
T(k,1)=1.;
else
T(k,1)=0.;
end
z(k) =(k-1)*dt;
end
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Value at the boundary
%for k=0:K
%end
% Implementation of the explicit method
for k=0:K % Time loop
for i=1:n % Space loop
for j=1:m
u(i,j,k+1) = b*u(i,j,k)+c*Gr*T(i,j,k+1)+d*[((u(i,j+1,k)-u(i,j,k))/dy)^(N-1)*((u(i,j+1,k)-u(i,j,k))/dy)]-d*[((u(i,j,k)-u(i,j-1,k))/dy)^(N-1)*((u(i,j,k)-u(i,j-1,k))/dy)]-d*[u(i,j,k)*((u(i,j,k)-u(i-1,j,k))/dx)+v(i,j,k)*((u(i,j+1,k)-u(i,j,k))/dy)];
v(i,j,k+1) = dy*[(u(i-1,j,k+1)-u(i,j,k+1))/dx]+v(i,j-1,k+1);
T(i,j,k+1) = T(i,j,k)+(dt/(Pr*Re))*{(T(i,j+1,k)-2*T(i,j,k)+T(i,j-1,k))/dy^2-Pr*Re{u(i,j,k)*((T(i,j,k)-T(i-1,j,k))/dx)+v(i,j,k)*((T(i,j+1,k)-T(i,j,k))/dy)}};
end
end
end
% Graphical representation of the wave at different selected times
plot(x,u(:,1),'-',x,u(:,10),'-',x,u(:,50),'-',x,u(:,100),'-')
title('graphs')
xlabel('X')
ylabel('Y')
But I am getting this error
Subscript indices must either be real positive integers or logicals.
I am trying to implement this
with boundary conditions
Can someone please help me out!
Thanks
To be quite honest, it looks like you started with something that's way over your head, just typed everything down in one go without thinking much, and now you are surprised that it doesn't work...
In the future, please break down problems like these into waaaay smaller chunks that you can individually plot, check, test, etc. Better yet, try simpler problems first (wave equation, heat equation, ...), gradually working your way up to this.
I say this so harshly, because there were quite a number of fairly basic things wrong with your code:
you've used braces ({}) and brackets ([]) exactly as they are written in the equation. In MATLAB, braces are a constructor for a special container object called a cell array, and brackets are used to construct arrays and matrices. To group things like in the equation, you always have to use parentheses (()).
You had quite a number of parentheses wrong, which became apparent when I re-grouped and broke up those huge unintelligible lines into multiple lines that humans can actually read with understanding
you forgot to take the absolute values in the 3rd and 4th terms of u
you looped over k = 0:K and j = 1:m and then happily index everything with k and j-1. MATLAB is 1-based, meaning, the first element of anything is element 1, and indexing with 0 is an error
you've initialized 3 vectors u, v and T, but then index those in the loop as if they are 3D arrays
Now, I've managed to come up with the following code, which runs OK and at least more or less agrees with the equations shown. But I think it still doesn't make much sense because I get only zeros out (except for the initial values).
But, with this feedback, you should be able to correct any problems left.
Lmax = 1.0; % Maximum length
Wmax = 1.0; % Maximum wedth
Tmax = 2.; % Maximum time
% Parameters needed to solve the equation
K = 30; % Number of time steps
n = 3; % Number of space steps
m = 30; % Number of space steps
M = 2;
N = 1;
Pr = 1;
Re = 1;
Gr = 5;
maxn = 20; % The wave-front: intermediate point from which u=0
maxm = 20;
maxk = 20;
dt = Tmax/K;
dx = Lmax/n;
dy = Wmax/m;
%M = a*B1^2*l/(p*U)
b = 1/(1+M*dt);
c = dt/(1+M*dt);
d = dt/((1+M*dt)*dy);
%Gr = gB*(T-T1)*l/U^2;
% Initial value of the function u (amplitude of the wave)
u = zeros(n,m,K+1);
x = zeros(n,1);
for i = 1:n
if i < maxn
u(i,1)=1.;
else
u(i,1)=0.;
end
x(i) =(i-1)*dx;
end
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
v = zeros(n,m,K+1);
y = zeros(m,1);
for j = 1:m
if j < maxm
v(1,j,1)=1.;
else
v(1,j,1)=0.;
end
y(j) =(j-1)*dy;
end
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
T = zeros(n,m,K+1);
z = zeros(K,1);
for k = 1:K
if k < maxk
T(1,1,k)=1.;
else
T(1,1,k)=0.;
end
z(k) =(k-1)*dt;
end
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Value at the boundary
%for k=0:K
%end
% Implementation of the explicit method
for k = 2:K % Time loop
for i = 2:n % Space loop
for j = 2:m-1
u(i,j,k+1) = b*u(i,j,k) + ...
c*Gr*T(i,j,k+1) + ...
d*(abs(u(i,j+1,k) - u(i,j ,k))/dy)^(N-1)*((u(i,j+1,k) - u(i,j ,k))/dy) - ...
d*(abs(u(i,j ,k) - u(i,j-1,k))/dy)^(N-1)*((u(i,j ,k) - u(i,j-1,k))/dy) - ...
d*(u(i,j,k)*((u(i,j ,k) - u(i-1,j,k))/dx) +...
v(i,j,k)*((u(i,j+1,k) - u(i ,j,k))/dy));
v(i,j,k+1) = dy*(u(i-1,j,k+1)-u(i,j,k+1))/dx + ...
v(i,j-1,k+1);
T(i,j,k+1) = T(i,j,k) + dt/(Pr*Re) * (...
(T(i,j+1,k) - 2*T(i,j,k) + T(i,j-1,k))/dy^2 - Pr*Re*(...
u(i,j,k)*((T(i,j,k) - T(i-1,j,k))/dx) + v(i,j,k)*((T(i,j+1,k) - T(i,j,k))/dy))...
);
end
end
end
% Graphical representation of the wave at different selected times
figure, hold on
plot(x, u(:, 1), '-',...
x, u(:, 10), '-',...
x, u(:, 50), '-',...
x, u(:,100), '-')
title('graphs')
xlabel('X')
ylabel('Y')
I've found myself needing to do a least-squares (or similar matrix-based operation) for every pixel in an image. Every pixel has a set of numbers associated with it, and so it can be arranged as a 3D matrix.
(This next bit can be skipped)
Quick explanation of what I mean by least-squares estimation :
Let's say we have some quadratic system that is modeled by Y = Ax^2 + Bx + C and we're looking for those A,B,C coefficients. With a few samples (at least 3) of X and the corresponding Y, we can estimate them by:
Arrange the (lets say 10) X samples into a matrix like X = [x(:).^2 x(:) ones(10,1)];
Arrange the Y samples into a similar matrix: Y = y(:);
Estimate the coefficients A,B,C by solving: coeffs = (X'*X)^(-1)*X'*Y;
Try this on your own if you want:
A = 5; B = 2; C = 1;
x = 1:10;
y = A*x(:).^2 + B*x(:) + C + .25*randn(10,1); % added some noise here
X = [x(:).^2 x(:) ones(10,1)];
Y = y(:);
coeffs = (X'*X)^-1*X'*Y
coeffs =
5.0040
1.9818
0.9241
START PAYING ATTENTION AGAIN IF I LOST YOU THERE
*MAJOR REWRITE*I've modified to bring it as close to the real problem that I have and still make it a minimum working example.
Problem Setup
%// Setup
xdim = 500;
ydim = 500;
ncoils = 8;
nshots = 4;
%// matrix size for each pixel is ncoils x nshots (an overdetermined system)
%// each pixel has a matrix stored in the 3rd and 4rth dimensions
regressor = randn(xdim,ydim, ncoils,nshots);
regressand = randn(xdim, ydim,ncoils);
So my problem is that I have to do a (X'*X)^-1*X'*Y (least-squares or similar) operation for every pixel in an image. While that itself is vectorized/matrixized the only way that I have to do it for every pixel is in a for loop, like:
Original code style
%// Actual work
tic
estimate = zeros(xdim,ydim);
for col=1:size(regressor,2)
for row=1:size(regressor,1)
X = squeeze(regressor(row,col,:,:));
Y = squeeze(regressand(row,col,:));
B = X\Y;
% B = (X'*X)^(-1)*X'*Y; %// equivalently
estimate(row,col) = B(1);
end
end
toc
Elapsed time = 27.6 seconds
EDITS in reponse to comments and other ideas
I tried some things:
1. Reshaped into a long vector and removed the double for loop. This saved some time.
2. Removed the squeeze (and in-line transposing) by permute-ing the picture before hand: This save alot more time.
Current example:
%// Actual work
tic
estimate2 = zeros(xdim*ydim,1);
regressor_mod = permute(regressor,[3 4 1 2]);
regressor_mod = reshape(regressor_mod,[ncoils,nshots,xdim*ydim]);
regressand_mod = permute(regressand,[3 1 2]);
regressand_mod = reshape(regressand_mod,[ncoils,xdim*ydim]);
for ind=1:size(regressor_mod,3) % for every pixel
X = regressor_mod(:,:,ind);
Y = regressand_mod(:,ind);
B = X\Y;
estimate2(ind) = B(1);
end
estimate2 = reshape(estimate2,[xdim,ydim]);
toc
Elapsed time = 2.30 seconds (avg of 10)
isequal(estimate2,estimate) == 1;
Rody Oldenhuis's way
N = xdim*ydim*ncoils; %// number of columns
M = xdim*ydim*nshots; %// number of rows
ii = repmat(reshape(1:N,[ncoils,xdim*ydim]),[nshots 1]); %//column indicies
jj = repmat(1:M,[ncoils 1]); %//row indicies
X = sparse(ii(:),jj(:),regressor_mod(:));
Y = regressand_mod(:);
B = X\Y;
B = reshape(B(1:nshots:end),[xdim ydim]);
Elapsed time = 2.26 seconds (avg of 10)
or 2.18 seconds (if you don't include the definition of N,M,ii,jj)
SO THE QUESTION IS:
Is there an (even) faster way?
(I don't think so.)
You can achieve a ~factor of 2 speed up by precomputing the transposition of X. i.e.
for x=1:size(picture,2) % second dimension b/c already transposed
X = picture(:,x);
XX = X';
Y = randn(n_timepoints,1);
%B = (X'*X)^-1*X'*Y; ;
B = (XX*X)^-1*XX*Y;
est(x) = B(1);
end
Before: Elapsed time is 2.520944 seconds.
After: Elapsed time is 1.134081 seconds.
EDIT:
Your code, as it stands in your latest edit, can be replaced by the following
tic
xdim = 500;
ydim = 500;
n_timepoints = 10; % for example
% Actual work
picture = randn(xdim,ydim,n_timepoints);
picture = reshape(picture, [xdim*ydim,n_timepoints])'; % note transpose
YR = randn(n_timepoints,size(picture,2));
% (XX*X).^-1 = sum(picture.*picture).^-1;
% XX*Y = sum(picture.*YR);
est = sum(picture.*picture).^-1 .* sum(picture.*YR);
est = reshape(est,[xdim,ydim]);
toc
Elapsed time is 0.127014 seconds.
This is an order of magnitude speed up on the latest edit, and the results are all but identical to the previous method.
EDIT2:
Okay, so if X is a matrix, not a vector, things are a little more complicated. We basically want to precompute as much as possible outside of the for-loop to keep our costs down. We can also get a significant speed-up by computing XT*X manually - since the result will always be a symmetric matrix, we can cut a few corners to speed things up. First, the symmetric multiplication function:
function XTX = sym_mult(X) % X is a 3-d matrix
n = size(X,2);
XTX = zeros(n,n,size(X,3));
for i=1:n
for j=i:n
XTX(i,j,:) = sum(X(:,i,:).*X(:,j,:));
if i~=j
XTX(j,i,:) = XTX(i,j,:);
end
end
end
Now the actual computation script
xdim = 500;
ydim = 500;
n_timepoints = 10; % for example
Y = randn(10,xdim*ydim);
picture = randn(xdim,ydim,n_timepoints); % 500x500x10
% Actual work
tic % start timing
picture = reshape(picture, [xdim*ydim,n_timepoints])';
% Here we precompute the (XT*Y) calculation to speed things up later
picture_y = [sum(Y);sum(Y.*picture)];
% initialize
est = zeros(size(picture,2),1);
picture = permute(picture,[1,3,2]);
XTX = cat(2,ones(n_timepoints,1,size(picture,3)),picture);
XTX = sym_mult(XTX); % precompute (XT*X) for speed
X = zeros(2,2); % preallocate for speed
XY = zeros(2,1);
for x=1:size(picture,2) % second dimension b/c already transposed
%For some reason this is a lot faster than X = XTX(:,:,x);
X(1,1) = XTX(1,1,x);
X(2,1) = XTX(2,1,x);
X(1,2) = XTX(1,2,x);
X(2,2) = XTX(2,2,x);
XY(1) = picture_y(1,x);
XY(2) = picture_y(2,x);
% Here we utilise the fact that A\B is faster than inv(A)*B
% We also use the fact that (A*B)*C = A*(B*C) to speed things up
B = X\XY;
est(x) = B(1);
end
est = reshape(est,[xdim,ydim]);
toc % end timing
Before: Elapsed time is 4.56 seconds.
After: Elapsed time is 2.24 seconds.
This is a speed up of about a factor of 2. This code should be extensible to X being any dimensions you want. For instance, in the case where X = [1 x x^2], you would change picture_y to the following
picture_y = [sum(Y);sum(Y.*picture);sum(Y.*picture.^2)];
and change XTX to
XTX = cat(2,ones(n_timepoints,1,size(picture,3)),picture,picture.^2);
You would also change a lot of 2s to 3s in the code, and add XY(3) = picture_y(3,x) to the loop. It should be fairly straight-forward, I believe.
Results
I sped up your original version, since your edit 3 was actually not working (and also does something different).
So, on my PC:
Your (original) version: 8.428473 seconds.
My obfuscated one-liner given below: 0.964589 seconds.
First, for no other reason than to impress, I'll give it as I wrote it:
%%// Some example data
xdim = 500;
ydim = 500;
n_timepoints = 10; % for example
estimate = zeros(xdim,ydim); %// initialization with explicit size
picture = randn(xdim,ydim,n_timepoints);
%%// Your original solution
%// (slightly altered to make my version's results agree with yours)
tic
Y = randn(n_timepoints,xdim*ydim);
ii = 1;
for x = 1:xdim
for y = 1:ydim
X = squeeze(picture(x,y,:)); %// or similar creation of X matrix
B = (X'*X)^(-1)*X' * Y(:,ii);
ii = ii+1;
%// sometimes you keep everything and do
%// estimate(x,y,:) = B(:);
%// sometimes just the first element is important and you do
estimate(x,y) = B(1);
end
end
toc
%%// My version
tic
%// UNLEASH THE FURY!!
estimate2 = reshape(sparse(1:xdim*ydim*n_timepoints, ...
builtin('_paren', ones(n_timepoints,1)*(1:xdim*ydim),:), ...
builtin('_paren', permute(picture, [3 2 1]),:))\Y(:), ydim,xdim).'; %'
toc
%%// Check for equality
max(abs(estimate(:)-estimate2(:))) % (always less than ~1e-14)
Breakdown
First, here's the version that you should actually use:
%// Construct sparse block-diagonal matrix
%// (Type "help sparse" for more information)
N = xdim*ydim; %// number of columns
M = N*n_timepoints; %// number of rows
ii = 1:N;
jj = ones(n_timepoints,1)*(1:N);
s = permute(picture, [3 2 1]);
X = sparse(ii,jj(:), s(:));
%// Compute ALL the estimates at once
estimates = X\Y(:);
%// You loop through the *second* dimension first, so to make everything
%// agree, we have to extract elements in the "wrong" order, and transpose:
estimate2 = reshape(estimates, ydim,xdim).'; %'
Here's an example of what picture and the corresponding matrix X looks like for xdim = ydim = n_timepoints = 2:
>> clc, picture, full(X)
picture(:,:,1) =
-0.5643 -2.0504
-0.1656 0.4497
picture(:,:,2) =
0.6397 0.7782
0.5830 -0.3138
ans =
-0.5643 0 0 0
0.6397 0 0 0
0 -2.0504 0 0
0 0.7782 0 0
0 0 -0.1656 0
0 0 0.5830 0
0 0 0 0.4497
0 0 0 -0.3138
You can see why sparse is necessary -- it's mostly zeros, but will grow large quickly. The full matrix would quickly consume all your RAM, while the sparse one will not consume much more than the original picture matrix does.
With this matrix X, the new problem
X·b = Y
now contains all the problems
X1 · b1 = Y1
X2 · b2 = Y2
...
where
b = [b1; b2; b3; ...]
Y = [Y1; Y2; Y3; ...]
so, the single command
X\Y
will solve all your systems at once.
This offloads all the hard work to a set of highly specialized, compiled to machine-specific code, optimized-in-every-way algorithms, rather than the interpreted, generic, always-two-steps-away from the hardware loops in MATLAB.
It should be straightforward to convert this to a version where X is a matrix; you'll end up with something like what blkdiag does, which can also be used by mldivide in exactly the same way as above.
I had a wee play around with an idea, and I decided to stick it as a separate answer, as its a completely different approach to my other idea, and I don't actually condone what I'm about to do. I think this is the fastest approach so far:
Orignal (unoptimised): 13.507176 seconds.
Fast Cholesky-decomposition method: 0.424464 seconds
First, we've got a function to quickly do the X'*X multiplication. We can speed things up here because the result will always be symmetric.
function XX = sym_mult(X)
n = size(X,2);
XX = zeros(n,n,size(X,3));
for i=1:n
for j=i:n
XX(i,j,:) = sum(X(:,i,:).*X(:,j,:));
if i~=j
XX(j,i,:) = XX(i,j,:);
end
end
end
The we have a function to do LDL Cholesky decomposition of a 3D matrix (we can do this because the (X'*X) matrix will always be symmetric) and then do forward and backwards substitution to solve the LDL inversion equation
function Y = fast_chol(X,XY)
n=size(X,2);
L = zeros(n,n,size(X,3));
D = zeros(n,n,size(X,3));
B = zeros(n,1,size(X,3));
Y = zeros(n,1,size(X,3));
% These loops compute the LDL decomposition of the 3D matrix
for i=1:n
D(i,i,:) = X(i,i,:);
L(i,i,:) = 1;
for j=1:i-1
L(i,j,:) = X(i,j,:);
for k=1:(j-1)
L(i,j,:) = L(i,j,:) - L(i,k,:).*L(j,k,:).*D(k,k,:);
end
D(i,j,:) = L(i,j,:);
L(i,j,:) = L(i,j,:)./D(j,j,:);
if i~=j
D(i,i,:) = D(i,i,:) - L(i,j,:).^2.*D(j,j,:);
end
end
end
for i=1:n
B(i,1,:) = XY(i,:);
for j=1:(i-1)
B(i,1,:) = B(i,1,:)-D(i,j,:).*B(j,1,:);
end
B(i,1,:) = B(i,1,:)./D(i,i,:);
end
for i=n:-1:1
Y(i,1,:) = B(i,1,:);
for j=n:-1:(i+1)
Y(i,1,:) = Y(i,1,:)-L(j,i,:).*Y(j,1,:);
end
end
Finally, we have the main script which calls all of this
xdim = 500;
ydim = 500;
n_timepoints = 10; % for example
Y = randn(10,xdim*ydim);
picture = randn(xdim,ydim,n_timepoints); % 500x500x10
tic % start timing
picture = reshape(pr, [xdim*ydim,n_timepoints])';
% Here we precompute the (XT*Y) calculation
picture_y = [sum(Y);sum(Y.*picture)];
% initialize
est2 = zeros(size(picture,2),1);
picture = permute(picture,[1,3,2]);
% Now we calculate the X'*X matrix
XTX = cat(2,ones(n_timepoints,1,size(picture,3)),picture);
XTX = sym_mult(XTX);
% Call our fast Cholesky decomposition routine
B = fast_chol(XTX,picture_y);
est2 = B(1,:);
est2 = reshape(est2,[xdim,ydim]);
toc
Again, this should work equally well for a Nx3 X matrix, or however big you want.
I use octave, thus I can't say anything about the resulting performance in Matlab, but would expect this code to be slightly faster:
pictureT=picture'
est=arrayfun(#(x)( (pictureT(x,:)*picture(:,x))^-1*pictureT(x,:)*randn(n_ti
mepoints,1)),1:size(picture,2));