probes/null_adapter: surface degenerate baseline as stats field (F02)
- SHA
edd5a034c331b98eb5a3315f98aee2f9ac80176b- Parents
-
ae445d3 - Tree
e088ad9
edd5a03
edd5a034c331b98eb5a3315f98aee2f9ac80176bae445d3
e088ad9| Status | File | + | - |
|---|---|---|---|
| M |
src/dlm_sway/probes/null_adapter.py
|
16 | 5 |
src/dlm_sway/probes/null_adapter.pymodified@@ -341,14 +341,25 @@ def _calibrate_at_rank( | ||
| 341 | 341 | |
| 342 | 342 | if raws: |
| 343 | 343 | mean = statistics.fmean(raws) |
| 344 | - std = statistics.pstdev(raws) if len(raws) > 1 else 0.0 | |
| 344 | + raw_std = statistics.pstdev(raws) if len(raws) > 1 else 0.0 | |
| 345 | + # F02 (Audit 03) — detect the degenerate case (``runs: 1`` | |
| 346 | + # or every seed producing the *exact* same raw) as a first- | |
| 347 | + # class property of the stats dict. The previous code hid | |
| 348 | + # this via ``max(std, 1e-6)`` which collided with | |
| 349 | + # :data:``_zscore.MIN_STD`` and let the z-score path fire | |
| 350 | + # on a std that had been synthetically lifted from ``0.0`` | |
| 351 | + # — the path that produced the ``+290,766σ`` observation in | |
| 352 | + # the audit. A multi-seed run with genuinely small variance | |
| 353 | + # (e.g. 5e-7 on a low-noise dummy) is NOT degenerate; we | |
| 354 | + # keep the 1e-6 floor for that case so valid-but-tight | |
| 355 | + # calibrations still z-score. ``z_score`` inspects both the | |
| 356 | + # ``degenerate`` flag and the ``std < MIN_STD`` threshold. | |
| 357 | + degenerate = len(raws) <= 1 or raw_std == 0.0 | |
| 345 | 358 | per_kind_stats[kind] = { |
| 346 | 359 | "mean": mean, |
| 347 | - # C9: clamp the std floor so the downstream z-score | |
| 348 | - # path doesn't blow up when every seed produces | |
| 349 | - # identical raws. | |
| 350 | - "std": max(std, 1e-6), | |
| 360 | + "std": max(raw_std, 1e-6), | |
| 351 | 361 | "n": float(len(raws)), |
| 362 | + "degenerate": 1.0 if degenerate else 0.0, | |
| 352 | 363 | } |
| 353 | 364 | per_kind_samples[kind] = raws |
| 354 | 365 | else: |