##  Copyright (C) 2015-2019  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{phit} =} ls_time_step (@var{t}, @var{phi0}, @var{f}, @var{h} = 1)
## @deftypefnx {Function File} {@var{phit} =} ls_time_step (@var{t}, @var{c}, @var{phi0}, @var{f}, @var{h} = 1)
## 
## Evolve the level-set equation with time stepping.  Perform explicit time
## stepping on the 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
## The initial value is given by @var{phi0}.  @var{phi0} and @var{f} must
## be of the same size.  If @var{h} is present, it sets the spatial grid size.
## The time stepping uses @code{upwind_gradient_norm} for the evaluation
## of the gradient norm at each step.
##
## @var{t} is the time (or vector of times) at which the solution should
## be returned.  If more than one time is given, the result @var{phit}
## will be a cell array of the evolved level-set functions at each of
## the requested time points.  If @var{t} is a scalar, @var{phit} is
## returned as array of the same size of @var{phi0}.
##
## The time step is chosen to satisfy the Courant-Friedrichs-Lewy condition
## @tex
## \begin{equation*}
##    \Delta t = c \frac{h}{F_m n}.
## \end{equation*}
## @end tex
## @ifnottex
##
## @example
## dt = c h / (Fm * n).
## @end example
##
## @end ifnottex
## Here, Fm is the maximum absolute value in @var{f}, and n is the number
## of space dimensions.  The desired ratio c is by default one, but can be
## passed explicitly with the second usage form.
##
## @seealso{ls_extract_solution, upwind_gradient_norm}
## @end deftypefn

function phit = ls_time_step (t, second, third, fourth, fifth)
  c = 1;
  h = 1;
  switch nargin ()
    case 3
      phi0 = second;
      F = third;
    case 4
      if (isscalar (second))
        c = second;
        phi0 = third;
        F = fourth;
      else
        phi0 = second;
        F = third;
        h = fourth;
      endif
    case 5
      c = second;
      phi0 = third;
      F = fourth;
      h = fifth;
    otherwise
      print_usage ();
  endswitch

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

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

  if (!isvector (t))
    error ("T must be a vector or scalar");
  endif

  Fm = norm (F(:), Inf);
  n = sum (size (phi0) > 1);
  dt = c * h / (Fm * n);

  phit = cell (length (t), 1);

  phi = phi0;
  curT = 0;
  nextTind = 1;

  while (nextTind <= length (t))
    gradNorm = upwind_gradient_norm (phi, F, h);

    dNextT = t(nextTind) - curT;
    if (dNextT < 0)
      error ("T must be ascending");
    endif

    if (dNextT <= dt)
      phi -= dNextT * F .* gradNorm;
      curT += dNextT;
      assert (curT, t(nextTind));
      phit{nextTind} = phi;
      ++nextTind;
    else
      phi -= dt * F .* gradNorm;
      curT += dt;
    endif
  endwhile

  if (length (phit) == 1)
    phit = phit{1};
  endif
endfunction

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

% Test for error conditions.
%!error <Invalid call to>
%!  ls_time_step (1, 2)
%!error <Invalid call to>
%!  ls_time_step (1, 2, 3, 4, 5, 6)
%!error <Invalid call to>
%!  ls_time_step (1, 2, 3, 4, [5, 5])
%!error <PHI0 and F must be of the same size>
%!  ls_time_step ([1; 1], [2, 2], [3, 3, 3])
%!error <PHI0 and F must be of the same size>
%!  ls_time_step ([1, 1], 1, [2, 2], [3, 3, 3])
%!error <T must be a vector or scalar>
%!  ls_time_step (ones (2, 2), 1, 1)

