time-to-botec

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

ibeta_power_terms.js (7497B)


      1 /**
      2 * @license Apache-2.0
      3 *
      4 * Copyright (c) 2018 The Stdlib Authors.
      5 *
      6 * Licensed under the Apache License, Version 2.0 (the "License");
      7 * you may not use this file except in compliance with the License.
      8 * You may obtain a copy of the License at
      9 *
     10 *    http://www.apache.org/licenses/LICENSE-2.0
     11 *
     12 * Unless required by applicable law or agreed to in writing, software
     13 * distributed under the License is distributed on an "AS IS" BASIS,
     14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     15 * See the License for the specific language governing permissions and
     16 * limitations under the License.
     17 *
     18 *
     19 * ## Notice
     20 *
     21 * 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/beta.hpp}. The implementation has been modified for JavaScript.
     22 *
     23 * ```text
     24 * (C) Copyright John Maddock 2006.
     25 *
     26 * Use, modification and distribution are subject to the
     27 * Boost Software License, Version 1.0. (See accompanying file
     28 * LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
     29 * ```
     30 */
     31 
     32 'use strict';
     33 
     34 // MODULES //
     35 
     36 var lanczosSumExpGScaled = require( './../../../../base/special/gamma-lanczos-sum-expg-scaled' );
     37 var maxabs = require( './../../../../base/special/maxabs' );
     38 var minabs = require( './../../../../base/special/minabs' );
     39 var expm1 = require( './../../../../base/special/expm1' );
     40 var log1p = require( './../../../../base/special/log1p' );
     41 var sqrt = require( './../../../../base/special/sqrt' );
     42 var abs = require( './../../../../base/special/abs' );
     43 var exp = require( './../../../../base/special/exp' );
     44 var pow = require( './../../../../base/special/pow' );
     45 var min = require( './../../../../base/special/min' );
     46 var ln = require( './../../../../base/special/ln' );
     47 var MAX_LN = require( '@stdlib/constants/float64/max-ln' );
     48 var MIN_LN = require( '@stdlib/constants/float64/min-ln' );
     49 var G = require( '@stdlib/constants/float64/gamma-lanczos-g' );
     50 var E = require( '@stdlib/constants/float64/e' );
     51 
     52 
     53 // MAIN //
     54 
     55 /**
     56 * Computes the leading power terms in the incomplete beta function.
     57 *
     58 * When normalized,
     59 *
     60 * ```tex
     61 * \frac{ x^a y^b }{ \operatorname{Beta}(a,b) }
     62 * ```
     63 *
     64 * and otherwise
     65 *
     66 * ```tex
     67 * x^a y^b
     68 * ```
     69 *
     70 * ## Notes
     71 *
     72 * -   Almost all of the error in the incomplete beta comes from this function, particularly when \\( a \\) and \\( b \\) are large. Computing large powers are _hard_ though, and using logarithms just leads to horrendous cancellation errors.
     73 *
     74 * -   For \\( l1 * l2 > 0 \\) or \\( \operatorname{min}( a, b ) < 1 \\), the two power terms both go in the same direction (toward zero or toward infinity). In this case if either term overflows or underflows, then the product of the two must do so also. Alternatively, if one exponent is less than one, then we can't productively use it to eliminate overflow or underflow from the other term.  Problems with spurious overflow/underflow can't be ruled out. In this case, but it is _very_ unlikely since one of the power terms will evaluate to a number close to 1.
     75 *
     76 * -   If \\( \max( \abs(l1), \abs(l2) ) < 0.5 \\), both exponents are near one and both the exponents are greater than one, and, further, these two power terms tend in opposite directions (one toward zero, the other toward infinity), so we have to combine the terms to avoid any risk of overflow or underflow. We do this by moving one power term inside the other, we have:
     77 *
     78 *     ```tex
     79 *     (1 + l_1)^a \cdot (1 + l_2)^b \\
     80 *     = ((1 + l_1) \cdot (1 + l_2)^(b/a))^a \\
     81 *     = (1 + l_1 + l_3 + l_1*l_3)^a
     82 *     ```
     83 *
     84 *     and
     85 *
     86 *     ```tex
     87 *     l_3 = (1 + l_2)^(b/a) - 1 \\
     88 *     = \exp((b/a) * \ln(1 + l_2)) - 1
     89 *     ```
     90 *
     91 *     The tricky bit is deciding which term to move inside. By preference, we move the larger term inside, so that the size of the largest exponent is reduced.  However, that can only be done as long as l3 (see above) is also small.
     92 *
     93 * @private
     94 * @param {NonNegativeNumber} a - function parameter
     95 * @param {NonNegativeNumber} b - function parameter
     96 * @param {Probability} x - function parameter
     97 * @param {Probability} y - probability equal to `1-x`
     98 * @param {boolean} normalized - boolean indicating whether to evaluate the power terms of the regularized or non-regularized incomplete beta function
     99 * @returns {number} power terms
    100 */
    101 function ibetaPowerTerms( a, b, x, y, normalized ) {
    102 	var result;
    103 	var smallA;
    104 	var ratio;
    105 	var agh;
    106 	var bgh;
    107 	var cgh;
    108 	var l1;
    109 	var l2;
    110 	var l3;
    111 	var p1;
    112 	var b1;
    113 	var b2;
    114 	var c;
    115 	var l;
    116 
    117 	if ( !normalized ) {
    118 		// Can we do better here?
    119 		return pow( x, a ) * pow( y, b );
    120 	}
    121 	c = a + b;
    122 
    123 	// Combine power terms with Lanczos approximation:
    124 	agh = a + G - 0.5;
    125 	bgh = b + G - 0.5;
    126 	cgh = c + G - 0.5;
    127 	result = lanczosSumExpGScaled( c );
    128 	result /= lanczosSumExpGScaled( a ) * lanczosSumExpGScaled( b );
    129 
    130 	// Combine with the leftover terms from the Lanczos approximation:
    131 	result *= sqrt( bgh / E );
    132 	result *= sqrt( agh / cgh );
    133 
    134 	// `l1` and `l2` are the base of the exponents minus one:
    135 	l1 = ( ( x * b ) - ( y * agh ) ) / agh;
    136 	l2 = ( ( y * a ) - ( x * bgh ) ) / bgh;
    137 	if ( minabs( l1, l2 ) < 0.2 ) {
    138 		// When the base of the exponent is very near 1 we get really gross errors unless extra care is taken:
    139 		if ( l1 * l2 > 0 || min( a, b ) < 1 ) {
    140 			if ( abs(l1) < 0.1 ) {
    141 				result *= exp( a * log1p( l1 ) );
    142 			} else {
    143 				result *= pow( ( x*cgh ) / agh, a );
    144 			}
    145 			if ( abs(l2) < 0.1 ) {
    146 				result *= exp( b * log1p( l2 ) );
    147 			} else {
    148 				result *= pow((y * cgh) / bgh, b);
    149 			}
    150 		}
    151 		else if ( maxabs( l1, l2 ) < 0.5 ) {
    152 			smallA = a < b;
    153 			ratio = b / a;
    154 			if (
    155 				(smallA && (ratio * l2 < 0.1)) ||
    156 				(!smallA && (l1 / ratio > 0.1))
    157 			) {
    158 				l3 = expm1( ratio * log1p( l2 ) );
    159 				l3 = l1 + l3 + ( l3 * l1 );
    160 				l3 = a * log1p( l3 );
    161 				result *= exp( l3 );
    162 			}
    163 			else {
    164 				l3 = expm1( log1p( l1 ) / ratio );
    165 				l3 = l2 + l3 + ( l3 * l2 );
    166 				l3 = b * log1p( l3 );
    167 				result *= exp( l3 );
    168 			}
    169 		}
    170 		else if ( abs(l1) < abs(l2) ) {
    171 			// First base near 1 only:
    172 			l = ( a * log1p( l1 ) ) + ( b * ln( ( y*cgh ) / bgh ) );
    173 			if ( l <= MIN_LN || l >= MAX_LN ) {
    174 				l += ln(result);
    175 				if ( l >= MAX_LN ) {
    176 					return NaN;
    177 				}
    178 				result = exp( l );
    179 			} else {
    180 				result *= exp( l );
    181 			}
    182 		}
    183 		else {
    184 			// Second base near 1 only:
    185 			l = ( b * log1p( l2 ) ) + ( a * ln( (x*cgh) / agh ) );
    186 			if ( l <= MIN_LN || l >= MAX_LN ) {
    187 				l += ln(result);
    188 				if ( l >= MAX_LN ) {
    189 					return NaN;
    190 				}
    191 				result = exp( l );
    192 			} else {
    193 				result *= exp( l );
    194 			}
    195 		}
    196 	}
    197 	else {
    198 		// General case:
    199 		b1 = (x * cgh) / agh;
    200 		b2 = (y * cgh) / bgh;
    201 		l1 = a * ln(b1);
    202 		l2 = b * ln(b2);
    203 		if (
    204 			l1 >= MAX_LN ||
    205 			l1 <= MIN_LN ||
    206 			l2 >= MAX_LN ||
    207 			l2 <= MIN_LN
    208 		) {
    209 			// Oops, under/overflow, sidestep if we can:
    210 			if ( a < b ) {
    211 				p1 = pow( b2, b / a );
    212 				l3 = a * ( ln(b1) + ln(p1) );
    213 				if ( l3 < MAX_LN && l3 > MIN_LN ) {
    214 					result *= pow( p1 * b1, a );
    215 				} else {
    216 					l2 += l1 + ln(result);
    217 					if ( l2 >= MAX_LN ) {
    218 						return NaN;
    219 					}
    220 					result = exp( l2 );
    221 				}
    222 			}
    223 			else {
    224 				p1 = pow( b1, a / b );
    225 				l3 = ( ln(p1) + ln(b2) ) * b;
    226 				if ( l3 < MAX_LN && l3 > MIN_LN ) {
    227 					result *= pow( p1 * b2, b );
    228 				} else {
    229 					l2 += l1 + ln( result );
    230 					if (l2 >= MAX_LN) {
    231 						return NaN;
    232 					}
    233 					result = exp( l2 );
    234 				}
    235 			}
    236 		}
    237 		else {
    238 			// Finally the normal case:
    239 			result *= pow( b1, a ) * pow( b2, b );
    240 		}
    241 	}
    242 	return result;
    243 }
    244 
    245 
    246 // EXPORTS //
    247 
    248 module.exports = ibetaPowerTerms;