temme2.js (7687B)
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_64_0/boost/math/special_functions/detail/ibeta_inverse.hpp}. The implementation has been modified for JavaScript. 22 * 23 * ```text 24 * Copyright John Maddock 2006. 25 * Copyright Paul A. Bristow 2007. 26 * 27 * Use, modification and distribution are subject to the 28 * Boost Software License, Version 1.0. (See accompanying file 29 * LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt) 30 * ``` 31 */ 32 33 'use strict'; 34 35 // MODULES // 36 37 var evalpoly = require( './../../../../base/tools/evalpoly' ); 38 var erfcinv = require( './../../../../base/special/erfcinv' ); 39 var abs = require( './../../../../base/special/abs' ); 40 var exp = require( './../../../../base/special/exp' ); 41 var ln = require( './../../../../base/special/ln' ); 42 var sqrt = require( './../../../../base/special/sqrt' ); 43 var sin = require( './../../../../base/special/sin' ); 44 var cos = require( './../../../../base/special/cos' ); 45 var temmeRootFinder = require( './root_finder.js'); 46 var newtonRaphsonIterate = require( './newton_raphson.js' ); 47 var polyval1 = require( './polyval_co1.js' ); 48 var polyval2 = require( './polyval_co2.js' ); 49 var polyval3 = require( './polyval_co3.js' ); 50 var polyval4 = require( './polyval_co4.js' ); 51 var polyval5 = require( './polyval_co5.js' ); 52 var polyval6 = require( './polyval_co6.js' ); 53 var polyval7 = require( './polyval_co7.js' ); 54 var polyval8 = require( './polyval_co8.js' ); 55 var polyval9 = require( './polyval_co9.js' ); 56 var polyval10 = require( './polyval_co10.js' ); 57 var polyval11 = require( './polyval_co11.js' ); 58 var polyval12 = require( './polyval_co12.js' ); 59 var polyval13 = require( './polyval_co13.js' ); 60 61 62 // VARIABLES // 63 64 // Workspaces for polynomial coefficients: 65 var workspace = [ 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 ]; 66 var terms = [ 0.0, 0.0, 0.0, 0.0 ]; 67 68 69 // MAIN // 70 71 /** 72 * Carries out the second method by Temme (described in section 3). 73 * 74 * ## References 75 * 76 * - Temme, N. M. 1992. "Incomplete Laplace Integrals: Uniform Asymptotic Expansion with Application to the Incomplete Beta Function." _Journal of Computational and Applied Mathematics_ 41 (1–2): 1638–63. doi:[10.1016/0377-0427(92)90244-R](https://doi.org/10.1016/0377-0427(92)90244-R). 77 * 78 * @private 79 * @param {number} z - function parameter 80 * @param {number} r - function parameter 81 * @param {number} theta - function parameter 82 * @returns {number} function value 83 */ 84 function temme2( z, r, theta ) { 85 var upper; 86 var lower; 87 var alpha; 88 var roots; 89 var eta0; 90 var eta; 91 var sc7; 92 var sc6; 93 var sc5; 94 var sc4; 95 var sc3; 96 var sc2; 97 var sc; 98 var lu; 99 var s2; 100 var c2; 101 var c; 102 var s; 103 var u; 104 var x; 105 106 // Get first estimate for eta, see Eq 3.9 and 3.10, but note there is a typo in Eq 3.10: 107 eta0 = erfcinv( 2.0*z ) / (-sqrt( r/2.0 )); 108 109 s = sin( theta ); 110 c = cos( theta ); 111 112 // Now we need to perturb eta0 to get eta, which we do by evaluating the polynomial in 1/r at the bottom of page 151, to do this we first need the error terms e1, e2 e3 which we'll fill into the array "terms". Since these terms are themselves polynomials, we'll need another array "workspace" to calculate those... 113 terms[ 0 ] = eta0; 114 115 // Some powers of sin(theta) cos(theta) that we'll need later: 116 s2 = s * s; 117 c2 = c * c; 118 sc = s * c; 119 sc2 = sc * sc; 120 sc3 = sc2 * sc; 121 sc4 = sc2 * sc2; 122 sc5 = sc2 * sc3; 123 sc6 = sc3 * sc3; 124 sc7 = sc4 * sc3; 125 126 // Calculate e1 and put it in terms[1], see the middle of page 151: 127 workspace[ 0 ] = ((2.0*s2) - 1.0) / ( 3.0*sc ); 128 workspace[ 1 ] = -polyval1( s2 ) / (36.0*sc2); 129 workspace[ 2 ] = polyval2( s2 ) / (1620.0*sc3); 130 workspace[ 3 ] = polyval3( s2 ) / (6480.0*sc4); 131 workspace[ 4 ] = polyval4( s2 ) / (90720.0*sc5); 132 workspace[ 5 ] = 0.0; 133 terms[ 1 ] = evalpoly( workspace, eta0 ); 134 135 // Now evaluate e2 and put it in terms[2]: 136 workspace[ 0 ] = -polyval5( s2 ) / (405.0*sc3); 137 workspace[ 1 ] = polyval6( s2 ) / (2592.0*sc4); 138 workspace[ 2 ] = -polyval7( s2 ) / (204120.0*sc5); 139 workspace[ 3 ] = -polyval8( s2 ) / (2099520.0*sc6); 140 workspace[ 4 ] = 0.0; 141 workspace[ 5 ] = 0.0; 142 terms[ 2 ] = evalpoly( workspace, eta0 ); 143 144 // And e3, and put it in terms[3]: 145 workspace[ 0 ] = polyval9( s2 ) / (102060.0*sc5); 146 workspace[ 1 ] = -polyval10( s2 ) / (20995200.0*sc6); 147 workspace[ 2 ] = polyval11( s2 ) / (36741600.0*sc7); 148 workspace[ 3 ] = 0.0; 149 workspace[ 4 ] = 0.0; 150 workspace[ 5 ] = 0.0; 151 terms[ 3 ] = evalpoly( workspace, eta0 ); 152 153 // Bring the correction terms together to evaluate eta; this is the last equation on page 151: 154 eta = evalpoly( terms, 1.0/r ); 155 156 // Now that we have eta we need to back solve for x, we seek the value of x that gives eta in Eq 3.2. The two methods used are described in section 5. Begin by defining a few variables we'll need later: 157 alpha = c / s; 158 alpha *= alpha; 159 lu = ( -( eta*eta )/( 2.0*s2 ) ) + ln(s2) + ( c2*ln(c2)/s2 ); 160 161 // Temme doesn't specify what value to switch on here, but this seems to work pretty well: 162 if ( abs(eta) < 0.7 ) { 163 // Small eta use the expansion Temme gives in the second equation of section 5, it's a polynomial in eta: 164 workspace[ 0 ] = s2; 165 workspace[ 1 ] = sc; 166 workspace[ 2 ] = (1.0-(2.0*s2)) / 3.0; 167 workspace[ 3 ] = polyval12( s2 ) / ( 36.0*sc ); 168 workspace[ 4 ] = polyval13( s2 ) / ( 270.0*sc2 ); 169 workspace[ 5 ] = 0.0; 170 x = evalpoly( workspace, eta ); 171 } else { 172 // If eta is large we need to solve Eq 3.2 more directly, begin by getting an initial approximation for x from the last equation on page 155, this is a polynomial in u: 173 u = exp( lu ); 174 workspace[ 0 ] = u; 175 workspace[ 1 ] = alpha; 176 workspace[ 2 ] = 0.0; 177 workspace[ 3 ] = 3.0 * alpha * ((3.0*alpha)+1.0) / 6.0; 178 workspace[ 4 ] = 4.0 * alpha * ((4.0*alpha)+1.0) * ((4.0*alpha)+2.0) / 24.0; // eslint-disable-line max-len 179 workspace[ 5 ] = 5.0 * alpha * ((5.0*alpha)+1.0) * ((5.0*alpha)+2.0) * ((5.0*alpha)+3.0) / 120.0; // eslint-disable-line max-len 180 x = evalpoly( workspace, u ); 181 182 // At this point we may or may not have the right answer, Eq-3.2 has two solutions for x for any given eta, however the mapping in 3.2 is 1:1 with the sign of eta and x-sin^2(theta) being the same. So we can check if we have the right root of 3.2, and if not switch x for 1-x. This transformation is motivated by the fact that the distribution is *almost* symmetric so 1-x will be in the right ball park for the solution: 183 if ( (x-s2)*eta < 0.0 ) { 184 x = 1.0 - x; 185 } 186 } 187 // The final step is a few Newton-Raphson iterations to clean up our approximation for x, this is pretty cheap in general, and very cheap compared to an incomplete beta evaluation. The limits set on x come from the observation that the sign of eta and x-sin^2(theta) are the same. 188 if ( eta < 0.0 ) { 189 lower = 0.0; 190 upper = s2; 191 } else { 192 lower = s2; 193 upper = 1.0; 194 } 195 // If our initial approximation is out of bounds then bisect: 196 if ( x < lower || x > upper ) { 197 x = (lower+upper) / 2.0; 198 } 199 roots = temmeRootFinder( -lu, alpha ); 200 201 // And iterate: 202 x = newtonRaphsonIterate( roots, x, lower, upper, 32, 100 ); 203 return x; 204 } 205 206 207 // EXPORTS // 208 209 module.exports = temme2;