% [u,err,x,t] = heat_cn(t_0,t_f,M,N)
%
% this solves the heat equation u_t = u_xx with initial data u_0 =
% cos(x) with periodic boundary conditions using finite-differences in
% space and Crank-Nicolson time-stepping.  t_0 is the initial time, t_f is the
% final time, N is the number of mesh-points, and M is the number of
% time steps.  err is the error. 

function [u,err,x,t] = heat_cn(t_0,t_f,M,N)

% define the mesh in space
dx = 2*pi/(N-1);
x = 0:dx:2*pi;
x = x';

% define the mesh in time
dt = (t_f-t_0)/M;
t = t_0:dt:t_f;

% define the ratio r
r = dt/dx^2 

for i=1:N
  u(i,1) = cos(x(i));
end 
err(:,1) = u(:,1) - exp(-t(1))*cos(x);

% for internal points, have
%    u_new(j) = u_old(j) + r/2*(u_old(j+1)-2*u_old(j)+u_old(j-1))
% for the two end-points, have
%    u_new(1) = u_old(1) + r/2*(u_old(2)-2*u_old(1)+u_old(N-1))     
%    u_new(N) = u_old(N) + r/2*(u_old(2)-2*u_old(N)+u_old(N-1))     
% clearly the endpoints are redundant: u(1)= u(N) at all times.  I just
% kept them around for plotting convenience.  

% define the matrix A which has to be inverted at every time-step.
%   u_new(1) - u_old(1) = dt/h^2 (u_new(2)-2*u_new(1)+u_new(N-1)
%   u_new(i) - u_old(i) = dt/h^2 (u_new(i+1)-2*u_new(i)+u_new(i-1)
%   u_new(N) - u_old(N) = dt/h^2 (u_new(2)-2*u_new(N)+u_new(N-1)
A = zeros(N-1,N-1);
A(1,1) = 1+r;
A(1,2) = -r/2;
A(1,N-1) = -r/2;
for i=2:N-2
  A(i,i-1) = -r/2;
  A(i,i) = 1+r;
  A(i,i+1) = -r/2;
end
A(N-1,N-1) = 1+r;
A(N-1,N-2) = -r/2;
A(N-1,1) = -r/2;

for j=1:M
  RHS(1) = u(1,j) + r/2*(u(2,j)-2*u(1,j)+u(N-1,j));
  for i=2:N-1
    RHS(i) = u(i,j) + r/2*(u(i+1,j)-2*u(i,j)+u(i-1,j));
  end
  u(1:N-1,j+1) = periodic_tridiag(A,RHS);  
  % impose periodicity
  u(N,j+1) = u(1,j+1);
  err(:,j+1) = u(:,j+1) - exp(-t(j+1))*cos(x);
end

