@trebco/treb 32.8.1 → 32.9.3

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/dist/treb.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- /*! API v32.8. Copyright 2018-2025 trebco, llc. All rights reserved. LGPL: https://treb.app/license */
1
+ /*! API v32.9. Copyright 2018-2025 trebco, llc. All rights reserved. LGPL: https://treb.app/license */
2
2
  /*
3
3
  * This file is part of TREB.
4
4
  *
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@trebco/treb",
3
- "version": "32.8.1",
3
+ "version": "32.9.3",
4
4
  "license": "LGPL-3.0-or-later",
5
5
  "homepage": "https://treb.app",
6
6
  "repository": {
@@ -1014,65 +1014,141 @@ export class Calculator extends Graph {
1014
1014
 
1015
1015
  /**
1016
1016
  * FIXME: there are cases we are not handling
1017
+ *
1018
+ * update to return a reference so you can use it as part of a
1019
+ * range. we're not handling literal arrays atm.
1017
1020
  */
1018
1021
  Index: {
1022
+ return_type: 'reference',
1019
1023
  arguments: [
1020
- { name: 'range', boxed: true },
1024
+ { name: 'range', metadata: true, },
1021
1025
  { name: 'row', },
1022
1026
  { name: 'column', }
1023
1027
  ],
1028
+
1029
+ // volatile: true, // not sure this is necessary bc input is the range
1024
1030
  volatile: false,
1025
1031
 
1026
1032
  // FIXME: handle full row, full column calls
1027
- fn: (data: UnionValue, row?: number, column?: number) => {
1033
+ fn: (range: UnionValue, row: number|undefined, column: number|undefined) => {
1028
1034
 
1029
- // ensure array
1030
- if (data && data.type !== ValueType.array) {
1031
- data = {
1032
- type: ValueType.array,
1033
- value: [[data]],
1034
- };
1035
+ if (!range) {
1036
+ return ArgumentError();
1037
+ }
1038
+
1039
+ // this is illegal, although we could just default to zeros
1040
+
1041
+ if (row === undefined && column === undefined) {
1042
+ return ArgumentError();
1035
1043
  }
1036
1044
 
1037
- if (row && column) {
1045
+ row = row || 0;
1046
+ column = column || 0;
1038
1047
 
1039
- // simple case: 2 indexes
1048
+ let arr: { value: {address: UnitAddress }}[][] = [];
1049
+
1050
+ // NOTE: could be a single cell. in that case it won't be passed
1051
+ // as an array so we'll need a second branch (or convert it)
1052
+
1053
+ if (range.type === ValueType.array) {
1054
+
1055
+ // FIXME: validate these are addresses (shouldn't be necessary
1056
+ // if we're marking the argument as metadata? what about literals?)
1057
+
1058
+ // check rows and columns. we might need to return an array.
1059
+
1060
+ arr = range.value as unknown as { value: {address: UnitAddress }}[][];
1040
1061
 
1041
- const c = data.value[column - 1];
1042
- if (c) {
1043
- const cell = c[row - 1];
1044
- if (cell) {
1045
- return cell;
1046
- }
1047
- }
1048
1062
  }
1049
- else if (row) {
1063
+ else if (range.type === ValueType.object) {
1050
1064
 
1051
- // return an array
1065
+ const metadata = range.value as { type: string, address?: UnitAddress };
1052
1066
 
1053
- const value: UnionValue[][] = [];
1054
- for (const c of data.value) {
1055
- if (!c[row - 1]) {
1056
- return ArgumentError();
1057
- }
1058
- value.push([c[row-1]]);
1067
+ if (metadata.type === 'metadata' && metadata.address) {
1068
+ arr.push([range as { value: { address: UnitAddress }}]);
1059
1069
  }
1070
+
1071
+ }
1072
+
1073
+ const columns = arr.length;
1074
+ const rows = arr[0]?.length || 0;
1075
+
1076
+ if (rows <= 0 || columns <= 0 || row > rows || column > columns || row < 0 || column < 0) {
1077
+ return ArgumentError();
1078
+ }
1079
+
1080
+
1081
+ // return everything if arguments are (0, 0)
1082
+
1083
+ if (column === 0 && row === 0) {
1084
+
1085
+ // because of the way we're structured this we might be
1086
+ // returning a range of length 1; in that case we want
1087
+ // to return it as an address
1088
+
1089
+ const expression: ExpressionUnit = (columns === 1 && rows === 1) ? {
1090
+ ...arr[0][0].value.address,
1091
+ } : {
1092
+ type: 'range',
1093
+ start: arr[0][0].value.address,
1094
+ end: arr[columns-1][rows-1].value.address,
1095
+ label: '', position: 0,
1096
+ id: 0,
1097
+ };
1098
+
1060
1099
  return {
1061
- type: ValueType.array,
1062
- value,
1100
+ type: ValueType.object,
1101
+ value: expression,
1102
+ };
1103
+
1104
+ }
1105
+
1106
+ // single cell
1107
+
1108
+ if ((row || rows === 1) && (column || columns === 1)) {
1109
+ return {
1110
+ type: ValueType.object,
1111
+ value: arr[column ? column - 1 : 0][row ? row - 1 : 0].value.address,
1063
1112
  };
1064
1113
  }
1114
+
1115
+ // sub array
1116
+
1117
+ if (row) {
1118
+
1119
+ // return column
1120
+
1121
+ const expression: ExpressionUnit = {
1122
+ type: 'range',
1123
+ start: arr[0][row - 1].value.address,
1124
+ end: arr[columns-1][row-1].value.address,
1125
+ label: '', position: 0,
1126
+ id: 0,
1127
+ };
1128
+
1129
+ return {
1130
+ type: ValueType.object,
1131
+ value: expression,
1132
+ };
1133
+
1134
+ }
1065
1135
  else if (column) {
1066
1136
 
1067
- // return an array
1137
+ // return row
1138
+
1139
+ const expression: ExpressionUnit = {
1140
+ type: 'range',
1141
+ start: arr[column - 1][0].value.address,
1142
+ end: arr[column-1][rows-1].value.address,
1143
+ label: '', position: 0,
1144
+ id: 0,
1145
+ };
1146
+
1147
+ return {
1148
+ type: ValueType.object,
1149
+ value: expression,
1150
+ };
1068
1151
 
1069
- const c = data.value[column - 1];
1070
- if (c) {
1071
- return {
1072
- type: ValueType.array,
1073
- value: [c],
1074
- };
1075
- }
1076
1152
  }
1077
1153
 
1078
1154
  return ArgumentError();
@@ -32,7 +32,7 @@ const Log1p = (x: number) => {
32
32
  /**
33
33
  * continued fraction expansion
34
34
  */
35
- const BetaContFrac = (a: number, b: number, x: number, epsabs: number) => {
35
+ export const BetaContFrac = (a: number, b: number, x: number, epsabs: number) => {
36
36
 
37
37
  const cutoff = 2.0 * Number.MIN_VALUE;
38
38
 
@@ -242,3 +242,17 @@ export const BetaCDF = (x: number, a: number, b: number) => {
242
242
 
243
243
  };
244
244
 
245
+ export const BetaInc = (x: number, a: number, b: number): number => {
246
+ if (x < 0 || x > 1) return 0; // throw new Error("Invalid x in betainc");
247
+ const bt =
248
+ x === 0 || x === 1
249
+ ? 0
250
+ : Math.exp(LnGamma(a + b) - LnGamma(a) - LnGamma(b) + a * Math.log(x) + b * Math.log(1 - x));
251
+ if (x < (a + 1) / (a + b + 2)) {
252
+ return bt * BetaContFrac(a, b, x, 0) / a;
253
+ } else {
254
+ return 1 - bt * BetaContFrac(b, a, 1 - x, 0) / b;
255
+ }
256
+ };
257
+
258
+
@@ -0,0 +1,37 @@
1
+
2
+ /*
3
+ * This file is part of TREB.
4
+ *
5
+ * TREB is free software: you can redistribute it and/or modify it under the
6
+ * terms of the GNU General Public License as published by the Free Software
7
+ * Foundation, either version 3 of the License, or (at your option) any
8
+ * later version.
9
+ *
10
+ * TREB is distributed in the hope that it will be useful, but WITHOUT ANY
11
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
12
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
13
+ * details.
14
+ *
15
+ * You should have received a copy of the GNU General Public License along
16
+ * with TREB. If not, see <https://www.gnu.org/licenses/>.
17
+ *
18
+ * Copyright 2022-2025 trebco, llc.
19
+ * info@treb.app
20
+ *
21
+ */
22
+
23
+ /** imprecise but reasonably fast normsinv function */
24
+ export const InverseNormal = (q: number): number => {
25
+
26
+ if (q === 0.50) {
27
+ return 0;
28
+ }
29
+
30
+ const p = (q < 1.0 && q > 0.5) ? (1 - q) : q;
31
+ const t = Math.sqrt(Math.log(1.0 / Math.pow(p, 2.0)));
32
+ const x = t - (2.515517 + 0.802853 * t + 0.010328 * Math.pow(t, 2.0)) /
33
+ (1.0 + 1.432788 * t + 0.189269 * Math.pow(t, 2.0) + 0.001308 * Math.pow(t, 3.0));
34
+
35
+ return (q > 0.5 ? x : -x);
36
+
37
+ };
@@ -27,6 +27,8 @@ import * as ComplexMath from '../complex-math';
27
27
 
28
28
  import { BetaCDF, BetaPDF, InverseBeta, LnGamma } from './beta';
29
29
  import { gamma_p } from './gamma';
30
+ import { InverseNormal } from './normal';
31
+ import { tCDF, tInverse, tPDF } from './students-t';
30
32
 
31
33
  /** error function (for gaussian distribution) */
32
34
  const erf = (x: number): number => {
@@ -62,21 +64,7 @@ const norm_dist = (x: number, mean: number, stdev: number, cumulative: boolean)
62
64
 
63
65
  }
64
66
 
65
- /** imprecise but reasonably fast normsinv function */
66
- const inverse_normal = (q: number): number => {
67
67
 
68
- if (q === 0.50) {
69
- return 0;
70
- }
71
-
72
- const p = (q < 1.0 && q > 0.5) ? (1 - q) : q;
73
- const t = Math.sqrt(Math.log(1.0 / Math.pow(p, 2.0)));
74
- const x = t - (2.515517 + 0.802853 * t + 0.010328 * Math.pow(t, 2.0)) /
75
- (1.0 + 1.432788 * t + 0.189269 * Math.pow(t, 2.0) + 0.001308 * Math.pow(t, 3.0));
76
-
77
- return (q > 0.5 ? x : -x);
78
-
79
- };
80
68
 
81
69
  const Median = (data: number[]) => {
82
70
  const n = data.length;
@@ -478,7 +466,7 @@ export const StatisticsFunctionLibrary: FunctionMap = {
478
466
  fn: (q: number, mean = 0, stdev = 1): UnionValue => {
479
467
  return {
480
468
  type: ValueType.number,
481
- value: inverse_normal(q) * stdev + mean,
469
+ value: InverseNormal(q) * stdev + mean,
482
470
  }
483
471
  }
484
472
  },
@@ -492,7 +480,7 @@ export const StatisticsFunctionLibrary: FunctionMap = {
492
480
  fn: (q: number): UnionValue => {
493
481
  return {
494
482
  type: ValueType.number,
495
- value: inverse_normal(q),
483
+ value: InverseNormal(q),
496
484
  }
497
485
  }
498
486
  },
@@ -831,6 +819,71 @@ export const StatisticsFunctionLibrary: FunctionMap = {
831
819
  }
832
820
  },
833
821
 
822
+ 'T.DIST': {
823
+ description: `Returns the left-tailed Student's t-distribution`,
824
+ arguments: [
825
+ { name: 'X', unroll: true, boxed: true, },
826
+ { name: 'degrees of freedom', unroll: true, boxed: true, },
827
+ { name: 'cumulative', unroll: true, boxed: true, },
828
+ ],
829
+ fn: (x: UnionValue, df: UnionValue, cumulative?: UnionValue) => {
830
+
831
+ const cum = cumulative ? !!cumulative.value : false;
832
+
833
+ if (df.type !== ValueType.number || x.type !== ValueType.number || df.value < 1) {
834
+ return ArgumentError();
835
+ }
836
+
837
+ return {
838
+ type: ValueType.number,
839
+ value: cum ? tCDF(x.value, df.value) : tPDF(x.value, df.value),
840
+ }
841
+
842
+ },
843
+ },
844
+
845
+ 'T.Inv': {
846
+ description: `Returns the left-tailed inverse of the Student's t-distribution`,
847
+ arguments: [
848
+ {
849
+ name: 'Probability', boxed: true, unroll: true,
850
+ },
851
+ {
852
+ name: 'Degrees of freedom', boxed: true, unroll: true,
853
+ },
854
+ ],
855
+ fn: (p: UnionValue, df: UnionValue) => {
856
+ if (df.type !== ValueType.number || df.value < 1 || p.type !== ValueType.number || p.value <= 0 || p.value >= 1) {
857
+ return ArgumentError();
858
+ }
859
+ return {
860
+ type: ValueType.number,
861
+ value: tInverse(p.value, df.value),
862
+ };
863
+ },
864
+ },
865
+
866
+ 'T.Inv.2T': {
867
+ description: `Returns the two-tailed inverse of the Student's t-distribution`,
868
+ arguments: [
869
+ {
870
+ name: 'Probability', boxed: true, unroll: true,
871
+ },
872
+ {
873
+ name: 'Degrees of freedom', boxed: true, unroll: true,
874
+ },
875
+ ],
876
+ fn: (p: UnionValue, df: UnionValue) => {
877
+ if (df.type !== ValueType.number || df.value < 1 ||p.type !== ValueType.number || p.value <= 0 || p.value >= 1) {
878
+ return ArgumentError();
879
+ }
880
+ return {
881
+ type: ValueType.number,
882
+ value: Math.abs(tInverse(1 - p.value/2, df.value)),
883
+ };
884
+ },
885
+ },
886
+
834
887
  GammaLn: {
835
888
  description: 'Returns the natural log of the gamma function',
836
889
  arguments: [{ name: 'value', boxed: true, unroll: true }],
@@ -0,0 +1,85 @@
1
+ /*
2
+ * This file is part of TREB.
3
+ *
4
+ * TREB is free software: you can redistribute it and/or modify it under the
5
+ * terms of the GNU General Public License as published by the Free Software
6
+ * Foundation, either version 3 of the License, or (at your option) any
7
+ * later version.
8
+ *
9
+ * TREB is distributed in the hope that it will be useful, but WITHOUT ANY
10
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
11
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
12
+ * details.
13
+ *
14
+ * You should have received a copy of the GNU General Public License along
15
+ * with TREB. If not, see <https://www.gnu.org/licenses/>.
16
+ *
17
+ * Copyright 2022-2025 trebco, llc.
18
+ * info@treb.app
19
+ *
20
+ */
21
+
22
+ import { BetaInc, LnGamma } from './beta';
23
+ import { InverseNormal } from './normal';
24
+
25
+ export const tCDF = (t: number, df: number) => {
26
+ const x = df / (df + t * t);
27
+ const a = df / 2;
28
+ const b = 0.5;
29
+ const ib = BetaInc(x, a, b);
30
+ return t >= 0 ? 1 - 0.5 * ib : 0.5 * ib;
31
+ };
32
+
33
+ export const tPDF = (t: number, nu: number): number => {
34
+
35
+ const logNumerator = LnGamma((nu + 1) / 2);
36
+ const logDenominator = LnGamma(nu / 2) + 0.5 * Math.log(nu * Math.PI);
37
+ const logFactor = -((nu + 1) / 2) * Math.log(1 + (t * t) / nu);
38
+
39
+ const logPDF = logNumerator - logDenominator + logFactor;
40
+
41
+ return Math.exp(logPDF);
42
+
43
+ }
44
+
45
+ export const tInverse = (p: number, df: number) => {
46
+
47
+ const EPS = 1e-12;
48
+
49
+ let x = 1;
50
+
51
+ // Hill's approximation for initial guess
52
+
53
+ {
54
+ const t = InverseNormal(p);
55
+ const g1 = (Math.pow(t, 3) + t) / 4;
56
+ const g2 = ((5 * Math.pow(t, 5)) + (16 * Math.pow(t, 3)) + (3 * t)) / 96;
57
+ const g3 = ((3 * Math.pow(t, 7)) + (19 * Math.pow(t, 5)) + (17 * Math.pow(t, 3)) - 15 * t) / 384;
58
+ const g4 = ((79 * Math.pow(t, 9)) + (776 * Math.pow(t, 7)) + (1482 * Math.pow(t, 5)) - (1920 * Math.pow(t, 3)) - (945 * t)) / 92160;
59
+
60
+ x = t + g1 / df + g2 / Math.pow(df, 2) + g3 / Math.pow(df, 3) + g4 / Math.pow(df, 4);
61
+ }
62
+
63
+ for (let i = 0; i < 16; i++) {
64
+
65
+ const error = tCDF(x, df) - p;
66
+ if (Math.abs(error) < EPS) {
67
+ break;
68
+ }
69
+
70
+ const derivative = tPDF(x, df);
71
+ if (derivative === 0) {
72
+ break;
73
+ }
74
+
75
+ const delta = error / derivative;
76
+ const step = Math.max(-1, Math.min(1, delta));
77
+
78
+ x -= step;
79
+
80
+ }
81
+
82
+ return x;
83
+
84
+ };
85
+
@@ -630,13 +630,23 @@ export class Parser {
630
630
  }).join(', ')).join('; ') + '}';
631
631
 
632
632
  case 'binary':
633
- return (
634
- this.Render(unit.left, options) +
635
- ' ' +
636
- unit.operator +
637
- ' ' +
638
- this.Render(unit.right, options)
639
- );
633
+
634
+ // in some cases we might see range constructs as binary units
635
+ // because one side (or maybe both sides) of the range is a
636
+ // function. in that case we don't want a space in front of the
637
+ // operator.
638
+
639
+ {
640
+ const separator = (unit.operator === ':' ? '': ' ');
641
+
642
+ return (
643
+ this.Render(unit.left, options) +
644
+ separator +
645
+ unit.operator +
646
+ separator +
647
+ this.Render(unit.right, options)
648
+ );
649
+ }
640
650
 
641
651
  case 'unary':
642
652
  return (