function [ data, g, data0 ] = ...
    fatteningDemo(which_approx, stencil_size, which_grid, accuracy)
% fatteningDemo: demonstrate motion by mean curvature on fattening example
%
%   [ data, g, data0 ] = fatteningDemo(which_approx, stencil_size, accuracy)
%
% Demonstrates the "fattening" example from figures 2 & 3 of 
%
%    Adam M. Oberman, "A Convergent Monotone Difference Scheme for Motion of
%    Level Sets by Mean Curvature," Numerische Mathematik, 99: 365-379
%    (2004), DOI: 10.1007/s00211-004-0566-1.
%
% The initial conditions are phi = |x| - |y|, and the function develops
% flat regions (where the value is identically zero) at the origin (and
% corners of the domain for periodic BC).
%  
% This function was originally designed as a script file, so most of the
% options can only be modified in the file.
%
% For example, edit the file to change the grid dimension, boundary
% conditions, flow field parameters, etc.
%
% Input Parameters:
%
%   which_approx: String.  Specifies method by which the mean curvature
%   term (one-Laplacian) is approximated.  Choices are:
%
%     'median': Use Oberman's approach.  Default.
%     'centered': Use a typical centered difference approximation.
%
%   stencil_size: Positive integer.  Controls the size of the stencil
%   when the median curvature approximation is chosen.  Ignored for the
%   centered curvature approximation.  Default is 3.
%
%   which_grid: String.  Specifies which grid should be tested.  Current
%   choices are '1:1' (grid cells are squares, default), '2:1', '3:2' or
%   '4:3' (grid cells are rectangles).  Warning: for the median
%   approximation, small stencil size (eg 1) can lead to obvious
%   distortion on rectangular grids.
%
%   accuracy: String.  Controls the order of temporal approximations.  Note
%   that the spatial approximation is always second order. Choices are:
%  
%     'low': Use odeCFL1 (Forward Euler).  Default.
%     'medium': Use odeCFL2.
%     'high': Use odeCFL3.
%
% Output Parameters:
%
%   data: Implicit surface function at t_max.
%
%   g: Grid structure on which data was computed.
%
%   data0: Implicit surface function at t_0.

% 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/21/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');

%---------------------------------------------------------------------------
% Handle default input arguments.
if(nargin < 1)
  which_approx = 'median';
end

if(nargin < 2)
  stencil_size = 3;
end

if(nargin < 3)
  which_grid = '1:1';
end

if(nargin < 4)
  accuracy = 'low';
end

%---------------------------------------------------------------------------
% Curvature speed parameter.
bValue = 1;

% For future testing of 3D mean curvature approximation.
which_dim = 2;

%---------------------------------------------------------------------------
% Integration parameters.
tMax = 0.045;                % End time.
plotSteps = 4;               % 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;

%---------------------------------------------------------------------------
% Visualization parameters can be set in the subfunction
% visualizeFattening below.

% 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;

%---------------------------------------------------------------------------
% Create the grid.
base_dx = 1/100;

g.dim = which_dim;
switch(which_grid)
 case '1:1'
  g.dx = base_dx;
    
 case '2:1'
  g.dx = base_dx * [ 1/2; 1 ];
  
 case '3:2'
  g.dx = base_dx * [ 2/3; 1 ];
 
 case '4:3'
  g.dx = base_dx * [ 3/4; 1 ];
    
 otherwise
  error('Unrecognized which_grid: %s', which_grid);
end

g.min = -1;
g.max = (1 - g.dx);
g.bdry = @addGhostPeriodic;
g = processGrid(g);

%---------------------------------------------------------------------------
% Create initial conditions.  In Oberman, phi(0,x) = |x| - |y|.
switch(which_dim)
 case 2
  % Oberman's suggestion.
  data = abs(g.xs{1}) - abs(g.xs{2});

  % Rotated version.
  data = abs(g.xs{1} + g.xs{2}) - abs(g.xs{1} - g.xs{2});
 
 case 3
  % Whatever rotation of the initial data is chosen, you need to modify
  % the slicing angle in visualizeFattening below.

  % In dimension 3, the "y" dimension is a combination of y and z.
  %data = abs(g.xs{1}) - (1 / sqrt(2)) * abs(g.xs{2} + g.xs{3});

  % In dimension 3, just ignore the z direction.
  data = abs(g.xs{1}) - abs(g.xs{2});
 
 otherwise
  error('Invalid which_dim parameter: %d', which_dim);
