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.
- package/CHANGELOG.md +325 -0
- package/LICENSE +190 -0
- package/NOTICE +78 -0
- package/README.md +490 -2
- package/RESEARCH-NOTES.md +274 -0
- package/dist/baseline.d.ts +45 -0
- package/dist/baseline.d.ts.map +1 -0
- package/dist/baseline.js +49 -0
- package/dist/baseline.js.map +1 -0
- package/dist/check.d.ts +64 -0
- package/dist/check.d.ts.map +1 -0
- package/dist/check.js +172 -0
- package/dist/check.js.map +1 -0
- package/dist/cluster.d.ts +328 -0
- package/dist/cluster.d.ts.map +1 -0
- package/dist/cluster.js +606 -0
- package/dist/cluster.js.map +1 -0
- package/dist/errors.d.ts +108 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +192 -0
- package/dist/errors.js.map +1 -0
- package/dist/evidence.d.ts +15 -0
- package/dist/evidence.d.ts.map +1 -0
- package/dist/evidence.js +30 -0
- package/dist/evidence.js.map +1 -0
- package/dist/frontier.d.ts +271 -0
- package/dist/frontier.d.ts.map +1 -0
- package/dist/frontier.js +505 -0
- package/dist/frontier.js.map +1 -0
- package/dist/gate.d.ts +175 -0
- package/dist/gate.d.ts.map +1 -0
- package/dist/gate.js +200 -0
- package/dist/gate.js.map +1 -0
- package/dist/index.d.ts +60 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +79 -0
- package/dist/index.js.map +1 -0
- package/dist/plan.d.ts +280 -0
- package/dist/plan.d.ts.map +1 -0
- package/dist/plan.js +626 -0
- package/dist/plan.js.map +1 -0
- package/dist/rand.d.ts +17 -0
- package/dist/rand.d.ts.map +1 -0
- package/dist/rand.js +104 -0
- package/dist/rand.js.map +1 -0
- package/dist/stats.d.ts +401 -0
- package/dist/stats.d.ts.map +1 -0
- package/dist/stats.js +1304 -0
- package/dist/stats.js.map +1 -0
- package/dist/stop.d.ts +164 -0
- package/dist/stop.d.ts.map +1 -0
- package/dist/stop.js +270 -0
- package/dist/stop.js.map +1 -0
- package/package.json +62 -3
- package/src/baseline.ts +72 -0
- package/src/check.ts +238 -0
- package/src/cluster.ts +842 -0
- package/src/errors.ts +275 -0
- package/src/evidence.ts +62 -0
- package/src/frontier.ts +837 -0
- package/src/gate.ts +363 -0
- package/src/index.ts +206 -0
- package/src/plan.ts +968 -0
- package/src/rand.ts +137 -0
- package/src/stats.ts +1663 -0
- package/src/stop.ts +464 -0
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
# What is new here, and what is not
|
|
2
|
+
|
|
3
|
+
peeksafe started life with four claims about being novel. This document is the adversarial
|
|
4
|
+
literature check that was run against them, and the record of what it cost: **two of the four did not
|
|
5
|
+
survive, one is a corollary of a textbook result, and one was not found on a search weak enough that
|
|
6
|
+
its absence proves little.**
|
|
7
|
+
|
|
8
|
+
It is kept in the repository, rather than quietly deleted once the README was corrected, for two
|
|
9
|
+
reasons. A reader deciding whether to trust a statistics library should be able to see where its
|
|
10
|
+
author was wrong. And the searches are written down, so anyone who thinks a verdict is too harsh,
|
|
11
|
+
or too generous, can check the work instead of taking it on faith.
|
|
12
|
+
|
|
13
|
+
**The measurements were never in question and none of them changed.** What changed is the sentence
|
|
14
|
+
above them.
|
|
15
|
+
|
|
16
|
+
## The short version
|
|
17
|
+
|
|
18
|
+
| Claim | Where it lives | Verdict |
|
|
19
|
+
| --- | --- | --- |
|
|
20
|
+
| Sequential testing plus BH is invalid, twice over | `gate`, `bhCorrect`, `ebhCorrect` | **Known.** Taught as a named tutorial section since 2019 |
|
|
21
|
+
| A cheap proxy screen's accuracy does not transfer | the `proxy` screen in `computeFrontier` | **Known since 1978.** It is spectrum bias |
|
|
22
|
+
| The two-sample e-value has an evidence ceiling | `evidenceCeilingLogE`, `samplesForEvidence` | **Partially known.** A corollary of Laplace asymptotics; the closed form was not found |
|
|
23
|
+
| The certifiable-within-budget frontier | `computeFrontier`, `enumerateFrontier` | **Not found**, on three searches, which is weak evidence for an engineering artifact |
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## 1 · Sequential testing plus Benjamini-Hochberg
|
|
28
|
+
|
|
29
|
+
**Verdict: known, both halves, and the more prominent half is a titled section in a widely-taught
|
|
30
|
+
tutorial. This was the headline claim and it does not survive as stated.**
|
|
31
|
+
|
|
32
|
+
The construction peeksafe exists to replace is: run each case until it looks decided, take a p-value
|
|
33
|
+
at the stopping boundary, then correct across cases with Benjamini-Hochberg.
|
|
34
|
+
|
|
35
|
+
**Half one: peeking invalidates the p-value.** Known since Armitage, McPherson and Rowe (1969), and
|
|
36
|
+
thoroughly re-established in the industrial A/B testing literature, where it is called peeking and is
|
|
37
|
+
the motivating problem for always-valid inference. Ramdas's KDD 2019 tutorial states it directly:
|
|
38
|
+
"repeatedly running batch tests on accumulating data invalidates the p-value and inflates type-1
|
|
39
|
+
error".
|
|
40
|
+
|
|
41
|
+
**Half two: BH cannot consume those p-values.** This is not merely implied in that literature. It is
|
|
42
|
+
a titled section of the same tutorial: **"Why Benjamini-Hochberg cannot be used online (5 mins)"**.
|
|
43
|
+
The tutorial further warns that "ignoring the interplay with the outer sequential process could
|
|
44
|
+
unknowingly inflate the number of false discoveries", which was this library's headline conclusion,
|
|
45
|
+
published in 2019.
|
|
46
|
+
|
|
47
|
+
The entire e-BH line of work exists because of this. Wang and Ramdas (2022) introduced e-BH so FDR
|
|
48
|
+
control survives arbitrary dependence and sequential evidence, and the follow-up on stopped e-BH
|
|
49
|
+
studies exactly what happens when e-processes are stopped adaptively and multiplicity is corrected
|
|
50
|
+
afterwards. A reader who knows that literature reads the original framing as a restatement of its
|
|
51
|
+
premise.
|
|
52
|
+
|
|
53
|
+
**A third part, also known.** Testing against a historical rate treated as if it were known inflates
|
|
54
|
+
type I error, and the standard remedy is to carry the reference arm's uncertainty into the null
|
|
55
|
+
rather than plugging in a point estimate. That is standard in the external-control and historical-
|
|
56
|
+
borrowing literature, and `gate`'s use of the baseline posterior as the null is that standard remedy
|
|
57
|
+
correctly applied, not a discovery.
|
|
58
|
+
|
|
59
|
+
### What genuinely survives
|
|
60
|
+
|
|
61
|
+
One observation, and it is worth keeping:
|
|
62
|
+
|
|
63
|
+
> BH's own conservativeness at large `m` masks the per-case invalidity, so the naive construction
|
|
64
|
+
> produces visible false discoveries **more often at `m = 10` than at `m = 200`**, the opposite of
|
|
65
|
+
> where a practitioner would look for it, and `m = 10` is the regime cost economics push you toward.
|
|
66
|
+
|
|
67
|
+
That was not found stated anywhere. It is a quantitative nuance on a known defect rather than a new
|
|
68
|
+
defect, and it is genuinely counterintuitive and practically useful. The measurement itself (4.6×
|
|
69
|
+
per-case inflation on an eval suite, with the same underlying draws analysed all three ways) is also
|
|
70
|
+
a contribution, because measurements on a new domain have value even when the theory is old.
|
|
71
|
+
|
|
72
|
+
This is what the README's section "The part that is genuinely counterintuitive" now leads with, and
|
|
73
|
+
the rest is presented as implementing a known-correct construction rather than discovering a defect.
|
|
74
|
+
|
|
75
|
+
### Citations
|
|
76
|
+
|
|
77
|
+
- Ramdas, *Fundamentals of large-scale sequential experimentation*, KDD 2019 tutorial, including the
|
|
78
|
+
section "Why Benjamini-Hochberg cannot be used online": https://stat.cmu.edu/~aramdas/kdd19/
|
|
79
|
+
- Wang and Ramdas, *False discovery rate control with e-values*, 2022.
|
|
80
|
+
- Wang, Dandapanthula and Ramdas, *Anytime-valid FDR control with the stopped e-BH procedure*, 2025:
|
|
81
|
+
https://arxiv.org/abs/2502.08539
|
|
82
|
+
- Grünwald, de Heide and Koolen, *Safe Testing*: https://arxiv.org/abs/1906.07801
|
|
83
|
+
- Principled type I error rate inflation in two-arm designs with external control borrowing:
|
|
84
|
+
https://arxiv.org/html/2508.16348
|
|
85
|
+
- Armitage, McPherson and Rowe, *Repeated significance tests on accumulating data*, JRSS-A 1969.
|
|
86
|
+
|
|
87
|
+
### Searches run
|
|
88
|
+
|
|
89
|
+
Five. Optional stopping invalidating p-values with BH and FDR; anytime-valid FDR and stopped e-BH;
|
|
90
|
+
external control type I error inflation with a plug-in null; e-value safe testing with informative
|
|
91
|
+
nulls; and a direct fetch of the tutorial page, because a search result had already named the
|
|
92
|
+
section. The confirming step was the fetch, not a search.
|
|
93
|
+
|
|
94
|
+
---
|
|
95
|
+
|
|
96
|
+
## 2 · The proxy screen that does not transfer
|
|
97
|
+
|
|
98
|
+
**Verdict: known. It has a name and a 1978 citation.**
|
|
99
|
+
|
|
100
|
+
`computeFrontier` offers three screens (`none`, `expensive`, and `proxy`) and prices the `proxy`
|
|
101
|
+
one by attenuating its effect with Youden's J. The claim under check was that a cheap screen's
|
|
102
|
+
accuracy measured on a whole suite does not transfer to the narrow population that actually decides
|
|
103
|
+
anything.
|
|
104
|
+
|
|
105
|
+
The phenomenon is **spectrum bias**, first reported by Ransohoff and Feinstein in 1978. Sensitivity
|
|
106
|
+
and specificity, and therefore Youden's J, differ between subgroups with different severity or
|
|
107
|
+
features, so a test's measured accuracy on a broad population does not carry to a narrow one. The
|
|
108
|
+
modern literature refines this into a spectrum *effect* (accuracy differs by subgroup) becoming a
|
|
109
|
+
spectrum *bias* (the difference changes the likelihood ratios, and therefore the decisions).
|
|
110
|
+
|
|
111
|
+
A screen with J of 0.67 over a whole suite and J of 0.00 over the handful of genuinely regressed
|
|
112
|
+
cases is that, exactly. The consequence for this library is a modelling constraint rather than a
|
|
113
|
+
result: a J measured suite-wide is the wrong number to attenuate the frontier with, and
|
|
114
|
+
`computeFrontier` therefore takes screen recall as a caller-supplied parameter rather than inferring
|
|
115
|
+
one.
|
|
116
|
+
|
|
117
|
+
### What would add value, and is not in this package
|
|
118
|
+
|
|
119
|
+
Two things, and both are about demonstration rather than the phenomenon: a controlled design that
|
|
120
|
+
holds the expensive labels fixed and varies only the text of a failing answer, and the distinction
|
|
121
|
+
between a proxy that is *noisy* and one that is *structurally blind* to a failure mode, the latter
|
|
122
|
+
explaining why no number of runs helps. Neither of those experiments ships here. peeksafe models the
|
|
123
|
+
screen; it does not measure one for you.
|
|
124
|
+
|
|
125
|
+
### Citations
|
|
126
|
+
|
|
127
|
+
- Ransohoff and Feinstein, *Problems of spectrum and bias in evaluating the efficacy of diagnostic
|
|
128
|
+
tests*, NEJM 1978: https://pubmed.ncbi.nlm.nih.gov/692598/
|
|
129
|
+
- Spectrum bias, Catalog of Bias: https://catalogofbias.org/biases/spectrum-bias/
|
|
130
|
+
- *Distinguishing spectrum effects from spectrum biases*, BMC Medical Research Methodology 2008:
|
|
131
|
+
https://link.springer.com/article/10.1186/1471-2288-8-7
|
|
132
|
+
- *Spectrum bias: why clinicians need to be cautious when applying diagnostic test studies*, Family
|
|
133
|
+
Practice: https://academic.oup.com/fampra/article/25/5/390/443697
|
|
134
|
+
|
|
135
|
+
### Searches run
|
|
136
|
+
|
|
137
|
+
One, which was decisive. The phenomenon is named and the first search returned the originating
|
|
138
|
+
citation, so further searching would only have confirmed it.
|
|
139
|
+
|
|
140
|
+
---
|
|
141
|
+
|
|
142
|
+
## 3 · The evidence ceiling
|
|
143
|
+
|
|
144
|
+
**Verdict: partially known. The mathematics is a corollary of a textbook result. The closed form, the
|
|
145
|
+
naming, and the planning inversion were not found.**
|
|
146
|
+
|
|
147
|
+
`twoSampleLogE` has a finite limit as candidate runs go to infinity, and `evidenceCeilingLogE`
|
|
148
|
+
computes it. The claim under check was that this ceiling is a new observation.
|
|
149
|
+
|
|
150
|
+
### What is established
|
|
151
|
+
|
|
152
|
+
The asymptotic expansion behind the derivation is the standard Laplace approximation to a marginal
|
|
153
|
+
likelihood, stated in Kass and Raftery's review as
|
|
154
|
+
|
|
155
|
+
```
|
|
156
|
+
log m(D) = log P(D | θ̂, H) − (d/2) log n + O(1)
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
With two hypotheses that share a likelihood and a parameter dimension, the first two terms are
|
|
160
|
+
identical and cancel, leaving the `O(1)` term: the ratio of prior densities at the maximum. So a
|
|
161
|
+
bounded Bayes factor in this situation is not a surprise; it is what the standard expansion says must
|
|
162
|
+
happen.
|
|
163
|
+
|
|
164
|
+
The complementary fact is equally standard and is stated the other way round in the Bayes factor
|
|
165
|
+
design literature: a Bayes factor is consistent when the alternative prior is continuous and positive
|
|
166
|
+
at the true value and the hypotheses are genuinely different distributions. The remark that "one
|
|
167
|
+
expects a Bayes factor to grow without bound in the data, and it does when the hypotheses are
|
|
168
|
+
different distributions" is a restatement of that known consistency condition, and should be read as
|
|
169
|
+
citing it rather than naming a failure of intuition nobody had noticed.
|
|
170
|
+
|
|
171
|
+
The practical consequence is also established, in a different vocabulary. In clinical trials the
|
|
172
|
+
information obtainable when testing against a historical or external control is capped by that
|
|
173
|
+
control's size, quantified as prior effective sample size with explicit maximal borrowing caps. That
|
|
174
|
+
is the same statement as "the ceiling grows linearly in baseline runs", written for a trial-design
|
|
175
|
+
audience.
|
|
176
|
+
|
|
177
|
+
### What was not found
|
|
178
|
+
|
|
179
|
+
- The phrase "evidence ceiling" as a named concept.
|
|
180
|
+
- The explicit closed form for the two-sample beta-binomial case,
|
|
181
|
+
`ceiling(n_b) ≈ n_b · KL(p̄_b ‖ p) + log f_alt(p) + ½ log(2π p̄_b(1−p̄_b)/n_b)`.
|
|
182
|
+
- The KL direction being called out as load bearing, with the 18% error from reversing it.
|
|
183
|
+
- The planning inversion `n_b ≳ log(m/q) / KL(p̄_b ‖ p̄_b − δ)`, and the observation that no planner
|
|
184
|
+
prints it.
|
|
185
|
+
|
|
186
|
+
Those four are the contribution, and `samplesForEvidence` is the last of them made executable. They
|
|
187
|
+
are engineering artifacts derived from known asymptotics, which is worth publishing, but it is a
|
|
188
|
+
different claim from discovering the ceiling.
|
|
189
|
+
|
|
190
|
+
### Citations
|
|
191
|
+
|
|
192
|
+
- Kass and Raftery, *Bayes Factors*, JASA 1995:
|
|
193
|
+
https://sites.stat.washington.edu/raftery/Research/PDF/weakliem1999.pdf
|
|
194
|
+
- Laplace approximation and Bayesian asymptotics, the expansion in standard form:
|
|
195
|
+
https://www.stats.ox.ac.uk/~steffen/teaching/bs2HT9/bayes.pdf
|
|
196
|
+
- Prior effective sample size when borrowing, Zhang et al., *Statistics in Medicine* 2025:
|
|
197
|
+
https://onlinelibrary.wiley.com/doi/10.1002/sim.70235
|
|
198
|
+
- Scoping review of borrowing quantification, including maximal borrowing caps:
|
|
199
|
+
https://arxiv.org/abs/2605.27184
|
|
200
|
+
- Bayes factor consistency conditions, closed-form power and sample size:
|
|
201
|
+
https://arxiv.org/pdf/2406.19940
|
|
202
|
+
|
|
203
|
+
### Searches run
|
|
204
|
+
|
|
205
|
+
Five. Bayes factor bounded, converging to a prior density ratio under Laplace; Bayes factor with two
|
|
206
|
+
priors on the same parameter; e-value safe testing with bounded evidence against a historical control
|
|
207
|
+
under an informative null; Kass and Raftery same-dimension cancellation; and the phrase "evidence
|
|
208
|
+
ceiling" itself, which returned nothing using that term.
|
|
209
|
+
|
|
210
|
+
---
|
|
211
|
+
|
|
212
|
+
## 4 · The certifiable-within-budget frontier
|
|
213
|
+
|
|
214
|
+
**Verdict: not found, on three searches. That is weak evidence, and adjacent work exists.**
|
|
215
|
+
|
|
216
|
+
`computeFrontier` and `enumerateFrontier` produce a Pareto region of configurations that can be
|
|
217
|
+
certified within a budget, with the amortisation window as a first-class axis. Neither the frontier
|
|
218
|
+
as an object, nor the finding that the amortisation window rather than variance decides between
|
|
219
|
+
paired and unpaired designs, was found.
|
|
220
|
+
|
|
221
|
+
Two caveats a reader should weigh:
|
|
222
|
+
|
|
223
|
+
1. **This is an engineering and economics artifact, not a theorem.** Work of this kind lives in
|
|
224
|
+
company engineering blogs and internal design documents far more often than in the literature, so
|
|
225
|
+
absence from academic search is weaker evidence here than for the claims above.
|
|
226
|
+
2. **Adjacent work exists.** Casting benchmarking as finite-population inference under a fixed query
|
|
227
|
+
budget is an active topic, seeking tight confidence intervals for model accuracy with valid
|
|
228
|
+
coverage under a budget constraint. That is a different object (accuracy intervals under a query
|
|
229
|
+
budget, rather than a Pareto region of certifiable configurations with an amortisation axis), but
|
|
230
|
+
it is close enough to belong in related work rather than be left out.
|
|
231
|
+
|
|
232
|
+
The classical paired-versus-unpaired decision is made on correlation and variance reduction. That the
|
|
233
|
+
amortisation window displaces that criterion is, as far as these searches went, this library's own.
|
|
234
|
+
It is stated as "we have not found this drawn anywhere" rather than as established novelty, and the
|
|
235
|
+
distinction matters.
|
|
236
|
+
|
|
237
|
+
### Citations
|
|
238
|
+
|
|
239
|
+
- *Efficient Evaluation of LLM Performance with Statistical Guarantees*:
|
|
240
|
+
https://arxiv.org/html/2601.20251v1
|
|
241
|
+
- Matched pairs design, standard exposition of the classical criterion:
|
|
242
|
+
https://www.growthbook.io/insights/matched-pairs-design-explained-definition-benefits
|
|
243
|
+
|
|
244
|
+
### Searches run
|
|
245
|
+
|
|
246
|
+
Three. Paired versus unpaired decided by control-arm cost and amortisation; LLM evaluation sample
|
|
247
|
+
size and budget for regression detection in CI; and the historical-control angle from claim 3's
|
|
248
|
+
searches, which surfaced the trial-design trade-offs but not a frontier.
|
|
249
|
+
|
|
250
|
+
---
|
|
251
|
+
|
|
252
|
+
## What this changed in the README
|
|
253
|
+
|
|
254
|
+
Three sentences, all narrowing:
|
|
255
|
+
|
|
256
|
+
1. The opening no longer says the sequential-plus-BH result is not written down anywhere. It says
|
|
257
|
+
outright that neither defect is a new observation, names the tutorial section, and states that
|
|
258
|
+
what the library contributes is the working construction plus a measurement of what the broken one
|
|
259
|
+
costs.
|
|
260
|
+
2. The evidence ceiling is introduced as a corollary of the standard Laplace expansion, with the
|
|
261
|
+
historical-borrowing cap named, before the closed form and the planning rule are claimed.
|
|
262
|
+
3. "As far as we can tell" is gone from the two places it could not be supported.
|
|
263
|
+
|
|
264
|
+
What did **not** change: every number. The 4.6× per-case inflation, the 13-against-0 false
|
|
265
|
+
discoveries, the ceiling ladder, the `log(m/q) / KL` planning rule against its exact answer, and the
|
|
266
|
+
frontier cells. Prior art does not touch a measurement, and `test/paper.test.ts` still reproduces all
|
|
267
|
+
of them from seeded draws with `pnpm test`.
|
|
268
|
+
|
|
269
|
+
## Standing invitation
|
|
270
|
+
|
|
271
|
+
If a verdict here is wrong in either direction (a claim marked known that isn't, or one marked "not
|
|
272
|
+
found" that is sitting in a paper somewhere), the citation is worth more than the claim. Open an
|
|
273
|
+
issue. The searches above are written down precisely so that this is checkable rather than a matter
|
|
274
|
+
of trust.
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* baseline.ts: what a case's reference revision did, and the null it implies.
|
|
3
|
+
*
|
|
4
|
+
* peeksafe never runs anything. A caller brings counts, and these are the shapes
|
|
5
|
+
* those counts arrive in.
|
|
6
|
+
*
|
|
7
|
+
* The distinction that matters is between a baseline that says "this case passed
|
|
8
|
+
* 51 of 60 times" and no baseline at all. The prototype this library came from
|
|
9
|
+
* treated a missing baseline as `0/60`, whose Beta(1,1) posterior median is 0.5,
|
|
10
|
+
* and cheerfully gated against a null of "this case passes half the time". It
|
|
11
|
+
* reported a verdict of PASS and a suite improvement of +87.5 points, measured
|
|
12
|
+
* against nothing. Every entry point here therefore takes `BaselineStat |
|
|
13
|
+
* undefined` explicitly and refuses to invent one.
|
|
14
|
+
*/
|
|
15
|
+
/** One case's recorded reference performance. */
|
|
16
|
+
export interface BaselineStat {
|
|
17
|
+
caseId: string;
|
|
18
|
+
successes: number;
|
|
19
|
+
trials: number;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* The minimum a case has to declare to be planned or gated. `suite` groups
|
|
23
|
+
* cases into families for the cluster-robust suite summary; leave it undefined
|
|
24
|
+
* and every case is its own family.
|
|
25
|
+
*/
|
|
26
|
+
export interface CaseRef {
|
|
27
|
+
id: string;
|
|
28
|
+
suite?: string;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* The rate the null is tested at.
|
|
32
|
+
*
|
|
33
|
+
* Not `successes / trials`. A 60-run baseline at a true rate of 0.85 has a
|
|
34
|
+
* standard error of 0.046, which is a third of a 15-point effect, so testing
|
|
35
|
+
* against the point estimate treats a noisy number as an oracle. When the
|
|
36
|
+
* baseline happens to have come in high, every candidate looks worse than it is.
|
|
37
|
+
* Taking a low quantile of the baseline's own posterior instead is what stops
|
|
38
|
+
* that, at the cost of some power.
|
|
39
|
+
*
|
|
40
|
+
* `q` is that quantile. Lower is more conservative.
|
|
41
|
+
*/
|
|
42
|
+
export declare function baselineNullRate(b: BaselineStat, q?: number): number;
|
|
43
|
+
/** Index a list of baselines by case id, rejecting duplicates rather than merging them. */
|
|
44
|
+
export declare function toBaselineMap(stats: readonly BaselineStat[]): Map<string, BaselineStat>;
|
|
45
|
+
//# sourceMappingURL=baseline.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"baseline.d.ts","sourceRoot":"","sources":["../src/baseline.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAKH,iDAAiD;AACjD,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;;;GAIG;AACH,MAAM,WAAW,OAAO;IACtB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,gBAAgB,CAAC,CAAC,EAAE,YAAY,EAAE,CAAC,SAAO,GAAG,MAAM,CAIlE;AAED,2FAA2F;AAC3F,wBAAgB,aAAa,CAAC,KAAK,EAAE,SAAS,YAAY,EAAE,GAAG,GAAG,CAAC,MAAM,EAAE,YAAY,CAAC,CAiBvF"}
|
package/dist/baseline.js
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* baseline.ts: what a case's reference revision did, and the null it implies.
|
|
3
|
+
*
|
|
4
|
+
* peeksafe never runs anything. A caller brings counts, and these are the shapes
|
|
5
|
+
* those counts arrive in.
|
|
6
|
+
*
|
|
7
|
+
* The distinction that matters is between a baseline that says "this case passed
|
|
8
|
+
* 51 of 60 times" and no baseline at all. The prototype this library came from
|
|
9
|
+
* treated a missing baseline as `0/60`, whose Beta(1,1) posterior median is 0.5,
|
|
10
|
+
* and cheerfully gated against a null of "this case passes half the time". It
|
|
11
|
+
* reported a verdict of PASS and a suite improvement of +87.5 points, measured
|
|
12
|
+
* against nothing. Every entry point here therefore takes `BaselineStat |
|
|
13
|
+
* undefined` explicitly and refuses to invent one.
|
|
14
|
+
*/
|
|
15
|
+
import { PeeksafeError, requireCounts, requireOpenProbability } from "./errors.js";
|
|
16
|
+
import { betaQuantile } from "./stats.js";
|
|
17
|
+
/**
|
|
18
|
+
* The rate the null is tested at.
|
|
19
|
+
*
|
|
20
|
+
* Not `successes / trials`. A 60-run baseline at a true rate of 0.85 has a
|
|
21
|
+
* standard error of 0.046, which is a third of a 15-point effect, so testing
|
|
22
|
+
* against the point estimate treats a noisy number as an oracle. When the
|
|
23
|
+
* baseline happens to have come in high, every candidate looks worse than it is.
|
|
24
|
+
* Taking a low quantile of the baseline's own posterior instead is what stops
|
|
25
|
+
* that, at the cost of some power.
|
|
26
|
+
*
|
|
27
|
+
* `q` is that quantile. Lower is more conservative.
|
|
28
|
+
*/
|
|
29
|
+
export function baselineNullRate(b, q = 0.25) {
|
|
30
|
+
requireCounts(b.successes, b.trials, "baselineNullRate");
|
|
31
|
+
requireOpenProbability(q, "q", "baselineNullRate");
|
|
32
|
+
return betaQuantile(1 + b.successes, 1 + b.trials - b.successes, q);
|
|
33
|
+
}
|
|
34
|
+
/** Index a list of baselines by case id, rejecting duplicates rather than merging them. */
|
|
35
|
+
export function toBaselineMap(stats) {
|
|
36
|
+
const m = new Map();
|
|
37
|
+
for (const b of stats) {
|
|
38
|
+
if (m.has(b.caseId)) {
|
|
39
|
+
throw new PeeksafeError("PEEKSAFE_E_CASE_DUPLICATE", `toBaselineMap: duplicate baseline for case ${b.caseId}`, {
|
|
40
|
+
detail: { caseId: b.caseId },
|
|
41
|
+
hint: "merging two baselines for one case would count its runs twice; dedupe them before calling",
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
requireCounts(b.successes, b.trials, `toBaselineMap(${b.caseId})`);
|
|
45
|
+
m.set(b.caseId, b);
|
|
46
|
+
}
|
|
47
|
+
return m;
|
|
48
|
+
}
|
|
49
|
+
//# sourceMappingURL=baseline.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"baseline.js","sourceRoot":"","sources":["../src/baseline.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,sBAAsB,EAAE,MAAM,aAAa,CAAC;AACnF,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAmB1C;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,gBAAgB,CAAC,CAAe,EAAE,CAAC,GAAG,IAAI;IACxD,aAAa,CAAC,CAAC,CAAC,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,kBAAkB,CAAC,CAAC;IACzD,sBAAsB,CAAC,CAAC,EAAE,GAAG,EAAE,kBAAkB,CAAC,CAAC;IACnD,OAAO,YAAY,CAAC,CAAC,GAAG,CAAC,CAAC,SAAS,EAAE,CAAC,GAAG,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC;AACtE,CAAC;AAED,2FAA2F;AAC3F,MAAM,UAAU,aAAa,CAAC,KAA8B;IAC1D,MAAM,CAAC,GAAG,IAAI,GAAG,EAAwB,CAAC;IAC1C,KAAK,MAAM,CAAC,IAAI,KAAK,EAAE,CAAC;QACtB,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC,EAAE,CAAC;YACpB,MAAM,IAAI,aAAa,CACrB,2BAA2B,EAC3B,8CAA8C,CAAC,CAAC,MAAM,EAAE,EACxD;gBACE,MAAM,EAAE,EAAE,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE;gBAC5B,IAAI,EAAE,2FAA2F;aAClG,CACF,CAAC;QACJ,CAAC;QACD,aAAa,CAAC,CAAC,CAAC,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,iBAAiB,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC;QACnE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;IACrB,CAAC;IACD,OAAO,CAAC,CAAC;AACX,CAAC"}
|
package/dist/check.d.ts
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* check.ts: the error rate and the power the gate delivers at rates you name.
|
|
3
|
+
*
|
|
4
|
+
* The two-sample e-value is not an e-value at every fixed rate (README, "What
|
|
5
|
+
* that guarantee rests on"), so its error control is computed rather than
|
|
6
|
+
* assumed. This is that computation, for a caller whose rates, baseline sizes
|
|
7
|
+
* or options sit outside the grid the tests check. Moving the candidate below
|
|
8
|
+
* the baseline turns the same computation into the power.
|
|
9
|
+
*/
|
|
10
|
+
import { type Evidence } from "./evidence.js";
|
|
11
|
+
export interface TypeOneErrorOptions {
|
|
12
|
+
/** Shared true pass rate of the baseline and the candidate. */
|
|
13
|
+
rate: number;
|
|
14
|
+
/** Baseline runs the case will be gated against. */
|
|
15
|
+
baselineTrials: number;
|
|
16
|
+
/** The bar is `1 / alpha`. For `gate`'s solo bar pass `fdr / suiteSize`. */
|
|
17
|
+
alpha: number;
|
|
18
|
+
/** The most candidate runs any case could reach, e.g. `shouldStop`'s `maxTrials`. */
|
|
19
|
+
horizon: number;
|
|
20
|
+
mde?: number;
|
|
21
|
+
altConcentration?: number;
|
|
22
|
+
/** Which e-value `gate` will use. */
|
|
23
|
+
evidence?: Evidence;
|
|
24
|
+
}
|
|
25
|
+
export interface CertifyProbabilityOptions extends Omit<TypeOneErrorOptions, "rate"> {
|
|
26
|
+
/** True pass rate of the baseline revision. */
|
|
27
|
+
baselineRate: number;
|
|
28
|
+
/** True pass rate of the candidate revision. */
|
|
29
|
+
candidateRate: number;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Exact probability that a case is certified as regressed within `horizon`
|
|
33
|
+
* candidate runs, however it was stopped. With the candidate below the baseline
|
|
34
|
+
* this is the power; at or above it, the type I error.
|
|
35
|
+
*
|
|
36
|
+
* Exact because the e-value depends on the candidate only through its counts,
|
|
37
|
+
* so a dynamic program over the success count replaces simulation. A 240-run
|
|
38
|
+
* baseline at a horizon of 600 takes tens of milliseconds.
|
|
39
|
+
*/
|
|
40
|
+
export declare function certifyProbability(options: CertifyProbabilityOptions): number;
|
|
41
|
+
/**
|
|
42
|
+
* Exact probability that a case which did not move is ever certified within
|
|
43
|
+
* `horizon` candidate runs, however it was stopped. At or below `alpha` means
|
|
44
|
+
* the gate keeps its promise for this case.
|
|
45
|
+
*/
|
|
46
|
+
export declare function typeOneError(options: TypeOneErrorOptions): number;
|
|
47
|
+
export interface PairedCertifyProbabilityOptions {
|
|
48
|
+
/** True pass rate of the baseline revision. */
|
|
49
|
+
baselineRate: number;
|
|
50
|
+
/** True pass rate of the candidate revision. */
|
|
51
|
+
candidateRate: number;
|
|
52
|
+
/** Share of pairs whose two runs see the same random draw (a shared seed); 0 for independent runs. */
|
|
53
|
+
coupling?: number;
|
|
54
|
+
/** The bar is `1 / alpha`. For `gate`'s solo bar pass `fdr / suiteSize`. */
|
|
55
|
+
alpha: number;
|
|
56
|
+
/** The most pairs any case could reach. */
|
|
57
|
+
horizon: number;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* `certifyProbability` for a paired case: the exact probability it is certified within
|
|
61
|
+
* `horizon` pairs, however it was stopped. The power when the candidate is lower.
|
|
62
|
+
*/
|
|
63
|
+
export declare function pairedCertifyProbability(options: PairedCertifyProbabilityOptions): number;
|
|
64
|
+
//# sourceMappingURL=check.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"check.d.ts","sourceRoot":"","sources":["../src/check.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAQH,OAAO,EAAE,KAAK,QAAQ,EAAmB,MAAM,eAAe,CAAC;AAI/D,MAAM,WAAW,mBAAmB;IAClC,+DAA+D;IAC/D,IAAI,EAAE,MAAM,CAAC;IACb,oDAAoD;IACpD,cAAc,EAAE,MAAM,CAAC;IACvB,4EAA4E;IAC5E,KAAK,EAAE,MAAM,CAAC;IACd,qFAAqF;IACrF,OAAO,EAAE,MAAM,CAAC;IAChB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,qCAAqC;IACrC,QAAQ,CAAC,EAAE,QAAQ,CAAC;CACrB;AAED,MAAM,WAAW,yBAA0B,SAAQ,IAAI,CAAC,mBAAmB,EAAE,MAAM,CAAC;IAClF,+CAA+C;IAC/C,YAAY,EAAE,MAAM,CAAC;IACrB,gDAAgD;IAChD,aAAa,EAAE,MAAM,CAAC;CACvB;AAED;;;;;;;;GAQG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,yBAAyB,GAAG,MAAM,CA6E7E;AA8BD;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,mBAAmB,GAAG,MAAM,CAIjE;AAED,MAAM,WAAW,+BAA+B;IAC9C,+CAA+C;IAC/C,YAAY,EAAE,MAAM,CAAC;IACrB,gDAAgD;IAChD,aAAa,EAAE,MAAM,CAAC;IACtB,sGAAsG;IACtG,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,4EAA4E;IAC5E,KAAK,EAAE,MAAM,CAAC;IACd,2CAA2C;IAC3C,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;;GAGG;AACH,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,+BAA+B,GAAG,MAAM,CAmDzF"}
|
package/dist/check.js
ADDED
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* check.ts: the error rate and the power the gate delivers at rates you name.
|
|
3
|
+
*
|
|
4
|
+
* The two-sample e-value is not an e-value at every fixed rate (README, "What
|
|
5
|
+
* that guarantee rests on"), so its error control is computed rather than
|
|
6
|
+
* assumed. This is that computation, for a caller whose rates, baseline sizes
|
|
7
|
+
* or options sit outside the grid the tests check. Moving the candidate below
|
|
8
|
+
* the baseline turns the same computation into the power.
|
|
9
|
+
*/
|
|
10
|
+
import { PeeksafeError, requireOpenProbability, requirePositiveConfig, requireProbability, } from "./errors.js";
|
|
11
|
+
import { requireEvidence } from "./evidence.js";
|
|
12
|
+
import { cannotDropBy, DEFAULT_GATE_OPTIONS } from "./gate.js";
|
|
13
|
+
import { logBeta, logGamma, pairedLogE, twoSamplePriors } from "./stats.js";
|
|
14
|
+
/**
|
|
15
|
+
* Exact probability that a case is certified as regressed within `horizon`
|
|
16
|
+
* candidate runs, however it was stopped. With the candidate below the baseline
|
|
17
|
+
* this is the power; at or above it, the type I error.
|
|
18
|
+
*
|
|
19
|
+
* Exact because the e-value depends on the candidate only through its counts,
|
|
20
|
+
* so a dynamic program over the success count replaces simulation. A 240-run
|
|
21
|
+
* baseline at a horizon of 600 takes tens of milliseconds.
|
|
22
|
+
*/
|
|
23
|
+
export function certifyProbability(options) {
|
|
24
|
+
const { baselineRate, candidateRate: pc, baselineTrials: nb, alpha, horizon } = options;
|
|
25
|
+
const mde = options.mde ?? DEFAULT_GATE_OPTIONS.mde;
|
|
26
|
+
const concentration = options.altConcentration ?? DEFAULT_GATE_OPTIONS.altConcentration;
|
|
27
|
+
const evidence = options.evidence ?? DEFAULT_GATE_OPTIONS.evidence;
|
|
28
|
+
requireEvidence(evidence, "certifyProbability");
|
|
29
|
+
requireOpenProbability(baselineRate, "baselineRate", "certifyProbability");
|
|
30
|
+
requireOpenProbability(pc, "candidateRate", "certifyProbability");
|
|
31
|
+
requireOpenProbability(alpha, "alpha", "certifyProbability");
|
|
32
|
+
requireOpenProbability(mde, "mde", "certifyProbability");
|
|
33
|
+
requirePositiveConfig(concentration, "altConcentration", "certifyProbability");
|
|
34
|
+
for (const [name, v] of [
|
|
35
|
+
["baselineTrials", nb],
|
|
36
|
+
["horizon", horizon],
|
|
37
|
+
]) {
|
|
38
|
+
if (!Number.isInteger(v) || v < 1) {
|
|
39
|
+
throw new PeeksafeError("PEEKSAFE_E_CONFIG", `certifyProbability: ${name} must be a positive integer, got ${v}`, {
|
|
40
|
+
detail: { [name]: v },
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
const bar = Math.log(1 / alpha);
|
|
45
|
+
const logChoose = (n, k) => logGamma(n + 1) - logGamma(k + 1) - logGamma(n - k + 1);
|
|
46
|
+
let total = 0;
|
|
47
|
+
for (let sb = 0; sb <= nb; sb++) {
|
|
48
|
+
const weight = Math.exp(logChoose(nb, sb) + sb * Math.log(baselineRate) + (nb - sb) * Math.log(1 - baselineRate));
|
|
49
|
+
if (weight < 1e-12 || cannotDropBy({ successes: sb, trials: nb }, mde))
|
|
50
|
+
continue;
|
|
51
|
+
const { nullPrior: n0, altPrior: a1 } = twoSamplePriors(sb, nb, mde, concentration);
|
|
52
|
+
let alive = new Float64Array(horizon + 1);
|
|
53
|
+
alive[0] = 1;
|
|
54
|
+
let crossed = 0;
|
|
55
|
+
// Only the band of success counts still carrying mass is stepped.
|
|
56
|
+
let lo = 0;
|
|
57
|
+
let hi = 0;
|
|
58
|
+
for (let n = 1; n <= horizon; n++) {
|
|
59
|
+
const next = new Float64Array(horizon + 1);
|
|
60
|
+
for (let s = lo; s <= hi; s++) {
|
|
61
|
+
next[s + 1] += alive[s] * pc;
|
|
62
|
+
next[s] += alive[s] * (1 - pc);
|
|
63
|
+
}
|
|
64
|
+
hi++;
|
|
65
|
+
// Every count in the band is tested: logE is not monotone in s, so the
|
|
66
|
+
// crossing set need not be a prefix. Successive counts differ by a ratio
|
|
67
|
+
// of Beta functions, which keeps this cheap.
|
|
68
|
+
let logE = rawLogE(lo, n, sb, nb, n0, a1);
|
|
69
|
+
for (let s = lo; s <= hi; s++) {
|
|
70
|
+
if (s > lo) {
|
|
71
|
+
const f = n - s + 1;
|
|
72
|
+
logE +=
|
|
73
|
+
Math.log((a1.a + s - 1) / (a1.b + f - 1)) - Math.log((n0.a + s - 1) / (n0.b + f - 1));
|
|
74
|
+
}
|
|
75
|
+
const decided = evidence === "universal"
|
|
76
|
+
? universalFromBayes(logE, s, n, sb, nb)
|
|
77
|
+
: s / n >= sb / nb
|
|
78
|
+
? Math.min(0, logE)
|
|
79
|
+
: logE;
|
|
80
|
+
if (decided >= bar) {
|
|
81
|
+
crossed += next[s];
|
|
82
|
+
next[s] = 0;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
while (lo < hi && next[lo] < 1e-18)
|
|
86
|
+
lo++;
|
|
87
|
+
while (hi > lo && next[hi] < 1e-18)
|
|
88
|
+
hi--;
|
|
89
|
+
alive = next;
|
|
90
|
+
}
|
|
91
|
+
total += weight * crossed;
|
|
92
|
+
}
|
|
93
|
+
return total;
|
|
94
|
+
}
|
|
95
|
+
const xlogy = (x, y) => (x === 0 ? 0 : x * Math.log(y));
|
|
96
|
+
/** The uncapped Bayes factor, which both statistics are built from. */
|
|
97
|
+
const rawLogE = (s, n, _sb, _nb, n0, a1) => logBeta(a1.a + s, a1.b + n - s) -
|
|
98
|
+
logBeta(a1.a, a1.b) -
|
|
99
|
+
(logBeta(n0.a + s, n0.b + n - s) - logBeta(n0.a, n0.b));
|
|
100
|
+
/** `universalTwoSampleLogE`, from the uncapped Bayes factor: times the pooled mixture over the null maximum. */
|
|
101
|
+
function universalFromBayes(rawLog, s, n, sb, nb) {
|
|
102
|
+
const S = sb + s;
|
|
103
|
+
const N = nb + n;
|
|
104
|
+
const pb = sb / nb;
|
|
105
|
+
const pc = s / n;
|
|
106
|
+
const sup = pc >= pb
|
|
107
|
+
? xlogy(sb, pb) + xlogy(nb - sb, 1 - pb) + xlogy(s, pc) + xlogy(n - s, 1 - pc)
|
|
108
|
+
: xlogy(S, S / N) + xlogy(N - S, 1 - S / N);
|
|
109
|
+
return rawLog + logBeta(1 + S, 1 + N - S) - sup;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Exact probability that a case which did not move is ever certified within
|
|
113
|
+
* `horizon` candidate runs, however it was stopped. At or below `alpha` means
|
|
114
|
+
* the gate keeps its promise for this case.
|
|
115
|
+
*/
|
|
116
|
+
export function typeOneError(options) {
|
|
117
|
+
const { rate, ...rest } = options;
|
|
118
|
+
requireOpenProbability(rate, "rate", "typeOneError");
|
|
119
|
+
return certifyProbability({ ...rest, baselineRate: rate, candidateRate: rate });
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* `certifyProbability` for a paired case: the exact probability it is certified within
|
|
123
|
+
* `horizon` pairs, however it was stopped. The power when the candidate is lower.
|
|
124
|
+
*/
|
|
125
|
+
export function pairedCertifyProbability(options) {
|
|
126
|
+
const { baselineRate: pb, candidateRate: pc, alpha, horizon } = options;
|
|
127
|
+
const rho = options.coupling ?? 0;
|
|
128
|
+
requireOpenProbability(pb, "baselineRate", "pairedCertifyProbability");
|
|
129
|
+
requireOpenProbability(pc, "candidateRate", "pairedCertifyProbability");
|
|
130
|
+
requireOpenProbability(alpha, "alpha", "pairedCertifyProbability");
|
|
131
|
+
requireProbability(rho, "coupling", "pairedCertifyProbability");
|
|
132
|
+
if (!Number.isInteger(horizon) || horizon < 1) {
|
|
133
|
+
throw new PeeksafeError("PEEKSAFE_E_CONFIG", `pairedCertifyProbability: horizon must be a positive integer, got ${horizon}`, {
|
|
134
|
+
detail: { horizon },
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
const worse = rho * Math.max(0, pb - pc) + (1 - rho) * pb * (1 - pc);
|
|
138
|
+
const better = rho * Math.max(0, pc - pb) + (1 - rho) * pc * (1 - pb);
|
|
139
|
+
const bar = Math.log(1 / alpha);
|
|
140
|
+
// The e-value depends on (worse, discordant) only, so crossing is tabulated once per state.
|
|
141
|
+
const crosses = Array.from({ length: horizon + 1 }, (_, d) => Uint8Array.from({ length: d + 1 }, (_, w) => (pairedLogE(w, d) >= bar ? 1 : 0)));
|
|
142
|
+
// alive[d][w]: probability of d discordant pairs, w of them worse, and no crossing yet.
|
|
143
|
+
let alive = [Float64Array.of(1)];
|
|
144
|
+
let crossed = 0;
|
|
145
|
+
for (let n = 1; n <= horizon; n++) {
|
|
146
|
+
const next = Array.from({ length: n + 1 }, (_, d) => new Float64Array(d + 1));
|
|
147
|
+
for (let d = 0; d < n; d++) {
|
|
148
|
+
const row = alive[d];
|
|
149
|
+
for (let w = 0; w <= d; w++) {
|
|
150
|
+
const m = row[w];
|
|
151
|
+
if (m < 1e-300)
|
|
152
|
+
continue;
|
|
153
|
+
next[d][w] += m * (1 - worse - better);
|
|
154
|
+
next[d + 1][w + 1] += m * worse;
|
|
155
|
+
next[d + 1][w] += m * better;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
for (let d = 1; d <= n; d++) {
|
|
159
|
+
const row = next[d];
|
|
160
|
+
const cross = crosses[d];
|
|
161
|
+
for (let w = 0; w <= d; w++) {
|
|
162
|
+
if (cross[w] && row[w] > 0) {
|
|
163
|
+
crossed += row[w];
|
|
164
|
+
row[w] = 0;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
alive = next;
|
|
169
|
+
}
|
|
170
|
+
return crossed;
|
|
171
|
+
}
|
|
172
|
+
//# sourceMappingURL=check.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"check.js","sourceRoot":"","sources":["../src/check.ts"],"names":[],"mappings":"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"}
|