diff --git a/rugnux/RugnuxScaleMerge.cpp b/rugnux/RugnuxScaleMerge.cpp index e6396538f..76f190707 100644 --- a/rugnux/RugnuxScaleMerge.cpp +++ b/rugnux/RugnuxScaleMerge.cpp @@ -2642,6 +2642,23 @@ bool Rugnux::ScaleMergeAndSymmetry(PipelineLocals &p) { // The metric only generates the candidate; the intensity CC gate below makes the decision // (a false pseudo-symmetry is rejected by the correlation). const auto cand = LatticeSearch(*end_msg.rotation_lattice); + // The candidate comes from the Niggli character table, which names a class on relative + // tolerances (3 deg). Le Page measures each two-fold's obliquity on the lattice itself, at + // the bound every other metric question of the run is put at (LATTICE_MAX_OBLIQUITY_DEG). + // Where it finds fewer rotations than the candidate's holohedry the lattice does not host + // that class, and what the intensities could confirm is a pseudo-symmetry: measured on a + // triclinic crystal whose C-monoclinic two-fold is 1.7 deg oblique (the angle XDS refines + // as well), C2 was adopted at operator CC 0.46 and written on a cell the group cannot + // describe. So it is not asked here; the metric re-ask below asks Le Page's own class. + const auto metric = LePageLattice(*end_msg.rotation_lattice); + const bool metric_hosts = metric + && static_cast(metric->n_operators) >= HolohedryRotations(cand.system); + if (!metric_hosts && cand.system != gemmi::CrystalSystem::Triclinic) + logger.Info("Centred-lattice test: the cell's character names a {} lattice, but some of its two-folds " + "are more than {:.1f} deg oblique (Le Page finds {} rotation(s)), so the lattice " + "does not host it and it is not offered to the intensities", + gemmi::crystal_system_str(cand.system), LATTICE_MAX_OBLIQUITY_DEG, + metric ? metric->n_operators : 0); // Exact integer reindex from the indexed setting straight to the candidate conventional // setting: P[i][j] = conv_real[i] . rot_reciprocal[j]. Derived from the two lattices, so it // holds even if the refined indexed cell is not exactly LatticeSearch's Niggli cell (whose @@ -2657,7 +2674,7 @@ bool Rugnux::ScaleMergeAndSymmetry(PipelineLocals &p) { reindex.a[i][j] = std::round(v); reindex_res = std::max(reindex_res, std::fabs(v - reindex.a[i][j])); } - if (cand.system != gemmi::CrystalSystem::Triclinic && reindex_res < 0.1) { + if (metric_hosts && cand.system != gemmi::CrystalSystem::Triclinic && reindex_res < 0.1) { std::vector merged_c = sm.merged; for (auto &m : merged_c) reindex_hkl(m, reindex); diff --git a/tests/LePageLatticeTest.cpp b/tests/LePageLatticeTest.cpp index 4027b86f1..f479f8046 100644 --- a/tests/LePageLatticeTest.cpp +++ b/tests/LePageLatticeTest.cpp @@ -6,6 +6,7 @@ #include "../common/Coord.h" #include "../common/UnitCell.h" #include "../image_analysis/lattice_search/LePageLattice.h" +#include "../image_analysis/lattice_search/LatticeSearch.h" #include "gemmi/symmetry.hpp" #include #include @@ -207,3 +208,24 @@ TEST_CASE("LePageLattice - a filtered group may not rest on an operator the filt }); CHECK(!r.has_value()); } + +TEST_CASE("LePageLattice - a two-fold the character table admits but the metric does not host") { + // A C-centred monoclinic description whose alpha is 1.7 deg off 90: the Niggli character table, + // on its relative tolerances, still names the class, while the two-fold is more oblique than + // Le Page's bound, so the lattice does not host it. The centred-lattice test in the rotation merge + // asks the intensities about a class only where both agree. A lattice 0.3 deg off is hosted. + const CrystalLattice oblique = CrystalLattice(120, 100, 70, 88.3, 115, 90).ToPrimitive('C'); + const auto named = LatticeSearch(oblique); + CHECK(named.system == gemmi::CrystalSystem::Monoclinic); + CHECK(named.centering == 'C'); + const auto metric = LePageLattice(oblique); + REQUIRE(metric.has_value()); + CHECK(metric->n_operators == 1); + CHECK(metric->system == gemmi::CrystalSystem::Triclinic); + + const auto hosted = LePageLattice(CrystalLattice(120, 100, 70, 89.7, 115, 90).ToPrimitive('C')); + REQUIRE(hosted.has_value()); + CHECK(hosted->n_operators == 2); + CHECK(hosted->system == gemmi::CrystalSystem::Monoclinic); + CHECK(hosted->centering == 'C'); +}