rugnux: gate the P1 space-group-search scaling at <I/sigma> >= 1
Build Packages / build:viewer-tgz:cpu (pull_request) Successful in 7m12s
Build Packages / build:viewer-tgz:cuda (pull_request) Successful in 7m57s
Build Packages / build:rpm (rocky8_nocuda) (pull_request) Successful in 10m10s
Build Packages / build:rpm (ubuntu2204_nocuda) (pull_request) Successful in 9m37s
Build Packages / build:rpm (ubuntu2404_nocuda) (pull_request) Successful in 9m18s
Build Packages / build:rpm (rocky9_nocuda) (pull_request) Successful in 10m45s
Build Packages / build:rpm (rocky8_sls9) (pull_request) Successful in 10m30s
Build Packages / build:windows:nocuda (pull_request) Successful in 14m39s
Build Packages / build:rpm (rocky9_sls9) (pull_request) Successful in 11m12s
Build Packages / build:rpm (ubuntu2204) (pull_request) Successful in 10m19s
Build Packages / build:rpm (rocky8) (pull_request) Successful in 11m10s
Build Packages / build:rpm (rocky9) (pull_request) Successful in 11m36s
Build Packages / build:windows:cuda (pull_request) Successful in 16m59s
Build Packages / build:rpm (ubuntu2404) (pull_request) Successful in 9m49s
Build Packages / Generate python client (pull_request) Successful in 20s
Build Packages / Build documentation (pull_request) Successful in 52s
Build Packages / Create release (pull_request) Skipped
Build Packages / XDS test (durin plugin) (pull_request) Successful in 7m24s
Build Packages / XDS test (neggia plugin) (pull_request) Successful in 6m28s
Build Packages / XDS test (JFJoch plugin) (pull_request) Successful in 7m20s
Build Packages / DIALS test (pull_request) Successful in 12m45s
Build Packages / Unit tests (pull_request) Successful in 1h32m18s
Build Packages / Unit tests (push) Successful in 1h39m29s
Build Packages / build:viewer-tgz:cpu (push) Successful in 6m8s
Build Packages / build:viewer-tgz:cuda (push) Successful in 5m59s
Build Packages / build:rpm (rocky8_nocuda) (push) Successful in 8m31s
Build Packages / build:rpm (rocky9_nocuda) (push) Successful in 9m59s
Build Packages / build:rpm (ubuntu2204_nocuda) (push) Successful in 9m11s
Build Packages / build:rpm (ubuntu2404_nocuda) (push) Successful in 7m52s
Build Packages / build:rpm (rocky8_sls9) (push) Successful in 9m3s
Build Packages / build:rpm (rocky9_sls9) (push) Successful in 10m45s
Build Packages / build:rpm (rocky8) (push) Successful in 9m7s
Build Packages / build:rpm (rocky9) (push) Successful in 10m48s
Build Packages / build:rpm (ubuntu2204) (push) Successful in 9m3s
Build Packages / build:rpm (ubuntu2404) (push) Successful in 9m34s
Build Packages / DIALS test (push) Successful in 11m55s
Build Packages / XDS test (durin plugin) (push) Successful in 8m9s
Build Packages / XDS test (JFJoch plugin) (push) Successful in 8m24s
Build Packages / XDS test (neggia plugin) (push) Successful in 7m52s
Build Packages / Generate python client (push) Successful in 11s
Build Packages / Build documentation (push) Successful in 39s
Build Packages / Create release (push) Skipped
Build Packages / build:windows:nocuda (push) Successful in 15m36s
Build Packages / build:windows:cuda (push) Successful in 17m51s

The de-novo space-group determination (point-group intensity correlation plus
the second-moment / twinning test) ran on the full resolution range, including
noise-dominated high-res shells. There a single artifact - a zinger the box sum
integrates whole, or a weak-reflection profile-fit runaway - has an
astronomical resolution-normalised E (I / <I>_shell with <I>_shell ~ 0) and
wrecks the normalised statistics, so the search collapses to P1.

Re-scale the P1 search pass with a <I/sigma> >= 1 cut over thin resolution
shells (POINTLESS-style: determine symmetry where there is signal). Only this
pass is cut; the final in-symmetry merge keeps the full range, so no output
resolution is lost. A manual --scaling-high-resolution, if coarser, wins.

