function [ t, y, schemeData ] = ...
          odeCFLab(schemeFunc, tspan, y0, options, schemeData, integratorData)
% odeCFLab: integrate a CFL constrained ODE (eg a PDE by method of lines).
%
% [ t, y, schemeData ] = ...
%         odeCFLab(schemeFunc, tspan, y0, options, schemeData, integratorData)
%
% Integrates a system forward in time by CFL constrained timesteps using a
% Runge-Kutta scheme described in the alpha-beta format used by:
%
%    C.-W. Shu & S. Osher, "Efficient Implementation of Essentially
%    Non-Oscillatory Shock-Capturing Schemes," J. Computational Physics,
%    v.77, pp. 439-471 (1988).
%
% and many other papers since then, such as
%
%    R. J. Spiteri & S. J. Ruuth, "A New Class of Optimal High-Order
%    Strong-Stability-Preserving Time Discretization Methods," SIAM
%    J. Numerical Analysis, v.40, n.2, pp. 469-491 (2002).
%
% For an ODE du/dt = L(u) resulting from the method of lines after the
% spatial discretization, the format for an m stage scheme is (where the
% index variables have been twiddled slightly):
%
%     u^{(1)} = u(t_n)
%     u^{(j)} = \sum_{k=1}^{j-1} (\alpha_{j-1,k} u^{(k)}            j=2...m+1 
%                               + \Delta t \beta_{j-1,k} L(u^{(k)}))
%     u(t_{n+1}) = u^{(m+1)}
%
% Note that \alpha_{j,k} >= 0.  For now we will also require that
% \beta_{j,k} >= 0, since the toolbox is not currently designed to offer the
% backward timestep operator (usually written \tilde L(u) in the
% literature) which must be used when \beta_{j,k} < 0.  This restriction
% has the side effect of requiring that \sum_k alpha_{j,k} = 1.
%
%
% Input Parameters:
%
%   schemeFunc: Function handle to a CFL constrained ODE system (typically an
%   approximation to an HJ term, see below).
%
%   tspan: Range of time over which to integrate (see below).
%
%   y0: Initial condition vector (typically the data array in vector form).
%
%   options: An option structure generated by odeCFLset (use [] as a
%   placeholder if necessary).
%
%   schemeData: Structure passed through to schemeFunc.
%
%   integratorData: Structure of data used by this integration routine.
%
% Output Parameters:
%
%   t: Output time(s) (see below).
%    
%   y: Output state (see below).
%
%   schemeData: Output version of schemeData (see below).
%
% integratorData is a structure containing data specific to this time
% integrator.  For this function it contains the field(s)
%
%   .alpha: A (lower triangular) square matrix containing the alpha
%   coefficients for the RK scheme.  All coefficients must be non-negative,
%   and because of the non-negativity restriction on beta, rows of this matrix
%   must sum to 1.
%
%   .beta: A (lower triangular) square matrix containing the beta coefficients
%   for the RK scheme.  All coefficients must be non-negative.  The matrix
%   must be the same size as the .alpha matrix.
%
% It may contain additional fields at the user's discretion.
%
%
% A CFL constrained ODE system is described by a function with prototype
%
%        [ ydot, stepBound ] = schemeFunc(t, y, schemeData)
%
% where t is the current time, y the current state vector and schemeData is
% passed directly through.  The output stepBound is the maximum allowed time
% step that will be taken by this function (typically the option parameter
% factorCFL will choose a smaller step size).
%
% The time interval tspan may be given as 
%   1) A two entry vector [ t0 tf ], in which case the output will 
%      be scalar t = tf and a row vector y = y(tf).
%
% Note that using this routine for integrating HJ PDEs will usually require
% that the data array be turned into a vector before the call and reshaped
% into an array after the call.  Option (2) for tspan should not be used in
% this case because of the excessive memory requirements for storing solutions
% at multiple timesteps.
%
% The output version of schemeData will normally be identical to the input
% version, and therefore can be ignored.  However, if a PostTimestep routine
% is used (see odeCFLset) then schemeData may be modified during integration,
% and the version of schemeData at tf is returned in this output argument.

% Copyright 2007 Ian M. Mitchell (mitchell@cs.ubc.ca).
% This software is used, copied and distributed under the licensing agreement
% contained in the file LICENSE in the top directory of the distribution.
%
% Ian Mitchell, 1/3/07.

