123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137 |
- /* eslint-disable no-loss-of-precision */
- // References
- // ----------
- // [1] Hare, "Computing the Principal Branch of log-Gamma", Journal of Algorithms, 1997.
- // [2] https://math.stackexchange.com/questions/1338753/how-do-i-calculate-values-for-gamma-function-with-complex-arguments
- import { lgammaNumber, lnSqrt2PI } from '../../plain/number/index.js';
- import { factory } from '../../utils/factory.js';
- import { copysign } from '../../utils/number.js';
- var name = 'lgamma';
- var dependencies = ['Complex', 'typed'];
- export var createLgamma = /* #__PURE__ */factory(name, dependencies, _ref => {
- var {
- Complex,
- typed
- } = _ref;
- // Stirling series is non-convergent, we need to use the recurrence `lgamma(z) = lgamma(z+1) - log z` to get
- // sufficient accuracy.
- //
- // These two values are copied from Scipy implementation:
- // https://github.com/scipy/scipy/blob/v1.8.0/scipy/special/_loggamma.pxd#L37
- var SMALL_RE = 7;
- var SMALL_IM = 7;
- /**
- * The coefficients are B[2*n]/(2*n*(2*n - 1)) where B[2*n] is the (2*n)th Bernoulli number. See (1.1) in [1].
- *
- * If you cannot access the paper, can also get these values from the formula in [2].
- *
- * 1 / 12 = 0.00833333333333333333333333333333
- * 1 / 360 = 0.00277777777777777777777777777778
- * ...
- * 3617 / 133400 = 0.02955065359477124183006535947712
- */
- var coeffs = [-2.955065359477124183e-2, 6.4102564102564102564e-3, -1.9175269175269175269e-3, 8.4175084175084175084e-4, -5.952380952380952381e-4, 7.9365079365079365079e-4, -2.7777777777777777778e-3, 8.3333333333333333333e-2];
- /**
- * Logarithm of the gamma function for real, positive numbers and complex numbers,
- * using Lanczos approximation for numbers and Stirling series for complex numbers.
- *
- * Syntax:
- *
- * math.lgamma(n)
- *
- * Examples:
- *
- * math.lgamma(5) // returns 3.178053830347945
- * math.lgamma(0) // returns Infinity
- * math.lgamma(-0.5) // returns NaN
- * math.lgamma(math.i) // returns -0.6509231993018536 - 1.8724366472624294i
- *
- * See also:
- *
- * gamma
- *
- * @param {number | Complex} n A real or complex number
- * @return {number | Complex} The log gamma of `n`
- */
- return typed(name, {
- number: lgammaNumber,
- Complex: lgammaComplex,
- BigNumber: function BigNumber() {
- throw new Error("mathjs doesn't yet provide an implementation of the algorithm lgamma for BigNumber");
- }
- });
- function lgammaComplex(n) {
- var TWOPI = 6.2831853071795864769252842; // 2*pi
- var LOGPI = 1.1447298858494001741434262; // log(pi)
- var REFLECTION = 0.1;
- if (n.isNaN()) {
- return new Complex(NaN, NaN);
- } else if (n.im === 0) {
- return new Complex(lgammaNumber(n.re), 0);
- } else if (n.re >= SMALL_RE || Math.abs(n.im) >= SMALL_IM) {
- return lgammaStirling(n);
- } else if (n.re <= REFLECTION) {
- // Reflection formula. see Proposition 3.1 in [1]
- var tmp = copysign(TWOPI, n.im) * Math.floor(0.5 * n.re + 0.25);
- var a = n.mul(Math.PI).sin().log();
- var b = lgammaComplex(new Complex(1 - n.re, -n.im));
- return new Complex(LOGPI, tmp).sub(a).sub(b);
- } else if (n.im >= 0) {
- return lgammaRecurrence(n);
- } else {
- return lgammaRecurrence(n.conjugate()).conjugate();
- }
- }
- function lgammaStirling(z) {
- // formula ref in [2]
- // computation ref:
- // https://github.com/scipy/scipy/blob/v1.8.0/scipy/special/_loggamma.pxd#L101
- // left part
- // x (log(x) - 1) + 1/2 (log(2PI) - log(x))
- // => (x - 0.5) * log(x) - x + log(2PI) / 2
- var leftPart = z.sub(0.5).mul(z.log()).sub(z).add(lnSqrt2PI);
- // right part
- var rz = new Complex(1, 0).div(z);
- var rzz = rz.div(z);
- var a = coeffs[0];
- var b = coeffs[1];
- var r = 2 * rzz.re;
- var s = rzz.re * rzz.re + rzz.im * rzz.im;
- for (var i = 2; i < 8; i++) {
- var tmp = b;
- b = -s * a + coeffs[i];
- a = r * a + tmp;
- }
- var rightPart = rz.mul(rzz.mul(a).add(b));
- // plus left and right
- return leftPart.add(rightPart);
- }
- function lgammaRecurrence(z) {
- // computation ref:
- // https://github.com/scipy/scipy/blob/v1.8.0/scipy/special/_loggamma.pxd#L78
- var signflips = 0;
- var sb = 0;
- var shiftprod = z;
- z = z.add(1);
- while (z.re <= SMALL_RE) {
- shiftprod = shiftprod.mul(z);
- var nsb = shiftprod.im < 0 ? 1 : 0;
- if (nsb !== 0 && sb === 0) signflips++;
- sb = nsb;
- z = z.add(1);
- }
- return lgammaStirling(z).sub(shiftprod.log()).sub(new Complex(0, signflips * 2 * Math.PI * 1));
- }
- });
|