% Test a monotone profile is shifted when we have constant speed.
% Also test basic usage.
%!test
%!  n = 100;
%!  x = linspace (0, 1, n);
%!  h = x(2) - x(1);
%!
%!  phi0 = sin (x);
%!  fPos = ones (size (phi0));
%!
%!  % Single time step, moving exactly one grid step.
%!  phit = ls_time_step (1, phi0, fPos);
%!  assert (phit(2 : end), phi0(1 : end - 1));
%!
%!  % t = 1 should correspond to the previous result, and t = 2
%!  % shifts one more time step.  However, due to boundary effects,
%!  % we have to exclude some elements in the comparisons.
%!  phitArr = ls_time_step ([1, 2], 0.5, phi0, fPos);
%!  assert (size (phitArr), [2, 1]);
%!  assert (phitArr{1}(3 : end), phit(3 : end), 1e-4);
%!  assert (phitArr{2}(5 : end), phi0(3 : end - 2), 1e-4);
%!
%!  % Try out movement in the other direction.
%!  phit = ls_time_step (0.5, 0.1, phi0, -fPos, 0.5);
%!  assert (phit(1 : end - 4), phi0(2 : end - 3), 1e-4);

% Compare evolution to the fast marching method.
%!function compareFastMarching (t, phi0, F, h, tol)
%!  phit = ls_time_step (t, phi0, F, h);
%!  d = ls_solve_stationary (phi0, F, h);
%!  phit_fm = ls_extract_solution (t, d, phi0, F);
%!
%!  sd1 = ls_signed_distance (phit, h);
%!  sd2 = ls_signed_distance (phit_fm, h);
%!  assert (sd1, sd2, tol);
%!endfunction

% Run comparison tests between fast marching and time stepping.
%!test
%!  warning ("off", "level-set:fast-marching:too-far-alive");
%!
%!  n = 100;
%!  x = linspace (-5, 5, n);
%!  h = x(2) - x(1);
%!  [XX, YY] = meshgrid (x, x);
%!
%!  phi0 = ls_genbasic (XX, YY, "sphere", [0, 0], [3, 1.5]);
%!  F = ones (size (phi0));
%!  compareFastMarching (0.5, phi0, F, h, h / 2);
%!  compareFastMarching (1.5, phi0, F, h, h / 2);
%!
%!  phi0 = ls_genbasic (XX, YY, "box", [-3, -3], [3, 3]);
%!  F = YY / 5;
%!  compareFastMarching (1, phi0, F, h, h);
%!
%!  F = ones (size (phi0));
%!  compareFastMarching (1, phi0, F, h, h / 2);
%!
%!  phi0 = ls_genbasic (XX, YY, "sphere", [0, 0], 3);
%!  F = sin (XX .* YY);
%!  compareFastMarching (1, phi0, F, h, h);

% Try also a 3D problem.
%!test
%!  warning ("off", "level-set:fast-marching:increased-distance");
%!
%!  n = 50;
%!  x = linspace (-5, 5, n);
%!  h = x(2) - x(1);
%!  [XX, YY, ZZ] = ndgrid (x, x, x);
%!
%!  phi0 = ls_genbasic (XX, YY, ZZ, "sphere", [0, 0, 0], 3);
%!  F = ones (size (phi0));
%!  compareFastMarching (1, phi0, F, h, h / 2);
%!  compareFastMarching (1, phi0, -F, h, h / 2);

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Demos.

%!demo
%!  n = 500;
%!  x = linspace (-5, 5, n);
%!  h = x(2) - x(1);
%!  [XX, YY] = meshgrid (x, x);
%!
%!  F = sin (XX .* YY);
%!  phi0 = ls_genbasic (XX, YY, "sphere", [0, 0], 3);
%!  phit = ls_time_step ([0.5, 1], phi0, F, h);
%!
%!  figure ();
%!  hold ("on");
%!  imagesc (x, x, F);
%!  axis ("equal");
%!  set (gca (), "ydir", "normal");
%!  ls_sign_colourmap ();
%!  contour (XX, YY, phi0, [0, 0], "k", "LineWidth", 2);
%!  contour (XX, YY, phit{1}, [0, 0], "k", "LineWidth", 2);
%!  contour (XX, YY, phit{2}, [0, 0], "k", "LineWidth", 2);
%!  hold ("off");
