From e7af8b0df55488a2475103824ffa6a24147b894f Mon Sep 17 00:00:00 2001 From: Filip Leonarski Date: Sun, 27 Sep 2026 20:36:24 +0200 Subject: [PATCH] rugnux: a pass re-run for a starved background ring stops after integration The canonical pass at the widened integration radius (and the walk's write pass) is thrown away when the integrator finds more than BKG_STARVED_MAX_FRACTION of the reflections without a background ring, and RunAllPasses re-runs it at the fixed radius. The pass knew this at integration but still scaled, merged, searched the space group and relabelled before marking itself superseded. Now RunAllPasses flags the two passes the starvation check can replace (rerun_if_starved_), and such a pass returns at the same point a probe pass does. Readers audited: the geometry walk reads only post_refine and validation_evidence (both in hand there); RunAllPasses reads only bkg_starved_fraction_ (set at integration) before replacing pass2; the space group the search leaves on experiment_ is reset at the top of every RunPipeline; the speculative walk probe is started before the exit point. The supercell-guard and header-geometry re-runs are not flagged, so they merge as before. Skipped when a _process.h5 writer is open. Measured on 5 starved open-arm sets (8agq, 8sqq, 9h0q, 6moj, 7qij, no --model) against rc173 and the r4-integration binary: p.hkl, p.mtz, p_P1.mtz, p_unmerged.mtz and p.cif byte-identical on all five, and on myob (not starved). Integration-to-re-run-decision time summed over the five: 82.8 s -> 19.0 s (8sqq 21.2 -> 3.9 s, 6moj 19.6 -> 5.3 s); wall totals were dominated by GPU-lock contention on a shared box. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_01D1G8gJVAy6gp1K5Dz3NE5C --- rugnux/Rugnux.cpp | 23 +++++++++++++++++++++++ rugnux/Rugnux.h | 5 +++++ 2 files changed, 28 insertions(+) diff --git a/rugnux/Rugnux.cpp b/rugnux/Rugnux.cpp index c8119f214..6fcb5a959 100644 --- a/rugnux/Rugnux.cpp +++ b/rugnux/Rugnux.cpp @@ -2665,7 +2665,9 @@ ProcessResult Rugnux::RunAllPasses(RugnuxObserver *observer) { logger.Info("Rotation two-pass geometry post-refinement: second pass (refined geometry, canonical) -> {}_*", base_prefix); speculate_geometry_probe_ = true; // a walk that follows starts with an indexing probe + rerun_if_starved_ = true; auto pass2 = RunPipeline(observer, /*write_output=*/true, /*geometry_prepass=*/false); + rerun_if_starved_ = false; speculate_geometry_probe_ = false; // Walk the geometry to the fit's fixed point. The post-refinement is fitted to reflections @@ -2860,7 +2862,9 @@ ProcessResult Rugnux::RunAllPasses(RugnuxObserver *observer) { "the last one that lowered the realised residual", best_round, best_geometry[2]); set_geometry(best_geometry); } + rerun_if_starved_ = true; pass2 = RunPipeline(observer, /*write_output=*/true, /*geometry_prepass=*/false); + rerun_if_starved_ = false; ++walk_passes; } @@ -3184,6 +3188,7 @@ void Rugnux::StartSpeculativeGeometryProbe(const std::array &geometry) run->indexing_probe_only_ = true; run->postrefine_probe_ = false; run->postrefine_probe_only_ = false; + run->rerun_if_starved_ = false; auto sp = std::make_shared(); sp->run = run; // A probe that fails leaves no memo, and the walk's own probe then runs it again and meets the @@ -6674,6 +6679,24 @@ ProcessResult Rugnux::RunPipeline(RugnuxObserver *observer, bool write_output, b return result; } } + // A pass RunAllPasses re-runs at the fixed radius for a starved background ring: its merge, + // search and files are all replaced by the re-run's, and what anything reads from it - the + // post-refinement and validation evidence above, and bkg_starved_fraction_ - is in hand. The + // pass after it starts from the group the run was asked for (top of RunPipeline), so the + // space group this pass would have determined is not carried either. Not with a _process.h5 + // writer open: that file is written whole by every pass that opens it. + if (rerun_if_starved_ && !writer && !cancelled_ && bragg_adaptive_ && bkg_starved_fraction_ + && *bkg_starved_fraction_ > spot_width::BKG_STARVED_MAX_FRACTION) { + if (census_future.valid()) + census_future.wait(); + result.processing_time_s = std::chrono::duration( + std::chrono::steady_clock::now() - start_time).count(); + logger.Info("Starved pass: {} images integrated in {:.2f} s - {:.2f}% of the reflections " + "lost their background ring, so this pass is re-run at the fixed radius and " + "does not merge", result.images_processed, result.processing_time_s, + 100.0 * *bkg_starved_fraction_); + return result; + } if (is_rotation) { if (experiment_.GetRefineRotationWedgeInScaling() || rot_ss.GetRotationWedgeForScaling().has_value()) diff --git a/rugnux/Rugnux.h b/rugnux/Rugnux.h index bf5017a8c..714dba77c 100644 --- a/rugnux/Rugnux.h +++ b/rugnux/Rugnux.h @@ -715,6 +715,11 @@ class Rugnux { // Everything below that point - the merges, the space-group search, the correction surfaces, the // reports - is work no comparison looks at, and on a long sweep it is four fifths of the pass. bool postrefine_probe_only_ = false; + // Whether RunAllPasses re-runs this pass at the fixed integration radius when its background + // ring is starved (bkg_starved_fraction_ over the bound). Such a pass learns it at integration, + // and what is read from it afterwards - the post-refinement and the validation evidence the + // geometry walk decides on - is in hand at the same point as on a probe pass, so it stops there. + bool rerun_if_starved_ = false; // The demoted arm of an ANGLE promotion (see RunAllPasses): the first pass takes the indexer's // free (triclinic) refinement in place of a monoclinic or orthorhombic class. bool lattice_angles_demoted_ = false;