Uniform-grid boundary transforms¶
Include openpfc/kernel/fft/real_transform.hpp for a rank-local
RealTransform, or openpfc/kernel/fft/mixed_transform.hpp for a
distributed MixedTransform. These additive APIs preserve IHostFFT,
IDeviceFFT and Chebyshev collocation.
They require FFTW; the mixed API also requires a heFFTe-enabled build.
Link the installed OpenPFC::openpfc target as usual.
Select the discrete problem explicitly¶
Both bounded ends have the same homogeneous condition. A boundary label
does not select the grid or transform type. BoundedAxis requires both:
Forward type |
Grid |
Unknown coordinates on [o,o+L] |
Boundary condition |
|---|---|---|---|
DCT1 |
Endpoints |
o+j L/(n-1), j=0..n-1 |
Neumann |
DCT2 |
CellCentered |
o+(j+1/2)L/n |
Neumann |
DST1 |
Interior |
o+(j+1)L/(n+1) |
Dirichlet, endpoints fixed zero |
DST2 |
CellCentered |
o+(j+1/2)L/n |
Dirichlet |
DCT1 needs n>=2; other types need n>=1. Inconsistent type/grid pairs throw. These are uniform physical grids. A DCT in this API does not differentiate on Chebyshev–Lobatto nodes.
The cosine basis is cos(m pi (x-o)/L), m=0..n-1; the sine basis is
sin(m pi (x-o)/L), m=1..n. Forward coefficients use the raw FFTW
convention, including its factors of two. Type I is its own inverse;
type II uses type III for the inverse. backward divides by 2(n-1)
for DCT1, 2(n+1) for DST1, and 2n for either type II. Scaling is fixed
and explicit; this interface does not use orthonormal coefficients.
An even extension implements cosine and an odd extension implements sine. Endpoint DCT1 mirrors without duplicating either endpoint. DST1 inserts the two zero endpoints before mirroring. Cell-centered transforms reflect around the two cell faces; their logical DFT has a half-sample phase. The physical samples and the extension represent the same discrete problem only when spacing, multiplier and normalization match.
Operators and nullspace¶
laplacian applies Delta; poisson solves -Delta u=f.
LaplacianSymbol::Spectral uses positive eigenvalue k² for -Delta.
CenteredDifference instead uses 4 sin²(k h/2)/h², the three-point
second difference on the mirrored grid. For endpoints this corresponds
to reflected ghost values, with trapezoidal weights for the Neumann
inner product. These symbols must not be interchanged.
Cosine has a constant zero mode. poisson rejects a zero coefficient
larger than compatibility_tolerance * max_abs_coefficient and sets the
solution coefficient to zero. The mean is arithmetic for DCT2 and
trapezoidal (half weight at endpoints) for DCT1. For the mixed solve,
this applies only to the global x=y=z zero mode, and compatibility is
checked collectively. Sine has no nullspace. Nonfinite forcing is rejected.
RealTransform::evaluate(coefficients,x,order) evaluates the interpolant,
its first derivative or its spectral second derivative at any physical
point, using the analytic sine/cosine basis. Cost is O(n) per point;
this is for arbitrary-point sampling and boundary traces. First derivatives
change parity: cosine differentiates to minus sine, sine to plus cosine.
At a cell-centered DST2 highest mode the first derivative samples vanish
although the spectral second derivative does not. The same issue arises
at endpoint DCT1 Nyquist. Applying a sampled first derivative twice is
therefore not an implementation of the spectral Laplacian.
Nonhomogeneous data can be handled by a caller-provided lifting g:
solve for v=u-g with forcing f+Delta g, then restore g. Neumann
compatibility includes the prescribed flux through that lifting.
Unlike conditions at the two ends, Robin conditions, variable-coefficient
diagonal solves, and type IV are unsupported by this API.
Usage and distributed layout¶
using namespace pfc::fft;
BoundedAxis z{RealTransformType::DST2, UniformGrid::CellCentered, 64, 2.0};
RealTransform line(z);
std::vector<double> f(z.n), u(z.n);
// Fill f at coordinate(z,j); homogeneous Dirichlet is imposed by the basis.
line.poisson(f, u);
// After MPI_Init: periodic x/y, same bounded z, y slabs with full x/z.
MixedTransform mixed(64, 64, 3.0, 4.0, z, MPI_COMM_WORLD);
std::vector<double> rhs(mixed.size_inbox()), solution(rhs.size());
// Fill rhs using mixed.inbox(), x-fastest, then local y, then z.
mixed.poisson(rhs, solution);
All ranks must pass identical geometry and valid buffer extents. The communicator is borrowed and must outlive the plan; destroy plans before MPI_Finalize. At most ny ranks are supported, each with complete z lines. The outbox reduces x to 0..nx/2 and retains all y frequencies and z modes. Both buffers are x-fastest. The bounded transform is a rank-local FFTW batch. The periodic planes are one heFFTe batched 2D FFT, including heFFTe’s existing communication and layout machinery. No new transpose implementation is involved. This supports all four rows of the table with periodic x/y, including unit periodic dimensions.
Plans and scratch are reused; input buffers are preserved. buffer_bytes
counts owned arrays, excluding opaque plan allocations. Planning and
destruction obey FFTW’s serial planner requirements. One instance must
not execute concurrently. MPI execution is collective. Arbitrary
decompositions that split z are not implemented.
Native distributed homogeneous sine/cosine transforms remain accessible
through heFFTe’s versioned rtransform API; its raw scaling depends on
the backend and must not be assumed to match this FFTW contract.
Regression tests cover mirrored DFT coefficients, round trips, analytic derivatives and boundary traces, both Laplacian symbols, Poisson solves, small odd/even grids, weighted Neumann compatibility and MPI mixed layouts.
Batched device transforms¶
openpfc/kernel/fft/batched_transform.hpp adds
BatchedRealTransform<MemorySpace> and BatchedMixedTransform<MemorySpace>.
Include openpfc/runtime/gpu/memory_space_gpu.hpp for pfc::CUDASpace or
pfc::HIPSpace. The corresponding vendor spectral build and FFTW are
required. Only the cell-centered DCT2/DST2 rows are supported; type I,
type IV and mixed endpoint conditions are rejected, not inferred.
The line plan accepts explicit batch count, sample stride and batch
distance, all positive, in doubles. Lines must not overlap. Buffer extents
include padding: (batches-1)*distance+(n-1)*stride+1. Padding is untouched.
Forward exposes the same raw real coefficients as RealTransform and
inverse is normalized. Input and coefficient arrays are preserved;
an identical input/output span is allowed for rank-local line transforms.
The mixed plan exposes the same complex coefficients and y-slab layout
as MixedTransform. All arrays passed as spans point to device memory:
BoundedAxis z{RealTransformType::DST2, UniformGrid::CellCentered, 64, 2};
BatchedMixedTransform<pfc::CUDASpace> mixed(64, 64, 3, 4, z);
// rhs and solution are device spans of mixed.size_inbox() doubles.
mixed.helmholtz(rhs, solution, 2.0); // (-Delta+2)u=f
// mixed.poisson(rhs, solution) and mixed.laplacian(rhs, solution) also work.
Plans/workspaces are reused without execution-time buffer allocation.
The legacy default stream is used; synchronize it before accessing results
from another stream or host. Construct, execute and destroy on the same
current device. One instance is not reentrant. The communicator is borrowed
and must remain live until destruction. One rank uses a vendor 3D FFT;
multiple ranks reuse heFFTe’s distribution with full z output lines.
GPU-aware MPI follows the configured OpenPFC_MPI_CUDA_AWARE or
OpenPFC_MPI_HIP_AWARE setting. Without it, heFFTe stages communication
through host buffers; no extra field copy is made by the bounded transform.
At most ny ranks are supported. The checked vendor extent limit is
nxnyn <= INT_MAX. Input values must be finite. GPU Neumann Poisson
additionally detects nonfinite coefficients while checking compatibility.
The ny bound is necessary for the owned y slabs, not sufficient for every
small grid: heFFTe must also be able to assign nonempty intermediate FFT
boxes. Its planner can reject an undersized intermediate decomposition.
helmholtz accepts finite nonnegative reaction; zero reaction is Poisson.
Both Laplacian symbols remain available. All ranks must agree on reaction,
symbol and compatibility tolerance. Neumann Poisson checks the global
constant mode against the maximum raw mixed coefficient, transfers only
three diagnostic scalars from device to host, then reduces them over MPI.
Its solution has zero arithmetic mean. Positive reaction retains the
constant mode. No field is copied to the host during a solve.
Internally the line transform reorders even-indexed samples ascending and
odd-indexed samples descending, using an n-point real FFT. DST2 uses
alternating signs and reversed cosine modes. The mixed solve permutes
whole z planes and uses a half-volume 3D FFT. With complex periodic modes
A=F[k], B=F[n-k] and theta=pi*k/(2n), its cosine coefficients are
C[k]=cos(theta)(A+B)-i sin(theta)(A-B) and
C[n-k]=sin(theta)(A+B)+i cos(theta)(A-B).
The two different diagonal symbols are applied together before recovering
the Fourier pair. Taking a real part here would lose the periodic phase.
Constant modes, even-size Nyquist modes and odd-size partners are handled
explicitly. Forward/backward still expose actual DCT/DST coefficients;
only complete operator applications fuse conversion and diagonalization.
Device regressions compare raw coefficients with direct trigonometric sums and independent host transforms, including padded/interleaved batches, odd/even periodic dimensions, nullspaces and both diagonal symbols.
Distributed exchange policy¶
The original BatchedMixedTransform constructor retains heFFTe’s
alltoallv exchange policy. An overload accepts the installed heFFTe
reshape_algorithm directly; include <heffte_plan_logic.h> when naming
its enumerators. Supported choices are alltoallv, alltoall, p2p and
p2p_plined, on both CUDA and HIP builds:
pfc::fft::BatchedMixedTransform<pfc::HIPSpace> transform(
nx, ny, period_x, period_y, z,
heffte::reshape_algorithm::p2p_plined, communicator);
All ranks must supply the same valid policy; invalid or mismatched values are rejected collectively before planning. This does not change y-slab / full-z ownership, normalization, the borrowed communicator or the existing build-time GPU-aware MPI policy. One-rank execution still uses the vendor FFT directly. No automatic policy selection or arbitrary decomposition is provided. Choose an exchange policy using verified end-to-end measurements on the target MPI installation, rather than assuming one is universally fastest.
Retained spectral ownership¶
Physical fields always use the existing y-slab/full-z decomposition. The default spectral ownership remains y slabs. An explicit overload allows full-z spectral x slabs, without changing the boundary grid or operator:
pfc::fft::BatchedMixedTransform<pfc::HIPSpace> transform(
nx, ny, period_x, period_y, z,
heffte::reshape_algorithm::p2p_plined,
pfc::fft::SpectralSlab::X, communicator);
inbox() still describes physical y slabs; outbox() now describes the
selected spectral ownership. Obtain buffer extents from the plan rather
than assuming they equal another rank’s extents. Both forward and backward
use canonical x-fast exposed coefficients, even though distributed complete
operators retain heFFTe’s y-fast/z-slow internal order. Conversion is fused
into the existing coefficient kernels; physical fields are not staged on
the host. Complete bounded-z lines remain local in both layouts.
All ranks must agree on the valid choice. X additionally requires
ranks <= nx/2 + 1; physical ownership still requires ranks <= ny.
These are necessary ownership bounds, not a promise that every tiny
intermediate library plan is valid. One rank keeps the original vendor
FFT and storage order. SpectralSlab::Y preserves the original layout and
all existing constructor defaults. Normalization, signs, zero modes,
compatibility checks, borrowed-communicator and device-lifetime contracts
are unchanged. No arbitrary decomposition or automatic selection is added.