flow 1.3.0
Kokkos cut-cell IBM incompressible Navier-Stokes solver + pnm pore extraction
Loading...
Searching...
No Matches
flow_ibm_mpi.hpp
Go to the documentation of this file.
1
8#ifndef PECLET_FLOW_FLOW_IBM_MPI_HPP
9#define PECLET_FLOW_FLOW_IBM_MPI_HPP
10
11namespace peclet::flow {
12
13#ifdef PECLET_FLOW_MPI
14template <class Grid>
15void Solver<Grid>::initMpi(int gnx, int gny, int gnz, MPI_Comm comm) {
16 int size = 1;
17 MPI_Comm_size(comm, &size);
18 weightedDec_ = false; // the equal-weight partition; rebalanceByWeights sets it
19 // Build the shared decomposition so the pressure MG can derive nested coarse levels
20 // (CutcellMG::coarsened) — aligned ORB by default, or coarse-first when `set_decomposition`
21 // asks for it. Depends only on the global grid and that setting, so it matches mpi_block()'s
22 // sizing exactly when the same values are passed there.
23 initMpi(CutcellMG::decomposition(static_cast<std::size_t>(size), gnx, gny, gnz, decompLevels_,
24 decompMaxImbalance_),
25 comm);
26}
27
28template <class Grid>
29void Solver<Grid>::initMpi(const peclet::core::decomp::BlockDecomposer<3>& dec, MPI_Comm comm) {
30 // The FIRST initMpi must precede the geometry. setSolid builds the pressure MG, the velocity MG,
31 // the SDF ghost ring and the cut-cell overlays for the partition in force when it runs; before
32 // initMpi that is the single-rank one, so each rank's hierarchy treats its own block as a
33 // periodic domain of its own. Nothing below rebuilds it, and the step then runs with a
34 // distributed momentum solve but P independent pressure solves: every PCG converges, and the
35 // velocity leaves each rank carrying the divergence of its block-local mean (measured, one
36 // step: a Gaussian cell force at np = 4, 2x2x1, max|du| 4.0e-2 of max|u| 0.36; a pure-gradient
37 // force at np = 2, max|div u| 7.5e-2 where single-rank gives 2e-15). np = 1 is exact by
38 // accident -- its block IS the domain -- so the order is enforced at every rank count.
39 // redistribute() re-enters here with distributed_ already set and rebuilds the geometry itself.
40 if (geometryBuilt_ && !distributed_)
41 throw std::runtime_error(
42 "init_mpi: call BEFORE the geometry (set_solid / set_pressure_geometry / "
43 "set_solid_from_scene) -- the geometry builds the pressure and velocity multigrids for the "
44 "partition in force when it runs, so a geometry built first gives every rank an "
45 "independent, block-periodic pressure solve (a wrong answer at np > 1, with no error)");
46 pcInDomain_ = CCField(); // WO-P3g: which ghosts carry a row depends on the decomposition
47 distributed_ = true;
48 comm_ = comm;
49 const auto& gs = dec.globalSize();
50 gnx_ = (int)gs[0];
51 gny_ = (int)gs[1];
52 gnz_ = (int)gs[2];
53 int rank = 0;
54 MPI_Comm_rank(comm, &rank);
55 std::array<bool, 3> per{true, true, true};
56 velHalo_ = std::make_shared<GridHaloTopology<3>>();
57 velHalo_->buildTopology(dec, rank, G, per, comm);
58 velDev_ = std::make_shared<GridHalo<double>>();
59 velDev_->setLabel("velocity g2");
60 velDev_->init(*velHalo_);
61 // Communication-avoiding momentum sweeps (see smoothComp): the velocity block is g=2 already,
62 // so the CA pair needs no new topology — only this float exchange for the stencil ring
63 // (operator coefficients are float). Eligible when every rank's block is >= 4 on every axis
64 // (rank-uniform: the decomposition is replicated), gated by `set_comm_avoiding`.
65 velDevF_ = std::make_shared<GridHalo<MReal>>();
66 velDevF_->init(*velHalo_);
67 long minExt = std::numeric_limits<long>::max();
68 for (const auto& s : dec.sizes())
69 for (int k = 0; k < 3; ++k)
70 minExt = std::min(minExt, (long)s[k]);
71 caMomentum_ = (caMode_ & kCaMomentum) && minExt >= 4;
72 for (bool& d : momStencilDirty_)
73 d = true;
74 dec_ = std::make_shared<peclet::core::decomp::BlockDecomposer<3>>(dec); // remember the partition
75 const auto oig = velHalo_->indexer().originInclGhost();
76 og_ = {(int)oig[0] + G, (int)oig[1] + G, (int)oig[2] + G}; // block inner origin -> global parity
77 // The colour field's OWN g=3 topology follows the same partition (VOF_PLAN §3 rule 1). Rebuilt
78 // here rather than only in enableVof() so the order enable_vof/init_mpi does not matter — and
79 // because a redistribute() re-inits through this path. The advector's block carries no state
80 // between steps (C's canonical storage is the G=2 registry field "C"), so reallocating it is
81 // free of consequence.
82 if (vofEnabled_)
84}
85
86template <class Grid>
87void Solver<Grid>::redistribute(const peclet::core::decomp::BlockDecomposer<3>& newDec) {
88 if (!distributed_ || !dec_)
89 return;
90 int rank = 0;
91 MPI_Comm_rank(comm_, &rank);
92 const auto ob = dec_->block(rank), nb = newDec.block(rank);
93 const int oex = (int)ob.size[0] + 2 * G, oey = (int)ob.size[1] + 2 * G,
94 oez = (int)ob.size[2] + 2 * G;
95 const int nex = (int)nb.size[0] + 2 * G, ney = (int)nb.size[1] + 2 * G,
96 nez = (int)nb.size[2] + 2 * G;
97
98 // 1. gather the surviving registered fields to host padded buffers on the OLD block.
99 const auto names = fields_.names();
100 std::vector<std::vector<double>> oldHost(names.size()), newHost(names.size());
101 for (std::size_t k = 0; k < names.size(); ++k) {
102 CCField f = fields_.at(names[k]).data;
103 auto h = Kokkos::create_mirror_view(f);
104 Kokkos::deep_copy(h, f);
105 oldHost[k].assign(h.data(), h.data() + (std::size_t)oex * oey * oez);
106 newHost[k].assign((std::size_t)nex * ney * nez, 0.0);
107 }
108 // 2. redistribute each field OLD -> NEW (host, bit-exact pure data movement).
109 std::vector<const double*> op(names.size());
110 std::vector<double*> np(names.size());
111 for (std::size_t k = 0; k < names.size(); ++k) {
112 op[k] = oldHost[k].data();
113 np[k] = newHost[k].data();
114 }
115 // 2b. The Chebyshev driver's kept power iterates (D3, doc/vof_step_performance_design.md §5.13)
116 // are cross-step state of the pressure MG, held on its g = 1 level-0 block: carried along
117 // in the same exchange (inner cells re-padded to G), handed back after the MG rebuild (5b).
118 // Dropping them would make the next re-estimate cold, and an np = 1 rebalance would stop
119 // being bit-exact. Collective decision: every rank carries them or none does.
120 int warmLocal = mg_.eigenWarmReady() ? 1 : 0, warmAll = 0;
121 MPI_Allreduce(&warmLocal, &warmAll, 1, MPI_INT, MPI_MIN, comm_);
122 std::vector<double> eigOld[2], eigNew[2];
123 if (warmAll) {
124 const CCField src[2] = {mg_.eigenWarmMax(), mg_.eigenWarmMin()};
125 const int o1x = (int)ob.size[0] + 2, o1y = (int)ob.size[1] + 2;
126 for (int v = 0; v < 2; ++v) {
127 auto h = Kokkos::create_mirror_view(src[v]);
128 Kokkos::deep_copy(h, src[v]);
129 eigOld[v].assign((std::size_t)oex * oey * oez, 0.0);
130 eigNew[v].assign((std::size_t)nex * ney * nez, 0.0);
131 for (int z = 0; z < (int)ob.size[2]; ++z)
132 for (int y = 0; y < (int)ob.size[1]; ++y)
133 for (int x = 0; x < (int)ob.size[0]; ++x)
134 eigOld[v][(std::size_t)(x + G) + (std::size_t)(y + G) * oex +
135 (std::size_t)(z + G) * oex * oey] =
136 h((std::size_t)(x + 1) + (std::size_t)(y + 1) * o1x +
137 (std::size_t)(z + 1) * o1x * o1y);
138 op.push_back(eigOld[v].data());
139 np.push_back(eigNew[v].data());
140 }
141 }
142 peclet::core::decomp::redistributeGridFields<double>(*dec_, newDec, rank, G, op, np, comm_);
143
144 // 3. reallocate every buffer to the new block; re-init the halo + MG on the new partition.
145 // `resizeForBlock` must run BETWEEN the two: `allocateBlock` only re-creates the buffers it
146 // owns by name, and `initMpi` -> `buildVofBlock` already writes through `cField_`, which is
147 // a registry alias. Without it the scatter below memcpy's the new padded extent into the
148 // old allocation (ASan heap-buffer-overflow -> `free(): invalid pointer`).
149 allocateBlock((int)nb.size[0], (int)nb.size[1], (int)nb.size[2]);
150 resizeForBlock();
151 initMpi(newDec, comm_);
152 // scatter a padded host buffer into a registered field's device buffer.
153 auto scatterPadded = [&](const std::string& name, const std::vector<double>& src) {
154 CCField f = fields_.at(name).data;
155 auto h = Kokkos::create_mirror_view(f);
156 std::memcpy(h.data(), src.data(), sizeof(double) * (std::size_t)nex * ney * nez);
157 Kokkos::deep_copy(f, h);
158 };
159 // 4. scatter all migrated fields into the fresh (new-block) buffers.
160 for (std::size_t k = 0; k < names.size(); ++k)
161 scatterPadded(names[k], newHost[k]);
162 // 5. rebuild geometry-derived state (openness/IBM/stencils/MG) from the migrated SDF. setSolid
163 // zeroes the velocity + pressure (it is the initial-geometry setup), so re-instate every
164 // non-SDF field afterward from the migrated data.
165 {
166 // A repartition is not an operator change: the balanced-force pre-projection keeps its
167 // Chebyshev bounds across it, as the main solve keeps its own (setSolid's MG rebuild would
168 // otherwise force a re-estimate, and an np = 1 rebalance would stop being bit-exact).
169 const bool keepBfpBounds = bfpChebSet_;
170 setSolid(gatherInner(sdf_), cutcellPressure_);
171 bfpChebSet_ = keepBfpBounds;
172 }
173 for (std::size_t k = 0; k < names.size(); ++k)
174 if (names[k] != "sdf")
175 scatterPadded(names[k], newHost[k]);
176 // 5b. hand the migrated Chebyshev power iterates back to the rebuilt MG (see 2b): inner cells
177 // into a fresh g = 1 level-0 vector (ghosts zero; no consumer reads them before a refill).
178 if (warmAll) {
179 const int n1x = (int)nb.size[0] + 2, n1y = (int)nb.size[1] + 2, n1z = (int)nb.size[2] + 2;
180 CCField dst[2];
181 for (int v = 0; v < 2; ++v) {
182 dst[v] = CCField(v == 0 ? "ev_vmax" : "ev_vmin", (std::size_t)n1x * n1y * n1z);
183 auto h = Kokkos::create_mirror_view(dst[v]);
184 Kokkos::deep_copy(h, 0.0);
185 for (int z = 0; z < (int)nb.size[2]; ++z)
186 for (int y = 0; y < (int)nb.size[1]; ++y)
187 for (int x = 0; x < (int)nb.size[0]; ++x)
188 h((std::size_t)(x + 1) + (std::size_t)(y + 1) * n1x +
189 (std::size_t)(z + 1) * n1x * n1y) =
190 eigNew[v][(std::size_t)(x + G) + (std::size_t)(y + G) * nex +
191 (std::size_t)(z + G) * nex * ney];
192 Kokkos::deep_copy(dst[v], h);
193 }
194 mg_.setEigenWarm(dst[0], dst[1]);
195 }
196 // 6. REFILL THE GHOSTS. `redistributeGridFields` moves the INNER cells only and says so
197 // ("the caller refills ghosts with a halo exchange afterwards"); the padded destination
198 // buffers were zero-initialised in step 1, so without this every migrated field enters the
199 // next step with a zero halo on the new partition. Measured at np = 4 on the VoF gate: one
200 // step after the move, du = 1.01e-01 against the single-rank reference (a run started
201 // directly on the SAME weighted partition reproduces it to 6.5e-17), and 0.00e+00 with the
202 // refill. np = 1/2 hid it because the new neighbour set happened to reproduce the old one.
203 for (std::size_t k = 0; k < names.size(); ++k)
204 fillGhosts(fields_.at(names[k]).data);
205 for (auto& sc : scalars_)
206 applyScalarBc(sc); // a scalar's own domain BCs override the halo/periodic base
207 // 6b. RE-SEED eps^n. It is STATE, not scratch: a re-partition is not a time step, so the
208 // porosity must not appear to have jumped across it -- eps^n := the migrated eps^{n+1}, i.e.
209 // d(eps)/dt = 0 over the redistribute, the only choice that leaves the projection RHS
210 // unchanged. It must happen HERE, after step 4 scattered the migrated eps into `epsField_`:
211 // it used to happen in `resizeForBlock` (step 3), where `epsField_` is the freshly allocated
212 // ZERO buffer whenever the block changes size, so eps^n = 0 and the first porous projection
213 // after a rebalance saw d(eps)/dt = eps/dt. Measured on coupling's test_mpi_rebalance (a
214 // 32^3 heap, np = 2 and 4): pressure off by 2.2e+02 and velocity by 2.4 (|u| <= 0.07) one
215 // step after the move; a rebalance that keeps every block's size hid it (no reallocation).
216 if (epsPrev_.extent(0) == n_ && epsField_.extent(0) == n_)
217 Kokkos::deep_copy(epsPrev_, epsField_);
218 // 7. RE-SEED THE COLLOCATED FACE FIELD. `uf_/vf_/wf_` are block scratch, not registry fields:
219 // step 3's `allocateBlock` handed us fresh ZERO buffers and nothing above migrates them, so
220 // the projected face field of the old partition is simply gone. Everything that rides it --
221 // momentum advection (ufAdvVelocity()), the colour transport, the scalars -- would read
222 // zeros on the step after a rebalance, and `advect_vof`'s divergence guard would wave them
223 // through (a zero field is perfectly solenoidal). Seeding from the migrated cell velocity
224 // with the same `centerToFace` map `project()` uses restores a consistent (not yet
225 // divergence-free) field, exactly as `set_state` does; the next `project()` makes it
226 // solenoidal again. A no-op on the staggered grid.
228}
229
230template <class Grid>
231int Solver<Grid>::rebalanceByWeights(const std::vector<peclet::core::Real>& w) {
232 if (!distributed_)
233 return 1;
234 int size = 1, rank = 0;
235 MPI_Comm_size(comm_, &size);
236 MPI_Comm_rank(comm_, &rank);
237 // The ALIGNED weighted ORB (amr/docs/amr_mg_core_boundary.md §6, §9.2, §11.4): split planes on
238 // multiples of 2^a, a the largest alignment whose weight imbalance stays within the 1.05 budget
239 // (a = 0: the plain weighted ORB, bit for bit). Every split is then even for a lifts, so the
240 // pressure MG coarsens in place for a levels before any telescope stage can fire, and whatever
241 // stage fires does so on a level 8^a smaller. A pure function of (ranks, grid, weights): every
242 // rank computes the same partition, and so must any code that co-decomposes from `w` -- which is
243 // why the chosen alignment is RETURNED: dem rebuilds this partition as the aligned weighted ORB
244 // `init(size, G, w, {2^a,...})`, and at a = 0 as the plain weighted ORB (core guarantees the
245 // two coincide bit for bit at align 1).
246 const auto c = peclet::core::decomp::chooseAlignedWeighted(
247 (std::size_t)size, peclet::core::IVec<3>{gnx_, gny_, gnz_}, w);
248 if (mgDebugLevel() && rank == 0) {
249 std::printf(
250 "[mg] rebalanceByWeights: aligned weighted ORB a = %d (align %d), weight imbalance "
251 "%.4f\n",
252 c.a, 1 << c.a, (double)c.imbalance);
253 std::fflush(stdout);
254 }
255 weightedDec_ = true; // before redistribute: its setSolid rebuilds the pressure MG
256 redistribute(c.dec);
257 return 1 << c.a;
258}
259
260template <class Grid>
261std::vector<CutcellMG::PlanRow> Solver<Grid>::pressureMgPlan() const {
262 return mg_.builtPlan();
263}
264#endif
265
266template <class Grid>
267void Solver<Grid>::resizeForBlock() {
268 reallocOwnedFields();
269 rebindFieldAliases();
270 resizeBlockScratch();
271 for (int f = 0; f < 6; ++f)
272 resampleBcProfile(f); // the resampled inlet profile is indexed by LOCAL face position
273}
274
275template <class Grid>
276void Solver<Grid>::reallocOwnedFields() {
277 for (const auto& nm : fields_.names()) {
278 auto& rec = fields_.at(nm);
279 if (!rec.ownStorage || rec.data.extent(0) == n_)
280 continue;
281 rec.data = CCField(nm, n_);
282 }
283}
284
285template <class Grid>
286void Solver<Grid>::rebindFieldAliases() {
287 auto bind = [this](CCField& f, const std::string& nm) {
288 if (fields_.has(nm))
289 f = fields_.at(nm).data;
290 };
291 bind(cField_, "C"); // VoF: the canonical G=2 colour mirror
292 bind(kappaField_, "kappa"); // VoF: the curvature mirrors
293 bind(kappaBranch_, "kappa_branch");
294 bind(rhoField_, "rho"); // property closures
295 bind(Pb_, "p_balanced"); // balanced-force projection split (doc/collocated_varrho_forces.md)
296 bind(muField_, "mu");
297 bind(epsField_, "eps"); // CFD-DEM: porosity + drag
298 bind(dragBeta_, "drag_beta");
299 bind(pcMdot_, "mdot"); // phase change
300 bind(pcSrc_, "pc_source");
301 bind(pcUser_, "div_source");
302 bind(pcSink_, "div_sink");
303 static const char* fn[3] = {"force_x", "force_y", "force_z"};
304 for (int c = 0; c < 3; ++c)
305 bind(cellForce_[c], fn[c]);
306 for (auto& sc : scalars_) {
307 bind(sc.c, sc.name);
308 if (sc.cut && sc.cut->solid.extent(0) > 0) // WO-7: a conjugate scalar's solid field
309 bind(sc.cut->solid, sc.name + "_solid");
310 }
311 for (auto& cl : closures_) { // property_closures.hpp keeps the registry keys for exactly this
312 if (fields_.has(cl.outName))
313 cl.out = fields_.at(cl.outName).data;
314 if (fields_.has(cl.in0Name))
315 cl.in0 = CCConst(fields_.at(cl.in0Name).data);
316 if (!cl.in1Name.empty() && fields_.has(cl.in1Name))
317 cl.in1 = CCConst(fields_.at(cl.in1Name).data);
318 }
319}
320
321template <class Grid>
322void Solver<Grid>::resizeIfAllocated(CCField& f, const char* label, std::size_t n) {
323 if (f.extent(0) != 0 && f.extent(0) != n)
324 f = CCField(label, n);
325}
326
327template <class Grid>
328void Solver<Grid>::resizeBlockScratch() {
329 const std::size_t n = n_, n1 = n1_;
330 // --- velocity / pressure scratch --------------------------------------------------------
331 resizeIfAllocated(velRes_, "velRes", n);
332 resizeIfAllocated(zp1_, "zp1", n1);
333 resizeIfAllocated(vmgTheta_, "vmgTheta", n);
334 resizeIfAllocated(vmgClean_, "vmgClean", n);
335 for (int c = 0; c < 3; ++c) {
336 resizeIfAllocated(uStar_[c], "uStar", n);
337 resizeIfAllocated(advRhs_[c], "advRhs", n);
338 resizeIfAllocated(uBc_[c], "uBc", n);
339 resizeIfAllocated(uwCell_[c], "uwCell", n);
340 resizeIfAllocated(uwAdv_[c], "uwAdv", n);
341 resizeIfAllocated(faceAcc_[c], "faceAcc", n);
342 }
343 resizeIfAllocated(pb1_, "pb1",
344 n1); // balanced-force projection scratch (P_b itself is registered)
345 haveUStar_ = false; // u* belonged to the old block's last momentum solve
346 haveAdvRhs_ = false;
347 // --- ghost projection (its overlay is rebuilt by setSolid) ------------------------------
348 resizeIfAllocated(oxb_, "oxb", n);
349 resizeIfAllocated(oyb_, "oyb", n);
350 resizeIfAllocated(ozb_, "ozb", n);
351 resizeIfAllocated(sdfGp_, "peclet::flow::sdfGp", n);
352 resizeIfAllocated(gpRh_, "gpRh", n1);
353 resizeIfAllocated(gpT_, "gpT", n1);
354 resizeIfAllocated(gpZ2_, "gpZ2", n1);
355 resizeIfAllocated(gpX2_, "gpXg2", n);
356 // --- VoF (the g=3 block itself is rebuilt by buildVofBlock) -----------------------------
357 resizeIfAllocated(vofCs_, "vofCs", n);
358 resizeIfAllocated(vofSolidG2_, "vofSolidG2", n);
359 for (int c = 0; c < 3; ++c) {
360 resizeIfAllocated(uAdv_[c], "uAdv", n);
361 resizeIfAllocated(vofDynVel_[c], "vofDynVel", n);
362 resizeIfAllocated(csfBlkF_[c], "vof::blockcsf", n);
363 }
364 // --- phase change -----------------------------------------------------------------------
365 resizeIfAllocated(pcArea_, "pc_area", n);
366 for (int c = 0; c < 3; ++c) {
367 resizeIfAllocated(pcNrm_[c], "pc_nrm", n);
368 resizeIfAllocated(pcGn_[c], "pc_gn", n);
369 }
370 resizeIfAllocated(pcDep_, "pc_dep", n);
371 resizeIfAllocated(pcTgt_, "pc_tgt", n);
372 resizeIfAllocated(pcCnew_, "pc_cnew", n);
373 resizeIfAllocated(pcDefic_, "pc_defic", n);
374 resizeIfAllocated(pcKcell_, "pc_k", n);
375 resizeIfAllocated(pcRcp_, "pc_rcp", n);
376 resizeIfAllocated(pcClsPrev_, "pc_cls_prev", n);
377 resizeIfAllocated(pcCarrySrc_, "pc_carry_src", n);
378 resizeIfAllocated(pcMdotFit_, "pc_mdot_fit", n);
379 resizeIfAllocated(pcKappa_, "pc_kappa", n);
380 resizeIfAllocated(pcAreaCg2_, "pc_area_cascade", n);
381 resizeIfAllocated(pcTgam_, "pc_tgam", n);
382 resizeIfAllocated(pcGphi_, "pc_gphi", n);
383 pcInDomain_ = CCField(); // which ghosts carry a row depends on the decomposition (WO-P3g)
384 pcMaskFresh_ = false;
385 // --- transported scalars (their operator is rebuilt; the solution rides the registry) -----
386 for (auto& sc : scalars_) {
387 resizeIfAllocated(sc.cOld, "scalar_old", n);
388 resizeIfAllocated(sc.b, "scalar_b", n);
389 resizeIfAllocated(sc.AC, "scalar_AC", n);
390 resizeIfAllocated(sc.AW, "scalar_AW", n);
391 resizeIfAllocated(sc.AE, "scalar_AE", n);
392 resizeIfAllocated(sc.AS, "scalar_AS", n);
393 resizeIfAllocated(sc.AN, "scalar_AN", n);
394 resizeIfAllocated(sc.AB, "scalar_AB", n);
395 resizeIfAllocated(sc.AT, "scalar_AT", n);
396 resizeIfAllocated(sc.dmask, "scalar_dmask", n);
397 resizeIfAllocated(sc.dval, "scalar_dval", n);
398 resizeIfAllocated(sc.gfmB, "scalar_gfmb", n);
399 sc.stencilBuilt = false; // the operator is a property of the block
400 }
401 // `ScalarField::kcell`/`rcp` are NOT the scalar's own storage: they ALIAS `pcKcell_`/`pcRcp_`
402 // (set at `set_phase_change_energy`). Resizing them independently would give the energy
403 // operator a different buffer from the one `pcUpdateEnergyProps` fills every step — measured
404 // as dP 3.09e-04 against the never-rebalanced control at np = 4. Re-alias instead.
405 if (pcEnergy_ && !pcTName_.empty() && hasScalar(pcTName_)) {
406 ScalarField& sc = scalarField(pcTName_);
407 sc.kcell = pcKcell_;
408 sc.rcp = pcRcp_;
409 }
410 // --- variable density / porous (CFD-DEM) -------------------------------------------------
411 resizeIfAllocated(rho1_, "rho1", n1);
412 resizeIfAllocated(cx1_, "cx1", n1);
413 resizeIfAllocated(cy1_, "cy1", n1);
414 resizeIfAllocated(cz1_, "cz1", n1);
415 resizeIfAllocated(eps1_, "eps1", n1);
416 resizeIfAllocated(beta1_, "beta1", n1);
417 resizeIfAllocated(depsdt_, "depsdt", n);
418 resizeIfAllocated(divAdv_, "divAdv", n);
419 resizeIfAllocated(epsRho_, "epsRho", n);
420 resizeIfAllocated(epsPrev_, "epsPrev", n); // eps^n is STATE: `redistribute` re-seeds it (6b)
421 for (int c = 0; c < 3; ++c)
422 for (int k = 0; k < 3; ++k)
423 if (tEx_[c][k].extent(0) != 0 && tEx_[c][k].extent(0) != (std::size_t)nx_ * ny_ * nz_) {
424 tEx_[c][k] = CCField(); // exact crossings are single-rank only; drop the stale set
425 hasExactCross_ = false;
426 }
427 // `cutOwner_` is deliberately NOT resized: every consumer guards on
428 // `extent(0) == nx_*ny_*nz_` and skips, so a stale-sized owner map is inert, while a
429 // freshly-zeroed one would silently name instance 0 as the owner of every cut cell.
430}
431
432} // namespace peclet::flow
433
434#endif // PECLET_FLOW_FLOW_IBM_MPI_HPP
void setEigenWarm(CCField vmax, CCField vmin)
void setSolid(const std::vector< double > &sdfInner, bool cutcellPressure)
Host entry point: upload the inner SDF once and delegate.
ScalarField & scalarField(const std::string &name)
void fillGhosts(CCField f)
Solver(int nx, int ny, int nz)
void resampleBcProfile(int face)
static constexpr int G
Definition flow_ibm.hpp:80
void allocateBlock(int nx, int ny, int nz)
bool hasScalar(const std::string &name) const
std::vector< double > gatherInner(CCField fld)
x
Definition cmp.py:24
y
Definition cmp.py:24
Kokkos::View< double *, CCMem > CCField
Kokkos::View< const double *, CCMem > CCConst