Execution and fields

These types connect logical fields to host or device execution. Backend setup and support constraints belong in the GPU path decision guide; halo layout choices are explained in Halo exchange.

pfc::View

template<typename T, std::size_t Rank, typename Layout = LayoutRight, typename MemorySpace = DefaultMemorySpace>
struct View

Kokkos-compatible multi-dimensional View.

Template Parameters:
  • T – Value type

  • Rank – Number of dimensions (runtime extents only)

  • Layout – LayoutRight or LayoutLeft

  • MemorySpace – HostSpace, CudaSpace, or HipSpace

Public Functions

View() = default

Default constructor: empty view, no allocation.

template<typename ...IntType>
inline View(const std::string &name, IntType... extents)

Allocating constructor.

Parameters:
  • name – Label for debugging/profiling (Kokkos-compatible)

  • extents – Per-dimension extents (number of args must equal Rank)

template<typename ...IntType>
inline View(pointer_type ptr, IntType... extents)

Unmanaged constructor: wrap existing pointer.

Parameters:
  • ptr – Pointer to existing allocation (not owned)

  • extents – Per-dimension extents

template<typename iType>
inline T &access(iType i0 = 0, iType i1 = 0, iType i2 = 0, iType i3 = 0, iType i4 = 0, iType i5 = 0, iType i6 = 0, iType i7 = 0)

Kokkos-compatible access(i0, i1, …) with defaulted trailing zeros.

inline bool is_managed() const

Whether this view owns its storage.

inline buffer_t *buffer_ptr()

Underlying buffer (only valid when is_managed())

pfc::data::Field

template<typename T, typename MemorySpace = pfc::HostSpace>
class Field

The one owning field container: DataBuffer + Box3i + halo + geometry-by-value.

Logical indices (i, j, k) are local (0-based on the owned box). Halo cells are addressable in [-halo, n + halo) on every axis, exactly as the legacy PaddedBrick.

Template Parameters:
  • T – Element type (e.g. double, std::complex<double>).

  • MemorySpace – Placement tag (HostSpace default; CudaSpace/HipSpace from the runtime headers). Selects the DataBuffer backend via memory_space_to_backend_t.

Public Functions

inline Field(const pfc::Domain &domain, const pfc::Box3i &owned_box, int halo_width = 0)

Construct from the global Domain, the local owned index box, and a halo width. Allocates prod(size + 2*halo) zero-initialized cells.

halo_width is both the storage padding and the default iteration halo (PaddedBrick convention). Prefer the four-argument overload when migrating unpadded LocalField face-halo layouts (storage 0, iteration n).

Throws:

std::invalid_argument – on a negative halo or an inconsistent box.

inline Field(const pfc::Domain &domain, const pfc::Box3i &owned_box, int storage_halo, int iteration_halo)

Construct with independent storage padding and iteration halo.

  • storage_halo sizes the buffer (prod(size + 2*storage_halo)).

  • iteration_halo is what halo_width() / for_each_interior report — matching LocalField’s metadata halo on an unpadded buffer.

Face-halo FD stacks use storage_halo=0 and iteration_halo=order/2. PaddedBrick-style fields use equal values for both.

inline std::size_t size() const noexcept

Total allocated cells, including halo padding.

inline std::vector<T> &vec()

Host std::vector view of the buffer (FFT / legacy APIs).

Note

HostSpace only — device-backed fields have no host std::vector primary storage.

inline const pfc::Box3i &box() const noexcept

Owned (interior) index box, in global index coordinates.

inline int storage_halo() const noexcept

Storage padding width (cells added on each side of the owned box).

inline int halo_width() const noexcept

Iteration / stencil halo (LocalField-compatible).

For padded fields this equals storage_halo(). For unpadded face-halo layouts it is the metadata width used by for_each_interior and FdGradient factories, while storage stays tightly packed.

inline pfc::Int3 local_size() const noexcept

