ModelStructureFactorsGPU computes what compute_model_factors() and
map_from_coefficients() compute on the CPU - F_calc from the model's
density (IT92, Refmac-compatible blur, unblurred as prepare_asu_data()
does) and F_mask from the Refmac bulk-solvent mask, both on the
reflections prepare_asu_data(d_min) lists, in its order; and a map from
ASU coefficients on the grid get_size_for_hkl(coef, 0, 3.0) sizes - on a
device. Made once per cell, group, resolution and model, then evaluated
as often as the coordinates change, so refinement or MR can call it in a
loop. The device is an explicit parameter; every call leaves the calling
thread's current device as it found it.
Pieces:
- ModelDensityGPU: the rigid body's deterministic brick gather, moved
out of RigidBodyGPU.cu into a component of its own (ModelMaskGPU's
pattern); the rigid body uses it unchanged. MAX_BRICKS_PER_AXIS 8 ->
16, so fine grids with high-B atoms (lysozyme at 1.2 A, a 0.9 A P1
cell) are no longer refused; existing zones are gridded identically.
- One copy of the content is gridded and the symmetry composed in
reciprocal space (SymmetryComposition), operators applied on the fly;
the mask is ModelMaskGPU (every image of every atom, islands, shrink).
- Maps: gemmi's get_f_phi_on_grid() in ZYX order on the host (the
coefficients written are the same), in-place cuFFT c2r, transposed back
to XYZ on the device. One map at a time, in the engine's buffers.
Decided once, up front, per card, from its TOTAL memory: the engine's
bytes (16 N + cuFFT work + reflections, N the larger of the structure-
factor and map grids) must be at most half the card - the rigid body's
engines take at most a quarter beside it. Otherwise, or where the gather
cannot grid the cell, the CPU path runs, logged with needed vs total.
Anything to a resolution other than d_min (the null's 3.5 A fits) stays
on the CPU, so all replicates and the real model's side of the null are
computed the same way. A CUDA failure takes the existing path: the
validation restarts on the CPU.
Measured, model validation total per run (CPU path -> GPU), 16 GB card:
F432 215 A cubic, 1.30 A, 500^3 grid: 47.7 -> 15.6 s (two validations;
14.3 -> 2.4 and 33.4 -> 13.2, the rest of the second is writing the
three 0.5 GB maps); F_calc + F_mask 5.7 s -> 0.05 s
C2 1.11 A: 23.9 -> 13.6 s; P3_2 1.55 A: 18.2 -> 10.2 s;
P2_1 1.25 A: 13.4 -> 6.5 s; F4_132 328 A: 13.0 -> 5.5 s;
P6_5: 8.8 -> 4.2 s; P4_3 0.97 A: 4.6 -> 2.5 s; P1 0.92 A: 4.2 -> 2.2 s;
small P1: 3.2 -> 1.3 s; P6_1: 8.8 -> 6.2 s; lysozyme: 1.8 -> 1.4 s.
p.mtz md5-identical on all 13 sets. Against the CPU path: FC within
1e-4 of mean |F|, phases of the strong half within 0.003 deg, maps within
1e-4 (2mFo-DFc) and 7e-4 (mFo-DFc) of their rms; every logged R, CC,
FOM, k_sol and anomalous site list identical at the printed precision,
except where a rigid-body commit sat on an exact R-free tie (0.2155 ->
0.2155) and fell the other way (R-work 0.2127 vs 0.2129). The GPU result
is bit-identical run to run and with -N 8 (maps, map MTZ, placed model).
Peak device memory of the engine: 2.5 GB at 500^3 (process total peaked
at 14.4 GB with what the merge still holds).
Tests: ModelStructureFactorsGPU_MatchesCPU (five groups, 3.5 and 1.5 A:
same reflections, F_calc <= 1e-5 of mean |F|, F_mask 2e-7 rms, repeat
bit-identical), ModelStructureFactorsGPU_MapMatchesCPU (<= 5e-6 of rms).
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SVmAWnzCmRKAXVUCdc4iNi
366 lines
16 KiB
Plaintext
366 lines
16 KiB
Plaintext
// SPDX-FileCopyrightText: 2026 Filip Leonarski, Paul Scherrer Institute <filip.leonarski@psi.ch>
|
|
// SPDX-License-Identifier: GPL-3.0-only
|
|
|
|
#include "ModelStructureFactorsGPUEngine.h"
|
|
|
|
#include <algorithm>
|
|
#include <string>
|
|
|
|
#include <cufft.h>
|
|
|
|
#include "../../common/JFJochException.h"
|
|
#include "../indexing/CUDAMemHelpers.h"
|
|
|
|
// Every kernel below is deterministic: the density is ModelDensityGPU's gather, the mask ModelMaskGPU's,
|
|
// and each reflection is composed by one thread over the operators in order. The same atoms give the same
|
|
// structure factors, bit for bit, on every run.
|
|
|
|
namespace {
|
|
|
|
void cuda_err(cudaError_t val) {
|
|
if (val != cudaSuccess)
|
|
throw JFJochException(JFJochExceptionCategory::GPUCUDAError, cudaGetErrorString(val));
|
|
}
|
|
|
|
void cufft_err(cufftResult val) {
|
|
if (val != CUFFT_SUCCESS)
|
|
throw JFJochException(JFJochExceptionCategory::GPUCUDAError,
|
|
"cuFFT error " + std::to_string(static_cast<int>(val)));
|
|
}
|
|
|
|
constexpr int THREADS = 256;
|
|
|
|
int blocks(size_t n) {
|
|
return static_cast<int>((n + THREADS - 1) / THREADS);
|
|
}
|
|
|
|
__device__ __forceinline__ int imod(int a, int n) {
|
|
const int r = a % n;
|
|
return r < 0 ? r + n : r;
|
|
}
|
|
|
|
// F of the crystal at each row, composed from the transform of one copy of its content (SymmetryComposition,
|
|
// RigidBodyRefine.h): F(h) = n_cen * unblur * sum over the operators x -> Rx + t of exp(+2 pi i h.t) F1(hR).
|
|
// `f1` is cuFFT's r2c transform of the one copy, u halved, which carries exp(-2 pi i h.x): conjugated and
|
|
// scaled by V/N it is gemmi's F1. An operator is gemmi's Op: rot row-major and tran, both in units of 1/den,
|
|
// and hR is its apply_to_hkl().
|
|
__global__ void compose_kernel(const float2 *f1, float norm, int nu, int nv, int nw, const int *ops, int n_ops,
|
|
int den, const int *row_hkl, const double *row_scale, int rows, float2 *f) {
|
|
const int m = blockIdx.x * blockDim.x + threadIdx.x;
|
|
if (m >= rows)
|
|
return;
|
|
const int h[3] = {row_hkl[3 * m], row_hkl[3 * m + 1], row_hkl[3 * m + 2]};
|
|
double sr = 0, si = 0;
|
|
for (int o = 0; o < n_ops; o++) {
|
|
const int *rot = ops + 12 * o, *tran = rot + 9;
|
|
int k[3];
|
|
for (int i = 0; i < 3; i++)
|
|
k[i] = (rot[i] * h[0] + rot[3 + i] * h[1] + rot[6 + i] * h[2]) / den;
|
|
// The half-u transform holds k_u >= 0, and F1(-k) = conj F1(k) for a real map.
|
|
const bool conj = k[0] < 0;
|
|
if (conj)
|
|
for (int i = 0; i < 3; i++)
|
|
k[i] = -k[i];
|
|
const float2 out = f1[k[0] + static_cast<size_t>(nu / 2 + 1) * (imod(k[1], nv) + static_cast<size_t>(nv) * imod(k[2], nw))];
|
|
const double vr = out.x * norm, vi = (conj ? out.y : -out.y) * norm;
|
|
// exp(+2 pi i h.t), exact for the twelfths and quarters a translation is made of
|
|
double s, c;
|
|
sincospi(2.0 * (h[0] * tran[0] + h[1] * tran[1] + h[2] * tran[2]) / den, &s, &c);
|
|
sr += c * vr - s * vi;
|
|
si += c * vi + s * vr;
|
|
}
|
|
const double scale = row_scale[m];
|
|
f[m] = make_float2(static_cast<float>(scale * sr), static_cast<float>(scale * si));
|
|
}
|
|
|
|
// F_mask at each row, read at h directly (the mask is symmetric): gemmi's prepare_asu_data() of the mask's
|
|
// transform, taking the Friedel mate where h is outside the stored half.
|
|
__global__ void fmask_kernel(const float2 *fm, float norm, const int *row_hkl, int rows, int nu, int nv, int nw,
|
|
float2 *fmask) {
|
|
const int m = blockIdx.x * blockDim.x + threadIdx.x;
|
|
if (m >= rows)
|
|
return;
|
|
int h = row_hkl[3 * m], k = row_hkl[3 * m + 1], l = row_hkl[3 * m + 2];
|
|
const bool conj = h < 0;
|
|
if (conj) {
|
|
h = -h;
|
|
k = -k;
|
|
l = -l;
|
|
}
|
|
const float2 out = fm[h + static_cast<size_t>(nu / 2 + 1) * (imod(k, nv) + static_cast<size_t>(nv) * imod(l, nw))];
|
|
const float re = out.x * norm, im = -out.y * norm;
|
|
fmask[m] = make_float2(re, conj ? -im : im);
|
|
}
|
|
|
|
// MapFromFPhi()'s input step: conjugated, as gemmi does it (rho = 1/V sum F exp(-2 pi i h.x), and the c2r
|
|
// transform carries exp(+2 pi i h.x)), with a missing coefficient (NaN) read as zero.
|
|
__global__ void conjugate_kernel(float2 *c, size_t n) {
|
|
const size_t i = blockIdx.x * static_cast<size_t>(blockDim.x) + threadIdx.x;
|
|
if (i >= n)
|
|
return;
|
|
const float2 x = c[i];
|
|
c[i] = isnan(x.y) ? make_float2(0.0f, 0.0f) : make_float2(x.x, -x.y);
|
|
}
|
|
|
|
// The c2r output, z fastest and each z row padded to 2 (nw / 2 + 1) floats, into x fastest, times 1/V.
|
|
__global__ void transpose_kernel(const float *in, int nu, int nv, int nw, float scale, float *out) {
|
|
const size_t n = static_cast<size_t>(nu) * nv * nw;
|
|
const size_t i = blockIdx.x * static_cast<size_t>(blockDim.x) + threadIdx.x;
|
|
if (i >= n)
|
|
return;
|
|
const int x = static_cast<int>(i % nu);
|
|
const int y = static_cast<int>(i / nu % nv);
|
|
const int z = static_cast<int>(i / (static_cast<size_t>(nu) * nv));
|
|
out[i] = in[z + static_cast<size_t>(2 * (nw / 2 + 1)) * (y + static_cast<size_t>(nv) * x)] * scale;
|
|
}
|
|
|
|
// The structure factors' transform: r2c of an (nu, nv, nw) grid stored u fastest, which halves u.
|
|
cufftHandle ForwardPlan(const ModelDensityGrid &g, size_t &work) {
|
|
cufftHandle plan;
|
|
cufft_err(cufftCreate(&plan));
|
|
cufft_err(cufftSetAutoAllocation(plan, 0));
|
|
int n[3] = {g.nw, g.nv, g.nu};
|
|
const int idist = g.nu * g.nv * g.nw, odist = (g.nu / 2 + 1) * g.nv * g.nw;
|
|
const cufftResult r = cufftMakePlanMany(plan, 3, n, nullptr, 1, idist, nullptr, 1, odist, CUFFT_R2C, 1, &work);
|
|
if (r != CUFFT_SUCCESS)
|
|
cufftDestroy(plan);
|
|
cufft_err(r);
|
|
return plan;
|
|
}
|
|
|
|
// A map's transform: in-place c2r of an (nu, nv, nw) grid stored as gemmi's ZYX half-l grid, w fastest and
|
|
// halved, the real output padded to 2 (nw / 2 + 1) per row as cuFFT does in place.
|
|
cufftHandle MapPlan(int nu, int nv, int nw, size_t &work) {
|
|
cufftHandle plan;
|
|
cufft_err(cufftCreate(&plan));
|
|
cufft_err(cufftSetAutoAllocation(plan, 0));
|
|
int n[3] = {nu, nv, nw};
|
|
int inembed[3] = {nu, nv, nw / 2 + 1};
|
|
int onembed[3] = {nu, nv, 2 * (nw / 2 + 1)};
|
|
const int idist = nu * nv * (nw / 2 + 1), odist = 2 * idist;
|
|
const cufftResult r = cufftMakePlanMany(plan, 3, n, inembed, 1, idist, onembed, 1, odist, CUFFT_C2R, 1, &work);
|
|
if (r != CUFFT_SUCCESS)
|
|
cufftDestroy(plan);
|
|
cufft_err(r);
|
|
return plan;
|
|
}
|
|
|
|
// The calling thread on `device` for as long as this lives, and back on its own device after.
|
|
class DeviceGuard {
|
|
public:
|
|
explicit DeviceGuard(int device) {
|
|
cuda_err(cudaGetDevice(&previous_));
|
|
cuda_err(cudaSetDevice(device));
|
|
}
|
|
~DeviceGuard() { cudaSetDevice(previous_); }
|
|
DeviceGuard(const DeviceGuard &) = delete;
|
|
DeviceGuard &operator=(const DeviceGuard &) = delete;
|
|
|
|
private:
|
|
int previous_ = 0;
|
|
};
|
|
|
|
size_t GridPoints(const ModelDensityGrid &g) {
|
|
return static_cast<size_t>(g.nu) * g.nv * g.nw;
|
|
}
|
|
|
|
size_t ComplexPoints(const ModelDensityGrid &g) {
|
|
return static_cast<size_t>(g.nu / 2 + 1) * g.nv * g.nw;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
struct ModelStructureFactorsGPUEngineImpl {
|
|
ModelStructureFactorsGPUSetup setup;
|
|
size_t work_bytes;
|
|
int device = 0;
|
|
CudaStream stream;
|
|
|
|
CudaDevicePtr<float4> pos;
|
|
CudaDevicePtr<ModelMaskAtom> mask_atoms;
|
|
CudaDevicePtr<float> grid; // the density, then the mask; a map's real grid
|
|
CudaDevicePtr<float2> spectrum; // their transforms; a map's coefficients, transformed in place
|
|
CudaDevicePtr<char> fft_work;
|
|
CudaDevicePtr<int> ops, row_hkl;
|
|
CudaDevicePtr<double> row_scale;
|
|
CudaDevicePtr<float2> f, fmask;
|
|
std::unique_ptr<ModelDensityGPU> density;
|
|
std::unique_ptr<ModelMaskGPU> mask;
|
|
cufftHandle forward = 0;
|
|
|
|
// On `dev`, which the caller has made current.
|
|
ModelStructureFactorsGPUEngineImpl(int dev, const ModelStructureFactorsGPUSetup &s, size_t work)
|
|
: setup(s), work_bytes(work), device(dev) {
|
|
const auto sync = CudaAlloc::Synchronous;
|
|
const size_t na = std::max<size_t>(setup.max_atoms, 1), nr = std::max<size_t>(setup.rows.size(), 1);
|
|
pos = CudaDevicePtr<float4>(na, sync);
|
|
mask_atoms = CudaDevicePtr<ModelMaskAtom>(na, sync);
|
|
grid = CudaDevicePtr<float>(std::max(GridPoints(setup.grid), setup.map_points), sync);
|
|
spectrum = CudaDevicePtr<float2>(std::max(ComplexPoints(setup.grid), setup.map_complex_points), sync);
|
|
fft_work = CudaDevicePtr<char>(std::max<size_t>(work_bytes, 1), sync);
|
|
ops = CudaDevicePtr<int>(std::max<size_t>(12 * setup.sym_ops.size(), 1), sync);
|
|
row_hkl = CudaDevicePtr<int>(3 * nr, sync);
|
|
row_scale = CudaDevicePtr<double>(nr, sync);
|
|
f = CudaDevicePtr<float2>(nr, sync);
|
|
fmask = CudaDevicePtr<float2>(nr, sync);
|
|
density = std::make_unique<ModelDensityGPU>(stream, setup.max_atoms, setup.max_pairs,
|
|
ModelDensityGPU::Bricks(setup.grid.nu, setup.grid.nv, setup.grid.nw));
|
|
mask = std::make_unique<ModelMaskGPU>(stream, GridPoints(setup.grid));
|
|
|
|
ModelMaskGrid mask_grid;
|
|
mask_grid.nu = setup.grid.nu;
|
|
mask_grid.nv = setup.grid.nv;
|
|
mask_grid.nw = setup.grid.nw;
|
|
std::copy(setup.grid.orth, setup.grid.orth + 9, mask_grid.orth);
|
|
mask_grid.volume = setup.volume;
|
|
mask->SetGrid(mask_grid, setup.mask_ops);
|
|
|
|
auto up = [&](void *dst, const void *src, size_t bytes) {
|
|
if (bytes > 0)
|
|
cuda_err(cudaMemcpyAsync(dst, src, bytes, cudaMemcpyHostToDevice, stream));
|
|
};
|
|
up(ops, setup.sym_ops.data(), setup.sym_ops.size() * 12 * sizeof(int));
|
|
up(row_hkl, setup.rows.data(), setup.rows.size() * 3 * sizeof(int));
|
|
up(row_scale, setup.row_scale.data(), setup.row_scale.size() * sizeof(double));
|
|
|
|
size_t plan_work = 0;
|
|
forward = ForwardPlan(setup.grid, plan_work);
|
|
if (plan_work > work_bytes) {
|
|
cufftDestroy(forward);
|
|
throw JFJochException(JFJochExceptionCategory::GPUCUDAError, "structure factors: cuFFT work area over the reserve");
|
|
}
|
|
cufft_err(cufftSetWorkArea(forward, fft_work));
|
|
cufft_err(cufftSetStream(forward, stream));
|
|
cuda_err(cudaStreamSynchronize(stream));
|
|
}
|
|
|
|
~ModelStructureFactorsGPUEngineImpl() {
|
|
cudaStreamSynchronize(stream);
|
|
cufftDestroy(forward);
|
|
}
|
|
|
|
float Norm() const {
|
|
return static_cast<float>(setup.volume / static_cast<double>(GridPoints(setup.grid)));
|
|
}
|
|
};
|
|
|
|
size_t ModelStructureFactorsGPUEngine::TotalMemory(int device) {
|
|
DeviceGuard guard(device);
|
|
size_t free = 0, total = 0;
|
|
cuda_err(cudaMemGetInfo(&free, &total));
|
|
return total;
|
|
}
|
|
|
|
size_t ModelStructureFactorsGPUEngine::FFTWorkBytes(int device, const ModelDensityGrid &grid, int map_nu, int map_nv,
|
|
int map_nw) {
|
|
DeviceGuard guard(device);
|
|
size_t forward = 0, inverse = 0;
|
|
cufftDestroy(ForwardPlan(grid, forward));
|
|
cufftDestroy(MapPlan(map_nu, map_nv, map_nw, inverse));
|
|
return std::max(forward, inverse);
|
|
}
|
|
|
|
size_t ModelStructureFactorsGPUEngine::DeviceBytes(const ModelStructureFactorsGPUSetup &s, size_t fft_work_bytes) {
|
|
const size_t na = s.max_atoms + 1, nr = s.rows.size() + 1;
|
|
const size_t points = GridPoints(s.grid);
|
|
size_t bytes = na * (sizeof(float4) + sizeof(ModelMaskAtom));
|
|
bytes += std::max(points, s.map_points) * sizeof(float);
|
|
bytes += std::max(ComplexPoints(s.grid), s.map_complex_points) * sizeof(float2);
|
|
bytes += fft_work_bytes;
|
|
bytes += 12 * s.sym_ops.size() * sizeof(int) + nr * (3 * sizeof(int) + sizeof(double) + 2 * sizeof(float2));
|
|
bytes += ModelDensityGPU::DeviceBytes(s.max_atoms, s.max_pairs, ModelDensityGPU::Bricks(s.grid.nu, s.grid.nv, s.grid.nw));
|
|
bytes += ModelMaskGPU::DeviceBytes(points);
|
|
return bytes;
|
|
}
|
|
|
|
ModelStructureFactorsGPUEngine::ModelStructureFactorsGPUEngine(int device, const ModelStructureFactorsGPUSetup &setup,
|
|
size_t fft_work_bytes) {
|
|
DeviceGuard guard(device);
|
|
impl_ = std::make_unique<ModelStructureFactorsGPUEngineImpl>(device, setup, fft_work_bytes);
|
|
}
|
|
|
|
ModelStructureFactorsGPUEngine::~ModelStructureFactorsGPUEngine() {
|
|
if (impl_) {
|
|
DeviceGuard guard(impl_->device);
|
|
impl_.reset();
|
|
}
|
|
}
|
|
|
|
void ModelStructureFactorsGPUEngine::Compute(const std::vector<ModelDensityAtom> &atoms,
|
|
const std::vector<std::array<float, 4>> &pos,
|
|
const std::vector<ModelMaskAtom> &mask_atoms,
|
|
std::vector<std::array<float, 2>> &fcalc,
|
|
std::vector<std::array<float, 2>> &fmask) {
|
|
ModelStructureFactorsGPUEngineImpl &e = *impl_;
|
|
DeviceGuard guard(e.device);
|
|
if (pos.size() != atoms.size() || mask_atoms.size() > e.setup.max_atoms)
|
|
throw JFJochException(JFJochExceptionCategory::GPUCUDAError, "structure factors: atoms over the reserve");
|
|
const ModelDensityGrid &g = e.setup.grid;
|
|
const int rows = static_cast<int>(e.setup.rows.size());
|
|
|
|
e.density->SetAtoms(g, atoms);
|
|
if (!pos.empty())
|
|
cuda_err(cudaMemcpyAsync(e.pos, pos.data(), pos.size() * sizeof(float4), cudaMemcpyHostToDevice, e.stream));
|
|
if (!mask_atoms.empty())
|
|
cuda_err(cudaMemcpyAsync(e.mask_atoms, mask_atoms.data(), mask_atoms.size() * sizeof(ModelMaskAtom),
|
|
cudaMemcpyHostToDevice, e.stream));
|
|
|
|
e.density->Compute(e.pos, e.grid);
|
|
cufft_err(cufftExecR2C(e.forward, e.grid, reinterpret_cast<cufftComplex *>(e.spectrum.get())));
|
|
if (rows > 0) {
|
|
compose_kernel<<<blocks(rows), THREADS, 0, e.stream>>>(e.spectrum, e.Norm(), g.nu, g.nv, g.nw, e.ops,
|
|
static_cast<int>(e.setup.sym_ops.size()), e.setup.den,
|
|
e.row_hkl, e.row_scale, rows, e.f);
|
|
cuda_err(cudaGetLastError());
|
|
}
|
|
|
|
e.mask->Compute(e.mask_atoms, static_cast<int>(mask_atoms.size()), e.grid);
|
|
cufft_err(cufftExecR2C(e.forward, e.grid, reinterpret_cast<cufftComplex *>(e.spectrum.get())));
|
|
if (rows > 0) {
|
|
fmask_kernel<<<blocks(rows), THREADS, 0, e.stream>>>(e.spectrum, e.Norm(), e.row_hkl, rows, g.nu, g.nv, g.nw,
|
|
e.fmask);
|
|
cuda_err(cudaGetLastError());
|
|
}
|
|
|
|
fcalc.resize(rows);
|
|
fmask.resize(rows);
|
|
if (rows > 0) {
|
|
cuda_err(cudaMemcpyAsync(fcalc.data(), e.f, rows * sizeof(float2), cudaMemcpyDeviceToHost, e.stream));
|
|
cuda_err(cudaMemcpyAsync(fmask.data(), e.fmask, rows * sizeof(float2), cudaMemcpyDeviceToHost, e.stream));
|
|
}
|
|
cuda_err(cudaStreamSynchronize(e.stream));
|
|
}
|
|
|
|
void ModelStructureFactorsGPUEngine::Map(int nu, int nv, int nw, double volume, const float *coefficients, float *map) {
|
|
ModelStructureFactorsGPUEngineImpl &e = *impl_;
|
|
DeviceGuard guard(e.device);
|
|
const size_t points = static_cast<size_t>(nu) * nv * nw;
|
|
const size_t complex_points = static_cast<size_t>(nu) * nv * (nw / 2 + 1);
|
|
if (points > e.setup.map_points || complex_points > e.setup.map_complex_points)
|
|
throw JFJochException(JFJochExceptionCategory::GPUCUDAError, "map: grid over the reserve");
|
|
size_t plan_work = 0;
|
|
const cufftHandle plan = MapPlan(nu, nv, nw, plan_work);
|
|
try {
|
|
if (plan_work > e.work_bytes)
|
|
throw JFJochException(JFJochExceptionCategory::GPUCUDAError, "map: cuFFT work area over the reserve");
|
|
cufft_err(cufftSetWorkArea(plan, e.fft_work));
|
|
cufft_err(cufftSetStream(plan, e.stream));
|
|
cuda_err(cudaMemcpyAsync(e.spectrum, coefficients, complex_points * sizeof(float2), cudaMemcpyHostToDevice,
|
|
e.stream));
|
|
conjugate_kernel<<<blocks(complex_points), THREADS, 0, e.stream>>>(e.spectrum, complex_points);
|
|
cuda_err(cudaGetLastError());
|
|
cufft_err(cufftExecC2R(plan, reinterpret_cast<cufftComplex *>(e.spectrum.get()),
|
|
reinterpret_cast<cufftReal *>(e.spectrum.get())));
|
|
transpose_kernel<<<blocks(points), THREADS, 0, e.stream>>>(reinterpret_cast<const float *>(e.spectrum.get()),
|
|
nu, nv, nw, static_cast<float>(1.0 / volume), e.grid);
|
|
cuda_err(cudaGetLastError());
|
|
cuda_err(cudaMemcpyAsync(map, e.grid, points * sizeof(float), cudaMemcpyDeviceToHost, e.stream));
|
|
cuda_err(cudaStreamSynchronize(e.stream));
|
|
} catch (...) {
|
|
cudaStreamSynchronize(e.stream);
|
|
cufftDestroy(plan);
|
|
throw;
|
|
}
|
|
cufftDestroy(plan);
|
|
}
|