From 091ef41f8de5d68199b525d44f53e3fe623d28bc Mon Sep 17 00:00:00 2001 From: Filip Leonarski Date: Sat, 19 Sep 2026 11:32:01 +0200 Subject: [PATCH] rugnux: the device merge before the correction surfaces sees the collapsed-scale drop DropCollapsedFullScales zeroes the corr of fulls whose frame scale collapsed on the host copy of the fulls; on the resident-fulls (GPU) path that zero reached the device only with the corrected corr pushed after the surfaces. The merge that measures CC1/2 before the correction surfaces runs in between, so on the GPU it still merged the dropped frames' fulls while the host path excluded them - the two builds measured different observations. The corr is now pushed to the device right after the drop, and the later push covers only the surfaces and the frame rejection. This changes the input of the two-pass quality guard (cc_half_before_corrections) on GPU runs where a full scale collapsed; the final merge is unchanged. Co-Authored-By: Claude Opus 5 (1M context) --- .../scale_merge/RotationScaleMerge.cpp | 35 ++++++++++++------- 1 file changed, 22 insertions(+), 13 deletions(-) diff --git a/image_analysis/scale_merge/RotationScaleMerge.cpp b/image_analysis/scale_merge/RotationScaleMerge.cpp index bfde3cd48..d3597ca03 100644 --- a/image_analysis/scale_merge/RotationScaleMerge.cpp +++ b/image_analysis/scale_merge/RotationScaleMerge.cpp @@ -4841,9 +4841,8 @@ RotationScaleMerge::Result RotationScaleMerge::Run(bool for_search, bool full_st } // The total scale each frame's fulls were amplified by - partial scale, incident flux and the fulls' // own G (corr = 1/G here) - taken BEFORE the correction surfaces fold per-observation terms into - // corr, and before a collapsed frame's fulls are zeroed: the resident fulls only get that zero with - // the corrected corr, so a merge on the device before then still sees them, and must weigh them by - // their own scale. Read by MergeAndStats for the CC1/2 weight. + // corr, and before a collapsed frame's fulls are zeroed (a zeroed corr says nothing about the + // frame's scale). Read by MergeAndStats for the CC1/2 weight. frame_scale_fulls.assign(n_frames, NAN); for (const auto &o : fulls) if (std::isfinite(o.corr) && o.corr > 0.0f && !std::isfinite(frame_scale_fulls[o.frame])) { @@ -4852,6 +4851,22 @@ RotationScaleMerge::Result RotationScaleMerge::Run(bool for_search, bool full_st } const bool rejected_full_scales = scale_fulls && DropCollapsedFullScales(combined_on_gpu && scaled_fulls_on_gpu); +#ifdef JFJOCH_USE_CUDA + // The host fulls' corr is the truth from here on; a merge on the resident (GPU) fulls reads the + // device copy, so every change to it is pushed back before the next device merge. + auto upload_fulls_corr = [&]() { + std::vector fcorr(fulls.size()); + ParallelChunks(static_cast(fulls.size()), ThreadsForWork(fulls.size(), nthreads), + [&](int lo, int hi) { + for (int i = lo; i < hi; ++i) fcorr[i] = fulls[i].corr; + }); + gpu_->SetFullsCorr(fcorr.data()); + }; + // Right away, not with the corrections below: the merge just before the surfaces must see the + // collapsed frames' fulls gone on the device exactly as the host merge does. + if (rejected_full_scales && combined_on_gpu && scaled_fulls_on_gpu) + upload_fulls_corr(); +#endif // --- 4b. Optional correction surfaces (decay = resolution x time; absorption = goniometer-frame // diffracted-beam direction), each an alternating multiplicative fit of the fulls' corr against @@ -4917,16 +4932,10 @@ RotationScaleMerge::Result RotationScaleMerge::Run(bool for_search, bool full_st CountSweepDisposition(); } #ifdef JFJOCH_USE_CUDA - // The corrections mutate the host fulls' corr; when the merge runs on the resident (GPU) fulls, push - // the corrected corr back to the device so the merge reads it. - if ((corrections || rejected_full_scales || rejected_frames) && combined_on_gpu && scaled_fulls_on_gpu) { - std::vector fcorr(fulls.size()); - ParallelChunks(static_cast(fulls.size()), ThreadsForWork(fulls.size(), nthreads), - [&](int lo, int hi) { - for (int i = lo; i < hi; ++i) fcorr[i] = fulls[i].corr; - }); - gpu_->SetFullsCorr(fcorr.data()); - } + // The corrections and the frame rejection mutate the host fulls' corr; push it to the device so the + // merge reads it. (The collapsed-scale drop was pushed where it happened.) + if ((corrections || rejected_frames) && combined_on_gpu && scaled_fulls_on_gpu) + upload_fulls_corr(); #endif // --- 5. Error model + merge + statistics. ---