########################################################################
##
## Copyright (C) 2017-2026 The Octave Project Developers
##
## See the file COPYRIGHT.md in the top-level directory of this
## distribution or <https://octave.org/copyright/>.
##
## 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
## <https://www.gnu.org/licenses/>.
##
########################################################################

## -*- texinfo -*-
## @deftypefn  {} {@var{m} =} mad (@var{x})
## @deftypefnx {} {@var{m} =} mad (@var{x}, @var{opt})
## @deftypefnx {} {@var{m} =} mad (@var{x}, @var{opt}, @var{dim})
## @deftypefnx {} {@var{m} =} mad (@var{x}, @var{opt}, @var{vecdim})
## @deftypefnx {} {@var{m} =} mad (@var{x}, @var{opt}, "all")
## Compute the mean or median absolute deviation (MAD) of the elements of
## @var{x}.
##
## The mean absolute deviation is defined as
##
## @example
## @var{mad} = mean (abs (@var{x} - mean (@var{x})))
## @end example
##
## The median absolute deviation is defined as
##
## @example
## @var{mad} = median (abs (@var{x} - median (@var{x})))
## @end example
##
## @code{mad} excludes NaN values from calculation similar to using the
## @code{omitnan} option in @code{mean} and @code{median}.
##
## If @var{x} is a vector, then @code{mad (@var{x})} returns the mean absolute
## deviation of the elements in @var{x}.
##
## If @var{x} is a matrix, then @code{mad (@var{x})} returns a row vector with
## each element containing the mean absolute deviation of the corresponding
## column in @var{x}.
##
## If @var{x} is an array, then @code{mad (@var{x})} computes the mean absolute
## deviation along the first non-singleton dimension of @var{x}.
##
## The optional argument @var{opt} specifies whether mean or median absolute
## deviation is calculated.  The default is @code{0} which corresponds to mean
## absolute deviation; a value of @code{1} corresponds to median absolute
## deviation.  Passing an empty input @code{[]} defaults to mean absolute
## deviation.
##
## The optional input @var{dim} specifies the dimension to operate on and must
## be a positive integer.  Specifying any singleton dimension of @var{x},
## including any dimension exceeding @code{ndims (@var{x})}, will return
## @code{zeros (size (@var{x}))}.
##
## Specifying the dimension as @var{vecdim}, a vector of non-repeating
## dimensions, will return the @code{mad} over the array slice defined by
## @var{vecdim}.  If @var{vecdim} indexes all dimensions of @var{x}, then it is
## equivalent to the option @qcode{"all"}.  Any dimension in @var{vecdim}
## greater than @code{ndims (@var{x})} is ignored.
##
## Specifying the dimension as @qcode{"all"} will cause @code{mad} to operate
## on all elements of @var{x}, and is equivalent to @code{mad (@var{x}(:))}.
##
## Usage Note: As a measure of dispersion, @code{mad} is less affected by
## outliers than @code{std}.
##
## @seealso{bounds, range, iqr, std, mean, median}
## @end deftypefn

function m = mad (x, opt = 0, dim)

  if (nargin < 1)
    print_usage ();
  endif

  if (! (isnumeric (x) || islogical (x)))
    error ("mad: X must be a numeric or logical array");
  endif

  if (isempty (opt))
    opt = 0;
  elseif (! isscalar (opt) || (opt != 0 && opt != 1))
    error ("mad: OPT must be 0 or 1");
  endif

  if (nargin < 3)
    ## Dim not provided

    ## First check for special empty case.
    if (isempty (x) && ndims (x) == 2 && size (x) == [0, 0])
      if (isa (x, "single"))
        m = NaN ("single");
      else
        m = NaN;
      endif
      return;
    endif

    ## Then find the first non-singleton dimension.
    (dim = find (size (x) != 1, 1)) || (dim = 1);
  endif

  if (opt == 0)
    fcn = @mean;
  else
    fcn = @median;
  endif

  m = fcn (abs (x - fcn (x, dim, "omitnan")), dim, "omitnan");

endfunction


