Rotation merge on the GPU reports a merge without fulls instead of crashing

When the GPU 3D combine made no full reflection (no rocking event captured
whole enough - e.g. a sweep exposed 0.2 deg out of every 0.6 deg, or a lattice
that does not describe the frames), the GPU scale-fulls step was skipped and
the host scale-fulls loop ran instead. That loop slices the per-frame fulls
CSR that only the host combine builds, so it read empty vectors and the
process died on SIGSEGV in RunArm. The host loop now runs only on fulls the
host combined; with no fulls the merge ends in the existing "nothing to merge"
error, as the CPU path already did. Runs that make fulls are unchanged
(merged and unmerged MTZ data bit-identical on two in-house sweeps).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HVeDQqN9qRD57vnShzTC37
This commit is contained in:
leonarski_fandClaude Opus 5.5 committed 2026-11-01 12:59:59 +01:00
1 parent 314e502733
commit 4cb911d675
2 files changed
+18 -1

No files matched your search

@@ -7527,7 +7527,9 @@ RotationScaleMerge::Result RotationScaleMerge::RunArm(bool scale_partials, bool
BuildFullsGroupCSR(n_groups);
// --- 4. Scale the fulls (XDS order, Unity model). ---
if (scale_fulls && !scaled_fulls_on_gpu) {
// On the fulls the host combined: the per-frame CSR this loop slices is built by Combine(). Fulls
// combined on the GPU were scaled there - unless there were none, and then there is nothing to scale.
if (scale_fulls && !combined_on_gpu) {
// On a working copy of the fulls, as the partials' loop (ScalingRef and ScalingFit in the header):
// full_index[q] is the full at position q, in the group CSR's order and then the fulls in no group.
const int nf = static_cast<int>(fulls.size());
+15
View File
@@ -229,3 +229,18 @@ TEST_CASE("RotationScaleMergeSegments_NoSegmentsIsTheEngineAsBefore", "[Rotation
else if (get_gpu_description().find("RTX 5080") != std::string::npos)
CHECK(hash == 6594155396455850264ULL);
}
// A sweep in which no rocking event is captured whole enough to make a full - as in one exposed 0.2 deg out
// of every 0.6 deg, where each reflection is caught on one frame and only in part. On the GPU the combine
// returned no fulls, the scale-fulls step then fell through to the host loop, which slices a per-frame CSR
// that only the host combine builds, and the process died on SIGSEGV instead of reporting the empty merge.
TEST_CASE("RotationScaleMerge_NoFullsIsAnErrorNotACrash", "[RotationScale]") {
const std::vector<SweepSpec> sweeps{{60, 1.0, 0.0}};
auto data = MakeSweeps(sweeps, 17);
auto x = SyntheticExperiment(false);
// More than any rocking event can capture: the combine makes no full.
ScalingSettings s = x.GetScalingSettings();
s.MinCapturedFraction(1000.0);
x.ImportScalingSettings(s);
CHECK_THROWS_AS(Merge(data, x, {}), JFJochException);
}