Per-axis count of owned cells (halo excluded).

inline pfc::Int3 size3() const noexcept

LocalField-compatible alias of local_size().

inline pfc::Int3 global_size() const noexcept

Global domain extents {Nx, Ny, Nz} (LocalField-compatible).

inline pfc::Int3 lower_global() const noexcept

Global index of local (0,0,0) (LocalField-compatible).

inline int padded_extent(int axis) const noexcept

Per-axis padded extent (owned + both storage-halo slabs).

inline pfc::Int3 global(int i, int j, int k) const noexcept

Global index (gi, gj, gk) of local logical (i, j, k).

inline pfc::Real3 coords(int i, int j, int k) const noexcept

Physical coordinates of local logical (i, j, k).

template<typename Fn>
inline void for_each_owned(Fn &&fn) const

Visit every owned cell in x-fastest order.

Callable may be any of (auto-detected):

  • void(int i, int j, int k)

  • void(double x, double y, double z, T value) (LocalField-compatible)

  • void(const Real3& x, T value) (LocalField-compatible)

template<typename Fn>
inline void for_each_interior(Fn &&fn) const

Iterate interior [hw, n-hw) per axis (LocalField-compatible).

Callable may be either of:

  • void(double x, double y, double z, T value)

  • void(const Real3& x, T value)

template<typename Fn>
inline void apply(Fn &&fn)

Fill every owned cell by sampling fn at its physical coords. Accepts T(double,double,double) or T(const Real3&). A host-side write, so the device copy (if any) is marked stale.

inline const Residency &residency() const noexcept

Host/device coherence state (see residency.hpp).

inline void note_device_write() noexcept

Record that a device kernel wrote the device buffer (host mirror stale).

inline void note_host_write() noexcept

Record a host-side write (device mirror stale). Public for axpy helpers.

inline void sync_to_device()

Push the host mirror to the device buffer when the device copy is stale. No-op for a host-space field. Call before a device kernel reads this field &#8212; the sync the audit-4.1 bug omitted.

template<typename Fn>
inline void with_host_view(Fn &&fn)

Bracket a host-side access. Ensures the host data is current (pulling device->host for a device field), invokes fn(T* data, std::size_t size) over the padded host buffer, then marks the host side authoritative (device copy stale).

Public Static Attributes

static constexpr bool is_host_space

True when this field’s memory space is host-accessible (no device mirror).

pfc::field::LocalField

template<class T>
class LocalField

Public Functions

inline std::vector<T> &vec() noexcept

Underlying vector — pass to FFT / halo exchanger / stepper APIs.

inline std::size_t idx(int ix, int iy, int iz) const noexcept

Flat row-major index for local logical (ix, iy, iz) (x fastest).

inline pfc::Int3 global(int ix, int iy, int iz) const noexcept

Global index (gi, gj, gk) of local logical (ix, iy, iz).

inline pfc::Real3 coords(int ix, int iy, int iz) const noexcept

Physical coordinates (x, y, z) of local logical (ix, iy, iz).

template<class Fn>
inline void apply(Fn &&fn)

Fill every owned cell by sampling fn at its physical coordinates.

Lambda may be either of these signatures (auto-detected):

  • T(double x, double y, double z)

  • T(const Real3& x)

template<class Fn>
inline void for_each_owned(Fn &&fn) const

Iterate every owned cell, exposing coords and value.

Lambda may be either of these signatures (auto-detected):

  • void(double x, double y, double z, T value)

  • void(const Real3& x, T value)

template<class U = T, class = std::enable_if_t<std::is_same_v<U, double>>>
inline LocalField &operator+=(const pfc::field::ScaledField &s)

In-place axpy: *this += s.alpha * s.data[0..s.size).

ScaledField is the proxy returned by operator*(double, ...), so the call site reads

