numerics 0.1.0
Loading...
Searching...
No Matches
stencil.hpp
Go to the documentation of this file.
1/// @file pde/stencil.hpp
2/// @brief Higher-order stencil and grid-sweep utilities.
3///
4/// 2D (NxN grid, row-major storage: index = i*N + j, Dirichlet or periodic BC):
5/// laplacian_stencil_2d -- y = (sum of 4 nbrs) - 4x, Dirichlet
6/// laplacian_stencil_2d_periodic -- same, periodic wrap (boundary-peeled for
7/// vectorization) sample_2d_periodic -- bilinear interpolation
8/// with stagger offset col_fiber_sweep -- for each column j:
9/// extract fiber, call f, write back row_fiber_sweep -- for
10/// each row i: extract fiber, call f, write back
11///
12/// 3D (Grid3D, central-difference, one-sided at boundaries):
13/// neg_laplacian_3d -- y = -Lap(x) (Dirichlet: boundary rows = identity)
14/// gradient_3d -- (gx,gy,gz) = grad(phi)
15/// divergence_3d -- div f = d(fx)/dx + d(fy)/dy + d(fz)/dz
16/// curl_3d -- curl(A)
17#pragma once
18
19#include "core/vector.hpp"
20#include "plot/plot.hpp"
21#include "spatial/grid3d.hpp"
22#include "fields/grid2d.hpp"
24#include <vector>
25#include <algorithm>
26#include <cmath>
27
28namespace num {
29
30// 2-D stencils (NxN, row-major: data[i*N + j])
31
32/// Compute y[i,j] = x[i+1,j] + x[i-1,j] + x[i,j+1] + x[i,j-1] - 4*x[i,j]///
33/// Dirichlet: out-of-bounds neighbors contribute 0. Works for T = real or cplx.
34template<typename T>
36 for (int i = 0; i < N; ++i) {
37 for (int j = 0; j < N; ++j) {
38 int k = i * N + j;
39 T val = T(-4) * x[k];
40 if (i > 0)
41 val += x[k - N];
42 if (i < N - 1)
43 val += x[k + N];
44 if (j > 0)
45 val += x[k - 1];
46 if (j < N - 1)
47 val += x[k + 1];
48 y[k] = val;
49 }
50 }
51}
52
53/// Same as laplacian_stencil_2d but with periodic (wrap-around) boundaries.
54///
55/// The j boundaries (j=0 and j=N-1) are peeled out of the inner loop so the
56/// interior range j=1..N-2 contains no modulo arithmetic and is
57/// auto-vectorizable. Row wrapping (i+/-1) uses precomputed indices.
58template<typename T>
61 int N) {
62 for (int i = 0; i < N; ++i) {
63 int ip = (i + 1) % N, im = (i + N - 1) % N;
64 const T* row = x.data() + i * N;
65 const T* row_p = x.data() + ip * N;
66 const T* row_m = x.data() + im * N;
67 T* d = y.data() + i * N;
68
69 // j = 0: left neighbor wraps to j = N-1
70 d[0] = row_p[0] + row_m[0] + row[1] + row[N - 1] - T(4) * row[0];
71 // j = 1..N-2: no wrap, compiler can vectorise
72 for (int j = 1; j < N - 1; ++j)
73 d[j] = row_p[j] + row_m[j] + row[j + 1] + row[j - 1]
74 - T(4) * row[j];
75 // j = N-1: right neighbor wraps to j = 0
76 d[N - 1] = row_p[N - 1] + row_m[N - 1] + row[0] + row[N - 2]
77 - T(4) * row[N - 1];
78 }
79}
80
81/// 4th-order accurate 2D Laplacian: 13-point cross stencil, Dirichlet BCs.
82///
83/// Sums the standard 4th-order 1D stencil independently along each axis:
84/// \f[
85/// y_{i,j} = \tfrac{1}{12}\bigl(
86/// -x_{i-2,j} + 16x_{i-1,j} - 30x_{i,j} + 16x_{i+1,j} - x_{i+2,j}
87/// -x_{i,j-2} + 16x_{i,j-1} + 16x_{i,j+1} - x_{i,j+2}
88/// \bigr)
89/// \f]
90///
91/// Out-of-bounds neighbors are treated as 0 (Dirichlet).
92/// Divide by h² to recover the discrete Laplacian: \f$\nabla^2 u \approx y/h^2\f$.
93/// Truncation error: \f$O(h^4)\f$.
94template<typename T>
96 for (int i = 0; i < N; ++i) {
97 for (int j = 0; j < N; ++j) {
98 int k = i * N + j;
99 T val = T(-30) * x[k];
100 // i-axis: ±1
101 if (i > 0) val += T(16) * x[(i - 1) * N + j];
102 if (i < N - 1) val += T(16) * x[(i + 1) * N + j];
103 // i-axis: ±2
104 if (i > 1) val -= x[(i - 2) * N + j];
105 if (i < N - 2) val -= x[(i + 2) * N + j];
106 // j-axis: ±1
107 if (j > 0) val += T(16) * x[k - 1];
108 if (j < N - 1) val += T(16) * x[k + 1];
109 // j-axis: ±2
110 if (j > 1) val -= x[k - 2];
111 if (j < N - 2) val -= x[k + 2];
112 y[k] = val / T(12);
113 }
114 }
115}
116
117/// Bilinear interpolation on a periodic NxN grid with configurable stagger
118/// offset.
119///
120/// field[i,j] is defined at physical position ((i + ox/h)*h, (j + oy/h)*h).
121/// Returns the interpolated field value at physical point (px, py).
122///
123/// @param ox x-axis origin offset in physical units (0 for unstaggered, h/2
124/// for v-face)
125/// @param oy y-axis origin offset in physical units (0 for unstaggered, h/2
126/// for u-face)
127///
128/// MAC grid usage:
129/// \f[
130/// \text{interp\_u}(px,py) = \texttt{sample\_2d\_periodic}(u, N, h,\; px,
131/// py,\; 0,\; h/2)
132/// \f]
133/// \f[
134/// \text{interp\_v}(px,py) = \texttt{sample\_2d\_periodic}(v, N, h,\; px,
135/// py,\; h/2,\; 0)
136/// \f]
137inline real sample_2d_periodic(const Vector& field,
138 idx N,
139 real h,
140 real px,
141 real py,
142 real ox,
143 real oy) {
144 real fx = std::fmod((px - ox) / h, static_cast<real>(N));
145 real fy = std::fmod((py - oy) / h, static_cast<real>(N));
146 if (fx < 0.0)
147 fx += N;
148 if (fy < 0.0)
149 fy += N;
150 idx i0 = static_cast<idx>(fx) % N;
151 idx i1 = (i0 + 1) % N;
152 real fi = fx - std::floor(fx);
153 idx j0 = static_cast<idx>(fy) % N;
154 idx j1 = (j0 + 1) % N;
155 real fj = fy - std::floor(fy);
156 return (1 - fi) * (1 - fj) * field[i0 * N + j0]
157 + fi * (1 - fj) * field[i1 * N + j0]
158 + (1 - fi) * fj * field[i0 * N + j1] + fi * fj * field[i1 * N + j1];
159}
160
161/// For each column j in [0,N), extract the x-direction fiber
162/// data[0..N-1, j] into a std::vector<T>, call f(fiber), then write back.
163///
164/// f must have signature: void(std::vector<T>&) and modifies in-place.
165///
166/// Use for ADI / Crank-Nicolson sweeps along x.
167template<typename T, typename F>
168void col_fiber_sweep(BasicVector<T>& data, int N, F&& f) {
169 std::vector<T> fiber(N);
170 for (int j = 0; j < N; ++j) {
171 for (int i = 0; i < N; ++i)
172 fiber[i] = data[i * N + j];
173 f(fiber);
174 for (int i = 0; i < N; ++i)
175 data[i * N + j] = fiber[i];
176 }
177}
178
179/// For each row i in [0,N), extract the y-direction fiber
180/// data[i, 0..N-1] into a std::vector<T>, call f(fiber), then write back.
181///
182/// Use for ADI / Crank-Nicolson sweeps along y.
183template<typename T, typename F>
184void row_fiber_sweep(BasicVector<T>& data, int N, F&& f) {
185 std::vector<T> fiber(N);
186 for (int i = 0; i < N; ++i) {
187 for (int j = 0; j < N; ++j)
188 fiber[j] = data[i * N + j];
189 f(fiber);
190 for (int j = 0; j < N; ++j)
191 data[i * N + j] = fiber[j];
192 }
193}
194
195// 2-D grid utilities
196
197/// Initialize an NxN field (row-major, node (i,j) at ((i+1)*h, (j+1)*h))
198/// from a callable f(x, y) -> real.
199template<typename F>
200void fill_grid(Vector& u, int N, double h, F&& f) {
201 for (int i = 0; i < N; ++i) {
202 double xi = (i + 1) * h;
203 for (int j = 0; j < N; ++j) {
204 u[static_cast<std::size_t>(i) * N + j] = f(xi, (j + 1) * h);
205 }
206 }
207}
208
209/// Extract row `row` of an NxN field as an (x, value) Series;
210/// node j sits at x = (j+1)*h.
211inline Series row_slice(const Vector& u, int N, double h, int row) {
212 Series s;
213 s.reserve(static_cast<std::size_t>(N));
214 for (int j = 0; j < N; ++j) {
215 s.store((j + 1) * h, u[static_cast<std::size_t>(row) * N + j]);
216 }
217 return s;
218}
219
220/// Extract column `col` of an NxN field as a (y, value) Series;
221/// node i sits at y = (i+1)*h.
222inline Series col_slice(const Vector& u, int N, double h, int col) {
223 Series s;
224 s.reserve(static_cast<std::size_t>(N));
225 for (int i = 0; i < N; ++i) {
226 s.store((i + 1) * h, u[static_cast<std::size_t>(i) * N + col]);
227 }
228 return s;
229}
230
231// ScalarField2D overloads -- N and h are carried by the grid
232
233template<typename F>
234void fill_grid(ScalarField2D& g, F&& f) {
235 fill_grid(g.vec(), g.N(), g.h(), std::forward<F>(f));
236}
237
238inline Series row_slice(const ScalarField2D& g, int row) {
239 return row_slice(g.vec(), g.N(), g.h(), row);
240}
241
242inline Series col_slice(const ScalarField2D& g, int col) {
243 return col_slice(g.vec(), g.N(), g.h(), col);
244}
245
249
251 laplacian_stencil_2d_4th(x.vec(), y.vec(), x.N());
252}
253
255 real ox = 0.0, real oy = 0.0) {
256 return sample_2d_periodic(g.vec(), static_cast<idx>(g.N()), g.h(), px, py, ox, oy);
257}
258
259namespace plt {
260/// Heatmap overload for ScalarField2D -- no need to pass N or h separately.
261inline void heatmap(const ScalarField2D& g, double vmin = 0.0, double vmax = 1.0) {
262 heatmap<ScalarField2D>(g, g.N(), g.h(), vmin, vmax);
263}
264} // namespace plt
265
266// 3-D stencils (Grid3D)
267
268/// Compute the negative Laplacian: y = -Lap(x) = (6x[i,j,k] - sum of 6 nbrs) /
269/// dx^2 Dirichlet BC: boundary nodes satisfy y[bdry] = x[bdry] (identity row,
270/// so that the system is SPD when used with a b=0 boundary RHS).
271///
272/// x and y are flat vectors in Grid3D layout: idx = k*ny*nx + j*nx + i.
273inline void neg_laplacian_3d(const Vector& x,
274 Vector& y,
275 int nx,
276 int ny,
277 int nz,
278 double inv_dx2) {
279 auto flat = [&](int i, int j, int k) -> idx {
280 return static_cast<idx>(k * ny * nx + j * nx + i);
281 };
282 for (int k = 0; k < nz; ++k)
283 for (int j = 0; j < ny; ++j)
284 for (int i = 0; i < nx; ++i) {
285 idx id = flat(i, j, k);
286 if (i == 0 || i == nx - 1 || j == 0 || j == ny - 1 || k == 0
287 || k == nz - 1) {
288 y[id] = x[id];
289 } else {
290 y[id] = inv_dx2
291 * (6.0 * x[id] - x[flat(i + 1, j, k)]
292 - x[flat(i - 1, j, k)] - x[flat(i, j + 1, k)]
293 - x[flat(i, j - 1, k)] - x[flat(i, j, k + 1)]
294 - x[flat(i, j, k - 1)]);
295 }
296 }
297}
298
299/// Compute the central-difference gradient of a scalar field./// One-sided
300/// differences at domain boundaries. gx, gy, gz must already be allocated with
301/// the same dimensions as phi.
302inline void gradient_3d(const Grid3D& phi, Grid3D& gx, Grid3D& gy, Grid3D& gz) {
303 int nx = phi.nx(), ny = phi.ny(), nz = phi.nz();
304 double inv2dx = 1.0 / (2.0 * phi.dx());
305 for (int k = 0; k < nz; ++k)
306 for (int j = 0; j < ny; ++j)
307 for (int i = 0; i < nx; ++i) {
308 int ip = std::min(i + 1, nx - 1), im = std::max(i - 1, 0);
309 int jp = std::min(j + 1, ny - 1), jm = std::max(j - 1, 0);
310 int kp = std::min(k + 1, nz - 1), km = std::max(k - 1, 0);
311 gx(i, j, k) = (phi(ip, j, k) - phi(im, j, k)) * inv2dx;
312 gy(i, j, k) = (phi(i, jp, k) - phi(i, jm, k)) * inv2dx;
313 gz(i, j, k) = (phi(i, j, kp) - phi(i, j, km)) * inv2dx;
314 }
315}
316
317/// Compute the central-difference divergence of a vector field (fx, fy, fz).
318/// One-sided differences at domain boundaries.
319/// out must already be allocated with the same dimensions.
320inline void divergence_3d(const Grid3D& fx,
321 const Grid3D& fy,
322 const Grid3D& fz,
323 Grid3D& out) {
324 int nx = fx.nx(), ny = fx.ny(), nz = fx.nz();
325 double inv2dx = 1.0 / (2.0 * fx.dx());
326 for (int k = 0; k < nz; ++k)
327 for (int j = 0; j < ny; ++j)
328 for (int i = 0; i < nx; ++i) {
329 int ip = std::min(i + 1, nx - 1), im = std::max(i - 1, 0);
330 int jp = std::min(j + 1, ny - 1), jm = std::max(j - 1, 0);
331 int kp = std::min(k + 1, nz - 1), km = std::max(k - 1, 0);
332 out(i, j, k) = ((fx(ip, j, k) - fx(im, j, k))
333 + (fy(i, jp, k) - fy(i, jm, k))
334 + (fz(i, j, kp) - fz(i, j, km)))
335 * inv2dx;
336 }
337}
338
339/// Compute the central-difference curl(A) of a vector field.
340/// One-sided differences at domain boundaries.
341/// bx, by, bz must already be allocated with the same dimensions as ax, ay, az.
342inline void curl_3d(const Grid3D& ax,
343 const Grid3D& ay,
344 const Grid3D& az,
345 Grid3D& bx,
346 Grid3D& by,
347 Grid3D& bz) {
348 int nx = ax.nx(), ny = ax.ny(), nz = ax.nz();
349 double inv2dx = 1.0 / (2.0 * ax.dx());
350 for (int k = 0; k < nz; ++k)
351 for (int j = 0; j < ny; ++j)
352 for (int i = 0; i < nx; ++i) {
353 int ip = std::min(i + 1, nx - 1), im = std::max(i - 1, 0);
354 int jp = std::min(j + 1, ny - 1), jm = std::max(j - 1, 0);
355 int kp = std::min(k + 1, nz - 1), km = std::max(k - 1, 0);
356 bx(i, j, k) = (az(i, jp, k) - az(i, jm, k) - ay(i, j, kp)
357 + ay(i, j, km))
358 * inv2dx;
359 by(i, j, k) = (ax(i, j, kp) - ax(i, j, km) - az(ip, j, k)
360 + az(im, j, k))
361 * inv2dx;
362 bz(i, j, k) = (ay(ip, j, k) - ay(im, j, k) - ax(i, jp, k)
363 + ax(i, jm, k))
364 * inv2dx;
365 }
366}
367
368} // namespace num
Dense vector with optional GPU storage, templated over scalar type T.
Definition vector.hpp:24
double dx() const
Definition grid3d.hpp:30
int ny() const
Definition grid3d.hpp:24
int nz() const
Definition grid3d.hpp:27
int nx() const
Definition grid3d.hpp:21
Vector & vec()
Satisfy VecField concept: exposes the underlying flat vector.
2D uniform interior grid: geometry only, no field data.
3D Cartesian scalar grid backed by num::Vector storage.
void heatmap(const ScalarField2D &g, double vmin=0.0, double vmax=1.0)
Heatmap overload for ScalarField2D – no need to pass N or h separately.
Definition stencil.hpp:261
void laplacian_stencil_2d(const BasicVector< T > &x, BasicVector< T > &y, int N)
Definition stencil.hpp:35
void row_fiber_sweep(BasicVector< T > &data, int N, F &&f)
Definition stencil.hpp:184
double real
Definition types.hpp:10
constexpr real phi
Golden ratio.
Definition math.hpp:44
void curl_3d(const Grid3D &ax, const Grid3D &ay, const Grid3D &az, Grid3D &bx, Grid3D &by, Grid3D &bz)
Definition stencil.hpp:342
void neg_laplacian_3d(const Vector &x, Vector &y, int nx, int ny, int nz, double inv_dx2)
Definition stencil.hpp:273
real sample_2d_periodic(const Vector &field, idx N, real h, real px, real py, real ox, real oy)
Definition stencil.hpp:137
void laplacian_stencil_2d_4th(const BasicVector< T > &x, BasicVector< T > &y, int N)
Definition stencil.hpp:95
Series col_slice(const Vector &u, int N, double h, int col)
Definition stencil.hpp:222
std::size_t idx
Definition types.hpp:11
void laplacian_stencil_2d_periodic(const BasicVector< T > &x, BasicVector< T > &y, int N)
Definition stencil.hpp:59
void divergence_3d(const Grid3D &fx, const Grid3D &fy, const Grid3D &fz, Grid3D &out)
Definition stencil.hpp:320
void gradient_3d(const Grid3D &phi, Grid3D &gx, Grid3D &gy, Grid3D &gz)
Definition stencil.hpp:302
void col_fiber_sweep(BasicVector< T > &data, int N, F &&f)
Definition stencil.hpp:168
Series row_slice(const Vector &u, int N, double h, int row)
Definition stencil.hpp:211
void fill_grid(Vector &u, int N, double h, F &&f)
Definition stencil.hpp:200
Matplotlib-style plotting via a gnuplot pipe.
Scalar field on a 2D uniform interior grid.
Ordered (x, y) series.
Definition plot.hpp:42
void store(double x, double y)
Append a point to the series.
Definition plot.hpp:45
Vector operations.