Skip to content

Commit dbb29c4

Browse files
agucovaclaude
andcommitted
feat(core): implement v7.1 statistical correctness fixes
Based on statistician review of v7.0 W₁ implementation: 1. Use raw W₁ in inference (not debiased/clamped) - Debiased W₁ is display-only to show effect above noise floor - Using debiased W₁ in likelihood would bias posterior 2. Calibrate θ_floor from null distribution - Bootstrap null W₁ replicates via within-class splits - c_floor = 95th percentile of scaled null W₁ values - Replaces heuristic 1.64 * sqrt(var_rate) formula 3. Rename n_eff → n_blocks in floor calculations - n_blocks = max(1, floor(n / L)) is block count - n_eff = n / τ remains for IACT diagnostics - Clarifies that we're counting blocks, not estimating ESS 4. Prior calibrated to θ_user (not θ_eff) - P(W > θ_user) ≈ 0.62 under half-t prior - Prior encodes threat model, not measurement limits - θ_floor handled separately in decision logic 5. Add κ robustness to likelihood - Student-t likelihood via κ ~ Gamma(ν_ℓ/2, ν_ℓ/2) - Changed ν_ℓ from 8 to 4 (matches prior df) - Guards against variance underestimation 6. Fix tail_slow_share denominator - Conditional on tail (p95+): fraction that are slowdowns - sum(max(d_i - shift, 0)) / sum(|d_i - shift|) for i in tail - Operates on quantile-aligned diffs, not sample identities All 418 tests passing. Updated spec to v7.1 and CHANGELOG.md. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
1 parent 02f6ef8 commit dbb29c4

53 files changed

Lines changed: 12371 additions & 651 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

CHANGELOG.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
99

1010
### Changed
1111

12+
- **Spec v7.1: Statistical correctness refinements**
13+
- **Inference uses raw W₁ (§3.1):** Bayesian inference now uses raw W₁ distance without debiasing or clamping. Debiased W₁ is computed only for display purposes to help users interpret effect magnitude above measurement noise. This prevents bias in the likelihood function.
14+
- **Floor from null distribution (§3.3.3):** Measurement floor constant $c_{\text{floor}}$ is now calibrated from the 95th percentile of null W₁ replicates (via within-class splits) rather than heuristic formulas based on Normal quantiles. Runtime floor: $\theta_{\text{floor}}(n) = \max(\theta_{\text{tick}}, c_{\text{floor}} / \sqrt{n_{\text{blocks}}})$ where $n_{\text{blocks}} = \max(1, \lfloor n / L \rfloor)$.
15+
- **Prior targets user threshold (§3.3.4):** Half-t prior scale $\sigma$ is calibrated so that $P(\delta > \theta_{\text{user}}) = 0.62$, not $P(\delta > \theta_{\text{eff}})$. The prior encodes security requirements, not measurement limitations.
16+
- **Robust likelihood (§3.4.2):** Student-t likelihood degrees of freedom changed from $\nu_{\ell} = 8$ to $\nu_{\ell} = 4$ (matching prior ν = 4) for consistency between prior and likelihood tail behavior.
17+
- **Tail directionality metric (§2.3):** `tail_slow_share` now correctly measures fraction of tail deviation magnitude (p95+) from slowdowns: $\sum_{i \in \text{tail}} \max(d_i - \text{shift}, 0) / \sum_{i \in \text{tail}} |d_i - \text{shift}|$. Operates on quantile-aligned differences.
18+
- **Block count terminology (Appendix A):** Variable $n_{\text{blocks}}$ replaces ambiguous $n_{\text{eff}}$ in floor calculations. Effective sample size $n_{\text{eff}} = n / \hat{\tau}$ remains for IACT-based diagnostics.
19+
- **Migration:** Results may differ slightly from v7.0 due to corrected floor calibration and prior targeting. Test outcomes may be more conservative (fewer false positives). No API changes required.
20+
21+
1222
- **Spec v5.5: Threshold elevation decision rule (§2.1, §2.6, §3.3.4, §3.5.2)**
1323
- Pass now requires θ_eff ≤ θ_user + ε_θ (cannot Pass when threshold is elevated)
1424
- When θ_floor > θ_user and P < pass_threshold, outcome is `Inconclusive(ThresholdElevated)`, not `Pass`

crates/tacet-c/src/lib.rs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -371,8 +371,8 @@ pub unsafe extern "C" fn to_calibrate(
371371
let c_floor = compute_c_floor_1d(var_rate, seed);
372372

373373
// Compute initial theta_floor
374-
let n_eff = (count / block_length).max(1);
375-
let theta_floor_stat = c_floor / (n_eff as f64).sqrt();
374+
let n_blocks = (count / block_length).max(1);
375+
let theta_floor_stat = c_floor / (n_blocks as f64).sqrt();
376376
let theta_tick = ns_per_tick; // 1 tick resolution
377377
let theta_floor_initial = theta_floor_stat.max(theta_tick);
378378
let theta_eff = theta_ns.max(theta_floor_initial);
@@ -948,18 +948,18 @@ pub unsafe extern "C" fn to_analyze(
948948

949949
// Compute theta_eff
950950
let c_floor = compute_c_floor_1d(var_rate, seed);
951-
let n_eff = (count / block_length).max(1);
952-
let theta_floor = (c_floor / (n_eff as f64).sqrt()).max(ns_per_tick);
951+
let n_blocks = (count / block_length).max(1);
952+
let theta_floor = (c_floor / (n_blocks as f64).sqrt()).max(ns_per_tick);
953953
let theta_eff = theta_ns.max(theta_floor);
954954

955955
// Calibrate prior
956956
let sigma_t = calibrate_halft_prior_scale_1d(var_rate, theta_eff, count, seed);
957957

958958
// Scale variance
959-
let var_n = var_rate / n_eff as f64;
959+
let var_n = var_rate / n_blocks as f64;
960960

961961
// Run Bayesian inference
962-
let bayes_result = compute_bayes_1d(w1_obs, var_n, sigma_t, theta_eff, seed);
962+
let bayes_result = compute_bayes_1d(w1_obs, var_n, sigma_t, theta_eff, seed, 4.0);
963963

964964
// Extract max effect from posterior mean (for 1D, this is just the absolute value)
965965
let max_effect_ns = bayes_result.w1_post.abs();
@@ -987,7 +987,7 @@ pub unsafe extern "C" fn to_analyze(
987987
// Build diagnostics from bayes_result (1D version has no lambda/kappa fields)
988988
let diagnostics = ToDiagnostics {
989989
dependence_length: block_length as u64,
990-
effective_sample_size: n_eff as u64,
990+
effective_sample_size: n_blocks as u64,
991991
stationarity_ratio: 1.0,
992992
stationarity_ok: true,
993993
discrete_mode,

0 commit comments

Comments
 (0)