From 6c9205a6701bfffe85c860998f2b470ffeadae94 Mon Sep 17 00:00:00 2001 From: Filip Leonarski Date: Fri, 25 Sep 2026 08:57:28 +0200 Subject: [PATCH] Report the strongest index-2 superstructure class and the cell it doubles to The report-only supercell probe now reaches the results report: SUPERCELL_CLASS (parity of the primitive indices), its occupancy and Bragg-like (rocking) part against the lattice's own reflections, its , and SUPERCELL_DOUBLED_CELL, the Niggli-reduced cell to give with -C. No decision is taken on it: correct cells whose half-integer class is diffuse, and cells whose depositor kept the sub-cell, cannot be told from a real doubling by the data alone. On a crystal with a real doubled axis the reported cell matched the deposited one, and processing on it with -C brought R_free against the deposited model from 0.60 to 0.29. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_01D1G8gJVAy6gp1K5Dz3NE5C --- docs/CHANGELOG.md | 1 + docs/RUGNUX_REPORT.md | 19 +++++++++++++++++++ image_analysis/IndexAndRefine.cpp | 7 +++++++ image_analysis/IndexAndRefine.h | 4 ++++ rugnux/ResultReport.cpp | 26 ++++++++++++++++++++++++++ rugnux/Rugnux.cpp | 29 +++++++++++++++++++++++++++++ rugnux/Rugnux.h | 10 ++++++++++ 7 files changed, 96 insertions(+) diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 1ad3e1b3e..c88ec9e35 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -9,6 +9,7 @@ * Rugnux accepts a detector-modulation or absorption correction surface when held-out data support it on Fisher's z, and fits them in the order modulation, time, goniometer frame; this recovers corrections that were wrongly refused. * Rugnux settles a disagreement between the file's and the measured beam centre over an axis harmonic in either direction, so a file centre off along the spindle no longer leaves the run on a doubled axis. * Rugnux commits a post-refined geometry only when the held-out residual falls by more than its own noise, instead of by a fixed 2 %. +* Rugnux reports the strongest index-2 superstructure class of a rotation run (`SUPERCELL_CLASS` and related keys), with the doubled cell to give with `-C`; it does not change the lattice. * Rugnux also reports `R_MEAS_WEIGHTED`, the R_meas with each observation weighted as the merge weights it, so weak frames kept in the merge at low weight can be told from real disagreement; `R_MEAS` is unchanged. * Rugnux no longer stops per-frame scaling as unsettled because of a single frame that is about to be dropped as blank. * `rugnux --polarization` is documented as the polarization degree (XDS `FRACTION_OF_POLARIZATION` = (1 + p)/2); the default 0.99 suits undulators. diff --git a/docs/RUGNUX_REPORT.md b/docs/RUGNUX_REPORT.md index 4d09807a1..ad966ea99 100644 --- a/docs/RUGNUX_REPORT.md +++ b/docs/RUGNUX_REPORT.md @@ -415,6 +415,25 @@ Rings are detected far more often than they are excluded: exclusion happens only found no usable lattice. The measurement is described in [CPU/GPU data analysis ▸ Resolution and ice-ring handling](CPU_DATA_ANALYSIS_IMAGE.md#33-resolution-and-ice-ring-handling). +## Index-2 superstructure + +On rotation data, section 4 reports whether the lattice the run adopted has intensity at half-integer +positions it does not index. On 60 frames spread over the sweep, after each frame's own integration, the +lattice is predicted doubled along all three primitive axes and integrated to 3 Å; the reflections split +into eight parity classes of h, k and l, of which `0 0 0` is the lattice itself and each of the other +seven is one index-2 superstructure. Nothing is decided on it: whether a superstructure belongs in the +cell is as much the depositor's call as the data's, and several crystals whose accepted cell is the +sub-cell carry one. + +**`SUPERCELL_CLASS=`** is the parity class with the most intensity over 20–3 Å. +**`SUPERCELL_OCCUPANCY_PCT=`** is its mean intensity against the lattice's own reflections on the same +frames, and **`SUPERCELL_ROCK_PCT=`** (± **`SUPERCELL_ROCK_SE_PCT=`**) the part of it that follows the +partiality the way a Bragg reflection does, from a fit I = a + b p over the class, on the same scale. +**`SUPERCELL_I_OVER_SIGMA=`** is its mean I/σ. A class near zero on both is empty. One that is occupied +and rocks is a superstructure whose reflections this run did not integrate; **`SUPERCELL_DOUBLED_CELL=`** +is the Niggli-reduced cell the lattice would double to, to give with `-C` to process on it. One that is +occupied but hardly rocks is diffuse or disordered intensity rather than Bragg reflections. + ## Translational pseudo-symmetry Two copies of the contents of the asymmetric unit related by a pure translation that is not a lattice diff --git a/image_analysis/IndexAndRefine.cpp b/image_analysis/IndexAndRefine.cpp index f4fca96c5..8dea3f1c9 100644 --- a/image_analysis/IndexAndRefine.cpp +++ b/image_analysis/IndexAndRefine.cpp @@ -640,6 +640,7 @@ void IndexAndRefine::ProbeSupercell(std::vector image_numbers) { std::sort(image_numbers.begin(), image_numbers.end()); supercell_probe_frames_ = std::move(image_numbers); supercell_probe_ = {}; + supercell_probe_primitive_.reset(); } SupercellProbe IndexAndRefine::GetSupercellProbe() { @@ -647,6 +648,11 @@ SupercellProbe IndexAndRefine::GetSupercellProbe() { return supercell_probe_; } +std::optional IndexAndRefine::GetSupercellProbePrimitive() { + const std::unique_lock ul(supercell_probe_mutex_); + return supercell_probe_primitive_; +} + void IndexAndRefine::ProbeSupercellFrame(const DataMessage &msg, BraggPrediction &prediction, const BraggIntegrateFn &integrate, const IndexingOutcome &outcome, const BraggPredictionSettings &settings) { @@ -684,6 +690,7 @@ void IndexAndRefine::ProbeSupercellFrame(const DataMessage &msg, BraggPrediction for (int i = 0; i < 8; i++) for (int j = 0; j < 2; j++) supercell_probe_[i][j] += frame[i][j]; + supercell_probe_primitive_ = prim; } std::optional diff --git a/image_analysis/IndexAndRefine.h b/image_analysis/IndexAndRefine.h index 7d5537fe0..f3b30055c 100644 --- a/image_analysis/IndexAndRefine.h +++ b/image_analysis/IndexAndRefine.h @@ -123,6 +123,7 @@ class IndexAndRefine { // Supercell probe: the image numbers it reads, and what it has read (see ProbeSupercell). std::vector supercell_probe_frames_; SupercellProbe supercell_probe_{}; + std::optional supercell_probe_primitive_; std::mutex supercell_probe_mutex_; void ProbeSupercellFrame(const DataMessage &msg, BraggPrediction &prediction, const BraggIntegrateFn &integrate, const IndexingOutcome &outcome, @@ -189,6 +190,9 @@ public: // The probe's integrations are the caller's to keep out of anything else it counts. void ProbeSupercell(std::vector image_numbers); [[nodiscard]] SupercellProbe GetSupercellProbe(); + // The primitive lattice the parity classes are indexed in (the last probed frame's; only its metric + // is meant to be read), so a class can be turned into the cell it would double to. + [[nodiscard]] std::optional GetSupercellProbePrimitive(); // Index a single frame (no integration) with the current forced rotation lattice; used to score // first-pass sampling schemes on the real per-image path. Returns whether the frame indexed. bool IndexFrameOnly(DataMessage &msg, const SpotFindingSettings &settings); diff --git a/rugnux/ResultReport.cpp b/rugnux/ResultReport.cpp index 5f485d3de..5ed23090a 100644 --- a/rugnux/ResultReport.cpp +++ b/rugnux/ResultReport.cpp @@ -1075,6 +1075,32 @@ ReportDocument BuildReportDocument(const std::string &output_prefix, } } + // ---- index-2 superstructure + // Report only, like the harmonic above: on 60 frames the probe integrates the lattice doubled + // along all three primitive axes and reads the seven half-integer parity classes. Whether a + // class that is there belongs in the cell is the depositor's call as much as the data's - + // several crystals whose accepted cell is the sub-cell carry one - so no verdict is printed. + if (result.supercell) { + const auto &sc = *result.supercell; + Add(s, KeyText("SUPERCELL_CLASS", fmt::format("{} {} {}", sc.h, sc.k, sc.l))); + Add(s, KeyReal("SUPERCELL_OCCUPANCY_PCT", sc.occupancy_pct, "{:.1f}")); + Add(s, KeyReal("SUPERCELL_ROCK_PCT", sc.rock_pct, "{:.1f}")); + Add(s, KeyReal("SUPERCELL_ROCK_SE_PCT", sc.rock_se_pct, "{:.1f}")); + Add(s, KeyReal("SUPERCELL_I_OVER_SIGMA", sc.i_over_sigma, "{:.2f}")); + Add(s, KeyText("SUPERCELL_DOUBLED_CELL", CellString(sc.doubled_cell))); + Add(s, Blank()); + Add(s, Prose( + " SUPERCELL_CLASS is the half-integer class (parity of h, k, l in the primitive cell of the\n" + " lattice) with the most intensity over 20-3 A, read on 60 frames. OCCUPANCY is its mean\n" + " intensity against the lattice's own reflections; ROCK is the part of it that follows the\n" + " partiality the way a Bragg reflection does, on the same scale. A class near zero on both is\n" + " empty. One that is occupied and rocks is an index-2 superstructure the lattice does not\n" + " index: its reflections are not integrated. To process on the doubled lattice, give\n" + " SUPERCELL_DOUBLED_CELL with -C. An occupied class that does not rock is diffuse or\n" + " disordered intensity rather than Bragg reflections.")); + Add(s, Blank()); + } + // ---- powder contamination // A crystalline phase other than the crystal, diffracting as rings among its reflections. // Measured in the pre-scan on every run, from the spots found there for the width and the diff --git a/rugnux/Rugnux.cpp b/rugnux/Rugnux.cpp index fd6207325..55b6918f4 100644 --- a/rugnux/Rugnux.cpp +++ b/rugnux/Rugnux.cpp @@ -5312,6 +5312,35 @@ ProcessResult Rugnux::RunPipeline(RugnuxObserver *observer, bool write_output, b } logger.Info("Supercell probe (the eight parity classes of the doubled primitive cell; , " "mean intensity, and the fit I = a + b p against the lattice's own):{}", table); + + // The class with the most intensity over 20-3 A goes into the report, with the cell it would + // double the lattice to: the vectors t of the primitive lattice with (h,k,l).t even - twice + // one axis the class is odd on, and each other axis plus that one where the class is odd + // on it too - Niggli-reduced. + const auto band = [&](int c) { SupercellProbeClass x = probe[c][0]; x += probe[c][1]; return x; }; + const SupercellProbeFit f0 = FitSupercellProbe(band(0)); + const auto prim = indexer->GetSupercellProbePrimitive(); + int best = 0; + for (int c = 1; c < 8; c++) + if (best == 0 || FitSupercellProbe(band(c)).mean_i > FitSupercellProbe(band(best)).mean_i) + best = c; + if (prim && f0.mean_i > 0.0 && f0.b != 0.0) { + const SupercellProbeFit f = FitSupercellProbe(band(best)); + const int p[3] = {(best >> 2) & 1, (best >> 1) & 1, best & 1}; + const Coord v[3] = {prim->Vec0(), prim->Vec1(), prim->Vec2()}; + const int i = p[0] ? 0 : p[1] ? 1 : 2; + Coord t[3]; + for (int j = 0; j < 3; j++) + t[j] = j == i ? v[i] * 2.0f : v[j] + v[i] * static_cast(p[j]); + ProcessResult::SupercellClass sc; + sc.h = p[0]; sc.k = p[1]; sc.l = p[2]; + sc.occupancy_pct = 100.0 * f.mean_i / f0.mean_i; + sc.rock_pct = 100.0 * f.b / f0.b; + sc.rock_se_pct = 100.0 * f.b_se / f0.b; + sc.i_over_sigma = f.mean_i_over_sigma; + sc.doubled_cell = CrystalLattice(t[0], t[1], t[2]).NiggliReduce().GetUnitCell(); + result.supercell = sc; + } } } diff --git a/rugnux/Rugnux.h b/rugnux/Rugnux.h index 29e1c315a..77dd4c715 100644 --- a/rugnux/Rugnux.h +++ b/rugnux/Rugnux.h @@ -362,6 +362,16 @@ struct ProcessResult { // Higher-order contamination of the beam, read off the spots the lattice did not take. Measured // on the images themselves, so it is there whether or not anything merged. HarmonicContaminationResult harmonic; + // The strongest index-2 superstructure class the supercell probe read (20-3 A, 60 frames): which + // parity class of the primitive cell, its mean intensity and the part that rocks like a Bragg + // reflection, both against the lattice's own, and the cell the lattice would double to were it + // real. Report only - nothing decides on it. + struct SupercellClass { + int h = 0, k = 0, l = 0; // parity of the primitive indices + double occupancy_pct = 0.0, rock_pct = 0.0, rock_se_pct = 0.0, i_over_sigma = 0.0; + UnitCell doubled_cell{}; + }; + std::optional supercell; // Powder contamination measured in the pre-scan: a crystalline phase other than the crystal, // diffracting as rings among its reflections. Measured and reported on every run. PowderRings powder;