RESULT-CHANGING (owner approved): stills unmerged MTZ written only for the crystals on the run's cell

The unmerged MTZ took every integrated still. A still whose cell is not close to the run's - the same
test, with the same tolerances, the merge applies - was indexed in another lattice (on few spots,
typically an axis multiple), so its H K L do not name the reflections a reading program takes them
for: aimless or careless would scale them in as wrong reflections. On two 25k-frame XFEL runs such
frames carried 60% of the integrated reflections, and the 824 MB file was the largest single cost
after the merge (about 7 s in --mode scale). Rotation runs are unchanged (one lattice).

The orientation matrix in the batch headers is likewise taken from the first crystal on the run's cell.
Test: Unmerged MTZ: on stills, only the crystals on the run's cell.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-10-14 13:15:13 +02:00
co-authored by Claude Opus 5.5
parent 2d118f6df1
commit a574e97b10
2 changed files with 39 additions and 3 deletions
+13 -3
View File
@@ -1033,6 +1033,15 @@ gemmi::Mtz UnmergedMtz(const std::vector<IntegrationOutcome> &outcomes,
// removing them turns its test into an assumption. XDS and DIALS draw the line in the same place.
const char centering = mtz.spacegroup ? mtz.spacegroup->hm[0] : 'P';
// On stills only the crystals the merge takes are written: one whose cell is not the run's was indexed
// in another lattice (on few spots, typically an axis multiple), so its indices do not name the
// reflections they would be read as. A rotation run has the one lattice.
const auto &indexing = experiment.GetIndexingSettings();
const auto merged_crystal = [&](const IntegrationOutcome &outcome) {
return scanning || outcome.latt.GetUnitCell().is_close(unitCell, indexing.GetUnitCellDistTolerance(),
indexing.GetUnitCellAngleTolerance_deg());
};
std::vector<std::vector<Reflection>> fulls;
if (scanning && sum_partials)
fulls = SumRockingEvents(outcomes,
@@ -1057,8 +1066,9 @@ gemmi::Mtz UnmergedMtz(const std::vector<IntegrationOutcome> &outcomes,
add_row(r);
} else {
for (const auto &outcome : outcomes)
for (const auto &r : outcome.reflections)
add_row(r);
if (merged_crystal(outcome))
for (const auto &r : outcome.reflections)
add_row(r);
}
// Every row has its own slot, so they are filled on several workers.
@@ -1104,7 +1114,7 @@ gemmi::Mtz UnmergedMtz(const std::vector<IntegrationOutcome> &outcomes,
// the sweep, which is the one matrix POINTLESS also writes into every batch.
std::optional<CrystalLattice> lattice_at_zero;
for (const auto &outcome : outcomes) {
if (outcome.reflections.empty() || outcome.latt.CalcVolume() <= 1.0f)
if (outcome.reflections.empty() || outcome.latt.CalcVolume() <= 1.0f || !merged_crystal(outcome))
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;