9#ifndef PECLET_FLOW_FLOW_IBM_VOF_HPP
10#define PECLET_FLOW_FLOW_IBM_VOF_HPP
20 "enable_vof_momentum and enable_vof_blocks* are not composable: momentum-consistent transport "
21 "advects rho*u with the geometric fluxes of the ONE structured colour field (at the head of "
22 "step()) and skips the colour stage that advects the block markers, so the markers would never "
23 "move and the momentum fluxes would not be theirs. Use the structured colour field with "
24 "enable_vof_momentum, or the block container without it.";
32 vofBlocks_->timingOn =
on;
47 vofBlocks_->debrisSeconds = 0.0;
48 vofBlocks_->debrisCalls = 0;
50 tStepSum_ = tPredSum_ = tMomSum_ = tProjSum_ = 0.0;
117#ifdef PECLET_FLOW_MPI
118 if (distributed_ &&
dec_) {
124 std::array<bool, 3> per{
true,
true,
true};
125 vofHalo_ = std::make_shared<GridHaloTopology<3>>();
127 vofDev_ = std::make_shared<GridHalo<double>>();
146 if (vofMomEnabled_) {
147 vofMom_.
init(vofAdv_, vofRhoG_, vofRhoL_);
148 for (
int c = 0; c < 3; ++c)
149 if (uAdv_[c].extent(0) != n_)
150 uAdv_[c] =
CCField(
"uAdv", n_);
156 const std::size_t
blockLen = (std::size_t)e3_.
x * e3_.
y * e3_.
z;
162 vofEnergy_.
init(vofAdv_, pcRcpG_, pcRcpL_);
168 if (!vofEnabled_ || !contactAngleSet_)
171 for (
int f = 0; f < 6; ++f)
172 if (bc_[f] == 1 || bc_[f] == 4)
181 const int q[3] = {gs.x, gs.y, gs.z};
183 for (
int a = 0; a < 3; ++a) {
184 const double c = (
double)
gi[a] + 0.5;
185 if (mask & (1 << (2 * a)))
186 d = Kokkos::fmin(d, c);
187 if (mask & (1 << (2 * a + 1)))
188 d = Kokkos::fmin(d, (
double)q[a] - c);
198 if ((!hasSolid_ || !cutcellPressure_) && !
wet) {
204 if (
wet && !cutcellPressure_)
205 throw std::runtime_error(
206 "set_contact_angle: a wetting DOMAIN wall (bc type 1/4) needs a cut-cell pressure "
207 "operator, because the colour transport weights its geometric fluxes with the face "
208 "openness. Build one with set_pressure_geometry(all_fluid_sdf) (all-fluid) or "
209 "set_solid(sdf, cutcell_pressure=True).");
211 if (hasSolid_ && cutcellPressure_) {
212 if (vofCs_.extent(0) != n_)
219 for (
int d = 0; d < 3; ++d) {
221 I3{e_.x, e_.y, e_.z},
G, d);
228 for (
int d = 0; d < 3; ++d)
247 if (vofSolidG2_.extent(0) != n_)
248 vofSolidG2_ =
CCField(
"vofSolidG2", n_);
258 const int g =
kVofG, mk = mask;
262 Kokkos::parallel_for(
265 const long i = (
long)x + (
long)y * e.x + (
long)z * (
long)e.x * e.y;
266 const int gx = x - g + org.x,
gy = y - g + org.y,
gz = z - g + org.z;
267 auto inside = [&](
int ax,
int ay,
int az) {
268 const int gi[3] = {ax, ay,
az};
269 const int q[3] = {gs.x, gs.y, gs.z};
270 for (
int a = 0; a < 3; ++a) {
271 if ((mk & (1 << (2 * a))) &&
gi[a] < 0)
273 if ((mk & (1 << (2 * a + 1))) &&
gi[a] >= q[a])
296 const int g =
kVofG, mk = mask;
298 Kokkos::parallel_for(
301 const long i = (
long)x + (
long)y * e.x + (
long)z * (
long)e.x * e.y;
302 const int gi[3] = {x - g + org.x, y - g + org.y, z - g + org.z};
303 const int q[3] = {gs.x, gs.y, gs.z};
304 for (
int a = 0; a < 3; ++a) {
305 if (((mk & (1 << (2 * a))) &&
gi[a] < 0) ||
306 ((mk & (1 << (2 * a + 1))) &&
gi[a] >= q[a])) {
320 for (
int a = 0; a < 3; ++a)
321 for (
int sd = 0;
sd < 2; ++
sd) {
322 if (!(mask & (1 << (2 * a +
sd))))
324 const int q = (a == 0) ? gs.x : (a == 1) ? gs.y : gs.z;
325 const int o = (a == 0) ? org.x : (a == 1) ? org.y : org.z;
327 const int i3 = (
sd == 0) ? (-3 - o + g) : (q - o + g + 2);
328 const int i2 = (
sd == 0) ? (-2 - o + g) : (q - o + g + 1);
329 const int dims[3] = {e.x, e.y, e.z};
332 const long st[3] = {1, (
long)e.x, (
long)e.x * (
long)e.y};
333 const int b = (a + 1) % 3, c = (a + 2) % 3;
334 const long sa = st[a],
sb = st[b], sc = st[c];
336 Kokkos::parallel_for(
337 "peclet::flow::vof_extend_wall_band",
339 const long base = (
long)p0 *
sb + (
long)
p1 * sc;
348 if (!vofEnabled_ || !vofAdv_.
hasGeometry() || !vofSolidG2_.extent(0) || !vofSolidZero_)
355 const int ex = e_.
x, ey = e_.
y, g =
G;
356 Kokkos::parallel_for(
359 const long i = (
long)(x + g) + (
long)(y + g) * ex + (
long)(z + g) * (
long)ex * ey;
368 return bc_[2 * a] == 0 && bc_[2 * a + 1] == 0;
389 for (
int d = 0; d < 3; ++d)
400 if (!(vofDyn_.
sigma > 0.0) && vofDyn_.
dynamic && !contactSigmaWarned_) {
401 contactSigmaWarned_ =
true;
403 "peclet.flow: set_contact_angle_dynamic is active but sigma is 0 "
404 "(no set_surface_tension and no explicit sigma) - Ca_cl is 0 and the "
405 "Cox-Voinov correction is inert.\n");
423#ifdef PECLET_FLOW_MPI
437 vof::clampFill(f,
I3{e3_.
x, e3_.
y, e3_.
z},
kVofG, org, gs, px, py, pz,
skip);
443#ifdef PECLET_FLOW_MPI
447 return I3{nx_, ny_, nz_};
452 return I3{og_.
x, og_.
y, og_.
z};
462 if constexpr (Grid::collocated) {
464 for (
int c = 0; c < 3; ++c) {
467 I3{e_.x, e_.y, e_.z},
G, c);
471 for (
int c = 0; c < 3; ++c) {
479 if (outflowCorrValid_)
487 I3{e_.x, e_.y, e_.z},
G, c);
493 std::vector<vof::VofBox> rb(
static_cast<std::size_t
>(size));
495#ifdef PECLET_FLOW_MPI
496 if (distributed_ &&
dec_) {
497 for (
int r = 0; r < size; ++r) {
498 const auto b =
dec_->block(
static_cast<std::size_t
>(r));
499 for (
int d = 0; d < 3; ++d) {
500 rb[r].lo[d] =
static_cast<int>(b.origin[d]);
501 rb[r].hi[d] =
static_cast<int>(b.origin[d] + b.size[d]);
520#ifdef PECLET_FLOW_MPI
526 pp.e =
I3{e3_.
x, e3_.
y, e3_.
z};
527 pp.n =
I3{nx_, ny_, nz_};
535 if (vofOverlapDensity_ && vofBlkS_.extent(0)) {
537 const long len3 =
static_cast<long>(e3_.
x) * e3_.
y * e3_.
z;
538 if (
static_cast<long>(vofBlkCeff_.extent(0)) !=
len3)
539 vofBlkCeff_ =
SField(
"vof::blockcsf::ceff",
len3);
540 const I3 e =
I3{e3_.
x, e3_.
y, e3_.
z};
543 Kokkos::parallel_for(
546 const long i =
L3(x, y, z, e);
547 const double sv =
sm(i);
548 ce(i) = (sv <= 1.0 + 1e-8) ?
un(i) : Kokkos::fmax(0.0, 2.0 - sv);
561 if (!vofBlocks_ || !(vofBlocks_->csfEnabled || vofOverlapDensity_))
563 const long len3 =
static_cast<long>(e3_.
x) * e3_.
y * e3_.
z;
564 if (
static_cast<long>(vofBlkS_.extent(0)) !=
len3)
565 vofBlkS_ =
SField(
"vof::blockcsf::sum",
len3);
566 vofBlocks_->scatterSum(vofBlkS_);
567 const I3 e =
I3{e3_.
x, e3_.
y, e3_.
z};
570 double mx = 0.0, ex = 0.0;
572 Kokkos::parallel_reduce(
573 "vof::block::overlap_census",
575 KOKKOS_LAMBDA(
int x,
int y,
int z,
double& m,
double& a,
long& c) {
576 const double sv =
sm(
L3(x, y, z, e));
577 m = Kokkos::fmax(m, sv);
578 if (sv > 1.0 + 1e-8) {
583 Kokkos::Max<double>(mx), ex, nc);
585#ifdef PECLET_FLOW_MPI
587 double gm = 0.0,
ge = 0.0;
599 vofOverlap_.
cells = nc;
607 vofOverlap_.
active = vofPhantomBound_ && vofBlocks_->csfEnabled && !vofOverlapDensity_ &&
619 vofBlockDebris_ =
enabled ? 1 : 0;
620 if (vofBlocks_ && vofBlocks_->csfEnabled)
621 vofBlocks_->debrisRemove =
enabled;
627 throw std::runtime_error(
"set_vof_block_overlap_density: call enable_vof_blocks first");
651 const double sig = sigmaCsf_, h = 1.0 / u_.
w[c];
653 Kokkos::parallel_for(
654 "csf_rhs",
MD(
space, {
G,
G,
G}, {e.x -
G + hi, e.y -
G + hi, e.z -
G + hi}),
656 const long i = (
long)x + (
long)y * e.x + (
long)z * (
long)e.x * e.y;
678 const double sig = sigmaCsf_, h = 1.0 / u_.
w[c];
680 Kokkos::parallel_for(
681 "csf_rhs_cellinterp",
MD(
space, {
G,
G,
G}, {e.x -
G + hi, e.y -
G + hi, e.z -
G + hi}),
683 const long i = (
long)x + (
long)y * e.x + (
long)z * (
long)e.x * e.y;
684 double f[2] = {0.0, 0.0};
685 for (
int q = 0; q < 2; ++q) {
686 const long j = i - (1 - q) *
strd;
689 f[q] = sig * kp(j) * 0.5 * (
cv(j +
strd) -
cv(j -
strd)) / h;
692 bb(i) +=
rs(i) * 0.5 * (f[0] + f[1]);
694 bb(i) += 0.5 * (f[0] + f[1]);
705 Kokkos::parallel_for(
706 "csf_rhs_blocks",
MD(
space, {
G,
G,
G}, {e.x -
G, e.y -
G, e.z -
G}),
708 const long i = (
long)x + (
long)y * e.x + (
long)z * (
long)e.x * e.y;
715 if (!vofEnabled_ || !kappaField_.extent(0))
716 throw std::runtime_error(
717 "csf_diagnostics: needs VoF + a curvature field (call set_surface_tension and step, or "
718 "compute_vof_curvature)");
722 const double sig = sigmaCsf_, eps = csfInterfaceEps_;
724 for (
int c = 0; c < 3; ++c) {
725 const double h = 1.0 / u_.
w[c];
728 long orph = 0,
forced = 0;
729 Kokkos::parallel_reduce(
731 KOKKOS_LAMBDA(
int x,
int y,
int z,
double& m,
long& o,
long& f) {
732 const long i = (
long)x + (
long)y * e.x + (
long)z * (
long)e.x * e.y;
740 else if (Kokkos::fabs(
dC) > eps)
744 Kokkos::Max<double>(mx), orph,
forced);
746 d.orphanFaces[c] = orph;
747 d.forcedFaces[c] =
forced;
764 if constexpr (Grid::collocated) {
770 throw std::runtime_error(
771 "enable_vof: geometric VoF on SolverColocated (rung V8) is ALL-FLUID only — immersed "
772 "solids on this path are the next package (doc/collocated_multiphase_solids_plan.md). "
773 "Use the staggered Solver (rung V5a supports cut cells).");
799 if (!(eps >= 0.0) || eps >= 0.5)
800 throw std::runtime_error(
"set_vof_wisp_eps: the threshold must be in [0, 0.5)");
804 vofBlocks_->setWispEps(eps);
828 pcMaskFresh_ =
false;
839 throw std::runtime_error(
"vof_diagnostics: VoF is not enabled (call enable_vof/set_vof)");
851 if (!vofEnabled_ || !vofAdv_.
hasGeometry() || !vofSolidG2_.extent(0))
854 const int ex = e_.
x, ey = e_.
y, g =
G;
856 Kokkos::parallel_reduce(
859 const long i = (
long)(x + g) + (
long)(y + g) * ex + (
long)(z + g) * (
long)ex * ey;
880 return vofEnabled_ ? vofAdv_.
lastCfl() : 0.0;
897 if constexpr (Grid::collocated) {
900 throw std::runtime_error(
902 ": geometric VoF on SolverColocated (rung V8) is ALL-FLUID only. The cut-cell colour "
903 "transport (rung V5a) is validated on the STAGGERED solver.");
908 m +=
": an immersed solid is present but the colour block has no cut-cell geometry. Rung V5a "
909 "needs the cut-cell openness, i.e. set_solid(sdf, cutcell_pressure=True) (the staircase "
910 "pressure operator has no face openness to weight the geometric fluxes with).";
911 throw std::runtime_error(m);
936 return vofSolidZero_;
942 throw std::runtime_error(
"vof_filled_color: VoF is not enabled");
952 throw std::runtime_error(
"vof_geometry: VoF is not enabled (needs enable_vof)");
954 throw std::runtime_error(
"vof_geometry: `which` must be 0..4");
963 return std::vector<double>((std::size_t)nx_ * ny_ * nz_, which == 4 ? 0.0 : 1.0);
979 throw std::runtime_error(
"set_contact_angle: theta must be in [0, 180] degrees");
981 contactAngleField_.clear();
982 contactAngleSet_ =
true;
992 throw std::runtime_error(
993 "set_contact_angle: this solver has no wetting wall - no immersed SDF solid (set_solid) "
994 "and no impermeable domain face (set_domain_bc(face, 1) no-slip or 4 free-slip). The "
995 "angle would be imposed nowhere. (Inflow/outflow faces carry no contact line.)");
1000 if (
thetaDeg.size() != (std::size_t)nx_ * ny_ * nz_)
1001 throw std::runtime_error(
"set_contact_angle_field: expected nx*ny*nz values");
1003 contactAngleSet_ =
true;
1009template <
class Gr
id>
1011 return contactAngleSet_;
1014template <
class Gr
id>
1016 return contactAngleDeg_;
1019template <
class Gr
id>
1022 throw std::runtime_error(
"set_contact_angle_pivot: mode must be 0..3");
1023 contactPivot_ = mode;
1027template <
class Gr
id>
1029 return contactPivot_;
1032template <
class Gr
id>
1039 throw std::runtime_error(
1040 "set_contact_angle_dynamic: the slip length must lie in (0, 1) cell (lambda < Delta)");
1041 if (!(muLiquid > 0.0))
1042 throw std::runtime_error(
"set_contact_angle_dynamic: mu_liquid must be positive");
1046 contactSigmaOverride_ = sigma;
1054 if (wallSlip_ && hasSolid_ && !Grid::collocated) {
1061template <
class Gr
id>
1064 throw std::runtime_error(
"set_wall_slip_length: lambda must be >= 0");
1067 throw std::runtime_error(
1068 "set_wall_slip_length: the Navier wall closure is a staggered-grid feature (the "
1069 "collocated scheme carries its own wall treatment)");
1081template <
class Gr
id>
1083 return wallSlip_ ? slipPhys_ : 0.0;
1086template <
class Gr
id>
1088 return slipSandwich_;
1091template <
class Gr
id>
1094 throw std::runtime_error(
"set_contact_angle_hysteresis: theta_a must be >= theta_r");
1096 throw std::runtime_error(
"set_contact_angle_hysteresis: angles must lie in [0, 180]");
1097 const double toRad = 3.14159265358979323846 / 180.0;
1101 if (!contactAngleSet_)
1107template <
class Gr
id>
1114template <
class Gr
id>
1119template <
class Gr
id>
1124template <
class Gr
id>
1126 return vofDyn_.
slip;
1129template <
class Gr
id>
1134template <
class Gr
id>
1137 throw std::runtime_error(
"set_contact_angle_clamp: need 0 < lo < hi < 180");
1138 const double toRad = 3.14159265358979323846 / 180.0;
1143template <
class Gr
id>
1145 return contactSigmaOverride_ > 0.0 ? contactSigmaOverride_ : sigmaCsf_;
1148template <
class Gr
id>
1151 throw std::runtime_error(
1152 "vof_dynamic_field: no dynamic contact angle (call set_contact_angle_dynamic / "
1153 "set_contact_angle_hysteresis first)");
1156 const double toDeg = 180.0 / 3.14159265358979323846;
1160 else if (which == 1)
1162 else if (which == 2)
1164 else if (which == 3)
1176template <
class Gr
id>
1179 throw std::runtime_error(
"contact_angle_diagnostics: VoF is not enabled");
1181 d.setAngle = contactAngleDeg_;
1185 long counts[vof::kVofWetCount];
1191 d.unfilledCells =
counts[vof::kVofWetNone];
1192 d.contactCells =
counts[vof::kVofWetTheta];
1193 d.neighbourCells =
counts[vof::kVofWetNeighbour];
1194 d.pureCells =
counts[vof::kVofWetPure];
1195 d.parallelCells =
counts[vof::kVofWetParallel];
1196 d.neutralCells =
counts[vof::kVofWetNeutral];
1198 const auto cs = vofDyn_.
census(vofAdv_);
1199 d.dynamicCells = cs.contactCells;
1203 d.meanImposedTheta = cs.meanImposedDeg;
1204 d.meanApparentTheta = cs.meanApparentDeg;
1205 d.maxCaCl = cs.maxCa;
1206 d.maxContactSpeed = cs.maxSpeed;
1211template <
class Gr
id>
1213 if (!contactAngleSet_ || !vofEnabled_ || !vofAdv_.
hasGeometry())
1233 const int g =
kVofG;
1235 Kokkos::parallel_for(
1238 const long i = (
long)x + (
long)y * e.x + (
long)z * (
long)e.x * e.y;
1239 const double d =
vofWallPlaneSdf(x - g + org.x, y - g + org.y, z - g + org.z, gs,
wet);
1240 sd(i) = Kokkos::fmin(
sd(i), d);
1245 const double toRad = 3.14159265358979323846 / 180.0;
1246 if (contactAngleField_.empty()) {
1250 std::vector<double> rad(contactAngleField_.size());
1251 for (std::size_t i = 0; i < rad.size(); ++i)
1252 rad[i] = contactAngleField_[i] *
toRad;
1265template <
class Gr
id>
1269 if constexpr (Grid::collocated)
1270 throw std::runtime_error(
"set_contact_angle_dynamic: SolverColocated has no immersed solid");
1271 const long st[3] = {1, (
long)e_.
x, (
long)e_.
x * (
long)e_.
y};
1272 for (
int c = 0; c < 3; ++c) {
1277 if (outflowCorrValid_)
1281 if (vofDynVel_[c].extent(0) != n_)
1282 vofDynVel_[c] =
CCField(
"vofDynVel", n_);
1285 const long sc = st[c];
1286 const int ex = e_.
x, ey = e_.
y, g =
G;
1287 Kokkos::parallel_for(
1290 const long i = (
long)(x + g) + (
long)(y + g) * ex + (
long)(z + g) * (
long)ex * ey;
1291 t(i) = 0.5 * (u(i) + u(i + sc));
1299template <
class Gr
id>
1304template <
class Gr
id>
1308 vofBlocks_->setStep(n);
1311template <
class Gr
id>
1316template <
class Gr
id>
1320 throw std::runtime_error(
"advect_vof: VoF is not enabled (call enable_vof / set_vof first)");
1327 if constexpr (Grid::collocated) {
1328 if (!faceFieldValid_)
1329 throw std::runtime_error(
1330 "advect_vof: SolverColocated advects the colour with the MAC FACE field uf_/vf_/wf_ "
1331 "(the only discretely divergence-free field on this grid), and it has never been "
1332 "built — it is all zeros, which the divergence guard would certify as solenoidal and "
1333 "the advection would be a silent no-op. Call set_state()/set_velocity() (which now "
1334 "seed the face field through the same centerToFace map project() uses) or step() "
1342 if (!cutcellPressure_)
1343 throw std::runtime_error(
1344 "advect_vof: no cut-cell pressure operator, so the divergence guard cannot measure "
1345 "anything (max_open_divergence() would return 0 whatever the field is). Build one with "
1346 "set_pressure_geometry(sdf) on an all-fluid box, or set_solid(sdf, "
1347 "cutcell_pressure=True) — Weymouth-Yue conservation is conditional on "
1348 "sum_f o_f u_f = 0 per cell and a cell-centre-sampled analytic field does NOT satisfy "
1349 "it (measured on LeVeque: max|div| 0.612, 4.93 % of the liquid lost in 50 steps).");
1354 if (!(div <= 1e-10)) {
1356 std::snprintf(
msg,
sizeof(
msg),
1357 "advect_vof: the current face velocity is not discretely divergence-free "
1358 "(max|div(open*u)| = %.6g > 1e-10). Weymouth-Yue conservation is conditional "
1359 "on it; project the field first (step() / project()).",
1361 throw std::runtime_error(
msg);
1368 vofEnergy_.
advect(vofAdv_, dt, vofStep_++);
1370 scalarFillGhosts(sc);
1372 vofAdv_.
advect(dt, vofStep_++);
1380template <
class Gr
id>
1383 for (
const auto&
sd : seeds)
1384 vofBlocks_->seedSphere(
sd[0],
sd[1],
sd[2],
sd[3]);
1385 vofBlocks_->scatter(vofAdv_.
colour());
1389template <
class Gr
id>
1392 throw std::runtime_error(
"enable_vof_blocks: VoF is not enabled (call enable_vof first)");
1396 throw std::runtime_error(
1397 "enable_vof_blocks: the block container is ALL-FLUID at rung W0 (the cut-cell block is "
1398 "rung W12). Drop set_solid, or use the structured colour field (advect_vof).");
1399 int rank = 0, size = 1;
1400#ifdef PECLET_FLOW_MPI
1406 vofBlocks_ = std::make_shared<vof::VofBlockSet>();
1414 vofBlocks_->cflLimit = vofCflLimit_;
1417 vofBlocks_->setWispEps(vofAdv_.
wispEps);
1418 vofBlockExch_ = std::make_shared<vof::VofBlockExchange>();
1420#ifdef PECLET_FLOW_MPI
1422 vofBlockExch_->setComm(
comm_);
1425 vofBlocks_->setExchange(vofBlockExch_);
1428template <
class Gr
id>
1430 vofBlocks_->assignMasters();
1431 vofBlocks_->scatter(vofAdv_.
colour());
1435template <
class Gr
id>
1437 return static_cast<bool>(vofBlocks_);
1440template <
class Gr
id>
1443 vofBlockExch_.reset();
1446template <
class Gr
id>
1450 throw std::runtime_error(
"advect_vof_blocks: call enable_vof_blocks first");
1452 if (!(div <= 1e-10)) {
1454 std::snprintf(
msg,
sizeof(
msg),
1455 "advect_vof_blocks: the current face velocity is not discretely "
1456 "divergence-free (max|div(open*u)| = %.6g > 1e-10). Weymouth-Yue conservation "
1457 "is conditional on it; project the field first (step() / project()).",
1459 throw std::runtime_error(
msg);
1461 const double _tv0 = vofTick();
1462 vofBlocks_->timingOn = vofTiming_;
1463 const double _td0 = vofBlocks_->debrisSeconds;
1465 vofBlocks_->advect(dt, vofAdv_.
colour());
1474template <
class Gr
id>
1477 throw std::runtime_error(
"vof_block_stats: call enable_vof_blocks first");
1478 return vofBlocks_->statsAll();
1481template <
class Gr
id>
1484 throw std::runtime_error(
"vof_block_imbalance: call enable_vof_blocks first");
1485 return vofBlocks_->cellImbalance();
1488template <
class Gr
id>
1491 throw std::runtime_error(
"vof_block_census: call enable_vof_blocks first");
1492 vofBlocks_->masterCensus(
masters, cells);
1495template <
class Gr
id>
1498 throw std::runtime_error(
"set_vof_block_assign: call enable_vof_blocks first");
1500 vofBlocks_->reassignEvery =
every;
1501 vofBlocks_->assignMasters();
1504template <
class Gr
id>
1507 throw std::runtime_error(
"vof_block_assign: call enable_vof_blocks first");
1508 return static_cast<int>(vofBlocks_->assignMode);
1511template <
class Gr
id>
1514 throw std::runtime_error(
"vof_block_imbalance_of: call enable_vof_blocks first");
1515 const auto save = vofBlocks_->assignMode;
1517 const std::vector<int> m = vofBlocks_->plannedMasters();
1518 vofBlocks_->assignMode =
save;
1519 const int np = vofBlocks_->size();
1520 std::vector<long> load(
static_cast<std::size_t
>(
np), 0);
1521 long tot = 0, mx = 0;
1522 for (std::size_t i = 0; i < m.size(); ++i) {
1523 const long w = vofBlocks_->blocks()[i].box.cells();
1524 load[
static_cast<std::size_t
>(m[i])] += w;
1528 mx = std::max(mx, v);
1529 return tot == 0 ? 1.0 :
static_cast<double>(mx) *
np /
static_cast<double>(tot);
1532template <
class Gr
id>
1535 throw std::runtime_error(
"set_vof_block_device_staging: call enable_vof_blocks first");
1536 vofBlockExch_->deviceStaging =
on;
1539template <
class Gr
id>
1542 throw std::runtime_error(
"set_vof_block_pool: call enable_vof_blocks first");
1543 vofBlocks_->usePool =
on;
1545 vofBlocks_->clearPool();
1548template <
class Gr
id>
1551 throw std::runtime_error(
"vof_block_pool_stats: call enable_vof_blocks first");
1552 return {vofBlocks_->poolHits(), vofBlocks_->poolMisses()};
1555template <
class Gr
id>
1558 for (
const auto& q :
boxes) {
1560 for (
int d = 0; d < 3; ++d) {
1562 bb.hi[d] = q[3 + d];
1564 vofBlocks_->seedBox(
bb);
1567 vofBlocks_->finishSeeding(vofAdv_.
colour());
1571template <
class Gr
id>
1574 throw std::runtime_error(
"vof_block_color: call enable_vof_blocks first");
1575 if (
id < 0 ||
static_cast<std::size_t
>(
id) >= vofBlocks_->count())
1576 throw std::runtime_error(
"vof_block_color: no such block id");
1577 return vofBlocks_->blockColourHost(
static_cast<std::size_t
>(
id));
1580template <
class Gr
id>
1583 throw std::runtime_error(
"vof_block_kappa: call enable_vof_blocks first");
1584 if (
id < 0 ||
static_cast<std::size_t
>(
id) >= vofBlocks_->count())
1585 throw std::runtime_error(
"vof_block_kappa: no such block id");
1586 return vofBlocks_->blockKappaHost(
static_cast<std::size_t
>(
id));
1589template <
class Gr
id>
1591 const std::vector<std::vector<double>>&
colours) {
1593 throw std::runtime_error(
"enable_vof_blocks_from_colors: one color array per box is required");
1595 for (std::size_t i = 0; i <
boxes.size(); ++i) {
1597 for (
int d = 0; d < 3; ++d) {
1601 vofBlocks_->seedBoxWithColour(
bb,
colours[i]);
1606template <
class Gr
id>
1609 throw std::runtime_error(
"enable_vof_block_csf: call enable_vof_blocks first");
1610 if (!(sigmaCsf_ > 0.0))
1611 throw std::runtime_error(
"enable_vof_block_csf: set_surface_tension(sigma) first (sigma > 0)");
1612 if constexpr (Grid::collocated)
1613 throw std::runtime_error(
1614 "enable_vof_block_csf: the block CSF is STAGGERED-only at rung W2 (the collocated face "
1615 "-acceleration form is V8's, and composing the two is not this rung).");
1624 vofBlocks_->curvProto.copyTunablesFrom(vofCurv_);
1625 vofBlocks_->curvProto.interfaceEps = csfInterfaceEps_;
1628 vofBlocks_->curvProto.pureEps = vofAdv_.
wispEps;
1629 vofBlocks_->setWispEps(vofAdv_.
wispEps);
1630 vofBlocks_->curvProto.kappaMax = vofBlockKappaMax_;
1631 vofBlocks_->debrisRemove = (vofBlockDebris_ != 0);
1632 vofBlocks_->enableCsf(sigmaCsf_);
1633 const long len3 =
static_cast<long>(e3_.
x) * e3_.
y * e3_.
z;
1634 for (
int c = 0; c < 3; ++c) {
1635 vofBlkF_[c] =
SField(
"vof::blockcsf::patch",
len3);
1636 csfBlkF_[c] =
CCField(
"vof::blockcsf", n_);
1642template <
class Gr
id>
1644 if (!vofBlocks_ || !vofBlocks_->csfEnabled)
1646 vofBlocks_->computeCsf(vofBlkF_[0], vofBlkF_[1], vofBlkF_[2]);
1647 for (
int c = 0; c < 3; ++c)
1651template <
class Gr
id>
1653 if (!csfBlkF_[c].extent(0))
1654 throw std::runtime_error(
"vof_block_force: call enable_vof_block_csf() first");
1658template <
class Gr
id>
1661 throw std::runtime_error(
"vof_block_curvature_stats: call enable_vof_blocks first");
1662 return vofBlocks_->csfCurvatureStats();
1665template <
class Gr
id>
1668 vofInflowSet_[f] =
true;
1669 vofInflowC_[f] = value;
1670 vofInflowProfRaw_[f].clear();
1674template <
class Gr
id>
1677 if ((
int)
prof.size() != nb * nc)
1678 throw std::runtime_error(
"set_vof_inflow_profile: profile size != nb*nc");
1679 vofInflowSet_[f] =
true;
1680 vofInflowProfRaw_[f] =
prof;
1681 vofInflowProfNb_[f] = nb;
1682 vofInflowProfNc_[f] = nc;
1686template <
class Gr
id>
1689 vofBackflowSet_[f] =
true;
1690 vofBackflowC_[f] = value;
1694template <
class Gr
id>
1696 return vofBcActive_;
1699template <
class Gr
id>
1701 return std::vector<double>(vofBcVol_, vofBcVol_ + 6);
1704template <
class Gr
id>
1706 return std::vector<double>(vofBcVolTotal_, vofBcVolTotal_ + 6);
1709template <
class Gr
id>
1711 for (
int f = 0; f < 6; ++f)
1712 vofBcVol_[f] = vofBcVolTotal_[f] = 0.0;
1715template <
class Gr
id>
1717 if (!vofBcActive_ || !vofEnabled_)
1719 if (f.data() != vofAdv_.
colour().data())
1721 const I3 e3{e3_.
x, e3_.
y, e3_.
z};
1722 for (
int face = 0; face < 6; ++face) {
1723 if (!touchesGlobalFace(face))
1725 const int a = face / 2,
sd = face % 2;
1726 if (bc_[face] == 2 && vofInflowSet_[face]) {
1727 if (vofInflowProf3_[face].extent(0))
1731 }
else if (bc_[face] == 3 && vofBackflowSet_[face]) {
1739template <
class Gr
id>
1741 if (!vofBcActive_ || !vofEnabled_)
1743 const bool isColour = cField_.extent(0) && f.data() == cField_.data();
1744 const I3 e2{e_.
x, e_.
y, e_.
z};
1745 for (
int face = 0; face < 6; ++face) {
1746 if (bc_[face] != 2 || !vofInflowSet_[face] || !touchesGlobalFace(face))
1748 const int a = face / 2,
sd = face % 2;
1750 if (vofInflowProfG2_[face].extent(0))
1755 for (
const auto&
cl : closures_)
1756 if (
cl.out.data() == f.data())
1762template <
class Gr
id>
1766 const I3 e3{e3_.
x, e3_.
y, e3_.
z};
1776 for (
int face = 0; face < 6; ++face)
1777 vofAdv_.
setBcFaceOwned(face, vofBcActive_ && bc_[face] != 0 && touchesGlobalFace(face));
1778 for (
int face = 0; face < 6; ++face) {
1779 vofInflowProf3_[face] =
CCField();
1780 vofInflowProfG2_[face] =
CCField();
1781 if (vofInflowProfRaw_[face].empty())
1783 vofInflowProf3_[face] =
1784 resampleFaceScalar(vofInflowProfRaw_[face], vofInflowProfNb_[face], vofInflowProfNc_[face],
1785 face, e3_,
kVofG, vofProf3Nc_[face]);
1786 vofInflowProfG2_[face] =
1787 resampleFaceScalar(vofInflowProfRaw_[face], vofInflowProfNb_[face], vofInflowProfNc_[face],
1788 face, e_,
G, vofProfG2Nc_[face]);
1792template <
class Gr
id>
1795 const int a = face / 2;
1796 const int dims[3] = {ext.
x, ext.
y, ext.
z};
1797 const int bax = (a + 1) % 3,
cax = (a + 2) % 3;
1800 auto h = Kokkos::create_mirror_view(pf);
1801 auto cl = [](
int v,
int n) {
return v < 0 ? 0 : (v >= n ? n - 1 : v); };
1802 for (
int p0 = 0; p0 <
Lb; ++p0)
1804 h((
long)p0 *
Lc +
p1) =
prof[(std::size_t)
cl(p0 - g, nb) * nc +
cl(
p1 - g, nc)];
1805 Kokkos::deep_copy(pf, h);
1810template <
class Gr
id>
1813 throw std::runtime_error(std::string(
who) +
": face must be 0..5 (-x,+x,-y,+y,-z,+z)");
1816 throw std::runtime_error(std::string(
who) +
": face " + std::to_string(f) +
1817 " has domain BC type " + std::to_string(bc_[f]) +
", not " +
1818 std::to_string(
wantType) +
" — call set_domain_bc(face, " +
1820 ", ...) first (2 = inflow, "
1824template <
class Gr
id>
1826 vofBcActive_ =
true;
1832 if (vofEnabled_ && cField_.extent(0))
1836template <
class Gr
id>
1840 for (
int f = 0; f < 6; ++f) {
1842 vofBcVolTotal_[f] += vofBcVol_[f];
1846template <
class Gr
id>
1849 throw std::runtime_error(
1850 "compute_vof_curvature: VoF is not enabled (call enable_vof / set_vof first)");
1851 if (!kappaField_.extent(0)) {
1853 kappaBranch_ =
addField(
"kappa_branch");
1870template <
class Gr
id>
1872 return vofCurvStats_;
1875template <
class Gr
id>
1877 if (!kappaField_.extent(0))
1878 throw std::runtime_error(
"vof_curvature: call compute_vof_curvature() first");
1879 std::vector<double> out =
gatherInner(kappaField_);
1882 for (
double& x : out)
1887template <
class Gr
id>
1889 if (!kappaBranch_.extent(0))
1890 throw std::runtime_error(
"vof_curvature_branch: call compute_vof_curvature() first");
1894template <
class Gr
id>
1899template <
class Gr
id>
1904template <
class Gr
id>
1909template <
class Gr
id>
1914template <
class Gr
id>
1916 if (!(sigma >= 0.0))
1917 throw std::runtime_error(
"set_surface_tension: sigma must be >= 0");
1920 if (!kappaField_.extent(0)) {
1922 kappaBranch_ =
addField(
"kappa_branch");
1940template <
class Gr
id>
1945template <
class Gr
id>
1947 csfInterfaceEps_ = eps;
1948 if (sigmaCsf_ > 0.0)
1952template <
class Gr
id>
1954 return csfInterfaceEps_;
1957template <
class Gr
id>
1966 throw std::runtime_error(
1967 "set_vof_block_kappa_clip: kappa_max must be > 0 (or None for 1/Delta_min)");
1969 vofBlockKappaMax_ = km;
1971 vofBlocks_->setKappaMax(km);
1974template <
class Gr
id>
1979template <
class Gr
id>
1984template <
class Gr
id>
1986 return static_cast<bool>(vofBlocks_) && vofBlocks_->csfEnabled;
1989template <
class Gr
id>
1991 return vofEnabled_ && sigmaCsf_ > 0.0 && (kappaField_.extent(0) != 0 ||
vofBlockCsf());
1994template <
class Gr
id>
1999template <
class Gr
id>
2001 return kappaFrozen_;
2004template <
class Gr
id>
2007 if (!kappaField_.extent(0)) {
2009 kappaBranch_ =
addField(
"kappa_branch");
2011 Kokkos::deep_copy(kappaField_, kappa / u_.
curvToPhys());
2012 Kokkos::deep_copy(kappaBranch_, (
double)vof::kCurvHf);
2013 kappaFrozen_ =
true;
2016template <
class Gr
id>
2021template <
class Gr
id>
2023 if (!(sigmaCsf_ > 0.0))
2024 return std::numeric_limits<double>::infinity();
2028 for (
int a = 1; a < 3; ++a)
2033 double lo = 0.0, hi = 0.0;
2040template <
class Gr
id>
2045template <
class Gr
id>
2047 return capillaryCfl_;
2050template <
class Gr
id>
2054 L.cflDt = (L.courant > 0.0) ? dtPhys_ * vofCflLimit_ / L.courant
2055 : std::numeric_limits<double>::infinity();
2057 const double cap = capillaryCfl_ * L.capillaryDt;
2058 L.capillaryBinds = cap < L.cflDt;
2059 L.binding = L.capillaryBinds ? cap : L.cflDt;
2063template <
class Gr
id>
2065 double lo = 0.0, hi = 0.0;
2070template <
class Gr
id>
2072 if (vofMomEnabled_) {
2073 rlo = std::fmin(vofRhoG_, vofRhoL_);
2074 rhi = std::fmax(vofRhoG_, vofRhoL_);
2094 Kokkos::parallel_reduce(
2095 "rho_minmax",
MD(
space, {
G,
G,
G}, {e.x -
G, e.y -
G, e.z -
G}),
2097 const long i = (
long)x + (
long)y * e.x + (
long)z * (
long)e.x * e.y;
2098 mn = Kokkos::fmin(
mn, f(i));
2099 mx = Kokkos::fmax(mx, f(i));
2101 Kokkos::Min<double>(lo), Kokkos::Max<double>(hi));
2102 if (hi > 0.0 ||
pass == 1)
2106#ifdef PECLET_FLOW_MPI
2108 double g[2] = {lo, -hi}, r[2];
2118template <
class Gr
id>
2121 throw std::runtime_error(
2122 "step_adaptive: needs enable_vof() (it picks dt from the two "
2123 "explicit two-phase limits vof_step_limits() reports)");
2125 throw std::runtime_error(
"step_adaptive: cfl_target, capillary_cfl and dt_max must be > 0");
2133 if (!(dt > 0.0) || !std::isfinite(dt))
2134 throw std::runtime_error(
"step_adaptive: no finite dt (cfl_dt = " + std::to_string(L.cflDt) +
2135 ", capillary_dt = " + std::to_string(L.capillaryDt) +
2136 ") - pass a finite dt_max when neither limit is active");
2140 vofPrecheckDone_ =
true;
2145template <
class Gr
id>
2147 const bool skip = vofPrecheckDone_;
2148 vofPrecheckDone_ =
false;
2149 if (!vofEnabled_ ||
skip)
2161 if (!(
cfl <= vofCflLimit_))
2162 throw std::runtime_error(
2163 "peclet::flow::vof::WyAdvector: CFL = max|uf| dt/h = " + std::to_string(
cfl) +
2164 " exceeds the Weymouth-Yue boundedness cap " + std::to_string(vofCflLimit_) +
2165 " (dt = " + std::to_string(dt_) +
", h = " + std::to_string(vofAdv_.
h()) +
2166 ") - reduce dt. Rejected at the HEAD of step(): no field has been advanced, so a retry "
2167 "at a smaller dt is exact (see set_vof_cfl_limit / step_adaptive).");
2170template <
class Gr
id>
2173 ?
"2 rho_min [block markers overlap: the phantom gas-gas interface, "
2174 "vof_overlap_design 5.6]"
2176 return "surface tension: dt = " + std::to_string(dt_) +
" exceeds the capillary limit " +
2177 std::to_string(cap) +
" (Brackbill sqrt((" + rho +
") h^3/(4 pi sigma)) = " +
2178 std::to_string(cap / (capillaryCfl_ > 0.0 ? capillaryCfl_ : 1.0)) +
" x safety factor " +
2179 std::to_string(capillaryCfl_) +
2180 "). Surface tension is EXPLICIT: this is a hard stability boundary (Denner & van "
2181 "Wachem 2015), not a margin. Reduce dt, or raise set_capillary_cfl deliberately.";
2184template <
class Gr
id>
2190 else if (!kappaFrozen_)
2197template <
class Gr
id>
2210 const double _tv0 = vofTick();
2212 const double _tb0 = vofTick();
2224 vofEnergy_.
advect(vofAdv_, dt_, vofStep_++);
2226 scalarFillGhosts(sc);
2228 vofAdv_.
advect(dt_, vofStep_++);
2235 const double _tb1 = vofTick();
2244template <
class Gr
id>
2247 if constexpr (Grid::collocated)
2248 throw std::runtime_error(
2249 "enable_vof_momentum: momentum-consistent VoF transport is STAGGERED-ONLY (rung V2b); the "
2250 "collocated construction is Favre-averaged face states, rung V8.");
2253 throw std::runtime_error(
2254 "enable_vof_momentum: the volume-averaged porous momentum and the momentum-consistent VoF "
2255 "transport both own the momentum time term; they are not composable at this rung.");
2257 throw std::runtime_error(
"enable_vof_momentum: both phase densities must be > 0");
2262 vofMomEnabled_ =
true;
2263 for (
int c = 0; c < 3; ++c)
2264 if (uAdv_[c].extent(0) != n_)
2265 uAdv_[c] =
CCField(
"uAdv", n_);
2266 vofMom_.
init(vofAdv_, vofRhoG_, vofRhoL_);
2269template <
class Gr
id>
2271 return vofMomEnabled_;
2274template <
class Gr
id>
2279template <
class Gr
id>
2284template <
class Gr
id>
2289template <
class Gr
id>
2294template <
class Gr
id>
2299template <
class Gr
id>
2304template <
class Gr
id>
2306 if (!vofMomEnabled_)
2307 throw std::runtime_error(
"vof_momentum_diagnostics: enable_vof_momentum was never called");
2311template <
class Gr
id>
2313 if (!vofMomEnabled_)
2314 throw std::runtime_error(
"vof_advected_velocity: enable_vof_momentum was never called");
2318template <
class Gr
id>
2320 if (!vofMomEnabled_)
2324 throw std::runtime_error(
2325 "enable_vof_momentum is incompatible with implicit advection (set_implicit_advection / a "
2326 "domain-BC stencil path): the momentum advection is already done conservatively by the "
2327 "VoF fluxes, and the implicit-FOU operator would add a second one.");
2329 throw std::runtime_error(
2330 "enable_vof_momentum requires the variable-density momentum/projection path: register a "
2331 "density closure on C (set_property_model('rho','linear','C',[rho_g, rho_l-rho_g])) or "
2332 "call set_density_mode('variable').");
2335 const double _tv0 = vofTick();
2337 const double _tb0 = vofTick();
2342 vofMom_.
advect(vofAdv_, dt_, vofStep_++);
2346 const double _tb1 = vofTick();
2353 for (
int c = 0; c < 3; ++c)
2360template <
class Gr
id>
2363#ifdef PECLET_FLOW_MPI
2375template <
class Gr
id>
2383template <
class Gr
id>
2384void Solver<Grid>::vofAdd(
double& acc,
double t0) {
void imposeDomainWallKind(int mask)
void vofExchangeScalar(CCField f)
The colour block's ghost policy WITHOUT the colour-specific rules (no solid-band fill,...
void setVofMomentumMuscl(bool on)
void setVofCurvatureMixedHeightFit(bool on)
void setContactAngleClamp(double loDeg, double hiDeg)
const VofTiming & vofTimingReport() const
bool vofBlocksEnabled() const
void setVofRhoFloorFrac(double f)
void harvestVofBlockUnion()
void enableVofBlocksFromField(const std::vector< std::array< int, 6 > > &boxes)
static constexpr int kVofG
void setContactAngleSmoothing(bool on)
void vofApplyColourBc(CCField f)
The colour field's boundary rules, applied at the END of vofFillGhosts — i.e.
void fillVelGhosts(int comp, int fold)
double maxOpenDivergenceInternal()
The same diagnostic in INDEX units (per tRef), which is what the solver's own guards read.
void addCsfRhs(int c, CCField dst, bool rowScaled, bool faceRange=false)
double capillaryCfl() const
void registerBalancedForceState()
void setContactAngleDynamicOff()
void scatterInner(CCField fld, const std::vector< double > &in)
void setContactAnglePivot(int mode)
void setVofBlockPool(bool on)
bool contactAngleSet() const
ScalarField & scalarField(const std::string &name)
void setVofKappaConstant(double kappa)
kappa is a PHYSICAL curvature, 1/length (the internal field is 1/h; see vof_curvature()).
void fillGhosts(CCField f)
std::vector< double > getVofCurvature()
The interface curvature kappa = 2H in the caller's units, i.e.
int vofBlockAssign() const
void fillPropGhosts(CCField f, bool edges=false)
void checkVofBcFace(int f, int wantType, const char *who)
A VoF boundary colour is only meaningful on a face that already carries the matching domain BC,...
void setVofTiming(bool on)
std::vector< double > vofBlockKappa(long id)
void addCsfRhsBlocks(int c)
void setVofMomentumCellFlag(bool on)
CCField addField(const std::string &name)
void vofBcArm()
Arm the rung: install the mask, rebuild the profiles, and zero the boundary volume ledger.
void computeVofCurvature()
void computeVofBlockCsf()
bool vofHasGeometry() const
double vofSolidColourSum()
std::vector< double > getVofFilledColour()
void enableVofBlocks(const std::vector< std::array< double, 4 > > &seeds)
void pcUpdateEnergyProps()
Refresh k(C) and (rho c_p)(C) on the G=2 block from the current colour.
std::vector< double > getVofAdvectedVelocity(int c)
void setContactAngleDynamic(double thetaEDeg, double slipCells, double muLiquid, double sigma=0.0)
void setPressureExactResidual(bool on)
WO-R2 item 3 — the exact (matrix-free, double, flux-form) level-0 operator apply in the residual and ...
std::vector< double > getVofGeometry(int which)
void vofRebuildBcBlock()
(Re)build everything that lives on the g=3 block for this rung: the out-of-domain mask and the resamp...
std::vector< double > vofBcVolumes() const
Signed liquid volume that crossed each of the six domain faces during the LAST colour advection,...
double maxOpenDivergenceProjectedInternal()
The same diagnostic in INDEX units (per tRef), which is what the solver's own guards read.
std::vector< double > getVof()
double surfaceTension() const
The surface tension in the caller's units (what set_surface_tension was given).
void copyInner(CCField dst, C3 de, int dg, CCConst src, C3 se, int sg)
double capillaryDtInternal()
The same limit in the solver's index time (per tRef) — what the step's own guards compare to.
double vofTimingProjectionSeconds() const
CsfDiagnostics csfDiagnostics()
void setSurfaceTension(double sigma)
void vofHarvestBcVolumes()
Move the advector's per-face boundary volume ledger into the solver's, once per advection.
void enableVofMomentum(double rhoGasPhys, double rhoLiquidPhys)
rhoGas / rhoLiquid are PHYSICAL densities, the same numbers the rho closure is given.
std::vector< double > getVofCurvatureBranch()
void setVofInflowProfile(int f, const std::vector< double > &prof, int nb, int nc)
Per-position inflow colour on face f: prof is (nb, nc) on the INNER grid of the face's two perpendicu...
double vofBlockImbalanceOf(int mode) const
void requireVofGeometry(const char *who)
double capillaryDt()
The Brackbill capillary limit in the CALLER's time unit.
void setVof(const std::vector< double > &c)
void setContactAngleHysteresis(double thetaADeg, double thetaRDeg)
static constexpr double defaultVofWispEps()
The value enableVof starts from.
void buildVelocityOverlays(bool resetU)
Build the three per-component Robust-Scaled cut-cell overlays + solid masks from the CURRENT sdf_ (ex...
vof::VofCurvature::Stats vofBlockCurvatureStats() const
void applyDomainWallGeometry(int mask)
std::vector< double > getVofBlockForce(int c)
bool vofKappaFrozen() const
std::array< long, 2 > vofBlockPoolStats() const
void setVofCutFluxClamp(bool on)
ContactAngleDiagnostics contactAngleDiagnostics()
void setVofCurvatureWeightWidth(double d)
bool vofCutFluxClamp() const
double vofRhoFloorFrac() const
double vofTimingPredictorSeconds() const
void vofExchangeRaw(CCField f)
double wallSlipLength() const
The Navier slip length in the caller's units (0 = no-slip).
double vofInterfaceEps() const
void setVofBlockAssign(int mode, long every)
double contactAngle() const
void setWallSlipLength(double lambdaCells)
WO-V6b – the VELOCITY half of the dynamic contact line.
void updateVofCurvature()
void requireWettingWall() const
void vofBlockCensus(std::vector< long > &masters, std::vector< long > &cells) const
void setVofBackflow(int f, double value)
inletOutlet backflow colour on outflow face f (default 0 = gas): where the boundary face velocity poi...
vof::MomentumConsistentAdvector::Diagnostics vofMomentumDiagnostics()
std::vector< double > vofBlockColour(long id)
void fillVelGhostsTo(CCField f, int comp, int fold, bool doOutflow=true)
std::vector< double > getVofDynamicField(int which)
void setVofCurvatureWorklist(bool on)
VofCurvature::useWorklist — run the height-function cascade and the PV fallback over a COMPACTED list...
void setContactAngleField(const std::vector< double > &thetaDeg)
void setCapillaryCfl(double f)
const vof::VofCurvature::Timing & vofCurvatureTiming() const
CCField resampleFaceScalar(const std::vector< double > &prof, int nb, int nc, int face, C3 ext, int g, int &outNc)
Clamp-resample a per-position face scalar from the user's (nb, nc) INNER grid onto the ghost-inclusiv...
bool vofMomentumEnabled() const
vof::VofCurvature::Stats vofCurvatureStats() const
void setVofPhantomCapillaryBound(bool enabled)
void updateVofBlockOverlap()
int contactAnglePivot() const
std::vector< double > vofBcVolumesTotal() const
The same, accumulated since enable_vof() (or the last resetVofBcVolumes()).
void setVofWorklist(bool on)
WyAdvector::useWorklist — the compaction of the PLIC reconstruction pass onto the mixed cells.
void buildVofCellVelocity()
void vofExtendWallBand(CCField f, int mask)
void setVofInflow(int f, double value)
Colour of the fluid entering through inflow face f (0..5 = -x,+x,-y,+y,-z,+z), in [0,...
bool contactAngleHysteresis() const
void setVofStepParity(long n)
long strideOf(int c) const
const vof::WyAdvector::Timing & vofKernelTiming() const
void collocatedV8AutoFallback(const char *why)
std::array< int, 3 > wallSlipSandwichCells() const
Cut-cell axes at which a one-cell fluid gap kept the no-slip closure (per component).
void setVofBlockDeviceStaging(bool on)
void setVofKappaFrozen(bool on)
double vofRhoFloor() const
double stepAdaptive(double cflTarget, double capillaryCflTarget, double dtMax)
void bridgeVelocityToVof()
void setVofBlockOverlapDensity(bool enabled)
long vofStepParity() const
static double vofWallPlaneSdf(int gx, int gy, int gz, I3 gs, int mask)
void enableVofBlocksFromColours(const std::vector< std::array< int, 6 > > &boxes, const std::vector< std::vector< double > > &colours)
void setVofBlockDebris(bool enabled)
void advectVofKinematic(double dtPhysArg)
dt is in the caller's time unit.
void setVofInterfaceEps(double eps)
void setVofWispEps(double eps)
WO-R2 item 4 — the wisp threshold on the advector's mixed-cell predicate and on the interface Courant...
double effectiveContactSigma() const
void setVofBlockKappaClip(bool enabled, double kappaMaxPhys=-1.0)
double vofLastCourant() const
std::string capillaryThrowMessage(double cap) const
bool contactAngleDynamic() const
double vofTimingMomentumSeconds() const
double vofTimingStepSeconds() const
void setVofSolidColourZero(bool on)
std::vector< vof::VofBox > vofBlockRankBoxes(int size) const
double vofCurvatureWeightWidth() const
void phaseDensityRange(double &lo, double &hi)
void vofBcPropGhosts(CCField f)
WO-R item 5 — the Neumann property policy at an inflow face.
void vofFillGhosts(CCField f)
void addCsfRhsCellInterp(int c, CCField dst, bool rowScaled, bool faceRange=false)
const vof::WyAdvector & vofAdvector() const
vof::WyAdvector::Diagnostics vofDiagnostics()
bool vofAxisPeriodic(int a) const
void setVofCflLimit(double v)
VofStepLimits vofStepLimits()
Both limits at the current state, in the CALLER's time unit (courant is dimensionless).
std::vector< double > gatherInner(CCField fld)
double vofWispEps() const
bool vofSolidColourZero() const
bool vofCurvatureWorklist() const
double vofBlockImbalance() const
int vofWetWallMask() const
std::vector< vof::VofBlockStats > vofBlockStats() const
void advectVofBlocks(double dtPhysArg, bool requireSolenoidal=true)
dt is in the caller's time unit.
void setContactAngle(double thetaDeg)
void setVofFluxClamp(bool on)
bool vofCurvatureMixedHeightFit() const
double contactAngleSlip() const
double vofCflLimit() const
bool clampFluxes
Ablation: drop the Weymouth flux clamp of point 3 (the raw geometric flux).
SField advectedVelocity(int e) const
The advected velocity of component e, indexed exactly like the solver's u_e (inner cells).
bool momentumMuscl
Opt-in MinMod-limited linear reconstruction of the donor velocity in the momentum flux,...
bool useCellDilationFlag
Ablation: use the PRESSURE-cell frozen dilation flag H(C^n - 1/2) on the shifted CV instead of the st...
void setPhaseDensities(double rhoG, double rhoL)
double lastRhoFloor() const
Diagnostics diagnostics() const
void advect(WyAdvector &w, double dt, long step)
One momentum-consistent Weymouth-Yue step: advances BOTH the colour field inside w and the half-shift...
void init(const WyAdvector &w, double rhoG, double rhoL)
double interfaceEps
Wisp threshold on the interfacial predicate.
double pureEps
Tolerance of the PURITY test that ends a height-function column (hfIsFull/hfIsEmpty),...
bool useMixedHeightFit
Tier 2b, and it ships OFF — a measured decision, see the WO-O findings entry.
double weightWidth
Wendland support width d of the PV fit, in cell units (Han et al. §5: 2.5 with S = 5).
const Timing & timing() const
Stats compute(SField c)
Compute the curvature over the inner region from a colour field on the SAME extended block.
void init(int nx, int ny, int nz, int ghost)
SField temperature() const
The transported temperature on the advector's block (inner cells mirror the solver's scalar).
void advect(WyAdvector &w, double dt, long step)
One consistent colour + energy Weymouth-Yue step.
void init(const WyAdvector &w, double rcpG, double rcpL)
void init(int nx, int ny, int nz, int ghost)
void init(int nx, int ny, int nz, int ghost)
Weymouth-Yue split advection of a colour field on an extended (inner + ghost) block.
SField contactAngle() const
The prescribed contact angle per cell, in RADIANS, measured through the liquid.
double bcFaceVolume(int f) const
Signed liquid volume that crossed domain face f (0..5 = −x,+x,−y,+y,−z,+z) since the last resetBcFace...
double maxCourantInterfaceAuto(double dth) const
The interface-local Courant number for this dt/h, using the CUT-CELL rule when geometry is attached (...
SField wettingNormal(int d) const
The fluid-only Youngs normal of each fluid cell (buildWettingNormals, then exchanged).
bool cutFluxClamp
Weymouth's admissible-interval clamp on the openness-weighted flux (cutcell.hpp vofCutFluxClamp).
void classifyGeometry()
Classify every cell from eps and the six face openness values (cutcell.hpp rule 1) into kindDouble().
SField colour() const
Colour field on the extended block (x-fastest).
void buildWettingNormals()
The fluid-only Youngs normal at every FLUID cell of the INNER region (the block's own ghost policy th...
void wettingCensusGhost(long counts[kVofWetCount], double &meanAppDeg, long &nApp, int ghost) const
bool interfaceLocalCfl
Take the CFL over INTERFACE-ADJACENT cells only (mixed cells and their face neighbours) instead of ov...
double cflLimit
Abort threshold on max |uf| dt / h.
void init(int nx, int ny, int nz, double h, int ghost=3)
bool useWorklist
Compaction of the reconstruction pass onto the mixed cells.
SField wallSdf() const
The SDF on this block, in cells, > 0 in fluid (the solver embeds + exchanges it).
static double wallSeconds()
SField faceOpenness(int d) const
Face openness of the +d face of each cell (the advector's own high-face convention — the SAME convent...
double wispEps
WO-R2 item 4 — the wisp tolerance on the mixed-cell predicate (wyIsMixed(c, wispEps)),...
std::function< double(double)> globalMax
All-reduce max across ranks; identity when unset (single block).
void solidBandFill()
The three-pass neutral (90 deg) solid-band fill of cutcell.hpp.
std::function< void(SField)> exchange
Refresh EVERY ghost layer of the given cell field (MPI exchange + domain BC fill)....
SField kindDouble() const
The cell classification as a DOUBLE (1 = solid), so it can ride the block's own halo exchange.
void setOutsideMask(UCField m)
Install the OUT-OF-DOMAIN mask (vof/colour_bc.hppbuildOutsideMask): 1 on every ghost cell outside the...
void finalizeGeometry()
kindDouble() (ghost-exchanged) -> the unsigned-char classification every kernel reads.
void disableGeometry()
Drop the geometry (back to the uncut kernels, byte-identically).
void advect(double dt, long step)
One Weymouth-Yue step: freeze the dilation flag, then three sweeps in the permutation selected by ste...
void setBcFaceOwned(int f, bool on)
Which of the six BLOCK boundary faces are GLOBAL domain faces this block owns.
SField epsFraction() const
Fluid volume fraction of each cell (buildCellFraction, a multiple of 1/64).
Diagnostics diagnostics() const
Local census over the inner region.
const Timing & timing() const
SField faceVel(int d) const
void enableGeometry()
Allocate the geometry views. Idempotent; does not classify.
double capillaryDt(double rhoSum, double h, double sigma)
Brackbill capillary time-step limit sqrt((ρ₁+ρ₂) h³ / (4πσ)).
bool csfKappaDefined(double branch)
Is the curvature of a cell carrying branch code branch usable?
void bcColourBackflow(SField f, I3 e, int g, int a, int s, SField un, double backflow)
inletOutlet on an outflow face: where the boundary face velocity points INTO the domain,...
void bcColourConst(SField f, I3 e, int g, int a, int s, double value)
Overwrite ALL g ghost layers of domain face (axis a, side s) with a constant colour.
void bcColourProfile(SField f, I3 e, int g, int a, int s, SField prof, int nc)
Same, with a per-position colour on the face's (b, c) plane.
void buildOutsideMask(UCField m, I3 e, int g, I3 o, I3 gs, bool px, bool py, bool pz)
1 for every ghost cell of the block that lies OUTSIDE the global domain on a non-periodic axis,...
@ kVofDynReceding
theta_app < theta_r: theta_r (+ Cox-Voinov)
@ kVofDynPinned
theta_r <= theta_app <= theta_a: the apparent angle is imposed
@ kVofDynAdvancing
theta_app > theta_a: theta_a (+ Cox-Voinov)
VofMasterAssign
How a block's MASTER rank is chosen.
void clampFill(SField f, I3 e, int g, I3 o, I3 gs, bool px, bool py, bool pz, int skip=0)
Zero-gradient (Neumann) fill of the ghost cells that lie OUTSIDE the global domain on a non-periodic ...
void copyFaceVelocity(SField dst, I3 de, int dg, SField src, I3 se, int sg, int dir)
Embed the ENTIRE source extended block (inner + its sg ghost layers) into a LARGER destination extend...
void periodicFill(SField f, I3 e, int g, bool px, bool py, bool pz)
Periodic wrap of a single-block extended field at ghost width g (the single-rank ghost path; under MP...
double csfFaceForce(double sigma, double kf, double dC, double hGrad)
The balanced-force CSF contribution to the momentum RHS at one staggered velocity unknown,...
Kokkos::View< unsigned char *, SMem > UCField
bool csfFaceCurvature(double k0, double b0, double k1, double b1, double &kf)
Face curvature for the balanced-force CSF, from the two cells sharing the face.
long L3(int x, int y, int z, I3 e)
void buildCellFraction(CCField cs, CCConst sdf, C3 e, int g)
void applyClosureFaceGhost(const Closure &cl, C3 e, int g, int a, int side)
constexpr const char * kVofMomBlocksMsg
enable_vof_momentum x enable_vof_blocks*: refused in EITHER call order.
Kokkos::View< double *, CCMem > CCField
Kokkos::DefaultExecutionSpace CCExec
Kokkos::DefaultExecutionSpace SExec
Kokkos::View< const double *, CCMem > CCConst
Kokkos::View< double *, SMem > SField
double w[3]
w_a = 1 / h_a'^2 (<= 1)
vof::VofMetric vofMetric() const
double timeToPhys() const
double hp[3]
h_a' = h_a / hRef (>= 1, exactly 1 on the finest axis)
double sigmaToInt() const
double curvToPhys() const
long cells
cells with S > 1 + 1e-8 (global)
bool active
the phantom-aware capillary bound is in force
double maxSum
max_cells S (global)
double excess
sum over cells of (S - 1)^+ (global, cell volumes)
double advect
the colour advection stage as a whole (advectVof)
double bridge
... of which: the G=2 <-> g=3 bridges + the C ghost policy
double momBridge
... of which: its bridges
double blockAdvect
the block-container colour stage as a whole (advectVofBlocks)
double momAdvect
the momentum-consistent colour+rho^c u stage (advectVofMomentum)
double blockDebris
... of which: the debris/residue pass (§5.3/§13 of the note)
This rank's own patch: the extended block the face velocity and the union colour live on.
Half-open global cell box [lo, hi).
Per-call census of the cascade over this block's inner region (LOCAL; a distributed caller sums them ...
SField stateField() const
VofDynamicState, as a double
SField cellVel(int d) const
cell-centre velocity (the solver fills it)
SField capillary() const
Ca_cl.
bool smooth
the 3-point in-wall mean of U_cl (ablation)
SField base() const
The STATIC per-cell base angle (radians) the selector starts from.
double muLiquid
the LIQUID dynamic viscosity used in Ca_cl
void measure(const WyAdvector &adv)
Pass A — the measurement.
SField speed() const
the SMOOTHED U_cl
bool hysteresis
theta_a / theta_r are configured
double slip
lambda, in CELLS (Delta = 1 cell), so logRatio = -ln(slip)
Census census(const WyAdvector &adv) const
bool dynamic
the Cox-Voinov correction is configured
SField imposed() const
the angle written this fill (rad)
double sigma
the surface tension used in Ca_cl (the solver resolves it)
SField apparent() const
the measured apparent angle (rad)
void impose(const WyAdvector &adv)
Pass B — the 3-point in-wall smoothing of U_cl, the selector and the write of theta into adv....