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/// The second-order 2D stencil stores \f$h^2\Delta_h u\f$:
5/// \f[
6/// y_{ij}=u_{i+1,j}+u_{i-1,j}+u_{i,j+1}+u_{i,j-1}-4u_{ij}.
7/// \f]
8#pragma once
9
10#include "core/vector.hpp"
11#include "fields/grid2d.hpp"
13#include "plot/plot.hpp"
14#include "spatial/grid3d.hpp"
15#include <algorithm>
16#include <cmath>
17#include <vector>
18
19namespace num {
20
21template<typename T>
23 for (int i = 0; i < N; ++i) {
24 for (int j = 0; j < N; ++j) {
25 int k = i * N + j;
26 T val = T(-4) * x[k];
27 if (i > 0)
28 val += x[k - N];
29 if (i < N - 1)
30 val += x[k + N];
31 if (j > 0)
32 val += x[k - 1];
33 if (j < N - 1)
34 val += x[k + 1];
35 y[k] = val;
36 }
37 }
38}
39
40/// @brief Periodic second-order 2D Laplacian stencil.
41template<typename T>
43 for (int i = 0; i < N; ++i) {
44 int ip = (i + 1) % N, im = (i + N - 1) % N;
45 const T* row = x.data() + i * N;
46 const T* row_p = x.data() + ip * N;
47 const T* row_m = x.data() + im * N;
48 T* d = y.data() + i * N;
49
50 d[0] = row_p[0] + row_m[0] + row[1] + row[N - 1] - T(4) * row[0];
51 for (int j = 1; j < N - 1; ++j)
52 d[j] = row_p[j] + row_m[j] + row[j + 1] + row[j - 1] - T(4) * row[j];
53 d[N - 1] = row_p[N - 1] + row_m[N - 1] + row[0] + row[N - 2] - T(4) * row[N - 1];
54 }
55}
56
57/// @brief Fourth-order 2D Laplacian cross stencil.
58///
59/// \f[
60/// y_{ij} = \frac{1}{12}\bigl(
61/// -x_{i-2,j} + 16x_{i-1,j} - 30x_{i,j} + 16x_{i+1,j} - x_{i+2,j}
62/// -x_{i,j-2} + 16x_{i,j-1} + 16x_{i,j+1} - x_{i,j+2}
63/// \bigr)
64/// \f]
65template<typename T>
67 for (int i = 0; i < N; ++i) {
68 for (int j = 0; j < N; ++j) {
69 int k = i * N + j;
70 T val = T(-30) * x[k];
71 // i-axis: +/-1
72 if (i > 0)
73 val += T(16) * x[(i - 1) * N + j];
74 if (i < N - 1)
75 val += T(16) * x[(i + 1) * N + j];
76 // i-axis: +/-2
77 if (i > 1)
78 val -= x[(i - 2) * N + j];
79 if (i < N - 2)
80 val -= x[(i + 2) * N + j];
81 // j-axis: +/-1
82 if (j > 0)
83 val += T(16) * x[k - 1];
84 if (j < N - 1)
85 val += T(16) * x[k + 1];
86 // j-axis: +/-2
87 if (j > 1)
88 val -= x[k - 2];
89 if (j < N - 2)
90 val -= x[k + 2];
91 y[k] = val / T(12);
92 }
93 }
94}
95
96/// Bilinear interpolation on a periodic NxN grid with configurable stagger
97/// offset.
98///
99/// field[i,j] is defined at physical position ((i + ox/h)*h, (j + oy/h)*h).
100/// Returns the interpolated field value at physical point (px, py).
101///
102/// @param ox x-axis origin offset in physical units (0 for unstaggered, h/2
103/// for v-face)
104/// @param oy y-axis origin offset in physical units (0 for unstaggered, h/2
105/// for u-face)
106///
107/// MAC grid usage:
108/// \f[
109/// \text{interp\_u}(px,py) = \texttt{sample\_2d\_periodic}(u, N, h,\; px,
110/// py,\; 0,\; h/2)
111/// \f]
112/// \f[
113/// \text{interp\_v}(px,py) = \texttt{sample\_2d\_periodic}(v, N, h,\; px,
114/// py,\; h/2,\; 0)
115/// \f]
116inline real sample_2d_periodic(const Vector& field,
117 idx N,
118 real h,
119 real px,
120 real py,
121 real ox,
122 real oy) {
123 real fx = std::fmod((px - ox) / h, static_cast<real>(N));
124 real fy = std::fmod((py - oy) / h, static_cast<real>(N));
125 if (fx < 0.0)
126 fx += N;
127 if (fy < 0.0)
128 fy += N;
129 idx i0 = static_cast<idx>(fx) % N;
130 idx i1 = (i0 + 1) % N;
131 real fi = fx - std::floor(fx);
132 idx j0 = static_cast<idx>(fy) % N;
133 idx j1 = (j0 + 1) % N;
134 real fj = fy - std::floor(fy);
135 return (1 - fi) * (1 - fj) * field[i0 * N + j0] + fi * (1 - fj) * field[i1 * N + j0]
136 + (1 - fi) * fj * field[i0 * N + j1] + fi * fj * field[i1 * N + j1];
137}
138
139/// @brief Apply a mutable 1D operation to each column fiber.
140template<typename T, typename F>
141void col_fiber_sweep(BasicVector<T>& data, int N, F&& f) {
142 std::vector<T> fiber(N);
143 for (int j = 0; j < N; ++j) {
144 for (int i = 0; i < N; ++i)
145 fiber[i] = data[i * N + j];
146 f(fiber);
147 for (int i = 0; i < N; ++i)
148 data[i * N + j] = fiber[i];
149 }
150}
151
152/// @brief Apply a mutable 1D operation to each row fiber.
153template<typename T, typename F>
154void row_fiber_sweep(BasicVector<T>& data, int N, F&& f) {
155 std::vector<T> fiber(N);
156 for (int i = 0; i < N; ++i) {
157 for (int j = 0; j < N; ++j)
158 fiber[j] = data[i * N + j];
159 f(fiber);
160 for (int j = 0; j < N; ++j)
161 data[i * N + j] = fiber[j];
162 }
163}
164
165/// @brief Fill grid values at \f$x_i=(i+1)h,\ y_j=(j+1)h\f$.
166template<typename F>
167void fill_grid(Vector& u, int N, double h, F&& f) {
168 for (int i = 0; i < N; ++i) {
169 double xi = (i + 1) * h;
170 for (int j = 0; j < N; ++j) {
171 u[static_cast<std::size_t>(i) * N + j] = f(xi, (j + 1) * h);
172 }
173 }
174}
175
176/// @brief Extract one row as \f$(x_j,u_j)\f$ plot data.
177inline Series row_slice(const Vector& u, int N, double h, int row) {
178 Series s;
179 s.reserve(static_cast<std::size_t>(N));
180 for (int j = 0; j < N; ++j) {
181 s.store((j + 1) * h, u[static_cast<std::size_t>(row) * N + j]);
182 }
183 return s;
184}
185
186/// @brief Extract one column as \f$(y_i,u_i)\f$ plot data.
187inline Series col_slice(const Vector& u, int N, double h, int col) {
188 Series s;
189 s.reserve(static_cast<std::size_t>(N));
190 for (int i = 0; i < N; ++i) {
191 s.store((i + 1) * h, u[static_cast<std::size_t>(i) * N + col]);
192 }
193 return s;
194}
195
196template<typename F>
197void fill_grid(ScalarField2D& g, F&& f) {
198 fill_grid(g.vec(), g.N(), g.h(), std::forward<F>(f));
199}
200
201inline Series row_slice(const ScalarField2D& g, int row) {
202 return row_slice(g.vec(), g.N(), g.h(), row);
203}
204
205inline Series col_slice(const ScalarField2D& g, int col) {
206 return col_slice(g.vec(), g.N(), g.h(), col);
207}
208
212
214 laplacian_stencil_2d_4th(x.vec(), y.vec(), x.N());
215}
216
218 real px,
219 real py,
220 real ox = 0.0,
221 real oy = 0.0) {
222 return sample_2d_periodic(g.vec(), static_cast<idx>(g.N()), g.h(), px, py, ox, oy);
223}
224
225namespace plt {
226inline void heatmap(const ScalarField2D& g, double vmin = 0.0, double vmax = 1.0) {
227 heatmap<ScalarField2D>(g, g.N(), g.h(), vmin, vmax);
228}
229} // namespace plt
230
231/// @brief Compute \f$-\Delta_h x\f$ on a 3D grid.
232inline void neg_laplacian_3d(const Vector& x,
233 Vector& y,
234 int nx,
235 int ny,
236 int nz,
237 double inv_dx2) {
238 auto flat = [&](int i, int j, int k) -> idx {
239 return static_cast<idx>(k * ny * nx + j * nx + i);
240 };
241 for (int k = 0; k < nz; ++k)
242 for (int j = 0; j < ny; ++j)
243 for (int i = 0; i < nx; ++i) {
244 idx id = flat(i, j, k);
245 if (i == 0 || i == nx - 1 || j == 0 || j == ny - 1 || k == 0
246 || k == nz - 1) {
247 y[id] = x[id];
248 } else {
249 y[id] = inv_dx2
250 * (6.0 * x[id] - x[flat(i + 1, j, k)] - x[flat(i - 1, j, k)]
251 - x[flat(i, j + 1, k)] - x[flat(i, j - 1, k)]
252 - x[flat(i, j, k + 1)] - x[flat(i, j, k - 1)]);
253 }
254 }
255}
256
257/// @brief Compute \f$\nabla\phi\f$ with central differences.
258inline void gradient_3d(const Grid3D& phi, Grid3D& gx, Grid3D& gy, Grid3D& gz) {
259 int nx = phi.nx(), ny = phi.ny(), nz = phi.nz();
260 double inv2dx = 1.0 / (2.0 * phi.dx());
261 for (int k = 0; k < nz; ++k)
262 for (int j = 0; j < ny; ++j)
263 for (int i = 0; i < nx; ++i) {
264 int ip = std::min(i + 1, nx - 1), im = std::max(i - 1, 0);
265 int jp = std::min(j + 1, ny - 1), jm = std::max(j - 1, 0);
266 int kp = std::min(k + 1, nz - 1), km = std::max(k - 1, 0);
267 gx(i, j, k) = (phi(ip, j, k) - phi(im, j, k)) * inv2dx;
268 gy(i, j, k) = (phi(i, jp, k) - phi(i, jm, k)) * inv2dx;
269 gz(i, j, k) = (phi(i, j, kp) - phi(i, j, km)) * inv2dx;
270 }
271}
272
273/// @brief Compute \f$\nabla\cdot f\f$ with central differences.
274inline void divergence_3d(const Grid3D& fx,
275 const Grid3D& fy,
276 const Grid3D& fz,
277 Grid3D& out) {
278 int nx = fx.nx(), ny = fx.ny(), nz = fx.nz();
279 double inv2dx = 1.0 / (2.0 * fx.dx());
280 for (int k = 0; k < nz; ++k)
281 for (int j = 0; j < ny; ++j)
282 for (int i = 0; i < nx; ++i) {
283 int ip = std::min(i + 1, nx - 1), im = std::max(i - 1, 0);
284 int jp = std::min(j + 1, ny - 1), jm = std::max(j - 1, 0);
285 int kp = std::min(k + 1, nz - 1), km = std::max(k - 1, 0);
286 out(i, j, k) =
287 ((fx(ip, j, k) - fx(im, j, k)) + (fy(i, jp, k) - fy(i, jm, k))
288 + (fz(i, j, kp) - fz(i, j, km)))
289 * inv2dx;
290 }
291}
292
293/// @brief Compute \f$\nabla\times A\f$ with central differences.
294inline void curl_3d(const Grid3D& ax,
295 const Grid3D& ay,
296 const Grid3D& az,
297 Grid3D& bx,
298 Grid3D& by,
299 Grid3D& bz) {
300 int nx = ax.nx(), ny = ax.ny(), nz = ax.nz();
301 double inv2dx = 1.0 / (2.0 * ax.dx());
302 for (int k = 0; k < nz; ++k)
303 for (int j = 0; j < ny; ++j)
304 for (int i = 0; i < nx; ++i) {
305 int ip = std::min(i + 1, nx - 1), im = std::max(i - 1, 0);
306 int jp = std::min(j + 1, ny - 1), jm = std::max(j - 1, 0);
307 int kp = std::min(k + 1, nz - 1), km = std::max(k - 1, 0);
308 bx(i, j, k) =
309 (az(i, jp, k) - az(i, jm, k) - ay(i, j, kp) + ay(i, j, km)) * inv2dx;
310 by(i, j, k) =
311 (ax(i, j, kp) - ax(i, j, km) - az(ip, j, k) + az(im, j, k)) * inv2dx;
312 bz(i, j, k) =
313 (ay(ip, j, k) - ay(im, j, k) - ax(i, jp, k) + ax(i, jm, k)) * inv2dx;
314 }
315}
316
317} // namespace num
Dense owning vector.
Definition vector.hpp:16
double dx() const
Definition grid3d.hpp:24
int ny() const
Definition grid3d.hpp:22
int nz() const
Definition grid3d.hpp:23
int nx() const
Definition grid3d.hpp:21
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)
Definition stencil.hpp:226
void laplacian_stencil_2d(const BasicVector< T > &x, BasicVector< T > &y, int N)
Definition stencil.hpp:22
void row_fiber_sweep(BasicVector< T > &data, int N, F &&f)
Apply a mutable 1D operation to each row fiber.
Definition stencil.hpp:154
double real
Definition types.hpp:10
constexpr real phi
Golden ratio.
Definition math.hpp:45
void curl_3d(const Grid3D &ax, const Grid3D &ay, const Grid3D &az, Grid3D &bx, Grid3D &by, Grid3D &bz)
Compute with central differences.
Definition stencil.hpp:294
void neg_laplacian_3d(const Vector &x, Vector &y, int nx, int ny, int nz, double inv_dx2)
Compute on a 3D grid.
Definition stencil.hpp:232
real sample_2d_periodic(const Vector &field, idx N, real h, real px, real py, real ox, real oy)
Definition stencil.hpp:116
void laplacian_stencil_2d_4th(const BasicVector< T > &x, BasicVector< T > &y, int N)
Fourth-order 2D Laplacian cross stencil.
Definition stencil.hpp:66
Series col_slice(const Vector &u, int N, double h, int col)
Extract one column as plot data.
Definition stencil.hpp:187
std::size_t idx
Definition types.hpp:11
void laplacian_stencil_2d_periodic(const BasicVector< T > &x, BasicVector< T > &y, int N)
Periodic second-order 2D Laplacian stencil.
Definition stencil.hpp:42
void divergence_3d(const Grid3D &fx, const Grid3D &fy, const Grid3D &fz, Grid3D &out)
Compute with central differences.
Definition stencil.hpp:274
void gradient_3d(const Grid3D &phi, Grid3D &gx, Grid3D &gy, Grid3D &gz)
Compute with central differences.
Definition stencil.hpp:258
void col_fiber_sweep(BasicVector< T > &data, int N, F &&f)
Apply a mutable 1D operation to each column fiber.
Definition stencil.hpp:141
Series row_slice(const Vector &u, int N, double h, int row)
Extract one row as plot data.
Definition stencil.hpp:177
void fill_grid(Vector &u, int N, double h, F &&f)
Fill grid values at .
Definition stencil.hpp:167
Matplotlib-style plotting via a gnuplot pipe.
Scalar field on a 2D uniform interior grid.
void store(double x, double y)
Definition plot.hpp:17
Dense vector storage and operations.