ResolutionCutoff: a bin's CC1/2 is the median over groups of its reflections
The automatic cut fitted its logistic to one Pearson CC1/2 per bin, which has no bound on what one pair can do to it. In a weak shell a single reflection with a huge intensity in both halves - an artefact the halves reproduce, or one observation with a sigma fifty times the shell's - carries nearly all of the covariance. Each bin's reflections are now dealt into up to seven groups by their indices (groups of at least ten reflections), and the bin reads the median of the groups' CC1/2. On a clean shell every group estimates the same CC1/2, so the median is the Pearson value; an outlier takes one group with it and the median does not follow. Only the cut decision changes; the reported shell table is unchanged. Found on a weak split myoglobin crystal: one reflection carried 50-99 % of the covariance in most fit bins past 2.6 A (e.g. 43647 in a bin with median I 1.3), reading them at CC1/2 0.93-0.98 where the median of groups reads 0.2. The cut swung between 1.21 and 2.03 A with --prepass-fraction; it is now 1.85 A at 0.5 and 1.89 A at 1 (XDS 1.51 A, rc.174 1.96 A). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SVmAWnzCmRKAXVUCdc4iNi
This commit is contained in:
@@ -316,6 +316,7 @@ namespace {
|
||||
std::normal_distribution<double> g(0.0, sigma);
|
||||
for (int j = 0; j < n; ++j) {
|
||||
MergedReflection m;
|
||||
m.h = static_cast<int32_t>(v.size()); // every reflection its own index
|
||||
m.d = static_cast<float>(1.0 / std::sqrt(s_from + (j + 0.5) * (s_to - s_from) / n));
|
||||
const double a = g(rng), b = g(rng);
|
||||
m.I_half[0] = static_cast<float>(a);
|
||||
@@ -345,6 +346,29 @@ TEST_CASE("ResolutionCutoff_CleanFallOff") {
|
||||
CHECK(*rc.d_cut == Catch::Approx(1.92).margin(0.15));
|
||||
}
|
||||
|
||||
// Past the fall-off, a few single reflections read hundreds of times the shell's noise in both halves -
|
||||
// an artefact the half-sets reproduce. Each carries nearly all of its shell's covariance, so a Pearson
|
||||
// CC1/2 reads those shells as signal and the cut runs on through them; one reflection per shell must not
|
||||
// decide where the signal stops.
|
||||
TEST_CASE("ResolutionCutoff_SingleReflectionsDoNotHoldTheCutOpen") {
|
||||
Logger logger("test");
|
||||
std::mt19937 rng(12345);
|
||||
std::vector<MergedReflection> merged;
|
||||
AddBand(merged, rng, 0.01, 0.25, 480, true);
|
||||
AddBand(merged, rng, 0.25, 0.51, 520, false);
|
||||
for (const double s : {0.26, 0.28, 0.30, 0.32}) {
|
||||
MergedReflection m;
|
||||
m.h = static_cast<int32_t>(merged.size());
|
||||
m.d = static_cast<float>(1.0 / std::sqrt(s));
|
||||
m.I_half[0] = m.I_half[1] = 300.0f;
|
||||
merged.push_back(m);
|
||||
}
|
||||
|
||||
const auto rc = ComputeCCHalfLogisticCutoff(merged, 0.30, logger);
|
||||
REQUIRE(rc.d_fit);
|
||||
CHECK(*rc.d_fit == Catch::Approx(2.0).margin(0.15));
|
||||
}
|
||||
|
||||
// A sweep with a long stretch where the crystal barely diffracted: the reflections measured only there
|
||||
// are noise scaled up by 1/G, sigmas with them, and there are many of them at every resolution. Counted
|
||||
// by the information they carry they must not hide where the well-measured reflections stop.
|
||||
|
||||
Reference in New Issue
Block a user