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) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EFEJG6WBQv8th4UJFNe53N
This commit is contained in:
2026-08-31 15:34:16 +02:00
co-authored by Claude Opus 5
parent 71cca91ec1
commit ba24946c0f
2 changed files with 9 additions and 1 deletions
+1
View File
@@ -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
@@ -195,7 +195,14 @@ TwinningAnalysisResult AnalyzeTwinning(const std::vector<MergedReflection>& 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;