Merge ribo-gap into refine-merge: the bulk-solvent mask grid never coarser than the density grid

No conflicts. The mask choice is per engine and independent of the solvent build, TLS, NCS and twin arms;
it changes only sets whose capped mask spacing exceeds d_min / 3 (4YBB in the manifests).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HVeDQqN9qRD57vnShzTC37
This commit is contained in:
leonarski_fandClaude Opus 5.5 committed 2026-10-27 20:23:53 +01:00
commit ae98b1e151
4 files changed
+23 -7

No files matched your search

+2 -1
View File
@@ -93,7 +93,8 @@ gradient gather, whatever the number of atoms. The derivative of a riding hydrog
**Scale and solvent.** The flat bulk-solvent mask is drawn with the REFMAC radii (probe 1.0 Å, shrink 0.8 Å)
on a grid of its own, with a spacing of $d_\mathrm{min}/3$ or 0.4 Å, whichever is finer (coarser only where
a cell would need more than $2^{26}$ points). On the atoms' density grid the shrink is lost at low
a cell would need more than $2^{26}$ points, and then never coarser than the density grid, which a cell that
large uses for its mask). On the atoms' density grid the shrink is lost at low
resolution: at 3 Å its 1 Å step leaves no grid point within the shrink radius. $k_\mathrm{sol}$ and
$B_\mathrm{sol}$ are searched over their physical range on the working reflections, and the overall scale
and anisotropic B are fitted at each point of that search (§14.2) - at the points of its fine pass by
+1 -1
View File
@@ -269,7 +269,7 @@ and both gradients come back into pinned buffers. The minimiser (L-BFGS) stays o
Once per macro-cycle ([§15.3](CPU_DATA_ANALYSIS_REFINEMENT.md#153-scale-bulk-solvent-and-the-likelihood-parameters)):
- **F_calc and F_mask** - the forward path above without the target, and the bulk-solvent mask drawn on the card
(`ModelMaskGPU.cu`) on **its own fine grid** (d_min/3 or 0.4 A, whichever is finer, at most 2^26 points),
(`ModelMaskGPU.cu`) on **its own fine grid** (d_min/3 or 0.4 A, whichever is finer, at most 2^26 points but never coarser than the density's grid),
with a grid, spectrum and cuFFT plan of its own where that grid is not the density's: one block per
(atom, symmetry image) sets the points within the atom's REFMAC radius, the islands are removed by a
union-find labelling (integer `atomicCAS`, rooted at each component's lowest index, so the partition does not
@@ -440,14 +440,24 @@ double FitTwinFraction(const RefinementReflections &refl, const XrayRowModel &ro
BulkSolventMask::BulkSolventMask(const gemmi::UnitCell &cell, const gemmi::SpaceGroup &sg, double d_min,
size_t nthreads)
: grid_(MaskGrid(cell, sg, d_min)), nthreads_(nthreads),
spacing_(std::max(std::min(d_min / 3.0, MASK_SPACING_MAX), std::cbrt(cell.volume / MASK_POINTS_MAX))) {}
: grid_(MaskGrid(cell, sg, d_min)), nthreads_(nthreads), spacing_(MaskSpacing(cell, d_min)) {}
double BulkSolventMask::MaskSpacing(const gemmi::UnitCell &cell, double d_min) {
const double spacing = std::max(std::min(d_min / 3.0, MASK_SPACING_MAX), std::cbrt(cell.volume / MASK_POINTS_MAX));
return std::min(spacing, d_min / 3.0);
}
gemmi::Grid<float> BulkSolventMask::MaskGrid(const gemmi::UnitCell &cell, const gemmi::SpaceGroup &sg, double d_min) {
// the cap would coarsen it beyond the atoms' grid: that grid, whose points the density holds anyway
if (std::cbrt(cell.volume / MASK_POINTS_MAX) > d_min / 3.0) {
gemmi::Grid<float> grid = RigidBodyZoneGrid(cell, sg, d_min);
std::vector<float>().swap(grid.data);
return grid;
}
gemmi::Grid<float> grid;
grid.unit_cell = cell;
grid.spacegroup = &sg;
const double spacing = std::max(std::min(d_min / 3.0, MASK_SPACING_MAX), std::cbrt(cell.volume / MASK_POINTS_MAX));
const double spacing = MaskSpacing(cell, d_min);
// set_size_from_spacing()'s size, without allocating the values
const std::array<int, 3> n = gemmi::good_grid_size({{cell.a / spacing, cell.b / spacing, cell.c / spacing}},
gemmi::GridSizeRounding::Up, &sg);
@@ -90,8 +90,11 @@ struct XrayRowModel {
// probe and shrink radii (1.0 and 0.8 A), which a grid at d_min / 3 cannot resolve at low resolution - at 3 A
// its 1 A step leaves no grid point within the shrink radius and the shrink is skipped. The step is therefore
// d_min / 3 or MASK_SPACING_MAX, whichever is finer - where the mask grid stays within MASK_POINTS_MAX points; a
// cell too large for that gets the spacing that fills it. The R-factors of low-resolution models settle at a
// step of 0.4 A (within 0.001 of 0.3 A) and move by up to 0.005 between 0.4 and 0.8 A.
// cell too large for that gets the spacing that fills it, but never one coarser than the atoms' density grid
// (RigidBodyZoneGrid, d_min / 3), which it then is: the density's points are on the card anyway, and the mask's
// own grid only ever saved memory. The R-factors of low-resolution models settle at a step of 0.4 A (within 0.001
// of 0.3 A) and move by up to 0.005 between 0.4 and 0.8 A; a cell coarsened by the cap past the shrink radius has
// no shrink at all, its macromolecule swollen by the whole probe, which costs far more.
// The flat solvent model of Fokine & Urzhumtsev (2002) Acta Cryst. D58, 1387-1392
// The probe-and-shrink mask of Jiang & Brunger (1994) J. Mol. Biol. 243, 100-115
class BulkSolventMask {
@@ -101,6 +104,8 @@ public:
BulkSolventMask(const gemmi::UnitCell &cell, const gemmi::SpaceGroup &sg, double d_min, size_t nthreads);
// The mask's grid (sizes and spacing set, values not allocated) - also the GPU engine's (RefinementGPU.cpp).
static gemmi::Grid<float> MaskGrid(const gemmi::UnitCell &cell, const gemmi::SpaceGroup &sg, double d_min);
// The step that grid is asked for.
static double MaskSpacing(const gemmi::UnitCell &cell, double d_min);
// Fmask (unscaled) of `model` at `hkl`.
void Fmask(const gemmi::Model &model, const std::vector<gemmi::Miller> &hkl,
std::vector<std::complex<float>> &f) const;