@cloudglides/nox 1.1.5 → 2.0.0
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/README.md +9 -9
- package/example/src/App.css +89 -84
- package/example/src/App.jsx +375 -151
- package/package.json +7 -6
- package/src/core.browser.js +10 -2
- package/src/core.js +32 -4
- package/src/generators/logistic.js +30 -25
- package/src/generators/mixer.js +7 -7
- package/src/generators/mt19937.js +10 -7
- package/src/generators/pcg64.js +23 -12
- package/src/generators/splitmix64.js +12 -6
- package/src/generators/tent.js +12 -7
- package/src/generators/xorshift64.js +6 -3
- package/src/index.d.ts +68 -4
- package/src/index.js +154 -2
- package/src/rng.browser.js +21 -10
- package/src/rng.js +95 -82
- package/src/utils/arrays.js +149 -0
- package/src/utils/bits.js +146 -21
- package/src/utils/categorical.js +68 -31
- package/src/utils/combinatorics.js +113 -69
- package/src/utils/confidence.js +145 -0
- package/src/utils/decomposition.js +204 -0
- package/src/utils/distributions-advanced.js +122 -0
- package/src/utils/distributions-extra.js +102 -11
- package/src/utils/distributions-special.js +77 -20
- package/src/utils/distributions.js +99 -35
- package/src/utils/effects.js +172 -0
- package/src/utils/entropy.browser.js +29 -26
- package/src/utils/entropy.js +18 -8
- package/src/utils/helpers.js +64 -0
- package/src/utils/hypothesis.js +167 -0
- package/src/utils/integration.js +137 -0
- package/src/utils/interpolation.js +221 -0
- package/src/utils/matrix.js +242 -0
- package/src/utils/noise.js +36 -22
- package/src/utils/odesolvers.js +176 -0
- package/src/utils/optimization.js +215 -0
- package/src/utils/precomputed.js +166 -0
- package/src/utils/probability.js +199 -0
- package/src/utils/regression.js +170 -0
- package/src/utils/resampling.js +112 -0
- package/src/utils/rootfinding.js +158 -0
- package/src/utils/sampling.js +86 -77
- package/src/utils/seed.js +10 -4
- package/src/utils/seeding.js +24 -12
- package/src/utils/sequence.js +116 -32
- package/src/utils/state.js +48 -36
- package/src/utils/statistics.js +64 -2
- package/src/utils/stochastic.js +91 -31
- package/src/utils/stratified.js +108 -0
- package/src/utils/timeseries.js +166 -0
- package/src/utils/transforms.js +146 -0
- package/test/comprehensive-new.js +126 -0
- package/test/comprehensive.js +4 -3
- package/test/error-handling.js +49 -0
- package/test/new-features.js +52 -0
- package/IMPROVEMENTS.md +0 -58
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
export const repeat = (count, fn) => {
|
|
2
|
+
if (typeof count !== 'number' || !Number.isInteger(count)) {
|
|
3
|
+
throw new TypeError('count must be an integer');
|
|
4
|
+
}
|
|
5
|
+
if (count < 0) {
|
|
6
|
+
throw new RangeError('count must be non-negative');
|
|
7
|
+
}
|
|
8
|
+
if (count === 0) {
|
|
9
|
+
return [];
|
|
10
|
+
}
|
|
11
|
+
if (typeof fn !== 'function') {
|
|
12
|
+
throw new TypeError('fn must be a function');
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const result = new Array(count);
|
|
16
|
+
for (let i = 0; i < count; i++) {
|
|
17
|
+
result[i] = fn(i);
|
|
18
|
+
}
|
|
19
|
+
return result;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export const until = (fn, maxIterations = Infinity) => {
|
|
23
|
+
if (typeof fn !== 'function') {
|
|
24
|
+
throw new TypeError('fn must be a function');
|
|
25
|
+
}
|
|
26
|
+
if (maxIterations !== Infinity && (typeof maxIterations !== 'number' || !Number.isInteger(maxIterations))) {
|
|
27
|
+
throw new TypeError('maxIterations must be an integer or Infinity');
|
|
28
|
+
}
|
|
29
|
+
if (maxIterations <= 0) {
|
|
30
|
+
throw new RangeError('maxIterations must be positive');
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
let iteration = 0;
|
|
34
|
+
while (iteration < maxIterations) {
|
|
35
|
+
const result = fn(iteration);
|
|
36
|
+
if (result) return iteration;
|
|
37
|
+
iteration++;
|
|
38
|
+
}
|
|
39
|
+
return -1;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export const times = (rng, count, fn) => {
|
|
43
|
+
if (!rng || typeof rng.nextFloat !== 'function') {
|
|
44
|
+
throw new TypeError('rng must be an RNG instance');
|
|
45
|
+
}
|
|
46
|
+
if (typeof count !== 'number' || !Number.isInteger(count)) {
|
|
47
|
+
throw new TypeError('count must be an integer');
|
|
48
|
+
}
|
|
49
|
+
if (count < 0) {
|
|
50
|
+
throw new RangeError('count must be non-negative');
|
|
51
|
+
}
|
|
52
|
+
if (count === 0) {
|
|
53
|
+
return [];
|
|
54
|
+
}
|
|
55
|
+
if (typeof fn !== 'function') {
|
|
56
|
+
throw new TypeError('fn must be a function');
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const result = new Array(count);
|
|
60
|
+
for (let i = 0; i < count; i++) {
|
|
61
|
+
result[i] = fn(rng, i);
|
|
62
|
+
}
|
|
63
|
+
return result;
|
|
64
|
+
};
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
export function tTest(group1, group2, paired = false) {
|
|
2
|
+
if (!Array.isArray(group1) || group1.length === 0) {
|
|
3
|
+
throw new TypeError('group1 must be non-empty array');
|
|
4
|
+
}
|
|
5
|
+
if (!Array.isArray(group2) || group2.length === 0) {
|
|
6
|
+
throw new TypeError('group2 must be non-empty array');
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
if (paired) {
|
|
10
|
+
if (group1.length !== group2.length) {
|
|
11
|
+
throw new RangeError('paired samples must have equal length');
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const diff = new Array(group1.length);
|
|
15
|
+
for (let i = 0; i < group1.length; i++) {
|
|
16
|
+
diff[i] = group1[i] - group2[i];
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const n = diff.length;
|
|
20
|
+
const mean = diff.reduce((a, b) => a + b, 0) / n;
|
|
21
|
+
let ss = 0;
|
|
22
|
+
|
|
23
|
+
for (let i = 0; i < n; i++) {
|
|
24
|
+
ss += (diff[i] - mean) ** 2;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const se = Math.sqrt(ss / (n * (n - 1)));
|
|
28
|
+
const t = mean / se;
|
|
29
|
+
|
|
30
|
+
return { t, df: n - 1, paired: true };
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const n1 = group1.length;
|
|
34
|
+
const n2 = group2.length;
|
|
35
|
+
|
|
36
|
+
const mean1 = group1.reduce((a, b) => a + b, 0) / n1;
|
|
37
|
+
const mean2 = group2.reduce((a, b) => a + b, 0) / n2;
|
|
38
|
+
|
|
39
|
+
let var1 = 0, var2 = 0;
|
|
40
|
+
for (let i = 0; i < n1; i++) {
|
|
41
|
+
var1 += (group1[i] - mean1) ** 2;
|
|
42
|
+
}
|
|
43
|
+
for (let i = 0; i < n2; i++) {
|
|
44
|
+
var2 += (group2[i] - mean2) ** 2;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
var1 /= n1 - 1;
|
|
48
|
+
var2 /= n2 - 1;
|
|
49
|
+
|
|
50
|
+
const pooledVar = ((n1 - 1) * var1 + (n2 - 1) * var2) / (n1 + n2 - 2);
|
|
51
|
+
const se = Math.sqrt(pooledVar * (1 / n1 + 1 / n2));
|
|
52
|
+
const t = (mean1 - mean2) / se;
|
|
53
|
+
|
|
54
|
+
return { t, df: n1 + n2 - 2, meandiff: mean1 - mean2 };
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function welchTTest(group1, group2) {
|
|
58
|
+
if (!Array.isArray(group1) || group1.length === 0) {
|
|
59
|
+
throw new TypeError('group1 must be non-empty array');
|
|
60
|
+
}
|
|
61
|
+
if (!Array.isArray(group2) || group2.length === 0) {
|
|
62
|
+
throw new TypeError('group2 must be non-empty array');
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const n1 = group1.length;
|
|
66
|
+
const n2 = group2.length;
|
|
67
|
+
|
|
68
|
+
const mean1 = group1.reduce((a, b) => a + b, 0) / n1;
|
|
69
|
+
const mean2 = group2.reduce((a, b) => a + b, 0) / n2;
|
|
70
|
+
|
|
71
|
+
let var1 = 0, var2 = 0;
|
|
72
|
+
for (let i = 0; i < n1; i++) {
|
|
73
|
+
var1 += (group1[i] - mean1) ** 2;
|
|
74
|
+
}
|
|
75
|
+
for (let i = 0; i < n2; i++) {
|
|
76
|
+
var2 += (group2[i] - mean2) ** 2;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
var1 /= n1 - 1;
|
|
80
|
+
var2 /= n2 - 1;
|
|
81
|
+
|
|
82
|
+
const se = Math.sqrt(var1 / n1 + var2 / n2);
|
|
83
|
+
const t = (mean1 - mean2) / se;
|
|
84
|
+
|
|
85
|
+
const df = (var1 / n1 + var2 / n2) ** 2 / (
|
|
86
|
+
(var1 / n1) ** 2 / (n1 - 1) + (var2 / n2) ** 2 / (n2 - 1)
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
return { t, df, meandiff: mean1 - mean2 };
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export function mannWhitneyU(group1, group2) {
|
|
93
|
+
if (!Array.isArray(group1) || group1.length === 0) {
|
|
94
|
+
throw new TypeError('group1 must be non-empty array');
|
|
95
|
+
}
|
|
96
|
+
if (!Array.isArray(group2) || group2.length === 0) {
|
|
97
|
+
throw new TypeError('group2 must be non-empty array');
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const combined = [...group1.map((x, i) => [x, 0, i]), ...group2.map((x, i) => [x, 1, i])];
|
|
101
|
+
combined.sort((a, b) => a[0] - b[0]);
|
|
102
|
+
|
|
103
|
+
const ranks1 = new Array(group1.length);
|
|
104
|
+
const ranks2 = new Array(group2.length);
|
|
105
|
+
|
|
106
|
+
for (let i = 0; i < combined.length; i++) {
|
|
107
|
+
if (combined[i][1] === 0) {
|
|
108
|
+
ranks1[combined[i][2]] = i + 1;
|
|
109
|
+
} else {
|
|
110
|
+
ranks2[combined[i][2]] = i + 1;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const r1 = ranks1.reduce((a, b) => a + b, 0);
|
|
115
|
+
const n1 = group1.length;
|
|
116
|
+
const n2 = group2.length;
|
|
117
|
+
|
|
118
|
+
const u1 = n1 * n2 + (n1 * (n1 + 1)) / 2 - r1;
|
|
119
|
+
const u2 = n1 * n2 - u1;
|
|
120
|
+
|
|
121
|
+
const mu = (n1 * n2) / 2;
|
|
122
|
+
const sigma = Math.sqrt((n1 * n2 * (n1 + n2 + 1)) / 12);
|
|
123
|
+
const z = (Math.min(u1, u2) - mu) / sigma;
|
|
124
|
+
|
|
125
|
+
return { U: Math.min(u1, u2), z, n1, n2 };
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export function oneWayAnova(groups) {
|
|
129
|
+
if (!Array.isArray(groups) || groups.length < 2) {
|
|
130
|
+
throw new TypeError('groups must be array of at least 2 arrays');
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
let N = 0;
|
|
134
|
+
const means = new Array(groups.length);
|
|
135
|
+
|
|
136
|
+
for (let i = 0; i < groups.length; i++) {
|
|
137
|
+
if (!Array.isArray(groups[i]) || groups[i].length === 0) {
|
|
138
|
+
throw new TypeError('each group must be non-empty array');
|
|
139
|
+
}
|
|
140
|
+
means[i] = groups[i].reduce((a, b) => a + b, 0) / groups[i].length;
|
|
141
|
+
N += groups[i].length;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const grandMean = groups.flat().reduce((a, b) => a + b, 0) / N;
|
|
145
|
+
|
|
146
|
+
let ssB = 0;
|
|
147
|
+
for (let i = 0; i < groups.length; i++) {
|
|
148
|
+
ssB += groups[i].length * (means[i] - grandMean) ** 2;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
let ssW = 0;
|
|
152
|
+
for (let i = 0; i < groups.length; i++) {
|
|
153
|
+
for (let j = 0; j < groups[i].length; j++) {
|
|
154
|
+
ssW += (groups[i][j] - means[i]) ** 2;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const k = groups.length;
|
|
159
|
+
const dfB = k - 1;
|
|
160
|
+
const dfW = N - k;
|
|
161
|
+
|
|
162
|
+
const msB = ssB / dfB;
|
|
163
|
+
const msW = ssW / dfW;
|
|
164
|
+
const f = msB / msW;
|
|
165
|
+
|
|
166
|
+
return { f, dfB, dfW, ssB, ssW, msB, msW };
|
|
167
|
+
}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
export function trapezoidal(f, a, b, n = 100) {
|
|
2
|
+
if (typeof f !== 'function') {
|
|
3
|
+
throw new TypeError('f must be function');
|
|
4
|
+
}
|
|
5
|
+
if (typeof a !== 'number' || typeof b !== 'number') {
|
|
6
|
+
throw new TypeError('a and b must be numbers');
|
|
7
|
+
}
|
|
8
|
+
if (typeof n !== 'number' || n < 1) {
|
|
9
|
+
throw new RangeError('n must be positive integer');
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const h = (b - a) / n;
|
|
13
|
+
let sum = (f(a) + f(b)) / 2;
|
|
14
|
+
|
|
15
|
+
for (let i = 1; i < n; i++) {
|
|
16
|
+
sum += f(a + i * h);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
return h * sum;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function simpsons(f, a, b, n = 100) {
|
|
23
|
+
if (typeof f !== 'function') {
|
|
24
|
+
throw new TypeError('f must be function');
|
|
25
|
+
}
|
|
26
|
+
if (typeof a !== 'number' || typeof b !== 'number') {
|
|
27
|
+
throw new TypeError('a and b must be numbers');
|
|
28
|
+
}
|
|
29
|
+
if (typeof n !== 'number' || n < 2 || n % 2 !== 0) {
|
|
30
|
+
throw new RangeError('n must be even integer >= 2');
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const h = (b - a) / n;
|
|
34
|
+
let sum = f(a) + f(b);
|
|
35
|
+
|
|
36
|
+
for (let i = 1; i < n; i++) {
|
|
37
|
+
const coeff = i % 2 === 0 ? 2 : 4;
|
|
38
|
+
sum += coeff * f(a + i * h);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return (h / 3) * sum;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function adaptiveSimpson(f, a, b, tol = 1e-6, depth = 0, maxDepth = 50) {
|
|
45
|
+
if (typeof f !== 'function') {
|
|
46
|
+
throw new TypeError('f must be function');
|
|
47
|
+
}
|
|
48
|
+
if (typeof a !== 'number' || typeof b !== 'number') {
|
|
49
|
+
throw new TypeError('a and b must be numbers');
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const c = (a + b) / 2;
|
|
53
|
+
const h = b - a;
|
|
54
|
+
const fa = f(a);
|
|
55
|
+
const fb = f(b);
|
|
56
|
+
const fc = f(c);
|
|
57
|
+
|
|
58
|
+
const S = (h / 6) * (fa + 4 * fc + fb);
|
|
59
|
+
const fd = f((a + c) / 2);
|
|
60
|
+
const fe = f((c + b) / 2);
|
|
61
|
+
|
|
62
|
+
const left = (h / 12) * (fa + 4 * fd + fc);
|
|
63
|
+
const right = (h / 12) * (fc + 4 * fe + fb);
|
|
64
|
+
|
|
65
|
+
if (depth >= maxDepth || Math.abs(left + right - S) <= 15 * tol) {
|
|
66
|
+
return left + right + (left + right - S) / 15;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return adaptiveSimpson(f, a, c, tol / 2, depth + 1, maxDepth) +
|
|
70
|
+
adaptiveSimpson(f, c, b, tol / 2, depth + 1, maxDepth);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function gaussQuadrature(f, a, b, n = 5) {
|
|
74
|
+
if (typeof f !== 'function') {
|
|
75
|
+
throw new TypeError('f must be function');
|
|
76
|
+
}
|
|
77
|
+
if (typeof a !== 'number' || typeof b !== 'number') {
|
|
78
|
+
throw new TypeError('a and b must be numbers');
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const weights = getGaussWeights(n);
|
|
82
|
+
const nodes = getGaussNodes(n);
|
|
83
|
+
|
|
84
|
+
let sum = 0;
|
|
85
|
+
for (let i = 0; i < n; i++) {
|
|
86
|
+
const x = ((b - a) * nodes[i] + (b + a)) / 2;
|
|
87
|
+
sum += weights[i] * f(x);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return ((b - a) / 2) * sum;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function getGaussWeights(n) {
|
|
94
|
+
const weights = {
|
|
95
|
+
1: [2],
|
|
96
|
+
2: [1, 1],
|
|
97
|
+
3: [0.5555555556, 0.8888888889, 0.5555555556],
|
|
98
|
+
4: [0.3478548451, 0.6521451549, 0.6521451549, 0.3478548451],
|
|
99
|
+
5: [0.2369268851, 0.4786286705, 0.5688888889, 0.4786286705, 0.2369268851]
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
return weights[n] || weights[5];
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function getGaussNodes(n) {
|
|
106
|
+
const nodes = {
|
|
107
|
+
1: [0],
|
|
108
|
+
2: [-0.5773502692, 0.5773502692],
|
|
109
|
+
3: [-0.7745966692, 0, 0.7745966692],
|
|
110
|
+
4: [-0.8611363116, -0.3399810436, 0.3399810436, 0.8611363116],
|
|
111
|
+
5: [-0.9061798459, -0.5384693101, 0, 0.5384693101, 0.9061798459]
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
return nodes[n] || nodes[5];
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export function numericalDerivative(f, x, h = 1e-5) {
|
|
118
|
+
if (typeof f !== 'function') {
|
|
119
|
+
throw new TypeError('f must be function');
|
|
120
|
+
}
|
|
121
|
+
if (typeof x !== 'number') {
|
|
122
|
+
throw new TypeError('x must be number');
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return (f(x + h) - f(x - h)) / (2 * h);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export function numericalSecondDerivative(f, x, h = 1e-5) {
|
|
129
|
+
if (typeof f !== 'function') {
|
|
130
|
+
throw new TypeError('f must be function');
|
|
131
|
+
}
|
|
132
|
+
if (typeof x !== 'number') {
|
|
133
|
+
throw new TypeError('x must be number');
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return (f(x + h) - 2 * f(x) + f(x - h)) / (h * h);
|
|
137
|
+
}
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
export class LinearInterpolator {
|
|
2
|
+
constructor(x, y) {
|
|
3
|
+
if (!Array.isArray(x) || !Array.isArray(y)) {
|
|
4
|
+
throw new TypeError('x and y must be arrays');
|
|
5
|
+
}
|
|
6
|
+
if (x.length !== y.length || x.length < 2) {
|
|
7
|
+
throw new RangeError('x and y must have same length >= 2');
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const sorted = x.map((xi, i) => [xi, y[i]]).sort((a, b) => a[0] - b[0]);
|
|
11
|
+
this.x = sorted.map(p => p[0]);
|
|
12
|
+
this.y = sorted.map(p => p[1]);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
evaluate(xVal) {
|
|
16
|
+
if (typeof xVal === 'number') {
|
|
17
|
+
return this._interpolate(xVal);
|
|
18
|
+
}
|
|
19
|
+
if (Array.isArray(xVal)) {
|
|
20
|
+
return xVal.map(x => this._interpolate(x));
|
|
21
|
+
}
|
|
22
|
+
throw new TypeError('xVal must be number or array');
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
_interpolate(xVal) {
|
|
26
|
+
if (xVal < this.x[0] || xVal > this.x[this.x.length - 1]) {
|
|
27
|
+
throw new RangeError(`xVal must be in [${this.x[0]}, ${this.x[this.x.length - 1]}]`);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
let i = 0;
|
|
31
|
+
while (i < this.x.length - 1 && this.x[i + 1] < xVal) {
|
|
32
|
+
i++;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const x0 = this.x[i];
|
|
36
|
+
const x1 = this.x[i + 1];
|
|
37
|
+
const y0 = this.y[i];
|
|
38
|
+
const y1 = this.y[i + 1];
|
|
39
|
+
|
|
40
|
+
return y0 + (y1 - y0) * (xVal - x0) / (x1 - x0);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export class CubicSplineInterpolator {
|
|
45
|
+
constructor(x, y) {
|
|
46
|
+
if (!Array.isArray(x) || !Array.isArray(y)) {
|
|
47
|
+
throw new TypeError('x and y must be arrays');
|
|
48
|
+
}
|
|
49
|
+
if (x.length !== y.length || x.length < 3) {
|
|
50
|
+
throw new RangeError('x and y must have same length >= 3');
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const sorted = x.map((xi, i) => [xi, y[i]]).sort((a, b) => a[0] - b[0]);
|
|
54
|
+
this.x = sorted.map(p => p[0]);
|
|
55
|
+
this.y = sorted.map(p => p[1]);
|
|
56
|
+
|
|
57
|
+
this._computeSplines();
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
_computeSplines() {
|
|
61
|
+
const n = this.x.length;
|
|
62
|
+
const h = new Array(n - 1);
|
|
63
|
+
const alpha = new Array(n - 1);
|
|
64
|
+
|
|
65
|
+
for (let i = 0; i < n - 1; i++) {
|
|
66
|
+
h[i] = this.x[i + 1] - this.x[i];
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
for (let i = 1; i < n - 1; i++) {
|
|
70
|
+
alpha[i] = (3 / h[i]) * (this.y[i + 1] - this.y[i]) - (3 / h[i - 1]) * (this.y[i] - this.y[i - 1]);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const l = new Array(n);
|
|
74
|
+
const mu = new Array(n);
|
|
75
|
+
const z = new Array(n);
|
|
76
|
+
|
|
77
|
+
l[0] = 1;
|
|
78
|
+
mu[0] = 0;
|
|
79
|
+
z[0] = 0;
|
|
80
|
+
|
|
81
|
+
for (let i = 1; i < n - 1; i++) {
|
|
82
|
+
l[i] = 2 * (this.x[i + 1] - this.x[i - 1]) - h[i - 1] * mu[i - 1];
|
|
83
|
+
mu[i] = h[i] / l[i];
|
|
84
|
+
z[i] = (alpha[i] - h[i - 1] * z[i - 1]) / l[i];
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
l[n - 1] = 1;
|
|
88
|
+
z[n - 1] = 0;
|
|
89
|
+
|
|
90
|
+
this.c = new Array(n);
|
|
91
|
+
this.b = new Array(n - 1);
|
|
92
|
+
this.d = new Array(n - 1);
|
|
93
|
+
|
|
94
|
+
this.c[n - 1] = 0;
|
|
95
|
+
|
|
96
|
+
for (let i = n - 2; i >= 0; i--) {
|
|
97
|
+
this.c[i] = z[i] - mu[i] * this.c[i + 1];
|
|
98
|
+
this.b[i] = (this.y[i + 1] - this.y[i]) / h[i] - h[i] * (this.c[i + 1] + 2 * this.c[i]) / 3;
|
|
99
|
+
this.d[i] = (this.c[i + 1] - this.c[i]) / (3 * h[i]);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
evaluate(xVal) {
|
|
104
|
+
if (typeof xVal === 'number') {
|
|
105
|
+
return this._interpolate(xVal);
|
|
106
|
+
}
|
|
107
|
+
if (Array.isArray(xVal)) {
|
|
108
|
+
return xVal.map(x => this._interpolate(x));
|
|
109
|
+
}
|
|
110
|
+
throw new TypeError('xVal must be number or array');
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
_interpolate(xVal) {
|
|
114
|
+
if (xVal < this.x[0] || xVal > this.x[this.x.length - 1]) {
|
|
115
|
+
throw new RangeError(`xVal must be in [${this.x[0]}, ${this.x[this.x.length - 1]}]`);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
let i = 0;
|
|
119
|
+
while (i < this.x.length - 1 && this.x[i + 1] < xVal) {
|
|
120
|
+
i++;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const dx = xVal - this.x[i];
|
|
124
|
+
return this.y[i] + this.b[i] * dx + this.c[i] * dx * dx + this.d[i] * dx * dx * dx;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export function lagrangeInterpolation(x, y, xVal) {
|
|
129
|
+
if (!Array.isArray(x) || !Array.isArray(y)) {
|
|
130
|
+
throw new TypeError('x and y must be arrays');
|
|
131
|
+
}
|
|
132
|
+
if (x.length !== y.length || x.length === 0) {
|
|
133
|
+
throw new RangeError('x and y must have same non-zero length');
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (typeof xVal === 'number') {
|
|
137
|
+
return _lagrange(x, y, xVal);
|
|
138
|
+
}
|
|
139
|
+
if (Array.isArray(xVal)) {
|
|
140
|
+
return xVal.map(x => _lagrange(x, y, x));
|
|
141
|
+
}
|
|
142
|
+
throw new TypeError('xVal must be number or array');
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function _lagrange(x, y, xVal) {
|
|
146
|
+
let result = 0;
|
|
147
|
+
const n = x.length;
|
|
148
|
+
|
|
149
|
+
for (let i = 0; i < n; i++) {
|
|
150
|
+
let term = y[i];
|
|
151
|
+
for (let j = 0; j < n; j++) {
|
|
152
|
+
if (i !== j) {
|
|
153
|
+
term *= (xVal - x[j]) / (x[i] - x[j]);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
result += term;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return result;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export function polynomialFit(x, y, degree) {
|
|
163
|
+
if (!Array.isArray(x) || !Array.isArray(y)) {
|
|
164
|
+
throw new TypeError('x and y must be arrays');
|
|
165
|
+
}
|
|
166
|
+
if (x.length !== y.length || x.length <= degree) {
|
|
167
|
+
throw new RangeError('need at least degree+1 points');
|
|
168
|
+
}
|
|
169
|
+
if (typeof degree !== 'number' || degree < 1) {
|
|
170
|
+
throw new RangeError('degree must be positive integer');
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const n = x.length;
|
|
174
|
+
const A = new Array(degree + 1);
|
|
175
|
+
|
|
176
|
+
for (let i = 0; i <= degree; i++) {
|
|
177
|
+
A[i] = new Array(degree + 2);
|
|
178
|
+
for (let j = 0; j <= degree; j++) {
|
|
179
|
+
let sum = 0;
|
|
180
|
+
for (let k = 0; k < n; k++) {
|
|
181
|
+
sum += Math.pow(x[k], i + j);
|
|
182
|
+
}
|
|
183
|
+
A[i][j] = sum;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
let sum = 0;
|
|
187
|
+
for (let k = 0; k < n; k++) {
|
|
188
|
+
sum += y[k] * Math.pow(x[k], i);
|
|
189
|
+
}
|
|
190
|
+
A[i][degree + 1] = sum;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
for (let i = 0; i <= degree; i++) {
|
|
194
|
+
let maxRow = i;
|
|
195
|
+
for (let k = i + 1; k <= degree; k++) {
|
|
196
|
+
if (Math.abs(A[k][i]) > Math.abs(A[maxRow][i])) {
|
|
197
|
+
maxRow = k;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
[A[i], A[maxRow]] = [A[maxRow], A[i]];
|
|
202
|
+
|
|
203
|
+
for (let k = i + 1; k <= degree; k++) {
|
|
204
|
+
const factor = A[k][i] / A[i][i];
|
|
205
|
+
for (let j = i; j <= degree + 1; j++) {
|
|
206
|
+
A[k][j] -= factor * A[i][j];
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
const coeffs = new Array(degree + 1);
|
|
212
|
+
for (let i = degree; i >= 0; i--) {
|
|
213
|
+
coeffs[i] = A[i][degree + 1];
|
|
214
|
+
for (let j = i + 1; j <= degree; j++) {
|
|
215
|
+
coeffs[i] -= A[i][j] * coeffs[j];
|
|
216
|
+
}
|
|
217
|
+
coeffs[i] /= A[i][i];
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
return coeffs;
|
|
221
|
+
}
|