26#include <Kokkos_Core.hpp>
29#include "peclet/core/common/types.hpp"
30#include "peclet/core/decomp/block_decomposer.hpp"
36using peclet::core::IVec;
37using peclet::core::decomp::BlockDecomposer;
47constexpr int kDim = 3;
54 std::fprintf(stderr, "CHECK failed: %s\n at %s:%d\n", #cond, __FILE__, __LINE__); \
63enum class Flow { Uniform, LeVeque, Cellular };
65KOKKOS_INLINE_FUNCTION
double cellPsi(
double x,
double y,
double A,
double phase) {
66 const double PI = 3.14159265358979323846;
67 return -(
A / (2.0 * PI)) * Kokkos::sin(2.0 * PI * x) * Kokkos::sin(2.0 * PI * y) * phase;
69void fillCellular(WyAdvector& a,
vofscene::Block b,
double h,
double A,
double phase) {
71 const double invh = 1.0 /
h;
73 b, KOKKOS_LAMBDA(
long i,
int gx,
int gy,
int) {
74 const double xn = gx *
h, yn = gy *
h, xp = (gx + 1) * h, yp = (gy + 1) *
h;
75 u(i) = (cellPsi(xp, yp, A, phase) - cellPsi(xp, yn, A, phase)) * invh;
76 v(i) = -(cellPsi(xp, yp, A, phase) - cellPsi(xn, yp, A, phase)) * invh;
81void fillFlow(Flow f, WyAdvector& a,
vofscene::Block b,
double h,
double phase) {
82 if (f == Flow::Uniform)
84 else if (f == Flow::LeVeque)
87 fillCellular(a, b, h, 1.0, phase);
95 std::vector<Seed> seeds;
100 bool orphanMaster =
false;
111std::vector<Seed> lattice(
int n,
double rmin,
double rmax) {
114 for (
int i = 0;
i <
n; ++
i)
115 for (
int j = 0;
j <
n; ++
j)
116 for (
int m = 0;
m <
n; ++
m, ++
k)
117 v.push_back(Seed{(i + 0.5) / n, (j + 0.5) / n, (m + 0.5) / n,
118 rmin + (rmax - rmin) * ((k % 8) / 7.0)});
122void runScene(
const SceneSpec& sp,
int rank,
int size) {
123 const int gn =
sp.gn;
124 const double h = 1.0 / gn;
125 const std::array<bool, 3> per{
true,
true,
true};
126 const I3
gs{gn, gn, gn};
129 BlockDecomposer<kDim> dec(
static_cast<std::size_t
>(size), IVec<kDim>{gn, gn, gn});
130 std::vector<VofBox> rankBox(
static_cast<std::size_t
>(size));
131 for (
int r = 0;
r < size; ++
r) {
132 const auto b = dec.block(
static_cast<std::size_t
>(r));
133 for (
int d = 0;
d < 3; ++
d) {
134 rankBox[
r].lo[
d] =
static_cast<int>(
b.origin[
d]);
135 rankBox[
r].hi[
d] =
static_cast<int>(
b.origin[
d] +
b.size[
d]);
138 const VofBox& me = rankBox[
rank];
139 const I3 ln{me.n(0), me.n(1), me.n(2)},
lo{me.lo[0], me.lo[1], me.lo[2]};
143 patch.init(ln.x, ln.y, ln.z, h,
G);
147 set.init(gs, per, rank, size, h);
148 auto ex = std::make_shared<VofBlockExchange>();
149 ex->init(gs, per, rankBox, rank);
150 VofBlockExchange::Patch pp;
151 pp.e = patch.extent();
152 pp.n = patch.inner();
155 ex->setPatch(pp, patch.faceU(), patch.faceV(), patch.faceW());
156 ex->setComm(MPI_COMM_WORLD);
159 set.reassignEvery =
sp.reassign;
160 for (
const auto& s :
sp.seeds)
163 set.scatter(patch.colour());
167 ref.init(gn, gn, gn, h,
G);
169 const I3
re =
ref.extent();
172 rset.init(gs, per, 0, 1, h);
174 rpatch.init(gn, gn, gn, h,
G);
177 auto rex = std::make_shared<VofBlockExchange>();
178 std::vector<VofBox>
one(1);
179 one[0].lo[0] =
one[0].lo[1] =
one[0].lo[2] = 0;
180 one[0].hi[0] =
one[0].hi[1] =
one[0].hi[2] = gn;
181 rex->init(gs, per, one, 0);
182 VofBlockExchange::Patch rp;
183 rp.e = rpatch.extent();
184 rp.n = rpatch.inner();
187 rex->setPatch(rp, rpatch.faceU(), rpatch.faceV(), rpatch.faceW());
188 rset.setExchange(rex);
191 rset.reassignEvery =
sp.reassign;
192 for (
const auto& s :
sp.seeds)
193 rset.seedSphere(
s.
x,
s.
y,
s.
z,
s.
r);
194 rset.assignMasters();
195 rset.scatter(rpatch.colour());
197 auto compare = [&](
const char* when) {
198 auto hLoc = Kokkos::create_mirror_view_and_copy(Kokkos::HostSpace(), patch.colour());
199 auto hRef = Kokkos::create_mirror_view_and_copy(Kokkos::HostSpace(), rpatch.colour());
202 for (
int z = 0;
z < ln.z; ++
z)
203 for (
int y = 0;
y < ln.y; ++
y)
204 for (
int x = 0;
x < ln.x; ++
x) {
205 const double a = hLoc(
L3(x +
G, y +
G, z +
G, patch.extent()));
206 const double b = hRef(
L3(x +
lo.x +
G, y +
lo.y +
G, z +
lo.z +
G, re));
207 if (std::memcmp(&a, &b,
sizeof(
double)) != 0) {
209 worst = std::fmax(worst, std::fabs(a - b));
214 MPI_Allreduce(&diff, &all, 1, MPI_LONG, MPI_SUM, MPI_COMM_WORLD);
215 MPI_Allreduce(&worst, &wall, 1, MPI_DOUBLE, MPI_MAX, MPI_COMM_WORLD);
216 if (all != 0 && rank == 0)
217 std::printf(
" %s: %ld inner cells differ (max|d| %.3e)\n", when, all, wall);
225 for (
long s = 0;
s <
sp.steps; ++
s) {
226 const double phase = std::cos(M_PI * (s + 0.5) *
sp.dt /
sp.T);
227 fillFlow(
sp.flow, patch, pblk, h, phase);
228 fillFlow(
sp.flow, rpatch, rpblk, h, phase);
229 set.advect(
sp.dt, patch.colour());
230 rset.advect(
sp.dt, rpatch.colour());
231 nMigrated += set.lastReassigned();
240 const std::size_t
nb = set.count();
241 std::vector<double> volLoc(nb, 0.0), volAll(nb, 0.0), volRef(nb, 0.0);
242 for (std::size_t i = 0;
i <
nb; ++
i)
243 if (set.blocks()[i].mine())
244 volLoc[
i] = set.blocks()[
i].stats().volume;
245 MPI_Allreduce(volLoc.data(), volAll.data(),
static_cast<int>(nb), MPI_DOUBLE, MPI_SUM,
247 for (std::size_t i = 0;
i <
nb; ++
i)
248 volRef[i] = rset.blocks()[
i].stats().volume;
249 double volDiff = 0.0;
250 for (std::size_t i = 0;
i <
nb; ++
i)
251 volDiff = std::fmax(volDiff, std::fabs(volAll[i] - volRef[i]));
254 long ownedByMaster = 0, blocksWithNoLocalCells = 0;
255 for (
const auto& b : set.
blocks()) {
256 const VofBox ov = VofBox::intersect(
b.box, rankBox[
b.master]);
258 ++blocksWithNoLocalCells;
260 ownedByMaster += ov.cells();
262 std::vector<long> mc, cc;
263 set.masterCensus(mc, cc);
266 std::printf(
" [%s] %d^3, %ld steps, np=%d, %zu blocks, local block %dx%dx%d\n",
sp.name, gn,
267 sp.steps, size, nb, ln.x, ln.y, ln.z);
268 std::printf(
" bitwise: first differing step %ld (-1 = none) |dV| vs np=1 %.3e\n",
270 std::printf(
" masters per rank:");
271 for (
int r = 0;
r < size; ++
r)
272 std::printf(
" %ld", mc[r]);
273 std::printf(
" block cells per rank:");
274 for (
int r = 0;
r < size; ++
r)
275 std::printf(
" %ld", cc[r]);
276 std::printf(
" cell imbalance %.3f\n", set.cellImbalance());
277 std::printf(
" blocks whose master owns NONE of their cells: %ld / %zu\n",
278 blocksWithNoLocalCells, nb);
281 " assignment mode %d, re-assigned every %ld steps: %ld master CHANGES migrated "
282 "(%ld B in %ld msgs on rank 0 at the last one)\n",
283 sp.assign,
sp.reassign, nMigrated, ex->migrateBytes(), ex->migrateMessages());
284 std::printf(
" exchange: gather %ld B in %ld msgs, scatter %ld B in %ld msgs (rank 0)\n",
285 ex->gatherBytes(), ex->gatherMessages(), ex->scatterBytes(), ex->scatterMessages());
287 CHECK(volDiff < 1e-15);
288 if (size > 1 &&
sp.orphanMaster)
289 CHECK(blocksWithNoLocalCells > 0);
294int main(
int argc,
char** argv) {
295 MPI_Init(&argc, &argv);
296 Kokkos::initialize(argc, argv);
298 int rank = 0, size = 1;
299 MPI_Comm_rank(MPI_COMM_WORLD, &rank);
300 MPI_Comm_size(MPI_COMM_WORLD, &size);
302 std::printf(
"VoF rung W0 (WO-W0) - distributed block container, backend: %s, np = %d\n",
303 SExec::name(), size);
306 runScene(SceneSpec{Flow::LeVeque,
311 {{0.35, 0.35, 0.35, 0.15}},
312 "G1 one bubble / LeVeque"},
317 runScene(SceneSpec{Flow::Cellular,
322 {{0.38, 0.5, 0.5, 0.10}, {0.62, 0.5, 0.5, 0.10}},
323 "G2 two bubbles / cellular"},
328 runScene(SceneSpec{Flow::Uniform,
333 {{0.30, 0.30, 0.30, 0.12}, {0.70, 0.65, 0.35, 0.10}, {0.5, 0.2, 0.8, 0.09}},
334 "G3 three bubbles / translation",
343 runScene(SceneSpec{Flow::LeVeque, 48,
345 1.5, 24, lattice(4, 2.0 / 48, 8.0 / 48),
346 "W1 64 bubbles / LeVeque / LPT + re-assignment",
true,
352 MPI_Allreduce(&
failures, &total, 1, MPI_INT, MPI_SUM, MPI_COMM_WORLD);
355 std::printf(
"%s (%d failure%s)\n",
failures ?
"FAILED" :
"PASSED",
failures,
flow — VoF Part III rung W0: the per-bubble VoF BLOCK, a third container over the same L1 kernels (su...
flow — VoF Part III rung W0: the block gather/scatter (VOF_PLAN.md §10 design items 2 and 4).
The gather / scatter / table replication of the block container.
The container: a replicated table of blocks, the per-step orchestration (gather → advect → re-centre ...
Weymouth-Yue split advection of a colour field on an extended (inner + ghost) block.
VofMasterAssign
How a block's MASTER rank is chosen.
long L3(int x, int y, int z, I3 e)
Kokkos::DefaultExecutionSpace SExec
Kokkos::View< double *, SMem > SField
void fillLeVeque(WyAdvector &a, Block b, double h, double phase)
void forEachExtended(Block b, F fn)
Visit every cell of the extended block with (local linear index, global cell index).
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).
Block blockOf(const WyAdvector &a, I3 origin)
Half-open global cell box [lo, hi).
Where this block sits in the global grid: o is the global index of inner cell (0,0,...
static constexpr int kDim
static bool compare(const char *what, const std::vector< Row > &built, const std::vector< Row > &predicted, int rank)