Global Wilson B: minimum fit window, no upper cap

CalcGlobalWilsonB fits ln<I> against 1/d^2 from 4 A to d_min. With d_min just under 4 A that window is a
sliver, and after the shells past the signal limit are dropped it held 0-2 shells, so the estimate was
NaN (or a fit over almost no range); and any B above 200 A^2 was discarded, which is every crystal
diffracting to 4-7 A.

Now the window is never narrower in 1/d^2 than the 4-3 A window a 3 A data set gets: a narrower one is
widened towards low resolution (d_min 3.15 A -> from 4.39 A, 3.61 A -> 5.96 A, 3.87 A -> 7.40 A; data
coarser than ~4.4 A use everything, as before). The 200 A^2 cap is dropped for the dataset-wide
estimate (kept for the per-image one). The window is logged, printed with the statistics and reported
(WILSON_B_RANGE_A, developer key); the correlation is reported as before. Data with d_min <= 3 A are
unchanged.

Report only: the value feeds the statistics text, the log, the report and the mmCIF
_reflns.B_iso_Wilson_estimate; nothing in scaling, merging or the MTZ reads it.

Measured on the open arm (replica on the a897b08 merged data, confirmed by rugnux runs): 12 sets
change, 7 previously NaN now finite - 5nw5 315, 9z44 493, 6r72 302 A^2 (were above the cap; model mean
B 328/310/114, deposited Wilson 388/322/179), 9yzk 210 (model 383, ctruncate 243, xtriage 104), 7qij 104
(model 194, ctruncate 150), 9yl4 78 (model 146), 8qq7 35 at 4 shells (model 197; its signal ends well
inside the written 3.15 A). 6yqf 132 (corr 0.51) -> 96 (corr 0.69; model 57, ctruncate 145,
xtriage 114); 9rcs 69 -> 148 (model 74, ctruncate 131). At 3-4 A the estimators themselves disagree by
1.5-3x, so these are better-defined numbers, not exact ones. p.hkl and MTZ data identical to the base
(e34a5fb88) on every set compared (ci tier, private arm, the Wilson sets).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-10-11 02:21:32 +02:00
co-authored by Claude Opus 5.5
parent fe181ca042
commit 40fcaa985c
8 changed files with 68 additions and 14 deletions
+30
View File
@@ -4,6 +4,7 @@
#include <catch2/catch_all.hpp>
#include <random>
#include "../image_analysis/bragg_integration/CalcISigma.h"
#include "../image_analysis/scale_merge/ErrorModel.h"
#include "../image_analysis/scale_merge/HKLKey.h"
#include "../image_analysis/scale_merge/Merge.h"
@@ -555,3 +556,32 @@ TEST_CASE("ErrorModel_DeviceFitIsTheHostFit") {
}
}
#endif
// The dataset-wide Wilson B: a pure exp(-B / 2d^2) fall-off, read back over the fit window. A crystal
// diffracting to just under 4 A has almost nothing between its d_min and the 4 A convention, so the
// window is widened to the 1/d^2 width a 3 A dataset gets; and a high B is a measurement, not garbage.
TEST_CASE("Global Wilson B over a widened window, with no upper cap", "[wilson]") {
const auto merge = [](double b, double d_min) {
std::vector<MergedReflection> out;
for (int i = 0; i < 20000; ++i) {
// Uniform in 1/d^3 from 20 A to d_min, as reflections are.
const double s3 = (i + 0.5) / 20000.0 * (1.0 / std::pow(d_min, 3) - 1.0 / 8000.0) + 1.0 / 8000.0;
MergedReflection r;
r.h = i; r.k = 0; r.l = 0;
r.d = static_cast<float>(1.0 / std::cbrt(s3));
r.I = static_cast<float>(1e6 * std::exp(-b / (2.0 * r.d * r.d)));
r.sigma = 0.01f * r.I;
out.push_back(r);
}
return out;
};
const GlobalWilsonB high_res = CalcGlobalWilsonB(merge(40.0, 2.0));
CHECK(high_res.b == Catch::Approx(40.0).epsilon(0.02));
CHECK(high_res.d_low_A == Catch::Approx(4.0));
const GlobalWilsonB low_res = CalcGlobalWilsonB(merge(300.0, 3.9));
CHECK(low_res.b == Catch::Approx(300.0).epsilon(0.02));
CHECK(low_res.n_shells >= 3);
CHECK(1.0 / (3.9 * 3.9) - 1.0 / (low_res.d_low_A * low_res.d_low_A)
== Catch::Approx(1.0 / 9.0 - 1.0 / 16.0).epsilon(0.01));
}