time-to-botec

Benchmark sampling in different programming languages
Log | Files | Refs | README

kernel_betaincinv.js (12813B)


      1 /* eslint-disable max-statements, max-lines */
      2 
      3 /**
      4 * @license Apache-2.0
      5 *
      6 * Copyright (c) 2018 The Stdlib Authors.
      7 *
      8 * Licensed under the Apache License, Version 2.0 (the "License");
      9 * you may not use this file except in compliance with the License.
     10 * You may obtain a copy of the License at
     11 *
     12 *    http://www.apache.org/licenses/LICENSE-2.0
     13 *
     14 * Unless required by applicable law or agreed to in writing, software
     15 * distributed under the License is distributed on an "AS IS" BASIS,
     16 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     17 * See the License for the specific language governing permissions and
     18 * limitations under the License.
     19 *
     20 *
     21 * ## Notice
     22 *
     23 * The original C++ code and copyright notice are from the [Boost library]{@link http://www.boost.org/doc/libs/1_62_0/boost/math/special_functions/detail/ibeta_inverse.hpp}. The implementation has been modified for JavaScript.
     24 *
     25 * ```text
     26 * Copyright John Maddock 2006.
     27 * Copyright Paul A. Bristow 2007.
     28 *
     29 * Use, modification and distribution are subject to the
     30 * Boost Software License, Version 1.0. (See accompanying file
     31 * LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
     32 * ```
     33 */
     34 
     35 'use strict';
     36 
     37 // MODULES //
     38 
     39 var evalpoly = require( './../../../../base/tools/evalpoly' );
     40 var betainc = require( './../../../../base/special/betainc' );
     41 var expm1 = require( './../../../../base/special/expm1' );
     42 var log1p = require( './../../../../base/special/log1p' );
     43 var asin = require( './../../../../base/special/asin' );
     44 var beta = require( './../../../../base/special/beta' );
     45 var sqrt = require( './../../../../base/special/sqrt' );
     46 var abs = require( './../../../../base/special/abs' );
     47 var exp = require( './../../../../base/special/exp' );
     48 var pow = require( './../../../../base/special/pow' );
     49 var sin = require( './../../../../base/special/sin' );
     50 var max = require( './../../../../base/special/max' );
     51 var min = require( './../../../../base/special/min' );
     52 var ln = require( './../../../../base/special/ln' );
     53 var FLOAT64_MIN_NORM = require( '@stdlib/constants/float64/smallest-normal' );
     54 var HALF_PI = require( '@stdlib/constants/float64/half-pi' );
     55 var EPSILON = require( '@stdlib/constants/float64/eps' );
     56 var findIBetaInvFromTDist = require( './find_ibeta_inv_from_t_dist.js' );
     57 var temme1 = require( './temme1.js' );
     58 var temme2 = require( './temme2.js' );
     59 var temme3 = require( './temme3.js' );
     60 var halleyIterate = require( './halley_iterate.js' );
     61 var ibetaRoots = require( './ibeta_roots.js' );
     62 
     63 
     64 // VARIABLES //
     65 
     66 var DIGITS = 32;
     67 var MAX_ITERATIONS = 1000;
     68 
     69 // Workspace for the polynomial coefficients:
     70 var terms = [ 0.0, 0.0, 0.0, 0.0, 0.0 ]; // WARNING: not thread safe
     71 
     72 
     73 // MAIN //
     74 
     75 /**
     76 * Calculates the inverse of the incomplete beta function.
     77 *
     78 * @private
     79 * @param {PositiveNumber} a - function parameter
     80 * @param {PositiveNumber} b - function parameter
     81 * @param {Probability} p - function parameter
     82 * @param {Probability} q - probability equal to `1 - p`
     83 * @returns {Array} two-element array holding function value `y` and `1-y`
     84 */
     85 function ibetaInvImp( a, b, p, q ) {
     86 	var digits;
     87 	var invert;
     88 	var lambda;
     89 	var lower;
     90 	var theta;
     91 	var upper;
     92 	var roots;
     93 	var maxv;
     94 	var minv;
     95 	var bet;
     96 	var ppa;
     97 	var tmp;
     98 	var xs2;
     99 	var ap1;
    100 	var bm1;
    101 	var fs;
    102 	var lx;
    103 	var ps;
    104 	var xg;
    105 	var xs;
    106 	var yp;
    107 	var a2;
    108 	var a3;
    109 	var b2;
    110 	var r;
    111 	var l;
    112 	var u;
    113 	var x;
    114 	var y;
    115 
    116 	// The flag invert is set to true if we swap a for b and p for q, in which case the result has to be subtracted from 1:
    117 	invert = false;
    118 
    119 	// Handle trivial cases first...
    120 	if ( q === 0.0 ) {
    121 		return [ 1.0, 0.0 ];
    122 	}
    123 	if ( p === 0.0 ) {
    124 		return [ 0.0, 1.0 ];
    125 	}
    126 	if ( a === 1.0 ) {
    127 		if ( b === 1.0 ) {
    128 			return [ p, 1.0-p ];
    129 		}
    130 		// Change things around so we can handle as b == 1 special case below:
    131 		tmp = b;
    132 		b = a;
    133 		a = tmp;
    134 
    135 		tmp = q;
    136 		q = p;
    137 		p = tmp;
    138 
    139 		invert = true;
    140 	}
    141 	// Depending upon which approximation method we use, we may end up calculating either x or y initially (where y = 1-x):
    142 	x = 0.0; // Set to a safe zero to avoid a
    143 
    144 	// For some of the methods we can put tighter bounds on the result than simply [0,1]:
    145 	lower = 0.0;
    146 	upper = 1.0;
    147 
    148 	// Student's T with b = 0.5 gets handled as a special case, swap around if the arguments are in the "wrong" order:
    149 	if ( a === 0.5 ) {
    150 		if ( b === 0.5 ) {
    151 			x = sin( p*HALF_PI );
    152 			x *= x;
    153 			y = sin( q*HALF_PI );
    154 			y *= y;
    155 			return [ x, y ];
    156 		}
    157 		if ( b > 0.5 ) {
    158 			tmp = b;
    159 			b = a;
    160 			a = tmp;
    161 
    162 			tmp = q;
    163 			q = p;
    164 			p = tmp;
    165 
    166 			invert = !invert;
    167 		}
    168 	}
    169 	// Select calculation method for the initial estimate:
    170 	if ( b === 0.5 && a >= 0.5 && p !== 1.0 ) {
    171 		// We have a Student's T distribution:
    172 		yp = {};
    173 		x = findIBetaInvFromTDist( a, p, yp );
    174 		y = yp.value;
    175 	}
    176 	else if ( b === 1.0 ) {
    177 		if ( p < q ) {
    178 			if ( a > 1.0 ) {
    179 				x = pow( p, 1.0/a );
    180 				y = -expm1( ln(p) / a );
    181 			} else {
    182 				x = pow( p, 1.0/a );
    183 				y = 1.0 - x;
    184 			}
    185 		} else {
    186 			x = exp( log1p(-q) / a );
    187 			y = -expm1( log1p(-q) / a );
    188 		}
    189 		if ( invert ) {
    190 			tmp = y;
    191 			y = x;
    192 			x = tmp;
    193 		}
    194 		return [ x, y ];
    195 	}
    196 	else if ( a+b > 5.0 ) {
    197 		// When a+b is large then we can use one of Prof Temme's asymptotic expansions, begin by swapping things around so that p < 0.5, we do this to avoid cancellations errors when p is large.
    198 		if ( p > 0.5 ) {
    199 			tmp = b;
    200 			b = a;
    201 			a = tmp;
    202 
    203 			tmp = q;
    204 			q = p;
    205 			p = tmp;
    206 
    207 			invert = !invert;
    208 		}
    209 		minv = min( a, b );
    210 		maxv = max( a, b );
    211 		if ( ( sqrt(minv) > (maxv-minv) ) && minv > 5.0 ) {
    212 			// When a and b differ by a small amount the curve is quite symmetrical and we can use an error function to approximate the inverse. This is the cheapest of the three Temme expansions, and the calculated value for x will never be much larger than p, so we don't have to worry about cancellation as long as p is small.
    213 			x = temme1( a, b, p );
    214 			y = 1.0 - x;
    215 		} else {
    216 			r = a + b;
    217 			theta = asin( sqrt( a/r ) );
    218 			lambda = minv / r;
    219 			if (
    220 				lambda >= 0.2 &&
    221 				lambda <= 0.8 &&
    222 				r >= 10
    223 			) {
    224 				// The second error function case is the next cheapest to use, it breaks down when the result is likely to be very small, if `a+b` is also small, but we can use a cheaper expansion there in any case. As before `x` won't be much larger than `p`, so as long as `p` is small we should be free of cancellation error.
    225 				ppa = pow( p, 1.0/a );
    226 				if ( ppa < 0.0025 && ( a+b ) < 200.0 ) {
    227 					x = ppa * pow( a*beta( a, b ), 1.0/a );
    228 				} else {
    229 					x = temme2( p, r, theta );
    230 				}
    231 				y = 1.0 - x;
    232 			} else {
    233 				// If we get here then a and b are very different in magnitude and we need to use the third of Temme's methods which involves inverting the incomplete gamma.  This is much more expensive than the other methods.  We also can only use this method when a > b, which can lead to cancellation errors if we really want y (as we will when x is close to 1), so a different expansion is used in that case.
    234 				if ( a < b ) {
    235 					tmp = b;
    236 					b = a;
    237 					a = tmp;
    238 
    239 					tmp = q;
    240 					q = p;
    241 					p = tmp;
    242 					invert = !invert;
    243 				}
    244 				// Try and compute the easy way first:
    245 				bet = 0.0;
    246 				if ( b < 2.0 ) {
    247 					bet = beta( a, b );
    248 				}
    249 				if ( bet === 0.0 ) {
    250 					y = 1.0;
    251 				} else {
    252 					y = pow( b*q*bet, 1.0/b );
    253 					x = 1.0 - y;
    254 				}
    255 			}
    256 			if ( y > 1.0e-5 ) {
    257 				x = temme3( a, b, p, q );
    258 				y = 1.0 - x;
    259 			}
    260 		}
    261 	}
    262 	else if ( a < 1.0 && b < 1.0 ) {
    263 		// Both a and b less than 1, there is a point of inflection at xs:
    264 		xs = ( 1.0-a ) / ( 2.0-a-b );
    265 
    266 		// Now we need to ensure that we start our iteration from the right side of the inflection point:
    267 		fs = betainc( xs, a, b ) - p;
    268 		if ( abs(fs)/p < EPSILON*3.0 ) {
    269 			// The result is at the point of inflection, best just return it:
    270 			if ( invert ) {
    271 				return [ 1.0-xs, xs ];
    272 			}
    273 			return [ xs, 1.0-xs ];
    274 		}
    275 		if ( fs < 0.0 ) {
    276 			tmp = b;
    277 			b = a;
    278 			a = tmp;
    279 
    280 			tmp = q;
    281 			q = p;
    282 			p = tmp;
    283 
    284 			invert = !invert;
    285 			xs = 1.0 - xs;
    286 		}
    287 		xg = pow( a*p*beta( a, b ), 1.0/a );
    288 		x = xg / ( 1.0+xg );
    289 		y = 1.0 / ( 1.0+xg );
    290 
    291 		// And finally we know that our result is below the inflection point, so set an upper limit on our search:
    292 		if ( x > xs ) {
    293 			x = xs;
    294 		}
    295 		upper = xs;
    296 	}
    297 	else if ( a > 1.0 && b > 1.0 ) {
    298 		// Small a and b, both greater than 1, there is a point of inflection at xs, and it's complement is xs2, we must always start our iteration from the right side of the point of inflection.
    299 		xs = ( a-1.0 ) / ( a+b-2.0 );
    300 		xs2 = ( b-1.0 ) / ( a+b-2.0 );
    301 		ps = betainc( xs, a, b ) - p;
    302 
    303 		if ( ps < 0.0 ) {
    304 			tmp = b;
    305 			b = a;
    306 			a = tmp;
    307 
    308 			tmp = q;
    309 			q = p;
    310 			p = tmp;
    311 
    312 			tmp = xs2;
    313 			xs2 = xs;
    314 			xs = tmp;
    315 
    316 			invert = !invert;
    317 		}
    318 		// Estimate x and y, using expm1 to get a good estimate for y when it's very small:
    319 		lx = ln( p*a*beta( a, b ) ) / a;
    320 		x = exp( lx );
    321 		y = ( x < 0.9 ) ? 1.0-x : -expm1(lx);
    322 
    323 		if ( b < a && x < 0.2 ) {
    324 			// Under a limited range of circumstances we can improve our estimate for x...
    325 			ap1 = a - 1.0;
    326 			bm1 = b - 1.0;
    327 			a2 = a * a;
    328 			a3 = a * a2;
    329 			b2 = b * b;
    330 			terms[ 0 ] = 0.0;
    331 			terms[ 1 ] = 1.0;
    332 			terms[ 2 ] = bm1 / ap1;
    333 			ap1 *= ap1;
    334 			terms[ 3 ] = bm1 * (3.0*a*b + 5.0*b + a2 - a - 4.0) / (2.0 * (a+2.0) * ap1); // eslint-disable-line max-len, no-mixed-operators
    335 			ap1 *= (a + 1.0);
    336 			terms[ 4 ] = bm1 * (33.0*a*b2 + 31.0*b2 + 8.0*a2*b2 - 30.0*a*b - 47.0*b + 11.0*a2*b + 6.0*a3*b + 18.0 + 4.0*a - a3 + a2*a2 - 10.0*a2); // eslint-disable-line max-len, no-mixed-operators
    337 			terms[ 4 ] /= (3.0 * (a+3.0) * (a+2.0) * ap1);
    338 			x = evalpoly( terms, x );
    339 		}
    340 		// Know that result is below the inflection point, so set an upper limit on search...
    341 		if ( x > xs ) {
    342 			x = xs;
    343 		}
    344 		upper = xs;
    345 	} else {
    346 		// Case: ( a <= 1 ) != ( b <= 1 ). If all else fails we get here, only one of a and b is above 1, and a+b is small.  Start by swapping things around so that we have a concave curve with b > a and no points of inflection in [0,1].  As long as we expect x to be small then we can use the simple (and cheap) power term to estimate x, but when we expect x to be large then this greatly underestimates x and leaves us trying to iterate "round the corner" which may take almost forever. We could use Temme's inverse gamma function case in that case, this works really rather well (albeit expensively) even though strictly speaking we're outside it's defined range. However it's expensive to compute, and an alternative approach which models the curve as a distorted quarter circle is much cheaper to compute, and still keeps the number of iterations required down to a reasonable level. With thanks to Prof. Temme for this suggestion.
    347 		if ( b < a ) {
    348 			tmp = b;
    349 			b = a;
    350 			a = tmp;
    351 
    352 			tmp = q;
    353 			q = p;
    354 			p = tmp;
    355 			invert = !invert;
    356 		}
    357 		if ( pow( p, 1.0/a ) < 0.5 ) {
    358 			x = pow( p*a*beta( a, b ), 1.0/a );
    359 			if ( x === 0.0 ) {
    360 				x = FLOAT64_MIN_NORM;
    361 			}
    362 			y = 1.0 - x;
    363 		}
    364 		// Case: pow(q, 1/b) < 0.1
    365 		else {
    366 			// Model a distorted quarter circle:
    367 			y = pow( 1.0-pow( p, b*beta( a, b ) ), 1.0/b );
    368 			if ( y === 0 ) {
    369 				y = FLOAT64_MIN_NORM;
    370 			}
    371 			x = 1.0 - y;
    372 		}
    373 	}
    374 	// Now we have a guess for x (and for y) we can set things up for iteration.  If x > 0.5 it pays to swap things round:
    375 	if ( x > 0.5 ) {
    376 		tmp = b;
    377 		b = a;
    378 		a = tmp;
    379 
    380 		tmp = q;
    381 		q = p;
    382 		p = tmp;
    383 
    384 		tmp = y;
    385 		y = x;
    386 		x = tmp;
    387 
    388 		invert = !invert;
    389 		l = 1.0 - upper;
    390 		u = 1.0 - lower;
    391 		lower = l;
    392 		upper = u;
    393 	}
    394 	// Lower bound for our search:  We're not interested in denormalized answers as these tend to take up lots of iterations, given that we can't get accurate derivatives in this area (they tend to be infinite).
    395 	if ( lower === 0 ) {
    396 		if ( invert ) {
    397 			// We're not interested in answers smaller than machine epsilon:
    398 			lower = EPSILON;
    399 			if ( x < lower ) {
    400 				x = lower;
    401 			}
    402 		} else {
    403 			lower = FLOAT64_MIN_NORM;
    404 		}
    405 		if ( x < lower ) {
    406 			x = lower;
    407 		}
    408 	}
    409 	// Figure out how many digits to iterate towards:
    410 	digits = DIGITS;
    411 	if ( x < 1.0e-50 && ( a < 1.0 || b < 1.0 ) ) {
    412 		// If we're in a region where the first derivative is very large, then we have to take care that the root-finder doesn't terminate prematurely.  We'll bump the precision up to avoid this, but we have to take care not to set the precision too high or the last few iterations will just thrash around and convergence may be slow in this case. Try 3/4 of machine epsilon:
    413 		digits *= 3;
    414 		digits /= 2;
    415 	}
    416 	// Now iterate, we can use either p or q as the target here depending on which is smaller:
    417 	roots = ibetaRoots( a, b, ( (p < q) ? p : q ), p >= q );
    418 	x = halleyIterate( roots, x, lower, upper, digits, MAX_ITERATIONS );
    419 
    420 	// Tidy up, if we "lower" was too high then zero is the best answer we have:
    421 	if ( x === lower ) {
    422 		x = 0.0;
    423 	}
    424 	if ( invert ) {
    425 		return [ 1.0-x, x ];
    426 	}
    427 	return [ x, 1.0-x ];
    428 }
    429 
    430 
    431 // EXPORTS //
    432 
    433 module.exports= ibetaInvImp;