Merge branch 'mv-cpu' into rc175: model validation CPU path - parallel density and mask, faster model-scale fits (exact)

Exact: p.hkl, MTZ and map data, p_model.pdb identical at -N 1/default/-N 8 on 9 sets (CPU build and GPU build on
the CPU path); GPU path unchanged. Indicative: 8xtg CPU validation 32-41 -> 23-25 s.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-10-10 11:52:55 +02:00
co-authored by Claude Opus 5.5
3 changed files with 318 additions and 64 deletions
@@ -38,6 +38,7 @@
#include "../scale_merge/ReindexAmbiguity.h" // ReindexReflections
#include "../scale_merge/CrystalSetting.h" // CellMappingOperators
#include "ModelFFT.h"
#include "ModelGrid.h" // PutModelDensityOnGrid, PutMaskOnGrid
#include "RigidBodyRefine.h"
#include "SigmaA.h"
#ifdef JFJOCH_USE_CUDA
@@ -649,20 +650,20 @@ ModelValidationResult Validate(const std::vector<MergedReflection> &merged,
dc.set_refmac_compatible_blur(ms.st.models[0]);
// The mask is made on a thread of its own, beside the density: both only read the model, each
// fills a grid of its own, and between them they are the whole cost of this step.
// fills a grid of its own, and between them they are the whole cost of this step. Both are
// gemmi's gridding spread over the threads (ModelGrid.h), the same grids bit for bit at any
// thread count; the two grids are the same size, so they share the symmetry's orbits.
// Refmac radii give a slightly lower R than the Cctbx set on our test cases, at no cost.
std::future<gemmi::AsuData<std::complex<float>>> fmask = std::async(std::launch::async,
[&model = ms.st.models[0], unit_cell = dc.grid.unit_cell, spacegroup = dc.grid.spacegroup,
spacing = dc.requested_grid_spacing(), to_d] {
gemmi::SolventMasker masker(gemmi::AtomicRadiiSet::Refmac);
gemmi::Grid<float> mask_grid;
mask_grid.unit_cell = unit_cell;
mask_grid.spacegroup = spacegroup;
mask_grid.set_size_from_spacing(spacing, gemmi::GridSizeRounding::Up);
masker.put_mask_on_grid(mask_grid, model);
gemmi::Grid<float> mask_grid;
mask_grid.unit_cell = dc.grid.unit_cell;
mask_grid.spacegroup = dc.grid.spacegroup;
mask_grid.set_size_from_spacing(dc.requested_grid_spacing(), gemmi::GridSizeRounding::Up);
const std::vector<size_t> orbit_leaders = OrbitLeaders(mask_grid, nthreads);
std::future<gemmi::AsuData<std::complex<float>>> fmask = std::async(std::launch::async, [&] {
PutMaskOnGrid(mask_grid, ms.st.models[0], orbit_leaders, nthreads);
return MapToFPhi(mask_grid).prepare_asu_data(to_d, 0);
});
dc.put_model_density_on_grid(ms.st.models[0]);
PutModelDensityOnGrid(dc, ms.st.models[0], orbit_leaders, nthreads);
ms.fcalc = MapToFPhi(dc.grid)
.prepare_asu_data(dc.d_min, dc.blur, false, false, false);
ms.fmask = fmask.get();