## Copyright (C) 2013 Pantxo Diribarne
## Copyright (C) 2022-2024 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{p} =} cdf (@var{name}, @var{x}, @var{A})
## @deftypefnx {statistics} {@var{p} =} cdf (@var{name}, @var{x}, @var{A}, @var{B})
## @deftypefnx {statistics} {@var{p} =} cdf (@var{name}, @var{x}, @var{A}, @var{B}, @var{C})
## @deftypefnx {statistics} {@var{p} =} cdf (@dots{}, @qcode{'upper'})
##
## Return the CDF of a univariate distribution evaluated at @var{x}.
##
## @code{cdf} is a wrapper for the univariate cumulative distribution functions
## available in the statistics package.  See the corresponding functions' help
## to learn the signification of the parameters after @var{x}.
##
## @code{@var{p} = cdf (@var{name}, @var{x}, @var{A})} returns the CDF for the
## one-parameter distribution family specified by @var{name} and the
## distribution parameter @var{A}, evaluated at the values in @var{x}.
##
## @code{@var{p} = cdf (@var{name}, @var{x}, @var{A}, @var{B})} returns the CDF
## for the two-parameter distribution family specified by @var{name} and the
## distribution parameters @var{A} and @var{B}, evaluated at the values in
## @var{x}.
##
## @code{@var{p} = cdf (@var{name}, @var{x}, @var{A}, @var{B}, @var{C})} returns
## the CDF for the three-parameter distribution family specified by @var{name}
## and the distribution parameters @var{A}, @var{B}, and @var{C}, evaluated at
## the values in @var{x}.
##
## @code{@var{p} = cdf (@dots{}, @qcode{'upper'})} returns the complement of the
## CDF using an algorithm that more accurately computes the extreme upper-tail
## probabilities.  @qcode{'upper'} can follow any of the input arguments in the
## previous syntaxes.
##
## @var{name} must be a char string of the name or the abbreviation of the
## desired cumulative distribution function as listed in the following table.
## The last column shows the number of required parameters that should be parsed
## after @var{x} to the desired CDF.  The optional input argument
## @qcode{'upper'} does not count in the required number of parameters.
##
## @multitable @columnfractions 0.4 0.2 0.3
## @headitem Distribution Name @tab Abbreviation @tab Input Parameters
## @item @qcode{'Beta'} @tab @qcode{'beta'} @tab 2
## @item @qcode{'Binomial'} @tab @qcode{'bino'} @tab 2
## @item @qcode{'Birnbaum-Saunders'} @tab @qcode{'bisa'} @tab 2
## @item @qcode{'Burr'} @tab @qcode{'burr'} @tab 3
## @item @qcode{'Cauchy'} @tab @qcode{'cauchy'} @tab 2
## @item @qcode{'Chi-squared'} @tab @qcode{'chi2'} @tab 1
## @item @qcode{'Extreme Value'} @tab @qcode{'ev'} @tab 2
## @item @qcode{'Exponential'} @tab @qcode{'exp'} @tab 1
## @item @qcode{'F-Distribution'} @tab @qcode{'f'} @tab 2
## @item @qcode{'Gamma'} @tab @qcode{'gam'} @tab 2
## @item @qcode{'Geometric'} @tab @qcode{'geo'} @tab 1
## @item @qcode{'Generalized Extreme Value'} @tab @qcode{'gev'} @tab 3
## @item @qcode{'Generalized Pareto'} @tab @qcode{'gp'} @tab 3
## @item @qcode{'Gumbel'} @tab @qcode{'gumbel'} @tab 2
## @item @qcode{'Half-normal'} @tab @qcode{'hn'} @tab 2
## @item @qcode{'Hypergeometric'} @tab @qcode{'hyge'} @tab 3
## @item @qcode{'Inverse Gaussian'} @tab @qcode{'invg'} @tab 2
## @item @qcode{'Laplace'} @tab @qcode{'laplace'} @tab 2
## @item @qcode{'Logistic'} @tab @qcode{'logi'} @tab 2
## @item @qcode{'Log-Logistic'} @tab @qcode{'logl'} @tab 2
## @item @qcode{'Lognormal'} @tab @qcode{'logn'} @tab 2
## @item @qcode{'Nakagami'} @tab @qcode{'naka'} @tab 2
## @item @qcode{'Negative Binomial'} @tab @qcode{'nbin'} @tab 2
## @item @qcode{'Noncentral F-Distribution'} @tab @qcode{'ncf'} @tab 3
## @item @qcode{'Noncentral Student T'} @tab @qcode{'nct'} @tab 2
## @item @qcode{'Noncentral Chi-Squared'} @tab @qcode{'ncx2'} @tab 2
## @item @qcode{'Normal'} @tab @qcode{'norm'} @tab 2
## @item @qcode{'Poisson'} @tab @qcode{'poiss'} @tab 1
## @item @qcode{'Rayleigh'} @tab @qcode{'rayl'} @tab 1
## @item @qcode{'Rician'} @tab @qcode{'rice'} @tab 2
## @item @qcode{'Student T'} @tab @qcode{'t'} @tab 1
## @item @qcode{'location-scale T'} @tab @qcode{'tls'} @tab 3
## @item @qcode{'Triangular'} @tab @qcode{'tri'} @tab 3
## @item @qcode{'Discrete Uniform'} @tab @qcode{'unid'} @tab 1
## @item @qcode{'Uniform'} @tab @qcode{'unif'} @tab 2
## @item @qcode{'Von Mises'} @tab @qcode{'vm'} @tab 2
## @item @qcode{'Weibull'} @tab @qcode{'wbl'} @tab 2
## @end multitable
##
## Distribution names are matched ignoring case, spaces and hyphens, so that
## @qcode{'Extreme Value'}, @qcode{'ExtremeValue'} and @qcode{'extreme-value'}
## all select the same distribution, and the same set of names is accepted by
## @code{cdf}, @code{pdf}, @code{icdf}, @code{random}, @code{makedist},
## @code{fitdist} and @code{mle}.
##
## This accepts more names than MATLAB.  MATLAB takes the spaced and the
## squashed spelling but refuses the hyphenated one, so
## @qcode{'Birnbaum-Saunders'} and @qcode{'Log-Logistic'} are errors there;
## Octave has always accepted them and continues to.  MATLAB also accepts
## @qcode{'tLocationScale'} in @code{makedist} while refusing it in
## @code{cdf} for the same distribution; Octave accepts it, and
## @qcode{'location-scale T'}, everywhere.  Code written against MATLAB's
## names therefore runs unchanged, but code relying on these names will not
## port back.
##
## @seealso{icdf, pdf, cdf, betacdf, binocdf, bisacdf, burrcdf, cauchycdf,
## chi2cdf, evcdf, expcdf, fcdf, gamcdf, geocdf, gevcdf, gpcdf, gumbelcdf,
## hncdf, hygecdf, invgcdf, laplacecdf, logicdf, loglcdf, logncdf, nakacdf,
## nbincdf, ncfcdf, nctcdf, ncx2cdf, normcdf, poisscdf, raylcdf, ricecdf, tcdf,
## tlscdf, tricdf, unidcdf, unifcdf, vmcdf, wblcdf}
## @end deftypefn