Battery of 24 rotation crystals: box-sum lysoC_14 P1 -> P41212; and with the
default gaussian integrator three previously mis-assigned crystals now get the
correct space group (cytC_10 155->152, EP_cs_01-24 5->4, Ins_I_3 196->197),
with no space-group regressions.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-10 15:20:10 +02:00
co-authored by Claude Opus 4.8
parent f6eca6c69e
commit 1b6831a2d3
2 changed files with 41 additions and 1 deletions
@@ -65,6 +65,10 @@ public:
// masked_ice_rings: rings (indices into ICE_RING_RES_A) to drop from the final merge; empty = none.
Result Run(bool for_search, const std::vector<char> &masked_ice_rings = {});
// Override the high-resolution cut for the next Run() - used to gate the de-novo P1 search pass at
// <I/sigma> >= 1 without cutting the final in-symmetry merge. Reset to the manual limit afterwards.
void SetDMinLimit(std::optional<double> d_min_A) { d_min_limit = d_min_A; }
private:
// One integrated observation - a per-frame partial during scaling/combine, or a combined full during
// scale-fulls/merge. Flat (not nested per image); a POD so the arrays translate straight to CUDA.
+37 -1
View File
@@ -538,11 +538,47 @@ ProcessResult Rugnux::Run(RugnuxObserver *observer) {
const auto initial_sg = experiment_.GetGemmiSpaceGroup();
auto sm = scale_and_merge(initial_sg ? initial_sg->short_name() : "P1", !initial_sg.has_value());
// For a de-novo search, gate the P1 scaling+merge (the pass that feeds the space-group search) at
// <I/sigma> >= 1 over thin resolution shells: noise-dominated high-res shells otherwise mislead
// the determination, where a single artifact (a zinger the box sum integrates whole, or a
// weak-reflection profile-fit runaway) has an astronomical resolution-normalised E and wrecks the
// intensity-correlation / second-moment statistics. The final in-symmetry merge keeps the full
// range (only this P1 search pass is cut); a manual --scaling-high-resolution, if coarser, wins.
double d_min_search = 0.0;
if (!initial_sg.has_value()) {
std::vector<std::pair<float, float>> rs; // (d, I/sigma) over the full P1 merge
rs.reserve(sm.merged.size());
for (const auto &m : sm.merged)
if (std::isfinite(m.I) && std::isfinite(m.sigma) && m.sigma > 0.0f &&
std::isfinite(m.d) && m.d > 0.0f)
rs.emplace_back(m.d, m.I / m.sigma);
if (rs.size() >= 400) {
std::sort(rs.begin(), rs.end(),
[](const auto &a, const auto &b) { return a.first > b.first; }); // low -> high res
const int bins = std::clamp(static_cast<int>(rs.size() / 100), 1, 40);
const size_t per = (rs.size() + bins - 1) / static_cast<size_t>(bins);
for (size_t b = 0; b * per < rs.size(); ++b) {
const size_t lo = b * per, hi = std::min(rs.size(), lo + per);
double sum = 0.0;
for (size_t j = lo; j < hi; ++j) sum += rs[j].second;
if (sum / static_cast<double>(hi - lo) < 1.0) { d_min_search = rs[lo].first; break; }
}
}
const double d_manual = experiment_.GetScalingSettings().GetHighResolutionLimit_A().value_or(0.0);
if (rsm && d_min_search > d_manual) {
logger.Info("Space-group search: re-scaling the P1 pass at {:.2f} A (<I/sigma> >= 1)", d_min_search);
rsm->SetDMinLimit(d_min_search);
sm = scale_and_merge("P1, space-group search", true);
rsm->SetDMinLimit(d_manual > 0.0 ? std::optional<double>(d_manual) : std::nullopt);
}
}
std::ostringstream stats_text;
if (!experiment_.GetGemmiSpaceGroup().has_value()) {
SearchSpaceGroupOptions sg_opts;
sg_opts.merge_friedel = experiment_.GetScalingSettings().GetMergeFriedel();
sg_opts.d_min_limit_A = experiment_.GetScalingSettings().GetHighResolutionLimit_A().value_or(0.0);
sg_opts.d_min_limit_A = std::max<double>(
d_min_search, experiment_.GetScalingSettings().GetHighResolutionLimit_A().value_or(0.0));
// Constrain the search to subgroups of the lattice (metric) symmetry found by rotation
// indexing. Centering is not constrained here - it is determined from the absences.
if (end_msg.rotation_lattice_type.has_value())