rugnux: keep merohedral twins in true symmetry, robustify twinning stats

SearchSpaceGroup: drop the log10(chi2_ref) widening of the point-group
chi^2-ratio bound and tighten max_merge_chi2_ratio 2.0 -> 1.85. The
variance-floor fix removed the error-model miscalibration the widening
compensated for, so the widening now only let a partial merohedral twin
through (Ins_H_2 R3->R32 twin 2-fold: ratio 2.01). Every genuine high
symmetry across the rotation-test battery stays within ~1.7x (worst real
case Thau P41212 at 1.71), so 1.85 keeps R3 in its true lower symmetry.

TwinningAnalysis: make the <I^2>/<I>^2 second moment robust - skip
noise-only shells (<I/sigma> < 1) and reject Wilson outliers (E^2 > 8)
with one shell-mean re-iteration, so a single strong reflection in a
collapsed-mean shell no longer dominates the moment. Add
MerohedralTwinningPossible: in a holohedral Laue class (4/mmm, 6/mmm,
m-3m, rhombohedral -3m) no twin law exists, so a low <|L|> there is a
statistical artefact and is no longer flagged as twinning.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-12 16:48:27 +02:00
co-authored by Claude Opus 4.8
parent fbebc0d56e
commit 4aab8078d6
4 changed files with 101 additions and 34 deletions
+10 -10
View File
@@ -388,17 +388,17 @@ SearchSpaceGroupResult SearchSpaceGroup(
int best_pg_order = 0;
double best_pg_min_cc = -2.0;
for (const auto& c : pg_cands) {
// Widen the chi^2 ratio bound by the error-model miscalibration. When even the best candidate's
// reduced chi^2 (chi2_ref) is far above 1 (weak data / uncorrected decay), the ratio grows with
// point-group order for genuine high symmetry too, so the fixed bound wrongly rejects it. Adding
// log10(chi2_ref) lets the bound exceed max_merge_chi2_ratio by ~the order of magnitude of the
// miscalibration - enough to keep the true group (F432 weak data: chi2_ref~150, ratio 3.2 vs
// bound ~4.2) while STILL rejecting a false operator whose ratio is far worse than its subgroup's
// (a twin/pseudo R32 on calibrated data: chi2_ref~2.6, ratio 4.2 vs bound ~2.4). The bound stays
// a per-candidate chi^2 test, so chi^2 still arbitrates between candidates - it is never bypassed.
const double allowed_ratio = opt.max_merge_chi2_ratio + std::log10(std::max(1.0, chi2_ref));
// A genuine symmetry operator merges equivalent reflections, so it barely changes the reduced
// chi^2 relative to the best subgroup - across the whole rotation-test battery every correct
// point group stays within ~1.7x, even on weak or badly-integrated data (F432 chi2_ref 8.3 ->
// 1.15; Thau P41212 -> 1.71). A twin law or pseudo-symmetry forces non-equivalent reflections
// together, so its ratio is markedly higher (Ins_H_2's twin 2-fold: R3 3.02 -> R32 6.07, ratio
// 2.01). max_merge_chi2_ratio sits between the two. (An earlier log10(chi2_ref) widening
// compensated for an under-calibrated error model that inflated real-symmetry ratios with data
// weakness; the variance-floor fix removed that inflation, and the widening now only let the
// twin through, so it is gone.)
const bool consistent = c.pg->rotations.empty() || !std::isfinite(c.chi2) ||
!std::isfinite(chi2_ref) || c.chi2 <= chi2_ref * allowed_ratio;
!std::isfinite(chi2_ref) || c.chi2 <= chi2_ref * opt.max_merge_chi2_ratio;
if (!consistent)
continue;
if (c.order > best_pg_order || (c.order == best_pg_order && c.min_class_cc > best_pg_min_cc)) {