## Copyright (C) 1996-2015 Piotr Held
##
## This file is part of Octave.
##
## Octave 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.
##
## Octave 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 Octave; see the file COPYING.  If not,
## see <http://www.gnu.org/licenses/>.

## -*- texinfo -*-
## @deftypefn{Function File} {@var{output} =} av_d2 (@var{d2_c2d_c1_out})
## @deftypefnx{Function File} {@var{output} =} av_d2 (@var{d2_c2d_c1_out}, @var{paramName}, @var{paramValue}, @dots{})
##
## This program takes the output of d2, c2d or c1 and smooths it by averaging
## over a given interval. It is also possible to specify the range of
##  embedding dimensions to be smoothed. This function makes most sense for
## the "d2" field of the d2 output or the output of c2d. By default it only
## smooths field "d2" of the d2 output.
##
## Assuming the two column vectors of a matrix (one of the fields of the input 
## struct) are @var{r} and @var{d} then one of the output matrices will be of 
## the form:
##
## @iftex
## @tex
## $$\begin{bmatrix} r_i & \frac{\sum_{j=-a}^{a}d_{i-j}}{2a+1} \end{bmatrix}$$
## @end tex
## @end iftex
## @ifnottex
## @example
##  _                              _
## |                  __ a          |
## |            1    \              |
## |   r    ,  ----   |       d     |
## |_   i      2a+1  /__ j=-a  i-j _|
## @end example
## @end ifnottex
##
## @strong{Input}
##
## The input needs to be the output of d2, c2d or c1.
##
## @strong{Parameters}
##
## @table @var
## @item mindim
## Minimum dimension to smooth, this also determines the size of the output
## struct [default = 1].
## @item maxdim
## Maximum dimension to smooth (also determines size of output struct)
## [default = 1].
## @item a
## Smooth over an interval of @code{2 * a + 1} points [default = 1].
## @end table
##
## @strong{Switch}
##
## @table @var
## @item smoothall
## This switch makes only works for inputs that were generated by d2.
## If this switch is set all of the fields of the input are smoothed and not
## just field "d2".
## @end table
##
## @strong{Output}
##
## The output is a struct array, which is a subarray of the input. The indexes
## used to create the subarray are @var{mindim}:@var{maxdim}. Some or all
## of the fields of this output have been smoothed (depending on the
## @var{smoothall} switch).
##
## @seealso{demo av_d2, d2, c2t, c2g}
##
## @strong{Algorithms}
##
## The algorithms for this functions have been taken from the TISEAN package.
## @end deftypefn

## Author: Piotr Held <pjheld@gmail.com>.
## This function is based on av-d2 of TISEAN 3.0.1 
## https://github.com/heggus/Tisean"

