|
numerics 0.1.0
|
include/pde/stencil.hpp provides templated, higher-order utilities for the most common grid operations in numerical PDE codes: finite-difference Laplacians, operator-splitting fiber sweeps, and vector-calculus operators on 3D grids. They eliminate the nested for loops that otherwise appear verbatim in every physics application. The old path include/spatial/stencil.hpp is a forwarding shim and continues to work.
Every PDE app in apps/ originally contained variations of the same two patterns:
Pattern 1 – 5-point Laplacian stencil (2D, Dirichlet)
This appeared twice in TDSE (compute_energy, ham_mv) and in the NS pressure Laplacian.
Pattern 2 – ADI fiber sweep (TDSE Crank-Nicolson)
sweep_x and sweep_y in TDSE were identical modulo the axis index.
With stencil.hpp both patterns become single calls, and sweep_x/sweep_y collapse into a shared cn_sweep_ helper.
All 2D functions operate on a flat BasicVector<T> representing an \(N \times N\) grid in row-major order: data[i*N + j] for row \(i\), column \(j\). The template parameter T can be num::real (double) or num::cplx (std::complex<double>).
Computes the unnormalized 5-point Laplacian at each grid point:
\[ y_{i,j} = x_{i+1,j} + x_{i-1,j} + x_{i,j+1} + x_{i,j-1} - 4\,x_{i,j} \]
Out-of-bounds neighbors contribute 0 (Dirichlet ghost cells). To obtain the scaled operator \(-\nabla^2_h x\) used in the Hamiltonian:
Used by: TDSE compute_energy, TDSE ham_mv (Lanczos Hamiltonian).
Same formula as above, but out-of-bounds neighbors wrap modulo \(N\). The \(j\)-boundary rows ( \(j=0\) and \(j=N-1\)) are peeled out of the inner loop so the interior range \(j = 1 \ldots N-2\) contains no modulo arithmetic and the compiler can auto-vectorize it (NEON/AVX-256).
Applying viscosity in one call (replaces the 15-line copy+double-loop in NS):
For the pressure Poisson solve (negative Laplacian matvec in CG):
Used by: NS apply_diffusion, NS solve_pressure.
For each fiber (column or row), these extract the \(N\) values into a std::vector<T>, invoke f(fiber), then write back. The callable f has signature void(std::vector<T>&) and modifies the fiber in-place.
col_fiber_sweep iterates over columns \(j = 0 \ldots N-1\) and extracts the x-direction fiber data[0..N-1, j] – used for Crank-Nicolson sweeps along \(x\) in TDSE.
row_fiber_sweep iterates over rows \(i = 0 \ldots N-1\) and extracts data[i, 0..N-1] – used for sweeps along \(y\).
Collapsing TDSE sweep_x and sweep_y into one helper:
Used by: TDSE sweep_x, sweep_y.
All 3D functions operate on num::Grid3D objects (uniform Cartesian grid, flat layout idx = k*ny*nx + j*nx + i, stored in Grid3D::data_). Central differences are used throughout; one-sided differences are applied at domain boundaries.
Computes \(y = -\Delta_h x\) on a flat vector in Grid3D layout:
\[ y_{i,j,k} = \frac{6\,x_{i,j,k} - \sum_{\pm} \bigl(x_{i\pm1,j,k} + x_{i,j\pm1,k} + x_{i,j,k\pm1}\bigr)}{h^2} \]
Boundary nodes satisfy \(y = x\) (identity row), making the operator SPD when paired with a zero-Dirichlet RHS – suitable for direct use with cg_matfree.
Replacing the 16-line triple-loop matvec in EM solve_poisson:
Used by: EM FieldSolver::solve_poisson.
Computes \((\nabla\phi)_{i,j,k}\) via second-order central differences, one-sided at boundaries:
\[ (\nabla\phi)^x_{i,j,k} = \frac{\phi_{i+1,j,k} - \phi_{i-1,j,k}}{2h}, \quad (\nabla\phi)^y_{i,j,k} = \frac{\phi_{i,j+1,k} - \phi_{i,j-1,k}}{2h}, \quad (\nabla\phi)^z_{i,j,k} = \frac{\phi_{i,j,k+1} - \phi_{i,j,k-1}}{2h} \]
Used by: EM FieldSolver::gradient, MagneticSolver::current_density.
Computes \(\nabla \cdot \mathbf{f}\) via central differences:
\[ (\nabla \cdot \mathbf{f})_{i,j,k} = \frac{f^x_{i+1,j,k} - f^x_{i-1,j,k}}{2h} + \frac{f^y_{i,j+1,k} - f^y_{i,j-1,k}}{2h} + \frac{f^z_{i,j,k+1} - f^z_{i,j,k-1}}{2h} \]
Used by: EM FieldSolver::divergence.
Computes \(\mathbf{B} = \nabla \times \mathbf{A}\) component-wise:
\[ B^x = \partial_y A^z - \partial_z A^y, \quad B^y = \partial_z A^x - \partial_x A^z, \quad B^z = \partial_x A^y - \partial_y A^x \]
All partial derivatives are second-order central differences, one-sided at boundaries.
Used by: EM FieldSolver::curl (and implicitly MagneticSolver::solve_magnetic_field via B = gradxA).
| Function | 2D/3D | BC | App | Purpose |
|---|---|---|---|---|
laplacian_stencil_2d | 2D | Dirichlet | TDSE | Hamiltonian matvec, energy expectation |
laplacian_stencil_2d_periodic | 2D | Periodic | NS | Viscosity step, pressure Poisson |
col_fiber_sweep | 2D | – | TDSE | Crank-Nicolson sweep along x |
row_fiber_sweep | 2D | – | TDSE | Crank-Nicolson sweep along y |
neg_laplacian_3d | 3D | Dirichlet | EM | Poisson solve matvec |
gradient_3d | 3D | one-sided | EM | Electric field, current density |
divergence_3d | 3D | one-sided | EM | Divergence diagnostic |
curl_3d | 3D | one-sided | EM | Magnetic field from vector potential |
Any new 2D app operating on an \(N \times N\) interior grid with Dirichlet or periodic BCs can directly call these functions without reimplementing stencil loops. For a heat equation solver:
For a 3D Poisson solve with Dirichlet BCs: