flow 1.3.0
Kokkos cut-cell IBM incompressible Navier-Stokes solver + pnm pore extraction
Loading...
Searching...
No Matches
test_vof_momentum_mpi.cpp
Go to the documentation of this file.
1// flow — multi-rank validation of MOMENTUM-CONSISTENT VoF transport (rung V2b, WO-K): `rho^c u_c`
2// advected on the half-shifted MAC control volumes by the same geometric fluxes as the colour
3// field.
4//
5// What is new here relative to `test_vof_twophase_mpi.cpp` (rung V2a), which gated the colour
6// chain:
7//
8// 1. THE SHIFTED CONTROL VOLUMES AND THEIR OWNERSHIP. `C^c` and the transported velocity are
9// ordinary cell fields on the advector's g = 3 block, indexed in `flow`'s own low-face
10// convention precisely so that the owned control volumes ARE the block's inner region and the
11// colour halo carries them unchanged. If that correspondence were off by one along a
12// component's own axis, a control volume would be owned by two ranks or by none — invisible
13// single-rank, and visible here as a pointwise difference at the rank boundary.
14// 2. SIX MORE HALO EXCHANGES PER SWEEP. `C^c` and the transported velocity are exchanged before
15// every sweep (the flux clamp reads the DONOR's own colour, which is a ghost on the first
16// owned face). Their ghost policy must be the colour's, i.e. globally-clamped on a
17// non-periodic axis (WO-E finding 2) — `walls-z` cuts the walled axis and exercises it.
18// 3. THE CONSISTENCY IDENTITY UNDER DECOMPOSITION. The uniform-velocity identity is exact in
19// floating point; it must stay exact on every rank, which is a statement that the flux at a
20// rank boundary is computed identically on both sides.
21//
22// Comparison protocol is `tests/kokkos_mpi`'s: pointwise against a full-grid single-rank reference
23// built on rank 0. np = 1 is bit-exact; np > 1 inherits the MPI reduction-order floor of the
24// pressure driver (Chebyshev bounds + removeMean), which the colour and the momentum inherit
25// through the velocity that advects them.
26#include <mpi.h>
27
28#include <cmath>
29#include <cstdio>
30#include <cstring>
31#include <Kokkos_Core.hpp>
32#include <vector>
33
34#include "flow_ibm.hpp"
35#include "peclet/core/common/types.hpp"
36#include "peclet/core/decomp/block_decomposer.hpp"
37
39
40static constexpr int NX = 16, NY = 16, NZ = 32;
41static constexpr std::size_t GCELLS = (std::size_t)NX * NY * NZ;
42
43struct Config {
44 const char* name;
45 bool walls; // uniform-velocity identity in a walled box (else the periodic sheared sphere)
46 double ratio;
47 double dt;
48 int steps;
49};
50
51static const double UNIF[3] = {1.0, 0.6, -0.4};
52
53// A tilted sharp plane: a pure function of GLOBAL indices, so every block gets bit-identical data
54// to the corresponding part of the single-rank run. Exact fractions from the analytic plane->volume
55// relation the reconstruction inverts.
56static double colourAt(int x, int y, int z) {
57 double m[3] = {0.6, -0.5, 0.62456};
58 const double l1 = std::fabs(m[0]) + std::fabs(m[1]) + std::fabs(m[2]);
59 for (double& v : m)
60 v /= l1;
61 const double alpha = 0.5 * (m[0] * NX + m[1] * NY + m[2] * NZ);
62 return peclet::flow::vof::plicVolume(m[0], m[1], m[2], alpha - (m[0] * x + m[1] * y + m[2] * z));
63}
64static double forceXAt(int, int, int z) {
65 return 5e-2 * std::sin(2 * M_PI * (z + 0.5) / NZ);
66}
67
68template <class Fn>
69static std::vector<double> blockOf(Fn f, int ox, int oy, int oz, int lnx, int lny, int lnz) {
70 std::vector<double> v((std::size_t)lnx * lny * lnz);
71 for (int z = 0; z < lnz; ++z)
72 for (int y = 0; y < lny; ++y)
73 for (int x = 0; x < lnx; ++x)
74 v[(std::size_t)x + (std::size_t)y * lnx + (std::size_t)z * lnx * lny] =
75 f(x + ox, y + oy, z + oz);
76 return v;
77}
78
79static void configure(IbmSolver& s, const Config& c, int ox, int oy, int oz, int lnx, int lny,
80 int lnz) {
81 s.setRho(c.ratio);
82 s.setMu(c.walls ? 0.0 : 0.05);
83 s.setDt(c.dt);
84 s.setAdvection(!c.walls);
85 if (c.walls) {
86 s.setDomainBc(4, 2, UNIF[0], UNIF[1], UNIF[2]); // inflow -z, the CUT axis
87 s.setDomainBc(5, 3, 0, 0, 0); // outflow +z
88 }
89 s.setPressureGeometry(std::vector<double>((std::size_t)lnx * lny * lnz, 10.0));
90 s.setVof(blockOf(colourAt, ox, oy, oz, lnx, lny, lnz));
91 s.setPropertyModel("rho", peclet::flow::ClosureKind::LinearMix, "C", "", {1.0, c.ratio - 1.0});
92 s.enableVofMomentum(1.0, c.ratio);
93 if (!c.walls) {
95 s.setField("force_x", blockOf(forceXAt, ox, oy, oz, lnx, lny, lnz));
96 }
97 const std::size_t nc = (std::size_t)lnx * lny * lnz;
98 if (c.walls) // the uniform-velocity identity configuration
99 s.uploadVelocity(std::vector<double>(nc, UNIF[0]), std::vector<double>(nc, UNIF[1]),
100 std::vector<double>(nc, UNIF[2]));
101}
102
103static std::vector<double> gatherGlobal(const std::vector<double>& local, int ox, int oy, int oz,
104 int lnx, int lny, int lnz, int rank, int size) {
105 std::vector<double> global;
106 if (rank == 0)
107 global.assign(GCELLS, 0.0);
108 for (int r = 0; r < size; ++r) {
109 int meta[6] = {ox, oy, oz, lnx, lny, lnz};
110 if (r == 0) {
111 if (rank == 0)
112 for (int z = 0; z < lnz; ++z)
113 for (int y = 0; y < lny; ++y)
114 std::memcpy(&global[(std::size_t)ox + (std::size_t)(y + oy) * NX +
115 (std::size_t)(z + oz) * NX * NY],
116 &local[(std::size_t)y * lnx + (std::size_t)z * lnx * lny],
117 (std::size_t)lnx * sizeof(double));
118 continue;
119 }
120 if (rank == r) {
121 MPI_Send(meta, 6, MPI_INT, 0, 100 + r, MPI_COMM_WORLD);
122 MPI_Send(local.data(), (int)local.size(), MPI_DOUBLE, 0, 200 + r, MPI_COMM_WORLD);
123 } else if (rank == 0) {
125 std::vector<double> buf((std::size_t)meta[3] * meta[4] * meta[5]);
126 MPI_Recv(buf.data(), (int)buf.size(), MPI_DOUBLE, r, 200 + r, MPI_COMM_WORLD,
128 for (int z = 0; z < meta[5]; ++z)
129 for (int y = 0; y < meta[4]; ++y)
130 std::memcpy(&global[(std::size_t)meta[0] + (std::size_t)(y + meta[1]) * NX +
131 (std::size_t)(z + meta[2]) * NX * NY],
132 &buf[(std::size_t)y * meta[3] + (std::size_t)z * meta[3] * meta[4]],
133 (std::size_t)meta[3] * sizeof(double));
134 }
135 }
136 return global;
137}
138
139static double maxAbsDiff(const std::vector<double>& a, const std::vector<double>& b) {
140 // WO-R2: NaN-PROPAGATING. `std::fmax(m, NaN) == m`, so the obvious loop returns 0.000e+00 for a
141 // field that has gone entirely NaN and every bitwise gate built on it passes (WO-R found this on
142 // a drained open-boundary run). A non-finite difference must fail, so return it.
143 double m = 0;
144 for (std::size_t i = 0; i < b.size(); ++i) {
145 const double d = std::fabs(a[i] - b[i]);
146 if (!(d == d))
147 return d; // NaN
148 m = std::fmax(m, d);
149 }
150 return m;
151}
152static double maxAbs(const std::vector<double>& a) {
153 double m = 0;
154 for (double v : a)
155 m = std::fmax(m, std::fabs(v));
156 return m;
157}
158static double maxAbsDev(const std::vector<double>& a, double r) {
159 double m = 0;
160 for (double v : a)
161 m = std::fmax(m, std::fabs(v - r));
162 return m;
163}
164// In a fully periodic box the pressure carries an arbitrary additive gauge, fixed by `removeMean`
165// whose MPI_SUM order depends on the rank count. Comparing the DE-MEANED field measures the
166// physics rather than that gauge; on the walled configuration the outflow Dirichlet pins P and the
167// raw field is compared.
168static std::vector<double> demean(std::vector<double> v) {
169 double s = 0;
170 for (double x : v)
171 s += x;
172 const double m = s / (double)v.size();
173 for (double& x : v)
174 x -= m;
175 return v;
176}
177static double sumOf(const std::vector<double>& a) {
178 double s = 0;
179 for (double v : a)
180 s += v;
181 return s;
182}
183
184int main(int argc, char** argv) {
185 MPI_Init(&argc, &argv);
186 Kokkos::initialize(argc, argv);
187 int fail = 0;
188 {
189 int rank = 0, size = 1;
192
193 auto dec = peclet::flow::CutcellMG::decomposition(static_cast<std::size_t>(size), NX, NY, NZ);
194 auto blk = dec.block(rank);
195 const int ox = (int)blk.origin[0], oy = (int)blk.origin[1], oz = (int)blk.origin[2];
196 const int lnx = (int)blk.size[0], lny = (int)blk.size[1], lnz = (int)blk.size[2];
197 const int gn[3] = {NX, NY, NZ};
198 bool cut[3] = {false, false, false};
199 for (const auto& sz : dec.sizes())
200 for (int a = 0; a < 3; ++a)
201 if ((int)sz[a] != gn[a])
202 cut[a] = true;
203 if (rank == 0)
204 std::printf("VOF MOMENTUM MPI np=%d grid %dx%dx%d block %dx%dx%d cut axes: %s%s%s\n", size,
205 NX, NY, NZ, lnx, lny, lnz, cut[0] ? "x" : "", cut[1] ? "y" : "",
206 cut[2] ? "z" : "");
207
208 const Config configs[] = {{"walls-z", true, 1000.0, 0.2, 10},
209 {"shear-per", false, 10.0, 0.2, 40}};
210
211 for (const Config& c : configs) {
212 if (size > 1 && !cut[2]) {
213 if (rank == 0)
214 std::printf(" [%-9s np=%d] FAIL — the decomposition does NOT cut z\n", c.name, size);
215 fail = 1;
216 continue;
217 }
219 sd.initMpi(dec, MPI_COMM_WORLD);
220 configure(sd, c, ox, oy, oz, lnx, lny, lnz);
221
222 // THE CONSISTENCY IDENTITY, ON THE FIRST STEP, PER RANK (the `walls-z` configuration, which
223 // is seeded with a uniform velocity): the advection's output must be uniform BITWISE — on
224 // every rank, at every decomposition. That is the statement that the flux at a rank boundary
225 // is computed identically on both sides.
226 sd.step();
227 double identLoc = 0.0;
228 if (c.walls)
229 for (int comp = 0; comp < 3; ++comp)
230 identLoc = std::fmax(identLoc, maxAbsDev(sd.getVofAdvectedVelocity(comp), UNIF[comp]));
231 double ident = 0.0;
233
234 const double v0Loc = sumOf(sd.getVof());
235 double v0 = 0.0;
237 for (int it = 1; it < c.steps; ++it)
238 sd.step();
239 const double v1Loc = sumOf(sd.getVof());
240 double v1 = 0.0;
242
243 std::vector<double> gu[3], gua[3];
244 for (int comp = 0; comp < 3; ++comp) {
245 gu[comp] = gatherGlobal(sd.getVelocity(comp), ox, oy, oz, lnx, lny, lnz, rank, size);
246 gua[comp] =
247 gatherGlobal(sd.getVofAdvectedVelocity(comp), ox, oy, oz, lnx, lny, lnz, rank, size);
248 }
249 const std::vector<double> gp =
250 gatherGlobal(sd.getPressure(), ox, oy, oz, lnx, lny, lnz, rank, size);
251 const std::vector<double> gc =
252 gatherGlobal(sd.getVof(), ox, oy, oz, lnx, lny, lnz, rank, size);
253
254 if (rank == 0) {
255 if (c.walls)
256 std::printf(" [%-9s np=%d] uniform-velocity identity on the advection: %.17g%s\n",
257 c.name, size, ident, ident == 0.0 ? " (BITWISE, all ranks)" : "");
258 if (!(ident == 0.0)) {
259 std::printf(" [%-9s np=%d] FAIL — the identity is not exact under decomposition\n",
260 c.name, size);
261 fail = 1;
262 }
263 IbmSolver ref(NX, NY, NZ);
264 configure(ref, c, 0, 0, 0, NX, NY, NZ);
265 for (int it = 0; it < c.steps; ++it)
266 ref.step();
267 double du = 0, umag = 0, dua = 0;
268 for (int comp = 0; comp < 3; ++comp) {
269 du = std::fmax(du, maxAbsDiff(gu[comp], ref.getVelocity(comp)));
270 dua = std::fmax(dua, maxAbsDiff(gua[comp], ref.getVofAdvectedVelocity(comp)));
271 umag = std::fmax(umag, maxAbs(ref.getVelocity(comp)));
272 }
273 const double dp = c.walls ? maxAbsDiff(gp, ref.getPressure())
274 : maxAbsDiff(demean(gp), demean(ref.getPressure()));
275 const double dc = maxAbsDiff(gc, ref.getVof());
276 const double pmag = maxAbs(ref.getPressure());
277 const double drift = (v1 - v0) / v0;
278 const double utol = (size == 1) ? 0.0 : std::fmax(1e-12, 1e-11 * umag);
279 const double ptol = (size == 1) ? 0.0 : std::fmax(1e-12, 1e-11 * pmag);
280 const double ctol = (size == 1) ? 0.0 : 1e-11;
281 std::printf(
282 " [%-9s np=%d] du %.3e du_adv %.3e (tol %.1e, |u| %.2e) dp %.3e (tol %.1e, "
283 "|P| %.2e) dC %.3e (tol %.1e) dV/V %+.3e\n",
284 c.name, size, du, dua, utol, umag, dp, ptol, pmag, dc, ctol, drift);
285 if (!(du <= utol) || !(dua <= utol) || !(dp <= ptol) || !(dc <= ctol)) {
286 std::printf(" [%-9s np=%d] FAIL — distributed vs single-rank\n", c.name, size);
287 fail = 1;
288 }
289 if (!c.walls && !(std::fabs(drift) < 1e-11)) {
290 std::printf(" [%-9s np=%d] FAIL — colour volume drift %.3e\n", c.name, size, drift);
291 fail = 1;
292 }
293 }
295 }
296 if (rank == 0)
297 std::printf(fail ? "FAILED\n" : "OK\n");
298 }
299 Kokkos::finalize();
300 MPI_Finalize();
301 return fail;
302}
void setDomainBc(int face, int type, double vx, double vy, double vz)
std::vector< double > getVofAdvectedVelocity(int c)
void setAdvection(bool on)
void uploadVelocity(const std::vector< double > &uu, const std::vector< double > &vv, const std::vector< double > &ww)
Upload an initial velocity field in the caller's PHYSICAL units (converted per component to the index...
std::vector< double > getVof()
void setPropertyModel(const std::string &target, ClosureKind kind, const std::string &in0, const std::string &in1, const std::vector< double > &params)
void enableVofMomentum(double rhoGasPhys, double rhoLiquidPhys)
rhoGas / rhoLiquid are PHYSICAL densities, the same numbers the rho closure is given.
void setVof(const std::vector< double > &c)
void setPressureGeometry(const std::vector< double > &sdfInner)
void setField(const std::string &name, const std::vector< double > &v)
std::vector< double > getVelocity(int c)
void setMu(double m)
Dynamic viscosity, in the caller's units.
void setRho(double r)
Fluid density, in the caller's units.
std::vector< double > getPressure()
int main()
Definition dbg_scene.cpp:13
flow — host-facing Kokkos IBM Navier-Stokes solver (drop-in flow-style API).
const char * name
static double maxAbs(const std::vector< double > &a)
static std::vector< double > demean(std::vector< double > v)
static constexpr int NY
static const double UNIF[3]
static std::vector< double > blockOf(Fn f, int ox, int oy, int oz, int lnx, int lny, int lnz)
static double forceXAt(int, int, int z)
static std::vector< double > gatherGlobal(const std::vector< double > &local, int ox, int oy, int oz, int lnx, int lny, int lnz, int rank, int size)
static double maxAbsDev(const std::vector< double > &a, double r)
static double maxAbsDiff(const std::vector< double > &a, const std::vector< double > &b)
static double sumOf(const std::vector< double > &a)
static double colourAt(int x, int y, int z)
static constexpr int NX
static constexpr std::size_t GCELLS
static constexpr int NZ
static void configure(IbmSolver &s, const Config &c, int ox, int oy, int oz, int lnx, int lny, int lnz)