Tail: the cell e.s.d.s and the unmerged MTZ table beside the refinement; the refined difference and anomalous maps side by side

The cell e.s.d.s and the unmerged MTZ read only the integrated observations and the group, both final once
the model has been read, and the refinement reads neither: both now start before the refinement instead of
after it. In MakeMaps the difference map is made on its own thread while the anomalous map is made.

Outputs identical (MTZ data, CIF/HKL/INS, refined model, maps, blobs, report minus provenance) on the
thaumatin and cytochrome C live runs. Live path without the unmerged MTZ (as the broker runs it), step
Refinement: thaumatin 2.76 -> 2.40 s, cytochrome C 1.99 -> 1.93 s. With the unmerged MTZ (CLI), Refinement +
Writing files: 3.21 -> 2.67 s and 3.33 -> 2.80 s.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015CeEGgpKdfkCTJb3YNMJcu
This commit is contained in:
leonarski_fandClaude Opus 5.5 committed 2026-10-26 18:23:27 +01:00
1 parent 067f3a7d7b
commit 7c49fefb54
2 files changed
+39 -24

No files matched your search

@@ -5,6 +5,7 @@
#include <algorithm>
#include <chrono>
#include <future>
#include <map>
#include <optional>
#include <cmath>
@@ -856,9 +857,12 @@ void MakeMaps(Context &ctx, const std::vector<MergedReflection> &merged, double
diff.v.push_back({ctx.refl.hkl[m], std::polar(static_cast<float>(delfwt), static_cast<float>(phi))});
}
result.maps = maps;
const std::array<int, 3> size = gemmi::get_size_for_hkl(diff, {{0, 0, 0}}, 3.0);
result.difference_map = std::make_shared<gemmi::Grid<float>>(MapFromFPhi(gemmi::get_f_phi_on_grid<float>(diff, size, true)));
result.difference_map_rms = gemmi::calculate_data_statistics(result.difference_map->data).rms;
// The difference map beside the anomalous one below: neither reads the other.
std::future<void> difference_map = std::async(std::launch::async, [&diff, &result] {
const std::array<int, 3> size = gemmi::get_size_for_hkl(diff, {{0, 0, 0}}, 3.0);
result.difference_map = std::make_shared<gemmi::Grid<float>>(MapFromFPhi(gemmi::get_f_phi_on_grid<float>(diff, size, true)));
result.difference_map_rms = gemmi::calculate_data_statistics(result.difference_map->data).rms;
});
// The anomalous difference map: F(+) - F(-) on the model's phase turned back by 90 degrees, read on the +
// rows of an anomalous merge only (as model validation reads it).
@@ -890,6 +894,7 @@ void MakeMaps(Context &ctx, const std::vector<MergedReflection> &merged, double
result.anomalous_map_rms = gemmi::calculate_data_statistics(result.anomalous_map->data).rms;
}
}
difference_map.get();
}
// The engine for the model as it is now: on the GPU where it is wanted and takes the case within its budget,
+31 -21
View File
@@ -4754,6 +4754,35 @@ bool Rugnux::ScaleMergeAndSymmetry(PipelineLocals &p) {
}
result.model_prior = std::move(mr);
}
// The cell's e.s.d.s (CellEsd.h), from the same integrated observations and at the same point as the
// unmerged MTZ below: the indexing and the group are final here, once the model has been read. Rotation
// only - a stills run keeps no per-image reflections to the end, and its crystals' cells differ by more than
// any one is measured to. Made beside the refinement, which reads neither.
std::future<std::optional<CellEsd>> cell_esd;
if (is_rotation && !geometry_prepass && !superseded && result.consensus_cell)
cell_esd = std::async(std::launch::async, [&outcomes = indexer->GetIntegrationOutcome(),
sg = experiment_.GetSpaceGroupOrP1(),
wavelength = experiment_.GetWavelength_A(),
nthreads = config_.nthreads, &logger = logger] {
TimingMark mark(logger, "cell e.s.d.");
return EstimateCellEsd(GatherCellObservations(outcomes, nthreads), sg, wavelength);
});
// The unmerged MTZ (kept with the other outputs, see there) is built from here on, beside the
// refinement and the reflection files: the indexing and the group it is written in are final once the
// model has been read, and nothing after this changes the integrated observations it reads. It gets its
// own copy of the experiment.
if (full && write_output && !geometry_prepass && !superseded && result.consensus_cell
&& config_.export_unmerged) {
unmerged_mtz = std::async(std::launch::async,
[&outcomes = indexer->GetIntegrationOutcome(),
cell = *result.consensus_cell, x = experiment_,
&provenance = config_.provenance, nthreads = config_.nthreads] {
return UnmergedMtzTable(outcomes, cell, x, true, provenance, nthreads);
});
}
if (model_run) {
result.model_placement = placed_by_rigid_body ? "RIGID_BODY"
: result.model_prior && result.model_prior->chosen >= 0 ? "MR" : "NOT_PLACED";
@@ -4833,13 +4862,8 @@ bool Rugnux::ScaleMergeAndSymmetry(PipelineLocals &p) {
}
}
// The cell's e.s.d.s (CellEsd.h), from the same integrated observations and at the same point as the
// unmerged MTZ below: the indexing and the group are final here. Rotation only - a stills run keeps no
// per-image reflections to the end, and its crystals' cells differ by more than any one is measured to.
if (is_rotation && !geometry_prepass && !superseded && result.consensus_cell) {
TimingMark mark(logger, "cell e.s.d.");
const auto obs = GatherCellObservations(indexer->GetIntegrationOutcome(), config_.nthreads);
result.cell_esd = EstimateCellEsd(obs, experiment_.GetSpaceGroupOrP1(), experiment_.GetWavelength_A());
if (cell_esd.valid()) {
result.cell_esd = cell_esd.get();
if (result.cell_esd) {
const CellEsd &e = *result.cell_esd;
logger.Info("Cell e.s.d.s from {} observations of {} reflections: {:.5f} {:.5f} {:.5f} {:.4f} {:.4f} {:.4f}"
@@ -4850,20 +4874,6 @@ bool Rugnux::ScaleMergeAndSymmetry(PipelineLocals &p) {
}
}
// The unmerged MTZ (kept with the other outputs, see there) is built from here on, beside the
// reflection files: the indexing and the group it is written in are final once the model has been
// read, and nothing after this changes the integrated observations it reads. It gets its own copy of
// the experiment.
if (full && write_output && !geometry_prepass && !superseded && result.consensus_cell
&& config_.export_unmerged) {
unmerged_mtz = std::async(std::launch::async,
[&outcomes = indexer->GetIntegrationOutcome(),
cell = *result.consensus_cell, x = experiment_,
&provenance = config_.provenance, nthreads = config_.nthreads] {
return UnmergedMtzTable(outcomes, cell, x, true, provenance, nthreads);
});
}
// Not on the geometry pre-pass, and not on a pass that is going to be re-run. Pass 1 exists to
// choose the space group and post-refine the geometry; the pass after it remakes these outputs
// seconds later at the refined geometry, and that is the answer anyone reads. The quality guard is