From 8874a788e68054c4445694f424eca00a2c6af459 Mon Sep 17 00:00:00 2001 From: Filip Leonarski Date: Wed, 12 Aug 2026 16:55:40 +0200 Subject: [PATCH] Two-pass: pass 2 reuses the space group the centred-lattice test found, like any other The second pass is meant to re-integrate at the refined geometry, not to re-decide the symmetry: it reuses pass 1's space group so a borderline determination cannot flip between the two passes. A group that came from the intensity-based centred-lattice test was excluded from that, on the grounds that its conventional setting is not the frame the de-novo indexer returns - but the primitive->conventional reindex that handles exactly that case, and the centring check that refuses a pass whose lattice the group cannot describe, were both already in place. The exclusion only switched them off, and with them the reuse itself, leaving pass 2 free to search again. On a crystal whose C-centring is pseudo-symmetric to 2.4% of axis-length equality - against the 3% tolerance the Bravais classification allows - that re-search is a coin toss. Pass 1 confirmed the centring from the intensities and pass 2, a quarter of a millimetre of refined detector distance away, classified the same lattice as triclinic and merged the run in P1, doubling the asymmetric unit. With the group carried over, pass 2's own lattice is reindexed into it where the metric allows and the pass is dropped where it does not, which is what happens for every non-reindexed group already. Only a run whose pass-1 group came from that test can change; every other run takes the identical branch. --- rugnux/Rugnux.cpp | 30 ++++++++++++++---------------- rugnux/Rugnux.h | 5 ----- 2 files changed, 14 insertions(+), 21 deletions(-) diff --git a/rugnux/Rugnux.cpp b/rugnux/Rugnux.cpp index 42290125..611626bb 100644 --- a/rugnux/Rugnux.cpp +++ b/rugnux/Rugnux.cpp @@ -346,7 +346,6 @@ ProcessResult Rugnux::Run(RugnuxObserver *observer) { prepass_rotation_scale_.reset(); prepass_result_.reset(); force_rotation_result_.reset(); - prepass_sg_reindexed_ = false; prepass_merge_sg_.reset(); prepass_promoted_point_group_ = false; @@ -388,10 +387,18 @@ ProcessResult Rugnux::Run(RugnuxObserver *observer) { // symmetry safeguards recover the true cell - reusing pass-1's group in the indexer forces build_sr // onto a wrong / doubled cell (a huge oblique cell collapses; a pseudo-centred cell doubles). Pass-1's // group is instead reinstated for the MERGE ONLY (RunPipeline, before RotationScaleMerge), so the - // symmetry stays fixed with no re-search / no flip - EXCEPT a reindex-derived group, whose conventional - // setting the de-novo primitive frame does not share, which is left to re-search. + // symmetry stays fixed with no re-search / no flip. + // + // This holds for a group the centred-lattice test determined too, even though its conventional + // setting is not the one the de-novo primitive frame comes back in. That is precisely what the + // primitive->conventional reindex below does, and when the reindex declines - pass 2's metric is + // not the group's - the centring check right after it stops the pass being adopted at all. Letting + // such a group be re-searched instead put the second pass's determination back in play at a + // slightly different geometry, which on a lattice whose centring is pseudo-symmetric to a couple of + // tenths of a percent is a coin toss: measured on a C-centred monoclinic crystal, pass 1 confirmed + // the centring and pass 2 dropped it, and the run merged in P1. experiment_.SpaceGroupNumber(std::nullopt); - prepass_merge_sg_ = prepass_sg_reindexed_ ? std::nullopt : pass1.space_group_number; + prepass_merge_sg_ = pass1.space_group_number; prepass_promoted_point_group_ = pass1.twinning.laue_class_was_chosen_by_promotion; logger.Info("Rotation two-pass geometry post-refinement: second pass (refined geometry, canonical) -> {}_*", @@ -1652,10 +1659,6 @@ ProcessResult Rugnux::RunPipeline(RugnuxObserver *observer, bool write_output, b } std::ostringstream stats_text; - // Set when the space group was determined via a change of basis (the intensity-based centred-lattice - // test): its conventional setting does not match the indexer's primitive frame, so the two-pass must - // NOT reuse it - the second pass re-searches (and re-runs the same test) instead. - bool sg_reindexed = false; // True when the point group below was chosen BY THE SEARCH (rather than given by the user) and is // above triclinic - i.e. a promotion was made, which is what makes a later "the Laue class is // holohedral so there is no twin law" conclusion circular. @@ -1865,7 +1868,6 @@ ProcessResult Rugnux::RunPipeline(RugnuxObserver *observer, bool write_output, b // centred setting: bring the integrated reflections + cell into that setting and re-ingest // the rotation merge, so the final scaling/merging folds the equivalents correctly. if (commit_reindex) { - sg_reindexed = true; for (auto &io : indexer->GetIntegrationOutcome()) { io.latt = io.latt.Multiply(*commit_reindex); for (auto &r : io.reflections) @@ -1900,13 +1902,9 @@ ProcessResult Rugnux::RunPipeline(RugnuxObserver *observer, bool write_output, b // second pass REUSES it instead of re-searching at the slightly changed geometry (which could flip a // borderline determination and mix the two passes). The lattice is deliberately NOT forced - the second // pass re-indexes at the refined geometry so the CELL comes out self-consistent with it (forcing the - // pre-pass lattice would pin the nominal cell and undo the geometry refinement). EXCEPTION: a - // reindex-derived space group (the centred-lattice test) is in a conventional setting the indexer's - // primitive frame does not share, so reusing it would break the second pass's indexing - remember it so - // the caller clears it and lets the second pass re-search. Unlike a throwaway pre-pass, this one now - // CONTINUES to the write below so its own (header-geometry) result is saved as the first pass. - if (geometry_prepass) - prepass_sg_reindexed_ = sg_reindexed; + // pre-pass lattice would pin the nominal cell and undo the geometry refinement). Unlike a throwaway + // pre-pass, this one now CONTINUES to the write below so its own (header-geometry) result is saved + // as the first pass. // Reference-based indexing-ambiguity resolution (rotation). When a reference MTZ is supplied and // the crystal has an indexing ambiguity (merohedral: lattice symmetry higher than the Laue group), diff --git a/rugnux/Rugnux.h b/rugnux/Rugnux.h index dfba2bce..a2d2e543 100644 --- a/rugnux/Rugnux.h +++ b/rugnux/Rugnux.h @@ -235,11 +235,6 @@ class Rugnux { // (RunPipeline calls indexer->ForceRotationIndexerResult when this is set); reset right after that re-run. std::optional force_rotation_result_; - // Two-pass geometry pre-pass: whether the first pass's space group came from the centred-lattice reindex - // test (a conventional setting the indexer's primitive frame does not share), so Run() clears it and lets - // the second pass re-search rather than reuse it. - bool prepass_sg_reindexed_ = false; - // Two-pass second pass: pass-1's space group, reinstated for the MERGE ONLY (RotationScaleMerge) after the // indexer has run de novo. The indexer runs with no fixed group so its pseudo-symmetry safeguards recover // the true cell (reusing the group in the indexer forces build_sr onto a wrong / doubled cell); the merge