flow 1.3.0
Kokkos cut-cell IBM incompressible Navier-Stokes solver + pnm pore extraction
Loading...
Searching...
No Matches
test_balanced_force.cpp
Go to the documentation of this file.
1// The balanced-force projection on the STAGGERED grid (doc/collocated_varrho_forces.md §4.6-4.7,
2// gate G8).
3//
4// `set_balanced_force_projection(True)` solves, once per step before the momentum predictor,
5// D(O c w G X) = D(O c beta) with the projection's own operator (c = rho0/rho_f, beta = the
6// predictor's own face force) and moves X into P. A gradient force is then balanced by the
7// pressure BEFORE the implicit momentum operator A = rho_f/dt - mu*Lap acts, so the static
8// balances the staggered predictor only APPROACHES at mu > 0 (WO-P's mu*dt^2 residue: A does not
9// commute with the discrete gradient at variable rho or next to a wall or a solid) are exact from
10// the first step. The option is state-independent, so it changes neither stability nor the
11// converged steady state; OFF is byte-identical (gated by the test_vof_* / test_vardensity_*
12// outputs and tests/regression/state_hash.py, not here).
13//
14// (b) T2 staggered drop, constant kappa, ratio 1 / 10 / 100 / 1000 and the ratio-1000 mu sweep:
15// face < 1e-13 after 30 steps.
16// (c) hydrostatic column, walled, ratio 1000, mu in {0, 1e-3, 1e-2, 1e-1}: dP/dz < 1e-10 after
17// ONE step, face < 1e-12.
18// (d) low-Ca walled drop, 32^3, walls on every face, R = 8, kappa = 0.25, sigma = 0.01, mu = 10,
19// dt = 0.5 capillary_dt: face < 1e-13 ON; OFF reported.
20// (e) immersed solids: (i) a ratio-1000 walled column with a solid sphere (R = 5) crossing the
21// interface, mu = 0.1: face < 1e-12 and dP/dz between fluid cells < 1e-10 after 30 steps;
22// (ii) the drop of (d) cut by an immersed plane 0.5 R below its centre, ratio 1 and 1000,
23// mu = 0.1: face < 1e-12. OFF reported.
24// (f) refusals: porous, ghost, block CSF, inflow/outflow, non-incremental -> named errors; the
25// V8 DEFAULT with an inflow/outflow face resolves OFF (notice) instead of throwing.
26#include <cmath>
27#include <cstdio>
28#include <functional>
29#include <Kokkos_Core.hpp>
30#include <memory>
31#include <stdexcept>
32#include <string>
33#include <vector>
34
35#include "flow_ibm.hpp"
36
37namespace {
41
42int failures = 0;
43#define CHECK(cond) \
44 do { \
45 if (!(cond)) { \
46 std::fprintf(stderr, "CHECK failed: %s\n at %s:%d\n", #cond, __FILE__, __LINE__); \
47 ++failures; \
48 } \
49 } while (0)
50
51std::size_t idx(int x, int y, int z, int nx, int ny) {
52 return (std::size_t)x + (std::size_t)y * nx + (std::size_t)z * (std::size_t)nx * ny;
53}
54double maxAbs(const std::vector<double>& v) {
55 double m = 0;
56 for (double x : v)
57 m = std::fmax(m, std::fabs(x));
58 return m;
59}
60template <class S>
61double maxFaceVel(S& s) {
62 return std::fmax(std::fmax(maxAbs(s.getFaceVelocity(0)), maxAbs(s.getFaceVelocity(1))),
63 maxAbs(s.getFaceVelocity(2)));
64}
65
66// Volume fractions of a sphere (the sampler of test_vof_collocated.cpp).
67std::vector<double> sphereC(int n, double R, double cx, double cy, double cz, int sub = 24) {
68 std::vector<double> C((std::size_t)n * n * n, 0.0);
69 const double w = 1.0 / sub;
70 for (int k = 0; k < n; ++k)
71 for (int j = 0; j < n; ++j)
72 for (int i = 0; i < n; ++i) {
73 double acc = 0.0;
74 for (int b = 0; b < sub; ++b)
75 for (int a = 0; a < sub; ++a) {
76 const double px = i + (a + 0.5) * w, py = j + (b + 0.5) * w;
77 const double r2 = R * R - (px - cx) * (px - cx) - (py - cy) * (py - cy);
78 if (r2 <= 0.0)
79 continue;
80 const double h = std::sqrt(r2);
81 const double lo = std::fmax(cz - h, (double)k), hi = std::fmin(cz + h, (double)k + 1);
82 if (hi > lo)
83 acc += hi - lo;
84 }
85 C[idx(i, j, k, n, n)] = acc / (sub * sub);
86 }
87 return C;
88}
89
90// ------------------------------------------------------------------ droplets: (b), (d), (e)(ii)
91struct DropCfg {
92 double ratio = 1.0, mu = 0.1, sigma = 1.0;
93 bool walls = false, plane = false;
94 int steps = 30;
95};
96
97double runDrop(const DropCfg& c, bool on, long* bfIters = nullptr) {
98 const int n = 32;
99 const double R = 8.0, cx = n / 2 + 0.13, cy = n / 2 + 0.27, cz = n / 2 + 0.11;
100 Stag s(n, n, n);
101 s.setRho(c.ratio);
102 s.setMu(c.mu);
103 s.setDt(1.0);
104 s.setVelocityResidualTolerance(0.0);
105 if (c.walls)
106 for (int f = 0; f < 6; ++f)
107 s.setDomainBc(f, 1, 0, 0, 0);
108 if (c.plane) { // an immersed plane 0.5 R below the drop centre: solid below z = cz - R/2
109 std::vector<double> sdf((std::size_t)n * n * n);
110 for (int z = 0; z < n; ++z)
111 for (int y = 0; y < n; ++y)
112 for (int x = 0; x < n; ++x)
113 sdf[idx(x, y, z, n, n)] = (z + 0.5) - (cz - 0.5 * R);
114 s.setSolid(sdf, true);
115 } else {
116 s.setPressureGeometry(std::vector<double>((std::size_t)n * n * n, 10.0));
117 }
118 s.setPressureChebyshev(true, 500, 1e-14);
119 s.enableVof();
120 s.setVof(sphereC(n, R, cx, cy, cz));
121 s.setPropertyModel("rho", ClosureKind::LinearMix, "C", "", {1.0, c.ratio - 1.0});
122 s.setSurfaceTension(c.sigma);
123 s.setVofKappaConstant(0.25);
124 s.setDt(0.5 * s.capillaryDt());
125 if (on)
126 s.setBalancedForceProjection(true);
127 s.setPressureChebyshev(true, 500, 1e-14);
128 long it = 0;
129 for (int k = 0; k < c.steps; ++k) {
130 s.step();
131 it = std::max(it, s.lastBalancedForceIterations());
132 }
133 if (bfIters)
134 *bfIters = it;
135 return maxFaceVel(s);
136}
137
138// ------------------------------------------------------------------ hydrostatic: (c), (e)(i)
139struct HydroResult {
140 double faceU = 0, pErr = 0;
141};
142
143HydroResult hydrostatic(double ratio, double mu, int steps, bool sphere, bool on) {
144 const int N = sphere ? 16 : 8, NZ = sphere ? 32 : 24;
145 const double g = 0.1;
146 Stag s(N, N, NZ);
147 s.setRho(1.0);
148 s.setMu(mu);
149 s.setDt(1.0);
150 s.setVelocityResidualTolerance(0.0);
151 s.setDomainBc(4, 1, 0, 0, 0);
152 s.setDomainBc(5, 1, 0, 0, 0);
153 std::vector<double> sdf((std::size_t)N * N * NZ, 10.0);
154 if (sphere)
155 for (int z = 0; z < NZ; ++z)
156 for (int y = 0; y < N; ++y)
157 for (int x = 0; x < N; ++x) {
158 const double dx = x + 0.5 - 8.2, dy = y + 0.5 - 7.9, dz = z + 0.5 - 16.3;
159 sdf[idx(x, y, z, N, N)] = std::sqrt(dx * dx + dy * dy + dz * dz) - 5.0;
160 }
161 if (sphere)
162 s.setSolid(sdf, true);
163 else
164 s.setPressureGeometry(sdf);
165 auto heavy = [&](int z) { return z < NZ / 2; };
166 std::vector<double> fld((std::size_t)N * N * NZ);
167 for (int z = 0; z < NZ; ++z)
168 for (int y = 0; y < N; ++y)
169 for (int x = 0; x < N; ++x)
170 fld[idx(x, y, z, N, N)] = heavy(z) ? ratio : 1.0;
171 s.addField("rho");
172 s.setField("rho", fld);
173 s.setDensityMode(true);
174 s.setPropertyModel("force_z", ClosureKind::LinearMix, "rho", "", std::vector<double>{0.0, -g});
175 if (on)
176 s.setBalancedForceProjection(true);
177 for (int k = 0; k < steps; ++k)
178 s.step();
179 HydroResult r;
180 r.faceU = maxFaceVel(s);
181 const auto p = s.getPressure();
182 for (int z = 1; z < NZ; ++z)
183 for (int y = 0; y < N; ++y)
184 for (int x = 0; x < N; ++x) {
185 if (sdf[idx(x, y, z, N, N)] < 0.0 || sdf[idx(x, y, z - 1, N, N)] < 0.0)
186 continue; // P inside the solid is not a fluid pressure
187 const double dp = p[idx(x, y, z, N, N)] - p[idx(x, y, z - 1, N, N)];
188 const double rf = 0.5 * ((heavy(z) ? ratio : 1.0) + (heavy(z - 1) ? ratio : 1.0));
189 r.pErr = std::fmax(r.pErr, std::fabs(dp + g * rf) / (g * ratio));
190 }
191 return r;
192}
193
194void gateDrop() {
195 std::printf("\n=== (b) staggered drop, constant kappa, 30 steps: face |u| OFF -> ON\n");
196 for (double ratio : {1.0, 10.0, 100.0, 1000.0}) {
197 DropCfg c;
198 c.ratio = ratio;
199 long it = 0;
200 const double off = runDrop(c, false), on = runDrop(c, true, &it);
201 std::printf(" ratio %6g OFF %.3e ON %.3e (balanced-force iters max %ld)\n", ratio, off, on,
202 it);
203 CHECK(on < 1e-13);
204 }
205 for (double mu : {0.0, 0.01, 0.1}) {
206 DropCfg c;
207 c.ratio = 1000.0;
208 c.mu = mu;
209 const double off = runDrop(c, false), on = runDrop(c, true);
210 std::printf(" ratio 1000 mu %-5g OFF %.3e ON %.3e\n", mu, off, on);
211 CHECK(on < 1e-13);
212 }
213 std::printf("\n=== (d) low-Ca walled drop (sigma 0.01, mu 10, walls on every face), 30 steps\n");
214 for (double ratio : {1.0, 1000.0}) {
215 DropCfg c;
216 c.ratio = ratio;
217 c.mu = 10.0;
218 c.sigma = 0.01;
219 c.walls = true;
220 const double off = runDrop(c, false), on = runDrop(c, true);
221 std::printf(" ratio %6g OFF %.3e (reported) ON %.3e\n", ratio, off, on);
222 CHECK(on < 1e-13);
223 }
224 std::printf(
225 "\n=== (e)(ii) constant-kappa sessile cap: the drop cut by an immersed plane, mu 0.1\n");
226 for (double ratio : {1.0, 1000.0}) {
227 DropCfg c;
228 c.ratio = ratio;
229 c.plane = true;
230 const double off = runDrop(c, false), on = runDrop(c, true);
231 std::printf(" ratio %6g OFF %.3e (reported) ON %.3e\n", ratio, off, on);
232 CHECK(on < 1e-12);
233 }
234}
235
236void gateHydro() {
237 std::printf(
238 "\n=== (c) hydrostatic column, walled, ratio 1000: ON after ONE step (OFF 100 steps)\n");
239 for (double mu : {0.0, 1e-3, 1e-2, 1e-1}) {
240 const auto off = hydrostatic(1000.0, mu, 100, false, false);
241 const auto on = hydrostatic(1000.0, mu, 1, false, true);
242 std::printf(" mu %-6g OFF face %.3e dP/dz %.3e | ON(1 step) face %.3e dP/dz %.3e\n", mu,
243 off.faceU, off.pErr, on.faceU, on.pErr);
244 CHECK(on.faceU < 1e-12);
245 CHECK(on.pErr < 1e-10);
246 }
247 std::printf(
248 "\n=== (e)(i) ratio-1000 column with a solid sphere (R = 5) across the interface, "
249 "mu 0.1, 30 steps\n");
250 const auto off = hydrostatic(1000.0, 0.1, 30, true, false);
251 const auto on = hydrostatic(1000.0, 0.1, 30, true, true);
252 std::printf(" OFF face %.3e dP/dz %.3e (reported) | ON face %.3e dP/dz %.3e\n", off.faceU,
253 off.pErr, on.faceU, on.pErr);
254 CHECK(on.faceU < 1e-12);
255 CHECK(on.pErr < 1e-10);
256}
257
258void expectThrow(const char* what, const std::function<void()>& f) {
259 bool threw = false;
260 std::string msg;
261 try {
262 f();
263 } catch (const std::exception& e) {
264 threw = true;
265 msg = e.what();
266 }
267 std::printf(" %-34s -> throws: %s%s%.90s\n", what, threw ? "yes" : "NO", threw ? " " : "",
268 msg.c_str());
269 CHECK(threw);
270}
271
272void gateRefusals() {
273 std::printf("\n=== (f) refusals\n");
274 const int N = 16;
275 const std::vector<double> fluid((std::size_t)N * N * N, 10.0);
276 auto base = [&](auto& s) {
277 s.setRho(1.0);
278 s.setMu(0.01);
279 s.setDt(1.0);
280 };
281 expectThrow("porous continuity", [&] {
282 Stag s(N, N, N);
283 base(s);
284 s.setPressureGeometry(fluid);
285 s.setPorousContinuity(true);
286 s.setBalancedForceProjection(true);
287 s.step();
288 });
289 expectThrow("ghost projection", [&] {
290 Stag s(N, N, N);
291 base(s);
292 s.setGhostProjection(true);
293 s.setBalancedForceProjection(true);
294 s.setPressureGeometry(fluid);
295 s.step();
296 });
297 expectThrow("block CSF", [&] {
298 Stag s(N, N, N);
299 base(s);
300 s.setPressureGeometry(fluid);
301 s.enableVof();
302 s.enableVofBlocks({{{N / 2.0, N / 2.0, N / 2.0, 4.0}}});
303 s.setSurfaceTension(0.01);
304 s.enableVofBlockCsf();
305 s.setBalancedForceProjection(true);
306 s.step();
307 });
308 expectThrow("inflow/outflow face", [&] {
309 Stag s(N, N, N);
310 base(s);
311 s.setDomainBc(0, 2, 0.1, 0.0, 0.0);
312 s.setDomainBc(1, 3, 0, 0, 0);
313 s.setPressureGeometry(fluid);
314 s.setBalancedForceProjection(true);
315 s.step();
316 });
317 // The V8 DEFAULT with an inflow/outflow face resolves OFF (a one-time stderr notice), and the
318 // step runs; only an EXPLICIT ON with an open face throws the named error.
319 {
320 Colo s(N, N, N);
321 base(s);
322 s.setDomainBc(0, 2, 0.1, 0.0, 0.0);
323 s.setDomainBc(1, 3, 0, 0, 0);
324 s.setPressureGeometry(fluid);
325 s.setDensityMode(true); // V8: the option's default would be ON
326 bool threw = false;
327 try {
328 s.step();
329 s.step();
330 } catch (const std::exception& e) {
331 threw = true;
332 std::printf(" V8 default + open face threw: %s\n", e.what());
333 }
334 std::printf(" V8 default + inflow/outflow: %s, option %s\n", threw ? "THREW" : "ran",
335 s.balancedForceProjection() ? "ON" : "OFF");
336 CHECK(!threw);
337 CHECK(!s.balancedForceProjection());
338 }
339 expectThrow("V8 + inflow/outflow face, explicit ON", [&] {
340 Colo s(N, N, N);
341 base(s);
342 s.setDomainBc(0, 2, 0.1, 0.0, 0.0);
343 s.setDomainBc(1, 3, 0, 0, 0);
344 s.setPressureGeometry(fluid);
345 s.setDensityMode(true);
346 s.setBalancedForceProjection(true);
347 s.step();
348 });
349 expectThrow("non-incremental pressure", [&] {
350 Stag s(N, N, N);
351 base(s);
352 s.setPressureGeometry(fluid);
353 s.setIncrementalPressure(false);
354 s.setBalancedForceProjection(true);
355 s.step();
356 });
357}
358
359} // namespace
360
361int main(int argc, char** argv) {
362 Kokkos::initialize(argc, argv);
363 {
364 gateRefusals();
365 gateHydro();
366 gateDrop();
367 }
368 Kokkos::finalize();
369 if (failures) {
370 std::fprintf(stderr, "\n%d CHECK(s) failed\n", failures);
371 return 1;
372 }
373 std::printf("\nAll staggered balanced-force projection gates passed.\n");
374 return 0;
375}
int main()
Definition dbg_scene.cpp:13
flow — host-facing Kokkos IBM Navier-Stokes solver (drop-in flow-style API).
x
Definition cmp.py:24
y
Definition cmp.py:24
hydrostatic(ratio, mu=0.0, steps=100, g=0.1, dt=1.0, N=8, NZ=24)
static constexpr int NZ
static constexpr int N
#define CHECK(cond)
static double maxAbs(const std::vector< double > &a)