From a0518abe62539c4f1e55bfcec99c6bee3bd6e79f Mon Sep 17 00:00:00 2001 From: Filip Leonarski Date: Fri, 25 Sep 2026 21:32:14 +0200 Subject: [PATCH] Rugnux: warn about further lattices only where they are domains of the crystal MULTIPLE_LATTICES and EXTRA_LATTICE_INTENSITY_PCT now count DOMAIN, TWIN_DOMAIN and SEGMENTED lattices only. A FOREIGN lattice (an unrelated cell) is still listed but no longer warned about: it is as often a wrong main lattice or tNCS as a second crystal (one of the four such sets in the battery census is a tNCS crystal). On the census this names 25 of 200 rotation sets instead of 29. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_01D1G8gJVAy6gp1K5Dz3NE5C --- docs/CHANGELOG.md | 2 +- docs/RUGNUX_REPORT.md | 4 +++- rugnux/ResultReport.cpp | 26 ++++++++++++++------------ tests/ResultReportTest.cpp | 9 +++++++++ 4 files changed, 27 insertions(+), 14 deletions(-) diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 07efaa35b..765506a73 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -23,7 +23,7 @@ * Rugnux puts the merge onto a reference MTZ's axes before inheriting its free-R flags, and does not inherit them where the merge does not match the reference (`REFERENCE_MISMATCH`); `REFERENCE_OPERATOR=`, `REFERENCE_CC=` and `REFERENCE_MATCHED_FRACTION=` report the match. * Rugnux places a space group given with `-S` on the axes its systematic absences name, so `-S 18` on a cell whose pure two-fold is not its longest axis no longer merges with the screw absences on the wrong rows. * `rugnux -z` also takes a PDB structure-factor mmCIF (`-sf.cif`, gzipped or not) as the reference, with its R-free set; `--reference` is a new name for `--reference-mtz`. -* Rugnux reports further lattices found in the spots the main lattice leaves over on rotation data - split-crystal and twin domains, a crystal that changes along the sweep, a related or an unrelated cell (`EXTRA_LATTICE_*` keys, a `Multiple lattices` summary line) - and warns (`MULTIPLE_LATTICES`) where they hold at least 10 % of the spot intensity; processing is unchanged. +* Rugnux reports further lattices found in the spots the main lattice leaves over on rotation data - split-crystal and twin domains, a crystal that changes along the sweep, a related or an unrelated cell (`EXTRA_LATTICE_*` keys, a `Multiple lattices` summary line) - and warns (`MULTIPLE_LATTICES`) where domains of the crystal hold at least 10 % of the spot intensity; processing is unchanged. * Rugnux reports the mosaicity the merge used (`MOSAICITY_DEG=` with `MOSAICITY_DEG_P10=`/`_P90=` over the frames, and a summary line), as XDS's sigma_M (`REFLECTING_RANGE_E.S.D.`). * `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 42cf2b0f8..c12490e4f 100644 --- a/docs/RUGNUX_REPORT.md +++ b/docs/RUGNUX_REPORT.md @@ -484,7 +484,9 @@ lattices listed. For each, numbered from 1 in the order found: - **`EXTRA_LATTICE__SWEEP_CONCENTRATION=`** — the share of its spots in the 2 densest of 8 blocks of the sweep; 0.25 is uniform. -**`EXTRA_LATTICE_INTENSITY_PCT=`** sums the intensity share of every listed lattice except `RELATED_CELL`, +**`EXTRA_LATTICE_INTENSITY_PCT=`** sums the intensity share of the domains (`DOMAIN`, `TWIN_DOMAIN`, `SEGMENTED`); +a `RELATED_CELL` or `FOREIGN` lattice is listed but not counted, since it is as often a wrong main lattice or tNCS +as a second crystal, and **`MULTIPLE_LATTICES=`** is `TRUE`, with a warning under the `MULTIPLE_LATTICES` flag, where that is at least 10 %. It is information, not a failure: a split crystal whose second domain is half as strong as the main one can still give data as good as XDS's, since the domains' reflections are only a problem diff --git a/rugnux/ResultReport.cpp b/rugnux/ResultReport.cpp index b026f160e..ce28d6439 100644 --- a/rugnux/ResultReport.cpp +++ b/rugnux/ResultReport.cpp @@ -59,20 +59,22 @@ namespace { return sc.i_over_sigma >= 0.5 && sc.rock_pct >= 2.0 && sc.rock_pct > 3.0 * sc.rock_se_pct; } - // A misoriented domain, a twin domain, a segment of the sweep or an unrelated crystal that together - // hold at least this share of the non-ice spot intensity is worth the user's look. A related cell - // is a question about the crystal's own cell and is left to the supercell keys. On 200 battery - // rotation sets this names 29: 25 with split-crystal or twin domains and 4 with an unrelated cell. + // Domains of the crystal itself - misoriented, turned by 180 deg, or holding a segment of the sweep - + // that together hold at least this share of the non-ice spot intensity are worth the user's look. A + // related cell is a question about the crystal's own cell (left to the supercell keys), and an + // unrelated cell is as often a wrong main lattice or tNCS as a second crystal: both are listed, not + // warned about. On 200 battery rotation sets this names 25. constexpr double MULTIPLE_LATTICES_INTENSITY_PCT = 10.0; - bool CountsAsSecondCrystal(const LeftoverLattice &l) { - return l.kind != LeftoverLatticeKind::Self && l.kind != LeftoverLatticeKind::RelatedCell; + bool CountsAsDomain(const LeftoverLattice &l) { + return l.kind == LeftoverLatticeKind::Domain || l.kind == LeftoverLatticeKind::TwinDomain + || l.kind == LeftoverLatticeKind::Segmented; } - double SecondCrystalIntensityPct(const LeftoverLatticeCensus &c) { + double DomainIntensityPct(const LeftoverLatticeCensus &c) { double pct = 0.0; for (const auto &l : c.lattices) - if (CountsAsSecondCrystal(l)) + if (CountsAsDomain(l)) pct += l.intensity_pct; return pct; } @@ -1200,17 +1202,17 @@ ReportDocument BuildReportDocument(const std::string &output_prefix, Add(s, KeyReal(key("SWEEP_CONCENTRATION").c_str(), l.sweep_concentration, "{:.2f}")); Add(s, KeyReal(key("Z").c_str(), l.z, "{:.1f}")); } - const double second = SecondCrystalIntensityPct(c); + const double second = DomainIntensityPct(c); const bool multiple = second >= MULTIPLE_LATTICES_INTENSITY_PCT; Add(s, KeyReal("EXTRA_LATTICE_INTENSITY_PCT", second, "{:.1f}")); Add(s, KeyBool("MULTIPLE_LATTICES", multiple)); if (multiple) { const LeftoverLattice *strongest = nullptr; for (const auto &l : c.lattices) - if (CountsAsSecondCrystal(l) && (!strongest || l.intensity_pct > strongest->intensity_pct)) + if (CountsAsDomain(l) && (!strongest || l.intensity_pct > strongest->intensity_pct)) strongest = &l; Warn(doc, PathologyCode::MULTIPLE_LATTICES, - fmt::format("Further lattices carry {:.0f}% of the spot intensity against {:.0f}% on the " + fmt::format("Further domains of the crystal carry {:.0f}% of the spot intensity against {:.0f}% on the " "main one (the strongest: {}); only the main lattice is integrated. This does " "not by itself make the data worse - check the crystal, and the result", second, c.primary_intensity_pct, LeftoverLatticeLine(*strongest))); @@ -2121,7 +2123,7 @@ ReportDocument BuildReportDocument(const std::string &output_prefix, c.lattices.size()) : "", LeftoverLatticeLine(*strongest), strongest->intensity_pct, c.primary_intensity_pct, - SecondCrystalIntensityPct(c) >= MULTIPLE_LATTICES_INTENSITY_PCT + DomainIntensityPct(c) >= MULTIPLE_LATTICES_INTENSITY_PCT ? " - check the crystal" : "")); } if (merged) { diff --git a/tests/ResultReportTest.cpp b/tests/ResultReportTest.cpp index 45e32b488..ad0ccc899 100644 --- a/tests/ResultReportTest.cpp +++ b/tests/ResultReportTest.cpp @@ -924,6 +924,15 @@ TEST_CASE("ResultReport_LeftoverLattices", "[Diagnostics]") { CHECK(text.find("EXTRA_LATTICE_1_MISORIENTATION_DEG") == std::string::npos); CHECK(text.find("MULTIPLE_LATTICES= FALSE") != std::string::npos); + // An unrelated cell is listed but not warned about: it is as often a wrong main lattice or tNCS. + LeftoverLattice foreign{.kind = LeftoverLatticeKind::Foreign, .cell = *result.consensus_cell, + .volume_ratio = 1.34, .misorientation_deg = NAN, .z = 40.0, + .spots_pct = 20.0, .intensity_pct = 30.0, .sweep_concentration = 0.3}; + result.leftover_lattices->lattices = {foreign}; + text = RenderResultReport("p", "in.h5", x, result); + CHECK(text.find("EXTRA_LATTICE_1_KIND= FOREIGN") != std::string::npos); + CHECK(text.find("MULTIPLE_LATTICES= FALSE") != std::string::npos); + LeftoverLattice domain{.kind = LeftoverLatticeKind::Domain, .cell = *result.consensus_cell, .volume_ratio = 1.0, .misorientation_deg = 2.7, .z = 250.0, .spots_pct = 20.0, .intensity_pct = 25.0, .sweep_concentration = 0.3};