##  Copyright (C) 2015  Daniel Kraft <d@domob.eu>
##  GNU Octave level-set package.
##
##  This program is free software: you can redistribute it and/or modify
##  it under the terms of the GNU General Public License as published by
##  the Free Software Foundation, either version 3 of the License, or
##  (at your option) any later version.
##
##  This program is distributed in the hope that it will be useful,
##  but WITHOUT ANY WARRANTY; without even the implied warranty of
##  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
##  GNU General Public License for more details.
##
##  You should have received a copy of the GNU General Public License
##  along with this program.  If not, see <http://www.gnu.org/licenses/>.

## -*- texinfo -*-
## @deftypefn  {Function File} {@var{gnorm} =} upwind_gradient_norm (@var{phi}, @var{h} = 1)
## @deftypefnx {Function File} {@var{gnorm} =} upwind_gradient_norm (@var{phi}, @var{f}, @var{h} = 1)
## 
## Approximate the gradient norm of @var{phi} using an upwind scheme.
## The scheme chosen is appropriate to propagate the level-set equation
## @tex
## \begin{equation*}
##    \phi_t + f \left| \nabla \phi \right| = 0
## \end{equation*}
## @end tex
## @ifnottex
##
## @example
## d/dt phi + f | grad phi | = 0
## @end example
##
## @end ifnottex
## in time.  If the argument @var{f} is given, its sign is used to
## find the correct upwind direction.  If it is not present, positive
## sign is assumed throughout the domain.  @var{h} gives the grid
## spacing to use for the difference approximation.
##
## @var{phi} can be an array of arbitrary dimension, and @var{gnorm}
## is always of the same size as @var{phi}.  If @var{f} is given, it
## must also be of the same size as @var{phi}.
##
## The gradient is approximated with finite differences, either the
## forward or backward difference quotient.  The direction chosen at
## each grid point depends on the sign of f and  the gradient of phi
## at that point.  The approximation used is from section 6.4 of
##
## J. A. Sethian:  Level Set Methods and Fast Marching Methods, second edition,
## 1999.  Cambridge Monographs on Applied and Computational Mathematics,
## Cambridge University Press.
##
## @seealso{ls_time_step}
## @end deftypefn

function gnorm = upwind_gradient_norm (phi, second, third)
  switch nargin ()
    case 1
      F = ones (size (phi));
      h = 1;
    case 2
      if (isscalar (second))
        F = ones (size (phi));
        h = second;
      else
        F = second;
        h = 1;
      endif
    case 3
      F = second;
      h = third;
    otherwise
      print_usage ();
  endswitch

  if (!isscalar (h))
    print_usage ();
  endif

  if (~all (size (phi) == size (F)))
    error ("PHI and F must be of the same size");
  endif

  gnorm = __levelset_upwindGrad (phi, F, h);
endfunction

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Tests.

% Test for error conditions.
%!error <Invalid call to>
%!  upwind_gradient_norm ()
%!error <Invalid call to>
%!  upwind_gradient_norm (1, 2, 3, 4)
%!error <Invalid call to>
%!  upwind_gradient_norm ([1, 1], [2, 2], [3, 3])
%!error <PHI and F must be of the same size>
%!  upwind_gradient_norm ([1, 1], [2, 2, 2])
%!error <PHI and F must be of the same size>
%!  upwind_gradient_norm ([1, 1], [2, 2, 2], 5)

% Basic test in 1D, check that the correct direction is chosen.
%!test
%!  phi = [0, 2, 3];
%!  fPos = ones (size (phi));
%!
%!  % Propagate the original phi to the right.  This should result
%!  % in the *backward* difference to be used in the middle.
%!  g = upwind_gradient_norm (phi);
%!  assert (g(2), 2);
%!  gp = upwind_gradient_norm (phi, fPos);
%!  assert (gp, g);
%!  gp = upwind_gradient_norm (phi, 0.5);
%!  assert (gp, 2 * g);
%!  gp = upwind_gradient_norm (phi, fPos, 0.5);
%!  assert (gp, 2 * g);
%!
%!  % Propagate to the left for the reverse effect.
%!  g = upwind_gradient_norm (phi, -fPos);
%!  assert (g(2), 1);
%!
%!  % Propagate -phi, which again turns everything around.
%!  g = upwind_gradient_norm (-phi, fPos);
%!  assert (g(2), 1);
%!  g = upwind_gradient_norm (-phi, -fPos);
%!  assert (g(2), 2);

% Test in 3D with a quadratic function, where we know the resulting
% expected gradient norm.
%!test
%!  n = 50;
%!  x = linspace (-1, 1, n);
%!  h = x(2) - x(1);
%!  [XX, YY, ZZ] = ndgrid (x, x, x);
%!
%!  phi = XX.^2 + YY.^2 + ZZ.^2;
%!  g = upwind_gradient_norm (phi, h);
%!  
%!  r = sqrt (phi);
%!  assert (g, 2 * r, 0.1);