end

data0 = data;

%---------------------------------------------------------------------------
% Set up motion by mean curvature with constant coefficient.

% Some components of the schemeData structure are the same for either
% approximation.
schemeData.grid = g;
schemeData.b = bValue;

switch(which_approx)
 case 'median'
  schemeFunc = @termCurvatureByMedian;
  schemeData.stencil_size = stencil_size;
  schemeData.stencil_shape = 'circle';
  
 case 'centered'
  schemeFunc = @termCurvature;
  schemeData.curvatureFunc = @curvatureSecond;

 otherwise
  error('Unknown which_approx: %s', which_approx);
end
  
%---------------------------------------------------------------------------
% Set up time approximation scheme.
integratorOptions = odeCFLset('factorCFL', 0.9, 'stats', 'on');

% Choose approximations at appropriate level of accuracy.
switch(accuracy)
 case 'low'
  integratorFunc = @odeCFL1;
 case 'medium'
  integratorFunc = @odeCFL2;
 case 'high'
  integratorFunc = @odeCFL3;
 otherwise
  error('Unknown accuracy level %s', accuracy);
end

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

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

% Set up subplot parameters if necessary.
if(useSubplots)
  rows = ceil(sqrt(plotSteps));
  cols = ceil(plotSteps / rows);
  plotNum = 1;
  subplot(rows, cols, plotNum);
end

  h = visualizeFattening(g, data, 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.
  [ t y ] = feval(integratorFunc, schemeFunc, tSpan, y0,...
                  integratorOptions, schemeData);
  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 = visualizeFattening(g, data, tNow);

  % Restore view.
  view(figureView);
  
end

endTime = cputime;
fprintf('Total execution time %g seconds', endTime - startTime);


%---------------------------------------------------------------------------
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%---------------------------------------------------------------------------
function h = visualizeFattening(g, data, t)
%
%
%
  
  % We want to see the boundaries of the fattening region.
  level = [ -0.02; +0.02 ];
  
  switch(g.dim)
   case 2
    % Copied from visualizeLevelSet.
    [ garbage, h ] = contour(g.xs{1}, g.xs{2}, data, level, 'b');
    axis square;  axis manual;

   case 3

    % Storage for the 3D slices.  Keep it persistent so that we don't
    % have to recalculate every time.
    persistent xd yd zd
    if isempty(xd)
      % Set up the slicing plane for 3D data.  This is modified from the
      % technique explained in the slice help entry.
      figure;
      hsp = surf(linspace(-1, +1, 50), linspace(-1, +1, 50), zeros(50));
      % Undocumented, but for some reason rotate's last argument is an
      % angle in DEGREES, not radians.
      rotate(hsp, [ +1, 0, 0 ], 0);
      xd = get(hsp, 'XData');
      yd = get(hsp, 'YData');
      zd = get(hsp, 'ZData');
      close;
    end

    % It sure would be nice to use slice or contourslice here.  Unfortunately,
    % stupid Matlab uses interp3 (requires meshgrid) which is incompatible
    % with interpn (requires ndgrid).  Modification of slice to use interpn
    % was successful, but not contourslice for some reason.  Either way,
    % I can't redistribute modified Matlab routines, so manual
    % interpolation is the next best thing...
    %
    % Interpolate data values on to slicing plane, and then plot contours
    % on that plane.
    vd = interpn(g.xs{:}, data, xd, yd, zd);
    [ garbage, h ] = contour(xd, yd, vd, level, 'b');
    
   otherwise
    error('Cannot visualize in dimension %d', g.dim);
  end
  
  title([ 't = ' num2str(t) ]);
  
  drawnow;
  
