Method benchmarking strategy#
Method benchmarking has two complementary parts: bounded comparisons with an external R implementation and independent statistical studies against properties implied by the derivation. Neither replaces the package’s exact-law, Gateaux, remainder, identity, and deliberate-mutation checks. Use this strategy when adding or changing a method-level row in the implementation validation grid.
Begin with the scientific claim#
Record the identified parameter, estimator construction, governing source, supported
compositions, and inference rule before choosing a comparator. Agreement with an R package is
acceptance evidence for the claims the comparison covers, as
Theory and comparators states.
A study that names a reference is comparator-backed: its page and its grid row carry that label,
and its StudyRecord declares a comparison kind. Agreement does not prove that the parameter, influence curve, or
theorem is correct.
Give materially different estimators separate evidence rows. Ordinary TMLE, stacked CV-TMLE, fold-evaluated CV-TMLE, and fold-specific extensions may share a limit while differing in finite samples; one method does not inherit another’s parity or coverage claims.
Reference implementation comparisons#
Pin the runtime base image and packages immutably. Record hashes for the Dockerfile, runner, and reference sources in the study manifest.
Give both implementations the same realized datasets, covariates, contrasts, nuisance-model families, bounds, targeting controls, and interval scale. Declare any setting that cannot be matched and exclude only the affected comparison.
Supply and assert the exact row-to-fold assignment for cross-fitted methods. A common seed or fold count is not enough when splitters or dependency versions differ.
Preserve native inference scales, including log-scale risk-ratio and odds-ratio intervals.
Record each fitting failure of either implementation, and account for it as Comparator fitting failures states. The run fails on a missing replication that has no record. Do not replace samples or summarize a shorter run than the one declared.
Resume a reference phase that a killed worker or container stopped. The phase refits only the groups without a checkpoint, as the testing strategy describes.
Retain paired replication-level estimates, standard errors, interval endpoints, truth, coverage, initial estimates where exposed, and pairing keys. Require a nonzero targeting witness so parity cannot be explained by both implementations returning their initial fits.
Test each implementation against known truth before comparing them. Use predeclared equivalence and non-inferiority margins for the paired comparison rather than a null test of exact equality.
Comparator fitting failures#
The R harness (tests/canonical/study_harness.R) runs each comparator call inside
study_stage. An error in that call becomes a failure record, and the run continues. The
Python phase fits each cleverly replicate through fit_replicates
(tests/studies/evidence/replication.py), and a recordable error there also becomes a record.
The driver publishes both sides’ records as failures.csv and the accounting as
failure-accounting.csv. Both files exist only when a fit failed.
rule |
statement |
|---|---|
bound |
|
bound 0 |
One failure makes the cell red. Diagnose the red cell, fix a genuine defect, and re-run under a fresh declaration. Otherwise report it |
raising a bound |
Declare a bound before the study runs under it. A study whose run has shown failures may not raise its bound. The driver refuses such a run before any fit ( |
manifest and claims |
A run writes |
fallback fit |
Do not replace a failed replicate by a fallback fit or a new sample |
invalid result |
A comparator row with a non-finite value, a non-positive standard error, an inverted interval, or a |
worst case |
Each verdict leg must pass under its least favourable candidate fill of the lost values. The bias, coverage, and similarity candidates lie inside the surviving range. The RMSE and coverage-difference legs fill the reference at its best case: the estimate at the truth and the interval covering it. They fill the subject at its worst case: each lost estimate at the surviving estimate farthest from the truth, and each lost interval missing the truth. The calibration leg tries every pair of the two sides’ candidate fills. The range is an assumption, and the accounting records it |
enumeration |
The SE-ratio and calibration candidates are a finite set: edge, mean, and root fills. They are not a proof that no other fill is less favourable |
routes |
A paired cell passes by equivalence or by superiority. A route survives when each of its legs passes its worst case. A |
pairing |
A paired cell drops the replicates that either side lost |
witness |
The accounting reports the subject’s mean error on the replicates only the reference lost and on the shared ones. It reports the reference’s mean error on the replicates only the subject lost and on the shared ones. The witness is descriptive. No gate reads it |
hooks |
A study hook must define |
calibration |
A group that fails during memory calibration is skipped, and calibration takes the next group of its scenario. When 20 groups of one scenario fail, the run stops as systematic |
recording off |
|
subject failures |
The Python phase records a |
subject rows |
A subject row that breaks a row rule of the |
side bounds |
Each implementation’s failures count against its own bound. A paired cell is red when either side exceeds its bound |
paired input |
A study whose reference input does not need the subject’s fit still writes the sample of a replicate that the subject lost, so the reference fits it. A study whose reference input comes from the subject’s fit writes no such sample. The driver then writes a |
Python checkpoints |
Under the driver, each Python replicate runs in a worker and leaves a checkpoint. A killed worker costs the replicates in flight. Each one runs once more alone, and a second kill stops the run. A |
zEpid |
The zEpid runner records failures with a copy of the Python rule that adds |
property phase |
A recordable error in a property fit costs the cells that the fit writes one replicate each. See Property fitting failures |
The stage covers the comparator call only. These conditions stay fatal and stop the run:
an error outside a stage, such as a data read or an output write,
a
study_fatal_error, such as a promoted warning,a C stack overflow,
the first 20 groups, or Python replicates, of one cell all fail (a cell is one scenario, or the set of scenarios one key spans), and
every group, or every Python replicate, of the run fails.
A memory-exhaustion error ends the worker as a kill. The same run then refits that group with half the workers. A failure message can carry a container path. The record keeps it as written.
Property fitting failures#
A property loop fits through fit_properties (tests/studies/evidence/replication.py). A
recordable error in one fit costs the cells that the fit writes one replicate each. The run
continues. The driver opens replication.recording() around the property phase and publishes the
records. These files exist only when a property fit failed. A study with no failure publishes the
bytes it published before.
file |
content |
|---|---|
|
one record for each failed fit, with the columns of |
|
for each summary row that read a lost replicate: the bound, then one row for each declared clause. Each clause row gives the complete-case slack, the worst-case slack, the label, and the gate |
|
the witness of each draw in each cell that lost a replicate |
Record and stamp rules#
rule |
statement |
|---|---|
multi-cell key |
One fit can write several cells. A lost fit costs every cell it writes one replicate. |
|
A module whose |
|
A module declares each base cell’s derived controls ( |
stamp |
The driver is the only writer of |
|
A cell may be short by exactly its stamped failures and must keep two replicates. Any other gap stops the summary |
pairing |
A paired statistic reads the replicates that both arms kept ( |
derived noise |
|
verdict budget |
|
rate rows |
|
draw split |
Each property loop draws its sample in |
byte identity |
|
no-draw loop |
|
|
A bootstrap that lost draws writes the side column |
memory cap |
|
classifier |
The classes and the stop conditions of the |
no collector |
Outside the driver, a recorded property failure raises |
checkpoints |
The property phase checkpoints each fit under |
The bound#
item |
rule |
|---|---|
declaration |
Every property module declares |
allowance |
A cell may lose |
gate |
A cell above its allowance reads |
declared before the run |
The value governs a study’s next property run. No committed property run lost a replicate, so the value rescues no committed result |
raising |
Raise the bound only while no committed run of the study shows a property failure. The driver refuses a property run whose |
lowering |
Lowering is allowed. It changes the |
manifest |
A property run writes |
primary phase |
Each implementation of each registered study declares 0.01 for its next run. No committed primary run lost a replicate, so the value moves no committed verdict. The primary bound has the same facts as the property bound (see the next table) |
The value 0.01 follows from these facts.
fact |
consequence |
|---|---|
Seeds are deterministic, so a pathological replicate fails again on every rerun |
At bound 0 the cell stays red. At 0.01 the worst case of the claim decides |
At bound 0 the gate returns |
At 0.01 every declared clause can act in a real run |
A lost fit is plausibly an extreme draw, so its estimate is the least likely to lie in the survivor range |
For an unbounded statistic, the worst-case mean shift is about |
A bounded statistic fills its full support |
The range assumption does not apply to it |
The primary bound has two more facts.
fact |
consequence |
|---|---|
The accounting only turns a cell red. A paired leg fills the reference at its best case and the subject at its worst case |
A lost replicate on either side never rescues a verdict. At 0.01 the reference side’s losses are judged by the same legs |
No committed run has a |
No manifest needs the key, no claim artifact changes, and no study reruns for this declaration |
Accounting statistics#
Each clause reads one statistic. Each statistic has a candidate set for the k lost values. A
cell is red when a clause that holds on the complete cases fails under one candidate. A
support clause fills a bounded variable over its full support and needs no assumption. A range
clause assumes that a lost value lies inside the survivor range and that the scale of the margin
stays at its complete-case value. Every bootstrap in a candidate uses the verdict’s own seed, so
the case k = 0 reproduces the complete case.
statistic |
verdicts it judges |
candidate fills |
tier |
label |
|---|---|---|---|---|
S1 bias interval |
equivalence, discrimination, and a fixed floor on the bias interval |
errors at every vertex split between the survivor minimum and maximum, with the scale held. A sided clause holds on its declared side |
exact |
|
S2 coverage rate |
coverage floors, bands, and ceilings |
|
exact |
|
S3 rejection rate |
type-I error, power, warning rate |
|
exact |
|
S4 SE ratio, point |
the SE sanity band |
the SEs at the survivor extremes against the estimates at the survivor mean and at each vertex split |
exact |
|
S5 SE and efficiency ratio intervals |
calibration bands and controls |
the S4 set, through |
enumerated |
|
S6 rate slope |
root-n rate, contraction slopes |
for each lost rung, every vertex split and one fill at the survivor mean, with exact pruning. For a bias slope, the fill nearest a zero mean replaces the survivor-mean fill |
point exact, interval enumerated |
|
S7 coverage gain |
paired overfitting and clustered gain |
a lost positive arm at |
exact |
|
S9 paired spread ratio |
randomizer projection, repeat stability |
the numerator at each vertex split and the denominator at the survivor mean, or the reverse for a lower bound |
point exact, interval enumerated |
|
S10 extra column mean |
a cap or a floor on a published column |
the lost rows at the adverse support bound for a share, otherwise at the survivor maximum or minimum |
exact |
|
S12 paired displacement |
the 16 necessity families |
the attainable difference of the arm means, with |
exact under the held scale |
|
S13 RMSE ratio, point |
selector necessity |
the numerator errors at the farther survivor end, the denominator estimates at the truth |
exact when the truth lies in the survivor range |
|
S14 two-size absolute-bias bound |
single robustness, sequential robustness |
the larger size at every vertex split, the smaller size at equal fills |
exact |
|
S15 standardized-bias growth |
|
every vertex split, the equal fills that maximize and minimize the ratio, and the fill nearest a zero mean |
point exact, interval enumerated |
|
“Exact” means that the candidate set reaches the worst value over every completion that the label allows. “Enumerated” means that the set is a finite list. The verdict’s own bootstrap evaluates it. The list does not prove that no other fill is worse.
Rule per family#
A module declares its rules in PROPERTY_RULES, beside summarize_properties. A module rule
takes precedence over the shared FAMILY_RULES entry. The fast tier fails a verdict family that
resolves to no rule. The group of a rule names the cells that share a joint verdict: row,
pair, label, or family. A module declares its pairs and labels in PROPERTY_GROUPS. The
rule never infers a pair from a cell name.
families |
clauses |
group |
|---|---|---|
|
positive: S1 equivalent, S4 in the union band. control: S1 discriminated, S4 |
|
|
positive: S2 floor, S4 sanity, S1 equivalent. control: S2 resolve rule |
|
|
S6 slope inside the band, or the sign of the bias slope |
|
|
S3, with the S2 floor for |
|
|
S5 in the band, S2 in the band, S5 efficiency where a band is declared |
|
|
S2 in the band, or at the ceiling for the control |
|
|
S5 and S2 on each cell, S7 gain on the pair |
|
|
S1 equivalent, S2 floor, S5 ceiling |
|
the 16 displacement families |
S1 on each cell, S12 minimum over the labels |
|
|
S1 on each arm, S13 maximum over the paths |
|
|
S5 and S2 on each cell, then every cell passes |
|
|
S5 on each cell, then every cell passes |
|
|
S1 on each cell, then every cell passes |
|
|
S2 on each cell, then every cell passes |
|
|
S1, S2, S4 as the verdict applies them |
|
|
S2 floor |
|
|
S1 on each arm, S9 on the pair of projections |
|
|
S9 |
|
|
S14 over the two sizes |
|
|
S14 over the two sizes, with S1, S2, S4 on the both-correct pattern |
|
|
S5 and S2 in the band |
|
|
rules C, V and F with S1, S2, S4, S5, and rule G with S15 |
|
|
S1 equivalent or beyond a floor, S2, S4 |
|
Three clauses are not accountable, because no sound candidate set exists. A lost replicate in
one of them turns the cell red at any bound, and the reason reads failures not accountable.
Diagnose the cell, fix the cause, and rerun.
clause |
reason |
|---|---|
|
The clause is a supremum of a difference with a fixed tolerance. One lost value moves a supremum by an order-one amount. A range fill leaves it unchanged, so the leg would pass whenever the complete case passes. A wider fill fails every time |
the closed-score clause of |
The claim is that the fit’s score closes. A recorded fit failure is a counterexample, and a range fill assumes it away. No range completion fails the clause, so the leg is vacuous |
Rows with no verdict (fold_policy, cluster_aggregation_rule, in_sample_agreement,
variance_direction, one_wrong_reported, construction_diagnostic, sdr_tmle_agreement,
censoring_complete_case, in_sample_fit, and the diagnostic rows of few_cluster_reference and
single_robustness) get no accounting row. The stamp is their record.
The informative-failure witness#
Each module declares PROPERTY_WITNESS, a statistic of the draw that needs no fit. A lost
replicate’s draw is regenerated from its deterministic seed. The accounting reports the statistic
and its quantile among the cell’s survivors. The report is not a gate, because no validated
threshold exists. A draw that does not separate from the fit reports nan with the reason
draw not separable.
The property differential#
A rule restates a verdict that the study’s own summarize_properties computes. A rule that drops
a conjunct would let a lost replicate turn a cell green. Two checks guard the rules.
check |
where |
content |
|---|---|---|
differential |
the |
The job perturbs the property rows of every committed study. It also perturbs synthetic rows of the uncommitted modules and of |
clause binding |
the same job |
Each clause that a subject uses must decide a verdict alone under a perturbation of that subject. Many clauses read a threshold of their own module, so a witness in another study does not bind them. |
binding reasons |
the same job |
A pair that its subject cannot decide alone has a row in |
no-loss candidates |
the same job |
At |
fast tier |
|
The job’s parametrization lists every committed study. Every verdict family resolves to a rule. A module that writes |
reason |
check |
where it applies |
|---|---|---|
|
The S2 or S3 clause gives one verdict at every success count from 0 to |
the root-n control’s |
|
The other subject runs the same module and record, and its witness decides the pair |
the committed and the synthetic rows of |
|
On every row, the other clause fails wherever this one fails. The check sweeps each role’s mean error from -3 to 3 times, scales its spread, and reads every shared perturbation |
|
The job runs 5,892 cases in about 7,600 CPU-seconds, about 35 minutes on four workers. So it does not run in the fast tier, and its timeout is 105 minutes, three times the measured run. It runs on every pull
request that touches tests/studies/, tests/canonical/, src/cleverly/, pyproject.toml,
uv.lock or the workflow. A failed git diff fails the job instead of skipping it. Make it a
required check. It always reports, and it skips its steps on other pull requests.
Choose the comparator before you write the runner#
Search for every maintained implementation of the parameter. Record what you found. Record why you rejected each candidate you did not use. A study that reports no comparator makes a claim about the field, and a reader cannot check that claim unless the study names what it examined. A study with no comparator is still eligible to register. It records the independent evidence that replaces the comparison, as Theory and comparators requires.
A comparator phase can finish after the method merges. The method merges when its Python phases pass with their expected properties. Until the comparator results are registered, the evidence page labels the evidence “comparator runs pending”. A later change registers the comparator results and fixes each defect that they show.
Prefer the implementation that the governing source publishes. That implementation reports the influence curve the source derives. A separate package that reproduces the point estimate does not have to report the same influence curve, and one such package does not.
Match the comparator’s nuisance specification. Give both implementations the same regression family over the same design. An asymmetric pair measures two model choices instead of two targeting steps. The asymmetry then fails a calibration comparison for a reason that belongs to neither estimator.
This survey covers the intervention family. Each verdict names the evidence behind it.
candidate |
parameter it reaches |
verdict |
|---|---|---|
R |
clustered point-treatment effects, clustered cross-fitted longitudinal regimen effects at equal cluster sizes, deterministic regimes, categorical longitudinal regimes, known stochastic regimes, modified treatment policies |
Used by point and longitudinal studies. The adapters supply analytic density ratios and exact rowwise folds. The clustered study also supplies identifiers and forms contrasts from joint influence curves. Its longitudinal TMLE fits each fold’s fluctuation on training rows, and |
R |
point-treatment effects, counterfactual densities, continuous-treatment curves, and incremental propensity-score interventions |
Used by the incremental study. Its public estimator list has no deterministic categorical longitudinal regimen. |
R |
static, dynamic, and stochastic longitudinal regimes with categorical exposures |
Not used for categorical parity. Its required long-form data introduces a second representation, while pinned |
Poulos |
simulation code for longitudinal multi-valued treatment |
Retained as supporting source provenance. It is a simulation repository, not a versioned package entry point for rowwise paired studies. |
R |
incremental odds curve |
Rejected. Its influence curve omits that derivative, so it witnesses the point curve and cannot gate inference. |
R |
deterministic point-treatment regimes |
Available and not used. |
R |
continuous shift interventions |
Not used. It estimates the exposure density through a second density path, which requires a separate study rather than serving as a second opinion on this one. |
R |
static and dynamic point-treatment regimes |
Rejected for stochastic regimes. |
The point-treatment survival survey covers one baseline treatment and an event over visits.
It also covers the hazard-based and monotone curves of
hazard-based survival targeting.
The study point-survival-hazard is registered, and its survtmle comparator is pending.
The study point-survival-curve-crossfit is registered and has no comparator, because no
maintained package ships hazard targeting with cross-fitted nuisances.
The study point-survival-curve is registered, and its tmle3 comparator is pending.
candidate |
parameter it reaches |
verdict |
|---|---|---|
R |
|
Used by |
R |
survival with a baseline treatment, a known policy or a modified treatment policy |
Used by |
R |
the per-horizon hazard TMLE of Moore and van der Laan (2009), for two arms and competing causes, one |
Declared for |
R |
|
Declared for |
MOSS 1.2.0 at |
|
Not paired. |
R |
|
Transcribed, not paired. |
|
continuous-time survival |
Not used. It belongs to X6 |
R |
a point treatment with survival nodes |
Not used. It adds no witness that |
The cluster survey records how each pinned comparator aggregates a clustered curve. At unequal cluster sizes the cluster sum is the delta-method variance of the row mean, and the cluster mean is a different variance (references gives the source locators).
candidate |
aggregation |
verdict |
|---|---|---|
R |
the cluster sum, \(t\) with \(J-1\) degrees of freedom below 100 clusters |
Used by |
R |
the cluster mean, normal reference |
Not used at unequal sizes. Its variance is a different quantity there |
R |
the cluster mean, normal reference |
Not used at unequal sizes, for the same reason |
R |
the cluster mean, normal reference |
Used by |
R |
no cluster argument |
Not applicable |
The missing-outcome survey is separate because the response mechanism changes the observed-data likelihood and the comparator boundary.
candidate |
parameter it reaches |
verdict |
|---|---|---|
R |
ordinary point-treatment TMLE with MAR outcomes |
Used for the observational arm-indexed row. It accepts separate treatment and response nuisance predictions and reports arm means and their contrast. |
R |
missing-outcome natural-course mean |
Used for the stacked natural-course row, conditional on supplied stitched outcome and response predictions. Used for both laws of the ordinary natural-course row, conditional on the supplied in-sample predictions. A continuous fit plants the two |
R |
stacked arm-indexed missing-outcome means and contrasts |
Used for the stacked arm-indexed row, conditional on supplied stitched outcome, treatment, and response predictions. L1 and L2 use the two-arm path. L3 and L4 run the population-mean path once for each arm, and the runner builds the joint covariance from the per-arm curves. A continuous fit plants the two |
R |
corrected randomized point-treatment means with missing outcomes |
Used only in the both-correct limit. Its |
The fixed-weight survey is separate because observation weights change the target law and every estimation stage.
candidate |
weighted construction it reaches |
verdict |
|---|---|---|
R |
ordinary point-treatment TMLE through |
Used by both weighted point-treatment rows. The fixed-nuisance runner supplies the same weights and exact predictions to both implementations. The learned-nuisance runner fits matching weighted main-effects Gaussian and binomial GLMs. In the fixed-nuisance row, R |
R |
ordinary weighted longitudinal TMLE through |
Used by the registered ordinary weighted longitudinal reporting study. Its sequential data layout adds no witness to the point-treatment row. |
R |
cross-fitted weighted longitudinal TMLE through |
Used by the registered cross-fitted weighted longitudinal reporting study. It is not ordinary point-treatment TMLE. |
The cross-fitting survey is separate because the question is how an implementation aggregates over folds, not which parameter it reaches. Every candidate below describes itself as cross-fitted or cross-validated, and the candidates do not agree on what that means. Read the aggregation line in the source before you accept one.
candidate |
how it aggregates over folds |
verdict |
|---|---|---|
R |
stacks the validation rows, targets once, and evaluates on the whole sample |
Used by the stacked CV-TMLE row. It is not the fold-evaluated construction. |
R |
accepts stitched out-of-fold outcome and response predictions, targets once, and evaluates on the whole sample |
Used by the stacked missing-outcome natural-course row and, once for each arm, by the stacked arm-indexed row. Each comparison is conditional on the supplied predictions. |
R |
pools the out-of-fold predictions and forms one estimate on the whole sample |
Rejected for the fold-evaluated row. |
R |
takes |
Rejected for the fold-evaluated row. Used elsewhere for its intervention family. |
R |
binds the per-fold results, then targets over the pooled validation rows |
Rejected. Its estimand is a mediation effect, and its aggregation is pooled. |
R |
averages the influence-function values over all rows |
Rejected. It is a one-step AIPW estimator, and its aggregation is pooled. |
Julia |
takes the mean of the counterfactual aggregate over all rows, after one fluctuation |
Rejected for the fold-evaluated row. The multi-arm selector survey below also rejects it as a canonical comparator. |
Python |
|
Used by the fold-targeted CV-TMLE row at two equal folds and one partition. The two point-estimate weightings coincide only at these equal sizes. At two folds, each nuisance training split is the complete validation-fold complement. It also corroborates repeated-report aggregation, but it does not compare with the stacked pooled update. |
R |
takes the median of the split estimates under a double cross-fit |
Rejected. Double cross-fitting fits each nuisance on a separate split, which is a different estimator. |
Chernozhukov et al. (2018), Definition 3.3 and equation (3.14) |
explicitly define median aggregation for fixed repeated partitions with within-partition variance plus squared split displacement |
Governing source for the median reporting rule and fixed-repeat first-order validity. It does not make the DML score a TMLE or establish full-method numerical parity. |
current R and Python |
uses median aggregation with a between-partition dispersion adjustment |
Further corroboration for the reporting rule, but rejected as a comparator because it is DML rather than a targeted estimator and does not expose |
The multi-arm collaborative survey is separate because the selector, not the parameter, is what a comparator has to reach.
candidate |
selector it reaches |
verdict |
|---|---|---|
R |
greedy and pre-ordered selectors for a binary treatment |
Rejected. |
R |
greedy and pre-ordered selectors on |
Rejected, and not because the parameter differs. An arm mean under the indicator is the same parameter. Per-arm selection is a different mechanism from this package’s joint multiclass selection, and the return value carries no influence curve, so a contrast would have no covariance. |
archived R |
the outcome-adaptive treatment model |
Used by the outcome-adaptive rows. It ships no greedy, ordered, or discrete selector. |
R |
the binary lasso path and the declared candidate sequence |
Used by the replay gates and the descriptive paired rows of the binary |
Julia |
componentwise |
Rejected as a canonical comparator and not planned as a roadmap priority. Its |
Multi-arm selector recommendation#
Keep the shipped joint selector. Do not replace it with the componentwise TMLE.jl path.
The library review records the alternative and its limitations; implementing it is not a current
priority and it is not tracked on the roadmap.
van der Laan and Gruber (2010) derive greedy
collaborative selection for one target parameter. Ju et al.
(2019) develop the scalable pre-ordered approach.
Neither paper supplies a theorem for separately selected multi-arm components and their contrast
covariance. The TMLE.jl JOSS paper documents the software,
but it does not add that theorem.
Two limits of the framework shape these tables. A study record names one reference, so a second
comparator needs a second registered study. A comparator that fits its own nuisances also fixes
the specification the subject must match.
Independent statistical evidence#
Each property cell declares an exact-truth law, fixed estimator configuration, replication budget, margin, and seed rule before the full run. Size the budget against the binding confidence bound, not the most favorable point estimate.
Primary replications use the study record’s replicate_seed; property cells use fixed shared
seeds so methods see identical draws and paired controls merge by replication index. Resampling
uses independently labelled stream_seed streams rather than adjacent integer offsets.
Use interval-shaped verdicts: bias intervals must fit inside a practical equivalence margin, coverage lower bounds must clear a validity floor, and one-sided type-I bounds must rule out material over-rejection. Positive claims need a control that makes the same instrument fail:
double robustness includes a both-wrong nuisance control;
type-I error includes a nonzero-effect power control;
coverage or standard-error calibration includes deliberately invalid inference; and
convergence uses at least three sample sizes and excludes a predeclared slower rate.
Repeated-sampling, large-sample, and flexible-learner claims belong in a registered study and its committed artifacts. Documentation examples never count as statistical evidence.
A truth that varies by replication#
Set truth_varies_by_replicate=True on a StudyRecord when the target is a data-adaptive
parameter. The value of the rules that each training fold learns is an example: each replication
has its own truth. The spread of the estimate then differs from the spread of the error, so every
statistic must read the error. The flag defaults to False. Every other record keeps its
arithmetic byte for byte, because subtracting a constant truth moves the last bits.
statistic |
a record with one truth |
a record that sets the flag |
|---|---|---|
schema check of the truth |
constant within |
finite on every row. |
inference scale |
any declared scale |
the level scale only |
bias, its interval, and the standardized bias |
the mean estimate minus the truth, over the SD of the estimate |
the mean error, over the SD of the error |
empirical SE, RMSE, SE ratio and its bootstrap |
from the estimates |
from the errors. The bootstrap resamples the error and the SE together |
the empirical efficiency ratio and the root-n |
the SD of the estimate |
the SD of the error |
paired displacement |
on the estimates |
on the errors. Each arm subtracts its own truth |
|
|
|
Such a record cannot declare a reference, because the paired comparison reads one truth per cell.
tests/unit/test_per_replicate_truth.py holds the witnesses. The learned-rule studies set the
flag.
Registration and acceptance#
Register the scenarios, estimands, sample size, replication count, margins, runner and property modules, expected cells, artifact directory, document anchor, and every result-determining module. A study without an external comparator records a valid empty equivalence artifact rather than a surrogate reference.
The manifest must record a hash under study_module_sha256 for every study-specific module. List
each one in the StudyRecord modules field, which supplies that list.
tests/unit/test_study_provenance.py checks the manifest against the rule below.
rule |
detail |
|---|---|
which modules count |
every |
which imports count |
every import in a file, including an import inside a function and a relative import. The test reads imports with |
what is excluded |
the shared framework under |
older gaps |
|
gaps from a result-neutral refactor |
the table with the |
The test names both routes when it finds a new gap. Choose the route by the kind of change.
change |
action |
|---|---|
result-determining |
Add the module to the |
result-neutral, for example a helper moved into a new module |
Declare the study slug and the module path in |
A gap leaves KNOWN_GAPS in one of two ways. Regenerate the study, or record the hash of a file
that git shows unchanged since the run and declare it in the same ledger. A declared gap leaves
the ledger when you regenerate the study.
Set publication_policy="reporting" when a red scientific result is part of the declared
evidence. This policy writes failed statistical verdicts. It does not permit an unrecorded missing
replication, invalid schemas, non-finite estimates, or incomplete provenance. Existing studies use
publication_policy="gated" unless they declare otherwise.
Published studies retain replicates.csv.gz, property-replicates.csv.gz, summary.csv,
performance-tests.csv, equivalence.csv, properties.csv, and a provenance- and hash-complete
manifest.json. A run with a recorded comparator failure also retains failures.csv and
failure-accounting.csv. A run with a recorded property failure also retains
property-failures.csv, property-failure-accounting.csv, and property-failure-witnesses.csv.
Run a disposable smoke study first, then the declared study without permitting
failed replications. The red cells section states what may change after a result.
A regeneration owns its declared core budget. Do not run the Python and R full-core phases concurrently. Do not run the fast tests
beside a study regeneration. Documentation quotes measured values through
tests/studies/evidence/claims.py so tests can check them against the artifacts.
Every evidence row states what it does not cover, including relevant outcome and treatment types, missingness, weights, clusters, fold repeats, learner class, truncation, interval type, and unsupported estimands. Validate changes with the complete fast suite and every registered study whose evaluated path a result-determining change can affect. Record each selected study and its regeneration command in the pull request.
Comparator runs pending#
A method merges when its Python studies pass. The comparator study does not block the merge or a dependent row, and a later change registers it. This is the owner’s rule, and it applies Theory and comparators.
step |
what to do |
|---|---|
register |
Set |
regenerate |
|
manifest |
|
document |
The grid’s canonical-implementation cell names the pending comparator. That cell, the paired cell and the study section carry the label “Python property evidence, with comparator runs pending”. The paired cell reads 0/0 |
add the comparator |
Move the name from |
tests/unit/test_method_evidence.py asserts the pending state for every registered study through
manifest.comparator_findings. It is a check, not a skip. A pending study that publishes a
comparator row or a paired row fails, because an empty paired table would otherwise read as passed.
tests/unit/test_comparator_pending.py holds the controls for each part.
To rerun the property families against the committed Python rows, pass --reuse-subject. The
driver offers the flag only when no reference process runs. It checks the committed rows against
their manifest hash and the declared budget, and then it fits no subject model.
Red cells#
Diagnose every red cell before you route it. The table gives the route for each diagnosis.
diagnosis |
route |
|---|---|
a genuine problem: an algorithm defect, an inconsistent estimator, or a test or design bug that makes the cell measure the wrong thing |
fix the code or the test. Then re-run under a fresh declaration commit that states what changed and why. This is allowed after a verdict |
no defect, such as a finite-sample limit |
publish the cell red under |
Never game a test. A change whose effect is to make a test less meaningful or easier to pass is refused, before or after a verdict. Examples are moving a margin, adding replications to cross a bound, weakening the law, dropping or relabelling a cell, and substituting an oracle.
What makes a study stale#
A study’s artifacts are evidence about one run. The manifest.json records three groups of
hashes, and each group answers a different question.
hash group |
what it covers |
what the fast tier does |
what a difference means |
|---|---|---|---|
|
every published artifact |
refuses every difference |
an artifact differs from the bytes its manifest records |
|
Dockerfiles, R runners, shared R harnesses |
refuses an undeclared difference |
the comparator’s source moved after the run |
|
the Python modules the record names |
checks that the list is complete, and gates no hash |
a Python source moved after the run |
Ask one question of any change to a hashed source. Does it change what the study computes?
A result-determining change makes the affected evidence stale. Regenerate that evidence under the testing strategy. Laws, margins, cells, seeds, learners, estimator arguments, the published schema, and package pins are all result-determining.
A result-neutral change does not. A rename, a comment, a type annotation, an extracted helper, and a script moved from an image into a mount are all result-neutral. A regeneration would spend hours to write identical bytes.
No tool separates the two, so each hash group takes its own position.
The published artifacts are the evidence itself. A difference from their recorded hashes fails. An approved regeneration records the hashes of its new artifacts. A targeted record also retains every baseline hash.
Python module hashes are not gated. Selective regeneration keeps the artifacts honest, and a hash gate
would mean regenerating twenty studies for a docstring. Refactor the shared machinery in
tests/studies/evidence/ freely. Regenerate only the studies whose results the change moves. A
wrong result-neutral judgment appears as a changed artifact at the next required regeneration.
Reference sources sit between the two. They are gated, because a pinned container is the one input
a reader cannot re-derive from this repository. Declare a result-neutral difference in
tests/canonical/provenance-revisions.md.
Do not rewrite a recorded hash. The manifest keeps the hash of the bytes that ran. A rewritten hash makes the manifest claim that bytes which did not exist produced the result.
Targeted regeneration record#
A targeted rerun combines inherited replications with new fits under one unchanged study design. Its manifest must identify both sources. It cannot describe every row as a new execution.
record |
required contents |
|---|---|
baseline |
immutable artifact commit, original manifest and its hash, artifact hashes, source identification, runtime, and original clean-state flag |
selection |
declared reason, complete affected replication keys, and old and new seeds for a seed repair |
replacement fits |
source commit, clean-state flag, result-determining module hashes, runtime, command, and fit failures |
inherited evidence |
preserved files and rows, their baseline attribution, and exact comparison results |
combined analysis |
complete replication counts, analysis source and runtime, unchanged design, recomputed summaries, bootstrap intervals, controls, and verdicts |
final artifacts |
hashes of every data artifact and replacement record; the completed run log records the manifest hash |
Preserve the original manifest metadata, including an original dirty-worktree flag. Record replacement and analysis provenance separately. Do not overwrite historical module hashes with current hashes. The record must expose a runtime difference even when the published verdict stays unchanged.
Derive the selection before fitting. Validate the complete combined key set and all dependent comparisons. Require an independent review of the reuse boundary and final artifacts. If verification fails, investigate the difference or use a complete rerun. Verify the committed run log against the completed execution record.
A committed notebook follows the same rule under a different name. scripts/execute_notebook.py
stamps it, and tests/notebooks.py splits the stamp in two.
stamp half |
holds |
behaves like |
|---|---|---|
gated |
the notebook’s code cells and stored outputs |
the published artifacts. Any difference fails |
recorded |
the package source, the dependency lock, and the generator |
the module hashes. Nothing is asserted equal |
The recorded half needs no declared exception, because no edit to your tree can fail it. Carry it forward when you restamp a notebook you did not re-execute. Recomputing it would name a checkout that never produced those outputs, which is the error the paragraph above describes.
Carry a value forward only while the digest that produced it is unchanged. A carried value that
predates a change to tests/notebooks.py matches no checkout at all, because no commit ever
computed it that way. Re-execute the notebook instead when a digest definition moves.
The recorded half names its generator file set. generator_files lists the files the generator
digest folded, and the fast tier compares that list against the current GENERATOR_MODULES. A
file-set change therefore fails with an instruction. A semantic digest change still needs a
schema-version change and review.
cleverly_commit and cleverly_version place the run in repository history. A digest reports
that two trees differ and never reports where. Read cleverly_worktree_clean before you interpret
the commit. A notebook runs before the commit that lands it, so the recorded commit is the parent.
A false value means that the commit does not reconstruct the executed tree.
Adding a method row#
A row is not written, it is earned by registering a study. The machinery in
tests/studies/evidence/ is method-agnostic, so a new method supplies only what is genuinely its
own and inherits every gate, every negative control and the checks over the validation grid.
Follow the rest of this guide rather than designing new margins, fold-matching rules,
artifacts, or provenance conventions for each comparison.
Declare it. Add a
StudyRecordtotests/studies/evidence/registry.py’s register: the name the validation grid’s first cell must carry, the artefact directory, the document and section anchor, the scenarios and their estimands, the replication count and sample size, and aMargins. TheMarginscarries every acceptance margin, declared before the run rather than chosen after it.Write only the method-specific half. A law to sample from with an exact parameter oracle, a fit function, and a reference implementation pinned by digest where one exists. Seeds come from
replicate_seedapplied to this study’s own record, so a replication is a fixed sample whatever the study’s size and a short probe redraws the published one. Reusing another study’s ready-made sampler inherits its seed silently while publishing your own, which a test now refuses: two registered studies may share a law but not a set of datasets.Say what a failure would look like. Property cells are only evidence if a claim can fail: pair each positive cell with a control that must fail the same instrument in the opposite direction, and pair any rejection-rate cell with a power cell so an inert test cannot pass.
Quote nothing by hand. Numbers in the section go in its measured-values table by quantity name; the gate resolves each against the artefacts and checks it at the precision printed. The per-test tables are generated between sentinel comments by
python -m tests.studies.evidence.document, so the section carries three empty<!-- generated: ... -->blocks and nothing typed into them.Name every key in plain English. Add each new scenario, estimand, property family and cell to
tests/studies/evidence/descriptions.py. A key the module does not describe fails the gate, so a study cannot publish a row whose meaning a reader cannot recover.Declare the blind spots. The limitations cell is the column the grid exists for. A composition the study does not reach is a separate row, not an implied one. Cross-fitting, weights, clustering, missing outcomes, and a flexible learner are each such a composition.
If a registered study has no canonical comparator, its cross-implementation cell says so and its row rests on the other two columns. A comparison is acceptance evidence for the claims it covers. A comparator-backed study starts that cell with “comparator-backed”.
The grid lists a study that declares property cells or a reference. A comparator-backed study with no property cells reads
“0/0 cells; the row rests on the comparison and the truth tests” in its theory-properties cell.
Each such study declares comparison and, where one equivalence row differs, comparison_overrides
with the key scenario:estimand or scenario:*. The kind is one of three: matching implementation,
different estimator, or related target. The agreement block of the page prints the kind of each row.
A study with no property cells and no reference can still register as a reporting study. It
declares publication_policy="reporting" and an empty property_cells. It keeps its own page,
with no properties block, and the red-cell ledger reads its verdicts. The validation grid does not
list it. tests/unit/test_method_evidence.py checks each of these facts. The learned-rule boundary
study is the example.