@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.
Files changed (168) hide show
  1. package/CHANGELOG.md +101 -0
  2. package/README.ja.md +36 -20
  3. package/README.md +34 -18
  4. package/THIRD_PARTY_NOTICES.md +25 -5
  5. package/dist/chunks/chunk-2JCIB5H2.js +2 -0
  6. package/dist/chunks/chunk-7JVSDGQQ.js +2 -0
  7. package/dist/chunks/chunk-7NK4SA6B.js +2 -0
  8. package/dist/chunks/chunk-AJYQNFXK.js +2 -0
  9. package/dist/chunks/chunk-DRNMIEYH.js +2 -0
  10. package/dist/chunks/chunk-NAYDS7CL.js +2 -0
  11. package/dist/chunks/chunk-NOCEAM44.js +2 -0
  12. package/dist/chunks/chunk-QNAIK4PY.js +2 -0
  13. package/dist/chunks/chunk-TPC6MTYC.js +2 -0
  14. package/dist/chunks/chunk-UGUQYWRQ.js +2 -0
  15. package/dist/chunks/chunk-XM2TVD63.js +2 -0
  16. package/dist/chunks/chunk-Y4IZV7XK.js +2 -0
  17. package/dist/chunks/chunk-ZWJ42QN6.js +2 -0
  18. package/dist/descriptive/basic.d.ts +73 -0
  19. package/dist/descriptive/basic.js +256 -0
  20. package/dist/descriptive/cov.d.ts +39 -0
  21. package/dist/descriptive/cov.js +105 -0
  22. package/dist/descriptive/hist.d.ts +64 -0
  23. package/dist/descriptive/hist.js +304 -0
  24. package/dist/descriptive/moments.d.ts +63 -0
  25. package/dist/descriptive/moments.js +158 -0
  26. package/dist/descriptive/quantile.d.ts +50 -0
  27. package/dist/descriptive/quantile.js +131 -0
  28. package/dist/descriptive/rank.d.ts +23 -0
  29. package/dist/descriptive/rank.js +72 -0
  30. package/dist/descriptive/table.d.ts +54 -0
  31. package/dist/descriptive/table.js +160 -0
  32. package/dist/descriptive.js +2 -0
  33. package/dist/distributions/arcsine.d.ts +24 -0
  34. package/dist/distributions/arcsine.js +73 -0
  35. package/dist/distributions/invgamma.d.ts +29 -0
  36. package/dist/distributions/invgamma.js +85 -0
  37. package/dist/distributions/kumaraswamy.d.ts +17 -0
  38. package/dist/distributions/kumaraswamy.js +96 -0
  39. package/dist/distributions/laplace.d.ts +23 -0
  40. package/dist/distributions/laplace.js +69 -0
  41. package/dist/distributions/multinomial.d.ts +24 -0
  42. package/dist/distributions/multinomial.js +70 -0
  43. package/dist/distributions/nbeta.js +28 -10
  44. package/dist/distributions/nchisq.js +20 -3
  45. package/dist/distributions/pareto.d.ts +23 -0
  46. package/dist/distributions/pareto.js +76 -0
  47. package/dist/distributions/summary.d.ts +25 -0
  48. package/dist/distributions/summary.js +289 -0
  49. package/dist/distributions/triangular.d.ts +25 -0
  50. package/dist/distributions/triangular.js +87 -0
  51. package/dist/distributions.js +2 -0
  52. package/dist/entries/descriptive.d.ts +7 -0
  53. package/dist/entries/descriptive.js +8 -0
  54. package/dist/entries/distributions.d.ts +41 -0
  55. package/dist/entries/distributions.js +41 -0
  56. package/dist/entries/linalg.d.ts +11 -0
  57. package/dist/entries/linalg.js +11 -0
  58. package/dist/entries/numerics.d.ts +8 -0
  59. package/dist/entries/numerics.js +6 -0
  60. package/dist/entries/special.d.ts +9 -0
  61. package/dist/entries/special.js +9 -0
  62. package/dist/entries/tests.d.ts +21 -0
  63. package/dist/entries/tests.js +20 -0
  64. package/dist/htest/ansariTest.d.ts +13 -0
  65. package/dist/htest/ansariTest.js +80 -0
  66. package/dist/htest/aov.d.ts +85 -0
  67. package/dist/htest/aov.js +178 -0
  68. package/dist/htest/binomTest.d.ts +36 -0
  69. package/dist/htest/binomTest.js +121 -0
  70. package/dist/htest/cor.d.ts +14 -0
  71. package/dist/htest/cor.js +4 -3
  72. package/dist/htest/core/ansari.d.ts +18 -0
  73. package/dist/htest/core/ansari.js +121 -0
  74. package/dist/htest/core/kolmogorov.d.ts +26 -0
  75. package/dist/htest/core/kolmogorov.js +221 -0
  76. package/dist/htest/core/mantelhaen.d.ts +26 -0
  77. package/dist/htest/core/mantelhaen.js +145 -0
  78. package/dist/htest/core/shapiro.d.ts +24 -0
  79. package/dist/htest/core/shapiro.js +142 -0
  80. package/dist/htest/core/smirnov.d.ts +20 -0
  81. package/dist/htest/core/smirnov.js +75 -0
  82. package/dist/htest/fisherTest.js +134 -120
  83. package/dist/htest/groups.d.ts +20 -0
  84. package/dist/htest/groups.js +62 -0
  85. package/dist/htest/ksTest.d.ts +31 -0
  86. package/dist/htest/ksTest.js +106 -0
  87. package/dist/htest/mantelhaenTest.d.ts +26 -0
  88. package/dist/htest/mantelhaenTest.js +196 -0
  89. package/dist/htest/mcnemarTest.d.ts +26 -0
  90. package/dist/htest/mcnemarTest.js +99 -0
  91. package/dist/htest/onewayTest.d.ts +26 -0
  92. package/dist/htest/onewayTest.js +97 -0
  93. package/dist/htest/pairwise.d.ts +64 -0
  94. package/dist/htest/pairwise.js +152 -0
  95. package/dist/htest/poissonTest.d.ts +35 -0
  96. package/dist/htest/poissonTest.js +170 -0
  97. package/dist/htest/propTest.d.ts +37 -0
  98. package/dist/htest/propTest.js +164 -0
  99. package/dist/htest/rankTests.d.ts +52 -0
  100. package/dist/htest/rankTests.js +234 -0
  101. package/dist/htest/types.d.ts +2 -0
  102. package/dist/htest/types.js +6 -0
  103. package/dist/htest/varTest.d.ts +48 -0
  104. package/dist/htest/varTest.js +118 -0
  105. package/dist/htest/wilcoxTest.js +136 -174
  106. package/dist/index.d.ts +6 -50
  107. package/dist/index.js +8 -50
  108. package/dist/integrate/quadpack.d.ts +29 -0
  109. package/dist/integrate/quadpack.js +525 -0
  110. package/dist/internal/seq.d.ts +5 -0
  111. package/dist/internal/seq.js +14 -0
  112. package/dist/internal/uniroot.js +10 -71
  113. package/dist/linalg/lu.d.ts +45 -0
  114. package/dist/linalg/lu.js +88 -0
  115. package/dist/linalg/ops.d.ts +90 -0
  116. package/dist/linalg/ops.js +320 -0
  117. package/dist/linalg.js +2 -0
  118. package/dist/models/lm.d.ts +98 -0
  119. package/dist/models/lm.js +187 -0
  120. package/dist/numerics/deriv.d.ts +37 -0
  121. package/dist/numerics/deriv.js +150 -0
  122. package/dist/numerics/integrate.d.ts +91 -0
  123. package/dist/numerics/integrate.js +264 -0
  124. package/dist/numerics/interp.d.ts +103 -0
  125. package/dist/numerics/interp.js +581 -0
  126. package/dist/numerics/ode.d.ts +23 -0
  127. package/dist/numerics/ode.js +59 -0
  128. package/dist/numerics.js +2 -0
  129. package/dist/optim/localmin.d.ts +16 -0
  130. package/dist/optim/localmin.js +97 -0
  131. package/dist/random/beta.js +1 -1
  132. package/dist/random/binomial.js +1 -1
  133. package/dist/random/continuous.js +1 -1
  134. package/dist/random/extra.d.ts +33 -0
  135. package/dist/random/extra.js +62 -0
  136. package/dist/random/gamma.js +1 -1
  137. package/dist/random/hyper.js +1 -1
  138. package/dist/random/mt.js +0 -1
  139. package/dist/random/poisson.js +0 -1
  140. package/dist/random/rank.js +1 -1
  141. package/dist/random/rng.js +1 -1
  142. package/dist/random/sample.js +1 -1
  143. package/dist/rstats.js +2 -2
  144. package/dist/special/besselIK.d.ts +38 -0
  145. package/dist/special/besselIK.js +315 -0
  146. package/dist/special/besselJY.d.ts +32 -0
  147. package/dist/special/besselJY.js +508 -0
  148. package/dist/special/besselSeries.d.ts +8 -0
  149. package/dist/special/besselSeries.js +24 -0
  150. package/dist/special/beta.js +22 -5
  151. package/dist/special/choose.d.ts +75 -0
  152. package/dist/special/choose.js +233 -0
  153. package/dist/special/erf.d.ts +28 -2
  154. package/dist/special/erf.js +37 -10
  155. package/dist/special/extra.d.ts +47 -0
  156. package/dist/special/extra.js +220 -0
  157. package/dist/special/igam.d.ts +4 -0
  158. package/dist/special/igam.js +57 -2
  159. package/dist/special/incbet.js +77 -22
  160. package/dist/special/lgammaDiff.d.ts +7 -0
  161. package/dist/special/lgammaDiff.js +16 -3
  162. package/dist/special/psigamma.d.ts +48 -0
  163. package/dist/special/psigamma.js +318 -0
  164. package/dist/special/rCompatible.d.ts +8 -0
  165. package/dist/special/rCompatible.js +1 -0
  166. package/dist/special.js +2 -0
  167. package/dist/tests.js +2 -0
  168. package/package.json +26 -1