u += dt * du;
which is the explicit-Euler update in math-shaped form. The proxy must point to a buffer the same size as this field; an exception is thrown otherwise so layout mismatches surface immediately.

Only available for T == double (the proxy is a double view).

Throws:

std::invalid_argument – if s.size() != size().

template<class Fn>
inline void for_each_interior(Fn &&fn) const

Iterate the interior [hw, n-hw) of every axis, exposing coords and value. No-op if the interior is empty.

Lambda may be either of these signatures (auto-detected):

  • void(double x, double y, double z, T value)

  • void(const Real3& x, T value)

Public Static Functions

static inline LocalField from_subdomain(const pfc::decomposition::Decomposition &decomp, int rank, int halo_width = 0)

FD subdomain layout: geometry from

decomposition::local_box(decomp,

rank)

, physical metadata from decomposition::domain(decomp).

Storage is sized to nx*ny*nz and value-initialized. halo_width is stored so for_each_interior can skip the per-rank halo region; the actual halo data, if needed, lives in a separate buffer (e.g. pfc::halo::FaceHalos<T>).

Throws:

std::invalid_argument – if halo_width < 0 or the local subdomain is too small to leave any interior cells given that halo width (i.e. requires size[i] > 2 * halo_width for every axis).

static inline LocalField from_inbox(const pfc::World &global_world, const pfc::fft::Box3i &inbox)

FFT inbox layout: geometry from a pfc::fft::Box3i (typically IFFT::get_inbox_bounds()) plus the global world. halo_width is 0 (spectral apps do not carry per-rank halos).

pfc::field::PaddedBrick

template<class T>
class PaddedBrick

Halo-padded contiguous brick: one buffer, [-hw, n+hw) indexing.

Storage layout (row-major, x fastest): linear = (i + hw) + (j + hw) * nx_pad + (k + hw) * nx_pad * ny_pad where nx_pad = nx + 2*hw, etc. The MPI subarray types built by pfc::halo::create_padded_face_types_6 use the same convention.

Each brick is self-contained: it owns the Decomposition (by value), its rank, and halo_width so downstream consumers — exchangers, gradient evaluators, iteration helpers — can pick everything they need from the brick alone. This avoids drift between e.g. an exchanger and an evaluator constructed against the same field but with mismatched halo widths.

The brick itself is MPI-unaware: the MPI_Comm lives on the exchanger (pfc::PaddedHaloExchanger<T>), not on the storage.

Template Parameters:

T – Element type (double for the heat equation; float / std::complex<double> are equally valid).

Public Functions

inline PaddedBrick(const pfc::decomposition::Decomposition &decomp, int rank, int halo_width)

Construct a padded brick from an existing decomposition.

Geometry comes from decomposition::local_box(decomp, rank) for the owned size + lower global index, and from decomposition::domain for the physical origin/spacing (so global_coords(i, j, k) returns the right physical position even for halo cells i = -1, which conceptually live at the rank’s left neighbor).

The returned buffer is value-initialized (T{}).

Throws:

std::invalid_argument – if halo_width < 0. Unlike LocalField::from_subdomain we do not require the owned region to exceed 2*halo_width per axis — a padded brick with a tiny owned core is still valid; the user just doesn’t get a non-empty inner region for that halo width.

inline T *data() noexcept

Pointer to the start of the padded buffer (i.e. the (-hw, -hw, -hw) cell, not the (0, 0, 0) owned cell).

This is the pointer that pfc::PaddedHaloExchanger<T> and the matching MPI subarray types in kernel/decomposition/padded_halo_mpi_types.hpp operate on.

inline std::size_t size() const noexcept

Total number of elements in the padded buffer (nx_pad*ny_pad*nz_pad).

inline std::vector<T> &vec() noexcept

Underlying vector — pass to allocators, exchangers, raw kernels.

inline pfc::Int3 size3() const noexcept

Local owned size (nx, ny, nz) (excludes the halo ring).

inline pfc::Int3 padded_size3() const noexcept

