The search was CPU-bound: on a 54-chain partial complex ~80% of all CPU samples were the score's FitModelScale grid (and libm's exp under it), with the GPU busy 13 of 37 s. - score_placement fits its scale on the GPU where its structure factors are made there (ModelScaleGPU's FitSolvent / Fit, as the rigid body already does); equal to rounding, the CPU build unchanged. - A model whose expected log-likelihood gain (sum sigma_A^4 / 2, McCoy et al. 2017; sigma_A^2 the part of the scattering one copy is by Matthews, times Luzzati's D^2) is below 10 is not searched for. Over the 193-set survey the lowest eLLG of a placed model is 26; every PAE domain below 24 placed nowhere, so no decision changes. - The likelihood setups made where first needed, one serving both where the reflections are the same. - After a candidate fails its null, the other placed candidates' nulls side by side. - The first copy's placements refined side by side on an engine each; the groups' translation functions side by side; CopyFactors and KnownStructureFactors over threads; the further copies' CopyFactors and gains for the peaks walked all at once; the known-structure TF setup made while the first copy is refined. All exact. Survey replay (193 sets): no decision changes; two R values differ in the 3rd decimal (GPU scale). Alternating runs, MODEL_PRIOR_SECONDS base -> new: 54-chain complex 38.3 -> 14.7, 4mtk 18.3 -> 9.7, 9v16 13.7 -> 8.3 (shared machine, indicative). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EizBKhTqrAYago9KJG3dA3
100 lines
5.3 KiB
C++
100 lines
5.3 KiB
C++
// SPDX-FileCopyrightText: 2026 Filip Leonarski, Paul Scherrer Institute <filip.leonarski@psi.ch>
|
|
// SPDX-License-Identifier: GPL-3.0-only
|
|
|
|
#pragma once
|
|
|
|
// A model's structure factors, and the maps made from coefficients on its reflections, on the GPU (CUDA
|
|
// builds only). It is what model validation computes on the CPU - F_calc from the model's density on a
|
|
// grid (gemmi's DensityCalculator, IT92, the Refmac-compatible blur and its unblur) and F_mask from the
|
|
// bulk-solvent mask (gemmi's SolventMasker with the Refmac radii), each transformed and read off as
|
|
// prepare_asu_data() lists them; and the inverse transform of a set of map coefficients, as
|
|
// get_f_phi_on_grid() and MapFromFPhi() make it - moved to the device. The two agree to rounding, not
|
|
// bit for bit: float distances and cuFFT for FFTW, and the crystal's symmetry is composed in reciprocal
|
|
// space (SymmetryComposition, RigidBodyRefine.h) instead of by symmetrizing the grid. The GPU is
|
|
// deterministic on its own.
|
|
//
|
|
// Made once for a cell, a group, a resolution and a model's atoms, then evaluated as often as the
|
|
// coordinates change: everything that depends only on the first four is worked out here, once, and
|
|
// the device buffers are reserved once. The atoms' B factors and occupancies are read at every
|
|
// evaluation, but the blur is the one the model had when the engine was made.
|
|
|
|
#include <complex>
|
|
#include <cstddef>
|
|
#include <memory>
|
|
#include <mutex>
|
|
#include <vector>
|
|
|
|
#include "gemmi/asudata.hpp"
|
|
#include "gemmi/dencalc.hpp" // DensityCalculator
|
|
#include "gemmi/grid.hpp"
|
|
#include "gemmi/it92.hpp"
|
|
#include "gemmi/model.hpp"
|
|
#include "gemmi/symmetry.hpp"
|
|
#include "gemmi/unitcell.hpp"
|
|
|
|
#include "ModelStructureFactorsGPUEngine.h"
|
|
|
|
// Each atom's density as PutModelDensityOnGrid() (ModelGrid.cpp) sets it up for `dc` - its d_min, rate
|
|
// and blur - in model order; and the atoms of the bulk-solvent mask as PutMaskOnGrid() takes them: each
|
|
// one's index in the model and its radius, probe included.
|
|
void ModelDensityAtoms(const gemmi::Model &model, const gemmi::DensityCalculator<gemmi::IT92<float>, float> &dc,
|
|
std::vector<ModelDensityAtom> &atoms, std::vector<int> &mask_atom,
|
|
std::vector<float> &mask_radius);
|
|
|
|
class ModelStructureFactorsGPU {
|
|
public:
|
|
// For `device`. Host work only: the grid, the reflections and what the device will need for them.
|
|
// Nothing is reserved until Reserve(), so DeviceBytes() can decide first whether to. Throws
|
|
// JFJochException where the device cannot be asked for its memory or its transform sizes. Every call works
|
|
// on `device` and leaves the calling thread's current device as it found it; which card it is does not
|
|
// change a number.
|
|
ModelStructureFactorsGPU(int device, const gemmi::Model &model, const gemmi::UnitCell &cell,
|
|
const gemmi::SpaceGroup &sg, double d_min);
|
|
~ModelStructureFactorsGPU();
|
|
|
|
// Whether the GPU reproduces this case: the gather needs every atom's box narrower than the cell.
|
|
bool Supported() const { return supported_; }
|
|
// The device memory Reserve() takes: the grid and its transform, the solvent mask's labels, the cuFFT
|
|
// work area and the reflections. The largest map Map() can be given shares the same buffers.
|
|
size_t DeviceBytes() const { return device_bytes_; }
|
|
double DMin() const { return d_min_; }
|
|
// The reflections Compute() gives F_calc and F_mask at.
|
|
size_t Reflections() const { return rows_.size(); }
|
|
int Device() const { return device_; }
|
|
// The device's total memory, which DeviceBytes() is to be judged against.
|
|
size_t DeviceTotalMemory() const { return device_total_; }
|
|
// The grid the structure factors are computed on.
|
|
std::array<int, 3> GridSize() const { return {setup_.grid.nu, setup_.grid.nv, setup_.grid.nw}; }
|
|
std::array<int, 3> MapSizeBound() const { return map_size_; }
|
|
|
|
// Reserves the device memory. Throws JFJochException on a CUDA failure.
|
|
void Reserve();
|
|
|
|
// F_calc and F_mask of `model` - the model this was made for, its atoms anywhere - as model validation's
|
|
// compute_model_factors() makes them: prepare_asu_data(d_min, blur) of the density and
|
|
// prepare_asu_data(d_min) of the mask. Throws JFJochException on a CUDA failure. Safe to call from
|
|
// several threads; they take turns.
|
|
void Compute(const gemmi::Model &model, gemmi::AsuData<std::complex<float>> &fcalc,
|
|
gemmi::AsuData<std::complex<float>> &fmask);
|
|
|
|
// The real-space map of ASU coefficients on this engine's reflections, as model validation's
|
|
// map_from_coefficients() makes it: on the grid get_size_for_hkl(coef, {0, 0, 0}, 3.0) sizes. Sorts
|
|
// `coef`. Throws JFJochException on a CUDA failure. Safe to call from several threads; they take turns.
|
|
gemmi::Grid<float> Map(gemmi::AsuData<std::complex<float>> &coef);
|
|
|
|
private:
|
|
int device_;
|
|
size_t device_total_;
|
|
gemmi::UnitCell cell_;
|
|
const gemmi::SpaceGroup *sg_;
|
|
double d_min_;
|
|
gemmi::DensityCalculator<gemmi::IT92<float>, float> dc_;
|
|
ModelStructureFactorsGPUSetup setup_;
|
|
std::vector<gemmi::Miller> rows_;
|
|
std::array<int, 3> map_size_{};
|
|
bool supported_ = false;
|
|
size_t fft_work_bytes_ = 0, device_bytes_ = 0;
|
|
std::unique_ptr<ModelStructureFactorsGPUEngine> engine_;
|
|
std::mutex m_;
|
|
};
|