lgamma.js 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", {
  3. value: true
  4. });
  5. exports.createLgamma = void 0;
  6. var _index = require("../../plain/number/index.js");
  7. var _factory = require("../../utils/factory.js");
  8. var _number = require("../../utils/number.js");
  9. /* eslint-disable no-loss-of-precision */
  10. // References
  11. // ----------
  12. // [1] Hare, "Computing the Principal Branch of log-Gamma", Journal of Algorithms, 1997.
  13. // [2] https://math.stackexchange.com/questions/1338753/how-do-i-calculate-values-for-gamma-function-with-complex-arguments
  14. var name = 'lgamma';
  15. var dependencies = ['Complex', 'typed'];
  16. var createLgamma = /* #__PURE__ */(0, _factory.factory)(name, dependencies, function (_ref) {
  17. var Complex = _ref.Complex,
  18. typed = _ref.typed;
  19. // Stirling series is non-convergent, we need to use the recurrence `lgamma(z) = lgamma(z+1) - log z` to get
  20. // sufficient accuracy.
  21. //
  22. // These two values are copied from Scipy implementation:
  23. // https://github.com/scipy/scipy/blob/v1.8.0/scipy/special/_loggamma.pxd#L37
  24. var SMALL_RE = 7;
  25. var SMALL_IM = 7;
  26. /**
  27. * 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].
  28. *
  29. * If you cannot access the paper, can also get these values from the formula in [2].
  30. *
  31. * 1 / 12 = 0.00833333333333333333333333333333
  32. * 1 / 360 = 0.00277777777777777777777777777778
  33. * ...
  34. * 3617 / 133400 = 0.02955065359477124183006535947712
  35. */
  36. 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];
  37. /**
  38. * Logarithm of the gamma function for real, positive numbers and complex numbers,
  39. * using Lanczos approximation for numbers and Stirling series for complex numbers.
  40. *
  41. * Syntax:
  42. *
  43. * math.lgamma(n)
  44. *
  45. * Examples:
  46. *
  47. * math.lgamma(5) // returns 3.178053830347945
  48. * math.lgamma(0) // returns Infinity
  49. * math.lgamma(-0.5) // returns NaN
  50. * math.lgamma(math.i) // returns -0.6509231993018536 - 1.8724366472624294i
  51. *
  52. * See also:
  53. *
  54. * gamma
  55. *
  56. * @param {number | Complex} n A real or complex number
  57. * @return {number | Complex} The log gamma of `n`
  58. */
  59. return typed(name, {
  60. number: _index.lgammaNumber,
  61. Complex: lgammaComplex,
  62. BigNumber: function BigNumber() {
  63. throw new Error("mathjs doesn't yet provide an implementation of the algorithm lgamma for BigNumber");
  64. }
  65. });
  66. function lgammaComplex(n) {
  67. var TWOPI = 6.2831853071795864769252842; // 2*pi
  68. var LOGPI = 1.1447298858494001741434262; // log(pi)
  69. var REFLECTION = 0.1;
  70. if (n.isNaN()) {
  71. return new Complex(NaN, NaN);
  72. } else if (n.im === 0) {
  73. return new Complex((0, _index.lgammaNumber)(n.re), 0);
  74. } else if (n.re >= SMALL_RE || Math.abs(n.im) >= SMALL_IM) {
  75. return lgammaStirling(n);
  76. } else if (n.re <= REFLECTION) {
  77. // Reflection formula. see Proposition 3.1 in [1]
  78. var tmp = (0, _number.copysign)(TWOPI, n.im) * Math.floor(0.5 * n.re + 0.25);
  79. var a = n.mul(Math.PI).sin().log();
  80. var b = lgammaComplex(new Complex(1 - n.re, -n.im));
  81. return new Complex(LOGPI, tmp).sub(a).sub(b);
  82. } else if (n.im >= 0) {
  83. return lgammaRecurrence(n);
  84. } else {
  85. return lgammaRecurrence(n.conjugate()).conjugate();
  86. }
  87. }
  88. function lgammaStirling(z) {
  89. // formula ref in [2]
  90. // computation ref:
  91. // https://github.com/scipy/scipy/blob/v1.8.0/scipy/special/_loggamma.pxd#L101
  92. // left part
  93. // x (log(x) - 1) + 1/2 (log(2PI) - log(x))
  94. // => (x - 0.5) * log(x) - x + log(2PI) / 2
  95. var leftPart = z.sub(0.5).mul(z.log()).sub(z).add(_index.lnSqrt2PI);
  96. // right part
  97. var rz = new Complex(1, 0).div(z);
  98. var rzz = rz.div(z);
  99. var a = coeffs[0];
  100. var b = coeffs[1];
  101. var r = 2 * rzz.re;
  102. var s = rzz.re * rzz.re + rzz.im * rzz.im;
  103. for (var i = 2; i < 8; i++) {
  104. var tmp = b;
  105. b = -s * a + coeffs[i];
  106. a = r * a + tmp;
  107. }
  108. var rightPart = rz.mul(rzz.mul(a).add(b));
  109. // plus left and right
  110. return leftPart.add(rightPart);
  111. }
  112. function lgammaRecurrence(z) {
  113. // computation ref:
  114. // https://github.com/scipy/scipy/blob/v1.8.0/scipy/special/_loggamma.pxd#L78
  115. var signflips = 0;
  116. var sb = 0;
  117. var shiftprod = z;
  118. z = z.add(1);
  119. while (z.re <= SMALL_RE) {
  120. shiftprod = shiftprod.mul(z);
  121. var nsb = shiftprod.im < 0 ? 1 : 0;
  122. if (nsb !== 0 && sb === 0) signflips++;
  123. sb = nsb;
  124. z = z.add(1);
  125. }
  126. return lgammaStirling(z).sub(shiftprod.log()).sub(new Complex(0, signflips * 2 * Math.PI * 1));
  127. }
  128. });
  129. exports.createLgamma = createLgamma;