function [ value, stencil_radius ] = ...
    oneLaplacian(grid, data, stencil_size, stencil_shape, ...
                 interpolate, visualize_stencil)
% oneLaplacian: approximation to \Delta_1 (used in motion by mean curvature)
%
%   [ value, stencil_radius ] = ...
%          oneLaplacian(grid, data, stencil_size, stencil_shape, ...
%                       interpolate, visualize_stencil)
%
% Computes an approximation to
%
%    \Delta_1 u = |\grad u| \grad \dot (\grad u/ \|\grad u\|)
%
% using the median construction in
%
%    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 one-Laplacian is the term that appears in motion by mean curvature.
%
% At this point, the code for circular stencils has only been tested in 2D,
% and definitely will not support grids with more than two different delta x
% parameters.  Square stencils appear generally dubious, because their
% approximation of the curvature may be off by a constant factor as high
% as sqrt(2).
%
% Input Parameters:
%
%   grid: Grid structure (see processGrid.m for details).
%
%   data: Data array.
%
%   stencil_size: Double.  Number of nodes outward to look for the
%   samples to use in the median calculation.  Measured in the direction
%   with largest dx, so the circle on which the median value will be
%   calculated is of radius stencil_size * max(grid.dx).
%
%   stencil_shape: String.  Specifies which stencil shape should be used
%   for finding the nodes from which the median is determined.  Choices
%   are 'circle' or 'square'.  Optional.  Defaults to 'circle'.
%
%   interpolate: Boolean.  For stencil nodes that are not precisely at the
%   desired distance, should we use linear interpolation to estimate the
%   value at the desired distance, or should we just use the node value?
%   Optional.  Defaults to 0 (use node value directly).
%
%   visualize_stencil: Boolean.  Show the stencil as a plot.  Note that the
%   stencil will only be shown once when it is created.  Repeated calls to
%   this routine will not show the stencil again (unless the m-file is
%   cleared) since the stencils are memoized in a persistent variable.
%   Optional.  Defaults to 0.
%
% Output Parameters:
%
%   value: Array same size as data.  Approximation of the one-Laplacian at
%   each node.
%
%   stencil_radius: Double.  Size of the circle on which the one-Laplacian
%   was computed.  Will be stencil_size * max(grid.dx).

% 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/18/07

  %---------------------------------------------------------------------------
  if(nargin < 4)
    stencil_shape = 'circle';
  end

  if(nargin < 5)
    interpolate = 0;
  end

  if(nargin < 6)
    visualize_stencil = 0;
  end
  
  % We still have not demonstrated theoretically that the square stencil is
  % consistent, but we use it enough that the warning was getting
  % annoying.

  %if strcmp(stencil_shape, 'square')
  %  warning('Consistency of square stencil is not verified');
  %end
  
  if(grid.dim > 2)
    warning('Stencil construction in dimension 3+ is not tested');
  end
  
  %---------------------------------------------------------------------------
  % Deal with stencil information.

  % The real stencil radius (distance from the center to the nodes in the
  % stencil) is the size times the largest grid.dx
  stencil_radius = stencil_size * max(grid.dx);

  % It is not always possible to get all stencil nodes at the desired
  % distance.  How much wiggle do we permit?
  stencil_width = 0.5 * max(grid.dx);
  
  % For grids with variable dx, we may need to scale the stencil.
  stencil_ghostcells_per_dim = ceil((stencil_radius+stencil_width) ./ grid.dx);

  %---------------------------------------------------------------------------
  % Add ghost cells to every dimension, sufficient to cover the stencil
  % in each dimension.  Since the stencil size depends on dimension, we
  % don't use addGhostAllDims.
  ghost_data = data;
  for d = 1 : grid.dim
    ghost_data = feval(grid.bdry{d}, ghost_data, d, ...
                       stencil_ghostcells_per_dim(d), grid.bdryData{d});
  end
  
  %---------------------------------------------------------------------------
  % Determine information about the stencil.  This is a slow procedure,
  % so try to save that information from run to run.
  persistent stencil_storage

  % Assue that we need to build a new stencil.
  found_stencil = 0;

  if isempty(stencil_storage)
    % Start with an empty cell vector, to which we will add elements below.
    stencil_storage = cell(0,1);
  else
    % Look to see whether we have already built the appropriate stencil.
    for i = 1 : length(stencil_storage)
      if((stencil_storage{i}.dim == grid.dim) ...
         && (stencil_storage{i}.stencil_size == stencil_size) ...
         && strcmp(stencil_storage{i}.stencil_shape, stencil_shape) ...
         && all(stencil_storage{i}.dx == grid.dx))
        this_stencil = stencil_storage{i};
        found_stencil = 1;
        break;
      end
    end    
  end  
  
  if ~found_stencil
    % Need to build a new stencil.
    this_stencil.dim = grid.dim;
    this_stencil.stencil_size = stencil_size;
    this_stencil.stencil_shape = stencil_shape;
    this_stencil.dx = grid.dx;

    [ this_stencil.stencil_array, this_stencil.scale_factor ] = ...
        createNewStencil(stencil_radius, stencil_width, stencil_shape,...
                         grid.dx, visualize_stencil);
    
    % Memoize the result.
    stencil_storage{length(stencil_storage) + 1, 1} = this_stencil;
  end

  %---------------------------------------------------------------------------
  % How many sample points are in the stencil?
  num_offsets = size(this_stencil.stencil_array, 1);
  
  % We need indices to the real data in the ghost cell padded array, as
  % well as indices into the unpadded array.
  ind_pad = cell(grid.dim, 1);
  ind_nopad = cell(grid.dim, 1);
  for d = 1 : grid.dim
    ind_nopad{d} = 1 : grid.N(d);
    ind_pad{d} = ind_nopad{d} + stencil_ghostcells_per_dim(d);
  end

  %---------------------------------------------------------------------------
  % To compute the median, we need to get all the various offset node's
  % values.  This will be a very big array of dimension grid.dim + 1.
  offset_data = zeros([ size(data), num_offsets ]);
  % This offset_data array will have no ghost cells.
  offset_indices = cell(grid.dim, 1);
  for i = 1 : num_offsets
    for d = 1 : grid.dim
      offset_indices{d} = ind_pad{d} + this_stencil.stencil_array(i,d);
    end
    if interpolate
      offset_data(ind_nopad{:}, i) = (data + this_stencil.scale_factor(i) ...
                                      * (ghost_data(offset_indices{:})-data));
    else
      offset_data(ind_nopad{:}, i) = ghost_data(offset_indices{:});
    end
  end

  % Get the median of all those offset neighbors.
  u_star = median(offset_data, grid.dim + 1);
  
  % From Oberman: \Laplace_1 u = 2 * (u - u_star) / dx^2
  value = -(data - u_star) * 2 / stencil_radius^2;



