nearzero.js (3987B)
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 factorial = require( './../../../../base/special/factorial' ); 41 var zeta = require( './../../../../base/special/riemann-zeta' ); 42 var abs = require( './../../../../base/special/abs' ); 43 var pow = require( './../../../../base/special/pow' ); 44 var NINF = require( '@stdlib/constants/float64/ninf' ); 45 var PINF = require( '@stdlib/constants/float64/pinf' ); 46 var EPS = require( '@stdlib/constants/float64/eps' ); 47 var MAX = require( '@stdlib/constants/float64/max' ); 48 49 50 // VARIABLES // 51 52 var debug = logger( 'polygamma' ); 53 var MAX_SERIES_ITERATIONS = 1000000; 54 55 56 // MAIN // 57 58 /** 59 * Evaluates the polygamma function near zero. 60 * 61 * ## Notes 62 * 63 * - If we take this [expansion][1] for `polygamma` and substitute in this [expression][2] for `polygamma(n, 1)`, we get an alternating series for polygamma when `x` is small in terms of zeta functions of integer arguments (which are easy to evaluate, at least when the integer is even). 64 * 65 * [1]: http://functions.wolfram.com/06.15.06.0003.02 66 * [2]: http://functions.wolfram.com/06.15.03.0009.01 67 * 68 * 69 * @private 70 * @param {PositiveInteger} n - derivative to evaluate 71 * @param {number} x - input value 72 * @returns {number} (n+1)'th derivative 73 */ 74 function nearzero( n, x ) { 75 var factorialPart; 76 var prefix; 77 var scale; 78 var term; 79 var sum; 80 var AX; 81 var k; 82 83 // In order to avoid spurious overflow, save the `n!` term for later, and rescale at the end: 84 scale = factorial( n ); 85 86 // "factorialPart" contains everything except the zeta function evaluations in each term: 87 factorialPart = 1; 88 89 // "prefix" is what we'll be adding the accumulated sum to, it will be `n! / z^(n+1)`, but since we're scaling by `n!` it is just `1 / z^(n+1)` for now: 90 prefix = pow( x, n+1 ); 91 if ( prefix === 0.0 ) { 92 return PINF; 93 } 94 prefix = 1.0 / prefix; 95 96 // First term in the series is necessarily `< zeta(2) < 2`, so ignore the sum if it will have no effect on the result: 97 if ( prefix > 2.0/EPS ) { 98 if ( n & 1 ) { 99 return ( AX/prefix < scale ) ? PINF : prefix * scale; 100 } 101 return ( AX/prefix < scale ) ? NINF : -prefix * scale; 102 } 103 sum = prefix; 104 for ( k = 0; ; ) { 105 // Get the k'th term: 106 term = factorialPart * zeta( k+n+1 ); 107 sum += term; 108 109 // Termination condition: 110 if ( abs( term ) < abs(sum * EPS ) ) { 111 break; 112 } 113 // Move on `k` and `factorialPart`: 114 k += 1; 115 factorialPart *= (-x * (n+k)) / k; 116 117 // Last chance exit: 118 if ( k > MAX_SERIES_ITERATIONS ) { 119 debug( 'Series did not converge, best value is %d.', sum ); 120 return NaN; 121 } 122 } 123 // We need to multiply by the scale, at each stage checking for overflow: 124 if ( MAX/scale < sum ) { 125 return PINF; 126 } 127 sum *= scale; 128 return ( n & 1 ) ? sum : -sum; 129 } 130 131 132 // EXPORTS // 133 134 module.exports = nearzero;