From 1355d6b2aaf0b3e655a397e485db7b288a111e99 Mon Sep 17 00:00:00 2001 From: Filip Leonarski Date: Thu, 30 Jul 2026 10:52:32 +0200 Subject: [PATCH] Space-group search: a screw threshold needs a row with controls behind it The row-relative cut scales the "too strong to be absent" threshold by the axial row's own median E^2, floored at the plain value - so it can only raise the bar, and a row whose control class holds a single strong reflection sets it from that one reflection. That direction invents screws: a genuine 4_2 whose 00l happen to be observed only at l=4n reads its l=4n+2 reflections as absent and ranks 4_1/4_3 above the truth. Require three controls before the row may set the scale; below that the row keeps the plain cut. Co-Authored-By: Claude Opus 5 (1M context) --- image_analysis/scale_merge/SearchSpaceGroup.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/image_analysis/scale_merge/SearchSpaceGroup.cpp b/image_analysis/scale_merge/SearchSpaceGroup.cpp index a355c281..4871cd5c 100644 --- a/image_analysis/scale_merge/SearchSpaceGroup.cpp +++ b/image_analysis/scale_merge/SearchSpaceGroup.cpp @@ -732,9 +732,16 @@ SearchSpaceGroupResult SearchSpaceGroup( // control rows, and on the same crystal one row can be 20x an average reflection while another // is half of one. Pooling them lets the weak rows set the threshold for the strong one and the // rescue never fires (that very crystal pooled to a row median of 0.7 and stayed at P42_12). + // A row needs more than a reflection or two behind its median before that median may set the + // threshold. The scale only ever RAISES the cut, so a row whose control class holds one strong + // reflection would license a screw claim the row does not support - a genuine 4_2 whose 00l are + // observed only at l=4n, strongly, would read its l=4n+2 reflections as absent and be reported as + // 4_1/4_3. Below this count the row falls back to the plain cut, i.e. no rescue. + constexpr size_t MIN_ROW_CONTROL_REFLECTIONS = 3; std::map row_median; for (auto& [row, esq] : row_present_esq) - row_median[row] = MedianOf(esq); + if (esq.size() >= MIN_ROW_CONTROL_REFLECTIONS) + row_median[row] = MedianOf(esq); const int screw_absent = static_cast(screw_absent_refl.size()); int screw_violations = 0;