Rugnux: build the unmerged MTZ beside the P1 cross-check merge

The unmerged file reads the integration outcomes and the determined group, neither of which the P1
merge changes, except each image's mosaicity, which the batch headers carry and the merge rewrites.
UnmergedMtz builds the file without it on a second thread, and SetUnmergedMtzMosaicity fills it in
after the merge, so the file is the same bytes as before.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D1G8gJVAy6gp1K5Dz3NE5C
This commit is contained in:
2026-09-26 22:45:48 +02:00
co-authored by Claude Opus 5.5
parent ab52c12137
commit 92877e5eb7
3 changed files with 62 additions and 11 deletions
+29 -9
View File
@@ -736,12 +736,11 @@ std::vector<Reflection> SumRockingEvents(const std::vector<IntegrationOutcome> &
} // namespace
void WriteUnmergedMtzReflections(const std::vector<IntegrationOutcome> &outcomes,
const UnitCell &unitCell,
const DiffractionExperiment &experiment,
bool sum_partials,
const std::string &filename,
size_t nthreads) {
gemmi::Mtz UnmergedMtz(const std::vector<IntegrationOutcome> &outcomes,
const UnitCell &unitCell,
const DiffractionExperiment &experiment,
bool sum_partials,
size_t nthreads) {
gemmi::Mtz mtz;
mtz.spacegroup = &experiment.GetSpaceGroupOrP1();
mtz.set_cell_for_all(unitCell);
@@ -885,13 +884,11 @@ void WriteUnmergedMtzReflections(const std::vector<IntegrationOutcome> &outcomes
// stood on that image. Turn the first indexed one back by its own angle to get the orientation of
// the sweep, which is the one matrix POINTLESS also writes into every batch.
std::optional<CrystalLattice> lattice_at_zero;
std::optional<float> mosaicity_deg;
for (const auto &outcome : outcomes) {
if (outcome.reflections.empty() || outcome.latt.CalcVolume() <= 1.0f)
continue;
const float mid_deg = phi_start_deg(outcome.reflections.front().image_number) + wedge_deg / 2.0f;
lattice_at_zero = gon ? outcome.latt.Multiply(gon->GetTransformationAngle(mid_deg)) : outcome.latt;
mosaicity_deg = outcome.mosaicity_deg;
break;
}
@@ -930,7 +927,6 @@ void WriteUnmergedMtzReflections(const std::vector<IntegrationOutcome> &outcomes
batch.floats[8 + 3 * i] = u[i] * z_cam;
}
}
batch.floats[21] = mosaicity_deg.value_or(0.0f); // crydat(0), the reflecting range
batch.floats[40] = 1.0f; // scanax = [0, 0, 1]: the rotation axis IS z in the Cambridge frame
batch.floats[47] = wedge_deg;
batch.floats[61] = 1.0f; // e1 = scanax, the only goniostat axis
@@ -975,6 +971,30 @@ void WriteUnmergedMtzReflections(const std::vector<IntegrationOutcome> &outcomes
mtz.data.swap(sorted);
mtz.sort_order = {{1, 2, 3, 4, 5}};
}
return mtz;
}
void SetUnmergedMtzMosaicity(gemmi::Mtz &mtz, const std::vector<IntegrationOutcome> &outcomes) {
// The reflecting range of the same image the orientation matrix is taken from.
std::optional<float> mosaicity_deg;
for (const auto &outcome : outcomes) {
if (outcome.reflections.empty() || outcome.latt.CalcVolume() <= 1.0f)
continue;
mosaicity_deg = outcome.mosaicity_deg;
break;
}
for (auto &batch : mtz.batches)
batch.floats[21] = mosaicity_deg.value_or(0.0f); // crydat(0), the reflecting range
}
void WriteUnmergedMtzReflections(const std::vector<IntegrationOutcome> &outcomes,
const UnitCell &unitCell,
const DiffractionExperiment &experiment,
bool sum_partials,
const std::string &filename,
size_t nthreads) {
gemmi::Mtz mtz = UnmergedMtz(outcomes, unitCell, experiment, sum_partials, nthreads);
SetUnmergedMtzMosaicity(mtz, outcomes);
mtz.write_to_file(filename);
}