From d35e8f680f078f6f314e531037fa969ec3c8b2aa Mon Sep 17 00:00:00 2001 From: Filip Leonarski Date: Sun, 30 Aug 2026 16:17:09 +0200 Subject: [PATCH] symmetry: say when the cell metric hosts more symmetry than the group adopted Every symmetry under-call in the corpus has the same signature: a lattice whose metric carries rotations the adopted group does not, with nothing in the run saying so. The user is left to notice that a P1 answer sits on a cell whose axes are equal and whose angles are 60 degrees. So a run that determines its own group now compares the two and says what it sees. It decides nothing - no threshold, no promotion, no demotion, no reprocessing - and the message says as much, because a pseudo-symmetric metric is ordinary and only the intensities can settle whether the extra rotations are real. Measured over 83 crystals it fires on 19 and covers 6 of the 7 point-group under-calls; on the crystal whose deposited group is F 4 3 2 and which rugnux reports as P 1, the line reads "the cell metric is cubic - it admits 24 rotations where P 1 has 1". The metric symmetry is Le Page's, taken through GEMMI's implementation, at an obliquity of 1 degree - the middle of the band over which the answer is stable. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01EFEJG6WBQv8th4UJFNe53N --- docs/ACKNOWLEDGEMENT.md | 7 +++++++ docs/CHANGELOG.md | 1 + rugnux/Rugnux.cpp | 40 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 48 insertions(+) diff --git a/docs/ACKNOWLEDGEMENT.md b/docs/ACKNOWLEDGEMENT.md index 949590f4a..77e01e0dd 100644 --- a/docs/ACKNOWLEDGEMENT.md +++ b/docs/ACKNOWLEDGEMENT.md @@ -149,6 +149,13 @@ structure-factor machinery, and MTZ / XDS_ASCII I/O. Vendored in `gemmi_gph/`, s licence obligation. M. Wojdyr, "GEMMI: A library for structural biology" (2022), J. Open Source Softw. 7, 4200 [doi:10.21105/joss.04200](https://doi.org/10.21105/joss.04200). +**Le Page's metric-symmetry search** - the obliquity of each of the 81 candidate two-folds of a +reduced cell, which is what tells a run that its lattice metric hosts more rotational symmetry than +the group its intensities supported. Used through GEMMI's implementation of it. Y. Le Page, "The +derivation of the axes of the conventional unit cell from the dimensions of the Buerger-reduced +cell" (1982), J. Appl. Cryst. 15, 255-259 +[doi:10.1107/S0021889882011959](https://doi.org/10.1107/S0021889882011959). + **Hexagonal-ice ring positions** — the eleven ring $d$ spacings from 3.895 to 1.522 Å that the ice-ring score, the ice-ring flagging and the ice calibrant are all built on are taken from the measurements of, not enumerated from a cell. D. W. Moreau, H. Atakisi and R. E. Thorne, "Ice in diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index eba08924c..65be44792 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -9,6 +9,7 @@ * `rugnux` writes the unmerged MTZ `_unmerged.mtz` by default; `--no-export-unmerged` skips it. * Every rotation run that determines its own space group also writes `_P1.mtz`, the same observations merged in P1, so a wrong space group can be re-merged, re-solved or re-refined without processing the images again; `--no-p1-crosscheck` declines it, and a run given `-S` writes nothing because its space group's centring absences were never integrated. * The space-group search names the setting the data support, so a screw or a 2-fold on the a or c axis is reported as such instead of costing the crystal its space group. +* A run whose cell metric admits more rotational symmetry than the space group it adopted says so, naming both, so a symmetry the intensities were too weak to confirm is visible rather than silent. * The run report carries `SPACE_GROUP_NAME` beside `SPACE_GROUP_NUMBER`, since the number alone does not say which axes a group's symmetry lies on. * `jfjoch_viewer` opens PILATUS miniCBF sweeps - naming any frame opens the whole sweep - and can run a processing job on one. * A miniCBF sweep takes its rotation axis from the goniometer angles the header states, instead of assuming the axis every such file was previously assumed to have. diff --git a/rugnux/Rugnux.cpp b/rugnux/Rugnux.cpp index a772114d5..48c3f069e 100644 --- a/rugnux/Rugnux.cpp +++ b/rugnux/Rugnux.cpp @@ -38,6 +38,7 @@ #include "../image_analysis/geom_refinement/GeometryRefiner.h" #include "../image_analysis/indexing/IndexerThreadPool.h" #include "../image_analysis/spot_finding/ImageSpotFinderCPU.h" +#include "gemmi/twin.hpp" #include "../image_analysis/azint/AzIntEngineCPU.h" #include "../image_analysis/image_preprocessing/ImagePreprocessorCPU.h" #include "../image_analysis/image_preprocessing/ImagePreprocessorBuffer.h" @@ -370,6 +371,44 @@ namespace { // construction, all five of its searches agree, and the small sigma they report says nothing is // wrong. Only the background estimator recovers that case, and only a human reading this line knows // to look. +// The lattice METRIC can carry more rotational symmetry than the group the intensities supported. +// That is not by itself a mistake - a pseudo-symmetric metric is ordinary, and only the intensities +// can say whether the extra rotations are real - but it is where the corpus's symmetry under-calls +// live, so the run says it out loud rather than leaving the user to notice. It decides nothing: no +// threshold, no promotion, no demotion. +// +// The metric symmetry is Le Page's, and its obliquity is the continuous measure of how nearly a +// two-fold holds; 1 degree is the middle of the band over which the answer is stable. +// Following Le Page (1982) J. Appl. Cryst. 15, 255-259 +static void WarnIfMetricHostsMoreSymmetry(const std::optional &cell, + const gemmi::SpaceGroup &sg) { + if (!cell) + return; + Logger logger("Rugnux"); + const gemmi::UnitCell gc(cell->a, cell->b, cell->c, cell->alpha, cell->beta, cell->gamma); + const size_t metric_rotations = + gemmi::find_lattice_symmetry(gc, sg.centring_type(), 1.0).sym_ops.size(); + const size_t group_rotations = sg.operations().sym_ops.size(); + if (metric_rotations <= group_rotations) + return; + // The proper rotations of each holohedry, which is what find_lattice_symmetry returns. + const char *metric_system = "a higher"; + switch (metric_rotations) { + case 2: metric_system = "monoclinic"; break; + case 4: metric_system = "orthorhombic"; break; + case 6: metric_system = "trigonal"; break; + case 8: metric_system = "tetragonal"; break; + case 12: metric_system = "hexagonal"; break; + case 24: metric_system = "cubic"; break; + default: break; + } + logger.Warning("The cell metric is {} - it admits {} rotations where {} has {}. The intensities " + "did not support the extra ones, so they are either absent or too weak to see; " + "this is a note, not a correction. Where the higher symmetry is real, merging in " + "it would raise multiplicity and completeness.", + metric_system, metric_rotations, sg.xhm(), group_rotations); +} + static void LogHeaderBeamCenter(const DiffractionExperiment &experiment, const PixelMask &pixel_mask) { Logger logger("Rugnux"); const float beam_x = experiment.GetBeamX_pxl(); @@ -3652,6 +3691,7 @@ ProcessResult Rugnux::RunPipeline(RugnuxObserver *observer, bool write_output, b end_msg.space_group_name = sg.xhm(); end_msg.space_group_number = static_cast(sg.number); result.space_group = sg; + WarnIfMetricHostsMoreSymmetry(result.consensus_cell, sg); phase("Re-scaling in space group " + sg.short_name()); sm = scale_and_merge(sg.short_name(), false); }