From 30770ddb1dc2f412b7dd8fa1d307388bd47fcc68 Mon Sep 17 00:00:00 2001 From: Filip Leonarski Date: Thu, 12 Nov 2026 11:59:10 +0100 Subject: [PATCH] rugnux: joint sweeps release the members' fulls, and the joint input holds no spare capacity The members' fulls were "released" with `= {}`, an assignment from an empty initializer list that keeps the vectors' memory (about 0.6 GB per sweep of a ~600 A cell, ~11 GB for 20 sweeps, held through the joint merge); they are now assigned empty vectors. The joint merge's input grew each frame's reflections one by one, up to twice their size in spare capacity; each frame is now reserved at its count first. Nothing the merge reads changes. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01HVeDQsU5a8AJUdJPgWevAK --- rugnux/RugnuxJointSweeps.cpp | 19 ++++++++++++++++--- rugnux/rugnux_cli.cpp | 5 +++-- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/rugnux/RugnuxJointSweeps.cpp b/rugnux/RugnuxJointSweeps.cpp index c6a6253e0..db0a59943 100644 --- a/rugnux/RugnuxJointSweeps.cpp +++ b/rugnux/RugnuxJointSweeps.cpp @@ -574,11 +574,24 @@ JointMergeInput BuildJointMergeInput(const std::vector &sweeps out.outcomes.resize(first + sweep.n_frames); for (int64_t f = 0; f < sweep.n_frames; ++f) out.outcomes[first + f].latt = lattice; + const auto usable = [](const ScaledFull &full) { + return std::isfinite(full.I) && std::isfinite(full.var_bkg) && full.var_per_I > 0.0f && full.d > 0.0f; + }; + const auto frame_of = [&](const ScaledFull &full) { + return std::clamp(std::llround(full.image_number), 0, sweep.n_frames - 1); + }; + // Each frame's reflections reserved at their count first: grown one by one, a large cell's + // sweeps held up to twice their size in spare capacity. + std::vector count(sweep.n_frames, 0); + for (const ScaledFull &full : sweep.fulls) + if (usable(full)) + count[frame_of(full)]++; + for (int64_t f = 0; f < sweep.n_frames; ++f) + out.outcomes[first + f].reflections.reserve(count[f]); for (const ScaledFull &full : sweep.fulls) { - if (!std::isfinite(full.I) || !std::isfinite(full.var_bkg) || !(full.var_per_I > 0.0f) - || !(full.d > 0.0f)) + if (!usable(full)) continue; - const auto frame = std::clamp(std::llround(full.image_number), 0, sweep.n_frames - 1); + const auto frame = frame_of(full); const gemmi::Op::Miller hkl = rel.cob.apply_to_hkl({{full.h, full.k, full.l}}); // A full as the merge takes a pre-combined observation, so that the combine rebuilds the variance // its own sweep's merge gave it: a count c of intensity I/c at correction c = var_per_I is a diff --git a/rugnux/rugnux_cli.cpp b/rugnux/rugnux_cli.cpp index 675567094..9ca68f76a 100644 --- a/rugnux/rugnux_cli.cpp +++ b/rugnux/rugnux_cli.cpp @@ -4262,9 +4262,10 @@ int RugnuxCli::RunJointSweeps(const RugnuxSweepPlan &plan) { JointMergeInput input = BuildJointMergeInput(members, relation); // The joint merge works on its own copy of the fulls (input.outcomes): the members' go, so that a large // cell's sweeps are not held twice. + // (Assigned an empty vector, not {}: that is an assignment from an empty list, which keeps the memory.) for (JointMemberSweep &m : members) { - m.fulls = {}; - m.merged = {}; + m.fulls = std::vector(); + m.merged = std::vector(); } if (input.segments.size() < 2) logger.Warning("Only {} sweep goes into the joint merge", input.segments.size());