function p = cdf (name, x, varargin)

  ## implemented functions
  persistent allDF = { ...
    {'beta'     , 'Beta'},                      @betacdf,      2, ...
    {'bino'     , 'Binomial'},                  @binocdf,      2, ...
    {'bisa'     , 'Birnbaum-Saunders'},         @bisacdf,      2, ...
    {'burr'     , 'Burr'},                      @burrcdf,      3, ...
    {'cauchy'   , 'Cauchy'},                    @cauchycdf,    2, ...
    {'chi2'     , 'Chi-squared'},               @chi2cdf,      1, ...
    {'ev'       , 'Extreme Value'},             @evcdf,        2, ...
    {'exp'      , 'Exponential'},               @expcdf,       1, ...
    {'f'        , 'F-Distribution'},            @fcdf,         2, ...
    {'gam'      , 'Gamma'},                     @gamcdf,       2, ...
    {'geo'      , 'Geometric'},                 @geocdf,       1, ...
    {'gev'      , 'Generalized Extreme Value'}, @gevcdf,       3, ...
    {'gp'       , 'Generalized Pareto'},        @gpcdf,        3, ...
    {'gumbel'   , 'Gumbel'},                    @gumbelcdf,    2, ...
    {'hn'       , 'Half-normal'},               @hncdf,        2, ...
    {'hyge'     , 'Hypergeometric'},            @hygecdf,      3, ...
    {'invg'     , 'Inverse Gaussian'},          @invgcdf,      2, ...
    {'laplace'  , 'Laplace'},                   @laplacecdf,   2, ...
    {'logi'     , 'Logistic'},                  @logicdf,      2, ...
    {'logl'     , 'Log-Logistic'},              @loglcdf,      2, ...
    {'logn'     , 'Lognormal'},                 @logncdf,      2, ...
    {'naka'     , 'Nakagami'},                  @nakacdf,      2, ...
    {'nbin'     , 'Negative Binomial'},         @nbincdf,      2, ...
    {'ncf'      , 'Noncentral F-Distribution'}, @ncfcdf,       3, ...
    {'nct'      , 'Noncentral Student T'},      @nctcdf,       2, ...
    {'ncx2'     , 'Noncentral Chi-squared'},    @ncx2cdf,      2, ...
    {'norm'     , 'Normal'},                    @normcdf,      2, ...
    {'poiss'    , 'Poisson'},                   @poisscdf,     1, ...
    {'rayl'     , 'Rayleigh'},                  @raylcdf,      1, ...
    {'rice'     , 'Rician'},                    @ricecdf,      2, ...
    {'t'        , 'Student T'},                 @tcdf,         1, ...
    {'tls', 'location-scale T', 'tLocationScale'}, @tlscdf,       3, ...
    {'tri'      , 'Triangular'},                @tricdf,       3, ...
    {'unid'     , 'Discrete Uniform'},          @unidcdf,      1, ...
    {'unif'     , 'Uniform'},                   @unifcdf,      2, ...
    {'vm'       , 'Von Mises'},                 @vmcdf,        2, ...
    {'wbl'      , 'Weibull'},                   @wblcdf,       2};

  ## Check NAME being a char string
  if (! ischar (name))
    error ("cdf: distribution NAME must be a char string.");
  endif

  ## Check X being numeric and real
  if (! isnumeric (x))
    error ("cdf: X must be numeric.");
  elseif (! isreal (x))
    error ("cdf: values in X must be real.");
  endif

  ## Get number of arguments
  nargs = numel (varargin);

  ## Get available functions
  cdfnames = allDF(1:3:end);
  cdfhandl = allDF(2:3:end);
  cdf_args = allDF(3:3:end);

  ## Search for CDF function
  ## Match on the folded key so that every spelling of a name resolves
  key = __distname_key__ (name);
  idx = cellfun (@(x) any (strcmp (key, cellfun (@__distname_key__, x, ...
                                   'UniformOutput', false))), cdfnames);

  if (any (idx))

    if (nargs == cdf_args{idx} + 1)
      ## Check for "upper" option
      if (! strcmpi (varargin{nargs}, 'upper'))
        error ("cdf: invalid argument for upper tail.");
      else
        ## Check that all remaining distribution parameters are numeric
        if (! all (cellfun (@(x)isnumeric (x), (varargin([1:nargs-1])))))
          error ("cdf: distribution parameters must be numeric.");
        endif
        ## Call appropriate CDF with "upper" flag
        p = feval (cdfhandl{idx}, x, varargin{:});
      endif

    elseif (nargs == cdf_args{idx})
      ## Check that all distribution parameters are numeric
      if (! all (cellfun (@(x)isnumeric (x), (varargin))))
        error ("cdf: distribution parameters must be numeric.");
      endif
      ## Call appropriate CDF without "upper" flag
      p = feval (cdfhandl{idx}, x, varargin{:});

    else
      if (cdf_args{idx} == 1)
        error ("cdf: %s distribution requires 1 parameter.", name);
      else
        error ("cdf: %s distribution requires %d parameters.", ...
               name, cdf_args{idx});
      endif

    endif

  else
    error ("cdf: %s distribution is not implemented in Statistics.", name);
  endif