% This routine is somewhat of an experiment as to whether it is worth
% continuing to try to keep the odeCFL* routines similar in prototype to
% Matlab's ode* routines.  While it makes it easier for users to pick up the
% protocol, it has some weird side effects (described in the docs) and has
% not yet seemed to have any concrete benefit (ie direct use of Matlab's
% routines for time integration).
%
% More importantly, Matlab's routines are not so easy to modify -- changing
% the option set requires changing the odeset & odeget routines, which has
% side effects on all ode* routines.  Since the time integrators for
% ToolboxLS are still being experimented with, it might make more sense to
% adopt the schemeData structure style, which has proved quite flexible for
% experimenting with various spatial terms.

  %---------------------------------------------------------------------------
  % How close (relative) do we need to be to the final time?
  small = 100 * eps;
  
  %---------------------------------------------------------------------------
  % Make sure we have the default options settings
  if((nargin < 4) | isempty(options))
    options = odeCFLset;
  end

  %---------------------------------------------------------------------------
  % This routine does not work without alpha and beta coefficients.
  % Check to make sure that they are well posed.
  if(nargin < 6)
    error('integratorData parameter is required');
  end

  checkStructureFields(integratorData, 'alpha', 'beta');
  alpha = integratorData.alpha;
  beta = integratorData.beta;

  if((ndims(alpha) ~= 2) || (size(alpha, 1) ~= size(alpha, 2)))
    error('integratorData.alpha must be a square matrix');
  end
  
  if((ndims(beta) ~= 2) || (size(beta, 1) ~= size(beta, 2)))
    error('integratorData.beta must be a square matrix');
  end

  % How many substeps are there in the RK scheme?
  rk_steps = size(alpha, 1);
  if(size(beta, 1) ~= rk_steps)
    error('integratorData.alpha and integratorData.beta must be same size');
  end

  % Check that the constraints on alpha and beta parameters are satisfied.
  if(any(alpha(:) < 0) || any(beta(:) < 0))
    error('integratorData.alpha and integratorData.beta must be non-negative');
  end

  alpha_upper = triu(alpha, +1);
  beta_upper = triu(beta, +1);
  if(any(alpha_upper(:)) || any(beta_upper(:)))
    error([ 'integratorData.alpha and integratorData.beta must be ' ...
            'lower triangular' ])
  end
  
  %---------------------------------------------------------------------------
  % To handle time-dependent systems, we need the times at which the
  % substeps are evaluated.  These are not naturally given in the alpha-beta
  % notation, so we need to do some computation.  Fortunately, these substep
  % times are all offsets from the start of the full step.  Therefore, we
  % can compute them once for all steps.

  % To determine the substep times, we need to convert from alpha-beta
  % notation back to get the c_ik coefficients.  They can be determined
  % recursively (see Spiteri & Ruuth 2002), working backwards.
  c = zeros(size(alpha));
  % Work on each substep.
  for j = 2 : rk_steps;
    for k = j - 1 : -1 : 1;
      c(j,k) = beta(j-1,k) + sum(alpha(j-1, k+1:j-1) .* c(k+1:j-1, k)');
    end
  end

  % Remember that these offsets must be multiplied by the chosen delta t.
  t_offset = sum(c, 2);
  
  %---------------------------------------------------------------------------
  % This routine includes multiple substeps, and the CFL restricted timestep
  % size is chosen on the first substep.  Subsequent substeps may violate
  % CFL slightly; how much should be allowed before generating a warning?

  % This choice allows 20% more than the user specified CFL number.
  % Because many of the alpha-beta described schemes allow CFL numbers
  % greater than unity, there is no additional cap.
  safetyFactorCFL = 1.2 * options.factorCFL;
  
  %---------------------------------------------------------------------------
  % Number of timesteps to be returned.
  numT = length(tspan);
  
  %---------------------------------------------------------------------------
  % If we were asked to integrate forward to a final time.
  if(numT == 2)

    % Is this a vector level set integration?
    if(iscell(y0))
      numY = length(y0);
    
      % We need a cell vector form of schemeFunc.
      if(iscell(schemeFunc))
        schemeFuncCell = schemeFunc;
      else
        [ schemeFuncCell{1:numY} ] = deal(schemeFunc);
      end

    else
      % Set numY, but be careful: ((numY == 1) & iscell(y0)) is possible.
      numY = 1;
    
      % We need a cell vector form of schemeFunc.
      schemeFuncCell = { schemeFunc };

    end

    % Set up the sequence of RK steps.
    t = tspan(1);
    y = y0;
    steps = 0;
    startTime = cputime;

    % Take RK steps until we achieve the final time.
    while(tspan(2) - t >= small * abs(tspan(2)))

      % - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      % Storage for the substeps.
      u = cell(rk_steps + 1, 1);
      % Storage for the substep operator results.
      Lu = cell(rk_steps + 1, 1);

      % - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      % First substep is just the old solution.
      u{1} = y;
      % Apply operator to the old solution.  Time is the beginning of 
      % the substep.
      [ Lu{1}, stepBound, schemeData ] = ...
          computeOperator(schemeFuncCell, t, u{1}, schemeData, numY);

      % Choose CFL bound on timestep, but not beyond the final time, or the 
      % maximum user specified timestep.  Use this fixed timestep for all 
      % substeps.
      deltaT = min([ options.factorCFL * stepBound; ...
                     tspan(2) - t; options.maxStep ]);      
      
      % - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      % Subsequent substeps of the RK scheme.
      for j = 2 : rk_steps + 1
        
        % Construct the next substep.
        for k = 1 : j - 1

          if(alpha(j-1,k) > 0)
            if isempty(u{j})
              u{j} = cellMatrixMultiply(alpha(j-1,k), u{k});
            else
              update = cellMatrixMultiply(alpha(j-1,k), u{k});
              u{j} = cellMatrixAdd(u{j}, update);
            end
          end      % Dealing with alpha_{j-1,k}.
          
          if(beta(j-1,k) > 0)
            
            if isempty(Lu{k})
              % We need to apply the operator to this substep.
              % Determine the substep time.
              t_substep = t + t_offset(k) * deltaT;
              % Apply the operator.
              [ Lu{k}, stepBound, schemeData ] = ...
                  computeOperator(schemeFuncCell, t_substep, ...
                                  u{k}, schemeData, numY);

              % Produce a warning if the substep CFL was too far violated.
              if(deltaT > safetyFactorCFL * stepBound);
                violation = deltaT / stepBound;
                warning([ 'Second substep violated CFL;' ...
                          ' effective number %f' ], violation);
              end
            end
            
            if isempty(u{j})
              u{j} = cellMatrixMultiply(deltaT * beta(j-1,k), Lu{k});
            else
              update = cellMatrixMultiply(deltaT * beta(j-1,k), Lu{k});
              u{j} = cellMatrixAdd(u{j}, update);
            end
          
          end      % Dealing with beta_{j-1,k}.
      
        end        % Construction of substep (for k...).
      
      end          % All substeps (for j...).

      % - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      % If there is a terminal event function registered, we need
      %   to maintain the info from the last timestep.
      if(~isempty(options.terminalEvent))
        yOld = y;
        tOld = t;
      end 

      % - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      % Last substep is the solution at the next step.
      y = u{end};
      t = t + deltaT;
      steps = steps + 1;

      % - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      % If there is one or more post-timestep routines, call them.
      if(~isempty(options.postTimestep))
        [ y, schemeData ] = odeCFLcallPostTimestep(t, y, schemeData, options);
      end

      % If we are in single step mode, then do not repeat.
      if(strcmp(options.singleStep, 'on'))
        break;
      end
    
      % If there is a terminal event function, establish initial sign
      %   of terminal event vector.
      if(~isempty(options.terminalEvent))

        [ eventValue, schemeData ] = ...
               feval(options.terminalEvent, t, y, tOld, yOld, schemeData);

        if((steps > 1) && any(sign(eventValue) ~= sign(eventValueOld)))
          break;
        else
          eventValueOld = eventValue;
        end
      end
    
    end
    
    endTime = cputime;

    if(strcmp(options.stats, 'on'))
      fprintf('\t%d steps in %g seconds from %g to %g\n', ...
              steps, endTime - startTime, tspan(1), t);
    end

  %---------------------------------------------------------------------------
  elseif(numT > 2)
    % If we were asked for the solution at multiple timesteps.
    error('Multiple timesteps are not currently supported.');

  %---------------------------------------------------------------------------
  else
    % Malformed time span.
    error('Parameter tspan must contain at least two entries');
  end



%---------------------------------------------------------------------------
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%---------------------------------------------------------------------------
function [ Lu, stepBound, schemeData ] = ...
                       computeOperator(schemeFuncCell, t, u, schemeData, numY)
% computeOperator: Apply operator L to solution u
%
%   [ Lu, stepBound, schemeData ] = ...
%                      computeOperator(schemeFuncCell, t, u, schemeData, numY)
%
% Returns the vector or cell vector result of applying the operator in
% schemeFuncCell to the current solution in u.
%
% Input Parameters:
%
%   schemeFuncCell: A cell vector containing the schemeFunc term
%   approximation function handles.  Must be a cell vector, even if the
%   solution is not a vector level set function.
%
%   t: Scalar.  Current time.
%
%   u: Vector or cell vector.  Current solution.
%
%   schemeData: Structure or cell vector passed through to the term
%   approximation routine.
%
%   numY: Scalar.  Number of functions in y.  For example, if y is not a
%   vector level set, numY = 1.
%
% Output Parameters:
%
%   Lu: Vector or cell vector (same as u).  Result of applying term
%   approximation routine(s) to the current solution.
%
%   stepBound: Scalar.  The minimum of the stepBounds returned by the
%   term approximation routines in schemeFuncCell.
%
%   schemeData: Output version of schemeData.

  Lu = cell(numY, 1);
  stepBound = zeros(numY, 1);
  
  % Approximate the derivative and collect CFL restriction.
  for i = 1 : numY
    [ Lu{i}, stepBound(i), schemeData ] = ...
                                feval(schemeFuncCell{i}, t, u, schemeData);

    % If this is a vector level set, rotate the lists of vector arguments.
    if(iscell(u))
      u = u([ 2:end, 1 ]);
    end

    if(iscell(schemeData))
      schemeData = schemeData([ 2:end, 1 ]);
    end
  end

  stepBound = min(stepBound);
  if(~iscell(u))
    Lu = Lu{1};
  end
