Fourier / Representation and execution
Same transform, different ownership
A local transform and a distributed transform can share an interface without sharing an ownership policy.
Download runnable lesson (.zip)
Includes the model, inputs, required headers and a short run guide.
Example 09 previously filled a manually decomposed field with random values and printed a transform. Here the data are deterministic, the field is explicitly bound to the transform’s real-space inbox, and the inverse checks that values survive either implementation.
Count the real and complex values
The real grid has \(16\times8\times4=512\) values. Conjugate symmetry for real input permits the x-half Fourier spectrum,
\[ (16/2+1)\times8\times4=288 \tag{1}\]
complex values. The x-Nyquist plane is included. This storage convention must be accounted for when applying Fourier symbols; it is not a second, smaller physical domain (Trefethen 2000, program 5).
Ask the transform for its layout
const auto domain = pfc::domain::create(
pfc::GridSize{{16, 8, 4}}, pfc::PhysicalOrigin{{0, 0, 0}},
pfc::GridSpacing{{2 * std::numbers::pi / 16, 1, 1}});
pfc::sim::stacks::SpectralCPUStack stack(domain, comm.rank(), comm.size(), comm,
implementation);
auto &u = stack.u();
auto &fft = stack.fft();
u.apply([](double x, double, double) { return 1 + std::cos(3 * x); });
std::vector<std::complex<double>> spectrum(fft.size_outbox());
const auto original = u.vec();
fft.forward(u.vec(), spectrum);
fft.backward(spectrum, u.vec());The stack’s u uses its FFT inbox. The coefficient vector uses size_outbox(). A distributed transform can redistribute values between those layouts, so deriving one layout by guessing from the other is unsafe.
IHostFFT defines the common transform contract. Implementation::Local selects a complete-domain FFTW transform and requires one communicator rank. Implementation::Heffte supports distributed ownership. Neither choice here selects a GPU backend.
Two runs of the same program
openpfc run model.cpp input.json --launcher=noneThe supplied JSON selects "implementation": "heffte". Change it to "local" and repeat. Both should report the totals in Equation 1 and a round-trip error near floating-point roundoff.
Now select HeFFTe and use --ranks=2 --launcher=mpirun. The global totals stay the same. Try the local implementation with two ranks: it should be rejected rather than silently running two unrelated copies of the global problem. That failure control is part of the numerical check.
What this comparison does and does not measure
This is a contract and layout demonstration. It records no timing and makes no performance recommendation. The original signal \(1+\cos(3x)\) is a small resolved mode; the coefficient lesson provides an independent DFT comparison instead of relying only on a round trip.
See the local FFT contract and host FFT interface for ownership, normalization and lifetime details.
The complete C++ model and starting input are available with this lesson.