flow 1.3.0
Kokkos cut-cell IBM incompressible Navier-Stokes solver + pnm pore extraction
Loading...
Searching...
No Matches
flow_ibm_bc.hpp
Go to the documentation of this file.
1
8#ifndef PECLET_FLOW_FLOW_IBM_BC_HPP
9#define PECLET_FLOW_FLOW_IBM_BC_HPP
10
11namespace peclet::flow {
12
13template <class Grid>
14void Solver<Grid>::setDomainBc(int face, int type, double vx, double vy, double vz) {
16 throw std::invalid_argument("set_domain_bc: face must be 0..5 (-x,+x,-y,+y,-z,+z)");
18 throw std::invalid_argument(
19 "set_domain_bc: type must be 0 periodic / 1 wall / 2 inflow / 3 outflow / 4 free-slip");
20 const bool valueUpdateOnly = geometryBuilt_ && type == bc_[face];
21 if (geometryBuilt_ && !valueUpdateOnly)
22 throw std::runtime_error(
23 "set_domain_bc: call BEFORE the geometry (set_solid / set_pressure_geometry / "
24 "set_solid_from_scene) to CHANGE a face's TYPE -- the type is folded into the "
25 "operators when the geometry is built. A VALUE update (vx, vy, vz) on a face whose "
26 "type is unchanged is allowed after the geometry.");
27 // WO-P3g: the "does this cell carry a row" mask depends on which domain faces are periodic.
28 pcInDomain_ = CCField();
29 bc_[face] = type;
30 // Boundary velocities are PHYSICAL; the kernels read the index velocity v_a = u_a*tRef/h_a.
31 bcVelPhys_[face][0] = vx;
32 bcVelPhys_[face][1] = vy;
33 bcVelPhys_[face][2] = vz;
34 for (int a = 0; a < 3; ++a)
35 bcVel_[face][a] = bcVelPhys_[face][a] * u_.velToInt(a);
36 hasBc_ = false;
37 hasOutflow_ = false;
38 for (int i = 0; i < 6; ++i) {
39 if (bc_[i])
40 hasBc_ = true;
41 if (bc_[i] == 3)
42 hasOutflow_ = true;
43 }
44 // ISSUES sweep item 3: which faces are WETTING walls is part of the colour block's geometry,
45 // so a BC changed after `enable_vof` has to rebuild it. Inert (and byte-identical) without a
46 // contact angle: `buildVofGeometry` then takes exactly the branch it took before.
47 if (vofEnabled_)
49 // A VALUE update on an already-built staggered geometry: setupBcDiffusion() bakes bcVel_'s
50 // TANGENTIAL component into the implicit-diffusion fold (bcDcorr_/bcBrhs_) once, at geometry
51 // time -- ghost fills read bcVel_ live every step (the NORMAL component takes effect on its
52 // own), but the fold does not, so a lid-driven cavity ramping its lid's tangential velocity
53 // after set_solid would silently keep stepping on the OLD speed without this refresh. The
54 // collocated grid uses explicit reflection ghosts (no fold) and needs none.
55 if (valueUpdateOnly && hasBc_ && !Grid::collocated)
57}
58
59template <class Grid>
60void Solver<Grid>::setDomainBcProfile(int face, const std::vector<double>& prof, int nb, int nc) {
61 if (geometryBuilt_ && bc_[face] != 2)
62 throw std::runtime_error(
63 "set_domain_bc_profile: call BEFORE the geometry (set_solid / set_pressure_geometry / "
64 "set_solid_from_scene) to make a face inflow -- the type is folded into the operators "
65 "when the geometry is built. Updating the profile on a face that is ALREADY inflow is "
66 "allowed after the geometry.");
67 // The user's (nb, nc, 3) profile is KEPT so the resampling can be redone when the block
68 // changes size — the resampled buffer is indexed by LOCAL face position, so a redistribute
69 // that changes this rank's face grid invalidates it (see resampleBcProfile).
70 bcProfRaw_[face] = prof;
71 bcProfNb_[face] = nb;
72 bcProfRawNc_[face] = nc;
74 bc_[face] = 2;
75 hasBc_ = true; // a profiled face is an inflow
76}
77
78template <class Grid>
80 if (bcProfRaw_[face].empty())
81 return;
82 const std::vector<double>& prof = bcProfRaw_[face];
83 const int nb = bcProfNb_[face], nc = bcProfRawNc_[face];
84 const int a = face / 2;
85 const int dims[3] = {e_.x, e_.y, e_.z};
86 const int bax = (a + 1) % 3, cax = (a + 2) % 3;
87 const int Lb = dims[bax], Lc = dims[cax];
88 CCField pf("bcprof", (std::size_t)Lb * Lc * 3);
89 auto h = Kokkos::create_mirror_view(pf);
90 auto cl = [](int v, int n) { return v < 0 ? 0 : (v >= n ? n - 1 : v); };
91 for (int p0 = 0; p0 < Lb; ++p0)
92 for (int p1 = 0; p1 < Lc; ++p1) {
93 const int ib = cl(p0 - G, nb), ic = cl(p1 - G, nc);
94 // The raw profile is kept in the caller's PHYSICAL velocity; convert per component here,
95 // so a re-resample after a redistribute (or a reference-scale change) reconverts too.
96 for (int k = 0; k < 3; ++k)
97 h(((long)p0 * Lc + p1) * 3 + k) =
98 prof[((std::size_t)ib * nc + ic) * 3 + k] * u_.velToInt(k);
99 }
100 Kokkos::deep_copy(pf, h);
101 bcProf_[face] = pf;
102 bcProfNc_[face] = Lc;
103}
104
105template <class Grid>
107 if (backflowBeta_ <= 0.0 || !hasOutflow_)
108 return;
110 const double beta = backflowBeta_, rho = rho_;
111 C3 e = e_;
112 int dims[3] = {e.x, e.y, e.z};
113 long st[3] = {1, e.x, (long)e.x * e.y};
114 FV AC = C[c].AC;
115 CCConst u = CCConst(C[c].u);
116 const int a = c; // the normal component of a face on axis a is component a
117 for (int s = 0; s < 2; ++s) {
118 if (bc_[2 * a + s] != 3 || !touchesGlobalFace(2 * a + s))
119 continue; // rank-owned outflow faces only
120 const long sa = st[a];
121 const int na = dims[a];
122 const int bic = (s == 0) ? G : (na - G - 1); // outflow-adjacent inner normal-velocity cell
123 const double sgn = (s == 0) ? 1.0 : -1.0; // reversal (u.n<0): u>0 at -a, u<0 at +a
124 const int b = (a + 1) % 3, cc = (a + 2) % 3;
125 const long sb = st[b], sc = st[cc];
126 using MD2 = MDRange2<CCExec>;
127 Kokkos::parallel_for(
128 "peclet::flow::backflow", MD2(space, {G, G}, {dims[b] - G, dims[cc] - G}),
129 KOKKOS_LAMBDA(int p0, int p1) {
130 const long i = (long)p0 * sb + (long)p1 * sc + (long)bic * sa;
131 const double back = sgn * u(i); // > 0 exactly where the outflow reverses (|min(u.n,0)|)
132 if (back > 0.0)
133 AC(i) += (MReal)(beta * rho * back); // dissipative diagonal (u_ext = 0)
134 });
135 }
136}
137
138template <class Grid>
141 C3 e = e_;
142 CCField P = P_;
143 int dims[3] = {e.x, e.y, e.z};
144 long st[3] = {1, e.x, (long)e.x * e.y};
145 for (int a = 0; a < 3; ++a)
146 for (int s = 0; s < 2; ++s) {
147 if (bc_[2 * a + s] == 0 || !touchesGlobalFace(2 * a + s))
148 continue; // rank-owned global face only (interior ghosts come from the halo)
149 const int b = (a + 1) % 3, c = (a + 2) % 3;
150 const long sa = st[a], sb = st[b], sc = st[c];
151 const int na = dims[a];
152 const int bic = (s == 0) ? G : (na - G - 1);
153 const int lo = (s == 0) ? 0 : (na - G), hi = (s == 0) ? (G - 1) : (na - 1);
154 Kokkos::parallel_for(
155 "pbcghost", MDRange2<CCExec>(space, {0, 0}, {dims[b], dims[c]}),
156 KOKKOS_LAMBDA(int p0, int p1) {
157 const long base = (long)p0 * sb + (long)p1 * sc;
158 const double pin = P(base + (long)bic * sa);
159 for (int ia = lo; ia <= hi; ++ia)
160 P(base + (long)ia * sa) = pin;
161 });
162 }
163}
164
165template <class Grid>
166void Solver<Grid>::fillVelGhosts(int comp, int fold) {
167 // This is the fill that RE-IMPOSES the zero-gradient outflow face, i.e. the one that erases
168 // `bcCorrectOutflow`'s correction (WO-R; see fillVelGhostsKeepOutflow). Record that so the
169 // VoF bridge knows whether there is a correction left to preserve.
170 outflowCorrValid_ = false;
171 fillVelGhostsTo(C[comp].u, comp, fold);
172}
173
174template <class Grid>
175void Solver<Grid>::applyVelocityBcComp(int comp, int fold, bool doOutflow) {
176 applyVelocityBcCompTo(C[comp].u, comp, fold, doOutflow);
177}
178
179template <class Grid>
180void Solver<Grid>::fillVelGhostsTo(CCField f, int comp, int fold, bool doOutflow) {
181#ifdef PECLET_FLOW_MPI
182 if (distributed_) {
183 // `doOutflow = false` asks for the mass-conserving outflow face the projection wrote to
184 // SURVIVE this fill (WO-R, gate F2 -- see the header comment on this function). The exchange
185 // below destroys it anyway, because that face is the first GHOST index along its axis and the
186 // decomposition wraps periodically on every axis: it comes back carrying the value from the
187 // OPPOSITE boundary, i.e. the inlet. SCALING_ISSUES #3/#8 -- invisible while the outlet's
188 // openness was ALSO the inlet's (the two wrapped together and stayed self-consistent), and it
189 // broke the VoF composed budget the moment the openness became the outlet's own aperture.
190 // Save the plane and put it back. Only component `comp` matters: the outflow correction lives
191 // on the NORMAL face of its own axis.
192 //
193 // STAGGERED ONLY, and on the collocated grid this is UNREACHABLE rather than merely skipped:
194 // all three callers that pass `doOutflow = false` return or throw before they get here on
195 // `SolverColocated` -- `bridgeVelocityToVof` takes its own face-field branch and returns,
196 // `buildVofCellVelocity` throws ("SolverColocated has no immersed solid"), and
197 // `maxOpenDivergenceProjectedInternal` delegates to `maxOpenDivergenceInternal`. The guard
198 // states that, so a future caller on the collocated path has to come here and decide
199 // deliberately: there, the outflow correction lives on the FACE field (`uf_`, written by the
200 // same `bcCorrectOutflow`) while this fills the CELL field, whose `doOutflow = false` means
201 // "leave the whole ghost BAND alone" -- one restored layer of two would be a third behaviour.
202 // See docs/SCALING_ISSUES.md #8.
203 const int a = comp;
204 const bool keep = !Grid::collocated && !doOutflow && hasOutflow_ && a >= 0 && a < 3 &&
205 bc_[2 * a + 1] == 3 && touchesGlobalFace(2 * a + 1);
206 B3 eb{e_.x, e_.y, e_.z};
207 if (keep) {
208 const int b = (a + 1) % 3, c = (a + 2) % 3;
209 const int d[3] = {e_.x, e_.y, e_.z};
210 const std::size_t np = (std::size_t)(d[b] - 2 * G) * (d[c] - 2 * G);
211 if (outflowPlane_[a].extent(0) != np)
212 outflowPlane_[a] = CCField(
213 Kokkos::view_alloc("peclet::flow::outflow_face_plane", Kokkos::WithoutInitializing),
214 np);
215 bcSaveHighFacePlaneInner(outflowPlane_[a], f, eb, G, a);
216 }
217 velDev_->exchange(f);
218 applyVelocityBcCompTo(f, comp, fold, doOutflow);
219 if (keep)
220 bcRestoreHighFacePlaneInner(f, outflowPlane_[a], eb, G, a);
221 return;
222 }
223#endif
224 fillDomainPeriodic(f); // the periodic axes (§14 H-3(d): one launch when there are two or three)
225 applyVelocityBcCompTo(f, comp, fold, doOutflow);
226}
227
228template <class Grid>
229void Solver<Grid>::applyVelocityBcCompTo(CCField f, int comp, int fold, bool doOutflow) {
230 if (!hasBc_)
231 return;
232 B3 e{e_.x, e_.y, e_.z};
233 if constexpr (Grid::collocated) {
234 // Cell-centered velocity: reflect this component about each non-periodic boundary face. Walls
235 // (type 1, vel 0) and Dirichlet/lid (type 2, prescribed vel) both use the same reflection;
236 // outflow (type 3) and per-position inlet profiles are the inflow/outflow milestone (phase
237 // 5b).
238 for (int a = 0; a < 3; ++a)
239 for (int s = 0; s < 2; ++s) {
240 const int ff = 2 * a + s;
241 const int t = bc_[ff];
242 if (t == 0 || !touchesGlobalFace(ff))
243 continue; // interior rank boundary: the halo exchange owns those ghosts
244 if (t == 3) {
245 if (doOutflow)
246 bcNeumannGhost(f, e, G, a, s);
247 continue;
248 } // outflow: zero-gradient ghost
249 if (t == 4) { // free-slip / symmetry: the normal component is odd-reflected about the
250 // face (face value 0, as a wall), the tangential ones mirrored (zero
251 // normal derivative)
252 if (comp == a)
253 bcVelocityColocated(f, e, G, a, s, 0.0);
254 else
255 bcMirrorGhost(f, e, G, a, s);
256 continue;
257 }
258 if (bcProf_[ff].extent(0) >
259 0) // per-position inlet profile (e.g. the BFS partial parabola)
260 bcVelocityColocated(f, e, G, a, s, 0.0, comp, bcProf_[ff], bcProfNc_[ff]);
261 else
262 bcVelocityColocated(f, e, G, a, s,
263 bcVel_[ff][comp]); // wall / inflow / lid (Dirichlet)
264 }
265 return;
266 }
267 for (int a = 0; a < 3; ++a) {
268 if (bcVelFacePair(a)) { // §14 H-3(e): both faces of this axis in one launch
270 for (int s = 0; s < 2; ++s) {
271 const int ff = 2 * a + s;
272 J.j[J.n++] = BcVelFace{f, bcProf_[ff], bcVel_[ff][comp], comp, s, bcProfNc_[ff]};
273 }
274 bcVelocityFaces(J, e, G, a, fold);
275 continue;
276 }
277 for (int s = 0; s < 2; ++s)
278 applyVelocityBcFace(f, comp, a, s, fold, doOutflow);
279 }
280}
281
282template <class Grid>
284 const int e[3] = {e_.x, e_.y, e_.z};
285 for (int s = 0; s < 2; ++s) {
286 const int t = bc_[2 * a + s];
287 if (t == 0 || t == 3 || t == 4 || !touchesGlobalFace(2 * a + s))
288 return false; // not a bcVelocityComp face on this rank
289 }
290 return e[a] > 2 * G; // the two ghost planes are disjoint
291}
292
293template <class Grid>
294void Solver<Grid>::applyVelocityBcFace(CCField f, int comp, int a, int s, int fold,
295 bool doOutflow) {
296 B3 e{e_.x, e_.y, e_.z};
297 const int ff = 2 * a + s;
298 const int t = bc_[ff];
299 if (t == 0 || !touchesGlobalFace(ff))
300 return; // interior rank boundary: the halo exchange owns those ghosts
301 if (t == 3) {
302 if (doOutflow)
303 bcOutflowComp(f, e, G, a, s, comp, fold);
304 return;
305 }
306 if (t == 4) { // free-slip / symmetry (mac_bc.hpp bcSlipComp)
307 bcSlipComp(f, e, G, a, s, comp, fold);
308 return;
309 }
310 if (bcProf_[ff].extent(0) > 0)
311 bcVelocityComp(f, e, G, a, s, comp, 0.0, fold, bcProf_[ff], bcProfNc_[ff]);
312 else
313 bcVelocityComp(f, e, G, a, s, comp, bcVel_[ff][comp], fold);
314}
315
316template <class Grid>
318 if (Grid::collocated || !hasBc_) {
319 for (int c = 0; c < 3; ++c)
321 return;
322 }
323 // Axis-major instead of component-major: each component's faces keep their order (axis by axis,
324 // s = 0 before s = 1) and the three components are different fields, so the result is identical.
325 B3 e{e_.x, e_.y, e_.z};
326 for (int a = 0; a < 3; ++a) {
327 if (bcVelFacePair(a)) { // §14 H-3(e): 3 components x 2 faces in one launch
329 for (int c = 0; c < 3; ++c)
330 for (int s = 0; s < 2; ++s) {
331 const int ff = 2 * a + s;
332 J.j[J.n++] = BcVelFace{C[c].u, bcProf_[ff], bcVel_[ff][c], c, s, bcProfNc_[ff]};
333 }
334 bcVelocityFaces(J, e, G, a, fold);
335 continue;
336 }
337 for (int c = 0; c < 3; ++c)
338 for (int s = 0; s < 2; ++s)
339 applyVelocityBcFace(C[c].u, c, a, s, fold, doOutflow);
340 }
341}
342
343template <class Grid>
345 B3 e{e_.x, e_.y, e_.z};
346 for (int c = 0; c < 3; ++c) {
347 Kokkos::deep_copy(bcDcorr_[c], 0.0);
348 Kokkos::deep_copy(bcBrhs_[c], 0.0);
349 for (int a = 0; a < 3; ++a) {
350 // The fold uses the beta of the FACE's own axis (doc/anisotropic_metric.md §2/§4.1);
351 // b_a == mu_ exactly on the isotropic path.
352 const double beta = mu_ * u_.w[a];
353 for (int s = 0; s < 2; ++s) {
354 const int t = bc_[2 * a + s];
355 if (!touchesGlobalFace(2 * a + s))
356 continue; // the implicit wall fold belongs to the rank owning that global face
357 double dval, bval;
358 if (t == 3) {
359 dval = -beta;
360 bval = 0.0;
361 } else if (t == 4 && c != a) { // free-slip: zero-gradient tangential (the mirror
362 dval = -beta; // neighbour IS the cell -> the face's beta drops out);
363 bval = 0.0; // the normal component is held at 0 like a wall
364 } else if (t != 0 && c != a) {
365 dval = beta;
366 bval = 2.0 * beta * bcVel_[2 * a + s][c];
367 } else
368 continue; // periodic, or the normal component at a wall (held directly)
369 bcDiffusionFold(bcDcorr_[c], bcBrhs_[c], e, G, a, s, dval, bval);
370 }
371 }
372 // dcorr is passed to the (double) const-coeff smoother diffSmoothColor each sweep -- matching
373 // CUDA diff_k (Ac + dcorr in double), NOT baked into the float stencil.
374 }
375}
376
377} // namespace peclet::flow
378
379#endif // PECLET_FLOW_FLOW_IBM_BC_HPP
void applyVelocityBcAll(int fold, bool doOutflow)
void fillVelGhosts(int comp, int fold)
void fillDomainPeriodic(CCField f)
void setDomainBc(int face, int type, double vx, double vy, double vz)
void applyBackflowStab(int c)
void applyVelocityBcComp(int comp, int fold, bool doOutflow)
void applyVelocityBcFace(CCField f, int comp, int a, int s, int fold, bool doOutflow)
void applyVelocityBcCompTo(CCField f, int comp, int fold, bool doOutflow)
bool bcVelFacePair(int a) const
void resampleBcProfile(int face)
void fillVelGhostsTo(CCField f, int comp, int fold, bool doOutflow=true)
static constexpr int G
Definition flow_ibm.hpp:80
void setDomainBcProfile(int face, const std::vector< double > &prof, int nb, int nc)
Kokkos::View< MReal *, CCMem > FV
Definition flow_ibm.hpp:79
void bcRestoreHighFacePlaneInner(BField dst, BField src, B3 ext, int g, int a)
Definition mac_bc.hpp:418
void bcDiffusionFold(BField dcorr, BField brhs, B3 ext, int g, int a, int s, double dval, double bval)
Definition mac_bc.hpp:296
void bcVelocityColocated(BField f, B3 ext, int g, int a, int s, double wall, int comp=0, BField prof=BField(), int prof_nc=0)
Definition mac_bc.hpp:200
void bcSaveHighFacePlaneInner(BField dst, BField src, B3 ext, int g, int a)
Definition mac_bc.hpp:402
void bcVelocityComp(BField f, B3 ext, int g, int a, int s, int comp, double wall, int fold, BField prof=BField(), int prof_nc=0)
Definition mac_bc.hpp:70
void bcMirrorGhost(BField f, B3 ext, int g, int a, int s)
Definition mac_bc.hpp:169
Kokkos::View< double *, CCMem > CCField
void bcVelocityFaces(const BcVelFaces &J, B3 ext, int g, int a, int fold)
Definition mac_bc.hpp:106
Kokkos::DefaultExecutionSpace CCExec
void bcOutflowComp(BField f, B3 ext, int g, int a, int s, int comp, int fold)
Definition mac_bc.hpp:266
void bcNeumannGhost(BField f, B3 ext, int g, int a, int s)
Definition mac_bc.hpp:246
Kokkos::View< const double *, CCMem > CCConst
void bcSlipComp(BField f, B3 ext, int g, int a, int s, int comp, int fold)
Definition mac_bc.hpp:138
Definition prof.py:1
double w[3]
w_a = 1 / h_a'^2 (<= 1)
Definition flow_ibm.hpp:179
double velToInt(int a) const
Definition flow_ibm.hpp:150
static constexpr double AC