@@ -0,0 +1,581 @@
1
+ // Interpolation: R's approx (linear and constant), spline and splinefun (cubic splines: "fmm", "natural",
2
+ // "periodic", "hyman", and "monoH.FC" for splinefun), splinefunH (cubic Hermite with given slopes), the barycentric
3
+ // Lagrange interpolation of pracma's barylag, and the global Hermite polynomial through values and derivatives.
4
+ //
5
+ // Sources. The "fmm" coefficients are a port of the public-domain netlib routine fmm/spline.f (Forsythe, Malcolm and
6
+ // Moler, "Computer Methods for Mathematical Computations", 1977, section 4.4): the end third derivatives are those of
7
+ // the cubics through the first and last four points. "natural" (end second derivatives 0) and "periodic" (the cyclic
8
+ // tridiagonal system, solved by Gaussian elimination with a bordered last row and column) use the same formulation of
9
+ // the book, written for rStats.js. "hyman" limits the fmm slopes to keep monotone data monotone (Hyman, SIAM J. Sci.
10
+ // Stat. Comput. 4, 1983) and "monoH.FC" is Fritsch and Carlson's monotone cubic Hermite spline (SIAM J. Numer. Anal.
11
+ // 17, 1980), both written from the papers. approx is plain linear (or step) interpolation between the merged knots,
12
+ // located with findInterval (src/descriptive/table.ts). The methods come from the sources cited here; the conventions
13
+ // (merging tied x values, extrapolation, the choice of the interval at a knot, defaults and error messages) follow
14
+ // R's documented and observed behaviour.
15
+ // Each spline piece is y + b h + c h^2 + d h^3 with h the distance from its left knot.
16
+ import { mean } from "../internal/summary.js";
17
+ import { seqLength } from "../internal/seq.js";
18
+ import { findInterval } from "../descriptive/table.js";
19
+ /**
20
+ * The data as R uses it for interpolation: pairs with a NaN dropped, sorted by x, and each run of equal x merged into
21
+ * one point whose y is `ties` of the run's y values (a single point keeps its y).
22
+ */
23
+ function regularize(x, y, ties = "mean") {
24
+ if (x.length !== y.length)
25
+ throw new RangeError("'x' and 'y' lengths differ");
26
+ const idx = [];
27
+ for (let i = 0; i < x.length; i++)
28
+ if (!Number.isNaN(x[i]) && !Number.isNaN(y[i]))
29
+ idx.push(i);
30
+ idx.sort((a, b) => x[a] - x[b]); // stable
31
+ const merge = typeof ties === "function" ? ties
32
+ : ties === "min" ? (v) => Math.min(...v)
33
+ : ties === "max" ? (v) => Math.max(...v)
34
+ : mean;
35
+ const xs = [];
36
+ const ys = [];
37
+ for (let k = 0; k < idx.length;) {
38
+ const xv = x[idx[k]];
39
+ let end = k + 1;
40
+ while (end < idx.length && x[idx[end]] === xv)
41
+ end++;
42
+ xs.push(xv);
43
+ ys.push(end - k === 1 ? y[idx[k]] : merge(idx.slice(k, end).map((i) => y[i])));
44
+ k = end;
45
+ }
46
+ return { x: xs, y: ys };
47
+ }
48
+ /** Linear interpolation at v strictly inside [X[i], X[i+1]], grouped as y_i + (y_{i+1} - y_i) ((v - x_i) / h_i). */
49
+ function linearAt(X, Y, i, v) {
50
+ const t = (v - X[i]) / (X[i + 1] - X[i]);
51
+ return Y[i] + (Y[i + 1] - Y[i]) * t;
52
+ }
53
+ /** Step value (1 - f) y_i + f y_{i+1}; a term with weight 0 is dropped, so an infinite y there does not give NaN. */
54
+ function stepAt(Y, i, f) {
55
+ const term = (w, yv) => (w === 0 ? 0 : w * yv);
56
+ return term(1 - f, Y[i]) + term(f, Y[i + 1]);
57
+ }
58
+ /**
59
+ * Linear or constant interpolation. Equivalent to R's `approx()`.
60
+ *
61
+ * @example
62
+ * ```ts
63
+ * approx([1, 2, 3], [10, 20, 40], { xout: [1.5, 2.5] }).y; // [15, 30]
64
+ * ```
65
+ */
66
+ export function approx(x, y, options = {}) {
67
+ const { method = "linear", n = 50, f = 0, rule = 1, ties = "mean" } = options;
68
+ const pts = regularize(x, y, ties);
69
+ const nx = pts.x.length;
70
+ if (nx <= 1 && method === "linear")
71
+ throw new RangeError("need at least two non-NA values to interpolate");
72
+ if (nx === 0)
73
+ throw new RangeError("zero non-NA points");
74
+ const [ruleLeft, ruleRight] = typeof rule === "number" ? [rule, rule] : rule;
75
+ const yleft = options.yleft ?? (ruleLeft === 1 ? NaN : pts.y[0]);
76
+ const yright = options.yright ?? (ruleRight === 1 ? NaN : pts.y[nx - 1]);
77
+ let xout;
78
+ if (options.xout !== undefined)
79
+ xout = Array.from(options.xout);
80
+ else {
81
+ if (n <= 0)
82
+ throw new RangeError("'approx' requires n >= 1");
83
+ xout = seqLength(pts.x[0], pts.x[nx - 1], n);
84
+ }
85
+ const X = pts.x;
86
+ const Y = pts.y;
87
+ // k = number of knots <= v (NaN for NaN): 0 left of the data, nx at or right of the last knot. The regularized knots
88
+ // are strictly increasing, so v is a knot exactly when it equals X[k - 1].
89
+ const counts = findInterval(xout, X);
90
+ const yout = xout.map((v, q) => {
91
+ const k = counts[q];
92
+ if (Number.isNaN(k))
93
+ return NaN;
94
+ if (k === 0)
95
+ return yleft;
96
+ const left = k - 1;
97
+ if (v === X[left])
98
+ return Y[left];
99
+ if (k === nx)
100
+ return yright;
101
+ return method === "linear" ? linearAt(X, Y, left, v) : stepAt(Y, left, f);
102
+ });
103
+ return { x: xout, y: yout };
104
+ }
105
+ /**
106
+ * "fmm" coefficients: port of the public-domain netlib routine fmm/spline.f (Forsythe, Malcolm and Moler 1977). The
107
+ * unknowns sigma_i = s''(x_i)/6 solve a tridiagonal system whose end rows match the third derivative of the cubic
108
+ * through the first (last) four points; elimination without pivoting, then back substitution.
109
+ */
110
+ function fmmCoef(x, y) {
111
+ const n = x.length;
112
+ const b = new Array(n).fill(0);
113
+ const c = new Array(n).fill(0);
114
+ const d = new Array(n).fill(0);
115
+ if (n < 2)
116
+ return { b, c, d };
117
+ if (n < 3) {
118
+ b[0] = (y[1] - y[0]) / (x[1] - x[0]);
119
+ b[1] = b[0];
120
+ return { b, c, d };
121
+ }
122
+ const nm1 = n - 1;
123
+ // b: diagonal, d: off-diagonal, c: right-hand side
124
+ d[0] = x[1] - x[0];
125
+ c[1] = (y[1] - y[0]) / d[0];
126
+ for (let i = 1; i < nm1; i++) {
127
+ d[i] = x[i + 1] - x[i];
128
+ b[i] = 2 * (d[i - 1] + d[i]);
129
+ c[i + 1] = (y[i + 1] - y[i]) / d[i];
130
+ c[i] = c[i + 1] - c[i];
131
+ }
132
+ // end conditions: third derivatives at the ends from divided differences
133
+ b[0] = -d[0];
134
+ b[nm1] = -d[n - 2];
135
+ c[0] = 0;
136
+ c[nm1] = 0;
137
+ if (n > 3) {
138
+ c[0] = c[2] / (x[3] - x[1]) - c[1] / (x[2] - x[0]);
139
+ c[nm1] = c[n - 2] / (x[nm1] - x[n - 3]) - c[n - 3] / (x[n - 2] - x[n - 4]);
140
+ c[0] = (c[0] * d[0] * d[0]) / (x[3] - x[0]);
141
+ c[nm1] = (-c[nm1] * d[n - 2] * d[n - 2]) / (x[nm1] - x[n - 4]);
142
+ }
143
+ // forward elimination
144
+ for (let i = 1; i < n; i++) {
145
+ const t = d[i - 1] / b[i - 1];
146
+ b[i] = b[i] - t * d[i - 1];
147
+ c[i] = c[i] - t * c[i - 1];
148
+ }
149
+ // back substitution: c becomes sigma
150
+ c[nm1] = c[nm1] / b[nm1];
151
+ for (let i = n - 2; i >= 0; i--)
152
+ c[i] = (c[i] - d[i] * c[i + 1]) / b[i];
153
+ // polynomial coefficients
154
+ b[nm1] = (y[nm1] - y[n - 2]) / d[n - 2] + d[n - 2] * (c[n - 2] + 2 * c[nm1]);
155
+ for (let i = 0; i < nm1; i++) {
156
+ b[i] = (y[i + 1] - y[i]) / d[i] - d[i] * (c[i + 1] + 2 * c[i]);
157
+ d[i] = (c[i + 1] - c[i]) / d[i];
158
+ c[i] = 3 * c[i];
159
+ }
160
+ c[nm1] = 3 * c[nm1];
161
+ d[nm1] = d[n - 2];
162
+ return { b, c, d };
163
+ }
164
+ /** Coefficients b, c, d on each interval from sigma_i = s''(x_i)/6 (i = 0..n-1). */
165
+ function coefFromSigma(x, y, sigma) {
166
+ const n = x.length;
167
+ const b = new Array(n).fill(0);
168
+ const c = new Array(n).fill(0);
169
+ const d = new Array(n).fill(0);
170
+ for (let i = 0; i < n - 1; i++) {
171
+ const h = x[i + 1] - x[i];
172
+ b[i] = (y[i + 1] - y[i]) / h - h * (sigma[i + 1] + 2 * sigma[i]);
173
+ c[i] = 3 * sigma[i];
174
+ d[i] = (sigma[i + 1] - sigma[i]) / h;
175
+ }
176
+ return { b, c, d };
177
+ }
178
+ /** "natural" coefficients: sigma_1 = sigma_n = 0 and the interior tridiagonal system (FMM formulation). */
179
+ function naturalCoef(x, y) {
180
+ const n = x.length;
181
+ if (n < 3)
182
+ return fmmCoef(x, y);
183
+ const h = Array.from({ length: n - 1 }, (_, i) => x[i + 1] - x[i]);
184
+ const slope = h.map((hi, i) => (y[i + 1] - y[i]) / hi);
185
+ const diag = new Array(n).fill(0);
186
+ const rhs = new Array(n).fill(0);
187
+ for (let i = 1; i < n - 1; i++) {
188
+ diag[i] = 2 * (h[i - 1] + h[i]);
189
+ rhs[i] = slope[i] - slope[i - 1];
190
+ }
191
+ for (let i = 2; i < n - 1; i++) {
192
+ const t = h[i - 1] / diag[i - 1];
193
+ diag[i] = diag[i] - t * h[i - 1];
194
+ rhs[i] = rhs[i] - t * rhs[i - 1];
195
+ }
196
+ const sigma = new Array(n).fill(0);
197
+ sigma[n - 2] = rhs[n - 2] / diag[n - 2];
198
+ for (let i = n - 3; i >= 1; i--)
199
+ sigma[i] = (rhs[i] - h[i] * sigma[i + 1]) / diag[i];
200
+ const coef = coefFromSigma(x, y, sigma);
201
+ // last knot: the slope of the last cubic at x_n; linear beyond
202
+ coef.b[n - 1] = slope[n - 2] + h[n - 2] * sigma[n - 2];
203
+ return coef;
204
+ }
205
+ /**
206
+ * "periodic" coefficients: sigma_1..sigma_{n-1} (sigma_n = sigma_1) from the cyclic tridiagonal system, solved by
207
+ * Gaussian elimination without pivoting (the system is symmetric positive definite) where the last row and column
208
+ * carry the wrap-around entries.
209
+ */
210
+ function periodicCoef(x, y) {
211
+ const n = x.length;
212
+ if (y[0] !== y[n - 1])
213
+ throw new RangeError("periodic spline needs y[1] == y[n]");
214
+ if (n < 3)
215
+ return { b: new Array(n).fill(0), c: new Array(n).fill(0), d: new Array(n).fill(0) };
216
+ const m = n - 1; // unknowns sigma_0..sigma_{m-1}
217
+ const h = Array.from({ length: m }, (_, i) => x[i + 1] - x[i]);
218
+ const slope = h.map((hi, i) => (y[i + 1] - y[i]) / hi);
219
+ const prev = (i) => (i === 0 ? m - 1 : i - 1);
220
+ // row i (i < m-1): diag[i] at column i, sup[i] at column i+1, last[i] at column m-1
221
+ const diag = Array.from({ length: m }, (_, i) => 2 * (h[prev(i)] + h[i]));
222
+ const rhs = Array.from({ length: m }, (_, i) => slope[i] - slope[prev(i)]);
223
+ const sup = h.slice();
224
+ const last = new Array(m).fill(0);
225
+ last[0] = h[m - 1]; // wrap-around entry
226
+ last[m - 2] = last[m - 2] + h[m - 2]; // the super-diagonal of row m-2 lies in the last column
227
+ // the last row's entry in the column being eliminated
228
+ let rowLast = last[0];
229
+ let diagLast = diag[m - 1];
230
+ let rhsLast = rhs[m - 1];
231
+ for (let i = 0; i < m - 1; i++) {
232
+ if (i + 1 < m - 1) {
233
+ const t = h[i] / diag[i];
234
+ diag[i + 1] = diag[i + 1] - t * sup[i];
235
+ last[i + 1] = last[i + 1] - t * last[i];
236
+ rhs[i + 1] = rhs[i + 1] - t * rhs[i];
237
+ }
238
+ const t = rowLast / diag[i];
239
+ diagLast -= t * last[i];
240
+ rhsLast -= t * rhs[i];
241
+ if (i + 1 < m - 1)
242
+ rowLast = (i + 1 === m - 2 ? h[m - 2] : 0) - t * sup[i];
243
+ }
244
+ const sigma = new Array(n).fill(0);
245
+ sigma[m - 1] = rhsLast / diagLast;
246
+ for (let i = m - 2; i >= 0; i--) {
247
+ const s = rhs[i] - last[i] * sigma[m - 1] - (i < m - 2 ? sup[i] * sigma[i + 1] : 0);
248
+ sigma[i] = s / diag[i];
249
+ }
250
+ sigma[n - 1] = sigma[0];
251
+ const coef = coefFromSigma(x, y, sigma);
252
+ coef.b[n - 1] = coef.b[0];
253
+ coef.c[n - 1] = coef.c[0];
254
+ coef.d[n - 1] = coef.d[0];
255
+ return coef;
256
+ }
257
+ /**
258
+ * "hyman": the fmm slopes limited so that the spline through monotone data stays monotone (Hyman 1983), then c and d
259
+ * rebuilt as the cubic Hermite pieces with those slopes.
260
+ */
261
+ function hymanCoef(x, y) {
262
+ const n = x.length;
263
+ const { b, c, d } = fmmCoef(x, y);
264
+ if (n < 2)
265
+ return { b, c, d };
266
+ const slope = Array.from({ length: n - 1 }, (_, i) => (y[i + 1] - y[i]) / (x[i + 1] - x[i]));
267
+ for (let i = 0; i < n; i++) {
268
+ const sm = slope[i === 0 ? 0 : i - 1];
269
+ const sp = slope[i === n - 1 ? n - 2 : i];
270
+ const t = Math.min(Math.abs(sm), Math.abs(sp));
271
+ const nonneg = sm * sp > 0 ? sp > 0 : b[i] >= 0;
272
+ b[i] = nonneg ? Math.min(Math.max(0, b[i]), 3 * t) : Math.max(Math.min(0, b[i]), -3 * t);
273
+ }
274
+ for (let i = 0; i < n - 1; i++) {
275
+ const h = x[i + 1] - x[i];
276
+ const s = (y[i + 1] - y[i]) / h;
277
+ c[i] = (3 * s - 2 * b[i] - b[i + 1]) / h;
278
+ d[i] = (b[i] + b[i + 1] - 2 * s) / (h * h);
279
+ }
280
+ const h = x[n - 1] - x[n - 2];
281
+ c[n - 1] = (b[n - 2] + 2 * b[n - 1] - (3 * (y[n - 1] - y[n - 2])) / h) / h;
282
+ d[n - 1] = d[n - 2];
283
+ return { b, c, d };
284
+ }
285
+ /** Regularized data and coefficients of a b/c/d spline. */
286
+ function cubicSpline(x, y, method, ties) {
287
+ const pts = regularize(x, y, ties);
288
+ const n = pts.x.length;
289
+ if (n === 0)
290
+ throw new RangeError("zero non-NA points");
291
+ if (method === "periodic" && pts.y[n - 1] !== pts.y[0])
292
+ pts.y[n - 1] = pts.y[0];
293
+ if (method === "hyman")
294
+ checkMonotone(pts.y);
295
+ const coef = method === "fmm" ? fmmCoef(pts.x, pts.y)
296
+ : method === "natural" ? naturalCoef(pts.x, pts.y)
297
+ : method === "periodic" ? periodicCoef(pts.x, pts.y)
298
+ : method === "hyman" ? hymanCoef(pts.x, pts.y)
299
+ : (() => { throw new RangeError(`invalid spline method '${String(method)}'`); })();
300
+ return { method, ...pts, ...coef };
301
+ }
302
+ function checkMonotone(y) {
303
+ let up = true;
304
+ let down = true;
305
+ for (let i = 1; i < y.length; i++) {
306
+ const dy = y[i] - y[i - 1];
307
+ if (!(dy >= 0))
308
+ up = false;
309
+ if (!(dy <= 0))
310
+ down = false;
311
+ }
312
+ if (!up && !down)
313
+ throw new RangeError("'y' must be increasing or decreasing");
314
+ }
315
+ /** The order of derivative as R takes it: truncated towards zero (as.integer), then checked to be 0 to 3. */
316
+ function checkDeriv(deriv) {
317
+ const d = Math.trunc(deriv);
318
+ if (!(d >= 0 && d <= 3))
319
+ throw new RangeError("'deriv' must be between 0 and 3");
320
+ return d;
321
+ }
322
+ /**
323
+ * Evaluates a b/c/d spline (or a derivative). The interval is carried over from the previous query while it still
324
+ * holds the point, and found again by bisection otherwise, so a query exactly at a knot can take either side, as in R.
325
+ */
326
+ function evalCubic(sp, xq, deriv0) {
327
+ const deriv = checkDeriv(deriv0);
328
+ const { x, y, b, c, d, method } = sp;
329
+ const n = x.length;
330
+ const natural = method === "natural";
331
+ const period = x[n - 1] - x[0];
332
+ let i = 0;
333
+ return Array.from(xq, (u0) => {
334
+ let u = u0;
335
+ if (method === "periodic" && n > 1) {
336
+ let r = (u - x[0]) % period;
337
+ if (r < 0)
338
+ r += period;
339
+ u = x[0] + r;
340
+ }
341
+ if (natural && deriv > 0 && u <= x[0])
342
+ return deriv === 1 ? b[0] : 0;
343
+ if (!(x[i] <= u && (i === n - 1 || u <= x[i + 1]))) {
344
+ // largest index with x_i <= u, or 0 left of the data
345
+ let lo = 0;
346
+ let hi = n;
347
+ while (hi - lo > 1) {
348
+ const mid = (lo + hi) >> 1;
349
+ if (x[mid] <= u)
350
+ lo = mid;
351
+ else
352
+ hi = mid;
353
+ }
354
+ i = lo;
355
+ }
356
+ const h = u - x[i];
357
+ const di = natural && u < x[0] ? 0 : d[i];
358
+ switch (deriv) {
359
+ case 0: return y[i] + h * (b[i] + h * (c[i] + h * di));
360
+ case 1: return b[i] + h * (2 * c[i] + h * (3 * di));
361
+ case 2: return 2 * c[i] + h * (6 * di);
362
+ default: return 6 * di;
363
+ }
364
+ });
365
+ }
366
+ /**
367
+ * Cubic spline interpolation at given or equally spaced points. Equivalent to R's `spline()`.
368
+ *
369
+ * @example
370
+ * ```ts
371
+ * spline([1, 2, 3, 4], [1, 4, 9, 16], { xout: [2.5] }).y; // [6.25]
372
+ * ```
373
+ */
374
+ export function spline(x, y, options = {}) {
375
+ const { method = "fmm", ties = "mean" } = options;
376
+ const sp = cubicSpline(x, y, method, ties);
377
+ let xout;
378
+ if (options.xout !== undefined)
379
+ xout = Array.from(options.xout);
380
+ else {
381
+ // n, xmin and xmax refer to the regularized data
382
+ const nk = sp.x.length;
383
+ const n = options.n ?? 3 * nk;
384
+ if (n <= 0)
385
+ throw new RangeError("'spline' requires n >= 1");
386
+ xout = seqLength(options.xmin ?? sp.x[0], options.xmax ?? sp.x[nk - 1], n);
387
+ }
388
+ return { x: xout, y: evalCubic(sp, xout, 0) };
389
+ }
390
+ /**
391
+ * The cubic spline through the points, as a function of `x` and the order of derivative (0 to 3; a fraction is
392
+ * truncated, as R does). Equivalent to R's `splinefun()`, except that the third derivative of `"monoH.FC"` is the
393
+ * exact one (see {@link splinefunH}).
394
+ *
395
+ * @example
396
+ * ```ts
397
+ * const s = splinefun([1, 2, 3, 4], [1, 4, 9, 16]);
398
+ * s([2.5]); // [6.25]
399
+ * s([2.5], 1); // [5]
400
+ * ```
401
+ */
402
+ export function splinefun(x, y, options = {}) {
403
+ const { method = "fmm", ties = "mean" } = options;
404
+ if (method === "monoH.FC") {
405
+ const pts = regularize(x, y, ties);
406
+ if (pts.x.length === 0)
407
+ throw new RangeError("zero non-NA points");
408
+ const m = fritschCarlsonSlopes(pts.x, pts.y);
409
+ return (xq, deriv = 0) => evalHermite(pts.x, pts.y, m, xq, deriv);
410
+ }
411
+ const sp = cubicSpline(x, y, method, ties);
412
+ return (xq, deriv = 0) => evalCubic(sp, xq, deriv);
413
+ }
414
+ /**
415
+ * Fritsch and Carlson's (1980) slopes: averages of the neighbouring secants, then, interval by interval from the left,
416
+ * pulled back onto the circle of radius 3 when (alpha, beta) leaves their monotonicity region.
417
+ */
418
+ function fritschCarlsonSlopes(x, y) {
419
+ const n = x.length;
420
+ if (n < 2)
421
+ throw new RangeError("length(m) must be at least two");
422
+ const S = Array.from({ length: n - 1 }, (_, k) => (y[k + 1] - y[k]) / (x[k + 1] - x[k]));
423
+ const m = Array.from({ length: n }, (_, k) => (k === 0 ? S[0] : k === n - 1 ? S[n - 2] : (S[k - 1] + S[k]) / 2));
424
+ for (let k = 0; k < n - 1; k++) {
425
+ const Sk = S[k];
426
+ if (Sk === 0) {
427
+ m[k] = 0;
428
+ m[k + 1] = 0;
429
+ continue;
430
+ }
431
+ const alpha = m[k] / Sk;
432
+ const beta = m[k + 1] / Sk;
433
+ const a = 2 * alpha + beta - 3;
434
+ const b = alpha + 2 * beta - 3;
435
+ if (a > 0 && b > 0 && alpha * (a + b) < a * a) {
436
+ const tau = (3 * Sk) / Math.sqrt(alpha * alpha + beta * beta);
437
+ m[k] = tau * alpha;
438
+ m[k + 1] = tau * beta;
439
+ }
440
+ }
441
+ return m;
442
+ }
443
+ /** Evaluates the cubic Hermite spline (or a derivative) with values y and slopes m at x; linear outside the data. */
444
+ function evalHermite(x, y, m, xq, deriv0) {
445
+ const deriv = checkDeriv(deriv0);
446
+ const n = x.length;
447
+ return Array.from(xq, (u) => {
448
+ if (Number.isNaN(u))
449
+ return NaN;
450
+ // i = number of knots <= u
451
+ let lo = 0;
452
+ let hi = n;
453
+ while (lo < hi) {
454
+ const mid = (lo + hi) >> 1;
455
+ if (x[mid] <= u)
456
+ lo = mid + 1;
457
+ else
458
+ hi = mid;
459
+ }
460
+ const i = lo;
461
+ if (i === 0 || i === n) {
462
+ const k = i === 0 ? 0 : n - 1;
463
+ return deriv === 0 ? y[k] + m[k] * (u - x[k]) : deriv === 1 ? m[k] : 0;
464
+ }
465
+ const x0 = x[i - 1];
466
+ const h = x[i] - x0;
467
+ const t = (u - x0) / h;
468
+ const t1 = t - 1;
469
+ const y0 = y[i - 1];
470
+ const y1 = y[i];
471
+ const m0 = m[i - 1];
472
+ const m1 = m[i];
473
+ const delta = (y1 - y0) / h;
474
+ switch (deriv) {
475
+ case 0: {
476
+ const h01 = t * t * (3 - 2 * t);
477
+ return y0 * (1 - h01) + h * m0 * (t * t1 * t1) + y1 * h01 + h * m1 * (t * t * t1);
478
+ }
479
+ case 1: return delta * (-6 * t * t1) + m0 * (t1 * (3 * t - 1)) + m1 * (t * (3 * t - 2));
480
+ case 2: return (delta * (6 - 12 * t) + m0 * (6 * t - 4) + m1 * (6 * t - 2)) / h;
481
+ default: return (-12 * delta + 6 * m0 + 6 * m1) / (h * h);
482
+ }
483
+ });
484
+ }
485
+ /**
486
+ * The cubic Hermite spline with values `y` and slopes `m` at the points `x`, as a function of `x` and the order of
487
+ * derivative (0 to 3; a fraction is truncated, as R does); linear outside the data. Equivalent to R's `splinefunH()`,
488
+ * except for the third derivative: on [x_i, x_{i+1}] it is the exact (6 m_i + 6 m_{i+1} - 12 delta) / h^2, with
489
+ * h = x_{i+1} - x_i and delta = (y_{i+1} - y_i) / h, where R divides by h only once (the two agree when h = 1).
490
+ *
491
+ * @example
492
+ * ```ts
493
+ * splinefunH([0, 2], [0, 0], [1, 1])([1], 3); // [3] (R: 6)
494
+ * ```
495
+ */
496
+ export function splinefunH(x, y, m) {
497
+ const n = x.length;
498
+ if (y.length !== n || m.length !== n)
499
+ throw new RangeError("'x', 'y' and 'm' must have the same length");
500
+ const order = Array.from({ length: n }, (_, i) => i).sort((a, b) => x[a] - x[b]);
501
+ const X = order.map((i) => x[i]);
502
+ const Y = order.map((i) => y[i]);
503
+ const M = order.map((i) => m[i]);
504
+ for (let i = 1; i < n; i++)
505
+ if (!(X[i] - X[i - 1] > 0))
506
+ throw new RangeError("'x' must be *strictly* increasing (non - NA)");
507
+ return (xq, deriv = 0) => evalHermite(X, Y, M, xq, deriv);
508
+ }
509
+ /**
510
+ * Lagrange interpolation in barycentric form through the points (xi, yi). Equivalent to `pracma::barylag()`; the
511
+ * points must lie within the range of the nodes.
512
+ */
513
+ export function barylag(xi, yi, x) {
514
+ const X = Array.from(xi);
515
+ const Y = Array.from(yi);
516
+ const n = X.length;
517
+ if (Y.length !== n)
518
+ throw new RangeError("'xi' and 'yi' must have the same length");
519
+ const xs = Array.from(x);
520
+ const lo = Math.min(...X);
521
+ const hi = Math.max(...X);
522
+ if (xs.some((v) => v < lo || v > hi))
523
+ throw new RangeError("Some interpolation points outside the nodes.");
524
+ const w = X.map((a, j) => {
525
+ let p = 1;
526
+ for (let k = 0; k < n; k++)
527
+ if (k !== j)
528
+ p *= a - X[k];
529
+ return 1 / p;
530
+ });
531
+ return xs.map((v) => {
532
+ let num = 0;
533
+ let den = 0;
534
+ // at a node, its value; with repeated nodes, that of the last one (as pracma)
535
+ const at = X.lastIndexOf(v);
536
+ if (at >= 0)
537
+ return Y[at];
538
+ for (let j = 0; j < n; j++) {
539
+ const diff = v - X[j];
540
+ const t = w[j] / diff;
541
+ num += t * Y[j];
542
+ den += t;
543
+ }
544
+ return num / den;
545
+ });
546
+ }
547
+ /**
548
+ * The polynomial of degree 2n - 1 through n points with given values and derivatives (global Hermite
549
+ * interpolation, jStat's hermite), evaluated at `x`. Computed by divided differences on the doubled nodes.
550
+ */
551
+ export function hermiteInterp(xi, yi, di, x) {
552
+ const X = Array.from(xi);
553
+ const Y = Array.from(yi);
554
+ const D = Array.from(di);
555
+ const n = X.length;
556
+ if (Y.length !== n || D.length !== n)
557
+ throw new RangeError("'xi', 'yi' and 'di' must have the same length");
558
+ const z = X.flatMap((v) => [v, v]);
559
+ const m = 2 * n;
560
+ // divided-difference table, column by column (in place)
561
+ const q = Y.flatMap((v) => [v, v]);
562
+ const coef = [q[0]];
563
+ let col = q.slice();
564
+ for (let k = 1; k < m; k++) {
565
+ const next = [];
566
+ for (let i = 0; i < m - k; i++) {
567
+ if (k === 1 && z[i + 1] === z[i])
568
+ next.push(D[i >> 1]);
569
+ else
570
+ next.push((col[i + 1] - col[i]) / (z[i + k] - z[i]));
571
+ }
572
+ coef.push(next[0]);
573
+ col = next;
574
+ }
575
+ return Array.from(x, (v) => {
576
+ let r = coef[m - 1];
577
+ for (let k = m - 2; k >= 0; k--)
578
+ r = r * (v - z[k]) + coef[k];
579
+ return r;
580
+ });
581
+ }
@@ -0,0 +1,23 @@
1
+ /** Derivative of the state: `func(t, y, parms)` returns dy/dt (one value per state variable). */
2
+ export type OdeFunc = (t: number, y: number[], parms: number[]) => readonly number[];
3
+ /** Options for {@link ode}. */
4
+ export interface OdeOptions {
5
+ /** `"euler"`, `"rk2"` (Heun) or `"rk4"` (classical Runge-Kutta, default). */
6
+ method?: "euler" | "rk2" | "rk4";
7
+ }
8
+ /**
9
+ * Solve the initial value problem y' = func(t, y, parms), y(times[0]) = y, with one fixed step per interval of
10
+ * `times`. Equivalent to `deSolve::ode(y, times, func, parms, method)` for `"euler"` and `"rk4"`; for `"rk2"`, to
11
+ * `deSolve::ode(..., method = rkMethod("rk2"))` (or `deSolve::rk(..., method = "rk2")`), since deSolve's `ode()`
12
+ * does not accept `method = "rk2"`.
13
+ *
14
+ * @returns One row per time: `[t, y1, y2, ...]`.
15
+ *
16
+ * @example
17
+ * ```ts
18
+ * // exponential decay y' = -0.7 y
19
+ * const out = ode([1], [0, 0.5, 1], (t, y, p) => [-p[0]! * y[0]!], [0.7], { method: "rk4" });
20
+ * out[2]; // [1, 0.4965...]
21
+ * ```
22
+ */
23
+ export declare function ode(y: readonly number[], times: readonly number[], func: OdeFunc, parms?: readonly number[], options?: OdeOptions): number[][];
@@ -0,0 +1,59 @@
1
+ // Fixed-step solvers of ordinary differential equations: deSolve's ode with method "euler" or "rk4", and its
2
+ // rkMethod("rk2") (deSolve's ode() itself does not accept method = "rk2").
3
+ //
4
+ // Written for rStats.js from the textbook formulas (no ported code). The step is the spacing of `times` (one step
5
+ // per interval), as in deSolve's fixed-step methods. rk2 is Heun's method (deSolve's rkMethod("rk2")): the slopes at
6
+ // the start and at the Euler predictor, averaged; rk4 is the classical fourth-order Runge-Kutta method.
7
+ /**
8
+ * Solve the initial value problem y' = func(t, y, parms), y(times[0]) = y, with one fixed step per interval of
9
+ * `times`. Equivalent to `deSolve::ode(y, times, func, parms, method)` for `"euler"` and `"rk4"`; for `"rk2"`, to
10
+ * `deSolve::ode(..., method = rkMethod("rk2"))` (or `deSolve::rk(..., method = "rk2")`), since deSolve's `ode()`
11
+ * does not accept `method = "rk2"`.
12
+ *
13
+ * @returns One row per time: `[t, y1, y2, ...]`.
14
+ *
15
+ * @example
16
+ * ```ts
17
+ * // exponential decay y' = -0.7 y
18
+ * const out = ode([1], [0, 0.5, 1], (t, y, p) => [-p[0]! * y[0]!], [0.7], { method: "rk4" });
19
+ * out[2]; // [1, 0.4965...]
20
+ * ```
21
+ */
22
+ export function ode(y, times, func, parms = [], options = {}) {
23
+ const method = options.method ?? "rk4";
24
+ if (method !== "euler" && method !== "rk2" && method !== "rk4")
25
+ throw new RangeError(`unknown method '${String(method)}'`);
26
+ const p = [...parms];
27
+ const n = y.length;
28
+ const deriv = (t, s) => {
29
+ const d = Array.from(func(t, s, p));
30
+ if (d.length !== n)
31
+ throw new RangeError(`The number of derivatives returned by func() (${d.length}) must equal the length of the initial conditions vector (${n})`);
32
+ return d;
33
+ };
34
+ const axpy = (s, h, k) => s.map((v, i) => v + h * k[i]);
35
+ let state = [...y];
36
+ const out = [];
37
+ if (times.length === 0)
38
+ return out;
39
+ out.push([times[0], ...state]);
40
+ for (let j = 1; j < times.length; j++) {
41
+ const t = times[j - 1];
42
+ const h = times[j] - t;
43
+ const k1 = deriv(t, state);
44
+ if (method === "euler")
45
+ state = axpy(state, h, k1);
46
+ else if (method === "rk2") {
47
+ const k2 = deriv(t + h, axpy(state, h, k1));
48
+ state = state.map((v, i) => v + h * (0.5 * k1[i] + 0.5 * k2[i]));
49
+ }
50
+ else {
51
+ const k2 = deriv(t + h / 2, axpy(state, h / 2, k1));
52
+ const k3 = deriv(t + h / 2, axpy(state, h / 2, k2));
53
+ const k4 = deriv(t + h, axpy(state, h, k3));
54
+ state = state.map((v, i) => v + (h / 6) * (k1[i] + 2 * k2[i] + 2 * k3[i] + k4[i]));
55
+ }
56
+ out.push([times[j], ...state]);
57
+ }
58
+ return out;
59
+ }