@trebco/treb 30.11.2 → 30.15.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.
@@ -22,9 +22,61 @@
22
22
  import type { FunctionMap } from '../descriptors';
23
23
  import * as Utils from '../utilities';
24
24
  import { ValueError, ArgumentError, NAError } from '../function-error';
25
- import { type Complex, type UnionValue, ValueType, type CellValue } from 'treb-base-types';
25
+ import { type Complex, type UnionValue, ValueType, type CellValue, ComplexOrReal } from 'treb-base-types';
26
26
  import * as ComplexMath from '../complex-math';
27
27
 
28
+ import { BetaCDF, BetaPDF, InverseBeta, LnGamma } from './beta';
29
+
30
+ /** error function (for gaussian distribution) */
31
+ const erf = (x: number): number => {
32
+
33
+ const a1 = 0.254829592;
34
+ const a2 = -0.284496736;
35
+ const a3 = 1.421413741;
36
+ const a4 = -1.453152027;
37
+ const a5 = 1.061405429;
38
+ const p = 0.3275911;
39
+
40
+ x = Math.abs(x);
41
+ const t = 1 / (1 + p * x);
42
+ return 1 - ((((((a5 * t + a4) * t) + a3) * t + a2) * t) + a1) * t * Math.exp(-1 * x * x);
43
+
44
+ };
45
+
46
+ const sqrt2pi = Math.sqrt(2 * Math.PI);
47
+
48
+ const norm_dist = (x: number, mean: number, stdev: number, cumulative: boolean) => {
49
+
50
+ let value = 0;
51
+
52
+ if (cumulative) {
53
+ const sign = (x < mean) ? -1 : 1;
54
+ value = 0.5 * (1.0 + sign * erf((Math.abs(x - mean)) / (stdev * Math.sqrt(2))));
55
+ }
56
+ else {
57
+ value = Math.exp(-1/2 * Math.pow((x - mean) / stdev, 2)) / (stdev * sqrt2pi);
58
+ }
59
+
60
+ return value;
61
+
62
+ }
63
+
64
+ /** imprecise but reasonably fast normsinv function */
65
+ const inverse_normal = (q: number): number => {
66
+
67
+ if (q === 0.50) {
68
+ return 0;
69
+ }
70
+
71
+ const p = (q < 1.0 && q > 0.5) ? (1 - q) : q;
72
+ const t = Math.sqrt(Math.log(1.0 / Math.pow(p, 2.0)));
73
+ const x = t - (2.515517 + 0.802853 * t + 0.010328 * Math.pow(t, 2.0)) /
74
+ (1.0 + 1.432788 * t + 0.189269 * Math.pow(t, 2.0) + 0.001308 * Math.pow(t, 3.0));
75
+
76
+ return (q > 0.5 ? x : -x);
77
+
78
+ };
79
+
28
80
  const Median = (data: number[]) => {
29
81
  const n = data.length;
30
82
  if (n % 2) {
@@ -68,10 +120,13 @@ const InterpolatedQuartiles = (data: number[], include_median = true) => {
68
120
 
69
121
  };
70
122
 
123
+ /**
124
+ * this is a Lanczos approximation. could be cleaned up.
125
+ * @param z
126
+ * @returns
127
+ */
71
128
  const Gamma = (z: Complex): Complex => {
72
129
 
73
- // this is a Lanczos approximation. could be cleaned up.
74
-
75
130
  const coefficients = [
76
131
  0.99999999999980993,
77
132
  676.5203681218851,
@@ -87,6 +142,9 @@ const Gamma = (z: Complex): Complex => {
87
142
  // generally speaking I'm against operator overloading but
88
143
  // it would be a big help for complex math
89
144
 
145
+ // how about a class based on the complex type? then you could
146
+ // use class methods on values. would be a little cleaner?
147
+
90
148
  const pi = Math.PI;
91
149
  const sin = ComplexMath.Sin;
92
150
  const div = ComplexMath.Divide;
@@ -144,6 +202,295 @@ export const Variance = (data: number[], sample = false) => {
144
202
 
145
203
  export const StatisticsFunctionLibrary: FunctionMap = {
146
204
 
205
+ Slope: {
206
+ arguments: [
207
+ { name: 'known_y' },
208
+ { name: 'known_x' },
209
+ ],
210
+ fn: (y: CellValue[][], x: CellValue[][]) => {
211
+
212
+ const flat_x = Utils.FlattenNumbers(x);
213
+ const flat_y = Utils.FlattenNumbers(y);
214
+
215
+ if (flat_x.length !== flat_y.length) {
216
+ return ArgumentError();
217
+ }
218
+
219
+ let sum_x = 0;
220
+ let sum_y = 0;
221
+ let sum_products = 0;
222
+ let sum_squared_x = 0;
223
+
224
+ for (let i = 0; i < flat_x.length; i++) {
225
+
226
+ const x = flat_x[i];
227
+ const y = flat_y[i];
228
+
229
+ sum_products += (x * y);
230
+ sum_x += x;
231
+ sum_y += y;
232
+
233
+ sum_squared_x += (x * x);
234
+ // sum_squared_y += (y * y);
235
+
236
+ }
237
+
238
+ const value =
239
+ ((flat_x.length * sum_products) - (sum_x * sum_y) ) /
240
+ ((flat_x.length * sum_squared_x) - (sum_x * sum_x));
241
+
242
+ return {
243
+ type: ValueType.number,
244
+ value,
245
+ }
246
+
247
+ },
248
+ },
249
+
250
+ Intercept: {
251
+ arguments: [
252
+ { name: 'known_y' },
253
+ { name: 'known_x' },
254
+ ],
255
+ fn: (y: CellValue[][], x: CellValue[][]) => {
256
+ const flat_x = Utils.FlattenNumbers(x);
257
+ const flat_y = Utils.FlattenNumbers(y);
258
+
259
+ if (flat_x.length !== flat_y.length) {
260
+ return ArgumentError();
261
+ }
262
+
263
+ const N = flat_x.length;
264
+
265
+ let sum_x = 0;
266
+ let sum_y = 0;
267
+ let sum_products = 0;
268
+ let sum_squared_x = 0;
269
+
270
+ for (let i = 0; i < N; i++) {
271
+
272
+ const x = flat_x[i];
273
+ const y = flat_y[i];
274
+
275
+ sum_products += (x * y);
276
+ sum_x += x;
277
+ sum_y += y;
278
+
279
+ sum_squared_x += (x * x);
280
+
281
+ }
282
+
283
+ let m = ((N * sum_products) - (sum_x * sum_y) );
284
+ m /= ((N * sum_squared_x) - (sum_x * sum_x));
285
+
286
+ return {
287
+ type: ValueType.number, value: (sum_y - (m * sum_x)) / N,
288
+ };
289
+
290
+ },
291
+ },
292
+
293
+ Phi: {
294
+ arguments: [
295
+ { name: 'x', boxed: true, unroll: true }
296
+ ],
297
+ fn: (x: UnionValue) => {
298
+
299
+ if (x.type === ValueType.number) {
300
+ return {
301
+ type: ValueType.number,
302
+ value: (1 / Math.sqrt(Math.PI * 2)) * Math.exp(-x.value * x.value / 2),
303
+ };
304
+ }
305
+
306
+ return ArgumentError();
307
+ }
308
+ },
309
+
310
+ 'Z.Test': {
311
+ arguments: [
312
+ { name: 'Array', boxed: true, },
313
+ { name: 'x', boxed: true, unroll: true, },
314
+ { name: 'Sigma', boxed: true, unroll: true, },
315
+ ],
316
+ fn: (array: UnionValue, x: UnionValue, sigma?: UnionValue) => {
317
+
318
+ const data: number[] = [];
319
+
320
+ if (array.type === ValueType.array) {
321
+ for (const row of array.value) {
322
+ for (const cell of row) {
323
+ if (cell.type === ValueType.number) {
324
+ data.push(cell.value);
325
+ }
326
+ }
327
+ }
328
+ }
329
+ else if (array.type === ValueType.number) {
330
+ data.push(array.value);
331
+ }
332
+
333
+ if (data.length && x.type === ValueType.number) {
334
+
335
+ let average = 0;
336
+ const n = data.length;
337
+ for (const value of data) { average += value; }
338
+ average /= n;
339
+
340
+ const s = sigma?.type === ValueType.number ? sigma.value :
341
+ Math.sqrt(Variance(data, true));
342
+
343
+ return {
344
+ type: ValueType.number,
345
+ value: 1 - norm_dist((average - x.value) / (s / Math.sqrt(n)), 0, 1, true),
346
+ };
347
+
348
+ }
349
+
350
+ return ArgumentError();
351
+
352
+ },
353
+ },
354
+
355
+ 'Beta.Dist': {
356
+ description: 'beta distribution',
357
+ arguments: [
358
+ { name: 'x', unroll: true},
359
+ { name: 'a', },
360
+ { name: 'b', },
361
+ { name: 'cumulative', },
362
+ ],
363
+ fn: (x: number, a: number, b: number, cumulative: boolean) => {
364
+
365
+ if (a < 0 || b < 0) {
366
+ return ArgumentError();
367
+ }
368
+
369
+ if (cumulative) {
370
+ return {
371
+ type: ValueType.number,
372
+ value: BetaCDF(x, a, b),
373
+ };
374
+ }
375
+ else {
376
+ return {
377
+ type: ValueType.number,
378
+ value: BetaPDF(x, a, b),
379
+ };
380
+ }
381
+
382
+ return ArgumentError();
383
+ }
384
+ },
385
+
386
+ 'Beta.Inv': {
387
+ description: 'Inverse of the beta distribution',
388
+ arguments: [
389
+ {name: 'probability', unroll: true},
390
+ {name: 'a', },
391
+ {name: 'b', },
392
+ ],
393
+ fn: (x: number, a: number, b: number) => {
394
+
395
+ if (a < 0 || b < 0) {
396
+ return ArgumentError();
397
+ }
398
+
399
+ return {
400
+ type: ValueType.number,
401
+ value: InverseBeta(x, a, b),
402
+ }
403
+ }
404
+ },
405
+
406
+ Erf: {
407
+ fn: (a: number): UnionValue => {
408
+ return { type: ValueType.number, value: erf(a) };
409
+ },
410
+ },
411
+
412
+ 'NormsInv': {
413
+
414
+ description: 'Inverse of the normal cumulative distribution',
415
+ arguments: [
416
+ {name: 'probability'},
417
+ ],
418
+
419
+ fn: (q: number): UnionValue => {
420
+ return {
421
+ type: ValueType.number,
422
+ value: inverse_normal(q),
423
+ }
424
+ }
425
+ },
426
+
427
+ 'Norm.Inv': {
428
+ description: 'Inverse of the normal cumulative distribution',
429
+ arguments: [
430
+ {name: 'probability'},
431
+ {name: 'mean', default: 0},
432
+ {name: 'standard deviation', default: 1},
433
+ ],
434
+ xlfn: true,
435
+ fn: (q: number, mean = 0, stdev = 1): UnionValue => {
436
+ return {
437
+ type: ValueType.number,
438
+ value: inverse_normal(q) * stdev + mean,
439
+ }
440
+ }
441
+ },
442
+
443
+ 'Norm.S.Inv': {
444
+ description: 'Inverse of the normal cumulative distribution',
445
+ arguments: [
446
+ {name: 'probability'},
447
+ {name: 'mean', default: 0},
448
+ {name: 'standard deviation', default: 1},
449
+ ],
450
+ xlfn: true,
451
+ fn: (q: number, mean = 0, stdev = 1): UnionValue => {
452
+ return {
453
+ type: ValueType.number,
454
+ value: inverse_normal(q) * stdev + mean,
455
+ }
456
+ }
457
+ },
458
+
459
+ 'Norm.Dist': {
460
+
461
+ description: 'Cumulative normal distribution',
462
+ arguments: [
463
+ {name: 'value'},
464
+ {name: 'mean', default: 0},
465
+ {name: 'standard deviation', default: 1},
466
+ {name: 'cumulative', default: true},
467
+ ],
468
+
469
+ // this does need xlfn but it also requires four parameters
470
+ // (we have three and they are not required).
471
+
472
+ xlfn: true,
473
+
474
+ fn: (x: number, mean = 0, stdev = 1, cumulative = true): UnionValue => {
475
+ return { type: ValueType.number, value: norm_dist(x, mean, stdev, cumulative) };
476
+ },
477
+ },
478
+
479
+ 'Norm.S.Dist': {
480
+
481
+ description: 'Cumulative normal distribution',
482
+ arguments: [
483
+ {name: 'value'},
484
+ {name: 'cumulative', default: true},
485
+ ],
486
+
487
+ xlfn: true,
488
+
489
+ fn: (x: number, cumulative = true): UnionValue => {
490
+ return { type: ValueType.number, value: norm_dist(x, 0, 1, cumulative) };
491
+ },
492
+ },
493
+
147
494
  'StDev.P': {
148
495
  description: 'Returns the standard deviation of a set of values, corresponding to a population',
149
496
  arguments: [{ name: 'data', }],
@@ -396,9 +743,46 @@ export const StatisticsFunctionLibrary: FunctionMap = {
396
743
  },
397
744
  },
398
745
 
746
+ GammaLn: {
747
+ description: 'Returns the natural log of the gamma function',
748
+ arguments: [{ name: 'value', boxed: true, unroll: true }],
749
+ fn: (value: UnionValue) => {
750
+ if (value.type === ValueType.number) {
751
+ return {
752
+ type: ValueType.number,
753
+ value: LnGamma(value.value),
754
+ };
755
+ }
756
+ return ArgumentError();
757
+ },
758
+ },
759
+
760
+ 'GammaLn.Precise': {
761
+ description: 'Returns the natural log of the gamma function',
762
+ arguments: [{ name: 'value', boxed: true, unroll: true }],
763
+ fn: (value: UnionValue) => {
764
+
765
+ let cpx: Complex|undefined;
766
+
767
+ if (value.type === ValueType.number) {
768
+ cpx = { real: value.value, imaginary: 0 };
769
+ }
770
+ else if (value.type === ValueType.complex) {
771
+ cpx = value.value;
772
+ }
773
+
774
+ if (cpx) {
775
+ const gamma = Gamma(cpx);
776
+ return ComplexOrReal(ComplexMath.Log(gamma));
777
+ }
778
+
779
+ return ArgumentError();
780
+ },
781
+ },
782
+
399
783
  Gamma: {
400
784
  description: 'Returns the gamma function for the given value',
401
- arguments: [{ name: 'value', boxed: true }],
785
+ arguments: [{ name: 'value', boxed: true, unroll: true }],
402
786
  fn: (value: UnionValue) => {
403
787
 
404
788
  let complex: Complex = { real: 0, imaginary: 0 };
@@ -127,7 +127,7 @@ export const FlattenBoxed = (args: UnionValue[]): UnionValue[] => {
127
127
 
128
128
  let result: UnionValue[] = [];
129
129
  for (const arg of args) {
130
- if (arg.type === ValueType.array) {
130
+ if (arg?.type === ValueType.array) {
131
131
 
132
132
  // possibly recursive
133
133
  for (const row of arg.value) {
@@ -280,7 +280,9 @@ export const ApplyArrayX = <TFunc extends (...args: any[]) => UnionValue>(map: b
280
280
  let shape: unknown[][] = [];
281
281
 
282
282
  for (const [i, arg] of args.entries()) {
283
+
283
284
  if (arg && map[i]) {
285
+
284
286
  const arr = Array.isArray(arg) ? arg : IsArrayUnion(arg) ? arg.value : undefined;
285
287
  if (arr) {
286
288
  arrays[i] = arr;
@@ -298,8 +300,24 @@ export const ApplyArrayX = <TFunc extends (...args: any[]) => UnionValue>(map: b
298
300
  return {
299
301
  type: ValueType.array,
300
302
  value: shape.map((_, i) => _.map((_, j) => {
301
- const apply = args.map((arg, index) => arrays[index] ? (arrays[index][i][j] || { type: ValueType.undefined }) : arg);
303
+
304
+ // this was breaking on boolean false, because
305
+ // it used || instead of ??. if we don't require
306
+ // boxed arguments, we need to handle naked booleans
307
+
308
+ // actually, this is still slightly wrong. we know (actually the
309
+ // caller of this function knows) if each argument is boxed or not,
310
+ // and if it's not, we should not return the boxed undefined type,
311
+ // we should just return undefined.
312
+
313
+ // that actually would basically solve false booleans as well, even
314
+ // if it were incorrectly returning undefined. the issue is we were
315
+ // returning a boxed undefined, which evaluates to true.
316
+
317
+ const apply = args.map((arg, index) => arrays[index] ? (arrays[index][i][j] ?? { type: ValueType.undefined }) : arg);
318
+
302
319
  return base(...apply as Parameters<TFunc>);
320
+
303
321
  })),
304
322
  };
305
323
 
@@ -2611,6 +2611,19 @@ export class EmbeddedSpreadsheet<USER_DATA_TYPE = unknown> {
2611
2611
  this.grid.ShowSheet(index, !hide);
2612
2612
  }
2613
2613
 
2614
+ /** list sheets in the model */
2615
+ public ListSheets() {
2616
+ return this.model.sheets.list.map(sheet => {
2617
+ const entry: { name: string, hidden?: boolean } = {
2618
+ name: sheet.name,
2619
+ };
2620
+ if (!sheet.visible) {
2621
+ entry.hidden = true;
2622
+ }
2623
+ return entry;
2624
+ });
2625
+ }
2626
+
2614
2627
  /**
2615
2628
  * Show or hide sheet. This method is deprecated because it's ambiguous.
2616
2629
  * To set a sheet's visibility, use `HideSheet`. To activate a sheet, use
@@ -7524,7 +7524,7 @@ export class Grid extends GridBase {
7524
7524
  if (typeof column === 'number') column = [column];
7525
7525
 
7526
7526
  const auto = typeof command.width !== 'number';
7527
- const width = Math.round(command.width || 0 / this.scale)
7527
+ const width = Math.round((command.width || 0) / this.scale);
7528
7528
 
7529
7529
  if (auto) {
7530
7530
  for (const entry of column) {