Local padded size (nx + 2hw, ny + 2hw, nz + 2hw).

inline const pfc::decomposition::Decomposition &decomposition() const noexcept

Decomposition that produced this brick (carried by value, MPI-free).

inline int rank() const noexcept

Rank in the parent decomposition the brick was built for.

inline int nx() const noexcept

Owned-x extent.

inline int ny() const noexcept

Owned-y extent.

inline int nz() const noexcept

Owned-z extent.

inline int nx_padded() const noexcept

Padded-x extent (nx + 2*hw).

inline int ny_padded() const noexcept

Padded-y extent (ny + 2*hw).

inline int nz_padded() const noexcept

Padded-z extent (nz + 2*hw).

inline std::size_t idx(int i, int j, int k) const noexcept

Flat row-major index for local (i, j, k). Valid for any i,j,k in [-hw, n+hw). Halo cells map to the outer ring.

Note

No bounds checking in the release build. Out-of-range arguments are undefined behaviour; pair with for_each_owned / _inner / _border to guarantee the iteration stays in-range.

inline T &operator()(int i, int j, int k) noexcept

Element access for any i,j,k in [-hw, n+hw).

inline std::size_t idx(const pfc::Int3 &c) const noexcept

pfc::Int3 overloads of idx / operator().

Lets generic code call brick(idx) after receiving a triple from for_each or indices(), instead of unpacking it manually.

inline T &operator[](const pfc::Int3 &c) noexcept

Same as operator()(c); allows brick[idx] with idx from for_each.

inline detail::OwnedIndexRange indices() const noexcept

Forward range of pfc::Int3{i, j, k} over the owned region [0, nx) x [0, ny) x [0, nz), in k-outer / j-middle / i-inner order.

Pair with pfc::field::for_each(brick, fn) for a cache-friendly sweep over every owned cell, or use the iterators directly:

for (const auto idx : brick.indices()) {
  brick(idx) = some_value(idx);
}
inline detail::OwnedIndexRange indices_inner(int r) const noexcept

Forward range of pfc::Int3{i, j, k} over the inner region [r, n - r) per axis (no r-thick boundary slab).

Returns an empty range whenever any axis has n <= 2*r, so the caller never iterates past the owned region. Stencils that read ±r neighbors entirely inside the owned core (without halo data) use this to bound the sweep.

inline pfc::Int3 global(int i, int j, int k) const noexcept

Global cell index (gi, gj, gk) of local (i, j, k).

For halo cells (e.g. i = -1) this returns the conceptual global index before any periodic wrap (so callers can implement either periodic or Dirichlet semantics in their own filling code).

inline std::tuple<double, double, double> global_xyz(int i, int j, int k) const noexcept

Physical coordinates (x, y, z) as scalars for local (i, j, k).

Same geometry as global_coords; pair with structured bindings, e.g. auto [x, y, z] = brick.global_xyz(i, j, k);.

inline pfc::Real3 global_coords(int i, int j, int k) const noexcept

Physical coordinates (x, y, z) of local (i, j, k).

Computed as origin + (lower + i) * spacing — same as LocalField::coords but valid across the halo ring as well.

template<class Fn>
inline void apply(Fn &&fn)

Fill every owned cell by sampling fn at its physical coordinates. Halo cells are left untouched (zero-initialized).

Lambda may be either of these signatures (auto-detected):

  • T(double x, double y, double z)

  • T(const Real3& x)

template<class U = T, class = std::enable_if_t<std::is_same_v<U, double>>>
inline PaddedBrick &operator+=(const ScaledField &s)

In-place axpy over the full padded buffer: m_data[k] += s.alpha * s.data[k].

Intended for u += dt * du when du is a DuField whose residual buffer matches size() and leaves halo lanes at zero (refreshed each step by PaddedHaloExchanger).

Throws:

std::invalid_argument – if s.size() != size().