include/pops/numerics/elliptic/poisson/poisson_fft.hpp Source FileΒΆ

adc_cpp: include/pops/numerics/elliptic/poisson/poisson_fft.hpp Source File
adc_cpp 0.3.0
Model-free C++23 core for coupled hyperbolic-elliptic systems on adaptive (AMR) meshes, with MPI and GPU (Kokkos) backends
poisson_fft.hpp
Go to the documentation of this file.
1#pragma once
2
25
27
28#include <cmath>
29#include <complex>
30#include <numbers> // std::numbers::pi (M_PI is not standard, absent under MSVC)
31#include <utility>
32#include <vector>
33
34#ifdef POPS_HAS_MPI
35#include <mpi.h>
36#endif
37
38namespace pops {
39
40using cplx = std::complex<double>;
41
42inline bool is_pow2(int n) {
43 return n > 0 && (n & (n - 1)) == 0;
44}
45
46// Direct O(n^2) DFT, CORRECTNESS fallback for lengths that are NOT powers of two
47// (the radix-2 below assumes n = 2^k: on an arbitrary n its butterfly overflows
48// the buffer, hence a corrupted, non-deterministic result). Same conventions as
49// fft1d (inv=false: exp(-i...), inv=true: exp(+i...) with 1/n), so the spectral
50// solver stays correct for an arbitrary Nx or Ny, at the cost of a quadratic
51// runtime. On a power-of-two grid (the intended case) we keep the fast FFT.
52inline void dft1d_direct(cplx* a, int n, bool inv) {
53 std::vector<cplx> out(static_cast<std::size_t>(n));
54 const double s = inv ? 1.0 : -1.0;
55 for (int k = 0; k < n; ++k) {
56 cplx acc(0.0, 0.0);
57 for (int j = 0; j < n; ++j) {
58 const double ang = s * 2.0 * std::numbers::pi * (static_cast<double>(k) * j / n);
59 acc += a[j] * cplx(std::cos(ang), std::sin(ang));
60 }
61 out[static_cast<std::size_t>(k)] = inv ? acc / static_cast<double>(n) : acc;
62 }
63 for (int i = 0; i < n; ++i)
64 a[i] = out[static_cast<std::size_t>(i)];
65}
66
67// In-place 1D radix-2 FFT (power-of-two length). inv=false: exp(-i...),
68// inv=true: exp(+i...) with 1/n normalization. Falls back to the direct DFT when n
69// is not a power of two (otherwise the radix-2 butterfly overflows the buffer).
70inline void fft1d(cplx* a, int n, bool inv) {
71 if (!is_pow2(n)) {
72 dft1d_direct(a, n, inv);
73 return;
74 }
75 for (int i = 1, j = 0; i < n; ++i) {
76 int bit = n >> 1;
77 for (; j & bit; bit >>= 1)
78 j ^= bit;
79 j ^= bit;
80 if (i < j)
81 std::swap(a[i], a[j]);
82 }
83 for (int len = 2; len <= n; len <<= 1) {
84 const double ang = 2.0 * std::numbers::pi / len * (inv ? 1.0 : -1.0);
85 const cplx wl(std::cos(ang), std::sin(ang));
86 for (int i = 0; i < n; i += len) {
87 cplx w(1.0, 0.0);
88 for (int k = 0; k < len / 2; ++k) {
89 const cplx u = a[i + k], v = a[i + k + len / 2] * w;
90 a[i + k] = u + v;
91 a[i + k + len / 2] = u - v;
92 w *= wl;
93 }
94 }
95 }
96 if (inv)
97 for (int i = 0; i < n; ++i)
98 a[i] /= n;
99}
100
102 public:
109 PoissonFFT(int Nx, int Ny, double Lx, double Ly, bool spectral = false)
110 : Nx_(Nx),
111 Ny_(Ny),
112 np_(n_ranks()),
113 rank_(my_rank()),
114 nyl_(Ny / np_),
115 nxl_(Nx / np_),
116 dx_(Lx / Nx),
117 dy_(Ly / Ny),
118 spectral_(spectral) {}
119
120 int ny_local() const { return nyl_; } // rows (y) owned by this rank
121 int nx() const { return Nx_; }
122 int y_begin() const { return rank_ * nyl_; } // first global row of the rank
123
124 // rho_local and phi_local: nyl_ x Nx_ (row-major, global rows
125 // [y_begin, y_begin+nyl_)). phi_local is (re)sized.
126 void solve(const std::vector<double>& rho_local, std::vector<double>& phi_local) {
127 std::vector<cplx> A(static_cast<std::size_t>(nyl_) * Nx_);
128 for (std::size_t t = 0; t < A.size(); ++t)
129 A[t] = cplx(rho_local[t], 0.0);
130 for (int jl = 0; jl < nyl_; ++jl)
131 fft1d(&A[static_cast<std::size_t>(jl) * Nx_], Nx_, false); // FFT-x
132
133 std::vector<cplx> B(static_cast<std::size_t>(nxl_) * Ny_);
134 transpose_fwd(A, B); // -> (nxl x Ny)
135 for (int il = 0; il < nxl_; ++il)
136 fft1d(&B[static_cast<std::size_t>(il) * Ny_], Ny_, false); // FFT-y
137
138 for (int il = 0; il < nxl_; ++il) {
139 const int kx = rank_ * nxl_ + il;
140 const int kxs = (kx < (Nx_ + 1) / 2) ? kx : kx - Nx_; // signed frequency (Nyquist -> -N/2)
141 const double wx = 2.0 * std::numbers::pi * kxs / (Nx_ * dx_);
142 const double lx =
143 spectral_ ? -(wx * wx)
144 : (2.0 * std::cos(2.0 * std::numbers::pi * kx / Nx_) - 2.0) / (dx_ * dx_);
145 for (int ky = 0; ky < Ny_; ++ky) {
146 const int kys = (ky < (Ny_ + 1) / 2) ? ky : ky - Ny_;
147 const double wy = 2.0 * std::numbers::pi * kys / (Ny_ * dy_);
148 const double ly =
149 spectral_ ? -(wy * wy)
150 : (2.0 * std::cos(2.0 * std::numbers::pi * ky / Ny_) - 2.0) / (dy_ * dy_);
151 const double lam = lx + ly;
152 cplx& v = B[static_cast<std::size_t>(il) * Ny_ + ky];
153 v = (std::abs(lam) < 1e-14) ? cplx(0.0, 0.0) : v / lam;
154 }
155 }
156
157 for (int il = 0; il < nxl_; ++il)
158 fft1d(&B[static_cast<std::size_t>(il) * Ny_], Ny_, true); // IFFT-y
159 transpose_bwd(B, A); // -> (nyl x Nx)
160 for (int jl = 0; jl < nyl_; ++jl)
161 fft1d(&A[static_cast<std::size_t>(jl) * Nx_], Nx_, true); // IFFT-x
162
163 phi_local.resize(A.size());
164 for (std::size_t t = 0; t < A.size(); ++t)
165 phi_local[t] = A[t].real();
166 }
167
168 private:
169 // transpose A(nyl x Nx) -> B(nxl x Ny): alltoall of (nyl x nxl) blocks.
170 void transpose_fwd(const std::vector<cplx>& A, std::vector<cplx>& B) {
171 const int blk = nyl_ * nxl_;
172 std::vector<cplx> send(static_cast<std::size_t>(np_) * blk), recv(send.size());
173 for (int s = 0; s < np_; ++s)
174 for (int jl = 0; jl < nyl_; ++jl)
175 for (int il = 0; il < nxl_; ++il)
176 send[static_cast<std::size_t>(s) * blk + static_cast<std::size_t>(jl) * nxl_ + il] =
177 A[static_cast<std::size_t>(jl) * Nx_ + static_cast<std::size_t>(s) * nxl_ + il];
178 alltoall(send, recv, blk);
179 for (int q = 0; q < np_; ++q)
180 for (int jl = 0; jl < nyl_; ++jl)
181 for (int il = 0; il < nxl_; ++il)
182 B[static_cast<std::size_t>(il) * Ny_ + static_cast<std::size_t>(q) * nyl_ + jl] =
183 recv[static_cast<std::size_t>(q) * blk + static_cast<std::size_t>(jl) * nxl_ + il];
184 }
185
186 // transpose B(nxl x Ny) -> A(nyl x Nx): alltoall of (nxl x nyl) blocks.
187 void transpose_bwd(const std::vector<cplx>& B, std::vector<cplx>& A) {
188 const int blk = nxl_ * nyl_;
189 std::vector<cplx> send(static_cast<std::size_t>(np_) * blk), recv(send.size());
190 for (int s = 0; s < np_; ++s)
191 for (int il = 0; il < nxl_; ++il)
192 for (int jl = 0; jl < nyl_; ++jl)
193 send[static_cast<std::size_t>(s) * blk + static_cast<std::size_t>(il) * nyl_ + jl] =
194 B[static_cast<std::size_t>(il) * Ny_ + static_cast<std::size_t>(s) * nyl_ + jl];
195 alltoall(send, recv, blk);
196 for (int q = 0; q < np_; ++q)
197 for (int il = 0; il < nxl_; ++il)
198 for (int jl = 0; jl < nyl_; ++jl)
199 A[static_cast<std::size_t>(jl) * Nx_ + static_cast<std::size_t>(q) * nxl_ + il] =
200 recv[static_cast<std::size_t>(q) * blk + static_cast<std::size_t>(il) * nyl_ + jl];
201 }
202
203 // all-to-all exchange of `blk` complex values per rank (identity if np==1).
204 void alltoall(const std::vector<cplx>& send, std::vector<cplx>& recv, int blk) {
205 if (np_ == 1) {
206 recv = send;
207 return;
208 }
209#ifdef POPS_HAS_MPI
210 MPI_Alltoall(send.data(), 2 * blk, MPI_DOUBLE, recv.data(), 2 * blk, MPI_DOUBLE,
211 MPI_COMM_WORLD);
212#else
213 recv = send;
214#endif
215 }
216
217 int Nx_, Ny_, np_, rank_, nyl_, nxl_;
218 double dx_, dy_;
219 bool spectral_ = false;
220};
221
222} // namespace pops
Definition poisson_fft.hpp:101
void solve(const std::vector< double > &rho_local, std::vector< double > &phi_local)
Definition poisson_fft.hpp:126
PoissonFFT(int Nx, int Ny, double Lx, double Ly, bool spectral=false)
spectral: false (default) = DISCRETE 5-point stencil eigenvalue (consistent with the transport gradie...
Definition poisson_fft.hpp:109
int nx() const
Definition poisson_fft.hpp:121
int ny_local() const
Definition poisson_fft.hpp:120
int y_begin() const
Definition poisson_fft.hpp:122
Parallel seam: minimal MPI abstraction (rank/size + collectives) with serial fallback.
Definition amr_hierarchy.hpp:29
std::complex< double > cplx
Definition poisson_fft.hpp:40
void dft1d_direct(cplx *a, int n, bool inv)
Definition poisson_fft.hpp:52
int n_ranks()
Definition comm.hpp:139
bool is_pow2(int n)
Definition poisson_fft.hpp:42
void fft1d(cplx *a, int n, bool inv)
Definition poisson_fft.hpp:70
int my_rank()
Definition comm.hpp:136