diff --git a/image_analysis/structure_refinement/Refinement.cpp b/image_analysis/structure_refinement/Refinement.cpp index 977eccf6f..96c274e52 100644 --- a/image_analysis/structure_refinement/Refinement.cpp +++ b/image_analysis/structure_refinement/Refinement.cpp @@ -5,6 +5,7 @@ #include #include +#include #include #include #include @@ -856,9 +857,12 @@ void MakeMaps(Context &ctx, const std::vector &merged, double diff.v.push_back({ctx.refl.hkl[m], std::polar(static_cast(delfwt), static_cast(phi))}); } result.maps = maps; - const std::array size = gemmi::get_size_for_hkl(diff, {{0, 0, 0}}, 3.0); - result.difference_map = std::make_shared>(MapFromFPhi(gemmi::get_f_phi_on_grid(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 difference_map = std::async(std::launch::async, [&diff, &result] { + const std::array size = gemmi::get_size_for_hkl(diff, {{0, 0, 0}}, 3.0); + result.difference_map = std::make_shared>(MapFromFPhi(gemmi::get_f_phi_on_grid(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 &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, diff --git a/rugnux/RugnuxScaleMerge.cpp b/rugnux/RugnuxScaleMerge.cpp index 113e46c6d..c1b35fd2d 100644 --- a/rugnux/RugnuxScaleMerge.cpp +++ b/rugnux/RugnuxScaleMerge.cpp @@ -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> 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