%---------------------------------------------------------------------------
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%---------------------------------------------------------------------------
function [ stencil, scale_factor ] = ...
      createNewStencil(sradius, swidth, sshape, dx, visualize_stencil)
% createNewStencil: creates set of points to sample for the median computation
%
%   [ stencil, scale_factor ] = ...
%        createNewStencil(sradius, swidth, sshape, dx, visualize_stencil)
%
% Creates a set of sample points from which to find the median for
% Oberman's mean curvature approximation.
%
% Input Parameters:
%
%   sradius: Double.  Radius of the stencil in absolute distance (ie
%   stencil_size * max(dx)).
%
%   swidth: Double.  Allowed amount of deviation from sradius.
%
%   sshape: String.  Specifies the shape of the stencil.  Must be either
%   'square' or 'circle'.
%
%   dx: Column vector.  Grid spacing in each dimension.
%
%   visualize_stencil: Boolean.  Show the stencil as a plot after creation.
%
% Output Parameters:
%
%   stencil: A matrix.  Each row is a set of index offsets to a sample
%   point for the stencil.
%
%   scale_factor: A column vector with one row for each node in the stencil.
%   Gives the inverse of the fraction of sradius of the distance from the
%   center to the corresponding node (which may be greater than 1).

  %---------------------------------------------------------------------------
  dim = length(dx);

  % First, we construct the sample points in the first orthant.
  switch(sshape)
    
   case 'circle'
    first = allNodesInCircle(sradius, swidth, dx);
    
   case 'square'
    first = allNodesInSquare(sradius, dx);
    
   otherwise
    error('Unknown stencil shape: %s', sshape);
  end

  % How many points in the first orthant.
  num_first = size(first, 1);

  % For debugging, return only the first orthant result.
  %stencil = first;  return;
  
  %---------------------------------------------------------------------------
  % Now mirror those points into every other orthant.

  % Allocate lots of space: one copy of the first orthant sample points
  % for 2^dim orthants.
  stencil = zeros(2^dim * num_first, dim);
  
  % Need a set of mirroring vectors.
  [ mirror_sets{1:dim} ] = deal([ +1, -1 ]);
  mirror = setCrossProduct(mirror_sets);

  % For each mirror vector, mirror all the sample points.
  for i = 1 : length(mirror)
    stencil((i-1)*num_first+1 : i*num_first, :) = ...
        first .* repmat(mirror(i,:), num_first, 1);
  end
  
  % Repeats will occur for all sample points whose indices include zero.
  % Remove the repeats.
  stencil = unique(stencil, 'rows');
      
  %---------------------------------------------------------------------------
  % Compute distances for each node.
  coordinate_stencil = stencil .* repmat(dx', size(stencil, 1), 1);
  distance_stencil = sqrt(sum(coordinate_stencil.^2, 2));
  scale_factor = sradius ./ distance_stencil;
  
  %---------------------------------------------------------------------------
  % Some code to visualize the chosen nodes.
  if visualize_stencil
    figure;
    switch(dim)
     
     case 2
      hc = plot(0, 0, 'r*');
      hold on;
      hs = plot(stencil(:,1), stencil(:, 2), 'b.');
      set([ hs, hc ], 'MarkerSize', 20);
      daspect(1 ./ [ dx', 1 ]);
      axis(axis + [ -1, +1, -1, +1 ]);
     
     case 3
      hc = plot(0, 0, 'r*');
      hold on;
      hs = plot3(stencil(:,1), stencil(:, 2), stencil(:, 3), 'b.');
      set([ hs, hc ], 'MarkerSize', 20);
      daspect(1 ./ dx');
      axis(axis + [ -1, +1, -1, +1, -1, +1 ]);
     
     otherwise
      warning('Visualization of stencils only possible in 2-3 dimensions');
    end
  end



%---------------------------------------------------------------------------
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%---------------------------------------------------------------------------
function nodes = allNodesInCircle(radius, width, dx)
% allNodesInCircle: Index of all nodes in first orthant donut.
%
%   nodes = allNodesInCircle(radius, width, dx)
%
% Finds the indices of all nodes in the first orthant that are within a
% distance width of a circle (sphere) of specified radius; in other words,
% all nodes within a donut in 2D.  If there are multiple nodes within the
% donut that lie along a single ray from the origin, only the node closest
% to radius will be kept.
%
% Input Parameters:
%
%   radius: Double.  Radius of the circle / sphere.
%
%   width: Double.  Width of the band within which nodes are accepted.
%   Distance from the origin to the node must be within radius +- width.
%   Nodes exactly on the inner edge of the band are accepted, but not
%   exactly on the outer edge of the band.
%
%   dx: Column vector.  The spacing in each dimension.
%
% Output Parameters:
%
%   nodes: Matrix.  Each row holds the indices to one of the nodes that
%   satisfied the conditions and lies near the desired circle / sphere.

  dim = length(dx);
  
  % For grids with different dx in different directions, the stencil will not
  % be symmetric among the dimensions.  Consequently, we omit some nodes
  % because they are too close in direction to other nodes.  There are too
  % choices for "too close": a arbitrary small angle, or an attempt to
  % sample directions roughly equally.  Note that nodes are picked in order
  % of distance from the desired radius, so the nodes furthest from the
  % desired radius are the ones which will be omitted by this test.
  sample_equally = 1;
  
  % Each node in the stencil can be represented by a direction vector.  We can
  % compare these direction vectors to determine whether we are sampling the
  % same direction more than once. How close is too close in direction?
  % This quantity is an angle.
  if sample_equally
    % Alternatively, we can try to sample directions roughly equally.  Roughly,
    % the largest angular gap will occur in the direction of the smallest
    % dx, across a cell diagonal that includes every dx except the smallest
    % (in 2D, this "diagonal" will be a grid edge of length max(dx)).  We
    % consider two nodes in the stencil to be too close together in
    % direction if the angle between them is less than some fraction of this
    % largest angular gap.
    gap_fraction = 0.25;
    % Unit vectors for the two stencil nodes with the largest angular gap.
    sorted_dx = sort(dx);
    node1_direction = [ 1; zeros(dim - 1, 1) ];
    node2_direction = [ radius; sorted_dx(2:end) ];
    node2_direction = node2_direction / norm(node2_direction);
    % Angle between the directions of those two nodes.
    largest_angle = acos(node1_direction' * node2_direction);
    small_angle = gap_fraction * largest_angle;
  
  else
    % Only omit nodes if they are in essentially the same direction.
    small_angle = 1 * pi / 180;
  
  end
  
  % Create a little array representing each node in the first orthant.
  first_vs = cell(dim, 1);
  for d = 1 : dim
    first_vs{d} = 0 : dx(d) : radius + width;
  end
  [ xs{1:dim} ] = ndgrid(first_vs{:});
  
  % Distance from each of these nodes to the center.
  dist = zeros(size(xs{1}));
  for d = 1 : dim
    dist = dist + xs{d}.^2;
  end
  dist = sqrt(dist);

  % Unit vectors for each of these nodes.
  unit_xs = xs;
  for d = 1 : dim
    unit_xs{d} = unit_xs{d} ./ max(dist, eps);
  end
  
  % Signed distance to the circle.
  dist = dist - radius;

  % Within the donut.  We allow nodes to be exactly on the inner boundary, but
  % not the outer boundary.
  within = ((dist >= -width) & (dist < width));
  
  % Subscripts of nodes within donut.
  [ indices{1:dim} ] = ind2sub(size(dist), find(within));
  % Convert into an index matrix.
  indices = cell2mat(indices);

  % Distances and unit vectors of nodes within the donut.
  dist_within = dist(within);
  unit_xs_within = zeros(length(dist_within), dim);
  for d = 1 : dim
    unit_xs_within(:,d) = unit_xs{d}(within);
  end
  
  % Go through all nodes within the donut in order from closest to the proper
  % radius outward to the edge of the donut.  Discard any nodes whose
  % direction is already accounted for by a node that is closer to the
  % proper radius.  This is a slow process.
  nodes = zeros(sum(within(:)), dim);
  nodes_unit_xs = zeros(size(nodes));
  next_node = 1;
  [ garbage, sort_indices ] = sort(abs(dist_within));
  for i = 1 : length(sort_indices)
    keep_node = 1;
    % Compare against all nodes which have already been accepted.
    for j = 1 : next_node - 1
      % Measure distance between directions using cos(theta) = x dot y.
      angle = acos(unit_xs_within(sort_indices(i), :) * nodes_unit_xs(j, :)');
      if(angle < small_angle)
        keep_node = 0;
        break;
      end
    end
    if keep_node
      nodes(next_node, :) = indices(sort_indices(i), :);
      nodes_unit_xs(next_node, :) = unit_xs_within(sort_indices(i), :);
      next_node = next_node + 1;
    end
  end
  
  % Omit the rows of the nodes array that were never filled.  Also, these
  % indices should be offsets, so we need to shift by 1.
  nodes = nodes(1 : next_node-1, :) - 1;



%---------------------------------------------------------------------------
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%---------------------------------------------------------------------------
function nodes = allNodesInSquare(radius, dx)
% allNodesInSquare: Index of all nodes in first orthant hypercube
%
%   nodes = allNodesInCircle(radius, dx)
%
% Finds the indices of all nodes in the first orthant that are at the
% edge of a hypercube of size radius.  Note that due to differences in
% dx, it may be slightly larger in some dimensions.
%
% Input Parameters:
%
%   radius: Double.  Half length of the hypercube.
%
%   dx: Column vector.  The spacing in each dimension.
%
% Output Parameters:
%
%   nodes: Matrix.  Each row holds the indices to one of the nodes that
%   satisfied the conditions and lies near the desired circle / sphere.

  dim = length(dx);

  % We just take the outside surface of a box of appropriate size.
  % Simple method: list the indices of all nodes in the box, and then
  % only take those nodes with at least one index at its maximum value.

  % How many nodes outwards in each dimension?
  stencil_size = ceil(radius ./ dx);

  % First: all nodes.
  index_sets = cell(dim, 1);
  for d = 1 : dim
    index_sets{d} = 0:stencil_size(d);
  end
  all_nodes = setCrossProduct(index_sets);

  % Next, find those with at least one index at maximum value.
  max_index = repmat(stencil_size', size(all_nodes, 1), 1);
  surface_indices = find(any(all_nodes == max_index, 2));
  nodes = all_nodes(surface_indices, :);



%---------------------------------------------------------------------------
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%---------------------------------------------------------------------------
function product = setCrossProduct(sets, dim, product)
% setCrossProduct: Build the cross product of a bunch of sets of numbers.
%
%   product = setCrossProduct(sets, dim, product)
%
% Generates a bunch of output sets (rows) each of which contains one element
% from each input set.  The entire set of output sets includes all
% combinations of input elements.
%
% Input Parameters:
%
%   sets: Column vector.  Each element is an input set: a vector of numbers.
%
%   dim: Current dimension which is being constructed.  Used only during
%   recursion.  User should leave out this parameter.
%
%   product: The partially constructed output sets.  Used only during
%   recursion.  Users should leave out this parameter.
%
% Output Parameters:
%
%   product: A 2D array containing the output sets as rows.

  if(nargin < 3)
    dim = 1;
    product = zeros(1,0);
  end
  
  if(dim <= length(sets))
    old_product_size = size(product, 1);
    new_set_size = length(sets{dim});

    % Copy the current product into an array big enough to hold the
    % product with the next set.
    new_product = [ repmat(product, new_set_size, 1), ...
                    zeros(old_product_size * new_set_size, 1) ];

    % Copy in the members of the next set.
    for i = 1 : new_set_size
      interval = (i-1)*old_product_size+1:i*old_product_size;
      new_product(interval, end) = sets{dim}(i);
    end

    % Move on to the next set.
    product = setCrossProduct(sets, dim+1, new_product);
  end
