atinfinityplus.js (4646B)
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_65_0/boost/math/special_functions/detail/polygamma.hpp}. The implementation follows the original but has been modified for JavaScript. 22 * 23 * ```text 24 * (C) Copyright Nikhar Agrawal 2013. 25 * (C) Copyright Christopher Kormanyos 2013. 26 * (C) Copyright John Maddock 2014. 27 * (C) Copyright Paul Bristow 2013. 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 logger = require( 'debug' ); 40 var bernoulli = require( './../../../../base/special/bernoulli' ); 41 var factorial = require( './../../../../base/special/factorial' ); 42 var gammaln = require( './../../../../base/special/gammaln' ); 43 var abs = require( './../../../../base/special/abs' ); 44 var exp = require( './../../../../base/special/exp' ); 45 var pow = require( './../../../../base/special/pow' ); 46 var ln = require( './../../../../base/special/ln' ); 47 var MAX_LN = require( '@stdlib/constants/float64/max-ln' ); 48 var LN_TWO = require( '@stdlib/constants/float64/ln-two' ); 49 var EPS = require( '@stdlib/constants/float64/eps' ); 50 51 52 // VARIABLES // 53 54 var debug = logger( 'polygamma' ); 55 var MAX_SERIES_ITERATIONS = 1000000; 56 var MAX_FACTORIAL = 172; 57 58 59 // MAIN // 60 61 /** 62 * Evaluates the polygamma function for large values of `x` such as for `x > 400`. 63 * 64 * @private 65 * @param {PositiveInteger} n - derivative to evaluate 66 * @param {number} x - input 67 * @returns {number} (n+1)'th derivative 68 * @see {@link http://functions.wolfram.com/GammaBetaErf/PolyGamma2/06/02/0001/} 69 */ 70 function atinfinityplus( n, x ) { 71 var partTerm; // Value of current term excluding the Bernoulli number part 72 var xsquared; 73 var term; // Value of current term to be added to sum 74 var sum; // Current value of accumulated sum 75 var nlx; 76 var k2; 77 var k; 78 79 if ( n+x === x ) { 80 // If `x` is very large, just concentrate on the first part of the expression and use logs: 81 if ( n === 1 ) { 82 return 1.0 / x; 83 } 84 nlx = n * ln( x ); 85 if ( nlx < MAX_LN && n < MAX_FACTORIAL ) { 86 return ( (n & 1) ? 1.0 : -1.0 ) * factorial( n-1 ) * pow( x, -n ); 87 } 88 return ( (n & 1) ? 1.0 : -1.0 ) * exp( gammaln( n ) - ( n*ln(x) ) ); 89 } 90 xsquared = x * x; 91 92 // Start by setting `partTerm` to `(n-1)! / x^(n+1)`, which is common to both the first term of the series (with k = 1) and to the leading part. We can then get to the leading term by: `partTerm * (n + 2 * x) / 2` and to the first term in the series (excluding the Bernoulli number) by: `partTerm n * (n + 1) / (2x)`. If either the factorial would over- or the power term underflow, set `partTerm` to 0 and then we know that we have to use logs for the initial terms: 93 if ( n > MAX_FACTORIAL && n*n > MAX_LN ) { 94 partTerm = 0.0; 95 } else { 96 partTerm = factorial( n-1 ) * pow( x, -n-1 ); 97 } 98 if ( partTerm === 0.0 ) { 99 // Either `n` is very large, or the power term underflows. Set the initial values of `partTerm`, `term`, and `sum` via logs: 100 partTerm = gammaln(n) - ( (n+1) * ln(x) ); 101 sum = exp( partTerm + ln( n + (2.0*x) ) - LN_TWO ); 102 partTerm += ln( n*(n+1) ) - LN_TWO - ln(x); 103 partTerm = exp( partTerm ); 104 } else { 105 sum = partTerm * ( n+(2.0*x) ) / 2.0; 106 partTerm *= ( n*(n+1) ) / 2.0; 107 partTerm /= x; 108 } 109 // If the leading term is 0, so is the result: 110 if ( sum === 0.0 ) { 111 return sum; 112 } 113 for ( k = 1; ; ) { 114 term = partTerm * bernoulli( k*2 ); 115 sum += term; 116 117 // Normal termination condition: 118 if ( abs( term/sum ) < EPS ) { 119 break; 120 } 121 122 // Increment our counter, and move `partTerm` on to the next value: 123 k += 1; 124 k2 = 2 * k; 125 partTerm *= ( n+k2-2 ) * ( n-1+k2 ); 126 partTerm /= ( k2-1 ) * k2; 127 partTerm /= xsquared; 128 if ( k > MAX_SERIES_ITERATIONS ) { 129 debug( 'Series did not converge, closest value was: %d.', sum ); 130 return NaN; 131 } 132 } 133 if ( ( n-1 ) & 1 ) { 134 sum = -sum; 135 } 136 return sum; 137 } 138 139 140 // EXPORTS // 141 142 module.exports = atinfinityplus;