RESULT-CHANGING: the centred-lattice test asks only about a class the lattice metric hosts

The centred-lattice test (run after a triclinic indexing whose search arms
could test no operator) takes its candidate from the Niggli character table,
which names a class on 3 deg relative tolerances. Le Page, which measures each
two-fold's obliquity on the lattice itself at the 1 deg bound every other
metric question of the run uses, can find fewer rotations than that class
carries. The test then adopted a group the lattice does not host: on a
triclinic crystal whose C-monoclinic two-fold is 1.7 deg oblique (XDS refines
the same angle), C2 was adopted at operator CC 0.46 and written on a cell the
group cannot describe, and the Le Page re-ask that follows cannot demote.

The test now runs only where Le Page carries the candidate's holohedry;
otherwise it logs why and leaves the question to the Le Page re-ask. Over the
rc181 open, in-house and private battery rows this was the one final point
group with more rotations than the metric hosts; every other centred-test
adoption is hosted and unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UqXYdL4qBGYRiSXSGXfCLu
This commit is contained in:
leonarski_fandClaude Opus 5.5 committed 2026-11-03 18:48:15 +01:00
1 parent 48d895d715
commit 486c0b06d1
2 files changed
+40 -1

No files matched your search

+18 -1
View File
@@ -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<size_t>(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<MergedReflection> merged_c = sm.merged;
for (auto &m : merged_c)
reindex_hkl(m, reindex);
+22
View File
@@ -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 <array>
#include <cmath>
@@ -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');
}