RotationScaleMerge: a merge with no rocking-curve runs reports itself instead of dividing by zero

With more than one thread, Combine() split its runs into per-thread chunks of
ceil(n_run / nt) with nt = min(nthreads, n_run); with no runs at all (a lattice
that integrated nothing) nt was 0 and the process died on SIGFPE in Step 5.
The empty case now takes the serial path, and the merge ends in the ordinary
exception. Test: RotationScaleMerge_NoReflectionsIsAnErrorNotACrash (fails
without the fix, on the host path).

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-10-29 00:25:15 +01:00
1 parent 297ef9dc78
commit 9499d77606
2 files changed
+20 -1

No files matched your search

@@ -4843,7 +4843,9 @@ void RotationScaleMerge::Combine() {
size_t n_used = 0;
std::vector<std::vector<Obs>> part; // the fulls of consecutive runs, in run order
if (observation_dump || nthreads <= 1) {
// No runs at all (a lattice that integrated nothing) takes the serial path too: the chunking below
// divides by the number of threads it would use, which is then 0.
if (observation_dump || nthreads <= 1 || n_run == 0) {
// Serial (diagnostic dump needs a single writer).
std::ostringstream dump;
if (observation_dump) {
+17
View File
@@ -281,3 +281,20 @@ TEST_CASE("TrimBlankSweepEnds_WeakCrystalKeepsItsEnds", "[RotationScale]") {
CHECK(sweep.front().reflections.size() == 200);
CHECK(sweep.back().reflections.size() == 200);
}
// A lattice that integrated nothing hands the merge frames without a single reflection. On more than one
// thread the 3D combine split its rocking-curve runs into per-thread chunks by dividing by the number of
// threads it would use - 0 with no runs - and the process died on SIGFPE instead of reporting the empty merge.
TEST_CASE("RotationScaleMerge_NoReflectionsIsAnErrorNotACrash", "[RotationScale]") {
DiffractionExperiment x(DetJF(1));
x.Goniometer(GoniometerAxis("omega", 0.0f, 0.2f, Coord(1, 0, 0), std::nullopt));
x.BeamX_pxl(512).BeamY_pxl(256).DetectorDistance_mm(150).IncidentEnergy_keV(WVL_1A_IN_KEV);
x.SetUnitCell(UnitCell(50, 60, 70, 90, 90, 90));
std::vector<IntegrationOutcome> outcomes(20);
for (auto &o : outcomes)
o.geom = x.GetDiffractionGeometry();
Logger logger("RotationScaleMerge_NoReflections");
RotationScaleMerge rsm(x, outcomes, x.GetUnitCell(), 3, 4, logger);
rsm.Ingest();
REQUIRE_THROWS_AS(rsm.Run(false, true, false), JFJochException);
}