## Copyright (C) 2023 Andreas Bertsatos <abertsatos@biol.uoa.gr>
##
## This file is part of the statistics package for GNU Octave.
##
## 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  {statistics} {@var{paramhat} =} hnfit (@var{x}, @var{mu})
## @deftypefnx {statistics} {[@var{paramhat}, @var{paramci}] =} hnfit (@var{x}, @var{mu})
## @deftypefnx {statistics} {[@var{paramhat}, @var{paramci}] =} hnfit (@var{x}, @var{mu}, @var{alpha})
##
## Estimate parameters and confidence intervals for the half-normal distribution.
##
## @code{@var{paramhat} = hnfit (@var{x})} returns the maximum likelihood
## estimates of the parameters of the half-normal distribution given the data in
## vector @var{x}.  @qcode{@var{paramhat}(1)} is the location parameter,
## @var{mu}, and @qcode{@var{paramhat}(2)} is the scale parameter, @var{sigma}.
## Although @var{mu} is returned in the estimated @var{paramhat}, @code{hnfit}
## does not estimate the location parameter @var{mu}, and it must be assumed to
## be known.
##
## @code{[@var{paramhat}, @var{paramci}] = hnfit (@var{x}, @var{mu})} returns
## the 95% confidence intervals for the estimated scale parameter @var{sigma}.
## The first colummn of @var{paramci} includes the location parameter @var{mu}
## without any confidence bounds.
##
## @code{[@dots{}] = hnfit (@var{x}, @var{alpha})} also returns the
## @qcode{100 * (1 - @var{alpha})} percent confidence intervals of the estimated
## scale parameter.  By default, the optional argument @var{alpha} is 0.05
## corresponding to 95% confidence intervals.
##
## The half-normal CDF is only defined for @qcode{@var{x} >= @var{mu}}.
##
## Further information about the half-normal distribution can be found at
## @url{https://en.wikipedia.org/wiki/Half-normal_distribution}
##
## @seealso{hncdf, hninv, hnpdf, hnrnd, hnlike}
## @end deftypefn

function [paramhat, paramci] = hnfit (x, mu, alpha)

  ## Check for valid number of input arguments
  if (nargin < 2)
    error ("hnfit: function called with too few input arguments.");
  endif

  ## Check X for being a vector
  if (isempty (x))
    phat = nan (1, 2, class (x));
    pci = nan (2, 2, class (x));
    return
  elseif (! isvector (x) || ! isreal (x))
    error ("hnfit: X must be a vector of real values.");
  endif

  ## Check for MU being a scalar real value
  if (! isscalar (mu) || ! isreal (mu))
    error ("hnfit: MU must be a real scalar value.");
  endif

  ## Check X >= MU
  if (any (x < mu))
    error ("hnfit: X cannot contain values less than MU.");
  endif

  ## Parse ALPHA argument or add default
  if (nargin > 2)
    if (! isscalar (alpha) || ! isreal (alpha) || alpha <= 0 || alpha >= 1)
      error ("hnfit: wrong value for ALPHA.");
    endif
  else
    alpha = 0.05;
  endif

  ## Estimate parameters
  sz = numel (x);
  x = x - mu;
  sigmahat = sqrt (sum (x .* x) ./ sz);
  paramhat = [mu, sigmahat];

  ## Compute confidence intervals
  if (nargout == 2)
    chi2cr = chi2inv ([alpha/2, 1-alpha/2], sz);
    shatlo = sigmahat * sqrt (sz / chi2inv (1 - alpha / 2, sz));
    shathi = sigmahat * sqrt (sz / chi2inv (alpha / 2, sz));
    paramci = [mu, shatlo; mu, shathi];
  endif

endfunction

%!demo
%! ## Sample 2 populations from different half-normal distibutions
%! rand ("seed", 1);   # for reproducibility
%! r1 = hnrnd (0, 5, 5000, 1);
%! rand ("seed", 2);   # for reproducibility
%! r2 = hnrnd (0, 2, 5000, 1);
%! r = [r1, r2];
%!
%! ## Plot them normalized and fix their colors
%! hist (r, [0.5:20], 1);
%! h = findobj (gca, "Type", "patch");
%! set (h(1), "facecolor", "c");
%! set (h(2), "facecolor", "g");
%! hold on
%!
%! ## Estimate their shape parameters
%! mu_sigmaA = hnfit (r(:,1), 0);
%! mu_sigmaB = hnfit (r(:,2), 0);
%!
%! ## Plot their estimated PDFs
%! x = [0:0.2:10];
%! y = hnpdf (x, mu_sigmaA(1), mu_sigmaA(2));
%! plot (x, y, "-pr");
%! y = hnpdf (x, mu_sigmaB(1), mu_sigmaB(2));
%! plot (x, y, "-sg");
%! xlim ([0, 10])
%! ylim ([0, 0.5])
%! legend ({"Normalized HIST of sample 1 with μ=0 and σ=5", ...
%!          "Normalized HIST of sample 2 with μ=0 and σ=2", ...
%!          sprintf("PDF for sample 1 with estimated μ=%0.2f and σ=%0.2f", ...
%!                  mu_sigmaA(1), mu_sigmaA(2)), ...
%!          sprintf("PDF for sample 2 with estimated μ=%0.2f and σ=%0.2f", ...
%!                  mu_sigmaB(1), mu_sigmaB(2))})
%! title ("Two population samples from different half-normal distibutions")
%! hold off

## Test output
%!test
%! x = 1:20;
%! [paramhat, paramci] = hnfit (x, 0);
%! assert (paramhat, [0, 11.9791], 1e-4);
%! assert (paramci, [0, 9.1648; 0, 17.2987], 1e-4);
%!test
%! x = 1:20;
%! [paramhat, paramci] = hnfit (x, 0, 0.01);
%! assert (paramci, [0, 8.4709; 0, 19.6487], 1e-4);

## Test input validation
%!error<hnfit: function called with too few input arguments.> hnfit ()
%!error<hnfit: function called with too few input arguments.> hnfit (1)
%!error<hnfit: X must be a vector of real values.> hnfit ([0.2, 0.5+i], 0);
%!error<hnfit: X must be a vector of real values.> hnfit (ones (2,2) * 0.5, 0);
%!error<hnfit: MU must be a real scalar value.> ...
%! hnfit ([0.5, 1.2], [0, 1]);
%!error<hnfit: MU must be a real scalar value.> ...
%! hnfit ([0.5, 1.2], 5+i);
%!error<hnfit: X cannot contain values less than MU.> ...
%! hnfit ([1:5], 2);
%!error<hnfit: wrong value for ALPHA.> hnfit ([0.01:0.1:0.99], 0, 1.2);
%!error<hnfit: wrong value for ALPHA.> hnfit ([0.01:0.1:0.99], 0, i);
%!error<hnfit: wrong value for ALPHA.> hnfit ([0.01:0.1:0.99], 0, -1);
%!error<hnfit: wrong value for ALPHA.> hnfit ([0.01:0.1:0.99], 0, [0.05, 0.01]);