function output = av_d2 (d2_c2d_c1_out, varargin)

  if (nargin < 1)
    print_usage;
  endif

  if (!(isfield (d2_c2d_c1_out, "dim") && (isfield (d2_c2d_c1_out, "c2") ...
        && isfield (d2_c2d_c1_out, "d2") && isfield (d2_c2d_c1_out, "h2") ...
        || isfield (d2_c2d_c1_out, "d") || isfield (d2_c2d_c1_out, "c1"))))
    error ('Octave:invalid-input-arg', ["d2_c2d_c1_out must be the output of ",...
                                        "d2 or c2d"])
  endif

  # Default values
  mindim = 1;
  maxdim = max (cell2mat ({d2_c2d_c1_out.dim}));
  aver   = 1;

  #### Parse the input
  p = inputParser ();
  p.FunctionName = "av_d2";

  isPositiveIntScalar    = @(x) isreal(x) && isscalar (x) && ...
                             (x > 0) && (x-round(x) == 0);

  p.addParamValue ("mindim", mindim, isPositiveIntScalar);
  p.addParamValue ("maxdim", maxdim, isPositiveIntScalar);
  p.addParamValue ("a", aver, isPositiveIntScalar);
  p.addSwitch ("smoothall");

  p.parse (varargin{:});

  # Assign input
  mindim     = p.Results.mindim;
  maxdim     = p.Results.maxdim;
  aver       = p.Results.a;
  smoothall  = p.Results.smoothall;

  # Input validation
  # Make note whether the input is from d2 or from c2d
  if (isfield (d2_c2d_c1_out, "d2"))
    input_type = 'd2';
  elseif (isfield (d2_c2d_c1_out, "d"))
    input_type = 'c2d';
  else # isfield (d2_c2d_c1_out, "c1")  == true -- was checked previously
    input_type = 'c1';
  endif

  # If mindim is too large shrink it
  if (mindim > maxdim)
    warning ("Octave:tisean", ["Parameter 'mindim' is greater than ", ...
                               "'maxdim', setting 'mindim' = 'maxdim'"]);
    mindim = maxdim;
  endif

  # Create smoothing functions
  # Smoothes single column
  smooth_column = @(col) sum (col((-aver:aver) ...
                                  .+(aver+1:(length(col)-aver)).'),2) ...
                         ./(2*aver + 1);
  # Smoothes one cell
  smooth_cell   = @(cell_mat) [smooth_column(cell_mat{1}(:,1)), ...
                               smooth_column(cell_mat{1}(:,2))];
  # Smoothes whole cell array
  smooth_array  = @(cell_array) arrayfun (smooth_cell, cell_array, ...
                                          'UniformOutput', false);

  # Smooth the input
  output = d2_c2d_c1_out(mindim:maxdim);

  switch (input_type)
    case 'd2'
      if (smoothall)
        output = struct ("dim", {output.dim}.', ...
                         "c2", smooth_array ({output.c2}.'), ...
                         "d2", smooth_array ({output.d2}.'), ...
                         "h2", smooth_array ({output.h2}.'));
      else
        output = struct ("dim", {output.dim}.', ...
                         "c2", {output.c2}.', ...
                         "d2", smooth_array ({output.d2}.'), ...
                         "h2", {output.h2}.');
      endif
    case 'c2d'
        if (smoothall)
          warning ("Octave:tisean", ["Input is from c2d - only one field ",...
                                    "to smooth"]);
        endif
        output = struct ("dim", {output.dim}.', ...
                         "d", smooth_array ({output.d}.'));
    case 'c1'
        if (smoothall)
          warning ("Octave:tisean", ["Input is from c1 - only one field ",...
                                    "to smooth"]);
        endif
        output = struct ("dim", {output.dim}.', ...
                         "c1", smooth_array ({output.c1}.'));
  endswitch

#  Below is the same code as above but uses loops, and only smoothes .d2
#  It is left to easily see what the algorithm does.
#  iterator = 1;
#  output = struct ("d2",[]);
#  for i = 1:size(d2_c2d_c1_out,1)
#    tmp = d2_c2d_c1_out(i);
#    if (tmp.dim >= mindim && tmp.dim <= maxdim)
#      d2_smooth = zeros (length(tmp.d2)-2*aver,2);
#      for k = aver+1:(length(tmp.d2)-aver)
#        d2_smooth(k-aver,:) = sum (tmp.d2((-aver:aver)+k,:))./(2*aver + 1);
#      endfor
#      output = [output; struct("d2",d2_smooth)];
#    endif
#  endfor
#  output(1) = [];

endfunction

%!demo
%! vals = d2 (henon (1000), 'd', 1, 't', 50, 'm', 5);
%!
%! subplot (2,3,1)
%! do_plot_slope = @(x) semilogx (x{1}(:,1),x{1}(:,2),'b');
%! hold on
%! arrayfun (do_plot_slope, {vals.d2});
%! hold off
%! axis tight
%! xlabel ("Epsilon")
%! ylabel ("Local slopes")
%! title ("Original");
%!
%! smooth = av_d2 (vals,'a',2);
%!
%! subplot (2,3,4)
%! do_plot_slope = @(x,y) semilogx (x{1}(:,1),x{1}(:,2),'r',...
%!                                  y{1}(:,1),y{1}(:,2),'b');
%! hold on
%! arrayfun (do_plot_slope, {smooth.d2}, {vals.d2}  );
%! hold off
%! axis tight
%! xlabel ("Epsilon")
%! ylabel ("Local slopes")
%! title ("Superimposed");
%!
%! subplot (2,3,[2 3 5 6])
%! do_plot_slope = @(x) semilogx (x{1}(:,1),x{1}(:,2),'r');
%! hold on
%! arrayfun (do_plot_slope, {smooth.d2});
%! hold off
%! axis tight
%! xlabel ("Epsilon")
%! ylabel ("Local slopes")
%! title ("Smooth");
%!###############################################################

%!shared val, c2d_res, c1_res
%! val = d2 (henon (1000), 'd', 1, 't', 50, 'm', 5);

%% test against some results from program av-d2 from TISEAN 3.0.1
%!test
%! av_d2_res = [2.227339 0.02227155;2.077224 0.07391772;1.937226 0.2742541;1.806664 0.482474;1.684901 0.603453;1.571344 0.598633;1.465441 0.5230358;1.366675 0.4940035;1.274566 0.4025534;1.188664 0.4291942;1.108552 0.720235;1.033839 0.9861274;0.9641622 1.036355;0.8991808 0.8166605;0.8385791 0.6758107;0.7820616 0.6766519;0.7293533 0.7019168;0.6801973 0.7465489;0.6343543 0.7729089;0.5916009 0.7865262;0.551729 0.7587416;0.5145443 0.7644434;0.4798658 0.8011066;0.4475244 0.8391553;0.4173628 0.9049456;0.3892339 0.9205563;0.3630009 0.949196;0.3385358 0.9231388;0.3157196 0.9548996;0.2944412 0.9584774;0.2745968 0.9921522;0.2560899 0.9393911;0.2388303 0.9163786;0.2227339 0.8300805;0.2077224 0.8629162;0.1937226 0.865825;0.1806664 0.8647303;0.1684901 0.8767034;0.1571344 0.9034054;0.1465441 0.9768383];
%! res = av_d2 (val);
%! assert (cell2mat({res.d2}.')(1:length(av_d2_res),:), av_d2_res, -1e-6);

%% test against some results from program av-d2 from TISEAN 3.0.1
%!test
%! av_d2_res_h2 = [1.965624 0.0536151;1.833148 0.07254237;1.7096 0.09431121;1.594379 0.1214435;1.486923 0.1495628;1.386709 0.1786416;1.29325 0.2090021;1.206089 0.2400019;1.124803 0.2694222;1.048995 0.2958831;0.978296 0.3231978;0.912362 0.3510871;0.8508719 0.3752123;0.7935261 0.3983067;0.740045 0.4201098;0.6901685 0.4411169;0.6436534 0.462419;0.6002733 0.4845437;0.5598169 0.5095501;0.5220871 0.5324418;0.4869002 0.55473;0.4540848 0.5774259;0.423481 0.6002299;0.3949398 0.6220529;0.3683222 0.6453185;0.3434985 0.6653961;0.3203478 0.6852458;0.2987574 0.706009;0.2786222 0.7280725;0.259844 0.7495944;0.2423314 0.7692112;0.225999 0.7882578;0.2107675 0.8074904;0.1965624 0.8228356;0.1833148 0.8381825];
%! res = av_d2 (val, 'mindim', 2, 'maxdim', 7, 'a',4,'smoothall');
%! assert (cell2mat({res.h2}.')(1:length(av_d2_res_h2),:), av_d2_res_h2,-1e-6);

%% testing if input from c2d is accepted
%! c2d_res = c2d (c1 (henon (1000)), 2);
%!test assert (!isequal(av_d2 (c2d_res),c2d_res(1)));

%% testing if input from c1 is accepted
%! c1_res = c1 (henon (1000));
%!test assert (!isequal(av_d2 (c1_res),c1_res(1)));

%% Testing input validation
%!error <output> av_d2 (1);
%% Promote warnings to error to not execute program
%!error <greater> warning("error", "Octave:tisean"); ...
%!                av_d2 (struct("d2",0,"h2",0,"c2",0,"dim",1), 'mindim',3, ...
%!                       'maxdim',2);
%% Test if the warning pops up when pass switch 'smoothall' with c2d output
%!error <one field> warning("error", "Octave:tisean"); ...
%!                av_d2 (c2d_res,'smoothall');
%% Test if the warning pops up when pass switch 'smoothall' with c1 output
%!error <one field> warning("error", "Octave:tisean"); ...
%!                av_d2 (c1_res,'smoothall');
