peeksafe 0.0.0-stage → 0.2.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.
Files changed (66) hide show
  1. package/CHANGELOG.md +325 -0
  2. package/LICENSE +190 -0
  3. package/NOTICE +78 -0
  4. package/README.md +490 -2
  5. package/RESEARCH-NOTES.md +274 -0
  6. package/dist/baseline.d.ts +45 -0
  7. package/dist/baseline.d.ts.map +1 -0
  8. package/dist/baseline.js +49 -0
  9. package/dist/baseline.js.map +1 -0
  10. package/dist/check.d.ts +64 -0
  11. package/dist/check.d.ts.map +1 -0
  12. package/dist/check.js +172 -0
  13. package/dist/check.js.map +1 -0
  14. package/dist/cluster.d.ts +328 -0
  15. package/dist/cluster.d.ts.map +1 -0
  16. package/dist/cluster.js +606 -0
  17. package/dist/cluster.js.map +1 -0
  18. package/dist/errors.d.ts +108 -0
  19. package/dist/errors.d.ts.map +1 -0
  20. package/dist/errors.js +192 -0
  21. package/dist/errors.js.map +1 -0
  22. package/dist/evidence.d.ts +15 -0
  23. package/dist/evidence.d.ts.map +1 -0
  24. package/dist/evidence.js +30 -0
  25. package/dist/evidence.js.map +1 -0
  26. package/dist/frontier.d.ts +271 -0
  27. package/dist/frontier.d.ts.map +1 -0
  28. package/dist/frontier.js +505 -0
  29. package/dist/frontier.js.map +1 -0
  30. package/dist/gate.d.ts +175 -0
  31. package/dist/gate.d.ts.map +1 -0
  32. package/dist/gate.js +200 -0
  33. package/dist/gate.js.map +1 -0
  34. package/dist/index.d.ts +60 -0
  35. package/dist/index.d.ts.map +1 -0
  36. package/dist/index.js +79 -0
  37. package/dist/index.js.map +1 -0
  38. package/dist/plan.d.ts +280 -0
  39. package/dist/plan.d.ts.map +1 -0
  40. package/dist/plan.js +626 -0
  41. package/dist/plan.js.map +1 -0
  42. package/dist/rand.d.ts +17 -0
  43. package/dist/rand.d.ts.map +1 -0
  44. package/dist/rand.js +104 -0
  45. package/dist/rand.js.map +1 -0
  46. package/dist/stats.d.ts +401 -0
  47. package/dist/stats.d.ts.map +1 -0
  48. package/dist/stats.js +1304 -0
  49. package/dist/stats.js.map +1 -0
  50. package/dist/stop.d.ts +164 -0
  51. package/dist/stop.d.ts.map +1 -0
  52. package/dist/stop.js +270 -0
  53. package/dist/stop.js.map +1 -0
  54. package/package.json +62 -3
  55. package/src/baseline.ts +72 -0
  56. package/src/check.ts +238 -0
  57. package/src/cluster.ts +842 -0
  58. package/src/errors.ts +275 -0
  59. package/src/evidence.ts +62 -0
  60. package/src/frontier.ts +837 -0
  61. package/src/gate.ts +363 -0
  62. package/src/index.ts +206 -0
  63. package/src/plan.ts +968 -0
  64. package/src/rand.ts +137 -0
  65. package/src/stats.ts +1663 -0
  66. package/src/stop.ts +464 -0
