flow 1.3.0
Kokkos cut-cell IBM incompressible Navier-Stokes solver + pnm pore extraction
Loading...
Searching...
No Matches
test_vof_advect_mpi.cpp
Go to the documentation of this file.
1// VoF rung V1 (WO-E) — distributed Weymouth-Yue split advection: np 2 and 4 must reproduce the
2// single-rank colour field BIT FOR BIT, periodic and non-periodic.
3//
4// The reference is not a stored baseline: every rank additionally runs the SAME problem on the
5// whole global grid as one block with a serial periodic/Neumann ghost fill (i.e. the np = 1 path),
6// and compares its own sub-block bitwise. Under np = 1 that makes the test a self-consistency check
7// of the two ghost-fill routes; under np = 2 / 4 it is the real decomposition-independence gate.
8//
9// Why bitwise identity is reachable at all: the WY update of a cell reads only its own C, the two
10// face velocities, the frozen dilation flag, and the two face fluxes -- and each face flux is
11// computed ONCE per face from the donor's PLIC, so the two cells sharing a face (in-rank, or across
12// a rank boundary) consume the identical double. No reduction enters the update; the CFL max is the
13// only all-reduce and it only gates the step. So there is no legitimate reason for a last-bit
14// difference, and one appearing in ghost-adjacent cells means the halo width or the
15// exchange-per-sweep choreography is wrong (WO-E escalation list).
16//
17// The colour field carries its OWN GridHaloTopology at ghost width 3 (VOF_PLAN.md §3 rule 1); the
18// solver's G = 2 machinery is untouched -- nothing here includes flow_ibm.hpp.
19#include <mpi.h>
20
21#include <array>
22#include <cmath>
23#include <cstdio>
24#include <cstring>
25#include <Kokkos_Core.hpp>
26#include <vector>
27
28#include "peclet/core/common/types.hpp"
29#include "peclet/core/common/view.hpp"
30#include "peclet/core/decomp/block_decomposer.hpp"
31#include "peclet/core/halo/grid_halo.hpp"
32#include "peclet/core/halo/grid_halo_topology.hpp"
33#include "vof/advect_wy.hpp"
34#include "vof_advect_scenes.hpp"
35
36namespace {
37using peclet::core::Index;
38using peclet::core::IVec;
39using peclet::core::decomp::BlockDecomposer;
40using peclet::core::halo::GridHalo;
41using peclet::core::halo::GridHaloTopology;
46using vofscene::Block;
47
48constexpr int kDim = 3;
49constexpr int G = 3; // the colour field's own ghost width
50
51int failures = 0;
52#define CHECK(cond) \
53 do { \
54 if (!(cond)) { \
55 std::fprintf(stderr, "CHECK failed: %s\n at %s:%d\n", #cond, __FILE__, __LINE__); \
56 ++failures; \
57 } \
58 } while (0)
59
60enum class Scene { UniformSphere, LeVequeWalls };
61
63void seedScene(Scene sc, WyAdvector& a, Block b, double h, long s, double dt, double T) {
64 if (sc == Scene::UniformSphere) {
65 if (s < 0)
66 vofscene::initSphere(a.colour(), b, h, 0.5, 0.375, 0.3125, 0.2);
67 else
68 vofscene::fillUniform(a, b, 1.0, 0.7, -0.4);
69 } else {
70 if (s < 0)
71 vofscene::initSphere(a.colour(), b, h, 0.35, 0.35, 0.35, 0.15);
72 else
73 vofscene::fillLeVeque(a, b, h, std::cos(M_PI * (s + 0.5) * dt / T));
74 }
75}
76
77struct SceneSpec {
78 Scene scene;
79 IVec<kDim> gs;
80 double h, dt, T;
81 long steps;
82 std::array<bool, kDim> periodic;
83 const char* name;
84};
85
88void runScene(const SceneSpec& sp, int rank, int size) {
89 const IVec<kDim> gs = sp.gs;
90
91 // ---------------- distributed block ----------------
92 BlockDecomposer<kDim> dec(static_cast<std::size_t>(size), gs);
93 GridHaloTopology<kDim> topo;
94 topo.buildTopology(dec, rank, G, sp.periodic, MPI_COMM_WORLD);
95 GridHalo<double> halo;
96 halo.init(topo);
97 const auto& idx = topo.indexer();
98 const auto inner = idx.sizeInner(), ogh = idx.originInclGhost();
99 const I3 origin{static_cast<int>(ogh[0]) + G, static_cast<int>(ogh[1]) + G,
100 static_cast<int>(ogh[2]) + G};
101
102 const I3 gsi{static_cast<int>(gs[0]), static_cast<int>(gs[1]), static_cast<int>(gs[2])};
103
104 WyAdvector adv;
105 adv.init(static_cast<int>(inner[0]), static_cast<int>(inner[1]), static_cast<int>(inner[2]), sp.h,
106 G);
107 const Block blk = vofscene::blockOf(adv, origin);
108 const std::array<bool, 3> per{sp.periodic[0], sp.periodic[1], sp.periodic[2]};
109 adv.exchange = [&halo, blk, gsi, per](SField f) {
110 halo.exchange(f); // cross-rank + periodic wrap (nothing written outside a non-periodic domain)
111 vofscene::clampFill(f, blk, gsi, per[0], per[1], per[2]);
112 };
113 adv.globalMax = [](double v) {
114 double r = v;
115 MPI_Allreduce(&v, &r, 1, MPI_DOUBLE, MPI_MAX, MPI_COMM_WORLD);
116 return r;
117 };
118
119 seedScene(sp.scene, adv, blk, sp.h, -1, sp.dt, sp.T);
120 adv.syncGhosts();
121 double v0Loc = adv.diagnostics().sumC, v0 = 0.0;
122 MPI_Allreduce(&v0Loc, &v0, 1, MPI_DOUBLE, MPI_SUM, MPI_COMM_WORLD);
123 for (long s = 0; s < sp.steps; ++s) {
124 seedScene(sp.scene, adv, blk, sp.h, s, sp.dt, sp.T);
125 adv.advect(sp.dt, s);
126 }
127 const auto dLoc = adv.diagnostics();
128 double v1Loc = dLoc.sumC, v1 = 0.0;
129 MPI_Allreduce(&v1Loc, &v1, 1, MPI_DOUBLE, MPI_SUM, MPI_COMM_WORLD);
130
131 // ---------------- whole-grid reference on every rank (the np = 1 path) ----------------
132 WyAdvector ref;
133 ref.init(static_cast<int>(gs[0]), static_cast<int>(gs[1]), static_cast<int>(gs[2]), sp.h, G);
134 const Block rblk = vofscene::blockOf(ref, I3{0, 0, 0});
135 const I3 eRef = ref.extent();
136 ref.exchange = [eRef, rblk, gsi, per](SField f) {
137 vofscene::periodicFill(f, eRef, G, per[0], per[1], per[2]);
138 vofscene::clampFill(f, rblk, gsi, per[0], per[1], per[2]);
139 };
140 seedScene(sp.scene, ref, rblk, sp.h, -1, sp.dt, sp.T);
141 ref.syncGhosts();
142 for (long s = 0; s < sp.steps; ++s) {
143 seedScene(sp.scene, ref, rblk, sp.h, s, sp.dt, sp.T);
144 ref.advect(sp.dt, s);
145 }
146
147 // ---------------- bitwise comparison of this rank's inner region ----------------
148 auto hLoc = Kokkos::create_mirror_view_and_copy(Kokkos::HostSpace(), adv.colour());
149 auto hRef = Kokkos::create_mirror_view_and_copy(Kokkos::HostSpace(), ref.colour());
150 long diff = 0, ghostAdjacent = 0;
151 double worst = 0.0;
152 for (int z = 0; z < static_cast<int>(inner[2]); ++z)
153 for (int y = 0; y < static_cast<int>(inner[1]); ++y)
154 for (int x = 0; x < static_cast<int>(inner[0]); ++x) {
155 const double a = hLoc(L3(x + G, y + G, z + G, adv.extent()));
156 const double b = hRef(L3(x + origin.x + G, y + origin.y + G, z + origin.z + G, eRef));
157 if (std::memcmp(&a, &b, sizeof(double)) != 0) {
158 ++diff;
159 worst = std::fmax(worst, std::fabs(a - b));
160 const bool edge = x == 0 || y == 0 || z == 0 || x == static_cast<int>(inner[0]) - 1 ||
161 y == static_cast<int>(inner[1]) - 1 ||
162 z == static_cast<int>(inner[2]) - 1;
163 if (edge)
164 ++ghostAdjacent;
165 }
166 }
167 long diffAll = 0, edgeAll = 0;
168 MPI_Allreduce(&diff, &diffAll, 1, MPI_LONG, MPI_SUM, MPI_COMM_WORLD);
169 MPI_Allreduce(&ghostAdjacent, &edgeAll, 1, MPI_LONG, MPI_SUM, MPI_COMM_WORLD);
170 double worstAll = 0.0;
171 MPI_Allreduce(&worst, &worstAll, 1, MPI_DOUBLE, MPI_MAX, MPI_COMM_WORLD);
172
173 if (rank == 0) {
174 std::printf(" [%s] %dx%dx%d, %ld steps, np=%d, block %dx%dx%d:\n", sp.name,
175 static_cast<int>(gs[0]), static_cast<int>(gs[1]), static_cast<int>(gs[2]), sp.steps,
176 size, static_cast<int>(inner[0]), static_cast<int>(inner[1]),
177 static_cast<int>(inner[2]));
178 std::printf(
179 " bitwise diffs vs whole-grid run: %ld (of which ghost-adjacent %ld)"
180 " max|diff| %.3e\n",
181 diffAll, edgeAll, worstAll);
182 std::printf(" global volume drift %.3e C[%.3e, %.6f] wisps(rank0) %ld\n",
183 std::fabs(v1 - v0) / v0, dLoc.minC, dLoc.maxC, dLoc.wisps);
184 }
185 CHECK(diffAll == 0);
186 CHECK(std::fabs(v1 - v0) / v0 < 1e-13);
187}
188
189} // namespace
190
191int main(int argc, char** argv) {
192 MPI_Init(&argc, &argv);
193 Kokkos::initialize(argc, argv);
194 {
195 int rank = 0, size = 1;
196 MPI_Comm_rank(MPI_COMM_WORLD, &rank);
197 MPI_Comm_size(MPI_COMM_WORLD, &size);
198 if (rank == 0)
199 std::printf("VoF rung V1 (WO-E) - distributed Weymouth-Yue, backend: %s, np = %d\n",
200 peclet::flow::SExec::name(), size);
201
202 // periodic: sphere in a uniform (non-diagonal-symmetric) translation
203 runScene(SceneSpec{Scene::UniformSphere,
204 IVec<kDim>{32, 24, 20},
205 1.0 / 32,
206 // At the default (proven 3D) cap of 0.25; this scene gates np-bitwise
207 // identity, so the exact CFL is free and is kept inside the bound.
208 0.25 * (1.0 / 32),
209 1.0,
210 24,
211 {true, true, true},
212 "periodic / uniform translation"},
213 rank, size);
214
215 // non-periodic: the LeVeque deformation field on the closed unit box (u.n = 0 on all six
216 // faces), Neumann colour ghosts outside the domain
217 runScene(SceneSpec{Scene::LeVequeWalls,
218 IVec<kDim>{24, 24, 24},
219 1.0 / 24,
220 3.0 / 600,
221 3.0,
222 24,
223 {false, false, false},
224 "walls / LeVeque deformation"},
225 rank, size);
226
227 // mixed periodicity, to exercise a block that is periodic on one axis and walled on another
228 runScene(SceneSpec{Scene::LeVequeWalls,
229 IVec<kDim>{24, 24, 24},
230 1.0 / 24,
231 3.0 / 600,
232 3.0,
233 24,
234 {true, false, true},
235 "mixed periodic/wall / LeVeque"},
236 rank, size);
237
238 int total = 0;
239 MPI_Allreduce(&failures, &total, 1, MPI_INT, MPI_SUM, MPI_COMM_WORLD);
240 failures = total;
241 if (rank == 0)
242 std::printf("%s (%d failure%s)\n", failures ? "FAILED" : "PASSED", failures,
243 failures == 1 ? "" : "s");
244 }
245 Kokkos::finalize();
246 MPI_Finalize();
247 return failures ? 1 : 0;
248}
flow — VoF rung V1: Weymouth & Yue (2010) directionally-split, exactly volume-conserving geometric ad...
Weymouth-Yue split advection of a colour field on an extended (inner + ghost) block.
int main()
Definition dbg_scene.cpp:13
float worst
Definition cmp.py:18
x
Definition cmp.py:24
y
Definition cmp.py:24
long L3(int x, int y, int z, I3 e)
Kokkos::View< double *, SMem > SField
void fillLeVeque(WyAdvector &a, Block b, double h, double phase)
void periodicFill(SField f, I3 e, int g, bool px, bool py, bool pz)
Periodic wrap of a single-block extended field (serial reference path; the MPI path uses core's GridH...
void fillUniform(WyAdvector &a, Block b, double ux, double uy, double uz)
Uniform translation. Discrete divergence is EXACTLY zero (identical values differenced).
void initSphere(SField c, Block b, double h, double cx, double cy, double cz, double r, int levels=4)
Sphere of fluid (C = 1 inside), exact to ~5e-7 relative volume at R/h = 16 via the octree helper in p...
void clampFill(SField f, Block b, I3 gs, bool px, bool py, bool pz)
Zero-gradient (Neumann) fill of the ghost cells that lie OUTSIDE the global domain on a non-periodic ...
Block blockOf(const WyAdvector &a, I3 origin)
Where this block sits in the global grid: o is the global index of inner cell (0,0,...
static constexpr int G
static constexpr int kDim
#define CHECK(cond)