endfunction

## Test results
%!shared x
%! x = [1:5];
%!assert_equal (cdf ('Beta', x, 5, 2), betacdf (x, 5, 2))
%!assert_equal (cdf ('beta', x, 5, 2, 'upper'), betacdf (x, 5, 2, 'upper'))
%!assert_equal (cdf ('Binomial', x, 5, 2), binocdf (x, 5, 2))
%!assert_equal (cdf ('bino', x, 5, 2, 'upper'), binocdf (x, 5, 2, 'upper'))
%!assert_equal (cdf ('Birnbaum-Saunders', x, 5, 2), bisacdf (x, 5, 2))
%!assert_equal (cdf ('bisa', x, 5, 2, 'upper'), bisacdf (x, 5, 2, 'upper'))
%!assert_equal (cdf ('Burr', x, 5, 2, 2), burrcdf (x, 5, 2, 2))
%!assert_equal (cdf ('burr', x, 5, 2, 2, 'upper'), burrcdf (x, 5, 2, 2, 'upper'))
%!assert_equal (cdf ('Cauchy', x, 5, 2), cauchycdf (x, 5, 2))
%!assert_equal (cdf ('cauchy', x, 5, 2, 'upper'), cauchycdf (x, 5, 2, 'upper'))
%!assert_equal (cdf ('Chi-squared', x, 5), chi2cdf (x, 5))
%!assert_equal (cdf ('chi2', x, 5, 'upper'), chi2cdf (x, 5, 'upper'))
%!assert_equal (cdf ('Extreme Value', x, 5, 2), evcdf (x, 5, 2))
%!assert_equal (cdf ('ev', x, 5, 2, 'upper'), evcdf (x, 5, 2, 'upper'))
%!assert_equal (cdf ('Exponential', x, 5), expcdf (x, 5))
%!assert_equal (cdf ('exp', x, 5, 'upper'), expcdf (x, 5, 'upper'))
%!assert_equal (cdf ('F-Distribution', x, 5, 2), fcdf (x, 5, 2))
%!assert_equal (cdf ('f', x, 5, 2, 'upper'), fcdf (x, 5, 2, 'upper'))
%!assert_equal (cdf ('Gamma', x, 5, 2), gamcdf (x, 5, 2))
%!assert_equal (cdf ('gam', x, 5, 2, 'upper'), gamcdf (x, 5, 2, 'upper'))
%!assert_equal (cdf ('Geometric', x, 5), geocdf (x, 5))
%!assert_equal (cdf ('geo', x, 5, 'upper'), geocdf (x, 5, 'upper'))
%!assert_equal (cdf ('Generalized Extreme Value', x, 5, 2, 2), gevcdf (x, 5, 2, 2))
%!assert_equal (cdf ('gev', x, 5, 2, 2, 'upper'), gevcdf (x, 5, 2, 2, 'upper'))
%!assert_equal (cdf ('Generalized Pareto', x, 5, 2, 2), gpcdf (x, 5, 2, 2))
%!assert_equal (cdf ('gp', x, 5, 2, 2, 'upper'), gpcdf (x, 5, 2, 2, 'upper'))
%!assert_equal (cdf ('Gumbel', x, 5, 2), gumbelcdf (x, 5, 2))
%!assert_equal (cdf ('gumbel', x, 5, 2, 'upper'), gumbelcdf (x, 5, 2, 'upper'))
%!assert_equal (cdf ('Half-normal', x, 5, 2), hncdf (x, 5, 2))
%!assert_equal (cdf ('hn', x, 5, 2, 'upper'), hncdf (x, 5, 2, 'upper'))
%!assert_equal (cdf ('Hypergeometric', x, 5, 2, 2), hygecdf (x, 5, 2, 2))
%!assert_equal (cdf ('hyge', x, 5, 2, 2, 'upper'), hygecdf (x, 5, 2, 2, 'upper'))
%!assert_equal (cdf ('Inverse Gaussian', x, 5, 2), invgcdf (x, 5, 2))
%!assert_equal (cdf ('invg', x, 5, 2, 'upper'), invgcdf (x, 5, 2, 'upper'))
%!assert_equal (cdf ('Laplace', x, 5, 2), laplacecdf (x, 5, 2))
%!assert_equal (cdf ('laplace', x, 5, 2, 'upper'), laplacecdf (x, 5, 2, 'upper'))
%!assert_equal (cdf ('Logistic', x, 5, 2), logicdf (x, 5, 2))
%!assert_equal (cdf ('logi', x, 5, 2, 'upper'), logicdf (x, 5, 2, 'upper'))
%!assert_equal (cdf ('Log-Logistic', x, 5, 2), loglcdf (x, 5, 2))
%!assert_equal (cdf ('logl', x, 5, 2, 'upper'), loglcdf (x, 5, 2, 'upper'))
%!assert_equal (cdf ('Lognormal', x, 5, 2), logncdf (x, 5, 2))
%!assert_equal (cdf ('logn', x, 5, 2, 'upper'), logncdf (x, 5, 2, 'upper'))
%!assert_equal (cdf ('Nakagami', x, 5, 2), nakacdf (x, 5, 2))
%!assert_equal (cdf ('naka', x, 5, 2, 'upper'), nakacdf (x, 5, 2, 'upper'))
%!assert_equal (cdf ('Negative Binomial', x, 5, 2), nbincdf (x, 5, 2))
%!assert_equal (cdf ('nbin', x, 5, 2, 'upper'), nbincdf (x, 5, 2, 'upper'))
%!assert_equal (cdf ('Noncentral F-Distribution', x, 5, 2, 2), ncfcdf (x, 5, 2, 2))
%!assert_equal (cdf ('ncf', x, 5, 2, 2, 'upper'), ncfcdf (x, 5, 2, 2, 'upper'))
%!assert_equal (cdf ('Noncentral Student T', x, 5, 2), nctcdf (x, 5, 2))
%!assert_equal (cdf ('nct', x, 5, 2, 'upper'), nctcdf (x, 5, 2, 'upper'))
%!assert_equal (cdf ('Noncentral Chi-Squared', x, 5, 2), ncx2cdf (x, 5, 2))
%!assert_equal (cdf ('ncx2', x, 5, 2, 'upper'), ncx2cdf (x, 5, 2, 'upper'))
%!assert_equal (cdf ('Normal', x, 5, 2), normcdf (x, 5, 2))
%!assert_equal (cdf ('norm', x, 5, 2, 'upper'), normcdf (x, 5, 2, 'upper'))
%!assert_equal (cdf ('Poisson', x, 5), poisscdf (x, 5))
%!assert_equal (cdf ('poiss', x, 5, 'upper'), poisscdf (x, 5, 'upper'))
%!assert_equal (cdf ('Rayleigh', x, 5), raylcdf (x, 5))
%!assert_equal (cdf ('rayl', x, 5, 'upper'), raylcdf (x, 5, 'upper'))
%!assert_equal (cdf ('Rician', x, 5, 1), ricecdf (x, 5, 1))
%!assert_equal (cdf ('rice', x, 5, 1, 'upper'), ricecdf (x, 5, 1, 'upper'))
%!assert_equal (cdf ('Student T', x, 5), tcdf (x, 5))
%!assert_equal (cdf ('t', x, 5, 'upper'), tcdf (x, 5, 'upper'))
%!assert_equal (cdf ('location-scale T', x, 5, 1, 2), tlscdf (x, 5, 1, 2))
%!assert_equal (cdf ('tls', x, 5, 1, 2, 'upper'), tlscdf (x, 5, 1, 2, 'upper'))
%!assert_equal (cdf ('Triangular', x, 5, 2, 2), tricdf (x, 5, 2, 2))
%!assert_equal (cdf ('tri', x, 5, 2, 2, 'upper'), tricdf (x, 5, 2, 2, 'upper'))
%!assert_equal (cdf ('Discrete Uniform', x, 5), unidcdf (x, 5))
%!assert_equal (cdf ('unid', x, 5, 'upper'), unidcdf (x, 5, 'upper'))
%!assert_equal (cdf ('Uniform', x, 5, 2), unifcdf (x, 5, 2))
%!assert_equal (cdf ('unif', x, 5, 2, 'upper'), unifcdf (x, 5, 2, 'upper'))
%!assert_equal (cdf ('Von Mises', x, 5, 2), vmcdf (x, 5, 2))
%!assert_equal (cdf ('vm', x, 5, 2, 'upper'), vmcdf (x, 5, 2, 'upper'))
%!assert_equal (cdf ('Weibull', x, 5, 2), wblcdf (x, 5, 2))
%!assert_equal (cdf ('wbl', x, 5, 2, 'upper'), wblcdf (x, 5, 2, 'upper'))

