rugnux model validation: the CPU structure factors grid the model and the mask on all threads

compute_model_factors' CPU path (no GPU, or the grid does not fit the card)
gridded the density with gemmi's serial put_model_density_on_grid and the mask
with SolventMasker::put_mask_on_grid. It now uses ModelGrid's
PutModelDensityOnGrid / PutMaskOnGrid with -N threads, sharing one set of orbit
leaders (the two grids are the same size) - gemmi's grids bit for bit at any
thread count, as the rigid body already uses them. The GPU path is untouched.

Exactness: p.hkl md5, p.mtz/p_maps.mtz reflection data, the three maps and
p_model.pdb identical to the base and the validation log lines identical (up to
the order of concurrent replicate lines) - CPU build, --mode scale --model on 5reo,
myob_x10sa, 8sa8, 8xtg, 5ebi at -N 1, default and -N 8; full CPU runs of 9ea5,
7dkp, 6iu5, 8tyy; the GPU build with no visible device on the five --mode scale
sets.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SVmAWnzCmRKAXVUCdc4iNi
This commit is contained in:
2026-10-10 10:47:27 +02:00
co-authored by Claude Opus 5.5
parent d271a55a74
commit 76f9192fb4
@@ -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();