flow 1.3.0
Kokkos cut-cell IBM incompressible Navier-Stokes solver + pnm pore extraction
Loading...
Searching...
No Matches
block_exchange.hpp
Go to the documentation of this file.
1
39#ifndef PECLET_FLOW_VOF_BLOCK_EXCHANGE_HPP
40#define PECLET_FLOW_VOF_BLOCK_EXCHANGE_HPP
41
42#include <algorithm>
43#include <array>
44#include <cassert>
45#include <cstdint>
46#include <Kokkos_Core.hpp>
47#include <map>
48#include <stdexcept>
49#include <type_traits>
50#include <vector>
51
52#ifdef PECLET_FLOW_MPI
53#include <mpi.h>
54#endif
55
56#include "mac_cutcell.hpp" // ccFor3 (rule H)
58#include "policy.hpp"
59
60namespace peclet::flow::vof {
61
64struct VofPiece {
65 int rank = 0;
67 int loc[3]{0, 0, 0};
68 long cells() const { return g.cells(); }
69};
70
73struct VofMoveJob {
74 const double* src;
75 double* dst;
76 I3 e, be;
77 I3 s, q;
78 I3 n;
79};
80
86
88inline bool vofMoveOverlap(const VofMoveJob& a, const VofMoveJob& b) {
89 if (a.dst != b.dst)
90 return false;
91 return a.q.x < b.q.x + b.n.x && b.q.x < a.q.x + a.n.x && a.q.y < b.q.y + b.n.y &&
92 b.q.y < a.q.y + a.n.y && a.q.z < b.q.z + b.n.z && b.q.z < a.q.z + a.n.z;
93}
94
101inline void vofBuildPieces(const VofBox& box, I3 gs, const std::array<bool, 3>& per,
102 const std::vector<VofBox>& rankBox, std::vector<VofPiece>& out,
103 int outside = 0) {
104 out.clear();
105 VofRun rx[5], ry[5], rz[5];
106 const int nx = vofAxisRuns(box.lo[0], box.hi[0], gs.x, per[0], rx, outside);
107 const int ny = vofAxisRuns(box.lo[1], box.hi[1], gs.y, per[1], ry, outside);
108 const int nz = vofAxisRuns(box.lo[2], box.hi[2], gs.z, per[2], rz, outside);
109 const int gsa[3] = {gs.x, gs.y, gs.z};
110 for (int iz = 0; iz < nz; ++iz)
111 for (int iy = 0; iy < ny; ++iy)
112 for (int ix = 0; ix < nx; ++ix) {
113 VofBox s;
114 s.lo[0] = rx[ix].g0;
115 s.hi[0] = rx[ix].g0 + rx[ix].len;
116 s.lo[1] = ry[iy].g0;
117 s.hi[1] = ry[iy].g0 + ry[iy].len;
118 s.lo[2] = rz[iz].g0;
119 s.hi[2] = rz[iz].g0 + rz[iz].len;
120 const int base[3] = {rx[ix].loc0, ry[iy].loc0, rz[iz].loc0};
121 for (std::size_t r = 0; r < rankBox.size(); ++r) {
122 VofBox own = rankBox[r];
123 if (outside > 0)
124 for (int d = 0; d < 3; ++d) {
125 if (per[d])
126 continue;
127 if (own.lo[d] == 0)
128 own.lo[d] = -outside;
129 if (own.hi[d] == gsa[d])
130 own.hi[d] = gsa[d] + outside;
131 }
132 const VofBox o = VofBox::intersect(s, own);
133 if (o.empty())
134 continue;
135 VofPiece p;
136 p.rank = static_cast<int>(r);
137 p.g = o;
138 for (int d = 0; d < 3; ++d)
139 p.loc[d] = base[d] + (o.lo[d] - s.lo[d]);
140 out.push_back(p);
141 }
142 }
143}
144
147 public:
149 struct Patch {
150 I3 e{0, 0, 0};
151 I3 n{0, 0, 0};
152 I3 o{0, 0, 0};
153 int g = 0;
154 };
155
158 void init(I3 gs, std::array<bool, 3> per, std::vector<VofBox> rankBox, int rank) {
159 gs_ = gs;
160 per_ = per;
161 rankBox_ = std::move(rankBox);
162 rank_ = rank;
163 size_ = static_cast<int>(rankBox_.size());
164 if (rank_ < 0 || rank_ >= size_)
165 throw std::runtime_error("peclet::flow::vof::VofBlockExchange: rank out of range");
166 }
167
172 void setRankBoxes(std::vector<VofBox> rankBox) {
173 if (rankBox.size() != rankBox_.size())
174 throw std::runtime_error(
175 "peclet::flow::vof::VofBlockExchange::setRankBoxes: rank count changed");
176 rankBox_ = std::move(rankBox);
177 }
178
182 patch_ = p;
183 lu_ = uf;
184 lv_ = vf;
185 lw_ = wf;
186 }
187
188#ifdef PECLET_FLOW_MPI
189 void setComm(MPI_Comm c) {
190 comm_ = c;
191 hasComm_ = true;
192 }
193#endif
194
195 long gatherBytes() const override { return gBytes_; }
196 long scatterBytes() const override { return sBytes_; }
198 long gatherMessages() const { return gMsgs_; }
199 long scatterMessages() const { return sMsgs_; }
200
201 // ================================================================== rung W1 item (b):
202 // DEVICE-RESIDENT packing.
203 //
204 // The pack/unpack kernels run in the block's own memory space, so the only host traffic left is
205 // the MPI staging copy of each MESSAGE (one `deep_copy` per peer, not one mirror of the whole
206 // local patch per step) -- and for the master's OWN cells there is no host traffic at all: the
207 // copy is device-to-device. That is exactly the order `core`'s grid halo grew its device-resident
208 // variant (host-staged first, proven, then the device pack with a host staging buffer at the MPI
209 // boundary), and the reason the staging survives is that the MPI here is not guaranteed
210 // CUDA-aware. Every step is a copy of a double, so the device path is BITWISE the host path;
211 // `deviceStaging = false` selects the W0 arm for the measurement.
212 //
213 // The four transfers of the container are one pattern with four instantiations:
214 //
215 // gatherFaceVel EXTENDED box, 3 components, patch -> block (blockBase = 0)
216 // gatherColour INNER box, 1 component, patch -> block (blockBase = ghost)
217 // scatterColourMax INNER box, 1 component, block -> patch, MAX (blockBase = ghost)
218 // scatterForceSum INNER box, 3 components, block -> patch, SUM (blockBase = ghost)
219 //
220 // so `gatherImpl` / `scatterImpl` take the box selector, the component count, the block-array
221 // base offset and (for the scatter) the combine rule, and nothing else differs.
222 bool deviceStaging = true;
223
226 void gatherImpl(std::vector<VofBlock>& blocks, int ghost, bool useExtended, int nc,
227 const SField* loc, SField (*blockView)(VofBlock&, int), int blockBase,
228 int outside = 0) {
229 gBytes_ = 0;
230 gMsgs_ = 0;
231 bufSeq_ = 0;
232 struct Recv {
233 std::size_t bi;
234 int from;
235 long cells;
236 SField dbuf;
237 HostView hbuf;
238 };
239 std::vector<Recv> recvs;
240 std::vector<SField> sendD(blocks.size());
241 std::vector<HostView> sendH(blocks.size());
242 std::vector<VofMoveJob> moves; // H-4(e), host only: the master's own pieces, every block
243#ifdef PECLET_FLOW_MPI
244 std::vector<MPI_Request> reqs;
245#endif
246 for (std::size_t bi = 0; bi < blocks.size(); ++bi) {
247 VofBlock& b = blocks[bi];
248 const VofBox box = useExtended ? b.extended(ghost) : b.box;
249 vofBuildPieces(box, gs_, per_, rankBox_, pieces_, outside);
250 const bool master = (b.master == rank_);
251 if (master)
252 for (int c = 0; c < nc; ++c)
253 Kokkos::deep_copy(SExec(), blockView(b, c), 0.0); // async: device work follows
254 std::map<int, long> counts;
255 for (const auto& p : pieces_)
256 counts[p.rank] += p.cells();
257 if (master)
258 for (const auto& kv : counts) {
259 if (kv.first == rank_)
260 continue;
261 Recv r;
262 r.bi = bi;
263 r.from = kv.first;
264 r.cells = kv.second;
265 r.dbuf = buffer(nc * kv.second);
266 r.hbuf = Kokkos::create_mirror_view(r.dbuf);
267 recvs.push_back(std::move(r));
268 }
269 if (!counts.count(rank_))
270 continue;
271 if (master) { // device-to-device, no host traffic at all
272 for (const auto& p : pieces_)
273 if (p.rank == rank_) {
274 if (kVofHostExec<SExec>) // H-4(e): collected, moved in one launch after the loop
275 appendMoveJobs(p, nc, loc, blockView, b, blockBase, moves);
276 else
277 movePiece(p, nc, loc, blockView, b, blockBase, /*toBlock=*/true);
278 }
279 } else {
280 sendD[bi] = buffer(nc * counts[rank_]);
281 long off = 0;
282 for (const auto& p : pieces_)
283 if (p.rank == rank_)
284 packFromPatch(p, nc, loc, sendD[bi], off);
285 Kokkos::fence();
286 sendH[bi] = Kokkos::create_mirror_view(sendD[bi]);
287 Kokkos::deep_copy(sendH[bi], sendD[bi]);
288 }
289 }
290 moveLocalPieces(moves); // after every block's zero fill; the pieces are disjoint
291 Kokkos::fence();
292#ifdef PECLET_FLOW_MPI
293 if (size_ > 1) {
294 requireComm();
295 for (auto& r : recvs) {
296 reqs.push_back(MPI_REQUEST_NULL);
297 MPI_Irecv(r.hbuf.data(), static_cast<int>(r.hbuf.extent(0)), MPI_DOUBLE, r.from,
298 tagOf(blocks[r.bi].id, 0), comm_, &reqs.back());
299 gBytes_ += static_cast<long>(r.hbuf.extent(0)) * 8;
300 ++gMsgs_;
301 }
302 for (std::size_t bi = 0; bi < blocks.size(); ++bi) {
303 if (sendH[bi].extent(0) == 0)
304 continue;
305 reqs.push_back(MPI_REQUEST_NULL);
306 MPI_Isend(sendH[bi].data(), static_cast<int>(sendH[bi].extent(0)), MPI_DOUBLE,
307 blocks[bi].master, tagOf(blocks[bi].id, 0), comm_, &reqs.back());
308 gBytes_ += static_cast<long>(sendH[bi].extent(0)) * 8;
309 ++gMsgs_;
310 }
311 if (!reqs.empty())
312 MPI_Waitall(static_cast<int>(reqs.size()), reqs.data(), MPI_STATUSES_IGNORE);
313 }
314#endif
315 for (auto& r : recvs) {
316 VofBlock& b = blocks[r.bi];
317 Kokkos::deep_copy(r.dbuf, r.hbuf);
318 vofBuildPieces(useExtended ? b.extended(ghost) : b.box, gs_, per_, rankBox_, pieces_,
319 outside);
320 long off = 0;
321 for (const auto& p : pieces_)
322 if (p.rank == r.from)
323 unpackToBlock(p, nc, r.dbuf, off, blockView, b, blockBase);
324 }
325 }
326
336 void scatterImpl(std::vector<VofBlock>& blocks, int nc, SField* loc,
337 SField (*blockView)(VofBlock&, int), int blockBase, int op,
338 bool gatherLocal = false) {
339 sBytes_ = 0;
340 sMsgs_ = 0;
341 bufSeq_ = 0;
342 if (!gatherLocal)
343 zeroPatchInner(nc, loc);
344 struct Recv {
345 std::size_t bi;
346 int from;
347 SField dbuf;
348 HostView hbuf;
349 };
350 std::vector<Recv> recvs;
351 std::vector<SField> sendD;
352 std::vector<HostView> sendH;
353 std::vector<int> sendTo;
354 std::vector<long> sendTag;
355#ifdef PECLET_FLOW_MPI
356 std::vector<MPI_Request> reqs;
357#endif
358 for (std::size_t bi = 0; bi < blocks.size(); ++bi) {
359 VofBlock& b = blocks[bi];
360 vofBuildPieces(b.box, gs_, per_, rankBox_, pieces_);
361 const bool master = (b.master == rank_);
362 std::map<int, long> counts;
363 for (const auto& p : pieces_)
364 counts[p.rank] += p.cells();
365 if (master) {
366 for (const auto& kv : counts) {
367 if (kv.first == rank_) {
368 if (!gatherLocal)
369 for (const auto& p : pieces_)
370 if (p.rank == rank_)
371 combineBlockIntoPatch(p, nc, blockView, b, blockBase, loc, op);
372 continue;
373 }
374 SField d = buffer(nc * kv.second);
375 long off = 0;
376 for (const auto& p : pieces_)
377 if (p.rank == kv.first)
378 packFromBlock(p, nc, blockView, b, blockBase, d, off);
379 Kokkos::fence();
380 HostView h = Kokkos::create_mirror_view(d);
381 Kokkos::deep_copy(h, d);
382 sendD.push_back(d);
383 sendH.push_back(h);
384 sendTo.push_back(kv.first);
385 sendTag.push_back(b.id);
386 }
387 } else if (counts.count(rank_)) {
388 Recv r;
389 r.bi = bi;
390 r.from = b.master;
391 r.dbuf = buffer(nc * counts[rank_]);
392 r.hbuf = Kokkos::create_mirror_view(r.dbuf);
393 recvs.push_back(std::move(r));
394 }
395 }
396 if (gatherLocal)
397 gatherLocalSum(blocks, nc, loc, blockView, blockBase);
398 Kokkos::fence();
399#ifdef PECLET_FLOW_MPI
400 if (size_ > 1) {
401 requireComm();
402 for (auto& r : recvs) {
403 reqs.push_back(MPI_REQUEST_NULL);
404 MPI_Irecv(r.hbuf.data(), static_cast<int>(r.hbuf.extent(0)), MPI_DOUBLE, r.from,
405 tagOf(blocks[r.bi].id, 1), comm_, &reqs.back());
406 sBytes_ += static_cast<long>(r.hbuf.extent(0)) * 8;
407 ++sMsgs_;
408 }
409 for (std::size_t k = 0; k < sendH.size(); ++k) {
410 reqs.push_back(MPI_REQUEST_NULL);
411 MPI_Isend(sendH[k].data(), static_cast<int>(sendH[k].extent(0)), MPI_DOUBLE, sendTo[k],
412 tagOf(sendTag[k], 1), comm_, &reqs.back());
413 sBytes_ += static_cast<long>(sendH[k].extent(0)) * 8;
414 ++sMsgs_;
415 }
416 if (!reqs.empty())
417 MPI_Waitall(static_cast<int>(reqs.size()), reqs.data(), MPI_STATUSES_IGNORE);
418 }
419#endif
420 for (auto& r : recvs) {
421 VofBlock& b = blocks[r.bi];
422 Kokkos::deep_copy(r.dbuf, r.hbuf);
423 vofBuildPieces(b.box, gs_, per_, rankBox_, pieces_);
424 long off = 0;
425 for (const auto& p : pieces_)
426 if (p.rank == rank_)
427 combineBufIntoPatch(p, nc, r.dbuf, off, loc, op);
428 }
429 }
430
437 void gatherLocalSum(std::vector<VofBlock>& blocks, int nc, SField* loc,
438 SField (*blockView)(VofBlock&, int), int blockBase) {
439 struct Job {
440 const double* f[3];
441 int lo[3], n[3];
442 I3 be;
443 };
444 std::vector<Job> jobs;
445 for (auto& b : blocks) {
446 if (b.master != rank_)
447 continue;
448 Job j;
449 for (int c = 0; c < 3; ++c)
450 j.f[c] = blockView(b, c < nc ? c : 0).data();
451 for (int d = 0; d < 3; ++d) {
452 j.lo[d] = b.box.lo[d];
453 j.n[d] = b.box.n(d);
454 }
455 j.be = b.advector().extent();
456 jobs.push_back(j);
457 }
458 GatherTable T;
459 T.nc = nc;
460 T.gh = blockBase;
461 for (int d = 0; d < 3; ++d)
462 T.per[d] = per_[d];
463 T.L[0] = gs_.x;
464 T.L[1] = gs_.y;
465 T.L[2] = gs_.z;
466 std::size_t j0 = 0;
467 do {
468 T.nj = static_cast<int>(std::min<std::size_t>(kVofBlockBatch, jobs.size() - j0));
469 T.first = (j0 == 0);
470 for (int k = 0; k < T.nj; ++k) {
471 for (int c = 0; c < 3; ++c)
472 T.f[k][c] = jobs[j0 + k].f[c];
473 for (int d = 0; d < 3; ++d) {
474 T.lo[k][d] = jobs[j0 + k].lo[d];
475 T.n[k][d] = jobs[j0 + k].n[d];
476 }
477 T.be[k] = jobs[j0 + k].be;
478 }
479 gatherLocalLaunch(T, loc);
480 j0 += kVofBlockBatch;
481 } while (j0 < jobs.size());
482 }
483 struct GatherTable {
484 const double* f[kVofBlockBatch][3];
487 int nj, nc, gh, L[3];
488 bool per[3], first;
489 };
490 void gatherLocalLaunch(const GatherTable& T, SField* loc) {
491 const I3 e = patch_.e, n = patch_.n, o = patch_.o;
492 const int g = patch_.g;
493 SField d0 = loc[0], d1 = T.nc > 1 ? loc[1] : loc[0], d2 = T.nc > 2 ? loc[2] : loc[0];
494 if constexpr (std::is_same_v<typename SField::memory_space, Kokkos::HostSpace>) {
495 // §14 H-3(b), host: rule H (ccFor3: rows, x inside) and per-axis job masks. m_d[x_d] holds
496 // bit k iff job k's box contains patch coordinate x_d on axis d under the periodic predicate
497 // below, so a cell visits exactly the jobs in mx[x] & my[y] & mz[z], in ascending k, with
498 // l[d] computed as there: the same jobs, order and additions -- bitwise -- without the 16
499 // jobs x 3 axes of ((v % L) + L) % L per cell. The device keeps the launch below.
500 static_assert(kVofBlockBatch <= 32, "the job masks are 32-bit");
501 const int nn[3] = {n.x, n.y, n.z}, oo[3] = {o.x, o.y, o.z};
502 std::vector<std::uint32_t> mk((std::size_t)n.x + n.y + n.z, 0u);
503 std::uint32_t* md[3] = {mk.data(), mk.data() + n.x, mk.data() + n.x + n.y};
504 for (int d = 0; d < 3; ++d)
505 for (int xd = 0; xd < nn[d]; ++xd)
506 for (int k = 0; k < T.nj; ++k) {
507 int v = xd + oo[d] - T.lo[k][d];
508 if (T.per[d])
509 v = ((v % T.L[d]) + T.L[d]) % T.L[d];
510 if (v >= 0 && v < T.n[k][d])
511 md[d][xd] |= (1u << k);
512 }
513 const std::uint32_t *mx = md[0], *my = md[1], *mz = md[2];
514 ccFor3(
515 "vof::block::gather_local_sum", C3{0, 0, 0}, C3{n.x, n.y, n.z},
516 KOKKOS_LAMBDA(int x, int y, int z) {
517 const int gp[3] = {x + o.x, y + o.y, z + o.z};
518 const long i = L3(x + g, y + g, z + g, e);
519 double acc[3] = {0.0, 0.0, 0.0};
520 if (!T.first) {
521 acc[0] = d0(i);
522 acc[1] = T.nc > 1 ? d1(i) : 0.0;
523 acc[2] = T.nc > 2 ? d2(i) : 0.0;
524 }
525 const std::uint32_t m = mx[x] & my[y] & mz[z];
526 for (int k = 0; k < T.nj; ++k) {
527 if (!((m >> k) & 1u))
528 continue;
529 int l[3];
530 for (int d = 0; d < 3; ++d) {
531 int v = gp[d] - T.lo[k][d];
532 if (T.per[d])
533 v = ((v % T.L[d]) + T.L[d]) % T.L[d];
534 l[d] = v;
535 }
536 const long q = L3(l[0] + T.gh, l[1] + T.gh, l[2] + T.gh, T.be[k]);
537 for (int c = 0; c < T.nc; ++c)
538 acc[c] += T.f[k][c][q];
539 }
540 d0(i) = acc[0];
541 if (T.nc > 1)
542 d1(i) = acc[1];
543 if (T.nc > 2)
544 d2(i) = acc[2];
545 });
546 Kokkos::fence(); // the masks live on this stack frame
547 return;
548 }
549 Kokkos::parallel_for(
550 "vof::block::gather_local_sum", MDRange3<SExec>(SExec(), {0, 0, 0}, {n.x, n.y, n.z}),
551 KOKKOS_LAMBDA(int x, int y, int z) {
552 const int gp[3] = {x + o.x, y + o.y, z + o.z};
553 const long i = L3(x + g, y + g, z + g, e);
554 double acc[3] = {0.0, 0.0, 0.0};
555 if (!T.first) {
556 acc[0] = d0(i);
557 acc[1] = T.nc > 1 ? d1(i) : 0.0;
558 acc[2] = T.nc > 2 ? d2(i) : 0.0;
559 }
560 for (int k = 0; k < T.nj; ++k) {
561 int l[3];
562 bool in = true;
563 for (int d = 0; d < 3 && in; ++d) {
564 int v = gp[d] - T.lo[k][d];
565 if (T.per[d])
566 v = ((v % T.L[d]) + T.L[d]) % T.L[d];
567 in = v >= 0 && v < T.n[k][d];
568 l[d] = v;
569 }
570 if (!in)
571 continue;
572 const long q = L3(l[0] + T.gh, l[1] + T.gh, l[2] + T.gh, T.be[k]);
573 for (int c = 0; c < T.nc; ++c)
574 acc[c] += T.f[k][c][q];
575 }
576 d0(i) = acc[0];
577 if (T.nc > 1)
578 d1(i) = acc[1];
579 if (T.nc > 2)
580 d2(i) = acc[2];
581 });
582 }
583
584 // ---- the four device kernels (public for nvcc's extended-lambda rule) -----------------------
585 void zeroPatchInner(int nc, SField* loc) {
586 const I3 e = patch_.e, n = patch_.n;
587 const int g = patch_.g;
588 for (int c = 0; c < nc; ++c) {
589 SField f = loc[c];
590 Kokkos::parallel_for(
591 "vof::block::zero_patch",
592 MDRange3<SExec>(SExec(), {0, 0, 0}, {n.x, n.y, n.z}),
593 KOKKOS_LAMBDA(int x, int y, int z) { f(L3(x + g, y + g, z + g, e)) = 0.0; });
594 }
595 }
596
597 void packFromPatch(const VofPiece& p, int nc, const SField* loc, SField buf, long& off) {
598 const I3 e = patch_.e, o = patch_.o;
599 const int g = patch_.g;
600 const int lo0 = p.g.lo[0], lo1 = p.g.lo[1], lo2 = p.g.lo[2];
601 const int n0 = p.g.n(0), n1 = p.g.n(1), n2 = p.g.n(2);
602 const long base = off;
603 SField s0 = loc[0], s1 = nc > 1 ? loc[1] : loc[0], s2 = nc > 2 ? loc[2] : loc[0];
604 Kokkos::parallel_for(
605 "vof::block::pack_patch",
606 MDRange3<SExec>(SExec(), {0, 0, 0}, {n0, n1, n2}),
607 KOKKOS_LAMBDA(int x, int y, int z) {
608 const long i = L3(lo0 + x - o.x + g, lo1 + y - o.y + g, lo2 + z - o.z + g, e);
609 const long k = base + x + static_cast<long>(y) * n0 + static_cast<long>(z) * n0 * n1;
610 buf(nc * k + 0) = s0(i);
611 if (nc > 1)
612 buf(nc * k + 1) = s1(i);
613 if (nc > 2)
614 buf(nc * k + 2) = s2(i);
615 });
616 off += static_cast<long>(n0) * n1 * n2;
617 }
618
619 void unpackToBlock(const VofPiece& p, int nc, SField buf, long& off,
620 SField (*blockView)(VofBlock&, int), VofBlock& b, int blockBase) {
621 const I3 be = b.advector().extent();
622 const int lo0 = p.g.lo[0], lo1 = p.g.lo[1], lo2 = p.g.lo[2];
623 const int n0 = p.g.n(0), n1 = p.g.n(1), n2 = p.g.n(2);
624 const int q0 = p.loc[0] + blockBase, q1 = p.loc[1] + blockBase, q2 = p.loc[2] + blockBase;
625 const long base = off;
626 (void)lo0;
627 (void)lo1;
628 (void)lo2;
629 for (int c = 0; c < nc; ++c) {
630 SField d = blockView(b, c);
631 const int cc = c;
632 Kokkos::parallel_for(
633 "vof::block::unpack_block",
634 MDRange3<SExec>(SExec(), {0, 0, 0}, {n0, n1, n2}),
635 KOKKOS_LAMBDA(int x, int y, int z) {
636 const long k = base + x + static_cast<long>(y) * n0 + static_cast<long>(z) * n0 * n1;
637 d(L3(q0 + x, q1 + y, q2 + z, be)) = buf(nc * k + cc);
638 });
639 }
640 off += static_cast<long>(n0) * n1 * n2;
641 }
642
644 void movePiece(const VofPiece& p, int nc, const SField* loc,
645 SField (*blockView)(VofBlock&, int), VofBlock& b, int blockBase, bool toBlock) {
646 const I3 e = patch_.e, o = patch_.o, be = b.advector().extent();
647 const int g = patch_.g;
648 const int lo0 = p.g.lo[0], lo1 = p.g.lo[1], lo2 = p.g.lo[2];
649 const int n0 = p.g.n(0), n1 = p.g.n(1), n2 = p.g.n(2);
650 const int q0 = p.loc[0] + blockBase, q1 = p.loc[1] + blockBase, q2 = p.loc[2] + blockBase;
651 for (int c = 0; c < nc; ++c) {
652 SField src = loc[c], dst = blockView(b, c);
653 if (!toBlock)
654 throw std::runtime_error("vof::block: movePiece is patch->block only");
655 Kokkos::parallel_for(
656 "vof::block::move_local",
657 MDRange3<SExec>(SExec(), {0, 0, 0}, {n0, n1, n2}),
658 KOKKOS_LAMBDA(int x, int y, int z) {
659 dst(L3(q0 + x, q1 + y, q2 + z, be)) =
660 src(L3(lo0 + x - o.x + g, lo1 + y - o.y + g, lo2 + z - o.z + g, e));
661 });
662 }
663 }
664
666 void appendMoveJobs(const VofPiece& p, int nc, const SField* loc,
667 SField (*blockView)(VofBlock&, int), VofBlock& b, int blockBase,
668 std::vector<VofMoveJob>& out) {
669 const I3 o = patch_.o;
670 const int g = patch_.g;
671 for (int c = 0; c < nc; ++c) {
673 J.src = loc[c].data();
674 J.dst = blockView(b, c).data();
675 J.e = patch_.e;
676 J.be = b.advector().extent();
677 J.s = I3{p.g.lo[0] - o.x + g, p.g.lo[1] - o.y + g, p.g.lo[2] - o.z + g};
678 J.q = I3{p.loc[0] + blockBase, p.loc[1] + blockBase, p.loc[2] + blockBase};
679 J.n = I3{p.g.n(0), p.g.n(1), p.g.n(2)};
680 out.push_back(J);
681 }
682 }
683
688 template <class Exec = SExec>
689 void moveLocalPieces(const std::vector<VofMoveJob>& mv) {
690 if constexpr (kVofHostExec<Exec>) {
691 for (std::size_t j0 = 0; j0 < mv.size(); j0 += kVofBlockBatch) {
692 VofMoveTable T;
693 T.nj = static_cast<int>(std::min<std::size_t>(kVofBlockBatch, mv.size() - j0));
694 for (int k = 0; k < T.nj; ++k)
695 T.job[k] = mv[j0 + k];
696#ifndef NDEBUG
697 for (int k = 0; k < T.nj; ++k)
698 for (int l = 0; l < k; ++l)
699 assert(!vofMoveOverlap(T.job[k], T.job[l]) && "vof::block: movePiece pieces overlap");
700#endif
702 "vof::block::move_local", T.nj,
703 [&](const int k) { return static_cast<long>(T.job[k].n.y) * T.job[k].n.z; },
704 [=](const int k, const long r) {
705 const VofMoveJob& J = T.job[k];
706 const int y = static_cast<int>(r % J.n.y), z = static_cast<int>(r / J.n.y);
707 const double* src = J.src + L3(J.s.x, J.s.y + y, J.s.z + z, J.e);
708 double* dst = J.dst + L3(J.q.x, J.q.y + y, J.q.z + z, J.be);
709 PECLET_FLOW_OMP_SIMD
710 for (int x = 0; x < J.n.x; ++x)
711 dst[x] = src[x];
712 });
713 }
714 }
715 }
716
717 void packFromBlock(const VofPiece& p, int nc, SField (*blockView)(VofBlock&, int), VofBlock& b,
718 int blockBase, SField buf, long& off) {
719 const I3 be = b.advector().extent();
720 const int n0 = p.g.n(0), n1 = p.g.n(1), n2 = p.g.n(2);
721 const int q0 = p.loc[0] + blockBase, q1 = p.loc[1] + blockBase, q2 = p.loc[2] + blockBase;
722 const long base = off;
723 for (int c = 0; c < nc; ++c) {
724 SField src = blockView(b, c);
725 const int cc = c;
726 Kokkos::parallel_for(
727 "vof::block::pack_block",
728 MDRange3<SExec>(SExec(), {0, 0, 0}, {n0, n1, n2}),
729 KOKKOS_LAMBDA(int x, int y, int z) {
730 const long k = base + x + static_cast<long>(y) * n0 + static_cast<long>(z) * n0 * n1;
731 buf(nc * k + cc) = src(L3(q0 + x, q1 + y, q2 + z, be));
732 });
733 }
734 off += static_cast<long>(n0) * n1 * n2;
735 }
736
737 void combineBlockIntoPatch(const VofPiece& p, int nc, SField (*blockView)(VofBlock&, int),
738 VofBlock& b, int blockBase, SField* loc, int op) {
739 const I3 e = patch_.e, o = patch_.o, be = b.advector().extent();
740 const int g = patch_.g;
741 const int lo0 = p.g.lo[0], lo1 = p.g.lo[1], lo2 = p.g.lo[2];
742 const int n0 = p.g.n(0), n1 = p.g.n(1), n2 = p.g.n(2);
743 const int q0 = p.loc[0] + blockBase, q1 = p.loc[1] + blockBase, q2 = p.loc[2] + blockBase;
744 for (int c = 0; c < nc; ++c) {
745 SField src = blockView(b, c), dst = loc[c];
746 const int opc = op;
747 Kokkos::parallel_for(
748 "vof::block::combine_block",
749 MDRange3<SExec>(SExec(), {0, 0, 0}, {n0, n1, n2}),
750 KOKKOS_LAMBDA(int x, int y, int z) {
751 const double v = src(L3(q0 + x, q1 + y, q2 + z, be));
752 const long i = L3(lo0 + x - o.x + g, lo1 + y - o.y + g, lo2 + z - o.z + g, e);
753 if (opc == 0) {
754 if (v > dst(i))
755 dst(i) = v;
756 } else {
757 dst(i) += v;
758 }
759 });
760 }
761 }
762
763 void combineBufIntoPatch(const VofPiece& p, int nc, SField buf, long& off, SField* loc, int op) {
764 const I3 e = patch_.e, o = patch_.o;
765 const int g = patch_.g;
766 const int lo0 = p.g.lo[0], lo1 = p.g.lo[1], lo2 = p.g.lo[2];
767 const int n0 = p.g.n(0), n1 = p.g.n(1), n2 = p.g.n(2);
768 const long base = off;
769 for (int c = 0; c < nc; ++c) {
770 SField dst = loc[c];
771 const int cc = c, opc = op;
772 Kokkos::parallel_for(
773 "vof::block::combine_buf",
774 MDRange3<SExec>(SExec(), {0, 0, 0}, {n0, n1, n2}),
775 KOKKOS_LAMBDA(int x, int y, int z) {
776 const long k = base + x + static_cast<long>(y) * n0 + static_cast<long>(z) * n0 * n1;
777 const double v = buf(nc * k + cc);
778 const long i = L3(lo0 + x - o.x + g, lo1 + y - o.y + g, lo2 + z - o.z + g, e);
779 if (opc == 0) {
780 if (v > dst(i))
781 dst(i) = v;
782 } else {
783 dst(i) += v;
784 }
785 });
786 }
787 off += static_cast<long>(n0) * n1 * n2;
788 }
789
790 // ---- the four public transfers ----------------------------------------------------------
791 static SField faceViewOf(VofBlock& b, int c) { return b.advector().faceVel(c); }
792 static SField colourViewOf(VofBlock& b, int c) {
793 (void)c;
794 return b.advector().colour();
795 }
796 static SField forceViewOf(VofBlock& b, int c) { return b.csfForce(c); }
797
798 void gatherFaceVel(std::vector<VofBlock>& blocks, int ghost) override {
799 // The block's extended box reaches `ghost` cells beyond a non-periodic domain face; those
800 // cells are read from the boundary rank's patch ghosts, so the patch must be at least as wide.
801 if (ghost > patch_.g)
802 throw std::runtime_error(
803 "peclet::flow::vof::VofBlockExchange: block ghost wider than the face-velocity patch");
804 if (!deviceStaging) {
805 gatherFaceVelHost(blocks, ghost);
806 return;
807 }
808 SField loc[3] = {lu_, lv_, lw_};
809 gatherImpl(blocks, ghost, /*useExtended=*/true, 3, loc, &faceViewOf, 0, /*outside=*/ghost);
810 }
811
812 void gatherColour(std::vector<VofBlock>& blocks, SField cLocal) override {
813 SField loc[1] = {cLocal};
814 gatherImpl(blocks, 0, /*useExtended=*/false, 1, loc, &colourViewOf, ghostOf(blocks));
815 }
816
817 void scatterColourMax(std::vector<VofBlock>& blocks, SField cLocal) override {
818 if (!deviceStaging) {
820 return;
821 }
822 SField loc[1] = {cLocal};
823 scatterImpl(blocks, 1, loc, &colourViewOf, ghostOf(blocks), /*op=*/0);
824 }
825
826 void scatterForceSum(std::vector<VofBlock>& blocks, SField fx, SField fy, SField fz) override {
827 SField loc[3] = {fx, fy, fz};
828 scatterImpl(blocks, 3, loc, &forceViewOf, ghostOf(blocks), /*op=*/1, /*gatherLocal=*/true);
829 }
830
833 void scatterColourSum(std::vector<VofBlock>& blocks, SField sLocal) override {
834 SField loc[1] = {sLocal};
835 scatterImpl(blocks, 1, loc, &colourViewOf, ghostOf(blocks), /*op=*/1);
836 }
837
841 void migrateColour(std::vector<VofBlock>& blocks, const std::vector<int>& oldMaster) override {
842 mBytes_ = 0;
843 mMsgs_ = 0;
844 bufSeq_ = 0;
845#ifdef PECLET_FLOW_MPI
846 if (size_ <= 1)
847 return;
848 requireComm();
849 std::vector<MPI_Request> reqs;
850 std::vector<SField> dbuf(blocks.size());
851 std::vector<HostView> hbuf(blocks.size());
852 std::vector<std::vector<double>> hAux(blocks.size());
853 for (std::size_t i = 0; i < blocks.size(); ++i) {
854 const int from = oldMaster[i], to = blocks[i].master;
855 if (from == to || (from != rank_ && to != rank_))
856 continue;
857 const I3 e = blocks[i].advector().extent();
858 const long len = static_cast<long>(e.x) * e.y * e.z;
859 dbuf[i] = buffer(len);
860 hbuf[i] = Kokkos::create_mirror_view(dbuf[i]);
861 hAux[i].resize(VofBlock::kAuxLen, 0.0);
862 if (from == rank_) {
863 Kokkos::deep_copy(dbuf[i], blocks[i].advector().colour());
864 Kokkos::deep_copy(hbuf[i], dbuf[i]);
865 blocks[i].serializeAux(hAux[i].data());
866 reqs.push_back(MPI_REQUEST_NULL);
867 MPI_Isend(hbuf[i].data(), static_cast<int>(len), MPI_DOUBLE, to, tagOf(blocks[i].id, 2),
868 comm_, &reqs.back());
869 reqs.push_back(MPI_REQUEST_NULL);
870 MPI_Isend(hAux[i].data(), VofBlock::kAuxLen, MPI_DOUBLE, to, tagOf(blocks[i].id, 3), comm_,
871 &reqs.back());
872 } else {
873 reqs.push_back(MPI_REQUEST_NULL);
874 MPI_Irecv(hbuf[i].data(), static_cast<int>(len), MPI_DOUBLE, from, tagOf(blocks[i].id, 2),
875 comm_, &reqs.back());
876 reqs.push_back(MPI_REQUEST_NULL);
877 MPI_Irecv(hAux[i].data(), VofBlock::kAuxLen, MPI_DOUBLE, from, tagOf(blocks[i].id, 3),
878 comm_, &reqs.back());
879 }
880 mBytes_ += (len + VofBlock::kAuxLen) * 8;
881 mMsgs_ += 2;
882 }
883 if (!reqs.empty())
884 MPI_Waitall(static_cast<int>(reqs.size()), reqs.data(), MPI_STATUSES_IGNORE);
885 for (std::size_t i = 0; i < blocks.size(); ++i)
886 if (oldMaster[i] != blocks[i].master && blocks[i].master == rank_) {
887 Kokkos::deep_copy(dbuf[i], hbuf[i]);
888 Kokkos::deep_copy(blocks[i].advector().colour(), dbuf[i]);
889 blocks[i].deserializeAux(hAux[i].data());
890 }
891 Kokkos::fence();
892#else
893 (void)blocks;
895#endif
896 }
897 long migrateBytes() const { return mBytes_; }
898 long migrateMessages() const { return mMsgs_; }
899
905 SField buffer(long n) {
906 const std::size_t k = static_cast<std::size_t>(bufSeq_++);
907 if (bufPool_.size() <= k)
908 bufPool_.resize(k + 1);
909 if (bufPool_[k].extent(0) != static_cast<std::size_t>(n))
910 bufPool_[k] = SField(Kokkos::view_alloc("vof::block::buf", Kokkos::WithoutInitializing),
911 static_cast<std::size_t>(n));
912 return bufPool_[k];
913 }
914
915 static int ghostOf(const std::vector<VofBlock>& blocks) {
916 for (const auto& b : blocks)
917 if (b.mine())
918 return b.advector().ghost();
919 return 3;
920 }
921
922 // ------------------------------------------------------- gather (rung W0, HOST-staged)
923 //
924 // Kept as the reference implementation and as the `deviceStaging = false` arm of the W1
925 // measurement: it mirrors the whole local patch to the host, packs with host loops, and mirrors
926 // the block arrays back. The device path below produces BIT-IDENTICAL buffers (every step is a
927 // copy of a double), which is what the W1 ctest gates.
928 void gatherFaceVelHost(std::vector<VofBlock>& blocks, int ghost) {
929 gBytes_ = 0;
930 gMsgs_ = 0;
931 auto hu = Kokkos::create_mirror_view_and_copy(Kokkos::HostSpace(), lu_);
932 auto hv = Kokkos::create_mirror_view_and_copy(Kokkos::HostSpace(), lv_);
933 auto hw = Kokkos::create_mirror_view_and_copy(Kokkos::HostSpace(), lw_);
934
935 std::vector<std::vector<double>> sendBuf(blocks.size());
936 std::vector<HostView> hbu(blocks.size()), hbv(blocks.size()), hbw(blocks.size());
937 struct Recv {
938 std::size_t bi;
939 int from;
940 std::vector<double> buf;
941 };
942 std::vector<Recv> recvs;
943#ifdef PECLET_FLOW_MPI
944 std::vector<MPI_Request> reqs;
945#endif
946
947 // 1. post receives (master side) and pack+send (owner side); do the master's own cells locally.
948 for (std::size_t bi = 0; bi < blocks.size(); ++bi) {
949 VofBlock& b = blocks[bi];
950 const VofBox eb = b.extended(ghost);
951 vofBuildPieces(eb, gs_, per_, rankBox_, pieces_, /*outside=*/ghost);
952 const bool master = (b.master == rank_);
953 if (master) {
954 hbu[bi] = Kokkos::create_mirror_view(b.advector().faceU());
955 hbv[bi] = Kokkos::create_mirror_view(b.advector().faceV());
956 hbw[bi] = Kokkos::create_mirror_view(b.advector().faceW());
957 zero(hbu[bi]);
958 zero(hbv[bi]);
959 zero(hbw[bi]);
960 }
961 // per-rank aggregated counts, in the canonical piece order
962 std::map<int, long> counts;
963 for (const auto& p : pieces_)
964 counts[p.rank] += p.cells();
965 if (master) {
966 for (const auto& kv : counts) {
967 if (kv.first == rank_)
968 continue;
969 recvs.push_back(Recv{bi, kv.first, std::vector<double>(3 * kv.second)});
970 }
971 }
972 if (counts.count(rank_)) {
973 if (master) {
974 // local: straight copy, no buffer
975 long off = 0;
976 for (const auto& p : pieces_) {
977 if (p.rank != rank_)
978 continue;
979 copyPatchToBlock(p, hu, hv, hw, hbu[bi], hbv[bi], hbw[bi], b, ghost);
980 off += p.cells();
981 }
982 (void)off;
983 } else {
984 std::vector<double>& sb = sendBuf[bi];
985 sb.resize(3 * counts[rank_]);
986 long off = 0;
987 for (const auto& p : pieces_) {
988 if (p.rank != rank_)
989 continue;
990 packPatch(p, hu, hv, hw, sb, off);
991 }
992 }
993 }
994 }
995#ifdef PECLET_FLOW_MPI
996 if (size_ > 1) {
997 requireComm();
998 for (auto& r : recvs) {
999 reqs.push_back(MPI_REQUEST_NULL);
1000 MPI_Irecv(r.buf.data(), static_cast<int>(r.buf.size()), MPI_DOUBLE, r.from,
1001 tagOf(blocks[r.bi].id, 0), comm_, &reqs.back());
1002 gBytes_ += static_cast<long>(r.buf.size()) * 8;
1003 ++gMsgs_;
1004 }
1005 for (std::size_t bi = 0; bi < blocks.size(); ++bi) {
1006 if (sendBuf[bi].empty())
1007 continue;
1008 reqs.push_back(MPI_REQUEST_NULL);
1009 MPI_Isend(sendBuf[bi].data(), static_cast<int>(sendBuf[bi].size()), MPI_DOUBLE,
1010 blocks[bi].master, tagOf(blocks[bi].id, 0), comm_, &reqs.back());
1011 gBytes_ += static_cast<long>(sendBuf[bi].size()) * 8;
1012 ++gMsgs_;
1013 }
1014 if (!reqs.empty())
1015 MPI_Waitall(static_cast<int>(reqs.size()), reqs.data(), MPI_STATUSES_IGNORE);
1016 }
1017#endif
1018 // 2. unpack
1019 for (auto& r : recvs) {
1020 VofBlock& b = blocks[r.bi];
1021 vofBuildPieces(b.extended(ghost), gs_, per_, rankBox_, pieces_, /*outside=*/ghost);
1022 long off = 0;
1023 for (const auto& p : pieces_) {
1024 if (p.rank != r.from)
1025 continue;
1026 unpackBlock(p, r.buf, off, hbu[r.bi], hbv[r.bi], hbw[r.bi], b, ghost);
1027 }
1028 }
1029 for (std::size_t bi = 0; bi < blocks.size(); ++bi) {
1030 if (blocks[bi].master != rank_)
1031 continue;
1032 Kokkos::deep_copy(blocks[bi].advector().faceU(), hbu[bi]);
1033 Kokkos::deep_copy(blocks[bi].advector().faceV(), hbv[bi]);
1034 Kokkos::deep_copy(blocks[bi].advector().faceW(), hbw[bi]);
1035 }
1036 }
1037
1038 // ------------------------------------------------------ scatter (rung W0, HOST-staged)
1039 void scatterColourMaxHost(std::vector<VofBlock>& blocks, SField cLocal) {
1040 sBytes_ = 0;
1041 sMsgs_ = 0;
1042 auto hc = Kokkos::create_mirror_view_and_copy(Kokkos::HostSpace(), cLocal);
1043 // UNPACK_MAX starts from an empty union: every inner cell not covered by a block is gas.
1044 const I3 e = patch_.e, n = patch_.n;
1045 const int g = patch_.g;
1046 for (int z = 0; z < n.z; ++z)
1047 for (int y = 0; y < n.y; ++y)
1048 for (int x = 0; x < n.x; ++x)
1049 hc(L3(x + g, y + g, z + g, e)) = 0.0;
1050
1051 struct Recv {
1052 std::size_t bi;
1053 int from;
1054 std::vector<double> buf;
1055 };
1056 std::vector<Recv> recvs;
1057 std::vector<std::vector<double>> sendBuf;
1058 std::vector<int> sendTo;
1059 std::vector<long> sendTag;
1060#ifdef PECLET_FLOW_MPI
1061 std::vector<MPI_Request> reqs;
1062#endif
1063 std::vector<HostView> hbc(blocks.size());
1064
1065 for (std::size_t bi = 0; bi < blocks.size(); ++bi) {
1066 VofBlock& b = blocks[bi];
1067 vofBuildPieces(b.box, gs_, per_, rankBox_, pieces_);
1068 const bool master = (b.master == rank_);
1069 if (master) {
1070 hbc[bi] = Kokkos::create_mirror_view(b.advector().colour());
1071 Kokkos::deep_copy(hbc[bi], b.advector().colour());
1072 }
1073 std::map<int, long> counts;
1074 for (const auto& p : pieces_)
1075 counts[p.rank] += p.cells();
1076 if (master) {
1077 for (const auto& kv : counts) {
1078 if (kv.first == rank_) {
1079 for (const auto& p : pieces_)
1080 if (p.rank == rank_)
1081 maxBlockIntoPatch(p, hbc[bi], b, hc);
1082 continue;
1083 }
1084 std::vector<double> sb(kv.second);
1085 long off = 0;
1086 for (const auto& p : pieces_)
1087 if (p.rank == kv.first)
1088 packBlockColour(p, hbc[bi], b, sb, off);
1089 sendBuf.push_back(std::move(sb));
1090 sendTo.push_back(kv.first);
1091 sendTag.push_back(b.id);
1092 }
1093 } else if (counts.count(rank_)) {
1094 recvs.push_back(Recv{bi, b.master, std::vector<double>(counts[rank_])});
1095 }
1096 }
1097#ifdef PECLET_FLOW_MPI
1098 if (size_ > 1) {
1099 requireComm();
1100 for (auto& r : recvs) {
1101 reqs.push_back(MPI_REQUEST_NULL);
1102 MPI_Irecv(r.buf.data(), static_cast<int>(r.buf.size()), MPI_DOUBLE, r.from,
1103 tagOf(blocks[r.bi].id, 1), comm_, &reqs.back());
1104 sBytes_ += static_cast<long>(r.buf.size()) * 8;
1105 ++sMsgs_;
1106 }
1107 for (std::size_t k = 0; k < sendBuf.size(); ++k) {
1108 reqs.push_back(MPI_REQUEST_NULL);
1109 MPI_Isend(sendBuf[k].data(), static_cast<int>(sendBuf[k].size()), MPI_DOUBLE, sendTo[k],
1110 tagOf(sendTag[k], 1), comm_, &reqs.back());
1111 sBytes_ += static_cast<long>(sendBuf[k].size()) * 8;
1112 ++sMsgs_;
1113 }
1114 if (!reqs.empty())
1115 MPI_Waitall(static_cast<int>(reqs.size()), reqs.data(), MPI_STATUSES_IGNORE);
1116 }
1117#endif
1118 for (auto& r : recvs) {
1119 VofBlock& b = blocks[r.bi];
1120 vofBuildPieces(b.box, gs_, per_, rankBox_, pieces_);
1121 long off = 0;
1122 for (const auto& p : pieces_)
1123 if (p.rank == rank_)
1124 maxBufIntoPatch(p, r.buf, off, hc);
1125 }
1126 Kokkos::deep_copy(cLocal, hc);
1127 }
1128
1129 // -------------------------------------------------------------------------------- table
1130 void syncTable(std::vector<VofBlock>& blocks) override {
1131#ifdef PECLET_FLOW_MPI
1132 if (size_ <= 1)
1133 return;
1134 requireComm();
1135 const int nb = static_cast<int>(blocks.size());
1136 std::vector<int> mine(6 * nb, 0), all(6 * nb, 0);
1137 for (int i = 0; i < nb; ++i)
1138 if (blocks[i].master == rank_)
1139 for (int d = 0; d < 3; ++d) {
1140 mine[6 * i + d] = blocks[i].box.lo[d];
1141 mine[6 * i + 3 + d] = blocks[i].box.hi[d];
1142 }
1143 // exactly one rank contributes each block's row, so a SUM is a broadcast that needs no
1144 // per-block communicator and no ordering assumption.
1145 MPI_Allreduce(mine.data(), all.data(), 6 * nb, MPI_INT, MPI_SUM, comm_);
1146 for (int i = 0; i < nb; ++i)
1147 for (int d = 0; d < 3; ++d) {
1148 blocks[i].box.lo[d] = all[6 * i + d];
1149 blocks[i].box.hi[d] = all[6 * i + 3 + d];
1150 }
1151#else
1152 (void)blocks;
1153#endif
1154 }
1155
1156 private:
1157 using HostView = decltype(Kokkos::create_mirror_view(SField()));
1158
1159 static void zero(HostView v) {
1160 for (std::size_t i = 0; i < v.extent(0); ++i)
1161 v(i) = 0.0;
1162 }
1163 static int tagOf(long id, int phase) { return static_cast<int>((id % 4096) * 4 + phase) + 4096; }
1164
1165#ifdef PECLET_FLOW_MPI
1166 void requireComm() const {
1167 if (!hasComm_)
1168 throw std::runtime_error("peclet::flow::vof::VofBlockExchange: no communicator set");
1169 }
1170#endif
1171
1173 long patchIdx(int gx, int gy, int gz) const {
1174 return L3(gx - patch_.o.x + patch_.g, gy - patch_.o.y + patch_.g, gz - patch_.o.z + patch_.g,
1175 patch_.e);
1176 }
1177
1178 template <class HV>
1179 void packPatch(const VofPiece& p, const HV& hu, const HV& hv, const HV& hw,
1180 std::vector<double>& buf, long& off) const {
1181 for (int z = p.g.lo[2]; z < p.g.hi[2]; ++z)
1182 for (int y = p.g.lo[1]; y < p.g.hi[1]; ++y)
1183 for (int x = p.g.lo[0]; x < p.g.hi[0]; ++x) {
1184 const long i = patchIdx(x, y, z);
1185 buf[3 * off + 0] = hu(i);
1186 buf[3 * off + 1] = hv(i);
1187 buf[3 * off + 2] = hw(i);
1188 ++off;
1189 }
1190 }
1191
1192 template <class HV, class HB>
1193 void copyPatchToBlock(const VofPiece& p, const HV& hu, const HV& hv, const HV& hw, HB bu, HB bv,
1194 HB bw, const VofBlock& b, int ghost) const {
1195 const I3 be = b.advector().extent();
1196 for (int z = p.g.lo[2]; z < p.g.hi[2]; ++z)
1197 for (int y = p.g.lo[1]; y < p.g.hi[1]; ++y)
1198 for (int x = p.g.lo[0]; x < p.g.hi[0]; ++x) {
1199 const long i = patchIdx(x, y, z);
1200 // block-local EXTENDED index: `loc` is the index inside the extended box, whose cell
1201 // (0,0,0) sits at the advector's (0,0,0) — i.e. `ghost` cells before the inner region.
1202 const int lx = p.loc[0] + (x - p.g.lo[0]);
1203 const int ly = p.loc[1] + (y - p.g.lo[1]);
1204 const int lz = p.loc[2] + (z - p.g.lo[2]);
1205 const long j = L3(lx, ly, lz, be);
1206 bu(j) = hu(i);
1207 bv(j) = hv(i);
1208 bw(j) = hw(i);
1209 }
1210 (void)ghost;
1211 }
1212
1213 template <class HB>
1214 void unpackBlock(const VofPiece& p, const std::vector<double>& buf, long& off, HB bu, HB bv,
1215 HB bw, const VofBlock& b, int ghost) const {
1216 const I3 be = b.advector().extent();
1217 for (int z = p.g.lo[2]; z < p.g.hi[2]; ++z)
1218 for (int y = p.g.lo[1]; y < p.g.hi[1]; ++y)
1219 for (int x = p.g.lo[0]; x < p.g.hi[0]; ++x) {
1220 const int lx = p.loc[0] + (x - p.g.lo[0]);
1221 const int ly = p.loc[1] + (y - p.g.lo[1]);
1222 const int lz = p.loc[2] + (z - p.g.lo[2]);
1223 const long j = L3(lx, ly, lz, be);
1224 bu(j) = buf[3 * off + 0];
1225 bv(j) = buf[3 * off + 1];
1226 bw(j) = buf[3 * off + 2];
1227 ++off;
1228 }
1229 (void)ghost;
1230 }
1231
1233 template <class HB>
1234 void packBlockColour(const VofPiece& p, const HB& bc, const VofBlock& b, std::vector<double>& buf,
1235 long& off) const {
1236 const I3 be = b.advector().extent();
1237 const int g = b.advector().ghost();
1238 for (int z = p.g.lo[2]; z < p.g.hi[2]; ++z)
1239 for (int y = p.g.lo[1]; y < p.g.hi[1]; ++y)
1240 for (int x = p.g.lo[0]; x < p.g.hi[0]; ++x) {
1241 const int lx = p.loc[0] + (x - p.g.lo[0]);
1242 const int ly = p.loc[1] + (y - p.g.lo[1]);
1243 const int lz = p.loc[2] + (z - p.g.lo[2]);
1244 buf[off++] = bc(L3(lx + g, ly + g, lz + g, be));
1245 }
1246 }
1247
1248 template <class HB, class HC>
1249 void maxBlockIntoPatch(const VofPiece& p, const HB& bc, const VofBlock& b, HC& hc) const {
1250 const I3 be = b.advector().extent();
1251 const int g = b.advector().ghost();
1252 for (int z = p.g.lo[2]; z < p.g.hi[2]; ++z)
1253 for (int y = p.g.lo[1]; y < p.g.hi[1]; ++y)
1254 for (int x = p.g.lo[0]; x < p.g.hi[0]; ++x) {
1255 const int lx = p.loc[0] + (x - p.g.lo[0]);
1256 const int ly = p.loc[1] + (y - p.g.lo[1]);
1257 const int lz = p.loc[2] + (z - p.g.lo[2]);
1258 const double v = bc(L3(lx + g, ly + g, lz + g, be));
1259 double& d = hc(patchIdx(x, y, z));
1260 if (v > d)
1261 d = v;
1262 }
1263 }
1264
1265 template <class HC>
1266 void maxBufIntoPatch(const VofPiece& p, const std::vector<double>& buf, long& off, HC& hc) const {
1267 for (int z = p.g.lo[2]; z < p.g.hi[2]; ++z)
1268 for (int y = p.g.lo[1]; y < p.g.hi[1]; ++y)
1269 for (int x = p.g.lo[0]; x < p.g.hi[0]; ++x) {
1270 const double v = buf[off++];
1271 double& d = hc(patchIdx(x, y, z));
1272 if (v > d)
1273 d = v;
1274 }
1275 }
1276
1277 I3 gs_{0, 0, 0};
1278 std::array<bool, 3> per_{true, true, true};
1279 std::vector<VofBox> rankBox_;
1280 int rank_ = 0, size_ = 1;
1281 Patch patch_;
1282 SField lu_, lv_, lw_;
1283 mutable std::vector<VofPiece> pieces_;
1284 std::vector<SField> bufPool_;
1285 long bufSeq_ = 0;
1286 long gBytes_ = 0, sBytes_ = 0, gMsgs_ = 0, sMsgs_ = 0, mBytes_ = 0, mMsgs_ = 0;
1287#ifdef PECLET_FLOW_MPI
1288 MPI_Comm comm_ = MPI_COMM_NULL;
1289 bool hasComm_ = false;
1290#endif
1291};
1292
1293} // namespace peclet::flow::vof
1294
1295#endif // PECLET_FLOW_VOF_BLOCK_EXCHANGE_HPP
flow — VoF Part III rung W0: the per-bubble VoF BLOCK, a third container over the same L1 kernels (su...
The gather / scatter / table replication of the block container.
SField buffer(long n)
Reusable device buffer of exactly n doubles, handed out in REQUEST ORDER — two live messages of the s...
void packFromBlock(const VofPiece &p, int nc, SField(*blockView)(VofBlock &, int), VofBlock &b, int blockBase, SField buf, long &off)
static SField faceViewOf(VofBlock &b, int c)
long gatherMessages() const
Messages posted by the last gather / scatter on this rank (sends + receives).
void combineBlockIntoPatch(const VofPiece &p, int nc, SField(*blockView)(VofBlock &, int), VofBlock &b, int blockBase, SField *loc, int op)
static SField colourViewOf(VofBlock &b, int c)
void init(I3 gs, std::array< bool, 3 > per, std::vector< VofBox > rankBox, int rank)
void scatterImpl(std::vector< VofBlock > &blocks, int nc, SField *loc, SField(*blockView)(VofBlock &, int), int blockBase, int op, bool gatherLocal=false)
block -> patch, combined with op (0 = max, 1 = sum).
void zeroPatchInner(int nc, SField *loc)
void unpackToBlock(const VofPiece &p, int nc, SField buf, long &off, SField(*blockView)(VofBlock &, int), VofBlock &b, int blockBase)
void moveLocalPieces(const std::vector< VofMoveJob > &mv)
H-4(e), host: every collected movePiece copy in one launch per chunk of kVofBlockBatch (piece,...
static int ghostOf(const std::vector< VofBlock > &blocks)
void gatherFaceVelHost(std::vector< VofBlock > &blocks, int ghost)
void packFromPatch(const VofPiece &p, int nc, const SField *loc, SField buf, long &off)
void scatterForceSum(std::vector< VofBlock > &blocks, SField fx, SField fy, SField fz) override
SUM every master block's CSF face force into the caller's three face-force patches (TBFsolver's UNPAC...
void setPatch(Patch p, SField uf, SField vf, SField wf)
The local face velocity, in the ADVECTOR's high-face convention (i.e.
static SField forceViewOf(VofBlock &b, int c)
void movePiece(const VofPiece &p, int nc, const SField *loc, SField(*blockView)(VofBlock &, int), VofBlock &b, int blockBase, bool toBlock)
The master's OWN cells: patch <-> block with no buffer and no host traffic.
void gatherImpl(std::vector< VofBlock > &blocks, int ghost, bool useExtended, int nc, const SField *loc, SField(*blockView)(VofBlock &, int), int blockBase, int outside=0)
patch -> block.
void gatherFaceVel(std::vector< VofBlock > &blocks, int ghost) override
Fill every MASTER block's faceU/V/W over its EXTENDED box, from the face velocity the ranks own.
void setRankBoxes(std::vector< VofBox > rankBox)
Re-bind the owned inner box of every rank WITHOUT disturbing the communicator or the local patch.
void scatterColourSum(std::vector< VofBlock > &blocks, SField sLocal) override
S = sum_k C_k (vof_overlap_design §5.6): the colour scatter with UNPACK_SUM.
void migrateColour(std::vector< VofBlock > &blocks, const std::vector< int > &oldMaster) override
Move a block's colour from its old master to its new one after a re-assignment (rung W1 item a).
long gatherBytes() const override
Bytes moved by the last gather / scatter on THIS rank (sent + received).
void scatterColourMax(std::vector< VofBlock > &blocks, SField cLocal) override
UNION the masters' inner colour into the caller's local colour patch: zero the inner region,...
void gatherLocalLaunch(const GatherTable &T, SField *loc)
void gatherColour(std::vector< VofBlock > &blocks, SField cLocal) override
Fill every MASTER block's INNER colour from the caller's colour patch (the mirror of scatterColourMax...
void syncTable(std::vector< VofBlock > &blocks) override
Replicate the (possibly re-centred) boxes so every rank's table agrees again.
void scatterColourMaxHost(std::vector< VofBlock > &blocks, SField cLocal)
void gatherLocalSum(std::vector< VofBlock > &blocks, int nc, SField *loc, SField(*blockView)(VofBlock &, int), int blockBase)
The local half of a gatherLocal UNPACK_SUM: every patch INNER cell written (so no zeroing pass is nee...
void appendMoveJobs(const VofPiece &p, int nc, const SField *loc, SField(*blockView)(VofBlock &, int), VofBlock &b, int blockBase, std::vector< VofMoveJob > &out)
H-4(e): the (piece, component) copies of movePiece for one piece, appended to out.
void combineBufIntoPatch(const VofPiece &p, int nc, SField buf, long &off, SField *loc, int op)
static constexpr int kAuxLen
The non-colour state a master carries between steps: the previous centroid (so a migrated block's rep...
flow — portable (Kokkos) cut-cell pressure-operator face openness from an SDF.
x
Definition cmp.py:24
y
Definition cmp.py:24
bool vofMoveOverlap(const VofMoveJob &a, const VofMoveJob &b)
Debug check of H-4(e)'s premise: two jobs writing the same block array overlap.
constexpr int kVofBlockBatch
Jobs per launch; a container with more master blocks runs each stage in chunks.
int vofAxisRuns(int a, int b, int gs, bool periodic, VofRun out[5], int outside=0)
Decompose [a, b) on one axis into global runs.
void vofBuildPieces(const VofBox &box, I3 gs, const std::array< bool, 3 > &per, const std::vector< VofBox > &rankBox, std::vector< VofPiece > &out, int outside=0)
Build the pieces of one block box against every rank's owned box, in canonical order.
long L3(int x, int y, int z, I3 e)
void ccFor3(const char *name, C3 lo, C3 hi, F f)
Kokkos::DefaultExecutionSpace SExec
Kokkos::View< double *, SMem > SField
flow — the multidimensional iteration policies: iteration order follows storage.
Gather / scatter / table replication.
This rank's own patch: the extended block the face velocity and the union colour live on.
I3 o
global index of inner cell (0,0,0)
Half-open global cell box [lo, hi).
static VofBox intersect(const VofBox &a, const VofBox &b)
Intersection in global coordinates (may be empty).
One (piece, component) copy of VofBlockExchange::movePiece – patch cells [s, s + n) to block cells [q...
I3 q
the piece's first cell in the patch and in the block array
I3 be
the patch's and the block array's extents
The job table of one move launch, by value.
VofMoveJob job[kVofBlockBatch]
One contiguous transfer: the global cells g of a block that rank r owns, and where they sit in the bl...
int loc[3]
block-local index (0-based within the block's box) of g.lo
VofBox g
the overlap, in global cell indices
One contiguous run of GLOBAL indices covered by a block's index range on one axis,...
static std::vector< double > colour(int m)