From ba24946c0f4f2dbf42553b928d74dea19e135b0c Mon Sep 17 00:00:00 2001 From: Filip Leonarski Date: Mon, 31 Aug 2026 15:34:16 +0200 Subject: [PATCH] twinning: do not report a twin the L-test rules out The two indicators can only move one way under twinning - <|L|> down from 0.500 toward 0.375, the second moment down from 2.0 toward 1.5 - but they were combined with an OR, so a narrow intensity distribution could report a twin on its own while the L-test said the opposite. That is not a marginal disagreement. It fires on small-cell rotation data, where a twin fraction of 0.50 was reported for a crystal whose <|L|> was 0.63: a value no twin can produce, and one that points at the opposite situation - a structure whose intensities behave centric. Suppress the flag when <|L|> sits at or above its untwinned value; the two indicators are otherwise combined exactly as before. Measured over 113 stored reports: two flip, both of them wrong today, and nothing else moves. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01EFEJG6WBQv8th4UJFNe53N --- docs/CHANGELOG.md | 1 + image_analysis/scale_merge/TwinningAnalysis.cpp | 9 ++++++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index b5eda98c1..b181deb0b 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -13,6 +13,7 @@ * The `rugnux` report gives the twinning statistics measured before the space group was decided, beside the ones measured after. * The `rugnux` report gives the strong-direction diffraction limit, and warns when CC1/2 is not monotone with resolution. * `rugnux` ranks screw axes on the evidence their absences carry, rather than on how many control reflections a candidate happens to have. +* Twinning is no longer reported when the L-test contradicts it. * The `jfjoch_viewer` grid scan view draws the cells in the proportion of the scan steps, so the map has the shape of the scanned area. ### 1.0.0-rc.165 diff --git a/image_analysis/scale_merge/TwinningAnalysis.cpp b/image_analysis/scale_merge/TwinningAnalysis.cpp index 56e6fbc4c..26b365e6d 100644 --- a/image_analysis/scale_merge/TwinningAnalysis.cpp +++ b/image_analysis/scale_merge/TwinningAnalysis.cpp @@ -195,7 +195,14 @@ TwinningAnalysisResult AnalyzeTwinning(const std::vector& merg // possible, so a low <|L|> is a statistical artefact (correlated near-neighbours) rather than a // twin, and must not be flagged. result.merohedral_twinning_possible = MerohedralTwinningPossible(space_group); - result.twinning_suspected = result.merohedral_twinning_possible && + // The two indicators can only move one way under twinning: <|L|> down from 0.500 towards 0.375, + // the second moment down from 2.0 towards 1.5. A narrow intensity distribution sitting next to an + // <|L|> at or ABOVE its untwinned value therefore has some other cause, and calling it a twin is + // the one reading the data rule out. It happens on small-cell rotation data, where a twin fraction + // of 0.50 was reported for a crystal whose <|L|> was 0.63 - a value a twin cannot produce, and one + // that points at the opposite situation, a structure whose intensities behave centric. + const bool l_test_contradicts_twin = result.l_test_pairs > 0 && result.mean_abs_l >= 0.50; + result.twinning_suspected = result.merohedral_twinning_possible && !l_test_contradicts_twin && ((result.l_test_pairs > 0 && result.mean_abs_l < 0.44) || (result.moment_reflections > 0 && result.second_moment < 1.85)); return result;