From ce2d2280fd6fdd002d8cb9c970217a2cd7f05e63 Mon Sep 17 00:00:00 2001 From: Filip Leonarski Date: Tue, 3 Nov 2026 11:37:55 +0100 Subject: [PATCH] Per-frame scale choice: an error model counts as resolved when the bootstrap bounds ISa The choice between the partials-scaled and fulls-alone per-frame scale read an error model as resolved only when the bin fit's b was two standard errors clear of zero. The bin fit reads a b too small for its 16 bins to measure exactly as it reads one that failed, so a merge that got better - a sweep whose dead second half had been removed, its ISa bounded on both sides by the bootstrap over the reflections - was taken for a failed scale, and the fulls-alone arm, with nothing resolved either, won by default. Resolved is now either test saying so. On the rc181 battery logs this flips no arm decision; it only skips running the fulls arm where partials were chosen anyway. One private set moves to the fulls-alone arm, now resolved by the bootstrap (ISa up, merging statistics equal). Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01UqXYdL4qBGYRiSXSGXfCLu --- image_analysis/scale_merge/RotationScaleMerge.cpp | 13 ++++++++++--- image_analysis/scale_merge/RotationScaleMerge.h | 3 ++- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/image_analysis/scale_merge/RotationScaleMerge.cpp b/image_analysis/scale_merge/RotationScaleMerge.cpp index 2e5d588c3..e3a838a2e 100644 --- a/image_analysis/scale_merge/RotationScaleMerge.cpp +++ b/image_analysis/scale_merge/RotationScaleMerge.cpp @@ -6407,8 +6407,9 @@ RotationScaleMerge::Result RotationScaleMerge::MergeAndStats(int n_groups, bool // What the report prints of ISa: the interval of b^2 by a bootstrap over the reflections of the // pool the reported fit was handed. The two-SE test above reads its error off how far the 16 bins // miss the two-term line, which does not shrink with more data, so on a full sweep it can refuse a - // b that every resampling of the reflections reproduces. Report-only - nothing below reads it - - // and only where there is a fit to resample: b measured, a off its clamp, the final statistics. + // b that every resampling of the reflections reproduces. The report prints it, and the choice of + // the per-frame scale (ErrorModelResolved) reads it; only where there is a fit to resample: b + // measured, a off its clamp, the final statistics. sub("isa bootstrap"); if (full_stats && error_model_active && chi2_pool && !error_model_b_unmeasured && !error_model_a_at_bound) { // 50 replicates of at most 100k samples each, one per thread: 0.2-0.5 s a merge on 8 threads. @@ -7123,8 +7124,14 @@ namespace { // rocking events per frame the partials' scale read ISa 10.5 against 8.7 from the fulls alone, and // SHELXL R1 0.105 against 0.062 - the partiality-model error the partial scale takes up is shared by // symmetry mates measured at the same rocking geometry, so their agreement cannot see it. + // + // Resolved is either test saying so: the bin fit's b two standard errors clear of zero, or the + // bootstrap over the reflections bounding ISa from above. The bin fit alone reads a b too small for + // its 16 bins to measure exactly as it reads one that failed, so a merge that got BETTER - a sweep + // whose dead second half had been removed, its ISa bounded on both sides by the bootstrap - was + // taken for a failed scale, and the fulls-alone arm, with nothing resolved either, won by default. bool ErrorModelResolved(const RotationScaleMerge::Result &r) { - return r.isa_resolved && std::isfinite(r.isa) && r.isa > 0.0; + return (r.isa_resolved || std::isfinite(r.isa_high)) && std::isfinite(r.isa) && r.isa > 0.0; } bool PartialScalingWins(const RotationScaleMerge::Result &with, const RotationScaleMerge::Result &without, diff --git a/image_analysis/scale_merge/RotationScaleMerge.h b/image_analysis/scale_merge/RotationScaleMerge.h index b85a98c6c..48d65db27 100644 --- a/image_analysis/scale_merge/RotationScaleMerge.h +++ b/image_analysis/scale_merge/RotationScaleMerge.h @@ -114,7 +114,8 @@ public: bool isa_resolved = true; // The 95% interval of `isa` by a bootstrap over the reflections, which is what the report prints; // isa_high is infinite where b's interval reaches zero (ISa >= isa_low), both NaN where there was - // no fit to resample. Report-only. + // no fit to resample. A finite isa_high also counts as resolved where the per-frame scale is chosen + // (ErrorModelResolved). double isa_low = std::numeric_limits::quiet_NaN(); double isa_high = std::numeric_limits::quiet_NaN(); double error_model_a = 0.0; // XDS convention: sigma^2 = a*(sigma0^2 + b*I^2)