smath 1.15.1 → 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 +51 -0
- package/dist/bin.js +6 -9
- package/dist/datafit/index.js +2 -0
- package/dist/datafit/lib.js +72 -0
- package/dist/datafit/types.js +1 -0
- package/dist/index.js +4 -570
- package/dist/smath.js +506 -0
- package/package.json +21 -4
- package/types/datafit/index.d.ts +2 -0
- package/types/datafit/lib.d.ts +25 -0
- package/types/datafit/types.d.ts +53 -0
- package/types/index.d.ts +2 -305
- package/types/smath.d.ts +305 -0
package/dist/smath.js
ADDED
|
@@ -0,0 +1,506 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Check if two numbers are approximately equal with a maximum abolute error.
|
|
3
|
+
* @param a Any number
|
|
4
|
+
* @param b Any number
|
|
5
|
+
* @param epsilon Maximum absolute error
|
|
6
|
+
* @returns True if `a` is approximately `b`
|
|
7
|
+
* @example
|
|
8
|
+
* const b1 = SMath.approx(1 / 3, 0.33, 1e-6), // false
|
|
9
|
+
* b2 = SMath.approx(1 / 3, 0.33, 1e-2); // true
|
|
10
|
+
*/
|
|
11
|
+
export function approx(a, b, epsilon = 1e-6) {
|
|
12
|
+
return a - b < epsilon && b - a < epsilon;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Clamp a number within a range.
|
|
16
|
+
* @param n The number to clamp
|
|
17
|
+
* @param min The minimum value of the range
|
|
18
|
+
* @param max The maximum value of the range
|
|
19
|
+
* @returns A clamped number
|
|
20
|
+
* @example
|
|
21
|
+
* const n1 = SMath.clamp(5, 0, 10), // 5
|
|
22
|
+
* n2 = SMath.clamp(-2, 0, 10); // 0
|
|
23
|
+
*/
|
|
24
|
+
export function clamp(n, min, max) {
|
|
25
|
+
if (n < min) {
|
|
26
|
+
return min;
|
|
27
|
+
}
|
|
28
|
+
if (n > max) {
|
|
29
|
+
return max;
|
|
30
|
+
}
|
|
31
|
+
return n;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Normalize the number `n` from the range `min, max` to the range `0, 1`
|
|
35
|
+
* @param n The number to normalize
|
|
36
|
+
* @param min The minimum value in the range
|
|
37
|
+
* @param max The maximum value in the range
|
|
38
|
+
* @returns A normalized value
|
|
39
|
+
* @example
|
|
40
|
+
* const y = SMath.normalize(18, 9, 99); // 0.1
|
|
41
|
+
*/
|
|
42
|
+
export function normalize(n, min, max) {
|
|
43
|
+
if (min === max) {
|
|
44
|
+
return 0;
|
|
45
|
+
}
|
|
46
|
+
return (n - min) / (max - min);
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Expand a normalized number `n` to the range `min, max`
|
|
50
|
+
* @param n A normalized number
|
|
51
|
+
* @param min The minimum value in the range
|
|
52
|
+
* @param max The maximum value in the range
|
|
53
|
+
* @returns A value within the number range
|
|
54
|
+
* @example
|
|
55
|
+
* const y = SMath.expand(0.25, 4, 6); // 4.5
|
|
56
|
+
*/
|
|
57
|
+
export function expand(n, min, max) {
|
|
58
|
+
return (max - min) * n + min;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Translate a number `n` from the range `min1, max1` to the range `min2, max2`
|
|
62
|
+
* @param n The number to translate
|
|
63
|
+
* @param min1 The minimum value from the initial range
|
|
64
|
+
* @param max1 The maximum value from the initial range
|
|
65
|
+
* @param min2 The minimum value for the final range
|
|
66
|
+
* @param max2 The maximum value for the final range
|
|
67
|
+
* @returns A translated number in the final range
|
|
68
|
+
* @example
|
|
69
|
+
* const C = 20,
|
|
70
|
+
* F = SMath.translate(C, 0, 100, 32, 212); // 68
|
|
71
|
+
*/
|
|
72
|
+
export function translate(n, min1, max1, min2, max2) {
|
|
73
|
+
return expand(normalize(n, min1, max1), min2, max2);
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Generate an array of linearly spaced numbers.
|
|
77
|
+
* @param min The initial value of the linear space
|
|
78
|
+
* @param max The final value of the linear space
|
|
79
|
+
* @param count The number of values in the space
|
|
80
|
+
* @returns The linear space as an array of numbers
|
|
81
|
+
* @example
|
|
82
|
+
* const space = SMath.linspace(1, 5, 6);
|
|
83
|
+
* // [ 1, 1.8, 2.6, 3.4, 4.2, 5 ]
|
|
84
|
+
*/
|
|
85
|
+
export function linspace(min, max, count) {
|
|
86
|
+
const space = [];
|
|
87
|
+
for (let i = 0; i < count; i++) {
|
|
88
|
+
space[i] = translate(i, 0, count - 1, min, max);
|
|
89
|
+
}
|
|
90
|
+
return space;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Generate an array of logarithmically spaced numbers.
|
|
94
|
+
* @param min The initial magnitude of the space
|
|
95
|
+
* @param max The final magnitude of the space
|
|
96
|
+
* @param count The number of values in the space
|
|
97
|
+
* @returns The logarithmic space as an array of numbers
|
|
98
|
+
* @example
|
|
99
|
+
* const space = SMath.logspace(0, 2, 5);
|
|
100
|
+
* // [ 1, 3.2, 10, 31.6, 100 ]
|
|
101
|
+
*/
|
|
102
|
+
export function logspace(min, max, count) {
|
|
103
|
+
return linspace(min, max, count).map(n => 10 ** n);
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Compute the factorial of `n`.
|
|
107
|
+
* @param n Any positive integer
|
|
108
|
+
* @returns `n!`
|
|
109
|
+
* @example
|
|
110
|
+
* const y = SMath.factorial(5); // 120
|
|
111
|
+
*/
|
|
112
|
+
export function factorial(n) {
|
|
113
|
+
if (n < 0 || (n | 0) !== n) {
|
|
114
|
+
throw new Error('Input must be a positive integer.');
|
|
115
|
+
}
|
|
116
|
+
else if (n === 0) {
|
|
117
|
+
return 1;
|
|
118
|
+
}
|
|
119
|
+
else if (n <= 2) {
|
|
120
|
+
return n;
|
|
121
|
+
}
|
|
122
|
+
else {
|
|
123
|
+
return n * factorial(n - 1);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Factorize `n` into its prime factors.
|
|
128
|
+
* @param n Any positive integer
|
|
129
|
+
* @returns The array of prime factors
|
|
130
|
+
* @example
|
|
131
|
+
* const y = SMath.factors(12); // [ 2, 2, 3 ]
|
|
132
|
+
*/
|
|
133
|
+
export function factors(n) {
|
|
134
|
+
if (n < 0 || (n | 0) !== n) {
|
|
135
|
+
throw new Error('Input must be a positive integer!');
|
|
136
|
+
}
|
|
137
|
+
if (n <= 3) {
|
|
138
|
+
return [n];
|
|
139
|
+
}
|
|
140
|
+
const f = [];
|
|
141
|
+
let i = 2;
|
|
142
|
+
while (n > 1 && i <= n) {
|
|
143
|
+
if ((n / i) === ((n / i) | 0)) {
|
|
144
|
+
n /= i;
|
|
145
|
+
f.push(i);
|
|
146
|
+
}
|
|
147
|
+
else {
|
|
148
|
+
i++;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
return f;
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Round a number to the nearest multiple of an arbitrary
|
|
155
|
+
* base. Does not round when the base is set to zero.
|
|
156
|
+
* @param n Any number to round
|
|
157
|
+
* @param base Any base to round to
|
|
158
|
+
* @returns `n` rounded to the nearest multiple of `base`
|
|
159
|
+
* @example
|
|
160
|
+
* const y = SMath.round2(Math.PI, 0.2); // 3.2
|
|
161
|
+
*/
|
|
162
|
+
export function round2(n, base) {
|
|
163
|
+
const rounded = base ? base * Math.round(n / base) : n;
|
|
164
|
+
const precision = 10; // Removes precision errors
|
|
165
|
+
return parseFloat(rounded.toFixed(precision));
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Calculate the relative normalized error or deviation from any
|
|
169
|
+
* value to an accepted value. An error of 0 indicates that the
|
|
170
|
+
* two values are identical. An error of -0.1 indicates that the
|
|
171
|
+
* experimental value is 10% smaller than (90% of) the accepted
|
|
172
|
+
* value. An error of 1.0 indicates that the experimental value
|
|
173
|
+
* is 100% greater (or twice the size) of the accepted value.
|
|
174
|
+
* @param experimental The value observed or produced by a test
|
|
175
|
+
* @param actual The accepted or theoretical value
|
|
176
|
+
* @returns The relative (normalized) error
|
|
177
|
+
* @example
|
|
178
|
+
* const e = SMath.error(22.5, 25); // -0.1
|
|
179
|
+
*/
|
|
180
|
+
export function error(experimental, actual) {
|
|
181
|
+
if (experimental === 0 && actual === 0) {
|
|
182
|
+
return 0;
|
|
183
|
+
}
|
|
184
|
+
else {
|
|
185
|
+
return (experimental - actual) / actual;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Add up all the inputs.
|
|
190
|
+
* If none are present, returns 0.
|
|
191
|
+
* @param data An array of numeric inputs
|
|
192
|
+
* @returns The sum total
|
|
193
|
+
* @example
|
|
194
|
+
* const y = SMath.sum([1, 2, 3]); // 6
|
|
195
|
+
*/
|
|
196
|
+
export function sum(data) {
|
|
197
|
+
return data.reduce((a, b) => a + b, 0);
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Multiply all the inputs.
|
|
201
|
+
* If none are present, returns 1.
|
|
202
|
+
* @param data An array of numeric inputs
|
|
203
|
+
* @returns The product
|
|
204
|
+
* @example
|
|
205
|
+
* const y = SMath.prod([2, 2, 3, 5]); // 60
|
|
206
|
+
*/
|
|
207
|
+
export function prod(data) {
|
|
208
|
+
return data.reduce((a, b) => a * b, 1);
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Compute the average, or mean, of a set of numbers.
|
|
212
|
+
* @param data An array of numeric inputs
|
|
213
|
+
* @returns The average, or mean
|
|
214
|
+
* @example
|
|
215
|
+
* const y = SMath.avg([1, 2, 4, 4]); // 2.75
|
|
216
|
+
*/
|
|
217
|
+
export function avg(data) {
|
|
218
|
+
return sum(data) / data.length;
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* Compute the median of a set of numbers.
|
|
222
|
+
* @param data An array of numeric inputs
|
|
223
|
+
* @returns The median of the dataset
|
|
224
|
+
* @example
|
|
225
|
+
* const y = SMath.median([2, 5, 3, 1]); // 2.5
|
|
226
|
+
*/
|
|
227
|
+
export function median(data) {
|
|
228
|
+
data.sort((a, b) => a - b);
|
|
229
|
+
if (data.length % 2) {
|
|
230
|
+
return data[(data.length - 1) / 2];
|
|
231
|
+
}
|
|
232
|
+
return avg([data[data.length / 2 - 1], data[data.length / 2]]);
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* Compute the variance of a **complete population**.
|
|
236
|
+
* @param data An array of numeric inputs
|
|
237
|
+
* @returns The population variance
|
|
238
|
+
* @example
|
|
239
|
+
* const y = SMath.varp([1, 2, 4, 4]); // 1.6875
|
|
240
|
+
*/
|
|
241
|
+
export function varp(data) {
|
|
242
|
+
const mean = avg(data), squares = data.map(x => (x - mean) ** 2);
|
|
243
|
+
return sum(squares) / data.length;
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* Compute the variance of a **sample**.
|
|
247
|
+
* @param data An array of numeric inputs
|
|
248
|
+
* @returns The sample variance
|
|
249
|
+
* @example
|
|
250
|
+
* const y = SMath.vars([1, 2, 4, 4]); // 2.25
|
|
251
|
+
*/
|
|
252
|
+
export function vars(data) {
|
|
253
|
+
const mean = avg(data), squares = data.map(x => (x - mean) ** 2);
|
|
254
|
+
return sum(squares) / (data.length - 1);
|
|
255
|
+
}
|
|
256
|
+
/**
|
|
257
|
+
* Compute the standard deviation of a **complete population**.
|
|
258
|
+
* @param data An array of numeric inputs
|
|
259
|
+
* @returns The population standard deviation
|
|
260
|
+
* @example
|
|
261
|
+
* const y = SMath.stdevp([1, 2, 3, 4]); // 1.118...
|
|
262
|
+
*/
|
|
263
|
+
export function stdevp(data) {
|
|
264
|
+
return Math.sqrt(varp(data));
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* Compute the standard deviation of a **sample**.
|
|
268
|
+
* @param data An array of numeric inputs
|
|
269
|
+
* @returns The sample standard deviation
|
|
270
|
+
* @example
|
|
271
|
+
* const y = SMath.stdevs([1, 2, 3, 4]); // 1.29...
|
|
272
|
+
*/
|
|
273
|
+
export function stdevs(data) {
|
|
274
|
+
return Math.sqrt(vars(data));
|
|
275
|
+
}
|
|
276
|
+
/**
|
|
277
|
+
* Generate a uniformly-distributed floating-point number within the range.
|
|
278
|
+
* @param min The minimum bound
|
|
279
|
+
* @param max The maximum bound
|
|
280
|
+
* @returns A random float within the range
|
|
281
|
+
* @example
|
|
282
|
+
* const y = SMath.runif(-2, 2); // 0.376...
|
|
283
|
+
*/
|
|
284
|
+
export function runif(min, max) {
|
|
285
|
+
return expand(Math.random(), min, max);
|
|
286
|
+
}
|
|
287
|
+
/**
|
|
288
|
+
* Generate a uniformly-distributed integer within the range.
|
|
289
|
+
* @param min The minimum bound (inclusive)
|
|
290
|
+
* @param max The maximum bound (inclusive)
|
|
291
|
+
* @returns A random integer within the range
|
|
292
|
+
* @example
|
|
293
|
+
* const y = SMath.rint(-4, 3); // -4
|
|
294
|
+
*/
|
|
295
|
+
export function rint(min, max) {
|
|
296
|
+
min |= 0;
|
|
297
|
+
max |= 0;
|
|
298
|
+
if (min < 0) {
|
|
299
|
+
min--;
|
|
300
|
+
}
|
|
301
|
+
if (max > 0) {
|
|
302
|
+
max++;
|
|
303
|
+
}
|
|
304
|
+
return clamp(runif(min, max), min, max) | 0; // `| 0` pulls toward 0
|
|
305
|
+
}
|
|
306
|
+
/**
|
|
307
|
+
* Generate a normally-distributed floating-point number.
|
|
308
|
+
* @param mean The mean of the population distribution
|
|
309
|
+
* @param stdev The standard deviation of the population
|
|
310
|
+
* @returns A random float
|
|
311
|
+
* @example
|
|
312
|
+
* const y = SMath.rnorm(2, 3); // 1.627...
|
|
313
|
+
*/
|
|
314
|
+
export function rnorm(mean = 0, stdev = 1) {
|
|
315
|
+
return mean + stdev * Math.sqrt(-2 * Math.log(Math.random())) * Math.cos(2 * Math.PI * Math.random());
|
|
316
|
+
}
|
|
317
|
+
/**
|
|
318
|
+
* Generate a population of normally-distributed floating-point numbers.
|
|
319
|
+
* @param count The number of values to generate
|
|
320
|
+
* @param mean The mean of the population distribution
|
|
321
|
+
* @param stdev The standard deviation of the population
|
|
322
|
+
* @returns A population of random floats
|
|
323
|
+
* @example
|
|
324
|
+
* const dataset = SMath.rdist(3); // [ 1.051..., -0.779..., -2.254... ]
|
|
325
|
+
*/
|
|
326
|
+
export function rdist(count, mean = 0, stdev = 1) {
|
|
327
|
+
const distribution = [];
|
|
328
|
+
for (let i = 0; i < count; i++) {
|
|
329
|
+
distribution[i] = rnorm(mean, stdev);
|
|
330
|
+
}
|
|
331
|
+
return distribution;
|
|
332
|
+
}
|
|
333
|
+
/**
|
|
334
|
+
* Randomize an array of arbitrary elements.
|
|
335
|
+
* @param stack An array of arbitrary elements
|
|
336
|
+
* @returns The `stack` array in a random order
|
|
337
|
+
* @example
|
|
338
|
+
* const shuffled = SMath.shuffle(['a', 'b', 'c']); // [ 'c', 'a', 'b' ]
|
|
339
|
+
*/
|
|
340
|
+
export function shuffle(stack) {
|
|
341
|
+
const rawData = [];
|
|
342
|
+
for (const item of stack) {
|
|
343
|
+
rawData.push({ index: Math.random(), value: item });
|
|
344
|
+
}
|
|
345
|
+
return rawData.sort((a, b) => a.index - b.index).map(a => a.value);
|
|
346
|
+
}
|
|
347
|
+
/**
|
|
348
|
+
* Select a single item from an array at random with uniform weights.
|
|
349
|
+
* @param stack An array of arbirary item
|
|
350
|
+
* @returns A single randomly selected item
|
|
351
|
+
* @example
|
|
352
|
+
* const selected = SMath.selectRandom([10, 20, 30, 40]); // 30
|
|
353
|
+
*/
|
|
354
|
+
export function selectRandom(stack) {
|
|
355
|
+
return stack[rint(0, stack.length - 1)];
|
|
356
|
+
}
|
|
357
|
+
/**
|
|
358
|
+
* Select a single index in an array at random with different weights.
|
|
359
|
+
* @param weights The weights for each item
|
|
360
|
+
* @returns The 0-based index of the randomly selected item
|
|
361
|
+
* @example
|
|
362
|
+
* const index = SMath.selectRandomWeighted([3.5, 4, 1]); // 1
|
|
363
|
+
*/
|
|
364
|
+
export function selectRandomWeighted(weights) {
|
|
365
|
+
const startWeights = [];
|
|
366
|
+
let accumulation = 0;
|
|
367
|
+
for (const weight of weights) {
|
|
368
|
+
accumulation += clamp(0, weight, Infinity);
|
|
369
|
+
startWeights.push(accumulation);
|
|
370
|
+
}
|
|
371
|
+
const random = runif(0, accumulation);
|
|
372
|
+
return startWeights.findIndex(weight => random < weight);
|
|
373
|
+
}
|
|
374
|
+
/**
|
|
375
|
+
* Take the limit of a function. A return value of `NaN` indicates
|
|
376
|
+
* that no limit exists either due to a discontinuity or imaginary value.
|
|
377
|
+
* @param f Function `f(x)`
|
|
378
|
+
* @param x The x-value where to take the limit
|
|
379
|
+
* @param h The approach distance
|
|
380
|
+
* @param discontinuity_cutoff The discontinuity cutoff
|
|
381
|
+
* @returns `lim(f(x->x))`
|
|
382
|
+
* @example
|
|
383
|
+
* const y = SMath.lim(Math.log, 0); // -Infinity
|
|
384
|
+
*/
|
|
385
|
+
export function lim(f, x, h = 1e-6, discontinuity_cutoff = 1e-3) {
|
|
386
|
+
const center = f(x), left1 = f(x - h), left2 = f(x - h / 2), right1 = f(x + h), right2 = f(x + h / 2);
|
|
387
|
+
let left, right;
|
|
388
|
+
if (Number.isFinite(center)) {
|
|
389
|
+
return center;
|
|
390
|
+
}
|
|
391
|
+
// Check the limit approaching from the left
|
|
392
|
+
if (Number.isFinite(left1) && Number.isFinite(left2)) {
|
|
393
|
+
if (approx(left1, left2, discontinuity_cutoff)) {
|
|
394
|
+
left = left2; // Converges
|
|
395
|
+
}
|
|
396
|
+
else if (left1 > 0 && left2 > left1) {
|
|
397
|
+
left = Infinity; // Diverges to +inf
|
|
398
|
+
}
|
|
399
|
+
else if (left1 < 0 && left2 < left1) {
|
|
400
|
+
left = -Infinity; // Diverges to -inf
|
|
401
|
+
}
|
|
402
|
+
else {
|
|
403
|
+
left = NaN; // Diverges
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
else {
|
|
407
|
+
left = NaN; // Discontinuous
|
|
408
|
+
}
|
|
409
|
+
// Check the limit approaching from the right
|
|
410
|
+
if (Number.isFinite(right1) && Number.isFinite(right2)) {
|
|
411
|
+
if (approx(right1, right2, discontinuity_cutoff)) {
|
|
412
|
+
right = right2; // Converges
|
|
413
|
+
}
|
|
414
|
+
else if (right1 > 0 && right2 > right1) {
|
|
415
|
+
right = Infinity; // Diverges to +inf
|
|
416
|
+
}
|
|
417
|
+
else if (right1 < 0 && right2 < right1) {
|
|
418
|
+
right = -Infinity; // Diverges to -inf
|
|
419
|
+
}
|
|
420
|
+
else {
|
|
421
|
+
right = NaN; // Diverges
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
else {
|
|
425
|
+
right = NaN; // Discontinuous
|
|
426
|
+
}
|
|
427
|
+
// Check if limits match or are close
|
|
428
|
+
if (left === right) { // Handles +/-Infinity case
|
|
429
|
+
return left;
|
|
430
|
+
}
|
|
431
|
+
else if (Number.isNaN(left) && Number.isNaN(right)) {
|
|
432
|
+
return center;
|
|
433
|
+
}
|
|
434
|
+
else if (!Number.isNaN(left) && Number.isNaN(right)) {
|
|
435
|
+
return left;
|
|
436
|
+
}
|
|
437
|
+
else if (Number.isNaN(left) && !Number.isNaN(right)) {
|
|
438
|
+
return right;
|
|
439
|
+
}
|
|
440
|
+
else if (approx(left, right, discontinuity_cutoff)) {
|
|
441
|
+
return avg([left, right]);
|
|
442
|
+
}
|
|
443
|
+
else {
|
|
444
|
+
return NaN;
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
/**
|
|
448
|
+
* Take the derivative of a function.
|
|
449
|
+
* @param f Function `f(x)`
|
|
450
|
+
* @param x The x-value where to evaluate the derivative
|
|
451
|
+
* @param epsilon Small step value
|
|
452
|
+
* @returns `f'(x)`
|
|
453
|
+
* @example
|
|
454
|
+
* const y = SMath.differentiate(x => 3 * x ** 2, 2); // 12
|
|
455
|
+
*/
|
|
456
|
+
export function differentiate(f, x, epsilon = 1e-6) {
|
|
457
|
+
return lim(h => (f(x + h) - f(x - h)) / (2 * h), 0, epsilon);
|
|
458
|
+
}
|
|
459
|
+
/**
|
|
460
|
+
* Compute the definite integral of a function.
|
|
461
|
+
* @param f Function `f(x)`
|
|
462
|
+
* @param a The miminum integral bound
|
|
463
|
+
* @param b The maximum integral bound
|
|
464
|
+
* @param Ndx The number of rectangles to compute
|
|
465
|
+
* @returns `F(b)-F(a)`
|
|
466
|
+
* @example
|
|
467
|
+
* const y = SMath.integrate(x => 3 * x ** 2, 1, 2); // 7
|
|
468
|
+
*/
|
|
469
|
+
export function integrate(f, a, b, Ndx = 1e6) {
|
|
470
|
+
return ((b - a) / Ndx) * sum(linspace(a, b, Ndx).map(x => f(x)));
|
|
471
|
+
}
|
|
472
|
+
/**
|
|
473
|
+
* Convert an arbitrary decimal number into a simplified fraction (or ratio).
|
|
474
|
+
* See `mixed()` for instructions on how to break out the whole number part.
|
|
475
|
+
* @param n The decimal number to convert
|
|
476
|
+
* @param epsilon Maximum absolute error
|
|
477
|
+
* @returns An object containing the fraction's numerator and denominator
|
|
478
|
+
* @example
|
|
479
|
+
* const frac = SMath.rat(0.625); // { num: 5, den: 8 }
|
|
480
|
+
*/
|
|
481
|
+
export function rat(n, epsilon = 1e-6) {
|
|
482
|
+
let num = 0, den = 1;
|
|
483
|
+
const sign = n < 0 ? -1 : 1;
|
|
484
|
+
while (!approx(sign * n, num / den, epsilon)) {
|
|
485
|
+
if (sign * n > num / den) {
|
|
486
|
+
num++;
|
|
487
|
+
}
|
|
488
|
+
else {
|
|
489
|
+
den++;
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
return { num: sign * num, den: den };
|
|
493
|
+
}
|
|
494
|
+
/**
|
|
495
|
+
* Convert an arbitrary decimal number into a simplified fraction, after
|
|
496
|
+
* breaking out the whole number part first. See `rat()` for keeping the
|
|
497
|
+
* number as a ratio without separating the whole number part.
|
|
498
|
+
* @param n A decimal number to convert
|
|
499
|
+
* @param epsilon Maximum absolute error
|
|
500
|
+
* @returns An object containing the whole part and fraction numerator and denominator
|
|
501
|
+
* @example
|
|
502
|
+
* const frac = SMath.mixed(-8 / 6); // { whole: -1, num: 1, den: 3 }
|
|
503
|
+
*/
|
|
504
|
+
export function mixed(n, epsilon = 1e-6) {
|
|
505
|
+
return { whole: n | 0, ...rat(n < -1 ? (n | 0) - n : n - (n | 0), epsilon) };
|
|
506
|
+
}
|
package/package.json
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "smath",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"description": "Small math function library",
|
|
5
5
|
"homepage": "https://npm.nicfv.com/",
|
|
6
|
+
"type": "module",
|
|
6
7
|
"bin": "dist/bin.js",
|
|
7
8
|
"main": "dist/index.js",
|
|
8
9
|
"types": "types/index.d.ts",
|
|
@@ -38,7 +39,23 @@
|
|
|
38
39
|
"extrapolate",
|
|
39
40
|
"extrapolation",
|
|
40
41
|
"linspace",
|
|
41
|
-
"logspace"
|
|
42
|
+
"logspace",
|
|
43
|
+
"curve",
|
|
44
|
+
"fit",
|
|
45
|
+
"fitting",
|
|
46
|
+
"least",
|
|
47
|
+
"squares",
|
|
48
|
+
"regression",
|
|
49
|
+
"data",
|
|
50
|
+
"dataset",
|
|
51
|
+
"point",
|
|
52
|
+
"parameters",
|
|
53
|
+
"linear",
|
|
54
|
+
"nonlinear",
|
|
55
|
+
"variable",
|
|
56
|
+
"multivariable",
|
|
57
|
+
"multivariant",
|
|
58
|
+
"multivariate"
|
|
42
59
|
],
|
|
43
60
|
"author": {
|
|
44
61
|
"name": "Nicolas Ventura",
|
|
@@ -52,7 +69,7 @@
|
|
|
52
69
|
"repository": "github:nicfv/npm",
|
|
53
70
|
"license": "MIT",
|
|
54
71
|
"devDependencies": {
|
|
55
|
-
"@types/node": "25.0
|
|
56
|
-
"t6": "1.
|
|
72
|
+
"@types/node": "25.6.0",
|
|
73
|
+
"t6": "1.3.0"
|
|
57
74
|
}
|
|
58
75
|
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { Datum, F, Summary, VariableType } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Minimize the sum of squared errors to fit a set of data
|
|
4
|
+
* points to a curve with a set of unknown parameters.
|
|
5
|
+
* @param f The model function for curve fitting.
|
|
6
|
+
* @param data The entire dataset, as an array of points.
|
|
7
|
+
* @param params_initial The initial guess for function
|
|
8
|
+
* parameters, which defaults to an array filled with zeroes.
|
|
9
|
+
* @param iterations The number of parameter sets to generate.
|
|
10
|
+
* @param maxDeviation The relative standard parameter deviation.
|
|
11
|
+
* This is a number [0.0-1.0] and affects the standard deviation
|
|
12
|
+
* on the first iteration. Every subsequent iteration has a
|
|
13
|
+
* decayed standard deviation until the final iteration.
|
|
14
|
+
* @returns The set of parameters and error for the best fit.
|
|
15
|
+
* @example
|
|
16
|
+
* // Define model function
|
|
17
|
+
* function f(x: number, a2: number = -0.5, a1: number = 3.9, a0: number = -1.2): number {
|
|
18
|
+
* return a2 * x ** 2 + a1 * x + a0;
|
|
19
|
+
* }
|
|
20
|
+
* // Construct a data set
|
|
21
|
+
* const data: Datum<number>[] = [0, 2, 4].map(x => ({ x: x, y: f(x) }));
|
|
22
|
+
* // Compute best-fit summary
|
|
23
|
+
* const summary = fit(f, data);
|
|
24
|
+
*/
|
|
25
|
+
export declare function fit<T extends VariableType>(f: F<T>, data: Datum<T>[], params_initial?: number[], iterations?: number, maxDeviation?: number): Summary<T>;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Declares whether this is a single- or multi-variable problem.
|
|
3
|
+
*/
|
|
4
|
+
export type VariableType = number | number[];
|
|
5
|
+
/**
|
|
6
|
+
* Represents a mathematical function y = f(x) with unknown parameters.
|
|
7
|
+
* @example
|
|
8
|
+
* // Single variable function in Typescript, 2nd degree polynomial:
|
|
9
|
+
* function f(x: number, a2: number, a1: number, a0: number): number {
|
|
10
|
+
* return a2 * x ** 2 + a1 * x + a0;
|
|
11
|
+
* }
|
|
12
|
+
* // Multivariable function in Typescript, general plane equation:
|
|
13
|
+
* function f([x, y]: number[], cx: number, cy: number, cz: number): number {
|
|
14
|
+
* return cx * x + cy * y + cz;
|
|
15
|
+
* }
|
|
16
|
+
*/
|
|
17
|
+
export type F<T extends VariableType> = (x: T, ...params: number[]) => number;
|
|
18
|
+
/**
|
|
19
|
+
* Stores a data point. For multivariable points, the `x`
|
|
20
|
+
* coordinate contains an array of all the free variables.
|
|
21
|
+
*/
|
|
22
|
+
export interface Datum<T extends VariableType> {
|
|
23
|
+
/**
|
|
24
|
+
* **Input:** X variable(s)
|
|
25
|
+
*/
|
|
26
|
+
readonly x: T;
|
|
27
|
+
/**
|
|
28
|
+
* **Output:** Y variable
|
|
29
|
+
*/
|
|
30
|
+
readonly y: number;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Includes information about a best-fit for a curve.
|
|
34
|
+
*/
|
|
35
|
+
export interface Summary<T extends VariableType> {
|
|
36
|
+
/**
|
|
37
|
+
* The model with best-fit parameters applied.
|
|
38
|
+
*/
|
|
39
|
+
readonly f: (x: T) => number;
|
|
40
|
+
/**
|
|
41
|
+
* Contains the set of best-fit parameters for the function `f(x)`
|
|
42
|
+
*/
|
|
43
|
+
readonly params: number[];
|
|
44
|
+
/**
|
|
45
|
+
* This is the residual sum of squared errors.
|
|
46
|
+
*/
|
|
47
|
+
readonly error: number;
|
|
48
|
+
/**
|
|
49
|
+
* The average absolute error per data point, comparing the given
|
|
50
|
+
* dataset to the model output with the set of best-fit parameters.
|
|
51
|
+
*/
|
|
52
|
+
readonly errorAvgAbs: number;
|
|
53
|
+
}
|