%!assert (mad (123), 0)
%!assert (mad (Inf), NaN)
%!assert (mad ([3, Inf]),Inf)
%!assert (mad ([0, 0, 1, 2, 100]), 31.76)
%!assert (mad (single ([0, 0, 1, 2, 100])), single (31.76))
%!assert (mad ([0, 0, 1, 2, 100]'), 31.76)
%!assert (mad ([0, 0, 1, 2, 100], 1), 1)
%!assert (mad (single ([0, 0, 1, 2, 100]), 1), single (1))
%!assert (mad ([0, 0, 1, 2, 100]', 1), 1)
%!assert (mad (magic (4)), [4, 4, 4, 4])
%!assert (mad (magic (4), [], 2), [6; 2; 2; 6])
%!assert (mad (magic (4), 1), [2.5, 3.5, 3.5, 2.5])
%!assert (mad (magic (4), 1, 2), [5.5; 1.5; 1.5; 5.5])
%!assert (mad (magic (4), 0, 3), zeros (4))
%!assert (mad (magic (4), 1, 3), zeros (4))
%!assert (mad (cat (3, magic (4), magic (4))), 4 * ones (1, 4, 2))

## Test all and vecdim options
%!assert (mad (magic (4), 0, "all"), 4)
%!assert (mad (magic (4), 1, "all"), 4)
%!assert (mad (magic (4), 0, [1 2]), 4)
%!assert (mad (magic (4), 0, [1 3]), mad (magic (4), 0, 1))
%!assert (mad (magic (4), 0, [1 2 3]), 4)
%!assert (mad (magic (4), 1, [1 2]), 4)
%!assert (mad (magic (4), 1, [1 3]), mad (magic (4), 1, 1))
%!assert (mad (magic (4), 1, [1 2 3]), 4)
%!assert (mad (magic (4), 0, [3 4 99]), zeros (4))
%!assert (mad (magic (4), 1, [3 4 99]), zeros (4))

## Verify ignoring NaN values unless all NaN
%!assert (mad (NaN), NaN)
%!assert (mad (NaN (2)), NaN (1, 2))
%!assert (mad ([1, 2; 3, NaN]), [1, 0])
%!assert (mad ([1, 2; 3, NaN], [], 1), [1, 0])
%!assert (mad ([1, 2; 3, NaN], [], 2), [0.5; 0], eps)
%!assert (mad ([1, NaN; 3, NaN], [], 1), [1, NaN])
%!assert (mad ([1, NaN; 3, NaN], [], 2), [0; 0])

## Verify compatible empty handling
%!assert (mad ([]), NaN)
%!assert (mad ([], 0, 1), NaN (1,0))
%!assert (mad ([], 0, 2), NaN (0,1))
%!assert (mad ([], 0, 3), NaN (0,0))
%!assert (mad (single ([])), NaN ('single'))
%!assert (mad (ones (0, 1)), NaN)
%!assert (mad (ones (0, 1), 0, 1), NaN (1, 1))
%!assert (mad (ones (0, 1), 0, 2), NaN (0, 1))
%!assert (mad (ones (0, 1), 0, 3), NaN (0, 1))
%!assert (mad (ones (1, 0)), NaN)
%!assert (mad (ones (1, 0), 0, 1), NaN (1, 0))
%!assert (mad (ones (1, 0), 0, 2), NaN (1, 1))
%!assert (mad (ones (1, 0), 0, 3), NaN (1, 0))
%!assert (mad (ones (0, 0, 0)), NaN (1, 0, 0))
%!assert (mad (ones (1, 0, 0)), NaN (1, 1, 0))
%!assert (mad (ones (0, 1, 0)), NaN (1, 1, 0))
%!assert (mad (ones (0, 0, 0)), NaN (1, 0, 0))
%!assert (mad (ones (0, 1, 0), 0, 1), NaN (1, 1, 0))
%!assert (mad (ones (0, 1, 0), 0, 2), NaN (0, 1, 0))
%!assert (mad (ones (0, 1, 0), 0, 3), NaN (0, 1, 1))
%!assert (mad (ones (0, 1, 0), 0, 4), NaN (0, 1, 0))
%!assert (mad (ones (0, 2, 1, 0)), ones (1, 2, 1, 0))
%!assert (mad (ones (2, 0, 1, 0)), ones (1, 0, 1, 0))

## Test Inf handling
%!assert (mad ([3, 4, Inf]), Inf)
%!assert (mad ([Inf, 3, 4]), Inf)
%!assert (mad ([3, 4, Inf], 0), Inf)
%!assert (mad ([3, 4, Inf], 0, 1), [0, 0, NaN])
%!assert (mad ([3, 4, Inf], 0, 2), Inf)
%!assert (mad ([3, 4, Inf], 0, 3), [0, 0, NaN])
%!assert (mad ([3, 4, Inf]', 0), Inf)
%!assert (mad ([3, 4, Inf]', 0, 1), Inf)
%!assert (mad ([3, 4, Inf]', 0, 2), [0; 0; NaN])
%!assert (mad ([3, 4, Inf]', 0, 3), [0; 0; NaN])

%!assert (mad ([Inf, 3, 4], 1), 1)
%!assert (mad ([3, 4, Inf], 1), 1)
%!assert (mad ([3, 4, Inf], 1, 1), [0, 0, NaN])
%!assert (mad ([3, 4, Inf], 1, 2), 1)
%!assert (mad ([3, 4, Inf], 1, 3), [0, 0, NaN])
%!assert (mad ([3, 4, Inf]', 1), 1)
%!assert (mad ([3, 4, Inf]', 1, 1), 1)
%!assert (mad ([3, 4, Inf]', 1, 2), [0; 0; NaN])
%!assert (mad ([3, 4, Inf]', 1, 3), [0; 0; NaN])

%!assert (mad ([3, Inf, Inf], 1), Inf)
%!assert (mad ([3, 4, 5, Inf], 1), 1)
%!assert (mad ([3, 4, Inf, Inf], 1), Inf)
%!assert (mad ([3, Inf, Inf, Inf], 1), Inf)

%!assert <*65405> (mad ([-Inf, Inf]), NaN)
%!assert <*65405> (mad ([-Inf, Inf], 0), NaN)
%!assert <*65405> (mad ([-Inf, Inf], 1), NaN)
%!assert <*65405> (mad ([-Inf, Inf]', 0), NaN)
%!assert <*65405> (mad ([-Inf, Inf]', 1), NaN)
%!assert <*65405> (mad ([-Inf, Inf]', 0, 1), NaN)
%!assert <*65405> (mad ([-Inf, Inf]', 0, 2), [NaN; NaN])
%!assert <*65405> (mad ([-Inf, Inf]', 0, 3), [NaN; NaN])
%!assert <*65405> (mad ([-Inf, Inf]', 1, 1), NaN)
%!assert <*65405> (mad ([-Inf, Inf]', 1, 2), [NaN; NaN])
%!assert <*65405> (mad ([-Inf, Inf]', 1, 3), [NaN; NaN])
%!assert <*65405> (mad (Inf (2), 0), [NaN, NaN])
%!assert <*65405> (mad (Inf (2), 1), [NaN, NaN])
%!assert <*65405> (mad (Inf (2), 0, 1), [NaN, NaN])
%!assert <*65405> (mad (Inf (2), 0, 2), [NaN; NaN])
%!assert <*65405> (mad (Inf (2), 0, 3), NaN (2))
%!assert <*65405> (mad (Inf (2), 1, 1), [NaN, NaN])
%!assert <*65405> (mad (Inf (2), 1, 2), [NaN; NaN])
%!assert <*65405> (mad (Inf (2), 1, 3), NaN (2))

## Test input case insensitivity
%!assert (mad ([1 2 3], 0, "aLL"), 2/3, eps)
%!assert (mad ([1 2 3], 1, "aLL"), 1)

## Test input validation
%!error <Invalid call> mad ()
%!error <X must be a numeric or logical> mad (['A'; 'B'])
%!error <OPT must be 0 or 1> mad (1, 2)
