% [u,x,t,kk,amp,M,H] = implicit_schroedinger(t_0,t_f,dt,NPrint,N)
%
% this solves the periodic Schroedinger equation 
%
%         i u_t + u_xx + |u|^2 u = 0
%
% with periodic boundary conditions using spectral derivatives in
% space and implicit time-stepping.  t_0 is the initial time, t_f is the
% final time, dt is the time-step size, N is the number of mesh-points, 
% and M is the number of printouts.  It also returns the power spectrum of the
% solution, where kk is the collection of wave numbers and amp = log(f_k^2)
% is the magnitude of the kth Fourier mode.
%
% Also, it returns M which is the mass \int |u|^2
% and H which is the hamiltonian \int |u_x|^2 - 1/2 |u|^4
%
% N SHOULD BE A POWER OF 2!  
% (It will work no matter what, but will go much faster if N is a power
% of 2.)

function [u,x,t,kk,amp,M,H] = implicit_schroedinger(t_0,t_f,dt,NPrint,N)

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

% total number of steps to be taken:
Ntot = (t_f-t_0)/dt;
% test to see if Ntot is an integer, to see if your choice of dt
% is compatible with your choice of t_0 and t_f
if abs(Ntot - round(Ntot)) > 100000*eps
  disp(' ')
  disp('dt is not consistent with your choice of t_0 and t_f.  Choose another dt!')
  disp(' ')
  break
end
Ntot = round(Ntot);
% number of steps to be taken before a (screen) printout
NRun = Ntot/NPrint;
% test to see if NRun is an integer to see if your choice of dt is
% consistent with NPrint
if abs(NRun - round(NRun)) > 1000*eps
  disp(' ')
  disp('NPrint is not consistent with dt.  Choose another NPrint!')
  disp(' ')
  break
end
NRun = round(NRun);
% define the vector t that is to be printed out...
t = t_0:NRun*dt:t_f;

% choose initial data
%u_now = cos(x) + 3*i*sin(2*x) - 8*exp(i*3*x);
u_now = exp(i*3*x);

% We're computing solutions of
%
%    u_t = i u_xx + i |u|^2 u
%
% With implicit time-stepping, this would be
%
%  u_new - u_now = dt*i*u_new_xx + i*dt*|u_now|^2 u_now
%
%  u_new - i*dt*u_new_xx = u_now + i*dt*|u_now|^2 u_now
%
% That is, we want to solve
%
%      L u_new = RHS
%
% This is especially easily done spectrally since
%
%  (1 + i dt k^2) u_new_hat(k) = RHS_hat(k)
%
% Hence
%
%  u_new_hat(k) = RHS_hat(k)/(1 + i dt k^2) 

% save first printout:
u(:,1) = u_now;
[M(1),H(1)] = conserved(u_now,dx,N);
v = fft(u_now); 
amp(N/2:N-1,1) = log10( abs(v(1:N/2)) + 1.e-16);
amp(1:N/2-1,1) = log10( abs(v(N/2+2:N)) + 1.e-16);

for jj=1:NPrint

  % take NRun time-steps
  for j=1:NRun

    % compute solution with one step of size dt
    u_coarse = tstep(u_now,dt,N);
    % compute solution with two steps of size dt/2
    u_fine = tstep(u_now,dt/2,N);
    u_fine = tstep(u_fine,dt/2,N);
    % use Richardson extrapolation to combine u_coarse and
    % u_fine to get a solution with local truncation
    % error of O(dt^3)...
    u_now = 2*u_fine - u_coarse;

  end

  % save printout
  u(:,jj+1) = u_now;
  [M(jj+1),H(jj+1)] = conserved(u_now,dx,N);
  v = fft(u_now); 
  amp(N/2:N-1,jj+1) = log10( abs(v(1:N/2)) + 1.e-16);
  amp(1:N/2-1,jj+1) = log10( abs(v(N/2+2:N)) + 1.e-16);
end
x(N+1) = 2*pi;
u(N+1,:) = u(1,:);

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
function u_now = tstep(u_now,dt,N);

    % start by computing power spectrum of u_now...
    v = fft(u_now);
    % compute i dt |u_now|^2 u_now
    wrk = i*dt*abs(u_now).^2.*u_now;
    % take its FFT and form the right-hand-side:
    RHS = fft(wrk) + v;

    % we now time-step the PDE at the spectral level
    % by solving L v = RHS...
    ii = 1:N/2;
    k = (ii-1)';
    v(ii) = RHS(ii)./(1 + i*dt*k.^2);
    ii = N/2+2:N;
    k = (ii-N-1)';
    v(ii) = RHS(ii)./(1 + i*dt*k.^2);

    % take the inverse fourier transform.  
    u_now = ifft(v);

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

function [M,H] = conserved(u,dx,N);
%
% use the Trapezoidal rule to compute the mass M and hamiltonian H
%             M = \int |u|^2
%             H = \int |u_x|^2 - 1/2 |u|^4
%

arg = abs(u(1:N)).^2 ;
M = dx*sum(arg);

v = fft(u);
ii = 1:N/2;
k = (ii-1)';
v(ii) = (i*k).*v(ii);
ii = N/2+2:N;
k = (ii-N-1)';
v(ii) = (i*k).*v(ii);
% take the inverse fourier transform.  This gives u_x.
v = ifft(v);

arg = abs(v(1:N)).^2 - 1/2*abs(u(1:N)).^4;
H = dx*sum(arg);

