ESPResSo
Extensible Simulation Package for Research on Soft Matter Systems
Loading...
Searching...
No Matches
P3MFFTKokkos.hpp
Go to the documentation of this file.
1/*
2 * Copyright (C) 2024-2026 The ESPResSo project
3 *
4 * This file is part of ESPResSo.
5 *
6 * ESPResSo is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * ESPResSo is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <http://www.gnu.org/licenses/>.
18 */
19
20#pragma once
21
22#include <config/config.hpp>
23
24#ifdef ESPRESSO_KOKKOS_FFT
25
26#include "p3m/P3MFFTBackend.hpp"
27
28#include <utils/Vector.hpp>
29
30#include <boost/mpi/communicator.hpp>
31
32#include <KokkosFFT.hpp>
33#include <Kokkos_Core.hpp>
34
35#include <cassert>
36#include <complex>
37#include <memory>
38#include <vector>
39
40/**
41 * @brief Single-MPI-rank P3M FFT backend built on kokkos-fft.
42 *
43 * kokkos-fft wraps the host FFT library (FFTW) behind a @c Kokkos::View API
44 * and performs purely local transforms, so it is only selected when the
45 * simulation runs on a single MPI rank; multi-rank runs keep the heFFTe
46 * backend (@ref P3MFFTHeffte). It reproduces heFFTe's row-major r2c layout
47 * (reduced last axis, size @c mesh[2]/2+1) and its unscaled convention
48 * (@c scale::none in both directions; P3M folds the @c 1/N into the influence
49 * function), so forces and energies agree with the heFFTe path to
50 * floating-point round-off. The transform runs on the host regardless of the
51 * Kokkos default device, matching the CPU heFFTe backend it replaces.
52 *
53 * The transforms execute in place on the caller's buffers via kokkos-fft's
54 * new-array execute path (FFTW @c fftw_execute_dft_r2c / @c _c2r on the pointer
55 * pair passed at call time). Because kokkos-fft plans with @c FFTW_ESTIMATE and
56 * without @c FFTW_UNALIGNED, a plan is only reused on buffers whose alignment
57 * matches the ones it was built with. We guarantee that by building each plan
58 * from the exact buffers it will run on (keyed and cached by pointer): the P3M
59 * k-space and no-halo real-space buffers are allocated once and stable, so
60 * their plans are built on the first step and reused thereafter with no data
61 * copies. The forward input goes through an owned, consistently aligned
62 * scratch view for the same reason; P3M fills it directly (via
63 * @ref forward_input_buffer) so no staging copy occurs, and only an external
64 * caller passing a foreign buffer pays one.
65 */
66template <typename FloatType, class FFTConfig>
67struct P3MFFTKokkos final : public P3MFFTBackend<FloatType, FFTConfig> {
68 static_assert(FFTConfig::use_r2c,
69 "kokkos-fft P3M backend implements the r2c transform only");
70 static_assert(FFTConfig::r2c_dir == 2u,
71 "kokkos-fft P3M backend reduces the contiguous last axis");
72 static_assert(FFTConfig::r_space_order == Utils::MemoryOrder::ROW_MAJOR and
73 FFTConfig::k_space_order == Utils::MemoryOrder::ROW_MAJOR,
74 "kokkos-fft P3M backend supports row-major layout only");
75
77 using ComplexType = typename Base::ComplexType;
78 using RSpaceScalar = typename Base::RSpaceScalar;
79 using KComplex = Kokkos::complex<FloatType>;
80 using ExecSpace = Kokkos::DefaultHostExecutionSpace;
81
82 using RealView =
83 Kokkos::View<FloatType ***, Kokkos::LayoutRight, Kokkos::HostSpace>;
84 using RealViewU =
85 Kokkos::View<FloatType ***, Kokkos::LayoutRight, Kokkos::HostSpace,
86 Kokkos::MemoryTraits<Kokkos::Unmanaged>>;
87 using CplxViewU =
88 Kokkos::View<KComplex ***, Kokkos::LayoutRight, Kokkos::HostSpace,
89 Kokkos::MemoryTraits<Kokkos::Unmanaged>>;
90 using ForwardPlan = KokkosFFT::Plan<ExecSpace, RealViewU, CplxViewU, 3>;
91 using BackwardPlan = KokkosFFT::Plan<ExecSpace, CplxViewU, RealViewU, 3>;
92
93 P3MFFTKokkos(boost::mpi::communicator comm,
97 Utils::Vector3i const & /* node_grid */)
99 m_ks_size{global_mesh[0], global_mesh[1], global_mesh[2] / 2 + 1} {
100 // kokkos-fft is local-only; single rank means the no-halo local mesh spans
101 // the whole global mesh, so there is no domain decomposition to mirror.
102 assert(comm.size() == 1);
104 static_cast<void>(comm);
105 static_cast<void>(rs_local_ld_index);
106 static_cast<void>(rs_local_ur_index);
107
108 m_real_scratch = RealView(Kokkos::view_alloc(Kokkos::WithoutInitializing,
109 "P3MFFTKokkos::real_scratch"),
110 m_mesh[0], m_mesh[1], m_mesh[2]);
111 }
112
113 Utils::Vector3i ks_local_ld_index() const override { return {0, 0, 0}; }
114 Utils::Vector3i ks_local_ur_index() const override { return m_ks_size; }
115 Utils::Vector3i ks_local_size() const override { return m_ks_size; }
116 Utils::Vector3i rs_local_size() const override { return m_mesh; }
117
118 // Hand the caller our owned, aligned scratch so it can fill the input
119 // directly (via extract_block); then forward() runs in place with no copy.
120 RSpaceScalar *forward_input_buffer() override {
121 return m_real_scratch.data();
122 }
123
124 void forward(RSpaceScalar const *in, ComplexType *out) override {
126 m_mesh[2]);
127 // If the caller filled our scratch in place (via forward_input_buffer),
128 // there is nothing to copy; otherwise stage the external input.
129 if (in != m_real_scratch.data()) {
130 RealViewU const in_view(const_cast<FloatType *>(in), m_mesh[0], m_mesh[1],
131 m_mesh[2]);
132 Kokkos::deep_copy(m_real_scratch, in_view);
133 }
134 CplxViewU const out_view(reinterpret_cast<KComplex *>(out), m_ks_size[0],
135 m_ks_size[1], m_ks_size[2]);
137 m_forward = std::make_unique<ForwardPlan>(
138 ExecSpace{}, scratch_view, out_view, KokkosFFT::Direction::forward,
139 KokkosFFT::axis_type<3>({0, 1, 2}));
141 }
142 KokkosFFT::execute(*m_forward, scratch_view, out_view,
143 KokkosFFT::Normalization::none);
144 }
145
146 void backward(ComplexType *in, RSpaceScalar *out) override {
147 // The c2r transform runs in place on the input and destroys it, which
148 // the interface's non-const parameter permits (see
149 // P3MFFTBackend::backward).
150 CplxViewU const in_view(reinterpret_cast<KComplex *>(in), m_ks_size[0],
151 m_ks_size[1], m_ks_size[2]);
152 RealViewU const out_view(out, m_mesh[0], m_mesh[1], m_mesh[2]);
153 KokkosFFT::execute(backward_plan(in, out, in_view, out_view), in_view,
154 out_view, KokkosFFT::Normalization::none);
155 }
156
157private:
158 /** @brief Plan bound to a fixed (input, output) buffer pair. */
159 struct BackwardEntry {
160 void const *in;
161 void const *out;
162 std::unique_ptr<BackwardPlan> plan;
163 };
164
165 /**
166 * @brief Fetch (or lazily build) the backward plan for a buffer pair.
167 *
168 * P3M calls @ref backward with the same handful of stable (@c ks_E_fields,
169 * @c rs_E_fields_no_halo) buffers every step, so building the plan on the
170 * exact buffers it runs on both fixes the FFTW alignment and lets it be
171 * reused indefinitely without a copy.
172 */
173 BackwardPlan &backward_plan(void const *in, void const *out,
174 CplxViewU const &in_view,
175 RealViewU const &out_view) {
176 for (auto &entry : m_backward) {
177 if (entry.in == in and entry.out == out) {
178 return *entry.plan;
179 }
180 }
181 auto plan = std::make_unique<BackwardPlan>(
182 ExecSpace{}, in_view, out_view, KokkosFFT::Direction::backward,
183 KokkosFFT::axis_type<3>({0, 1, 2}));
184 m_backward.push_back({in, out, std::move(plan)});
185 return *m_backward.back().plan;
186 }
187
191 ComplexType const *m_forward_out = nullptr;
192 std::unique_ptr<ForwardPlan> m_forward;
193 std::vector<BackwardEntry> m_backward;
194};
195
196#endif // ESPRESSO_KOKKOS_FFT
Vector implementation and trait types for boost qvm interoperability.
DEVICE_QUALIFIER constexpr pointer data() noexcept
Definition Array.hpp:132
cudaStream_t stream[1]
CUDA streams for parallel computing on CPU and GPU.
Abstract interface for the P3M reciprocal-space FFT.