rugnux --model: model structure factors with FFTW instead of pocketfft
Every map -> structure-factor transform on the model path - Fcalc from the
model density and Fmask from the bulk-solvent mask, in the fit, the rigid-body
evaluations (two per evaluation, ~120 evaluations per placement), the frame
probe and the stills model reference - went through gemmi's vendored pocketfft,
single-threaded. They now go through MapToFPhi (rugnux/ModelFFT.{h,cpp}): an
FFTW r2c (fftw3f, already fetched and linked) planned with the guru interface on
the same layout gemmi uses (u fastest, w halved), scaled by V/N, into the same
FPhiGrid, so gemmi's prepare_asu_data() extracts the reflections exactly as
before. gemmi's vendored code is not modified. The F -> map transforms of the
output maps are left on gemmi.
Plans are FFTW_ESTIMATE only (MEASURE times candidates and could choose
differently between runs), made under a mutex - FFTW's planner is not
thread-safe, and the null replicates and the parallel Jacobian call this
concurrently - cached per grid size for the life of the process, and executed
with fftwf_execute_dft_r2c on fftwf_malloc buffers (new-array execution needs
the planning arrays' alignment, which a std::vector does not promise).
NOT bit-identical with pocketfft: the two libraries order their arithmetic
differently, so structure factors move in the last float bits and everything
downstream (scale fit, rigid body, R-factors, maps) can move in the last
printed digit. Verify by tolerance, separately from the parallelisation commits:
MODEL_* keys equal to printed precision and the same MODEL_FIT / hand /
indexing decisions on the audit set.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013nW6FNRP1bBJJ8pfHiByAT
This commit is contained in:
@@ -11,8 +11,10 @@
|
||||
#include <sstream>
|
||||
|
||||
#include <gemmi/mmread_gz.hpp>
|
||||
#include <gemmi/fourier.hpp>
|
||||
|
||||
#include "../common/Logger.h"
|
||||
#include "../rugnux/ModelFFT.h"
|
||||
#include "../rugnux/ModelValidation.h"
|
||||
#include "../rugnux/RigidBodyRefine.h"
|
||||
#include "../rugnux/SigmaA.h"
|
||||
@@ -587,3 +589,36 @@ TEST_CASE("ModelValidation_CCModelFollowsTheSignalByShell", "[ModelValidation]")
|
||||
std::filesystem::remove(prefix + suffix);
|
||||
std::filesystem::remove(path);
|
||||
}
|
||||
|
||||
// The model path's structure factors come from FFTW; they must be gemmi's own transform to float
|
||||
// precision, in the same layout, so prepare_asu_data() reads the same reflections from either.
|
||||
TEST_CASE("ModelValidation_MapToFPhiMatchesGemmi", "[ModelValidation]") {
|
||||
gemmi::Grid<float> map;
|
||||
map.unit_cell.set(40.0, 50.0, 60.0, 90.0, 95.0, 90.0);
|
||||
map.spacegroup = gemmi::find_spacegroup_by_name("P 1");
|
||||
map.set_size(20, 24, 30);
|
||||
std::mt19937 rng(7);
|
||||
std::uniform_real_distribution<float> dist(-1.0f, 1.0f);
|
||||
for (auto &x : map.data)
|
||||
x = dist(rng);
|
||||
|
||||
gemmi::FPhiGrid<float> ref = gemmi::transform_map_to_f_phi(map, true);
|
||||
gemmi::FPhiGrid<float> ours = MapToFPhi(map);
|
||||
REQUIRE(ours.nu == ref.nu);
|
||||
REQUIRE(ours.nv == ref.nv);
|
||||
REQUIRE(ours.nw == ref.nw);
|
||||
REQUIRE(ours.half_l == ref.half_l);
|
||||
REQUIRE(ours.data.size() == ref.data.size());
|
||||
double largest = 0, worst = 0;
|
||||
for (size_t i = 0; i < ref.data.size(); i++) {
|
||||
largest = std::max(largest, static_cast<double>(std::abs(ref.data[i])));
|
||||
worst = std::max(worst, static_cast<double>(std::abs(ours.data[i] - ref.data[i])));
|
||||
}
|
||||
CHECK(worst <= 1e-5 * largest);
|
||||
|
||||
const auto a = ref.prepare_asu_data(4.0, 0, false, false, false);
|
||||
const auto b = ours.prepare_asu_data(4.0, 0, false, false, false);
|
||||
REQUIRE(a.v.size() == b.v.size());
|
||||
for (size_t i = 0; i < a.v.size(); i++)
|
||||
CHECK(a.v[i].hkl == b.v[i].hkl);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user