diff scripts/specfun/betaincinv.m @ 24907:bd89440407aa stable

Incomplete beta function moved to a .m file, fixing accuracy and input validation (see bugs #51157 and #34405). It's inverse also has been rewritten as .m file. -- added libinterp/corefcn/__betainc_lentz__.cc added scripts/specfun/betainc.m added scripts/specfun/betaincinv.m changed libinterp/corefcn/module.mk changed liboctave/external/slatec-fn/module.mk changed liboctave/numeric/lo-specfun.cc changed liboctave/numeric/lo-specfun.h changed scripts/specfun/module.mk changed scripts/statistics/distributions/betainv.m changed scripts/statistics/distributions/binocdf.m removed libinterp/corefcn/betainc.cc removed liboctave/external/slatec-fn/betai.f removed liboctave/external/slatec-fn/dbetai.f removed liboctave/external/slatec-fn/xbetai.f removed liboctave/external/slatec-fn/xdbetai.f
author Michele Ginesi <michele.ginesi@gmail.com>
date Thu, 07 Sep 2017 16:13:16 +0200
parents
children c280560d9c96
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/scripts/specfun/betaincinv.m	Thu Sep 07 16:13:16 2017 +0200
@@ -0,0 +1,283 @@
+## Copyright (C) 2017 Michele Ginesi
+##
+## 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/>.
+
+## Author: Michele Ginesi <michele.ginesi@gmail.com>
+
+## -*- texinfo -*-
+## @deftypefn  {} {} betaincinv (@var{y}, @var{a}, @var{b})
+## @deftypefnx {} {} betaincinv (@var{y}, @var{a}, @var{b}, "lower")
+## @deftypefnx {} {} betaincinv (@var{y}, @var{a}, @var{b}, "upper")
+## Compute the inverse of the normalized incomplete beta function.
+##
+## The normalized incomplete beta function is defined as
+## @tex
+## $$
+##  I_x (a, b) = {1 \over {B(a,b)}} \displaystyle{\int_0^x t^{a-1} (1-t)^{b-1} dt}
+## $$
+## @end tex
+## @ifnottex
+##
+## @example
+## @group
+##                x
+##               /
+##              |
+## I_x (a, b) = | t^(a-1) (1-t)^(b-1) dt
+##              |
+##              /
+##             0
+## @end group
+## @end example
+##
+## @end ifnottex
+##
+## If two inputs are scalar, then @code{betaincinv (@var{y}, @var{a}, @var{b})}
+## is returned for each of the other inputs.
+##
+## If two or more inputs are not scalar, the sizes of them must agree, and
+## @code{betaincinv} is applied element-by-element.
+##
+## The variable @var{y} must be in the interval [0,1], while @var{a} and @var{b}
+## must be real and strictly positive.
+##
+## By default the inverse of the incomplete beta function integrated from 0
+## to @var{x} is computed.  If @qcode{"upper"} is given then the complementary
+## function integrated from @var{x} to 1 is inverted.
+##
+## The function is computed by standard Newton's method, by solving
+## @tex
+## $$
+##  y - I_x (a, b) = 0
+## $$
+## @end tex
+## @ifnottex
+##
+## @example
+## @group
+##
+## @var{y} - betainc (@var{x}, @var{a}, @var{b}) = 0
+##
+## @end group
+## @end example
+##
+## @end ifnottex
+##
+##
+## @seealso{beta, betainc, betaln}
+## @end deftypefn
+
+function [x] = betaincinv (y, a, b, tail = "lower")
+
+  if (nargin > 4 || nargin < 3)
+    print_usage ();
+  endif
+
+  if (! isscalar (y) || ! isscalar (a) || ! isscalar (b))
+    [err, y, a, b] = common_size (y, a, b);
+    if (err > 0)
+      error ("betaincinv: y, a must be of common size or scalars");
+    endif
+  endif
+
+  if (iscomplex (y) || iscomplex (a) || iscomplex (b))
+    error ("betaincinv: inputs must be real or integer");
+  endif
+
+  if (any (a <= 0) | any (b <= 0))
+    error ("betaincinv: a must be strictly positive");
+  endif
+
+  if (any (y > 1 | y < 0))
+    error ("betaincinv: y must be between 0 and 1");
+  endif
+
+  if (isinteger (y))
+    y = double (y);
+  endif
+
+  if (isinteger (a))
+    a = double (a);
+  endif
+
+  if (isinteger (b))
+    b = double (b);
+  endif
+
+
+  # Extract the size.
+  sz = size (y);
+  # Write the inputs as two column vectors.
+  y = y(:);
+  a = a(:);
+  b = b(:);
+  l = length (y);
+  # Initialise the output.
+  x = zeros (l, 1);
+
+  # If one of the inputs is of single type, also the output should be
+
+  if (strcmpi (class (y), "single") || strcmpi (class (a), "single") || strcmpi (class (b), "single"))
+    a = single (a);
+    b = single (b);
+    y = single (y);
+    x = single (x);
+  endif
+
+  # Parameters of the Newton method
+  maxit = 20;
+  tol = eps (class (y));
+
+  if (strcmpi (tail, "upper"))
+    p = 1 - y;
+    q = y;
+    x(y == 0) = 1;
+    x(y == 1) = 0;
+  elseif (strcmpi (tail, "lower"))
+    p = y;
+    q = 1 - y;
+    x(y == 0) = 0;
+    x(y == 1) = 1;
+  else
+    error ("betaincinv: invalid value for tail")
+  endif
+
+  # Trivial values have been already computed.
+  i_miss = ((y != 0) & (y != 1));
+
+  # We will invert the lower version for p < 0.5 and the upper otherwise.
+  i_low = (p < 0.5);
+  i_upp = (!i_low);
+
+  len_low = nnz (i_miss & i_low);
+  len_upp = nnz (i_miss & i_upp);
+
+  if (any (i_miss & i_low));
+    # Function and derivative of the lower version.
+    F = @(x, a, b, y) y - betainc (x, a, b);
+    JF = @(x, a, b) - real (exp ((a-1) .* log (x) + (b-1) .* log1p (-x) + ...
+        gammaln (a+b) - gammaln (a) - gammaln (b)));
+    # We compute the initial guess with a bisection method of 10 steps.
+    x0 = bisection_method (F, zeros (len_low,1), ones (len_low,1),...
+        a(i_low), b(i_low), p(i_low), 10);
+    x(i_low) = newton_method (F, JF, x0, a(i_low), b(i_low), p(i_low), ...
+        tol, maxit);
+  endif
+
+  if (any (i_miss & i_upp));
+    # Function and derivative of the lower version.
+    F = @(x, a, b, y) y - betainc (x, a, b, "upper");
+    JF = @(x, a, b) real (exp ((a-1) .* log (x) + (b-1) .* log1p (-x) + ...
+        gammaln (a+b) - gammaln (a) - gammaln (b)));
+    # We compute the initial guess with a bisection method of 10 steps.
+    x0 = bisection_method (F, zeros (len_upp,1), ones (len_upp,1),...
+        a(i_upp), b(i_upp), q(i_upp), 10);
+    x(i_upp) = newton_method (F, JF, x0, a(i_upp), b(i_upp), q(i_upp), ...
+        tol, maxit);
+  endif
+
+  ## Re-organize the output.
+
+  x = reshape (x, sz);
+
+endfunction
+
+
+## Subfunctions: Bisection and Newton Methods
+
+function xc = bisection_method (F, xl, xr, a, b, y, maxit)
+    F_l = feval (F, xl, a, b, y);
+    F_r = feval (F, xr, a, b, y);
+   for it = 1:maxit
+    xc = (xl + xr) / 2;
+    F_c = feval (F, xc, a, b, y);
+    flag_l = ((F_c .* F_r) < 0);
+    flag_r = ((F_c .* F_l) < 0);
+    flag_c = (F_c == 0);
+    xl(flag_l) = xc(flag_l);
+    xr(flag_r) = xc(flag_r);
+    xl(flag_c) = xr(flag_c) = xc(flag_c);
+    F_l(flag_l) = F_c(flag_l);
+    F_r(flag_r) = F_c(flag_r);
+    F_l(flag_c) = F_r(flag_c) = 0;
+   endfor
+endfunction
+
+function x = newton_method (F, JF, x0, a, b, y, tol, maxit);
+  l = length (y);
+  res = -feval (F, x0, a, b, y) ./ feval (JF, x0, a, b);
+  i_miss = (abs(res) >= tol * abs (x0));
+  x = x0;
+  it = 0;
+  while (any (i_miss) && (it < maxit))
+    it++;
+    x(i_miss) += res(i_miss);
+    res(i_miss) = - feval (F, x(i_miss), a(i_miss), b(i_miss), ...
+        y(i_miss)) ./ feval (JF, x(i_miss), a(i_miss), b(i_miss));
+    i_miss = (abs(res) >= tol * abs (x));
+  endwhile
+  x += res;
+endfunction
+
+
+## Test
+
+%!test
+%! x = linspace(0.1, 0.9, 11);
+%! a = [2, 3, 4];
+%! [x,a,b] = ndgrid (x,a,a);
+%! xx = betaincinv (betainc (x, a, b), a, b);
+%! assert (xx, x, 3e-15);
+
+%!test
+%! x = linspace(0.1, 0.9, 11);
+%! a = [2, 3, 4];
+%! [x,a,b] = ndgrid (x,a,a);
+%! xx = betaincinv (betainc (x, a, b, "upper"), a, b, "upper");
+%! assert (xx, x, 3e-15);
+
+%!test
+%! x = linspace(0.1, 0.9, 11);
+%! a = [0.1:0.1:1];
+%! [x,a,b] = ndgrid (x,a,a);
+%! xx = betaincinv (betainc (x, a, b), a, b);
+%! assert (xx, x, 3e-15);
+
+%!test
+%! x = linspace(0.1, 0.9, 11);
+%! a = [0.1:0.1:1];
+%! [x,a,b] = ndgrid (x,a,a);
+%! xx = betaincinv (betainc (x, a, b, "upper"), a, b, "upper");
+%! assert (xx, x, 3e-15);
+
+## Test on the conservation of the class
+%!assert (class (betaincinv (0.5, 1, 1)), "double")
+%!assert (class (betaincinv (single (0.5), 1, 1)), "single")
+%!assert (class (betaincinv (0.5, single (1), 1)), "single")
+%!assert (class (betaincinv (int8 (0), 1, 1)), "double")
+%!assert (class (betaincinv (0.5, int8 (1), 1)), "double")
+%!assert (class (betaincinv (int8 (0), single (1), 1)), "single")
+%!assert (class (betaincinv (single (0.5), int8 (1), 1)), "single")
+
+## Test input validation
+%!error betaincinv ()
+%!error betaincinv (1)
+%!error betaincinv (1,2,3,4)
+%!error betaincinv (1, "2")
+%!error betaincinv (0.5i, 1, 2)
+%!error betaincinv (0, 1i, 1)
+%!error betaincinv (0, 1, 1i)