flow 1.3.0
Kokkos cut-cell IBM incompressible Navier-Stokes solver + pnm pore extraction
Loading...
Searching...
No Matches
test_cutcellmg_aniso.cpp
Go to the documentation of this file.
1// G4 — the ANISOTROPIC pressure hierarchy: the OPERATOR converges at the cubic rate (commit C2)
2// and the aspect-ratio COARSENING RULE gives it the cubic RATE too (commit C3)
3// (flow/doc/anisotropic_metric.md §8.5).
4//
5// `CutcellMG` is driven DIRECTLY (no Solver, no velocity) on a triply periodic all-fluid box, the
6// way the pressure gates of this suite are written: the only thing under test is the operator the
7// per-axis weights of §1.3 assemble and the multigrid that inverts it.
8//
9// cells (N, 2N, N/2) on the CUBE L^3 -> h = (dx, dx/2, 2dx), dx = L/N
10// setOpenness(o = 1, w_a = (dx/h_a)^2) = (1, 4, 1/4) [the §8.5 numbers]
11//
12// so the assembled operator is A = -dx^2 * (the anisotropic 7-point Laplacian). With the
13// MANUFACTURED solution
14//
15// phi = cos(2 pi x/L) cos(2 pi y/L) cos(2 pi z/L), A phi = -dx^2 lap(phi) = 3 k^2 dx^2 phi
16//
17// evaluated at the physical cell centres and the CONTINUOUS right-hand side b = 3 k^2 dx^2 phi
18// (k = 2 pi / L), the discrete solution is exactly (3 k^2 dx^2 / Lambda) phi with
19// Lambda = sum_a w_a 2(1 - cos(k h_a)), i.e. the L2 error is
20// |3k^2dx^2/Lambda - 1| ||phi||_2 = k^2 (hx^2 + hy^2 + hz^2)/36 + O(h^4) -- SECOND ORDER in the
21// mesh, with the coarse z axis (h_z = 2 dx) dominating. That is the statement of the gate:
22// giving the coarse axis its own w_a is what keeps the anisotropic operator second order, and a
23// missing or mis-assigned weight destroys the rate rather than merely the constant.
24//
25// GATE (ORDER, commit C2): least-squares order over N = 16, 32, 64 >= 1.95 on the stretched grid;
26// the cubic control (N, N, N) with w = (1,1,1) gives ~2.00.
27//
28// COMMIT C3 adds the other three halves of §8.5, all about the COARSENING RULE of §5:
29//
30// (level table) levelRatios() on (N, 2N, N/2) with hp = (1, 1/2, 2) is
31// (1,2,1), (2,2,1), (2,2,2), (2,2,2), ... -- always coarsen the FINEST
32// coarsenable axis, defer one already >= theta times coarser -- and on (N, N, N)
33// it is today's (2,2,2), ... unchanged. The isotropic table is asserted to be
34// BITWISE the no-metric one, which is the whole G0 statement of this commit.
35// (rate a) all-fluid, random mean-zero RHS: the residual reduction per V-cycle over
36// cycles 2-8 <= 0.2.
37// (rate b/c) with the Zick & Homsy sphere openness (phi = 0.216, sampled SDF through the
38// solver's own buildOpenness with hp), MG-PCG iterations to rtol 1e-10 on the
39// stretched grid <= cubic (SAME cell count) + 2 at N = 32 and 64; and the same
40// measurement under --theta=1e9 (today's full coarsening) must be
41// worse or equal. That ablation is a SEPARATE RUN of this binary
42// (`test_cutcellmg_aniso --theta=1e9`); with --theta given the gates below print
43// instead of asserting and the run is a measurement, not a test.
44//
45// Both hierarchies share one rule (CutcellMG::mgChooseRatio, called by VelocityMG too), so this
46// file gates the rule itself.
47#include <cmath>
48#include <cstdio>
49#include <cstring>
50#include <Kokkos_Core.hpp>
51#include <vector>
52
53#include "mac_cutcell.hpp" // buildOpenness (the solver's own aperture model)
54#include "mac_cutcell_mg.hpp"
55
56using namespace peclet::flow;
57
58namespace {
59
64double gTheta = 2.0;
65
67bool ablation() {
68 return gTheta != 2.0;
69}
70int failures = 0;
71
72struct Run {
73 double l2 = 0.0;
74 double l2exact = 0.0;
75 double rrel = 0.0;
76 int iters = 0;
77 int levels = 0;
78};
79
82Run solveOne(int N, bool stretched, bool fcg = false) {
83 const int G = CutcellMG::G; // level-0 ghost width (1)
84 const int nx = N, ny = stretched ? 2 * N : N, nz = stretched ? N / 2 : N;
85 const double L = 1.0;
86 const double hx = L / nx, hy = L / ny, hz = L / nz;
87 const double dref = hx; // the reference length the weights are built on
88 const double wx = (dref / hx) * (dref / hx), wy = (dref / hy) * (dref / hy),
89 wz = (dref / hz) * (dref / hz);
90 const C3 e{nx + 2 * G, ny + 2 * G, nz + 2 * G};
91 const std::size_t n = (std::size_t)e.x * e.y * e.z;
92 const double k = 2.0 * M_PI / L;
93
94 CCField ox("ox", n), oy("oy", n), oz("oz", n);
95 Kokkos::deep_copy(ox, 1.0); // all fluid
96 Kokkos::deep_copy(oy, 1.0);
97 Kokkos::deep_copy(oz, 1.0);
98
99 // b and the exact solution at the physical cell centres of the inner block.
100 std::vector<double> hb(n, 0.0), hex(n, 0.0);
101 for (int z = G; z < e.z - G; ++z)
102 for (int y = G; y < e.y - G; ++y)
103 for (int x = G; x < e.x - G; ++x) {
104 const double px = ((x - G) + 0.5) * hx, py = ((y - G) + 0.5) * hy,
105 pz = ((z - G) + 0.5) * hz;
106 const double ph = std::cos(k * px) * std::cos(k * py) * std::cos(k * pz);
107 const std::size_t i =
108 (std::size_t)x + (std::size_t)y * e.x + (std::size_t)z * (std::size_t)e.x * e.y;
109 hex[i] = ph;
110 hb[i] = 3.0 * k * k * dref * dref * ph; // A phi = -dref^2 lap(phi)
111 }
112 CCField b("b", n), xf("x", n), r("r", n), p("p", n), zz("z", n), Ap("Ap", n);
113 {
114 auto m = Kokkos::create_mirror_view(b);
115 for (std::size_t i = 0; i < n; ++i)
116 m(i) = hb[i];
117 Kokkos::deep_copy(b, m);
118 }
119 Kokkos::deep_copy(xf, 0.0);
120
121 CutcellMG mg;
123 // C3: the per-axis spacings BEFORE init (trap 5) -- hp = h_a/dref = (1, 1/2, 2) stretched,
124 // (1,1,1) cubic. The rule is scale-free (it compares H's), so this IS the note's hp.
125 const double hp[3] = {hx / dref, hy / dref, hz / dref};
126 mg.setMetric(hp);
127 mg.init(nx, ny, nz, 6);
128 mg.setOpenness(CCConst(ox), CCConst(oy), CCConst(oz), wx, wy, wz);
129
130 Run out;
131 out.levels = mg.nLevels();
132 if (fcg) {
133 CCField zp("zp", n);
134 out.iters = mg.solveFCG(b, xf, r, p, zz, zp, Ap, 500, 1e-10, 2, 2, 12);
135 } else {
136 out.iters = mg.solvePCG(b, xf, r, p, zz, Ap, 500, 1e-10, 2, 2, 12);
137 }
138 // The EXACT solution of the discrete system: phi = cos*cos*cos is an eigenvector of the weighted
139 // 7-point operator with eigenvalue Lambda = sum_a w_a 2(1 - cos(k h_a)), so
140 // phi_h = (3 k^2 dref^2 / Lambda) phi and the L2 error is |3 k^2 dref^2 / Lambda - 1| * ||phi||_2
141 // with ||phi||_2 = (1/2)^(3/2) exactly (a full period on every axis). Printed beside the
142 // measured one: it separates a discretization statement from an unconverged solve.
143 {
144 const double lam = wx * 2.0 * (1.0 - std::cos(k * hx)) + wy * 2.0 * (1.0 - std::cos(k * hy)) +
145 wz * 2.0 * (1.0 - std::cos(k * hz));
146 out.l2exact = std::fabs(3.0 * k * k * dref * dref / lam - 1.0) * std::pow(0.5, 1.5);
147 }
148 { // the residual solvePCG/solveFCG left behind, relative to |b|inf
149 auto hr = Kokkos::create_mirror_view(r);
150 Kokkos::deep_copy(hr, r);
151 double rm = 0.0, bm = 0.0;
152 for (int z = G; z < e.z - G; ++z)
153 for (int y = G; y < e.y - G; ++y)
154 for (int x = G; x < e.x - G; ++x) {
155 const std::size_t i =
156 (std::size_t)x + (std::size_t)y * e.x + (std::size_t)z * (std::size_t)e.x * e.y;
157 if (!(std::fabs(hr(i)) <= rm))
158 rm = std::fabs(hr(i));
159 if (!(std::fabs(hb[i]) <= bm))
160 bm = std::fabs(hb[i]);
161 }
162 out.rrel = bm > 0.0 ? rm / bm : rm;
163 }
164
165 auto hxv = Kokkos::create_mirror_view(xf);
166 Kokkos::deep_copy(hxv, xf);
167 // Both fields are defined up to a constant on a periodic box (the solver removes the mean of
168 // its own iterate); compare them mean-free.
169 double ma = 0.0, mb = 0.0;
170 long cnt = 0;
171 for (int z = G; z < e.z - G; ++z)
172 for (int y = G; y < e.y - G; ++y)
173 for (int x = G; x < e.x - G; ++x) {
174 const std::size_t i =
175 (std::size_t)x + (std::size_t)y * e.x + (std::size_t)z * (std::size_t)e.x * e.y;
176 ma += hxv(i);
177 mb += hex[i];
178 ++cnt;
179 }
180 ma /= (double)cnt;
181 mb /= (double)cnt;
182 double acc = 0.0;
183 for (int z = G; z < e.z - G; ++z)
184 for (int y = G; y < e.y - G; ++y)
185 for (int x = G; x < e.x - G; ++x) {
186 const std::size_t i =
187 (std::size_t)x + (std::size_t)y * e.x + (std::size_t)z * (std::size_t)e.x * e.y;
188 const double d = (hxv(i) - ma) - (hex[i] - mb);
189 acc += d * d;
190 }
191 out.l2 = std::sqrt(acc / (double)cnt);
192 return out;
193}
194
196double fitOrder(const std::vector<int>& Ns, const std::vector<double>& err) {
197 const int m = (int)Ns.size();
198 double sx = 0, sy = 0, sxx = 0, sxy = 0;
199 for (int i = 0; i < m; ++i) {
200 const double X = std::log((double)Ns[i]), Y = std::log(err[i]);
201 sx += X;
202 sy += Y;
203 sxx += X * X;
204 sxy += X * Y;
205 }
206 return -(m * sxy - sx * sy) / (m * sxx - sx * sx);
207}
208
209void ladder(bool stretched, bool fcg, double minOrder) {
210 const std::vector<int> Ns{16, 32, 64};
211 std::vector<double> err;
212 std::printf(" %s, %s:\n",
213 stretched ? "stretched (N, 2N, N/2), w = (1, 4, 1/4)"
214 : "cubic control (N, N, N), w = (1, 1, 1)",
215 fcg ? "FCG (the converged control)" : "MG-PCG (the §8.5 driver)");
216 for (int N : Ns) {
217 const Run rr = solveOne(N, stretched, fcg);
218 err.push_back(rr.l2);
219 std::printf(
220 " N = %3d cells %4d x %4d x %4d levels %d %3d iters r/|b| %.2e "
221 "L2 err %.6e (exact discrete %.6e)",
222 N, N, stretched ? 2 * N : N, stretched ? N / 2 : N, rr.levels, rr.iters, rr.rrel, rr.l2,
223 rr.l2exact);
224 if (err.size() > 1)
225 std::printf(" order %.4f", std::log(err[err.size() - 2] / err.back()) / std::log(2.0));
226 std::printf("\n");
227 }
228 const double ord = fitOrder(Ns, err);
229 std::printf(" least-squares order = %.4f (require >= %.2f)\n", ord, minOrder);
230 if (!(ord >= minOrder)) {
231 std::fprintf(stderr, "FAIL: observed order %.4f < %.2f\n", ord, minOrder);
232 ++failures;
233 }
234}
235
236// ---------------------------------------------------------------------------------------------
237// C3 — the aspect-ratio coarsening rule of doc/anisotropic_metric.md §5
238// ---------------------------------------------------------------------------------------------
239
242std::vector<C3> levelTable(int nx, int ny, int nz, const double hp[3], int levels,
243 bool withMetric) {
244 CutcellMG mg;
246 if (withMetric)
247 mg.setMetric(hp);
248 mg.init(nx, ny, nz, levels);
249 return mg.levelRatios();
250}
251
252void printTable(const char* what, const std::vector<C3>& t) {
253 std::printf(" %-46s", what);
254 for (const C3& r : t)
255 std::printf(" (%d,%d,%d)", r.x, r.y, r.z);
256 std::printf("\n");
257}
258
259bool sameTable(const std::vector<C3>& a, const std::vector<C3>& b) {
260 if (a.size() != b.size())
261 return false;
262 for (std::size_t i = 0; i < a.size(); ++i)
263 if (a[i].x != b[i].x || a[i].y != b[i].y || a[i].z != b[i].z)
264 return false;
265 return true;
266}
267
268void levelTables() {
269 std::printf(" LEVEL TABLE (§8.5, C3), levels = 6, theta = %.4g:\n", gTheta);
270 const double hpS[3] = {1.0, 0.5, 2.0}; // the stretched (N, 2N, N/2) box on a cube
271 const double hpC[3] = {1.0, 1.0, 1.0}; // the cubic control
272 const C3 want[4] = {{1, 2, 1}, {2, 2, 1}, {2, 2, 2}, {2, 2, 2}};
273 for (int N : {16, 32, 64}) {
274 const std::vector<C3> st = levelTable(N, 2 * N, N / 2, hpS, 6, true);
275 const std::vector<C3> stOld = levelTable(N, 2 * N, N / 2, hpS, 6, false);
276 const std::vector<C3> cu = levelTable(N, N, N, hpC, 6, true);
277 const std::vector<C3> cuOld = levelTable(N, N, N, hpC, 6, false);
278 std::printf(" N = %3d\n", N);
279 printTable("stretched (N, 2N, N/2), hp = (1, 1/2, 2)", st);
280 printTable(" the same WITHOUT the metric (today's rule)", stOld);
281 printTable("cubic (N, N, N), hp = (1, 1, 1)", cu);
282 printTable(" the same WITHOUT the metric", cuOld);
283 if (ablation())
284 continue;
285 for (int L = 0; L < 4; ++L)
286 if (!(st[(std::size_t)L].x == want[L].x && st[(std::size_t)L].y == want[L].y &&
287 st[(std::size_t)L].z == want[L].z)) {
288 std::fprintf(stderr,
289 "FAIL: N = %d stretched level %d ratio (%d,%d,%d), expected (%d,%d,%d)\n", N,
290 L, st[(std::size_t)L].x, st[(std::size_t)L].y, st[(std::size_t)L].z, want[L].x,
291 want[L].y, want[L].z);
292 ++failures;
293 }
294 // The ISOTROPIC hierarchy must be today's BY CONSTRUCTION -- this is the G0 statement of C3.
295 if (!sameTable(cu, cuOld)) {
296 std::fprintf(stderr, "FAIL: N = %d cubic level table moved with the metric set\n", N);
297 ++failures;
298 }
299 for (std::size_t L = 0; L + 1 < cu.size(); ++L)
300 if (!(cu[L].x == 2 && cu[L].y == 2 && cu[L].z == 2)) {
301 std::fprintf(stderr, "FAIL: N = %d cubic level %zu is not (2,2,2)\n", N, L);
302 ++failures;
303 }
304 }
305}
306
310double residualInf(CutcellMG& mg, const std::vector<double>& b) {
311 CutcellMG::Level& l0 = mg.level(0);
312 const C3 e = l0.ext;
313 const int G = CutcellMG::G;
314 const int nx = l0.inner.x, ny = l0.inner.y, nz = l0.inner.z;
315 auto hx = Kokkos::create_mirror_view(l0.x);
316 Kokkos::deep_copy(hx, l0.x);
317 auto hAC = Kokkos::create_mirror_view(l0.AC);
318 // A2 face form: the bands are AW = AFX(c), AE = AFX(c+1), AS = AFY(c), AN = AFY(c+ex), ...
319 auto hAFX = Kokkos::create_mirror_view(l0.AFX);
320 auto hAFY = Kokkos::create_mirror_view(l0.AFY);
321 auto hAFZ = Kokkos::create_mirror_view(l0.AFZ);
322 Kokkos::deep_copy(hAC, l0.AC);
323 Kokkos::deep_copy(hAFX, l0.AFX);
324 Kokkos::deep_copy(hAFY, l0.AFY);
325 Kokkos::deep_copy(hAFZ, l0.AFZ);
326 const std::size_t sy = e.x, sz = (std::size_t)e.x * e.y;
327 auto hAW = [&](std::size_t c) { return hAFX(c); };
328 auto hAE = [&](std::size_t c) { return hAFX(c + 1); };
329 auto hAS = [&](std::size_t c) { return hAFY(c); };
330 auto hAN = [&](std::size_t c) { return hAFY(c + sy); };
331 auto hAB = [&](std::size_t c) { return hAFZ(c); };
332 auto hAT = [&](std::size_t c) { return hAFZ(c + sz); };
333 auto id = [&](int i, int j, int k) {
334 const int wi = (i % nx + nx) % nx, wj = (j % ny + ny) % ny, wk = (k % nz + nz) % nz;
335 return (std::size_t)(wi + G) + (std::size_t)(wj + G) * e.x +
336 (std::size_t)(wk + G) * (std::size_t)e.x * e.y;
337 };
338 double m = 0.0;
339 for (int k = 0; k < nz; ++k)
340 for (int j = 0; j < ny; ++j)
341 for (int i = 0; i < nx; ++i) {
342 const std::size_t c = id(i, j, k);
343 const double Ax =
344 (double)hAC(c) * hx(c) + (double)hAW(c) * hx(id(i - 1, j, k)) +
345 (double)hAE(c) * hx(id(i + 1, j, k)) + (double)hAS(c) * hx(id(i, j - 1, k)) +
346 (double)hAN(c) * hx(id(i, j + 1, k)) + (double)hAB(c) * hx(id(i, j, k - 1)) +
347 (double)hAT(c) * hx(id(i, j, k + 1));
348 const double r = b[c] - Ax;
349 if (!(std::fabs(r) <= m))
350 m = std::fabs(r);
351 }
352 return m;
353}
354
358void vcycleRate(int N, double maxFactor) {
359 const int G = CutcellMG::G;
360 const int nx = N, ny = 2 * N, nz = N / 2;
361 const C3 e{nx + 2 * G, ny + 2 * G, nz + 2 * G};
362 const std::size_t n = (std::size_t)e.x * e.y * e.z;
363 CCField ox("ox", n), oy("oy", n), oz("oz", n);
364 Kokkos::deep_copy(ox, 1.0);
365 Kokkos::deep_copy(oy, 1.0);
366 Kokkos::deep_copy(oz, 1.0);
367 CutcellMG mg;
369 const double hp[3] = {1.0, 0.5, 2.0};
370 mg.setMetric(hp);
371 mg.init(nx, ny, nz, 6);
372 mg.setOpenness(CCConst(ox), CCConst(oy), CCConst(oz), 1.0, 4.0, 0.25);
373
374 // Deterministic pseudo-random, mean-free over the inner cells (the operator's null space is the
375 // constants on a periodic all-fluid box, so the RHS must be compatible).
376 std::vector<double> hb(n, 0.0);
377 unsigned int st = 12345u;
378 double sum = 0.0;
379 long cnt = 0;
380 for (int k = 0; k < nz; ++k)
381 for (int j = 0; j < ny; ++j)
382 for (int i = 0; i < nx; ++i) {
383 st = st * 1103515245u + 12345u;
384 const double v = (double)((st >> 16) & 0x7fff) / 32767.0 - 0.5;
385 hb[(std::size_t)(i + G) + (std::size_t)(j + G) * e.x +
386 (std::size_t)(k + G) * (std::size_t)e.x * e.y] = v;
387 sum += v;
388 ++cnt;
389 }
390 const double mean = sum / (double)cnt;
391 for (int k = 0; k < nz; ++k)
392 for (int j = 0; j < ny; ++j)
393 for (int i = 0; i < nx; ++i)
394 hb[(std::size_t)(i + G) + (std::size_t)(j + G) * e.x +
395 (std::size_t)(k + G) * (std::size_t)e.x * e.y] -= mean;
396
397 CutcellMG::Level& l0 = mg.level(0);
398 {
399 auto m = Kokkos::create_mirror_view(l0.rhs);
400 for (std::size_t i = 0; i < n; ++i)
401 m(i) = hb[i];
402 Kokkos::deep_copy(l0.rhs, m);
403 }
404 Kokkos::deep_copy(l0.x, 0.0);
405 std::printf(
406 " V-CYCLE RATE (§8.5 rate (a)), stretched (%d, %d, %d), 2/2 sweeps, random "
407 "mean-zero RHS:\n",
408 nx, ny, nz);
409 double prev = 0.0, worst = 0.0;
410 for (int c = 1; c <= 8; ++c) {
411 mg.vcycle(0, /*sym=*/true);
412 const double rn = residualInf(mg, hb);
413 const double f = (c > 1 && prev > 0.0) ? rn / prev : 0.0;
414 if (c >= 2) {
415 std::printf(" cycle %d max|r| %.6e factor %.4f\n", c, rn, f);
416 if (!(f <= worst))
417 worst = f;
418 } else {
419 std::printf(" cycle %d max|r| %.6e\n", c, rn);
420 }
421 prev = rn;
422 }
423 std::printf(" worst reduction factor over cycles 2-8 = %.4f (require <= %.2f)\n", worst,
424 maxFactor);
425 if (!ablation() && !(worst <= maxFactor)) {
426 std::fprintf(stderr, "FAIL: V-cycle reduction factor %.4f > %.2f\n", worst, maxFactor);
427 ++failures;
428 }
429}
430
433Run solveSphere(int N, bool stretched, bool fcg) {
434 const int G = CutcellMG::G;
435 const int nx = N, ny = stretched ? 2 * N : N, nz = stretched ? N / 2 : N;
436 const double L = 1.0;
437 const double hx = L / nx, hy = L / ny, hz = L / nz;
438 const double dref = hx;
439 const double hp[3] = {hx / dref, hy / dref, hz / dref};
440 const double wx = 1.0 / (hp[0] * hp[0]), wy = 1.0 / (hp[1] * hp[1]), wz = 1.0 / (hp[2] * hp[2]);
441 const C3 e{nx + 2 * G, ny + 2 * G, nz + 2 * G};
442 const std::size_t n = (std::size_t)e.x * e.y * e.z;
443 const double R = 0.3722 * L; // phi = 0.216 in the unit cell
444
445 CCField sdf("sdf", n), ox("ox", n), oy("oy", n), oz("oz", n);
446 { // d' = (|x - c|_minimage - R) / dref at the PHYSICAL cell centres, ghosts included
447 auto m = Kokkos::create_mirror_view(sdf);
448 for (int k = 0; k < e.z; ++k)
449 for (int j = 0; j < e.y; ++j)
450 for (int i = 0; i < e.x; ++i) {
451 double px = ((i - G) + 0.5) * hx - 0.5 * L, py = ((j - G) + 0.5) * hy - 0.5 * L,
452 pz = ((k - G) + 0.5) * hz - 0.5 * L;
453 px -= L * std::round(px / L);
454 py -= L * std::round(py / L);
455 pz -= L * std::round(pz / L);
456 const double d = std::sqrt(px * px + py * py + pz * pz) - R;
457 m((std::size_t)i + (std::size_t)j * e.x + (std::size_t)k * (std::size_t)e.x * e.y) =
458 d / dref;
459 }
460 Kokkos::deep_copy(sdf, m);
461 }
462 buildOpenness(ox, oy, oz, CCConst(sdf), e, hp[0], hp[1], hp[2], 1);
463
464 CutcellMG mg;
466 mg.setMetric(hp);
467 mg.init(nx, ny, nz, 6);
468 mg.setOpenness(CCConst(ox), CCConst(oy), CCConst(oz), wx, wy, wz);
469
470 // A compatible RHS: the smooth mode of the order gate, masked to the rows that carry an open
471 // face (AC > 0) and mean-removed over exactly those rows.
472 std::vector<double> hb(n, 0.0);
473 {
474 auto hAC = Kokkos::create_mirror_view(mg.level(0).AC);
475 Kokkos::deep_copy(hAC, mg.level(0).AC);
476 const double k = 2.0 * M_PI / L;
477 double sum = 0.0;
478 long cnt = 0;
479 for (int z = G; z < e.z - G; ++z)
480 for (int y = G; y < e.y - G; ++y)
481 for (int x = G; x < e.x - G; ++x) {
482 const std::size_t i =
483 (std::size_t)x + (std::size_t)y * e.x + (std::size_t)z * (std::size_t)e.x * e.y;
484 if (!(hAC(i) > 0.0f))
485 continue;
486 const double px = ((x - G) + 0.5) * hx, py = ((y - G) + 0.5) * hy,
487 pz = ((z - G) + 0.5) * hz;
488 const double v = std::cos(k * px) * std::cos(k * py) * std::cos(k * pz);
489 hb[i] = v;
490 sum += v;
491 ++cnt;
492 }
493 const double mean = cnt ? sum / (double)cnt : 0.0;
494 for (int z = G; z < e.z - G; ++z)
495 for (int y = G; y < e.y - G; ++y)
496 for (int x = G; x < e.x - G; ++x) {
497 const std::size_t i =
498 (std::size_t)x + (std::size_t)y * e.x + (std::size_t)z * (std::size_t)e.x * e.y;
499 if (hAC(i) > 0.0f)
500 hb[i] -= mean;
501 }
502 }
503 CCField b("b", n), xf("x", n), r("r", n), pp("p", n), zz("z", n), Ap("Ap", n);
504 {
505 auto m = Kokkos::create_mirror_view(b);
506 for (std::size_t i = 0; i < n; ++i)
507 m(i) = hb[i];
508 Kokkos::deep_copy(b, m);
509 }
510 Kokkos::deep_copy(xf, 0.0);
511 Run out;
512 out.levels = mg.nLevels();
513 if (fcg) {
514 CCField zp("zp", n);
515 out.iters = mg.solveFCG(b, xf, r, pp, zz, zp, Ap, 500, 1e-10, 2, 2, 12);
516 } else {
517 out.iters = mg.solvePCG(b, xf, r, pp, zz, Ap, 500, 1e-10, 2, 2, 12);
518 }
519 {
520 auto hr = Kokkos::create_mirror_view(r);
521 Kokkos::deep_copy(hr, r);
522 double rm = 0.0, bm = 0.0;
523 for (int z = G; z < e.z - G; ++z)
524 for (int y = G; y < e.y - G; ++y)
525 for (int x = G; x < e.x - G; ++x) {
526 const std::size_t i =
527 (std::size_t)x + (std::size_t)y * e.x + (std::size_t)z * (std::size_t)e.x * e.y;
528 if (!(std::fabs(hr(i)) <= rm))
529 rm = std::fabs(hr(i));
530 if (!(std::fabs(hb[i]) <= bm))
531 bm = std::fabs(hb[i]);
532 }
533 out.rrel = bm > 0.0 ? rm / bm : rm;
534 }
535 return out;
536}
537
538void sphereGate() {
539 std::printf(
540 " SPHERE RATE (§8.5 rate (b)/(c)), Zick & Homsy phi = 0.216, MG-PCG rtol 1e-10, "
541 "theta = %.4g:\n",
542 gTheta);
543 for (int N : {32, 64}) {
544 const Run st = solveSphere(N, /*stretched=*/true, /*fcg=*/false);
545 const Run cu = solveSphere(N, /*stretched=*/false, /*fcg=*/false);
546 const Run stF = solveSphere(N, /*stretched=*/true, /*fcg=*/true);
547 std::printf(
548 " N = %3d stretched %4d x %4d x %4d levels %d PCG %3d iters (r/|b| %.2e)"
549 " FCG %3d iters\n",
550 N, N, 2 * N, N / 2, st.levels, st.iters, st.rrel, stF.iters);
551 std::printf(
552 " cubic %4d x %4d x %4d levels %d PCG %3d iters (r/|b| %.2e)"
553 " [require stretched <= cubic + 2 = %d]\n",
554 N, N, N, cu.levels, cu.iters, cu.rrel, cu.iters + 2);
555 if (!ablation() && !(st.iters <= cu.iters + 2)) {
556 std::fprintf(stderr, "FAIL: N = %d stretched PCG %d iters > cubic %d + 2\n", N, st.iters,
557 cu.iters);
558 ++failures;
559 }
560 }
561}
562} // namespace
563
564int main(int argc, char** argv) {
565 for (int i = 1; i < argc; ++i)
566 if (std::strncmp(argv[i], "--theta=", 8) == 0)
567 gTheta = std::atof(argv[i] + 8);
568 Kokkos::initialize(argc, argv);
569 {
570 std::printf("=== cutcellmg_aniso (G4-order, doc/anisotropic_metric.md §8.5) ===\n");
571 ladder(/*stretched=*/true, /*fcg=*/false, 1.95);
572 ladder(/*stretched=*/false, /*fcg=*/false, 1.95);
573 // The same operator through the FLEXIBLE CG driver, which flow/CLAUDE.md names as the one to
574 // reach for when MG-PCG caps. It is NOT a substitute for the §8.5 gate above; it is the
575 // CONVERGED control that separates the discretization statement from the multigrid RATE. On
576 // the stretched grid today's FULL-coarsening rule leaves the V-cycle preconditioner stalling
577 // (the MG-PCG rows above cap, and their L2 error then drifts a few percent from the exact
578 // discrete value); §5 of doc/anisotropic_metric.md is the semi-coarsening rule that fixes it
579 // and commit C3 is where the RATE part of G4 gates it.
580 ladder(/*stretched=*/true, /*fcg=*/true, 1.95);
581 ladder(/*stretched=*/false, /*fcg=*/true, 1.95);
582 std::printf("=== C3 — the aspect-ratio coarsening rule (doc/anisotropic_metric.md §5) ===\n");
583 if (ablation())
584 std::printf(
585 " --theta=%.4g -> ABLATION RUN: the gates below PRINT, they do "
586 "not assert (§8.5 item (c)).\n",
587 gTheta);
588 levelTables();
589 vcycleRate(32, 0.2);
590 sphereGate();
591 }
592 Kokkos::finalize();
593 if (failures) {
594 std::fprintf(stderr, "%d failure(s)\n", failures);
595 return 1;
596 }
597 std::printf("OK\n");
598 return 0;
599}
int solvePCG(CCField b, CCField x, CCField r, CCField p, CCField z, CCField Ap, int maxit, double rtol, int pre, int post, int bottom, const StarOverlay *star=nullptr, int nStar=0, C3 nnStar=C3{0, 0, 0})
void setMetric(const double hp[3])
Per-axis cell spacings h_a' = h_a / hRef of the physical domain (doc/anisotropic_metric....
std::vector< C3 > levelRatios() const
The per-level coarsening ratio actually chosen (the level table of §5, gate §8.5).
void vcycle(int L, bool sym)
void setOpenness(CCConst ox, CCConst oy, CCConst oz, double idx2, double idy2, double idz2)
void setAspectThreshold(double theta)
Anisotropic-coarsening aspect threshold theta (see aspectTheta_).
static constexpr int G
void init(int nx, int ny, int nz, int nLevels)
int solveFCG(CCField b, CCField x, CCField r, CCField p, CCField z, CCField zp, CCField Ap, int maxit, double rtol, int pre, int post, int bottom, const StarOverlay *star=nullptr, int nStar=0, C3 nnStar=C3{0, 0, 0})
int main()
Definition dbg_scene.cpp:13
flow — portable (Kokkos) cut-cell pressure-operator face openness from an SDF.
flow — portable (Kokkos) geometric multigrid for the cut-cell (variable-openness) pressure Poisson.
float worst
Definition cmp.py:18
x
Definition cmp.py:24
y
Definition cmp.py:24
Kokkos::View< double *, CCMem > CCField
void buildOpenness(CCField ox, CCField oy, CCField oz, CCConst sdf, C3 ext, double dx, double dy, double dz, int order=1)
Kokkos::View< const double *, CCMem > CCConst
list iters
Definition prof.py:105
std::vector< int > iters
static constexpr int N
static constexpr int G
static double mean(const std::vector< double > &a)