package/dist/gate.d.ts ADDED
@@ -0,0 +1,175 @@
1
+ /**
2
+ * gate.ts: the decision.
3
+ *
4
+ * You bring counts. This decides which cases regressed, at a false discovery
5
+ * rate you name, in a way that stays valid however many times you looked at the
6
+ * data on the way here.
7
+ *
8
+ * ## Why this is safe to peek at
9
+ *
10
+ * The usual construction is: run the suite, stop a case when it looks
11
+ * significant, compute a p-value from the counts, hand the p-values to
12
+ * Benjamini-Hochberg. That is invalid in two independent places.
13
+ *
14
+ * A p-value computed at a stopping boundary is not a p-value. `P(p_n <= a) <= a`
15
+ * holds for each fixed `n`; a procedure that adds runs and re-tests until the
16
+ * result looks significant reports `min` over the peeks, and that minimum is not
17
+ * bounded by `a`. Benjamini-Hochberg takes `P(p <= t) <= t` as its input
18
+ * assumption, so it inherits nothing and controls nothing.
19
+ *
20
+ * This module never computes a p-value. It computes an e-value, which is a
21
+ * non-negative statistic with mean at most 1 under the null. Ville's inequality
22
+ * bounds `P(sup_n E_n >= 1/a) <= a` over the whole trajectory, so an e-value is
23
+ * valid at any stopping time, including one chosen after looking. (The mean is
24
+ * exactly 1 averaged over the shared rate, not at every fixed rate; the README
25
+ * section "What that guarantee rests on" says where the bound is checked.) The
26
+ * multiplicity correction is e-BH (Wang and Ramdas), which takes e-values
27
+ * directly and controls FDR under arbitrary dependence between cases.
28
+ *
29
+ * The practical consequence: you may stop a case whenever you like, for any
30
+ * reason, including "it already looks bad". Nothing here cares when you stopped.
31
+ *
32
+ * ## What it will refuse to do
33
+ *
34
+ * Gate a case with no baseline. There is no null to test against, and the
35
+ * failure mode of inventing one is not a wrong number, it is a confident wrong
36
+ * number: a missing baseline read as `0/0` has a Beta(1,1) posterior median of
37
+ * 0.5, which gates the case against "it passes half the time" and reports a
38
+ * large improvement against nothing at all. Cases without a baseline come back
39
+ * in `newCases` and take no part in the verdict or the e-BH family.
40
+ *
41
+ * ## Paired cases
42
+ *
43
+ * A case may instead carry `paired` counts from re-running the baseline next to
44
+ * the candidate, one baseline run per candidate run. Its e-value is
45
+ * `pairedLogE` on the discordant pairs, which is valid at every rate by
46
+ * construction and has no ceiling, whichever `evidence` the unpaired cases use.
47
+ * The pairs need not share a seed: independent runs paired in order are still
48
+ * valid, and a shared seed only makes discordances more informative.
49
+ */
50
+ import type { BaselineStat } from "./baseline.js";
51
+ import { type Evidence } from "./evidence.js";
52
+ import { type PairedCounts } from "./stats.js";
53
+ /**
54
+ * True when the baseline's observed rate leaves no room for an `mde`-sized drop.
55
+ * `makePlan` calls the same cases IMPOSSIBLE, by the same rule.
56
+ */
57
+ export declare const cannotDropBy: (baseline: {
58
+ successes: number;
59
+ trials: number;
60
+ }, mde: number) => boolean;
61
+ /** `options` without its `undefined` entries, so a spread over the defaults leaves those defaults in place. */
62
+ export declare function definedOptions<T extends object>(options: T): T;
63
+ /** One case's candidate result, and the baseline it is measured against. */
64
+ export interface GateCase {
65
+ id: string;
66
+ /** Successes observed for the revision under test. */
67
+ successes: number;
68
+ /** Trials run for the revision under test. Stop whenever you like. */
69
+ trials: number;
70
+ /** The reference revision's recorded counts, or undefined if there are none. */
71
+ baseline?: BaselineStat;
72
+ paired?: undefined;
73
+ }
74
+ /** One case run as pairs: each candidate run next to a fresh baseline run. */
75
+ export interface PairedGateCase {
76
+ id: string;
77
+ /** The pair table so far. Stop whenever you like. */
78
+ paired: PairedCounts;
79
+ }
80
+ export interface GateOptions {
81
+ /** Minimum detectable effect: the drop, in rate, that counts as a regression. */
82
+ mde?: number;
83
+ /** Target false discovery rate across the suite. */
84
+ fdr?: number;
85
+ /**
86
+ * Concentration of the alternative prior. Lower spreads the alternative over
87
+ * "anything materially worse", which is closer to how a broken case behaves
88
+ * than a point mass exactly `mde` below the baseline. At most
89
+ * `MAX_ALT_CONCENTRATION` (16) with the default `bayes` statistic.
90
+ */
91
+ altConcentration?: number;
92
+ /**
93
+ * Which e-value decides. `bayes` has more power and error control checked
94
+ * numerically; `universal` is valid at every rate by construction and needs
95
+ * more runs. See the README section "What that guarantee rests on".
96
+ */
97
+ evidence?: Evidence;
98
+ }
99
+ export declare const DEFAULT_GATE_OPTIONS: {
100
+ readonly mde: 0.15;
101
+ readonly fdr: 0.05;
102
+ readonly altConcentration: 8;
103
+ readonly evidence: "bayes";
104
+ };
105
+ export interface CaseVerdict {
106
+ id: string;
107
+ /** True when e-BH certifies this case as regressed at the suite's FDR. */
108
+ regressed: boolean;
109
+ /** The e-value. Larger is more evidence against the null. */
110
+ evalue: number;
111
+ /** log of the above, which is what the arithmetic actually uses. */
112
+ logE: number;
113
+ /**
114
+ * The largest e-value this case could ever reach against its current
115
+ * baseline, however many candidate runs you add. When this is below
116
+ * `threshold`, the case is undetectable at any candidate budget and the only
117
+ * remedy is more baseline runs.
118
+ */
119
+ ceiling: number;
120
+ /** True when `ceiling` is below the bar the case has to clear. */
121
+ undetectable: boolean;
122
+ /**
123
+ * True when the baseline rate is at or below `mde`, so an `mde`-sized drop
124
+ * cannot happen. The case is not tested: its e-value is fixed at 1, which
125
+ * keeps it in the e-BH family without it ever being certified.
126
+ */
127
+ impossible: boolean;
128
+ /** For a paired case, each arm's passes over the number of pairs. */
129
+ observed: {
130
+ successes: number;
131
+ trials: number;
132
+ };
133
+ baseline: {
134
+ successes: number;
135
+ trials: number;
136
+ };
137
+ design: "unpaired" | "paired";
138
+ /** The pair table, for a paired case. */
139
+ paired?: PairedCounts;
140
+ }
141
+ export interface GateResult {
142
+ /** PASS when nothing was certified as regressed. */
143
+ verdict: "PASS" | "FAIL";
144
+ cases: CaseVerdict[];
145
+ /** Cases certified as regressed, in the order given. */
146
+ regressed: CaseVerdict[];
147
+ /**
148
+ * Cases with no baseline. These are reported, not gated: they have no null to
149
+ * be tested against and they are excluded from the e-BH family.
150
+ */
151
+ newCases: string[];
152
+ /** The e-value a case had to beat, given how many discoveries were made. */
153
+ threshold: number;
154
+ /** The e-value a case must reach to be certified whatever the others do. */
155
+ soloThreshold: number;
156
+ options: Required<GateOptions>;
157
+ /**
158
+ * One line an operator can read without knowing what an e-value is.
159
+ *
160
+ * Names the two things a passing suite can hide: cases with no baseline,
161
+ * which were not gated at all, and cases whose baseline is too thin to
162
+ * certify an `mde`-sized drop however many runs you add. A PASS that is
163
+ * really "nothing was checked" should not read like a PASS.
164
+ */
165
+ headline: string;
166
+ }
167
+ /**
168
+ * Decide which cases regressed.
169
+ *
170
+ * Valid at any stopping time. Run every case to a fixed count, or stop each one
171
+ * the moment it looks decided, or anything in between; the guarantee does not
172
+ * depend on the rule you used.
173
+ */
174
+ export declare function gate(cases: readonly (GateCase | PairedGateCase)[], options?: GateOptions): GateResult;
175
+ //# sourceMappingURL=gate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gate.d.ts","sourceRoot":"","sources":["../src/gate.ts"],"names":[],"mappings":"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"}
package/dist/gate.js ADDED
@@ -0,0 +1,200 @@
1
+ /**
2
+ * gate.ts: the decision.
3
+ *
4
+ * You bring counts. This decides which cases regressed, at a false discovery
5
+ * rate you name, in a way that stays valid however many times you looked at the
6
+ * data on the way here.
7
+ *
8
+ * ## Why this is safe to peek at
9
+ *
10
+ * The usual construction is: run the suite, stop a case when it looks
11
+ * significant, compute a p-value from the counts, hand the p-values to
12
+ * Benjamini-Hochberg. That is invalid in two independent places.
13
+ *
14
+ * A p-value computed at a stopping boundary is not a p-value. `P(p_n <= a) <= a`
15
+ * holds for each fixed `n`; a procedure that adds runs and re-tests until the
16
+ * result looks significant reports `min` over the peeks, and that minimum is not
17
+ * bounded by `a`. Benjamini-Hochberg takes `P(p <= t) <= t` as its input
18
+ * assumption, so it inherits nothing and controls nothing.
19
+ *
20
+ * This module never computes a p-value. It computes an e-value, which is a
21
+ * non-negative statistic with mean at most 1 under the null. Ville's inequality
22
+ * bounds `P(sup_n E_n >= 1/a) <= a` over the whole trajectory, so an e-value is
23
+ * valid at any stopping time, including one chosen after looking. (The mean is
24
+ * exactly 1 averaged over the shared rate, not at every fixed rate; the README
25
+ * section "What that guarantee rests on" says where the bound is checked.) The
26
+ * multiplicity correction is e-BH (Wang and Ramdas), which takes e-values
27
+ * directly and controls FDR under arbitrary dependence between cases.
28
+ *
29
+ * The practical consequence: you may stop a case whenever you like, for any
30
+ * reason, including "it already looks bad". Nothing here cares when you stopped.
31
+ *
32
+ * ## What it will refuse to do
33
+ *
34
+ * Gate a case with no baseline. There is no null to test against, and the
35
+ * failure mode of inventing one is not a wrong number, it is a confident wrong
36
+ * number: a missing baseline read as `0/0` has a Beta(1,1) posterior median of
37
+ * 0.5, which gates the case against "it passes half the time" and reports a
38
+ * large improvement against nothing at all. Cases without a baseline come back
39
+ * in `newCases` and take no part in the verdict or the e-BH family.
40
+ *
41
+ * ## Paired cases
42
+ *
43
+ * A case may instead carry `paired` counts from re-running the baseline next to
44
+ * the candidate, one baseline run per candidate run. Its e-value is
45
+ * `pairedLogE` on the discordant pairs, which is valid at every rate by
46
+ * construction and has no ceiling, whichever `evidence` the unpaired cases use.
47
+ * The pairs need not share a seed: independent runs paired in order are still
48
+ * valid, and a shared seed only makes discordances more informative.
49
+ */
50
+ import { PeeksafeError, requireAltConcentration, requireCounts, requireOpenProbability, requirePairedCounts, } from "./errors.js";
51
+ import { ceilingLogEvidence, logEvidence, requireEvidence, toEvalue, } from "./evidence.js";
52
+ import { discordant, ebhCorrect, ebhSoloThreshold, pairedLogE, } from "./stats.js";
53
+ /**
54
+ * True when the baseline's observed rate leaves no room for an `mde`-sized drop.
55
+ * `makePlan` calls the same cases IMPOSSIBLE, by the same rule.
56
+ */
57
+ export const cannotDropBy = (baseline, mde) => baseline.successes / baseline.trials <= mde;
58
+ /** `options` without its `undefined` entries, so a spread over the defaults leaves those defaults in place. */
59
+ export function definedOptions(options) {
60
+ return Object.fromEntries(Object.entries(options ?? {}).filter(([, v]) => v !== undefined));
61
+ }
62
+ export const DEFAULT_GATE_OPTIONS = {
63
+ mde: 0.15,
64
+ fdr: 0.05,
65
+ altConcentration: 8,
66
+ evidence: "bayes",
67
+ };
68
+ /**
69
+ * Decide which cases regressed.
70
+ *
71
+ * Valid at any stopping time. Run every case to a fixed count, or stop each one
72
+ * the moment it looks decided, or anything in between; the guarantee does not
73
+ * depend on the rule you used.
74
+ */
75
+ export function gate(cases, options = {}) {
76
+ const opts = { ...DEFAULT_GATE_OPTIONS, ...definedOptions(options) };
77
+ requireOpenProbability(opts.mde, "mde", "gate");
78
+ requireOpenProbability(opts.fdr, "fdr", "gate");
79
+ requireEvidence(opts.evidence, "gate");
80
+ requireAltConcentration(opts.altConcentration, opts.evidence, "gate");
81
+ const seen = new Set();
82
+ for (const c of cases) {
83
+ if (seen.has(c.id)) {
84
+ throw new PeeksafeError("PEEKSAFE_E_CASE_DUPLICATE", `gate: duplicate case id ${c.id}`, {
85
+ detail: { id: c.id },
86
+ hint: "two cases sharing an id would be merged into one baseline entry and counted once",
87
+ });
88
+ }
89
+ seen.add(c.id);
90
+ if (c.paired) {
91
+ requirePairedCounts(c.paired, `gate(${c.id}).paired`);
92
+ continue;
93
+ }
94
+ requireCounts(c.successes, c.trials, `gate(${c.id})`);
95
+ if (c.baseline)
96
+ requireCounts(c.baseline.successes, c.baseline.trials, `gate(${c.id}).baseline`);
97
+ }
98
+ const hasBaseline = (c) => c.paired !== undefined || (c.baseline !== undefined && c.baseline.trials > 0);
99
+ const newCases = cases.filter((c) => !hasBaseline(c)).map((c) => c.id);
100
+ const gated = cases.filter(hasBaseline);
101
+ if (gated.length === 0) {
102
+ throw new PeeksafeError("PEEKSAFE_E_BASELINE_MISSING", "gate: no case has a baseline, so there is nothing to test against", {
103
+ detail: { cases: cases.length, newCases: newCases.length },
104
+ hint: 'record a baseline on the reference revision first; gating without one compares against a Beta(1,1) prior, which reads as "this case passes half the time"',
105
+ });
106
+ }
107
+ const m = gated.length;
108
+ // Testing these anyway clamps the alternative onto a rate near zero, which is
109
+ // where the case already sits, and the false positive rate then exceeds `fdr`.
110
+ // A paired case has an exact null and never needs this.
111
+ const impossible = gated.map((c) => !c.paired && cannotDropBy(c.baseline, opts.mde));
112
+ const logEs = gated.map((c, i) => {
113
+ if (c.paired)
114
+ return pairedLogE(c.paired.worse, discordant(c.paired));
115
+ return impossible[i]
116
+ ? 0
117
+ : logEvidence(opts.evidence, c.successes, c.trials, c.baseline.successes, c.baseline.trials, opts.mde, opts.altConcentration);
118
+ });
119
+ const evalues = logEs.map(toEvalue);
120
+ const ebh = ebhCorrect(evalues, opts.fdr);
121
+ const solo = ebhSoloThreshold(m, opts.fdr);
122
+ const verdicts = gated.map((c, i) => {
123
+ const shared = { id: c.id, regressed: ebh.rejected[i], evalue: evalues[i], logE: logEs[i] };
124
+ if (c.paired) {
125
+ const p = c.paired;
126
+ const pairs = p.bothPass + p.worse + p.better + p.bothFail;
127
+ return {
128
+ ...shared,
129
+ ceiling: Infinity,
130
+ undetectable: false,
131
+ impossible: false,
132
+ observed: { successes: p.bothPass + p.better, trials: pairs },
133
+ baseline: { successes: p.bothPass + p.worse, trials: pairs },
134
+ design: "paired",
135
+ paired: { ...p },
136
+ };
137
+ }
138
+ // The ceiling is evaluated at the rate a regression would actually sit at,
139
+ // one MDE below the baseline's own rate. That is the alternative the gate is
140
+ // trying to detect, so it is the right place to ask "could this ever clear
141
+ // the bar". Matches how plan.ts prices the same case.
142
+ const bRate = c.baseline.successes / c.baseline.trials;
143
+ const pAlt = Math.max(1e-6, Math.min(1 - 1e-6, bRate - opts.mde));
144
+ const ceiling = impossible[i]
145
+ ? 1
146
+ : Math.exp(ceilingLogEvidence(opts.evidence, pAlt, c.baseline.successes, c.baseline.trials, opts.mde, opts.altConcentration));
147
+ return {
148
+ ...shared,
149
+ ceiling,
150
+ undetectable: !impossible[i] && ceiling < solo,
151
+ impossible: impossible[i],
152
+ observed: { successes: c.successes, trials: c.trials },
153
+ baseline: { successes: c.baseline.successes, trials: c.baseline.trials },
154
+ design: "unpaired",
155
+ };
156
+ });
157
+ const regressed = verdicts.filter((v) => v.regressed);
158
+ const blind = verdicts.filter((v) => v.undetectable && !v.regressed);
159
+ const untested = verdicts.filter((v) => v.impossible);
160
+ const caveats = [];
161
+ if (blind.length > 0) {
162
+ caveats.push(`${blind.length} of ${m} gated case(s) have a baseline too thin to certify a ` +
163
+ `${(opts.mde * 100).toFixed(0)}pt drop at any candidate budget (${blind
164
+ .slice(0, 3)
165
+ .map((v) => v.id)
166
+ .join(", ")}` +
167
+ `${blind.length > 3 ? ", …" : ""}), which needs more baseline runs, not more candidate runs`);
168
+ }
169
+ if (untested.length > 0) {
170
+ caveats.push(`${untested.length} case(s) pass at or below ${(opts.mde * 100).toFixed(0)}% and cannot drop by ` +
171
+ `${(opts.mde * 100).toFixed(0)}pts, so they were not tested (${untested
172
+ .slice(0, 3)
173
+ .map((v) => v.id)
174
+ .join(", ")}` +
175
+ `${untested.length > 3 ? ", …" : ""})`);
176
+ }
177
+ if (newCases.length > 0) {
178
+ caveats.push(`${newCases.length} case(s) have no baseline and were not gated ` +
179
+ `(${newCases.slice(0, 3).join(", ")}${newCases.length > 3 ? ", …" : ""})`);
180
+ }
181
+ const headline = (regressed.length === 0
182
+ ? `PASS: no case cleared the bar of ${solo.toFixed(0)} at ${(opts.fdr * 100).toFixed(0)}% FDR over ${m} gated case(s)`
183
+ : `FAIL: ${regressed.length} of ${m} gated case(s) certified as regressed at ${(opts.fdr * 100).toFixed(0)}% FDR ` +
184
+ `(${regressed
185
+ .slice(0, 3)
186
+ .map((v) => v.id)
187
+ .join(", ")}${regressed.length > 3 ? ", …" : ""})`) +
188
+ (caveats.length > 0 ? `. Read with care: ${caveats.join("; ")}.` : "");
189
+ return {
190
+ verdict: regressed.length === 0 ? "PASS" : "FAIL",
191
+ cases: verdicts,
192
+ regressed,
193
+ newCases,
194
+ threshold: ebh.threshold,
195
+ soloThreshold: solo,
196
+ options: opts,
197
+ headline,
198
+ };
199
+ }
200
+ //# sourceMappingURL=gate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gate.js","sourceRoot":"","sources":["../src/gate.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,60 @@
1
+ /**
2
+ * peeksafe: statistically valid gates for non-deterministic eval suites.
3
+ *
4
+ * The problem this exists for: you run an eval suite, watch results arrive, stop
5
+ * a case when it looks decided, then correct for multiplicity with
6
+ * Benjamini-Hochberg. That construction has no error control. A p-value computed
7
+ * at a stopping boundary is not a p-value, and BH assumes it is.
8
+ *
9
+ * The library is organised in four layers, and most callers only need the first.
10
+ *
11
+ * ## 1. The decision
12
+ *
13
+ * gate(cases, options) which cases regressed, valid at any stopping time
14
+ *
15
+ * You bring counts. It brings e-values and e-BH. Nothing here knows or cares how
16
+ * you ran the evals, and nothing here will gate a case that has no baseline.
17
+ *
18
+ * ## 2. The budget
19
+ *
20
+ * makePlan(...) how many runs each case needs, and which are impossible
21
+ * computeFrontier(...) what is certifiable inside a budget
22
+ * affordabilityGrid(...) the same question as a grid over two axes
23
+ *
24
+ * These answer "how many runs do I need" before you spend anything, including
25
+ * the case where the honest answer is that no candidate budget will do and only
26
+ * more baseline runs will.
27
+ *
28
+ * ## 3. The check
29
+ *
30
+ * typeOneError(...) the error rate the gate delivers at rates you name
31
+ * certifyProbability(...) the same computation with separate baseline and candidate rates
32
+ * pairedCertifyProbability(...) the power of a paired design
33
+ *
34
+ * These are exact, not simulated, so a caller can check the gate on their own
35
+ * rates before trusting a verdict.
36
+ *
37
+ * ## 4. The statistics
38
+ *
39
+ * Everything the first two layers are built from, exported so a caller can check
40
+ * the arithmetic, build a different gate, or use one piece on its own: special
41
+ * functions, intervals, the SPRT, e-values, e-BH, power, paired designs, and the
42
+ * cluster-robust suite summary.
43
+ *
44
+ * Cases in a suite are not independent draws. Several cases generated from one
45
+ * source file share a failure mode, so a change that breaks the file moves all
46
+ * of them together. `clusteredEffect` and the rest of `cluster` are what stop
47
+ * the suite-level interval from being too narrow for that reason.
48
+ */
49
+ export { type BaselineStat, baselineNullRate, type CaseRef, toBaselineMap, } from "./baseline.js";
50
+ export { type CertifyProbabilityOptions, certifyProbability, type PairedCertifyProbabilityOptions, pairedCertifyProbability, type TypeOneErrorOptions, typeOneError, } from "./check.js";
51
+ export { type ClusteredEffect, type ClusteredMean, type ClusterKeyDiagnostic, type ClusterObservation, caseFamily, clusteredEffect, clusterKey, clusterKeyDiagnostic, clusterRobustMean, compareEstimators, type FamilyEffect, type GroupingSummary, groupByCluster, iidMean, MIN_TRUSTWORTHY_CLUSTERS, type RandomEffectsMean, randomEffectsMean, } from "./cluster.js";
52
+ export { MAX_ALT_CONCENTRATION, PeeksafeError, type PeeksafeErrorCode, type PeeksafeErrorOptions, } from "./errors.js";
53
+ export type { Evidence } from "./evidence.js";
54
+ export { computeFrontier, costShares, DEFAULT_AXES, DEFAULT_FRONTIER, enumerateFrontier, evaluatePoint, type FrontierAxes, type FrontierBasis, type FrontierCell, type FrontierConfig, type FrontierCost, type FrontierDesign, type FrontierPoint, type FrontierResult, type FrontierScreen, fmtRate, typicalObservationsPerCase, } from "./frontier.js";
55
+ export { type CaseVerdict, DEFAULT_GATE_OPTIONS, type GateCase, type GateOptions, type GateResult, gate, type PairedGateCase, } from "./gate.js";
56
+ export { type AffordabilityCell, affordabilityGrid, DEFAULT_PLAN, type Detectability, expectedSequentialSamples, makePlan, type Plan, type PlanCase, type PlanConfig, type PlanTotals, planCase, } from "./plan.js";
57
+ export { hash32, makeRand, type Rand, streamFor } from "./rand.js";
58
+ export { type BetaPosterior, type BhResult, bernoulliEntropy, betaCredibleInterval, betaMassBetween, betaPosterior, betaQuantile, bhCorrect, cohensH, diffInterval, discordant, type EbhResult, ebhCorrect, ebhSoloThreshold, erf, evidenceCeilingAsymptotic, evidenceCeilingLogE, evidenceCeilingSlope, expectedLogE, expectedLogEExact, fisherExact2x2, gammaP, type Interval, ibeta, logBeta, logBetaPdf, logGamma, logMarginalBetaBinomial, mcnemarSamplesForEvidence, normalCdf, normalQuantile, type PairedCounts, pairedDiscordance, pairedLogE, pairPhi, probabilityMoved, type SprtDecision, type SprtOpts, type SprtResult, sampleSizeTwoProportion, samplesForEvidence, sprtDecision, sprtExpectedN, type TestResult, twoProportionZTest, twoSampleLogE, twoSamplePriors, universalCeilingLogE, universalTwoSampleLogE, wilsonInterval, } from "./stats.js";
59
+ export { DEFAULT_STOP_OPTIONS, type PairedStopDecision, type PairedStopOptions, type PairedStopReason, type StopDecision, type StopOptions, type StopReason, shouldStop, shouldStopPaired, } from "./stop.js";
60
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/dist/index.js ADDED
@@ -0,0 +1,79 @@
1
+ /**
2
+ * peeksafe: statistically valid gates for non-deterministic eval suites.
3
+ *
4
+ * The problem this exists for: you run an eval suite, watch results arrive, stop
5
+ * a case when it looks decided, then correct for multiplicity with
6
+ * Benjamini-Hochberg. That construction has no error control. A p-value computed
7
+ * at a stopping boundary is not a p-value, and BH assumes it is.
8
+ *
9
+ * The library is organised in four layers, and most callers only need the first.
10
+ *
11
+ * ## 1. The decision
12
+ *
13
+ * gate(cases, options) which cases regressed, valid at any stopping time
14
+ *
15
+ * You bring counts. It brings e-values and e-BH. Nothing here knows or cares how
16
+ * you ran the evals, and nothing here will gate a case that has no baseline.
17
+ *
18
+ * ## 2. The budget
19
+ *
20
+ * makePlan(...) how many runs each case needs, and which are impossible
21
+ * computeFrontier(...) what is certifiable inside a budget
22
+ * affordabilityGrid(...) the same question as a grid over two axes
23
+ *
24
+ * These answer "how many runs do I need" before you spend anything, including
25
+ * the case where the honest answer is that no candidate budget will do and only
26
+ * more baseline runs will.
27
+ *
28
+ * ## 3. The check
29
+ *
30
+ * typeOneError(...) the error rate the gate delivers at rates you name
31
+ * certifyProbability(...) the same computation with separate baseline and candidate rates
32
+ * pairedCertifyProbability(...) the power of a paired design
33
+ *
34
+ * These are exact, not simulated, so a caller can check the gate on their own
35
+ * rates before trusting a verdict.
36
+ *
37
+ * ## 4. The statistics
38
+ *
39
+ * Everything the first two layers are built from, exported so a caller can check
40
+ * the arithmetic, build a different gate, or use one piece on its own: special
41
+ * functions, intervals, the SPRT, e-values, e-BH, power, paired designs, and the
42
+ * cluster-robust suite summary.
43
+ *
44
+ * Cases in a suite are not independent draws. Several cases generated from one
45
+ * source file share a failure mode, so a change that breaks the file moves all
46
+ * of them together. `clusteredEffect` and the rest of `cluster` are what stop
47
+ * the suite-level interval from being too narrow for that reason.
48
+ */
49
+ export { baselineNullRate, toBaselineMap, } from "./baseline.js";
50
+ export { certifyProbability, pairedCertifyProbability, typeOneError, } from "./check.js";
51
+ export { caseFamily, clusteredEffect, clusterKey, clusterKeyDiagnostic, clusterRobustMean, compareEstimators, groupByCluster, iidMean, MIN_TRUSTWORTHY_CLUSTERS, randomEffectsMean, } from "./cluster.js";
52
+ /* ── errors and determinism ──────────────────────────────────────────────── */
53
+ export { MAX_ALT_CONCENTRATION, PeeksafeError, } from "./errors.js";
54
+ export { computeFrontier, costShares, DEFAULT_AXES, DEFAULT_FRONTIER, enumerateFrontier, evaluatePoint, fmtRate, typicalObservationsPerCase, } from "./frontier.js";
55
+ /* ── 1. the decision ─────────────────────────────────────────────────────── */
56
+ export { DEFAULT_GATE_OPTIONS, gate, } from "./gate.js";
57
+ /* ── 2. the budget ───────────────────────────────────────────────────────── */
58
+ export { affordabilityGrid, DEFAULT_PLAN, expectedSequentialSamples, makePlan, planCase, } from "./plan.js";
59
+ export { hash32, makeRand, streamFor } from "./rand.js";
60
+ /* ── 3. the statistics ───────────────────────────────────────────────────── */
61
+ export { bernoulliEntropy, betaCredibleInterval, betaMassBetween, betaPosterior, betaQuantile,
62
+ /* multiplicity */
63
+ bhCorrect, cohensH, diffInterval, discordant, ebhCorrect, ebhSoloThreshold, erf, evidenceCeilingAsymptotic, evidenceCeilingLogE, evidenceCeilingSlope, expectedLogE, expectedLogEExact, fisherExact2x2, gammaP, ibeta, logBeta, logBetaPdf,
64
+ /* special functions */
65
+ logGamma,
66
+ /* e-values */
67
+ logMarginalBetaBinomial, mcnemarSamplesForEvidence, normalCdf, normalQuantile, pairedDiscordance,
68
+ /* paired designs */
69
+ pairedLogE, pairPhi, probabilityMoved,
70
+ /* power */
71
+ sampleSizeTwoProportion, samplesForEvidence,
72
+ /* sequential */
73
+ sprtDecision, sprtExpectedN,
74
+ /* fixed-sample tests, for comparison rather than for gating */
75
+ twoProportionZTest, twoSampleLogE, twoSamplePriors, universalCeilingLogE, universalTwoSampleLogE,
76
+ /* intervals and posteriors */
77
+ wilsonInterval, } from "./stats.js";
78
+ export { DEFAULT_STOP_OPTIONS, shouldStop, shouldStopPaired, } from "./stop.js";
79
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+CG;AAEH,OAAO,EAEL,gBAAgB,EAEhB,aAAa,GACd,MAAM,eAAe,CAAC;AACvB,OAAO,EAEL,kBAAkB,EAElB,wBAAwB,EAExB,YAAY,GACb,MAAM,YAAY,CAAC;AACpB,OAAO,EAKL,UAAU,EACV,eAAe,EACf,UAAU,EACV,oBAAoB,EACpB,iBAAiB,EACjB,iBAAiB,EAGjB,cAAc,EACd,OAAO,EACP,wBAAwB,EAExB,iBAAiB,GAClB,MAAM,cAAc,CAAC;AACtB,gFAAgF;AAChF,OAAO,EACL,qBAAqB,EACrB,aAAa,GAGd,MAAM,aAAa,CAAC;AAErB,OAAO,EACL,eAAe,EACf,UAAU,EACV,YAAY,EACZ,gBAAgB,EAChB,iBAAiB,EACjB,aAAa,EAUb,OAAO,EACP,0BAA0B,GAC3B,MAAM,eAAe,CAAC;AACvB,gFAAgF;AAChF,OAAO,EAEL,oBAAoB,EAIpB,IAAI,GAEL,MAAM,WAAW,CAAC;AACnB,gFAAgF;AAChF,OAAO,EAEL,iBAAiB,EACjB,YAAY,EAEZ,yBAAyB,EACzB,QAAQ,EAKR,QAAQ,GACT,MAAM,WAAW,CAAC;AACnB,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAa,SAAS,EAAE,MAAM,WAAW,CAAC;AACnE,gFAAgF;AAChF,OAAO,EAGL,gBAAgB,EAChB,oBAAoB,EACpB,eAAe,EACf,aAAa,EACb,YAAY;AACZ,kBAAkB;AAClB,SAAS,EACT,OAAO,EACP,YAAY,EACZ,UAAU,EAEV,UAAU,EACV,gBAAgB,EAChB,GAAG,EACH,yBAAyB,EACzB,mBAAmB,EACnB,oBAAoB,EACpB,YAAY,EACZ,iBAAiB,EACjB,cAAc,EACd,MAAM,EAEN,KAAK,EACL,OAAO,EACP,UAAU;AACV,uBAAuB;AACvB,QAAQ;AACR,cAAc;AACd,uBAAuB,EACvB,yBAAyB,EACzB,SAAS,EACT,cAAc,EAEd,iBAAiB;AACjB,oBAAoB;AACpB,UAAU,EACV,OAAO,EACP,gBAAgB;AAIhB,WAAW;AACX,uBAAuB,EACvB,kBAAkB;AAClB,gBAAgB;AAChB,YAAY,EACZ,aAAa;AAEb,+DAA+D;AAC/D,kBAAkB,EAClB,aAAa,EACb,eAAe,EACf,oBAAoB,EACpB,sBAAsB;AACtB,8BAA8B;AAC9B,cAAc,GACf,MAAM,YAAY,CAAC;AACpB,OAAO,EACL,oBAAoB,EAOpB,UAAU,EACV,gBAAgB,GACjB,MAAM,WAAW,CAAC"}