@numero-uno-jpn/rstats 0.5.0 → 0.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +101 -0
- package/README.ja.md +36 -20
- package/README.md +34 -18
- package/THIRD_PARTY_NOTICES.md +25 -5
- package/dist/chunks/chunk-2JCIB5H2.js +2 -0
- package/dist/chunks/chunk-7JVSDGQQ.js +2 -0
- package/dist/chunks/chunk-7NK4SA6B.js +2 -0
- package/dist/chunks/chunk-AJYQNFXK.js +2 -0
- package/dist/chunks/chunk-DRNMIEYH.js +2 -0
- package/dist/chunks/chunk-NAYDS7CL.js +2 -0
- package/dist/chunks/chunk-NOCEAM44.js +2 -0
- package/dist/chunks/chunk-QNAIK4PY.js +2 -0
- package/dist/chunks/chunk-TPC6MTYC.js +2 -0
- package/dist/chunks/chunk-UGUQYWRQ.js +2 -0
- package/dist/chunks/chunk-XM2TVD63.js +2 -0
- package/dist/chunks/chunk-Y4IZV7XK.js +2 -0
- package/dist/chunks/chunk-ZWJ42QN6.js +2 -0
- package/dist/descriptive/basic.d.ts +73 -0
- package/dist/descriptive/basic.js +256 -0
- package/dist/descriptive/cov.d.ts +39 -0
- package/dist/descriptive/cov.js +105 -0
- package/dist/descriptive/hist.d.ts +64 -0
- package/dist/descriptive/hist.js +304 -0
- package/dist/descriptive/moments.d.ts +63 -0
- package/dist/descriptive/moments.js +158 -0
- package/dist/descriptive/quantile.d.ts +50 -0
- package/dist/descriptive/quantile.js +131 -0
- package/dist/descriptive/rank.d.ts +23 -0
- package/dist/descriptive/rank.js +72 -0
- package/dist/descriptive/table.d.ts +54 -0
- package/dist/descriptive/table.js +160 -0
- package/dist/descriptive.js +2 -0
- package/dist/distributions/arcsine.d.ts +24 -0
- package/dist/distributions/arcsine.js +73 -0
- package/dist/distributions/invgamma.d.ts +29 -0
- package/dist/distributions/invgamma.js +85 -0
- package/dist/distributions/kumaraswamy.d.ts +17 -0
- package/dist/distributions/kumaraswamy.js +96 -0
- package/dist/distributions/laplace.d.ts +23 -0
- package/dist/distributions/laplace.js +69 -0
- package/dist/distributions/multinomial.d.ts +24 -0
- package/dist/distributions/multinomial.js +70 -0
- package/dist/distributions/nbeta.js +28 -10
- package/dist/distributions/nchisq.js +20 -3
- package/dist/distributions/pareto.d.ts +23 -0
- package/dist/distributions/pareto.js +76 -0
- package/dist/distributions/summary.d.ts +25 -0
- package/dist/distributions/summary.js +289 -0
- package/dist/distributions/triangular.d.ts +25 -0
- package/dist/distributions/triangular.js +87 -0
- package/dist/distributions.js +2 -0
- package/dist/entries/descriptive.d.ts +7 -0
- package/dist/entries/descriptive.js +8 -0
- package/dist/entries/distributions.d.ts +41 -0
- package/dist/entries/distributions.js +41 -0
- package/dist/entries/linalg.d.ts +11 -0
- package/dist/entries/linalg.js +11 -0
- package/dist/entries/numerics.d.ts +8 -0
- package/dist/entries/numerics.js +6 -0
- package/dist/entries/special.d.ts +9 -0
- package/dist/entries/special.js +9 -0
- package/dist/entries/tests.d.ts +21 -0
- package/dist/entries/tests.js +20 -0
- package/dist/htest/ansariTest.d.ts +13 -0
- package/dist/htest/ansariTest.js +80 -0
- package/dist/htest/aov.d.ts +85 -0
- package/dist/htest/aov.js +178 -0
- package/dist/htest/binomTest.d.ts +36 -0
- package/dist/htest/binomTest.js +121 -0
- package/dist/htest/cor.d.ts +14 -0
- package/dist/htest/cor.js +4 -3
- package/dist/htest/core/ansari.d.ts +18 -0
- package/dist/htest/core/ansari.js +121 -0
- package/dist/htest/core/kolmogorov.d.ts +26 -0
- package/dist/htest/core/kolmogorov.js +221 -0
- package/dist/htest/core/mantelhaen.d.ts +26 -0
- package/dist/htest/core/mantelhaen.js +145 -0
- package/dist/htest/core/shapiro.d.ts +24 -0
- package/dist/htest/core/shapiro.js +142 -0
- package/dist/htest/core/smirnov.d.ts +20 -0
- package/dist/htest/core/smirnov.js +75 -0
- package/dist/htest/fisherTest.js +134 -120
- package/dist/htest/groups.d.ts +20 -0
- package/dist/htest/groups.js +62 -0
- package/dist/htest/ksTest.d.ts +31 -0
- package/dist/htest/ksTest.js +106 -0
- package/dist/htest/mantelhaenTest.d.ts +26 -0
- package/dist/htest/mantelhaenTest.js +196 -0
- package/dist/htest/mcnemarTest.d.ts +26 -0
- package/dist/htest/mcnemarTest.js +99 -0
- package/dist/htest/onewayTest.d.ts +26 -0
- package/dist/htest/onewayTest.js +97 -0
- package/dist/htest/pairwise.d.ts +64 -0
- package/dist/htest/pairwise.js +152 -0
- package/dist/htest/poissonTest.d.ts +35 -0
- package/dist/htest/poissonTest.js +170 -0
- package/dist/htest/propTest.d.ts +37 -0
- package/dist/htest/propTest.js +164 -0
- package/dist/htest/rankTests.d.ts +52 -0
- package/dist/htest/rankTests.js +234 -0
- package/dist/htest/types.d.ts +2 -0
- package/dist/htest/types.js +6 -0
- package/dist/htest/varTest.d.ts +48 -0
- package/dist/htest/varTest.js +118 -0
- package/dist/htest/wilcoxTest.js +136 -174
- package/dist/index.d.ts +6 -50
- package/dist/index.js +8 -50
- package/dist/integrate/quadpack.d.ts +29 -0
- package/dist/integrate/quadpack.js +525 -0
- package/dist/internal/seq.d.ts +5 -0
- package/dist/internal/seq.js +14 -0
- package/dist/internal/uniroot.js +10 -71
- package/dist/linalg/lu.d.ts +45 -0
- package/dist/linalg/lu.js +88 -0
- package/dist/linalg/ops.d.ts +90 -0
- package/dist/linalg/ops.js +320 -0
- package/dist/linalg.js +2 -0
- package/dist/models/lm.d.ts +98 -0
- package/dist/models/lm.js +187 -0
- package/dist/numerics/deriv.d.ts +37 -0
- package/dist/numerics/deriv.js +150 -0
- package/dist/numerics/integrate.d.ts +91 -0
- package/dist/numerics/integrate.js +264 -0
- package/dist/numerics/interp.d.ts +103 -0
- package/dist/numerics/interp.js +581 -0
- package/dist/numerics/ode.d.ts +23 -0
- package/dist/numerics/ode.js +59 -0
- package/dist/numerics.js +2 -0
- package/dist/optim/localmin.d.ts +16 -0
- package/dist/optim/localmin.js +97 -0
- package/dist/random/beta.js +1 -1
- package/dist/random/binomial.js +1 -1
- package/dist/random/continuous.js +1 -1
- package/dist/random/extra.d.ts +33 -0
- package/dist/random/extra.js +62 -0
- package/dist/random/gamma.js +1 -1
- package/dist/random/hyper.js +1 -1
- package/dist/random/mt.js +0 -1
- package/dist/random/poisson.js +0 -1
- package/dist/random/rank.js +1 -1
- package/dist/random/rng.js +1 -1
- package/dist/random/sample.js +1 -1
- package/dist/rstats.js +2 -2
- package/dist/special/besselIK.d.ts +38 -0
- package/dist/special/besselIK.js +315 -0
- package/dist/special/besselJY.d.ts +32 -0
- package/dist/special/besselJY.js +508 -0
- package/dist/special/besselSeries.d.ts +8 -0
- package/dist/special/besselSeries.js +24 -0
- package/dist/special/beta.js +22 -5
- package/dist/special/choose.d.ts +75 -0
- package/dist/special/choose.js +233 -0
- package/dist/special/erf.d.ts +28 -2
- package/dist/special/erf.js +37 -10
- package/dist/special/extra.d.ts +47 -0
- package/dist/special/extra.js +220 -0
- package/dist/special/igam.d.ts +4 -0
- package/dist/special/igam.js +57 -2
- package/dist/special/incbet.js +77 -22
- package/dist/special/lgammaDiff.d.ts +7 -0
- package/dist/special/lgammaDiff.js +16 -3
- package/dist/special/psigamma.d.ts +48 -0
- package/dist/special/psigamma.js +318 -0
- package/dist/special/rCompatible.d.ts +8 -0
- package/dist/special/rCompatible.js +1 -0
- package/dist/special.js +2 -0
- package/dist/tests.js +2 -0
- package/package.json +26 -1
package/dist/special/igam.js
CHANGED
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
// - The prefactor x^a e^-x / Gamma(a) is computed with Loader's dpoisRaw (= x^a e^-x / Gamma(a+1)),
|
|
10
10
|
// to avoid cancellation in the log for large a and x
|
|
11
11
|
import { erfc } from "./erf.js";
|
|
12
|
+
import { SPLITTER } from "./dd.js";
|
|
12
13
|
import { MACHEP } from "./constants.js";
|
|
13
14
|
import { lgamma, lgamma1p } from "./gamma.js";
|
|
14
15
|
import { IGAM_ASYMP_COEFF_D, IGAM_ASYMP_COEFF_K, IGAM_ASYMP_COEFF_N } from "./igamAsympCoeff.js";
|
|
@@ -34,7 +35,10 @@ function igamSeriesBoth(a, x) {
|
|
|
34
35
|
const d = dpoisRawBoth(a, x);
|
|
35
36
|
return [d[0] + Math.log(ans), d[1] * ans];
|
|
36
37
|
}
|
|
37
|
-
|
|
38
|
+
/** sum_n x^n / ((a + 1) ... (a + n)): P(a, x) = x^a e^-x / Gamma(a + 1) times this */
|
|
39
|
+
export function igamSeriesSum(a, x) {
|
|
40
|
+
if (a > 1000 && x > 0.97 * a)
|
|
41
|
+
return igamSeriesSumDD(a, x);
|
|
38
42
|
let r = a;
|
|
39
43
|
let c = 1;
|
|
40
44
|
let ans = 1;
|
|
@@ -47,6 +51,56 @@ function igamSeriesSum(a, x) {
|
|
|
47
51
|
}
|
|
48
52
|
return ans;
|
|
49
53
|
}
|
|
54
|
+
/**
|
|
55
|
+
* igamSeriesSum in double-double, for large a with x above 0.97 a (the mpmath audit needs it from about there; below,
|
|
56
|
+
* the series is short enough for double). The terms then shrink slowly (ratio x / (a + n) close to 1, thousands
|
|
57
|
+
* of terms), and in double precision the roundings of the ratios, correlated between neighbouring terms, add up to
|
|
58
|
+
* 2e-14 relative; stopping at c <= eps ans also leaves out a tail of up to c (a + n) / (a + n - x). Written out on
|
|
59
|
+
* scalars (no arrays), as the loop is long
|
|
60
|
+
*/
|
|
61
|
+
const TAIL = 2 ** -64;
|
|
62
|
+
function igamSeriesSumDD(a, x) {
|
|
63
|
+
let ch = 1;
|
|
64
|
+
let cl = 0;
|
|
65
|
+
let ah = 1;
|
|
66
|
+
let al = 0;
|
|
67
|
+
for (let i = 1; i < MAXITER; i++) {
|
|
68
|
+
// d = a + i exactly (dh + dl)
|
|
69
|
+
const dh = a + i;
|
|
70
|
+
const bb = dh - a;
|
|
71
|
+
const dl = a - (dh - bb) + (i - bb);
|
|
72
|
+
// q = x / d: q1 + q2
|
|
73
|
+
const q1 = x / dh;
|
|
74
|
+
let t = SPLITTER * q1;
|
|
75
|
+
const q1h = t - (t - q1);
|
|
76
|
+
const q1l = q1 - q1h;
|
|
77
|
+
t = SPLITTER * dh;
|
|
78
|
+
const dhh = t - (t - dh);
|
|
79
|
+
const dhl = dh - dhh;
|
|
80
|
+
const p = q1 * dh;
|
|
81
|
+
const pe = q1h * dhh - p + q1h * dhl + q1l * dhh + q1l * dhl;
|
|
82
|
+
const q2 = (x - p - pe - q1 * dl) / dh;
|
|
83
|
+
// c = c q
|
|
84
|
+
t = SPLITTER * ch;
|
|
85
|
+
const chh = t - (t - ch);
|
|
86
|
+
const chl = ch - chh;
|
|
87
|
+
const m = ch * q1;
|
|
88
|
+
const me = chh * q1h - m + chh * q1l + chl * q1h + chl * q1l + (ch * q2 + cl * q1);
|
|
89
|
+
ch = m + me;
|
|
90
|
+
cl = me - (ch - m);
|
|
91
|
+
// ans += c
|
|
92
|
+
const s = ah + ch;
|
|
93
|
+
const sb = s - ah;
|
|
94
|
+
const e = ah - (s - sb) + (ch - sb) + (al + cl);
|
|
95
|
+
ah = s + e;
|
|
96
|
+
al = e - (ah - s);
|
|
97
|
+
// the rest is at most c q / (1 - q) (q decreasing); stop once that is below 2^-64 ans, well under the
|
|
98
|
+
// rounding of the double result
|
|
99
|
+
if (ch * q1 <= TAIL * ah * (1 - q1))
|
|
100
|
+
break;
|
|
101
|
+
}
|
|
102
|
+
return ah + al;
|
|
103
|
+
}
|
|
50
104
|
/** Continued fraction for Q(a, x) (DLMF 8.9.2) */
|
|
51
105
|
function igamcContinuedFraction(a, x, log) {
|
|
52
106
|
const ans = igamcContinuedFractionValue(a, x);
|
|
@@ -61,7 +115,8 @@ function igamcContinuedFractionBoth(a, x) {
|
|
|
61
115
|
const d = dpoisRawBoth(a, x);
|
|
62
116
|
return [Math.log(a) + d[0] + Math.log(ans), a * d[1] * ans];
|
|
63
117
|
}
|
|
64
|
-
|
|
118
|
+
/** The continued fraction of DLMF 8.9.2: Q(a, x) = x^a e^-x / Gamma(a) times this */
|
|
119
|
+
export function igamcContinuedFractionValue(a, x) {
|
|
65
120
|
let y = 1 - a;
|
|
66
121
|
let z = x + y + 1;
|
|
67
122
|
let c = 0;
|
package/dist/special/incbet.js
CHANGED
|
@@ -12,8 +12,8 @@
|
|
|
12
12
|
// Loader's dbinomRaw (avoids cancellation in the log for large a, b)
|
|
13
13
|
// - The log is formed directly from the result of each computation
|
|
14
14
|
import { MACHEP } from "./constants.js";
|
|
15
|
-
import { dbinomRawDD } from "./loader.js";
|
|
16
|
-
import { ddAdd, ddDiv, ddLog, ddMul, ddMulNum, twoProd, twoSum } from "./dd.js";
|
|
15
|
+
import { dbinomExponent, dbinomRawDD } from "./loader.js";
|
|
16
|
+
import { ddAdd, ddDiv, ddLog, ddMul, ddMulNum, ddSub, twoProd, twoSum } from "./dd.js";
|
|
17
17
|
import { lgamma1p } from "./gamma.js";
|
|
18
18
|
import { lgammaDiff } from "./lgammaDiff.js";
|
|
19
19
|
const BIG = 4.503599627370496e15;
|
|
@@ -33,6 +33,21 @@ function prefactor(a, b, x, xc) {
|
|
|
33
33
|
}
|
|
34
34
|
/** Lower limit of normal numbers. If the prefactor is larger, multiply on the linear scale */
|
|
35
35
|
const NORMAL_MIN = 2.2250738585072014e-308 * 1e3;
|
|
36
|
+
/**
|
|
37
|
+
* m x^a (1-x)^b / B(a, b) for a prefactor below NORMAL_MIN (m a moderate factor such as the series sum).
|
|
38
|
+
* Going through the log would round an exponent near -708 to 0.5 ulp, which is 1e-13 relative after exp,
|
|
39
|
+
* so the exponent of dbinom(a; a+b, x) is kept in double-double and exp is taken once at the end.
|
|
40
|
+
* (multiplying by exp(-700) last rounds once, so a subnormal result is still correctly rounded).
|
|
41
|
+
* Returns undefined when the result underflows to 0 (the caller then works on the log scale)
|
|
42
|
+
*/
|
|
43
|
+
function smallPrefactorTimes(a, b, x, xc, m) {
|
|
44
|
+
const { e } = dbinomExponent(a, b, x, xc);
|
|
45
|
+
const c = (m * ((a * b) / (a + b))) / Math.sqrt(2 * Math.PI * a * (b / (a + b)));
|
|
46
|
+
if (!(e[0] < 1400) || !Number.isFinite(c) || c === 0)
|
|
47
|
+
return undefined;
|
|
48
|
+
const v = Math.exp(700 - e[0]) * (1 - e[1]) * c * Math.exp(-700);
|
|
49
|
+
return v > 0 ? v : undefined;
|
|
50
|
+
}
|
|
36
51
|
/**
|
|
37
52
|
* Series sum S = Σ_{n>=1} (1-b)_n / n! x^n / (a + n) (the core of xsf's pseries).
|
|
38
53
|
* xsf stops on the relative error of the sum including 1/a; here we stop on S itself
|
|
@@ -98,6 +113,9 @@ function pseries(a, b, xd, xcd, log) {
|
|
|
98
113
|
const pf = prefactor(a, b, xd, xcd);
|
|
99
114
|
if (pf > NORMAL_MIN)
|
|
100
115
|
return (s * pf) / Math.exp(b * l1m);
|
|
116
|
+
const v = smallPrefactorTimes(a, b, xd, xcd, s / Math.exp(b * l1m));
|
|
117
|
+
if (v !== undefined)
|
|
118
|
+
return v;
|
|
101
119
|
}
|
|
102
120
|
const lv = Math.log(s) + logPrefactor(a, b, xd, xcd) - b * l1m;
|
|
103
121
|
return log ? lv : Math.exp(lv);
|
|
@@ -110,32 +128,47 @@ function pseries(a, b, xd, xcd, log) {
|
|
|
110
128
|
* the rounding of x = 0.999 (1e-19) alone shifts the result by 6e-13 relative (measured at a = 1e5, b = 100;
|
|
111
129
|
* reference values checked with Rmpfr). So x is kept as an exact double-double and the recurrence also runs in
|
|
112
130
|
* double-double.
|
|
131
|
+
*
|
|
132
|
+
* For the same reason a + b and b - 1 are kept in double-double: with a ≫ b, rounding a + b loses digits of b
|
|
133
|
+
* (5e-11 relative at a = 1e8, b = 0.01, x = 1 - 1e-6; checked against mpmath). From 2^52 on, the steps k += 1 of
|
|
134
|
+
* the other coefficients would be lost as well, so there every coefficient is a double-double (huge)
|
|
113
135
|
*/
|
|
114
136
|
function continuedFractionCore(a, b, xd, xcd, second) {
|
|
115
137
|
// #1: coefficients use x; #2: coefficients use z = x/(1-x)
|
|
116
138
|
const z = second ? ddDiv(xd, xcd) : xd;
|
|
139
|
+
const huge = Math.max(a, b) >= 2 ** 52;
|
|
140
|
+
// Partial numerators k1 x k2 / (k3 k4) and k5 x k6 / (k7 k8). k1, k3, k4, k7, k8 are doubles, with
|
|
141
|
+
// double-double copies (h1, ...) that are used and advanced only when huge
|
|
117
142
|
let k1 = a;
|
|
118
|
-
let k2 = second ? b -
|
|
143
|
+
let k2 = second ? twoSum(b, -1) : twoSum(a, b);
|
|
119
144
|
let k3 = a;
|
|
120
145
|
let k4 = a + 1;
|
|
121
146
|
let k5 = 1;
|
|
122
|
-
let k6 = second ? a
|
|
147
|
+
let k6 = second ? twoSum(a, b) : twoSum(b, -1);
|
|
123
148
|
let k7 = a + 1;
|
|
124
149
|
let k8 = a + 2;
|
|
150
|
+
let h1 = [a, 0];
|
|
151
|
+
let h3 = [a, 0];
|
|
152
|
+
let h4 = twoSum(a, 1);
|
|
153
|
+
let h7 = twoSum(a, 1);
|
|
154
|
+
let h8 = twoSum(a, 2);
|
|
155
|
+
const d1 = second ? -1 : 1;
|
|
125
156
|
let pkm2 = [0, 0];
|
|
126
157
|
let qkm2 = [1, 0];
|
|
127
158
|
let pkm1 = [1, 0];
|
|
128
159
|
let qkm1 = [1, 0];
|
|
129
160
|
let ans = [1, 0];
|
|
130
161
|
for (let n = 0; n < 100000; n++) {
|
|
131
|
-
let xk =
|
|
132
|
-
|
|
133
|
-
|
|
162
|
+
let xk = huge
|
|
163
|
+
? ddDiv(ddMul(ddMul(z, h1), k2), ddMul(h3, h4))
|
|
164
|
+
: ddDiv(ddMul(ddMulNum(z, k1), k2), twoProd(k3, k4));
|
|
165
|
+
let pk = ddSub(pkm1, ddMul(pkm2, xk));
|
|
166
|
+
let qk = ddSub(qkm1, ddMul(qkm2, xk));
|
|
134
167
|
pkm2 = pkm1;
|
|
135
168
|
pkm1 = pk;
|
|
136
169
|
qkm2 = qkm1;
|
|
137
170
|
qkm1 = qk;
|
|
138
|
-
xk = ddDiv(
|
|
171
|
+
xk = ddDiv(ddMul(ddMulNum(z, k5), k6), huge ? ddMul(h7, h8) : twoProd(k7, k8));
|
|
139
172
|
pk = ddAdd(pkm1, ddMul(pkm2, xk));
|
|
140
173
|
qk = ddAdd(qkm1, ddMul(qkm2, xk));
|
|
141
174
|
pkm2 = pkm1;
|
|
@@ -149,21 +182,23 @@ function continuedFractionCore(a, b, xd, xcd, second) {
|
|
|
149
182
|
if (t < MACHEP && n > 1)
|
|
150
183
|
break;
|
|
151
184
|
}
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
185
|
+
k2 = ddAdd(k2, [d1, 0]);
|
|
186
|
+
k6 = ddAdd(k6, [-d1, 0]);
|
|
187
|
+
k5 += 1;
|
|
188
|
+
if (huge) {
|
|
189
|
+
h1 = ddAdd(h1, [1, 0]);
|
|
190
|
+
h3 = ddAdd(h3, [2, 0]);
|
|
191
|
+
h4 = ddAdd(h4, [2, 0]);
|
|
192
|
+
h7 = ddAdd(h7, [2, 0]);
|
|
193
|
+
h8 = ddAdd(h8, [2, 0]);
|
|
156
194
|
}
|
|
157
195
|
else {
|
|
158
196
|
k1 += 1;
|
|
159
|
-
|
|
160
|
-
|
|
197
|
+
k3 += 2;
|
|
198
|
+
k4 += 2;
|
|
199
|
+
k7 += 2;
|
|
200
|
+
k8 += 2;
|
|
161
201
|
}
|
|
162
|
-
k3 += 2;
|
|
163
|
-
k4 += 2;
|
|
164
|
-
k5 += 1;
|
|
165
|
-
k7 += 2;
|
|
166
|
-
k8 += 2;
|
|
167
202
|
const mag = Math.abs(qk[0]) + Math.abs(pk[0]);
|
|
168
203
|
if (mag > BIG) {
|
|
169
204
|
pkm2 = ddMulNum(pkm2, BIGINV);
|
|
@@ -198,8 +233,12 @@ function continuedFraction(a, b, xd, xcd, log) {
|
|
|
198
233
|
// I = x^a (1-x)^b / (a B(a, b)) * w
|
|
199
234
|
if (!log) {
|
|
200
235
|
const pf = prefactor(a, b, xd, xcd);
|
|
236
|
+
// w / a first: pf / a can fall into the subnormal range for huge a (1e-316 at a = 7e18) and lose digits
|
|
201
237
|
if (pf > NORMAL_MIN)
|
|
202
|
-
return
|
|
238
|
+
return pf * (w / a);
|
|
239
|
+
const v = smallPrefactorTimes(a, b, xd, xcd, w / a);
|
|
240
|
+
if (v !== undefined)
|
|
241
|
+
return v;
|
|
203
242
|
}
|
|
204
243
|
const lv = logPrefactor(a, b, xd, xcd) - Math.log(a) + Math.log(w);
|
|
205
244
|
return log ? lv : Math.exp(lv);
|
|
@@ -292,8 +331,13 @@ function betaIncCore(a, b, xd, xcd, lower, wantLog) {
|
|
|
292
331
|
const otherDirect = () => {
|
|
293
332
|
if (pseriesOk(oa, ob, ox[0]))
|
|
294
333
|
return pseries(oa, ob, ox, oxc, false);
|
|
295
|
-
if (pseriesOk(pa, pb, px[0]))
|
|
296
|
-
|
|
334
|
+
if (pseriesOk(pa, pb, px[0])) {
|
|
335
|
+
const v = pseriesComplement(pa, pb, px, false);
|
|
336
|
+
// Cancellation check as for the requested tail (see below)
|
|
337
|
+
if (pa * Math.abs(Math.log(px[0])) > 32 * v && cfOk(oa, ob, ox[0]))
|
|
338
|
+
return continuedFraction(oa, ob, ox, oxc, false);
|
|
339
|
+
return v;
|
|
340
|
+
}
|
|
297
341
|
if (cfOk(oa, ob, ox[0]))
|
|
298
342
|
return continuedFraction(oa, ob, ox, oxc, false);
|
|
299
343
|
return undefined;
|
|
@@ -308,6 +352,17 @@ function betaIncCore(a, b, xd, xcd, lower, wantLog) {
|
|
|
308
352
|
}
|
|
309
353
|
if (pseriesOk(oa, ob, ox[0])) {
|
|
310
354
|
const v = pseriesComplement(oa, ob, ox, false);
|
|
355
|
+
// The exponent L of pseriesComplement is a sum of terms of size a |log x| that can cancel, and their rounding
|
|
356
|
+
// (absolute, about ε a |log x|) is then large relative to a small result (1e-14 at a = 0.01, b = 1e4, x = 1e-4).
|
|
357
|
+
// When a |log x| exceeds the result 32 times and the continued fraction applies in this tail's frame, use it
|
|
358
|
+
// instead (a lower threshold would also send cases where the series is accurate, e.g. most of pt near 0, to the
|
|
359
|
+
// slower continued fraction)
|
|
360
|
+
if (oa * Math.abs(Math.log(ox[0])) > 32 * v && cfOk(pa, pb, px[0])) {
|
|
361
|
+
const w = continuedFraction(pa, pb, px, pxc, false);
|
|
362
|
+
if (wantLog && w < TINY)
|
|
363
|
+
return [continuedFraction(pa, pb, px, pxc, true), w];
|
|
364
|
+
return finish(w, otherDirect);
|
|
365
|
+
}
|
|
311
366
|
return finish(v, () => pseries(oa, ob, ox, oxc, false));
|
|
312
367
|
}
|
|
313
368
|
if (cfOk(pa, pb, px[0])) {
|
|
@@ -1,2 +1,9 @@
|
|
|
1
|
+
import { type DD } from "./dd.js";
|
|
1
2
|
/** lgamma(b + a) - lgamma(b). a >= 0, b > 0 */
|
|
2
3
|
export declare function lgammaDiff(a: number, b: number): number;
|
|
4
|
+
/**
|
|
5
|
+
* lgammaDiff in double-double: the term a log(B + a), which reaches hundreds for large b (exp of it would turn its
|
|
6
|
+
* rounding into a relative error of 1e-14 at B(0.5, 1e300)), is formed in double-double; the others are of the size
|
|
7
|
+
* of a or less and stay in double precision
|
|
8
|
+
*/
|
|
9
|
+
export declare function lgammaDiffDD(a: number, b: number): DD;
|
|
@@ -9,11 +9,21 @@
|
|
|
9
9
|
// D(a, B) = (B - 1/2) log1p(a/B) + a log(B + a) - a + [stirlerr(B + a) - stirlerr(B)]
|
|
10
10
|
// and compute the stirlerr difference with expm1 from the differences of the Stirling series terms,
|
|
11
11
|
// B^-k ((1 + a/B)^-k - 1)
|
|
12
|
+
import { ddAdd, ddLog, ddMulNum, twoSum } from "./dd.js";
|
|
12
13
|
const S = [1 / 12, -1 / 360, 1 / 1260, -1 / 1680, 1 / 1188, -691 / 360360, 1 / 156];
|
|
13
14
|
/** lgamma(b + a) - lgamma(b). a >= 0, b > 0 */
|
|
14
15
|
export function lgammaDiff(a, b) {
|
|
16
|
+
const d = lgammaDiffDD(a, b);
|
|
17
|
+
return d[0] + d[1];
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* lgammaDiff in double-double: the term a log(B + a), which reaches hundreds for large b (exp of it would turn its
|
|
21
|
+
* rounding into a relative error of 1e-14 at B(0.5, 1e300)), is formed in double-double; the others are of the size
|
|
22
|
+
* of a or less and stay in double precision
|
|
23
|
+
*/
|
|
24
|
+
export function lgammaDiffDD(a, b) {
|
|
15
25
|
if (a === 0)
|
|
16
|
-
return 0;
|
|
26
|
+
return [0, 0];
|
|
17
27
|
let acc = 0;
|
|
18
28
|
let B = b;
|
|
19
29
|
while (B <= 15) {
|
|
@@ -21,7 +31,10 @@ export function lgammaDiff(a, b) {
|
|
|
21
31
|
B += 1;
|
|
22
32
|
}
|
|
23
33
|
const l1p = Math.log1p(a / B);
|
|
24
|
-
|
|
34
|
+
const lead0 = a * Math.log(B + a);
|
|
35
|
+
// (in double precision where it is small: its rounding is then below the others', and incbet calls this often)
|
|
36
|
+
const lead = Math.abs(lead0) > 2 && Number.isFinite(lead0) && a < 1e300 ? ddMulNum(ddLog(twoSum(B, a)), a) : [lead0, 0];
|
|
37
|
+
let d = (B - 0.5) * l1p - a;
|
|
25
38
|
// stirlerr(B + a) - stirlerr(B) = Σ S_k [ (B + a)^-(2k+1) - B^-(2k+1) ]
|
|
26
39
|
let pw = 1 / B;
|
|
27
40
|
const inv2 = 1 / (B * B);
|
|
@@ -30,5 +43,5 @@ export function lgammaDiff(a, b) {
|
|
|
30
43
|
d += S[k] * pw * Math.expm1(-m * l1p);
|
|
31
44
|
pw *= inv2;
|
|
32
45
|
}
|
|
33
|
-
return acc + d;
|
|
46
|
+
return ddAdd(lead, [acc + d, 0]);
|
|
34
47
|
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { RCompatibleOptions } from "./rCompatible.js";
|
|
2
|
+
/**
|
|
3
|
+
* Digamma function, the derivative of `lgamma`. Equivalent to R's `digamma(x)` for a single number.
|
|
4
|
+
*
|
|
5
|
+
* @param x - Argument.
|
|
6
|
+
* @returns psi(x) (`NaN` at zero and the negative integers).
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```ts
|
|
10
|
+
* digamma(1); // -0.5772156649015329 (minus Euler's constant)
|
|
11
|
+
* ```
|
|
12
|
+
*/
|
|
13
|
+
export declare function digamma(x: number): number;
|
|
14
|
+
/**
|
|
15
|
+
* Trigamma function, the second derivative of `lgamma`. Equivalent to R's `trigamma(x)` for a single number.
|
|
16
|
+
*
|
|
17
|
+
* `trigamma(-Infinity)` is `Infinity` as in R (`NaN` with `rCompatible: false`).
|
|
18
|
+
*
|
|
19
|
+
* @param x - Argument.
|
|
20
|
+
* @param options - `rCompatible` (default `true`).
|
|
21
|
+
* @returns psi'(x) (`Infinity` at zero and the negative integers).
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* ```ts
|
|
25
|
+
* trigamma(1); // 1.6449340668482264 (pi^2 / 6)
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
28
|
+
export declare function trigamma(x: number, options?: RCompatibleOptions): number;
|
|
29
|
+
/**
|
|
30
|
+
* Polygamma function, the derivative of order `deriv` of `digamma`. Equivalent to R's
|
|
31
|
+
* `psigamma(x, deriv)` for a single number.
|
|
32
|
+
*
|
|
33
|
+
* `psigamma(-Infinity, deriv)` is `Infinity` for odd `deriv` as in R, which treats it like the poles
|
|
34
|
+
* (`NaN` with `rCompatible: false`). Unlike R, which returns `NaN` for `x < 0` with `deriv >= 6` and
|
|
35
|
+
* where the result overflows, the value is computed there.
|
|
36
|
+
*
|
|
37
|
+
* @param x - Argument.
|
|
38
|
+
* @param deriv - Order of the derivative, an integer from 0 to 100 (rounded half to even; default 0, digamma).
|
|
39
|
+
* @param options - `rCompatible` (default `true`).
|
|
40
|
+
* @returns psi^(deriv)(x). At zero and the negative integers, `Infinity` for odd `deriv` and `NaN` for even.
|
|
41
|
+
* `NaN` if `deriv` is outside 0 to 100.
|
|
42
|
+
*
|
|
43
|
+
* @example
|
|
44
|
+
* ```ts
|
|
45
|
+
* psigamma(2, 2); // -0.40411380631918853 (-2 (zeta(3) - 1))
|
|
46
|
+
* ```
|
|
47
|
+
*/
|
|
48
|
+
export declare function psigamma(x: number, deriv?: number, options?: RCompatibleOptions): number;
|
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
// Digamma and polygamma functions: digamma, trigamma, psigamma.
|
|
2
|
+
//
|
|
3
|
+
// digamma is ported from xsf include/xsf/cephes/psi.h and include/xsf/digamma.h (xsf commit c6a3afe; the
|
|
4
|
+
// rational approximation on [1, 2] in psi.h is from Boost.Math, (C) John Maddock 2006, BSL-1.0).
|
|
5
|
+
// Cephes Math Library Release 2.8, Copyright 1984, 1987, 1992, 2000 by Stephen L. Moshier.
|
|
6
|
+
// xsf Copyright (c) 2024, SciPy. BSD-3-Clause. See THIRD_PARTY_NOTICES.md.
|
|
7
|
+
//
|
|
8
|
+
// The derivatives (deriv >= 1) are written for rStats.js:
|
|
9
|
+
// psi^(n)(x) = (-1)^(n+1) n! zeta(n + 1, x) (x > 0)
|
|
10
|
+
// with the Hurwitz zeta function summed by Euler-Maclaurin as in xsf include/xsf/cephes/zeta.h, but scaled by
|
|
11
|
+
// x^(n+1) so that n! / x^(n+1) and the sum can be combined without overflow, and with direct terms up to
|
|
12
|
+
// a >= n + 1 so that the Bernoulli corrections converge for large n. For x < 0 the reflection formula
|
|
13
|
+
// psi^(n)(x) = (-1)^n psi^(n)(1 - x) - pi^(n+1) P_n(cot(pi x))
|
|
14
|
+
// is used, where d^n/dx^n cot(pi x) = pi^n P_n(cot(pi x)), P_0(t) = t, P_(k+1)(t) = -(1 + t^2) P_k'(t)
|
|
15
|
+
// (P_n has terms of one parity only, all of the same sign, so it is evaluated without cancellation).
|
|
16
|
+
import { roundHalfEven } from "../internal/integer.js";
|
|
17
|
+
import { EULER, MACHEP } from "./constants.js";
|
|
18
|
+
import { polevl } from "./polevl.js";
|
|
19
|
+
const PSI_A = [
|
|
20
|
+
8.33333333333333333333e-2, -2.10927960927960927961e-2, 7.57575757575757575758e-3, -4.16666666666666666667e-3,
|
|
21
|
+
3.96825396825396825397e-3, -8.33333333333333333333e-3, 8.33333333333333333333e-2,
|
|
22
|
+
];
|
|
23
|
+
const PSI_Y = 0.99558162689208984; // a float constant in Boost
|
|
24
|
+
const PSI_ROOT1 = 1569415565.0 / 1073741824.0;
|
|
25
|
+
const PSI_ROOT2 = 381566830.0 / 1073741824.0 / 1073741824.0;
|
|
26
|
+
const PSI_ROOT3 = 0.9016312093258695918615325266959189453125e-19;
|
|
27
|
+
const PSI_P = [
|
|
28
|
+
-0.0020713321167745952, -0.045251321448739056, -0.28919126444774784, -0.65031853770896507, -0.32555031186804491,
|
|
29
|
+
0.25479851061131551,
|
|
30
|
+
];
|
|
31
|
+
const PSI_Q = [
|
|
32
|
+
-0.55789841321675513e-6, 0.0021284987017821144, 0.054151797245674225, 0.43593529692665969, 1.4606242909763515,
|
|
33
|
+
2.0767117023730469, 1.0,
|
|
34
|
+
];
|
|
35
|
+
/** Rational approximation on [1, 2]: digamma(x) = (x - root) (Y + R(x - 1)) */
|
|
36
|
+
function digammaImp12(x) {
|
|
37
|
+
let g = x - PSI_ROOT1;
|
|
38
|
+
g -= PSI_ROOT2;
|
|
39
|
+
g -= PSI_ROOT3;
|
|
40
|
+
const r = polevl(x - 1, PSI_P) / polevl(x - 1, PSI_Q);
|
|
41
|
+
return g * PSI_Y + g * r;
|
|
42
|
+
}
|
|
43
|
+
function psiAsy(x) {
|
|
44
|
+
let y = 0;
|
|
45
|
+
if (x < 1e17) {
|
|
46
|
+
const z = 1 / (x * x);
|
|
47
|
+
y = z * polevl(z, PSI_A);
|
|
48
|
+
}
|
|
49
|
+
return Math.log(x) - 0.5 / x - y;
|
|
50
|
+
}
|
|
51
|
+
/** Cephes psi */
|
|
52
|
+
function psi(x) {
|
|
53
|
+
let y = 0;
|
|
54
|
+
if (Number.isNaN(x) || x === Infinity)
|
|
55
|
+
return x;
|
|
56
|
+
if (x === -Infinity)
|
|
57
|
+
return NaN;
|
|
58
|
+
if (x === 0)
|
|
59
|
+
return NaN;
|
|
60
|
+
if (x < 0) {
|
|
61
|
+
// argument reduction before evaluating tan(pi x)
|
|
62
|
+
const r = x - Math.trunc(x);
|
|
63
|
+
if (r === 0)
|
|
64
|
+
return NaN;
|
|
65
|
+
y = -Math.PI / Math.tan(Math.PI * r);
|
|
66
|
+
x = 1 - x;
|
|
67
|
+
}
|
|
68
|
+
// positive integers up to 10
|
|
69
|
+
if (x <= 10 && x === Math.floor(x)) {
|
|
70
|
+
const n = x;
|
|
71
|
+
for (let i = 1; i < n; i++)
|
|
72
|
+
y += 1 / i;
|
|
73
|
+
return y - EULER;
|
|
74
|
+
}
|
|
75
|
+
// use the recurrence to move x into [1, 2]
|
|
76
|
+
if (x < 1) {
|
|
77
|
+
y -= 1 / x;
|
|
78
|
+
x += 1;
|
|
79
|
+
}
|
|
80
|
+
else if (x < 10) {
|
|
81
|
+
while (x > 2) {
|
|
82
|
+
x -= 1;
|
|
83
|
+
y += 1 / x;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
if (x >= 1 && x <= 2)
|
|
87
|
+
return y + digammaImp12(x);
|
|
88
|
+
return y + psiAsy(x);
|
|
89
|
+
}
|
|
90
|
+
// Series around the negative root closest to 0 (xsf digamma.h; values computed with mpmath there)
|
|
91
|
+
const NEGROOT = -0.504083008264455409;
|
|
92
|
+
const NEGROOTVAL = 7.2897639029768949e-17;
|
|
93
|
+
/** zeta(n + 1, NEGROOT) for n = 1, 2, ... (the series coefficients), computed on first use */
|
|
94
|
+
let negrootZeta;
|
|
95
|
+
function digammaNearNegroot(x) {
|
|
96
|
+
negrootZeta ??= Array.from({ length: 100 }, (_, i) => hurwitzZeta(i + 2, NEGROOT));
|
|
97
|
+
let res = NEGROOTVAL;
|
|
98
|
+
let coeff = -1;
|
|
99
|
+
const z = x - NEGROOT;
|
|
100
|
+
for (let n = 1; n < 100; n++) {
|
|
101
|
+
coeff *= -z;
|
|
102
|
+
const term = coeff * negrootZeta[n - 1];
|
|
103
|
+
res += term;
|
|
104
|
+
if (Math.abs(term) < Number.EPSILON * Math.abs(res))
|
|
105
|
+
break;
|
|
106
|
+
}
|
|
107
|
+
return res;
|
|
108
|
+
}
|
|
109
|
+
/** Expansion coefficients (2k)! / B_2k for the Euler-Maclaurin formula (xsf cephes/zeta.h) */
|
|
110
|
+
const ZETA_A = [
|
|
111
|
+
12.0, -720.0, 30240.0, -1209600.0, 47900160.0, -1.8924375803183791606e9, 7.47242496e10, -2.950130727918164224e12,
|
|
112
|
+
1.1646782814350067249e14, -4.5979787224074726105e15, 1.8152105401943546773e17, -7.1661652561756670113e18,
|
|
113
|
+
];
|
|
114
|
+
/**
|
|
115
|
+
* Hurwitz zeta function zeta(s, q) times q^s, for s > 1 and q > 0 (Euler-Maclaurin summation as in Cephes, with
|
|
116
|
+
* every term scaled by q^s). Direct terms are added until the term is negligible or a = q + i exceeds both 9 and s,
|
|
117
|
+
* so that the Bernoulli corrections, whose ratio is about ((s + 2i) / (2 pi a))^2, converge
|
|
118
|
+
*/
|
|
119
|
+
function hurwitzZetaScaled(s, q) {
|
|
120
|
+
let sum = 1;
|
|
121
|
+
let a = q;
|
|
122
|
+
let b = 1;
|
|
123
|
+
let i = 0;
|
|
124
|
+
// Above 1e8 no direct terms: the expansion at a = q itself, q / (s - 1) + 1/2 + s / (12 q) - ... (cutting it after
|
|
125
|
+
// the 1/2 left out s / (12 q), 1e-14 relative at q = 1e8, s = 39)
|
|
126
|
+
while (q <= 1e8 && (i < 9 || a <= 9 || a < s)) {
|
|
127
|
+
i += 1;
|
|
128
|
+
a += 1;
|
|
129
|
+
b = Math.exp(-s * Math.log1p(i / q)); // (q / a)^s
|
|
130
|
+
sum += b;
|
|
131
|
+
if (Math.abs(b / sum) < MACHEP)
|
|
132
|
+
return sum;
|
|
133
|
+
}
|
|
134
|
+
const w = a;
|
|
135
|
+
sum += (b * w) / (s - 1);
|
|
136
|
+
sum -= 0.5 * b;
|
|
137
|
+
let fac = 1;
|
|
138
|
+
let k = 0;
|
|
139
|
+
for (let j = 0; j < 12; j++) {
|
|
140
|
+
fac *= s + k;
|
|
141
|
+
b /= w;
|
|
142
|
+
const t = (fac * b) / ZETA_A[j];
|
|
143
|
+
sum += t;
|
|
144
|
+
if (Math.abs(t / sum) < MACHEP)
|
|
145
|
+
break;
|
|
146
|
+
k += 1;
|
|
147
|
+
fac *= s + k;
|
|
148
|
+
b /= w;
|
|
149
|
+
k += 1;
|
|
150
|
+
}
|
|
151
|
+
return sum;
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Hurwitz zeta function zeta(s, q) for integer s > 1 and any q that is not a non-positive integer, unscaled as in
|
|
155
|
+
* Cephes (negative q are allowed for integer s: the direct sum runs until q + i > 9). Used for the series
|
|
156
|
+
* coefficients around the negative root of digamma
|
|
157
|
+
*/
|
|
158
|
+
function hurwitzZeta(s, q) {
|
|
159
|
+
let sum = Math.pow(q, -s);
|
|
160
|
+
let a = q;
|
|
161
|
+
let b = 0;
|
|
162
|
+
let i = 0;
|
|
163
|
+
while (i < 9 || a <= 9 || a < s) {
|
|
164
|
+
i += 1;
|
|
165
|
+
a += 1;
|
|
166
|
+
b = Math.pow(a, -s);
|
|
167
|
+
sum += b;
|
|
168
|
+
if (Math.abs(b / sum) < MACHEP)
|
|
169
|
+
return sum;
|
|
170
|
+
}
|
|
171
|
+
const w = a;
|
|
172
|
+
sum += (b * w) / (s - 1);
|
|
173
|
+
sum -= 0.5 * b;
|
|
174
|
+
let fac = 1;
|
|
175
|
+
let k = 0;
|
|
176
|
+
for (let j = 0; j < 12; j++) {
|
|
177
|
+
fac *= s + k;
|
|
178
|
+
b /= w;
|
|
179
|
+
const t = (fac * b) / ZETA_A[j];
|
|
180
|
+
sum += t;
|
|
181
|
+
if (Math.abs(t / sum) < MACHEP)
|
|
182
|
+
break;
|
|
183
|
+
k += 1;
|
|
184
|
+
fac *= s + k;
|
|
185
|
+
b /= w;
|
|
186
|
+
k += 1;
|
|
187
|
+
}
|
|
188
|
+
return sum;
|
|
189
|
+
}
|
|
190
|
+
/** psi^(n)(x) for x > 0, n >= 1: (-1)^(n+1) n! / x^(n+1) * [x^(n+1) zeta(n + 1, x)] */
|
|
191
|
+
function polygammaPositive(n, x) {
|
|
192
|
+
if (x === Infinity)
|
|
193
|
+
return n % 2 === 0 ? -0 : 0;
|
|
194
|
+
// n! / x^n as a running product (no overflow before the result does), then the sum divided by x
|
|
195
|
+
let p = 1;
|
|
196
|
+
for (let k = 1; k <= n; k++)
|
|
197
|
+
p *= k / x;
|
|
198
|
+
const v = p * (hurwitzZetaScaled(n + 1, x) / x);
|
|
199
|
+
return n % 2 === 0 ? -v : v;
|
|
200
|
+
}
|
|
201
|
+
/** Coefficients of P_n in t^2: P_n(t) = t^(n+1 mod 2) * sum_j c_j t^(2j), computed on first use */
|
|
202
|
+
const cotDerivCache = [];
|
|
203
|
+
function cotDerivPoly(n) {
|
|
204
|
+
// Dense coefficients of P_k(t) by recurrence, then keep those of the parity of n + 1
|
|
205
|
+
if (cotDerivCache[n] !== undefined)
|
|
206
|
+
return cotDerivCache[n];
|
|
207
|
+
let p = [0, 1]; // P_0 = t
|
|
208
|
+
for (let k = 0; k < n; k++) {
|
|
209
|
+
// P' then -(1 + t^2) P'
|
|
210
|
+
const d = p.slice(1).map((c, i) => c * (i + 1));
|
|
211
|
+
const next = new Array(d.length + 2).fill(0);
|
|
212
|
+
d.forEach((c, i) => {
|
|
213
|
+
next[i] = next[i] - c;
|
|
214
|
+
next[i + 2] = next[i + 2] - c;
|
|
215
|
+
});
|
|
216
|
+
p = next;
|
|
217
|
+
}
|
|
218
|
+
const parity = (n + 1) % 2;
|
|
219
|
+
const coef = [];
|
|
220
|
+
for (let i = parity; i < p.length; i += 2)
|
|
221
|
+
coef.push(p[i]);
|
|
222
|
+
cotDerivCache[n] = coef;
|
|
223
|
+
return coef;
|
|
224
|
+
}
|
|
225
|
+
/** P_n(t) (all coefficients of one sign, so no cancellation) */
|
|
226
|
+
function cotDeriv(n, t) {
|
|
227
|
+
const c = cotDerivPoly(n);
|
|
228
|
+
const t2 = t * t;
|
|
229
|
+
// Horner from the leading coefficient (starting from 0 would give 0 * Infinity = NaN when t^2 overflows)
|
|
230
|
+
let s = c[c.length - 1];
|
|
231
|
+
for (let j = c.length - 2; j >= 0; j--)
|
|
232
|
+
s = s * t2 + c[j];
|
|
233
|
+
return (n + 1) % 2 === 1 ? s * t : s;
|
|
234
|
+
}
|
|
235
|
+
/**
|
|
236
|
+
* cot(pi x), with x reduced exactly to r in [-1/2, 1/2]. The cosine is taken as sin(pi (1/2 - |r|)), where 1/2 - |r|
|
|
237
|
+
* is exact near 1/2, so that cot is exactly 0 at half-integers and keeps its relative accuracy near them (P_n
|
|
238
|
+
* multiplies the error of a cot that should be 0 by its huge linear coefficient: 1e174 instead of -6e117 for
|
|
239
|
+
* psigamma(-1.5, 100))
|
|
240
|
+
*/
|
|
241
|
+
function cotpi(x) {
|
|
242
|
+
const r = x - Math.round(x);
|
|
243
|
+
const c = Math.sin(Math.PI * (0.5 - Math.abs(r)));
|
|
244
|
+
return c / Math.sin(Math.PI * r);
|
|
245
|
+
}
|
|
246
|
+
function polygamma(n, x, rCompatible) {
|
|
247
|
+
if (Number.isNaN(x))
|
|
248
|
+
return NaN;
|
|
249
|
+
// R treats -Infinity like the poles (it equals its own rounding)
|
|
250
|
+
if (x === -Infinity)
|
|
251
|
+
return rCompatible && n % 2 === 1 ? Infinity : NaN;
|
|
252
|
+
if (x <= 0 && x === Math.floor(x))
|
|
253
|
+
return n % 2 === 1 ? Infinity : NaN;
|
|
254
|
+
if (x > 0)
|
|
255
|
+
return polygammaPositive(n, x);
|
|
256
|
+
const refl = polygammaPositive(n, 1 - x);
|
|
257
|
+
return (n % 2 === 0 ? refl : -refl) - Math.pow(Math.PI, n + 1) * cotDeriv(n, cotpi(x));
|
|
258
|
+
}
|
|
259
|
+
/**
|
|
260
|
+
* Digamma function, the derivative of `lgamma`. Equivalent to R's `digamma(x)` for a single number.
|
|
261
|
+
*
|
|
262
|
+
* @param x - Argument.
|
|
263
|
+
* @returns psi(x) (`NaN` at zero and the negative integers).
|
|
264
|
+
*
|
|
265
|
+
* @example
|
|
266
|
+
* ```ts
|
|
267
|
+
* digamma(1); // -0.5772156649015329 (minus Euler's constant)
|
|
268
|
+
* ```
|
|
269
|
+
*/
|
|
270
|
+
export function digamma(x) {
|
|
271
|
+
if (Math.abs(x - NEGROOT) < 0.3)
|
|
272
|
+
return digammaNearNegroot(x);
|
|
273
|
+
return psi(x);
|
|
274
|
+
}
|
|
275
|
+
/**
|
|
276
|
+
* Trigamma function, the second derivative of `lgamma`. Equivalent to R's `trigamma(x)` for a single number.
|
|
277
|
+
*
|
|
278
|
+
* `trigamma(-Infinity)` is `Infinity` as in R (`NaN` with `rCompatible: false`).
|
|
279
|
+
*
|
|
280
|
+
* @param x - Argument.
|
|
281
|
+
* @param options - `rCompatible` (default `true`).
|
|
282
|
+
* @returns psi'(x) (`Infinity` at zero and the negative integers).
|
|
283
|
+
*
|
|
284
|
+
* @example
|
|
285
|
+
* ```ts
|
|
286
|
+
* trigamma(1); // 1.6449340668482264 (pi^2 / 6)
|
|
287
|
+
* ```
|
|
288
|
+
*/
|
|
289
|
+
export function trigamma(x, options) {
|
|
290
|
+
return polygamma(1, x, options?.rCompatible ?? true);
|
|
291
|
+
}
|
|
292
|
+
/**
|
|
293
|
+
* Polygamma function, the derivative of order `deriv` of `digamma`. Equivalent to R's
|
|
294
|
+
* `psigamma(x, deriv)` for a single number.
|
|
295
|
+
*
|
|
296
|
+
* `psigamma(-Infinity, deriv)` is `Infinity` for odd `deriv` as in R, which treats it like the poles
|
|
297
|
+
* (`NaN` with `rCompatible: false`). Unlike R, which returns `NaN` for `x < 0` with `deriv >= 6` and
|
|
298
|
+
* where the result overflows, the value is computed there.
|
|
299
|
+
*
|
|
300
|
+
* @param x - Argument.
|
|
301
|
+
* @param deriv - Order of the derivative, an integer from 0 to 100 (rounded half to even; default 0, digamma).
|
|
302
|
+
* @param options - `rCompatible` (default `true`).
|
|
303
|
+
* @returns psi^(deriv)(x). At zero and the negative integers, `Infinity` for odd `deriv` and `NaN` for even.
|
|
304
|
+
* `NaN` if `deriv` is outside 0 to 100.
|
|
305
|
+
*
|
|
306
|
+
* @example
|
|
307
|
+
* ```ts
|
|
308
|
+
* psigamma(2, 2); // -0.40411380631918853 (-2 (zeta(3) - 1))
|
|
309
|
+
* ```
|
|
310
|
+
*/
|
|
311
|
+
export function psigamma(x, deriv = 0, options) {
|
|
312
|
+
const n = roundHalfEven(deriv);
|
|
313
|
+
if (Number.isNaN(n) || n < 0 || n > 100)
|
|
314
|
+
return NaN;
|
|
315
|
+
if (n === 0)
|
|
316
|
+
return digamma(x);
|
|
317
|
+
return polygamma(n, x, options?.rCompatible ?? true);
|
|
318
|
+
}
|