Space group: a screw zone's evidence no longer hangs on its largest absence
A screw's predicted-absent class is one axial row - half a dozen to a few dozen reflections - and its evidence is a SUM over them, so it is decided by its largest member. The file's own LIMIT comment said so; 7n2s is that limit firing on real data. Between two scaling passes that differed only in which weak frames were rejected, one of eight dead 0k0 moved from 14 +- 9 to 99 +- 10 while the other seven did not move at all, and the zone fell from 30.1 nats to 17.1 and lost the 2(1) under a bound of 20. That reflection was never measured to the precision its sigma claimed: its two half-set merges read 198 and 2.5. The zone's sum is now taken with its single largest member dropped and rescaled for the trim - divided by n - H_n, the expected sum of the other n-1 under the null, and multiplied back by n. ScrewZoneEvidence reads the result exactly as before: same statistic, same floor, same bound, same calibration, with a robust estimate of the zone's deadness in place of a fragile one. One member only, whatever the zone holds: a zone with two strong absences is a zone that is not extinct. On a uniformly dead zone the rescale under-states by 2.2 nats at eight absences and 3.9 at sixty-four - it only ever refuses, never claims. Glide zones keep the untrimmed sum: a plane holds hundreds to thousands of reflections and no single one can carry the verdict. 7n2s -> P 1 21 1 (zone 27.6 nats, set by the seven reflections that did not move), matching its deposit; a second monoclinic crystal decided six nats under the bound (7 absent, 1 violation, 13.9 nats) reaches 21.9 and its 2(1) as well. Unchanged on 7mzt, 7k1l, 11if, 9hs7, 9zlo and four in-house reference sets. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013nW6FNRP1bBJJ8pfHiByAT
This commit is contained in:
@@ -499,6 +499,37 @@ TEST_CASE("SearchSpaceGroup does not promote triclinic data on a pseudo-orthorho
|
||||
CHECK(result.point_group_order == 1);
|
||||
}
|
||||
|
||||
// A screw zone is a handful of axial reflections and its evidence is a SUM over them, so it is
|
||||
// decided by its largest member. Measured on a monoclinic crystal whose eight absent 0k0 are dead in
|
||||
// every run: between two scaling passes that differed only in which weak frames were rejected, ONE
|
||||
// of the eight moved from 14 +- 9 to 99 +- 10 (its two half-set merges reading 198 and 2.5, so it
|
||||
// was never measured to the precision its sigma claimed) while the other seven did not move at all -
|
||||
// and the zone went from 30.1 nats to 17.1 and lost the 2(1) under a bound of 20. Trimming the
|
||||
// largest member and rescaling for the trim makes the two passes agree.
|
||||
TEST_CASE("A screw zone's evidence does not hang on its largest absence", "[SearchSpaceGroup]") {
|
||||
// Seven reflections at a hundredth of their row, and one that moved between the two passes.
|
||||
const double dead_seven = 7 * 0.01;
|
||||
const double before = ScrewZoneEvidence(TrimmedZoneSum(dead_seven + 0.03, 0.03, 8), 8);
|
||||
const double after = ScrewZoneEvidence(TrimmedZoneSum(dead_seven + 0.39, 0.39, 8), 8);
|
||||
|
||||
CHECK(before == Catch::Approx(after).margin(0.01)); // the same seven reflections, the same verdict
|
||||
CHECK(after > 20.0); // and the screw survives the move
|
||||
// Untrimmed, that one reflection is the whole difference and it crosses the bound.
|
||||
CHECK(ScrewZoneEvidence(dead_seven + 0.03, 8) > 20.0);
|
||||
CHECK(ScrewZoneEvidence(dead_seven + 0.39, 8) < 20.0);
|
||||
|
||||
// Only ONE member is trimmed, whatever the zone holds: a zone with two strong absences is not a
|
||||
// zone with a bad reflection in it, it is a zone that is not extinct.
|
||||
CHECK(ScrewZoneEvidence(TrimmedZoneSum(dead_seven + 0.39 + 0.39, 0.39, 8), 8) < 20.0);
|
||||
|
||||
// On a uniform zone the rescale under-states rather than over-states - the safe direction.
|
||||
CHECK(ScrewZoneEvidence(TrimmedZoneSum(8 * 0.01, 0.01, 8), 8) <
|
||||
ScrewZoneEvidence(8 * 0.01, 8));
|
||||
// A class that merged non-positive throughout is unchanged: sum and max are both zero, so the
|
||||
// floor in ScrewZoneEvidence is what answers, exactly as before.
|
||||
CHECK(TrimmedZoneSum(0.0, 0.0, 8) == 0.0);
|
||||
}
|
||||
|
||||
// A zone whose predicted absences were never measurable must not outscore a zone that is genuinely
|
||||
// dead. sum_u is a sum of max(0, E^2)/row_mean, so it is EXACTLY zero when every absent reflection in
|
||||
// the zone merged non-positive - and the Beta tail then diverges, worth ~690 nats per reflection. That
|
||||
|
||||
Reference in New Issue
Block a user