Files
Jungfraujoch/image_analysis/structure_refinement/ModelFFT.h
T
leonarski_fandClaude Opus 5.5 2881e6255e Molecular replacement: the CPU translation function gathers its grid once per setup, straight into FFTW's input, output unchanged
On the CPU every orientation's translation function scattered its terms into a complex<double> grid,
converted that to an FPhiGrid, which MapFromFPhi copied into FFTW's input, transformed, and copied out
again into a map the statistics then read: 24 bytes a grid point, 1.6 GB an orientation on 4nwv's cell,
with the 2 x 32 orientations of the groups' functions in flight at once.

Now, once per setup and for all its orientations (a new TranslationFunction over a list of rotations, which
the search calls), each half-grid point's entries are listed in the order the terms and pairs come, cross
terms after; an orientation sums each point over its own list - the same additions in the same order as
the scatter - writes it conjugated into FFTW's input as MapFromFPhi would (MapFromFPhiExecute, the same
plan) and reads the statistics off FFTW's output scaled as MapFromFPhi scales it: 8 bytes a point.

CPU-only (--no-gpu), timing benchmark on 4nwv: the translation stage 61.5 -> 17.7 s, provisional (load
average 24-31 during both runs, and every other stage moved by as much again with no change). Reports,
MR candidate lines, reflection files, maps and placed models byte-identical on myob, cytc and 4nwv,
CPU-only and on the GPU. [MolecularReplacement] passes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 15:25:24 +02:00

35 lines
2.2 KiB
C++

// SPDX-FileCopyrightText: 2026 Filip Leonarski, Paul Scherrer Institute <filip.leonarski@psi.ch>
// SPDX-License-Identifier: GPL-3.0-only
#pragma once
#include "gemmi/grid.hpp"
#include "gemmi/recgrid.hpp"
// The structure factors of a real-space map on the model path (Fcalc from the model density, Fmask
// from the bulk-solvent mask): what gemmi::transform_map_to_f_phi(map, true) returns - the
// conjugated forward transform (F = sum rho exp(+2 pi i h.x)) scaled by V/N, kept for l >= 0 on an (nu, nv, nw/2+1) grid in the map's own XYZ order,
// ready for prepare_asu_data() - computed with FFTW rather than gemmi's single-threaded pocketfft.
// The same transform, not the same bits: the two libraries order their arithmetic differently.
//
// Plans are FFTW_ESTIMATE only - MEASURE would time candidate algorithms and could pick a different
// one from run to run - and made once per grid size and kept, so the rigid body's hundred-odd
// evaluations on one grid plan once. Safe to call from several threads at once.
gemmi::FPhiGrid<float> MapToFPhi(const gemmi::Grid<float> &map);
// The inverse: the real-space map of an (nu, nv, nw/2+1) half-l coefficient grid in XYZ order, as
// get_f_phi_on_grid() fills it - what gemmi::transform_f_phi_grid_to_map() returns (rho = 1/V sum F
// exp(-2 pi i h.x) on an (nu, nv, 2*(nw-1)) grid, NaN coefficients read as zero), computed with FFTW
// under the same planning rules.
gemmi::Grid<float> MapFromFPhi(const gemmi::FPhiGrid<float> &hkl);
// MapFromFPhi's transform itself, for a caller that fills its input: `in`, the (nu, nv, nw/2+1) half-l coefficients
// u fastest as MapFromFPhi hands them to FFTW (conjugated, a NaN as zero), is overwritten, and `out` receives the
// (nu, nv, nw) map before MapFromFPhi's 1/V. Both from fftwf_malloc, as a plan requires.
void MapFromFPhiExecute(int nu, int nv, int nw, std::complex<float> *in, float *out);
// The half-l grid `full` with only the indices |k|, l <= reach kept, on a grid of full.nu x (2 reach + 1) x
// (reach + 1) that an index wraps into as it wraps into the whole one (the molecular transform's, which is
// read no further out).
gemmi::FPhiGrid<float> CropHalfL(const gemmi::FPhiGrid<float> &full, int reach);