## Test input validation
%!test
%! ## Every spelling of a name reaches the same distribution: case, spaces,
%! ## hyphens and underscores are all ignored.
%! for n = {'Extreme Value', 'ExtremeValue', 'extreme-value', 'EXTREME VALUE'}
%!   assert_equal (cdf (n{1}, 1, 2, 3), cdf ('ev', 1, 2, 3));
%! endfor

%!test
%! ## The name that makedist uses is accepted here too, and the reverse
%! assert_equal (cdf ('tLocationScale', 1, 2, 3, 4), cdf ('tls', 1, 2, 3, 4));

%!error<cdf: distribution NAME must be a char string.> cdf (1)
%!error<cdf: distribution NAME must be a char string.> cdf ({'beta'})
%!error<cdf: X must be numeric.> cdf ('beta', {[1 2 3 4 5]})
%!error<cdf: X must be numeric.> cdf ('beta', 'text')
%!error<cdf: values in X must be real.> cdf ('beta', 1+i)
%!error<cdf: distribution parameters must be numeric.> ...
%! cdf ('Beta', x, 'a', 2)
%!error<cdf: distribution parameters must be numeric.> ...
%! cdf ('Beta', x, 5, '')
%!error<cdf: distribution parameters must be numeric.> ...
%! cdf ('Beta', x, 5, {2})
%!error<cdf: chi2 distribution requires 1 parameter.> cdf ('chi2', x)
%!error<cdf: Beta distribution requires 2 parameters.> cdf ('Beta', x, 5)
%!error<cdf: Burr distribution requires 3 parameters.> cdf ('Burr', x, 5)
%!error<cdf: Burr distribution requires 3 parameters.> cdf ('Burr', x, 5, 2)
