From d0d1985ed08a08c5ec5e30c4e37c3bf2c0ec3929 Mon Sep 17 00:00:00 2001 From: Filip Leonarski Date: Sat, 26 Sep 2026 21:08:18 +0200 Subject: [PATCH] Rugnux: run the leftover-lattice census beside the image loop The census (report only: what the crystal's lattice leaves over on the scheme and validation frames, up to three further lattices) ran on the critical path of the canonical pass between the first pass and the image loop - ~1.5 s on the GPU build for a set where it finds lattices. Its spots are still found in place; the rest now runs in the background on copies of what it reads (the experiment, those frames' spot lists, the lattice, the validation settings) and is collected into the result before the pass returns. It no longer runs on a pass that only post-refines, whose result nothing reads. md5-identical output and identical report on four sets; GPU 24.1 -> 22.8 s on a set with leftover lattices. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_01D1G8gJVAy6gp1K5Dz3NE5C --- rugnux/Rugnux.cpp | 41 ++++++++++++++++++++++++++++++++--------- 1 file changed, 32 insertions(+), 9 deletions(-) diff --git a/rugnux/Rugnux.cpp b/rugnux/Rugnux.cpp index 81820dddd..bf6c3483f 100644 --- a/rugnux/Rugnux.cpp +++ b/rugnux/Rugnux.cpp @@ -3366,6 +3366,9 @@ ProcessResult Rugnux::RunPipeline(RugnuxObserver *observer, bool write_output, b // Full-analysis shared engines. std::unique_ptr indexer_pool; + // The leftover-lattice census, run in the background (see there); after indexer_pool, which it + // uses, so that it is waited for before the pool goes. + std::future census_future; std::unique_ptr indexer; // The FFT search bound indexer_pool was built with. The long-axis rescue needs it to tell whether a // cell it recovered still fits, since a pool's transform is sized once, at construction. @@ -5350,14 +5353,31 @@ ProcessResult Rugnux::RunPipeline(RugnuxObserver *observer, bool write_output, b // displaced-spindle-angle null as the first pass, its spots are marked in turn, and the search // goes on for up to three lattices. Its own IndexAndRefine instances and indexer, so nothing // the run goes on with is touched. Run on the pass that writes the result only. - if (!geometry_prepass) { - const auto census_start = std::chrono::steady_clock::now(); - const bool ice_ok = experiment_.GetIndexingSettings().GetIndexIceRings(); + // + // Its spots are found here; the rest runs in the background, beside the image loop, on + // copies of everything it reads, and its answer is collected before the pass returns. Not on + // a pass that only post-refines: nothing reads its result there. + if (!geometry_prepass && !postrefine_probe_only_) { std::vector census_frames = spread_ordinals; census_frames.insert(census_frames.end(), validation.begin(), validation.end()); std::ranges::sort(census_frames); census_frames.erase(std::unique(census_frames.begin(), census_frames.end()), census_frames.end()); prefetch_spots(census_frames); + std::map> census_spots; + for (const int f : census_frames) + census_spots.emplace(f, spot_cache.at(f)); + census_future = std::async(std::launch::async, + [&, x = experiment_, census_frames, census_spots, + lattice = *best.result, validation, spread_ordinals, + validation_settings]() -> LeftoverLatticeCensus { + const auto census_start = std::chrono::steady_clock::now(); + const bool ice_ok = x.GetIndexingSettings().GetIndexIceRings(); + const auto &spot_cache = census_spots; + const auto rot_angle = [&](int ordinal) -> std::optional { + if (const auto g = x.GetGoniometer()) + return g->GetAngle_deg(static_cast(ordinal)) + g->GetWedge_deg() / 2.0f; + return std::nullopt; + }; // assign[frame][spot]: -2 ice, -1 no lattice, 0 the crystal's lattice, 1.. a leftover lattice std::map> assign; for (const int f : census_frames) { @@ -5369,7 +5389,7 @@ ProcessResult Rugnux::RunPipeline(RugnuxObserver *observer, bool write_output, b } // Mark the still unassigned spots lattice `r` takes as `id`. auto mark = [&](const RotationIndexerResult &r, int id) { - IndexAndRefine idx(experiment_, indexer_pool.get(), /*retain_outcomes=*/false); + IndexAndRefine idx(x, indexer_pool.get(), /*retain_outcomes=*/false); idx.ForceRotationIndexerResult(r); idx.IntegrateEveryFrame(true); std::mutex m_assign; @@ -5392,7 +5412,7 @@ ProcessResult Rugnux::RunPipeline(RugnuxObserver *observer, bool write_output, b // Leftover validation spots on `r` at the true spindle angles, and the median of five // displaced ones - the first pass's null (pooled_evidence) on the leftovers. auto leftover_evidence = [&](const RotationIndexerResult &r) -> std::pair { - IndexAndRefine idx(experiment_, indexer_pool.get(), /*retain_outcomes=*/false); + IndexAndRefine idx(x, indexer_pool.get(), /*retain_outcomes=*/false); idx.ForceRotationIndexerResult(r); const int nv = static_cast(validation.size()); auto at_shift = [&](int shift) { @@ -5442,7 +5462,7 @@ ProcessResult Rugnux::RunPipeline(RugnuxObserver *observer, bool write_output, b return LatticeSearch(r.lattice.ToPrimitive(r.search_result.centering)).primitive_reduced; }; - mark(*best.result, 0); + mark(lattice, 0); int64_t n_all = 0; double i_all = 0.0; for (const auto &[f, a] : assign) @@ -5456,9 +5476,9 @@ ProcessResult Rugnux::RunPipeline(RugnuxObserver *observer, bool write_output, b census.frames = static_cast(census_frames.size()); census.primary_spots_pct = 100.0 * t0.n / std::max(1, n_all); census.primary_intensity_pct = 100.0 * t0.intensity / std::max(1.0, i_all); - const CrystalLattice primary_reduced = reduced(*best.result); + const CrystalLattice primary_reduced = reduced(lattice); for (int id = 1; id <= 3; id++) { - RotationIndexer ri(experiment_, *indexer_pool); + RotationIndexer ri(x, *indexer_pool); for (const int f : spread_ordinals) { std::vector left; const auto &sp = spot_cache.at(f); @@ -5504,7 +5524,8 @@ ProcessResult Rugnux::RunPipeline(RugnuxObserver *observer, bool write_output, b census.primary_spots_pct, n_all, census.primary_intensity_pct, census.frames, census.lattices.size(), std::chrono::duration(std::chrono::steady_clock::now() - census_start).count()); - result.leftover_lattices = census; + return census; + }); } if (best.result->tilt_walk) { // What the walked tilt does to the prediction: the keystone at the far corner of the @@ -8930,5 +8951,7 @@ ProcessResult Rugnux::RunPipeline(RugnuxObserver *observer, bool write_output, b result.used_direct_beam_x_pxl = direct.first; result.used_direct_beam_y_pxl = direct.second; } + if (census_future.valid()) + result.leftover_lattices = census_future.get(); return result; }