function [ data, g, data0, errs ] = ...
         frontTestRK(source, s, p, cfl, gridSize, derivFunc, shape, add_normal)
% frontTestRK: demonstrate various RK integrators.
%
%   [ data, g, data0, errs ] = ...
%        frontTestRK(source, s, p, cfl, gridSize, derivFunc, shape, add_normal)
%  
% This file was modified from convectionDemo to test odeCFLab and odeCFLsp
% on a relatively simple example.  There are many options in the function
% which can only be modifed by changing the file.
%
% Input Parameters:
%
%   source: String.  Source of RK scheme.  Two types of options:
%
%     'odeCFLn': In this case, the toolbox's standard odeCFLn routine is
%     used, where n \in { 1, 2, 3 } and is specified by the s parameter
%     below.  In this case, the p parameter is ignored.  Default.
%
%     anything else: In this case the source string is passed directly on
%     to odeCFLsp.
%
%   s: Integer.  Number of steps in RK scheme.  Either chooses the 'n' in
%   odeCFLn (if source == 'odeCFLn) or is passed directly to odeCFLsp
%   (otherwise).  Default = 2.
%
%   p: Integer.  Order of RK scheme.  Either ignored or passed directly to
%   odeCFLsp.  Default = 2.
%
%   cfl: Double.  CFL number to use.  For appropriate choices, see the
%   table in odeCFLsp's help entry.  Optional.  Default = 0.95.
%
%   gridSize: Integer.  Number of grid cells in each dimension.
%   Optional.  Default = 101.
%
%   derivFunc: Function handle.  A spatial first derivative approximation
%   routine.  Optional.  Default = @upwindFirstWENO5.
%
%   shape: String.  Specifies what shape to use for initial conditions.
%   Also determines the manner in which error is computed.  Currently
%   supported options: 'rectangle', 'circle', 'slotted' (Zalesak's slotted
%   disk).  Optional.  Default = 'rectangle'.
%
%   add_normal: Boolean.  Specifies whether the flow field should be purely
%   rotational (add_normal = 0) or should include some motion in the normal
%   direction so that the shape grows and then shrinks (add_normal = 1).
%   Optional.  Default = 0 (purely rotational flow).  Note that motion in
%   the normal direction will destroy the slot in Zalesak's disk, so
%   add_normal = 1 is not recommended for shape = 'slotted'.
%
% Output Parameters:
%
%   data: Implicit surface function at t_max.
%
%   g: Grid structure on which data was computed.
%
%   data0: Implicit surface function at t_0.
%
%   errs: Structure containing error information.  Fields are '.one'
%   (average error), '.two' (RMS error) and '.inf' (maximum error).  Error
%   is computed at the nodes which should be precisely on the front (for
%   rectangular initial conditions) or for nodes near the front (circular
%   initial conditions).  In the former case, the error at a node is equal
%   to the absolute value of the level set function at that node (if the
%   node should be at the front, the level set function should be zero at
%   that node), otherwise the node value is compared to the initial
%   condition (which is assumed to be the correct value at the final time as
%   well).  For the former test to work correctly, the number of grid nodes
%   must be chosen so that there are node precisely on the front at the
%   initial time.

% 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/4/07
% Added 'slotted' shape option and add_normal parameter, Ian Mitchell, 6/4/07.

%---------------------------------------------------------------------------
% You will see many executable lines that are commented out.
%   These are included to show some of the options available; modify
%   the commenting to modify the behavior.
  
%---------------------------------------------------------------------------
% Make sure we can see the kernel m-files.
run('../../addPathToKernel');

%---------------------------------------------------------------------------
% Integration parameters.
tMax = 1.5;                  % End time.
plotSteps = 7;               % How many intermediate plots to produce?
t0 = 0;                      % Start time.
singleStep = 0;              % Plot at each timestep (overrides tPlot).

% Period at which intermediate plots should be produced.
tPlot = (tMax - t0) / (plotSteps - 1);

% How close (relative) do we need to get to tMax to be considered finished?
small = 100 * eps;

%---------------------------------------------------------------------------
% What level set should we view?
level = 0;

% Pause after each plot?
pauseAfterPlot = 0;

% Delete previous plot before showing next?
deleteLastPlot = 0;

% Plot in separate subplots (set deleteLastPlot = 0 in this case)?
useSubplots = 1;

% What kind of display?
displayType = 'contour';
%displayType = 'surf';

% How many steps of reinitialization after each timestep?
%   Set to zero for no reinitialization (which seems to be insufficient).
reinitSteps = 0;
% If you use reinit, what degree of accuracy is expected?
reinitAccuracy = 'high';

%---------------------------------------------------------------------------
% Handle optional parameters.
if(nargin < 1)
  source = 'odeCFLn';
end

if(nargin < 2)
  s = 2;
end

if(nargin < 3)
  p = 2;
end

if(nargin < 4)
  cfl = 0.95;
end

if(nargin < 5)
  gridSize = 101;
end

if(nargin < 6)
  derivFunc = @upwindFirstWENO5;
end

if(nargin < 7)
  shape = 'rectangle';
end

if(nargin < 8)
  add_normal = 0;
end

%---------------------------------------------------------------------------
% Create the grid.
g.dim = 2;
g.min = -1;
g.N = gridSize;
g.max = +1;
g.bdry = @addGhostExtrapolate;
g = processGrid(g);

%---------------------------------------------------------------------------
% Create initial conditions.
center = [ 0.0; 0.0 ];
widths = [ 0.8; 0.4 ];
radius = 0.4;
switch(shape)
 case 'rectangle'
  data = shapeRectangleByCenter(g, center, widths);
 case 'circle'
  data = shapeSphere(g, center, radius);
 case 'slotted'
  % We need to make the slotted disk vertically symmetric so that the
  % error tests will work correctly.
  center = [ 0.0; 0.5 ];
  radius = 0.3;
  slotted_data = shapeZalesakDisk(g, center, radius);
  data = shapeUnion(slotted_data, flipdim(slotted_data, 2));
 otherwise
  error('Unknown shape: %s', shape);
end
data0 = data;

%---------------------------------------------------------------------------
% Rotational convective flow.
rotationV = cellMatrixMultiply(pi, { -g.xs{2}, g.xs{1} });
convectionData.velocity = rotationV;

% Set up spatial approximation scheme.
convectionFunc = @termConvection;
convectionData.grid = g;
convectionData.derivFunc = derivFunc;

%---------------------------------------------------------------------------
% Set up motion in the normal direction.

% We want to move outward by 0.4 units by the halfway point.  We choose
% 0.4 units to make sure that the rectangle never gets too close to the
% computational boundary.
outward_speed = 0.4 / (2 * tMax / 3);

normalFunc = @termNormal;
normalData.grid = g;
normalData.speed = @switchValue;
normalData.tSwitch = 2 * tMax / 3;
normalData.one = +outward_speed;
normalData.two = -2 * outward_speed;
normalData.derivFunc = derivFunc;

%---------------------------------------------------------------------------
% Combine components of motion if necessary
if add_normal
  schemeFunc = @termSum;
  schemeData.innerFunc = { normalFunc; convectionFunc };
  schemeData.innerData = { normalData; convectionData };
else
  schemeFunc = convectionFunc;
  schemeData = convectionData;
end
  
%---------------------------------------------------------------------------
% Set up time approximation scheme.
if strcmp(lower(source), 'odecfln')
  use_odeCFLn = 1;
  if((s < 1) || (s > 3))
    error('odeCFLn option only works with s \in { 1, 2, 3 }');
  end
  integratorFunc = str2func([ 'odeCFL' num2str(s) ]);

else
  use_odeCFLn = 0;
  
  % Pass everything along to odeCFLsp for parsing.
  integratorFunc = @odeCFLsp;

  integratorData.source = source;
  integratorData.s = s;
  integratorData.p = p;
end

% Some integration parameters apply to any integrator.
integratorOptions = odeCFLset('factorCFL', cfl, 'stats', 'on');

if(singleStep)
  integratorOptions = odeCFLset(integratorOptions, 'singleStep', 'on');
end

%---------------------------------------------------------------------------
% Set up reinitialization if necessary.
if(reinitSteps)
  % Register reinitialization postTimestep routine.
  integratorOptions = odeCFLset(integratorOptions, ...
                                'PostTimestep', @postTimestepReinit);
  
  % Add necessary components to schemeData structure.
  schemeData.reinitAccuracy = reinitAccuracy;
  schemeData.reinitSteps = reinitSteps;
  
end

%---------------------------------------------------------------------------
% Initialize Display
f = figure;

% Set up subplot parameters if necessary.
if(useSubplots)
  if(plotSteps == 7)
    % Special case to make a nice picture.
    rows = 2;
    cols = 4;
  else
    rows = ceil(sqrt(plotSteps));
    cols = ceil(plotSteps / rows);
  end
  plotNum = 1;
  subplot(rows, cols, plotNum);
end

h = visualizeLevelSet(g, data, displayType, level, [ 't = ' num2str(t0) ]);

hold on;
if(g.dim > 1)
  axis(g.axis);
  daspect([ 1 1 1 ]);
end

%---------------------------------------------------------------------------
% Loop until tMax (subject to a little roundoff).
tNow = t0;
startTime = cputime;
while(tMax - tNow > small * tMax)

  % Reshape data array into column vector for ode solver call.
  y0 = data(:);

  % How far to step?
  tSpan = [ tNow, min(tMax, tNow + tPlot) ];
  
  % Take a timestep.
  if use_odeCFLn
    % Need to distinguish the two cases, since odeCFLsp has an additional
    % parameter.
    [ t y ] = feval(integratorFunc, schemeFunc, tSpan, y0,...
                    integratorOptions, schemeData);
  else    
    [ t y ] = feval(integratorFunc, schemeFunc, tSpan, y0,...
                    integratorOptions, schemeData, integratorData);
  end
  tNow = t(end);

  % Get back the correctly shaped data array
  data = reshape(y, g.shape);

  if(pauseAfterPlot)
    % Wait for last plot to be digested.
    pause;
  end

  % Get correct figure, and remember its current view.
  figure(f);
  figureView = view;

  % Delete last visualization if necessary.
  if(deleteLastPlot)
    delete(h);
  end

  % Move to next subplot if necessary.
  if(useSubplots)
    plotNum = plotNum + 1;
    subplot(rows, cols, plotNum);
  end

  % Create new visualization.
  h = visualizeLevelSet(g, data, displayType, level, [ 't = ' num2str(tNow) ]);
  daspect([ 1 1 1 ]);
  
  % Restore view.
  view(figureView);
  
end

%---------------------------------------------------------------------------
% Turn off the TeX interpreter so that underscore is not misinterpreted
% as a subscript.
title([ 'source = ' source ', s = ' num2str(s) ... 
        ', p = ' num2str(p), ', N = ' num2str(gridSize) ], ...
      'Interpreter', 'none');

% Let the user know how long it all took.
endTime = cputime;
fprintf('Total execution time %g seconds', endTime - startTime);

%---------------------------------------------------------------------------
% Compute the error statistics.

% The nodes on which we will test error depend on the type of initial
% condition: for rectangles we can test only those nodes exactly on the
% front; otherwise we test nodes near the front.
switch(shape)
 case 'rectangle'
  near_front_distance = 0.25 * min(g.dx);
 case { 'circle', 'slotted' }
  near_front_distance = 1.5 * min(g.dx); 
 otherwise
  error('Unknown shape: %s', shape);
end

% For an even number of quarter turns, use data0 directly specifies distance
% from the front as well as correct solution at the final time.  For an odd
% number of quarter turns, flip data0.  Note that this test is not quite
% right for the unsymmetric slotted disk.
if(abs(tMax - round(tMax)) < sqrt(eps))
  % Even number of quarter turns.
  exact_solution = data0;
elseif(abs(2 * tMax - round(2 * tMax)) < sqrt(eps))
  % Odd number of quarter turns.
  exact_solution = data0';
else
  % Not an integer number of quarter turns.
  if(nargout > 3)
    error('Unable to evaluate error for specified tMax: %f', tMax);
  end
end

at_front = abs(exact_solution) < near_front_distance;
num_at_front = sum(at_front(:));
node_error = at_front .* abs(data - exact_solution);

errs.one = sum(node_error(:)) / num_at_front;
errs.two = sqrt(sum(node_error(:).^2) / num_at_front);
errs.inf = max(node_error(:));



%---------------------------------------------------------------------------
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%---------------------------------------------------------------------------
function out = switchValue(t, data, schemeData)
% switchValue: switches between two values.
%
%  out = switchValue(t, data, schemeData)
%
% Returns a constant value:
%           one     for t <= tSwitch;
%           two     for t >  tSwitch.
%
% By setting one and two correctly, this function can implement
%   the velocityFunc prototype for termConvection;
%   the scalarGridFunc prototype for termNormal, termCurvature and others;
%   and possibly some other prototypes...
%
% Parameters:
%   t            Current time.
%   data         Level set function.
%   schemeData   Structure (see below).
%
%   out          Either schemeData.one or schemeData.two.
%
% schemeData is a structure containing data specific to this type of 
%   term approximation.  For this function it contains the field(s)
%
%   .one         The value to return for t <= tSwitch.
%   .two         The value to return for t >  tSwitch.
%   .tSwitch     The time at which the switch between flow fields occurs.
%
% schemeData may contain other fields.

  checkStructureFields(schemeData, 'one', 'two', 'tSwitch');
  
  if(t <= schemeData.tSwitch)
    out = schemeData.one;
  else
    out = schemeData.two;
  end
