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;