8#ifndef PECLET_FLOW_FLOW_IBM_PHASE_CHANGE_HPP
9#define PECLET_FLOW_FLOW_IBM_PHASE_CHANGE_HPP
15 if constexpr (Grid::collocated)
16 throw std::runtime_error(
17 "enable_phase_change: rungs P0/P1 are STAGGERED-ONLY (the collocated variable-density "
18 "pair has not been composed with the divergence-source deposit).");
21 throw std::runtime_error(
22 "enable_phase_change: an immersed solid is out of scope at rungs P0/P1 (the source "
23 "deposit and the regression would need the solid-clipped flux polygons of rung V5a's "
24 "follow-on). Use an all-fluid set_pressure_geometry.");
26 throw std::runtime_error(
27 "enable_phase_change is not composable with enable_vof_momentum at this rung: both own "
28 "the head of the step, and momentum consistency would have to carry the interfacial mass "
29 "transfer in its own fluxes.");
30 if (!(rhoG > 0.0) || !(rhoL > 0.0))
31 throw std::runtime_error(
"enable_phase_change: both phase densities must be > 0");
33 throw std::runtime_error(
"enable_phase_change: the latent heat h_lv must be > 0");
53 if (pcArea_.extent(0) != n_) {
54 pcArea_ =
CCField(
"pc_area", n_);
55 pcNrm_[0] =
CCField(
"pc_nx", n_);
56 pcNrm_[1] =
CCField(
"pc_ny", n_);
57 pcNrm_[2] =
CCField(
"pc_nz", n_);
60 pcCnew_ =
CCField(
"pc_cnew", n_);
61 pcDefic_ =
CCField(
"pc_defic", n_);
75 Kokkos::deep_copy(pcMdot_, v * u_.
mdotToInt());
85 std::vector<double>
vi(v.size());
86 for (std::size_t i = 0; i < v.size(); ++i)
101 throw std::runtime_error(
103 ": under a physical domain this writes a dimensional value into a field, converted once "
104 "with the reference scales; call set_rho and set_dt first (the first of each pins the "
105 "scale, and a field converted before that would be left in the wrong units)");
111 pcRhoG_ = pcRhoGPhys_ * u_.
rhoToInt();
112 pcRhoL_ = pcRhoLPhys_ * u_.
rhoToInt();
117 pcRcpG_ = pcRcpGPhys_ * u_.
rcpToInt();
118 pcRcpL_ = pcRcpLPhys_ * u_.
rcpToInt();
131 throw std::runtime_error(
"set_phase_change_thermal: no scalar named '" +
tname +
132 "' (call add_scalar first)");
134 throw std::invalid_argument(
"set_phase_change_thermal: '" +
tname +
135 "' is a cut-cell scalar; the phase-change energy path takes a "
136 "legacy scalar only (doc/scalar_ibm_design.md §8.1)");
176 throw std::runtime_error(
177 "set_phase_change_area: mode must be 0 (PLIC/MYC), 1 (cascade metric), 2 (cascade "
178 "normal), 3 (cascade footprint) or 4-7 (the joined marching-tetrahedra sheet: 4 colour/"
179 "centroid, 5 colour/split, 6 PLIC-distance/centroid, 7 PLIC-distance/split)");
191 throw std::runtime_error(
"vof_interface_area: VoF is not enabled (call enable_vof first)");
203 Kokkos::parallel_reduce(
204 "peclet::flow::vof_area_plic",
207 const long i = (
long)x + (
long)y *
e3.x + (
long)z * sz;
211 for (
int kk = -1;
kk <= 1; ++
kk)
212 for (
int jj = -1;
jj <= 1; ++
jj)
213 for (
int ii = -1;
ii <= 1; ++
ii)
214 st[vof::plicSt(
ii + 1,
jj + 1,
kk + 1)] = c(i +
ii +
jj * sy +
kk * sz);
216 vof::mycNormal(st, m);
224#ifdef PECLET_FLOW_MPI
237 if (!pcAreaMc_.
ready())
240 const auto m = pcAreaMc_.
compute(vofAdv_.
colour(), pcAreaMode_);
242 s.interfacial = m.cells;
243 s.hf = m.cells - m.orphanCells;
246 s.noEstimate = m.orphanCells;
248 pcMcOrphanArea_ = m.orphanArea;
251 pcMcOrphanArea_ = 0.0;
252 if (!pcAreaC_.
ready())
268 throw std::runtime_error(
269 "set_phase_change_energy: call set_phase_change_thermal first (the consistent transport "
270 "is for the energy scalar it names)");
272 throw std::runtime_error(
"set_phase_change_energy: both phase rho*c_p must be > 0");
276 if (pcKcell_.extent(0) != n_) {
277 pcKcell_ =
CCField(
"pc_k", n_);
278 pcRcp_ =
CCField(
"pc_rcp", n_);
285 vofEnergy_.
init(vofAdv_, pcRcpG_, pcRcpL_);
306 if (!pcTName_.empty() &&
hasScalar(pcTName_))
319 if (
on && pcClsPrev_.extent(0) != n_) {
320 pcClsPrev_ =
CCField(
"pc_cls_prev", n_);
321 Kokkos::deep_copy(pcClsPrev_, -1.0);
333 pcCarryConserve_ =
on;
334 if (
on && pcCarrySrc_.extent(0) != n_) {
335 pcCarrySrc_ =
CCField(
"pc_carry_src", n_);
336 Kokkos::deep_copy(pcCarrySrc_, 0.0);
342 return pcCarryConserve_;
359 if (order != 1 && order != 2)
360 throw std::runtime_error(
"set_phase_change_energy_order: order must be 1 or 2");
369 return (pcMdotOperator_ && pcGfmOrder_ == 2 && pcCurvDist_ && pcCarryConserve_) ? 2 : 1;
375 if (
on && pcRint_ != 0.0 && !pcThermal_)
376 throw std::runtime_error(
"set_phase_change_mdot_operator: needs the thermal mass flux");
377 pcMdotOperator_ =
on;
378 if (
on && pcMdotFit_.extent(0) != n_)
379 pcMdotFit_ =
CCField(
"pc_mdot_fit", n_);
384 return pcMdotOperator_;
390 if (order != 1 && order != 2)
391 throw std::runtime_error(
"set_phase_change_gfm_order: order must be 1 or 2");
404 if (
on && pcKappa_.extent(0) != n_)
405 pcKappa_ =
CCField(
"pc_kappa", n_);
406 pcMaskFresh_ =
false;
417 pcDepositFallback_ =
on;
422 return pcDepositFallback_;
465 b.dEoverwriteNew *=
ke;
476 const long sx = 1, sy = e_.
x, sz = (
long)e_.
x * e_.
y;
481 Kokkos::parallel_reduce(
482 "peclet::flow::pc_band_div",
485 const long i = (
long)x + (
long)y * sy + (
long)z * sz;
488 const double d = (ox(i +
sx) *
U(i +
sx) - ox(i) *
U(i)) +
489 (oy(i + sy) * V(i + sy) - oy(i) * V(i)) +
490 (oz(i + sz) * W(i + sz) - oz(i) * W(i));
491 acc = Kokkos::fmax(acc, Kokkos::fabs(d));
493 Kokkos::Max<double>(m));
495 return Kokkos::fmax(m, 0.0);
503 CCField kf = pcKcell_, rf = pcRcp_;
505 const double kg = pcKg_,
kl = pcKl_,
rg = pcRcpG_,
rl = pcRcpL_;
506 Kokkos::parallel_for(
507 "peclet::flow::pc_energy_props",
510 const long i = (
long)x + (
long)y * e.x + (
long)z * (
long)e.x * e.y;
511 const double cl = Kokkos::fmin(Kokkos::fmax(c(i), 0.0), 1.0);
523 if (pcUser_.extent(0) != n_)
527 std::vector<double>
vi(v.size());
528 for (std::size_t i = 0; i < v.size(); ++i)
546 if (pcSink_.extent(0) != n_)
553 Kokkos::parallel_reduce(
554 "peclet::flow::pc_sink_weight",
557 a +=
wv((
long)x + (
long)y * e.x + (
long)z * (
long)e.x * e.y);
561#ifdef PECLET_FLOW_MPI
569 throw std::runtime_error(
"set_divergence_sink: the weights must have a positive global sum");
593 if (pcBudgetOn_ && pcThermal_ && pcClsPrev_.extent(0) == n_) {
595 if (sc.dmask.extent(0) == n_)
617 Kokkos::parallel_reduce(
618 "peclet::flow::pc_extrema",
621 const long i = (
long)x + (
long)y * e.x + (
long)z * (
long)e.x * e.y;
622 lo = Kokkos::fmin(lo, c(i));
623 hi = Kokkos::fmax(hi, c(i));
625 Kokkos::Min<double>(
mn), Kokkos::Max<double>(mx));
631 vofEnergy_.
extrema(d.Tmin, d.Tmax);
644 d.removedVolume *= kv;
645 d.redistributed *= kv;
672 if (pcMdotOperator_ && pcThermal_ && !pcMaskFresh_) {
682 if (pcAreaCg2_.extent(0) != n_)
683 pcAreaCg2_ =
CCField(
"pc_area_cascade", n_);
694 const long sy = e_.
x, sz = (
long)e_.
x * e_.
y;
698 const bool thermal = pcThermal_;
701 const bool quad = pcQuadFit_;
703 const double Tsat = pcTsat_, kg = pcKg_,
kl = pcKl_,
hlv = pcHlv_,
Rint = pcRint_;
704 const double kapPresc = pcFitKappa_;
705 const double rhoG = pcRhoG_, rhoL = pcRhoL_;
709 const bool curvDist = pcCurvDist_ && pcKappa_.extent(0) == n_;
713 pcMdotOperator_ &&
thermal && pcGphi_.extent(0) == n_ &&
scT->dmask.extent(0) == n_;
721 throw std::runtime_error(
722 "set_phase_change_mdot_operator / set_phase_change_energy_order(2) under a physical "
723 "domain need set_phase_change_energy(True, rho_cp_gas, rho_cp_liquid): without it the "
724 "operator's interfacial heat is a diffusivity flux with an implicit rho*c_p of one "
725 "internal unit, which is not a physical heat capacity");
742 Kokkos::parallel_reduce(
746 const long i = (
long)x + (
long)y * e.x + (
long)z * sz;
759 for (
int kk = -1;
kk <= 1; ++
kk)
760 for (
int jj = -1;
jj <= 1; ++
jj)
761 for (
int ii = -1;
ii <= 1; ++
ii)
762 st[vof::plicSt(
ii + 1,
jj + 1,
kk + 1)] = c(i +
ii +
jj * sy +
kk * sz);
764 vof::mycNormal(st, m);
765 const double al = vof::plicAlpha(m[0], m[1], m[2], c(i));
770 double n[3] = {1.0, 0.0, 0.0};
771 const double sMet = vof::vofPhysNormal(m,
gme, n);
795 for (
int dz = -2; dz <= 2; ++dz)
796 for (
int dy = -2; dy <= 2; ++dy)
797 for (
int dx = -2; dx <= 2; ++dx) {
798 if (dx == 0 && dy == 0 && dz == 0)
810 const long j = i + dx + dy * sy + dz * sz;
811 const double cj = c(j);
818 else if (
cj >= 1.0 - pureEps && phi < 0.0)
838 const long stq[3] = {1, sy, sz};
840 for (
int d = 0; d < 3; ++d)
841 for (
int sg = -1; sg <= 1; sg += 2) {
845 if (!(
inD(
pc) > 0.5))
847 const double of = (d == 0) ? ((sg > 0) ?
oxF(i + 1) :
oxF(i))
848 : (d == 1) ? ((sg > 0) ?
oyF(i + sy) :
oyF(i))
849 : ((sg > 0) ?
ozF(i + sz) :
ozF(i));
852 const double ofB = (d == 0) ? ((sg > 0) ?
oxF(
pc + 1) :
oxF(
pc))
853 : (d == 1) ? ((sg > 0) ?
oyF(
pc + sy) :
oyF(
pc))
874 if (
behind && row.aBehind != 1.0) {
876 qsum += (row.aBehind - 1.0) * kb *
ofB * (T(
pc) - T(
jb));
880 Aeff = (A > 0.0) ? A : 1.0;
885 mfacc += mdotFit(i) * A;
912 int tx = 0,
ty = 0,
tz = 0;
914 for (
int k = 1; k <= 2 && !
found; ++k) {
915 const int ox = (int)Kokkos::round(k * n[0]);
916 const int oy = (int)Kokkos::round(k * n[1]);
917 const int oz = (int)Kokkos::round(k * n[2]);
918 if (ox == 0 && oy == 0 && oz == 0)
920 if (c(i + ox + oy * sy + oz * sz) <= pureEps) {
942 for (
int dz = -2; dz <= 2; ++dz)
943 for (
int dy = -2; dy <= 2; ++dy)
944 for (
int dx = -2; dx <= 2; ++dx) {
945 if (dx == 0 && dy == 0 && dz == 0)
953 if (!(dp[0] * n[0] + dp[1] * n[1] + dp[2] * n[2] > 0.0))
958 if (c(i + dx + dy * sy + dz * sz) > pureEps)
981 pcDiag_.
mdotFit = pcMdotFitMean_;
987 double mn = 0.0, mx = 0.0;
992 Kokkos::parallel_reduce(
993 "peclet::flow::pc_mdot_extrema",
996 const long i = (
long)x + (
long)y * e.x + (
long)z * sz;
998 lo = Kokkos::fmin(lo,
md(i));
999 hi = Kokkos::fmax(hi,
md(i));
1002 Kokkos::Min<double>(
mn), Kokkos::Max<double>(mx));
1014 for (
int d = 0; d < 3; ++d)
1020 for (
int d = 0; d < 3; ++d)
1024template <
class Gr
id>
1027 const long sy = e_.
x, sz = (
long)e_.
x * e_.
y;
1032 Kokkos::parallel_reduce(
1033 "peclet::flow::pc_source_gather",
1036 const long i = (
long)x + (
long)y * e.x + (
long)z * sz;
1038 for (
int dz = -2; dz <= 2; ++dz)
1039 for (
int dy = -2; dy <= 2; ++dy)
1040 for (
int dx = -2; dx <= 2; ++dx) {
1041 const long j = i + dx + dy * sy + dz * sz;
1042 const double dj =
dep(j);
1047 if (
tx + dx == 0 &&
ty + dy == 0 &&
tz + dz == 0)
1057#ifdef PECLET_FLOW_MPI
1067 const double f = sum / pcSinkW_;
1069 Kokkos::parallel_for(
1070 "peclet::flow::pc_sink_apply",
1073 const long i = (
long)x + (
long)y * e.x + (
long)z * sz;
1074 src(i) -= f *
wv(i);
1083template <
class Gr
id>
1086 const long sy = e_.
x, sz = (
long)e_.
x * e_.
y;
1089 const double rhoL = pcRhoL_;
1096 Kokkos::parallel_reduce(
1099 const long i = (
long)x + (
long)y * e.x + (
long)z * sz;
1100 const double A =
ar(i);
1107 const double raw =
Cf(i) - dV;
1108 const double cl = Kokkos::fmin(Kokkos::fmax(raw, 0.0), 1.0);
1112 (x >=
G && x < e.x -
G && y >=
G && y < e.y -
G && z >=
G && z < e.z -
G);
1119 rd += Kokkos::fabs(raw -
cl);
1126 Kokkos::parallel_for(
1127 "peclet::flow::pc_regress_apply",
1130 const long i = (
long)x + (
long)y * e.x + (
long)z * sz;
1132 const long st[3] = {1, sy, sz};
1133 for (
int d = 0; d < 3; ++d)
1134 for (
int s = -1; s <= 1; s += 2) {
1135 const long j = i + (
long)s * st[d];
1136 const double dj =
df(j);
1142 const double sgn =
dj < 0.0 ? -1.0 : 1.0;
1147 for (
int q = 0; q < 3; ++q) {
1148 const double p = sgn * n[q];
1149 const int sq = (p > 0.0) ? 1 : ((p < 0.0) ? -1 : 0);
1150 const double ct =
sq == 0 ? 0.0 :
cn(j + (
long)
sq * st[q]);
1168 Kokkos::parallel_reduce(
1169 "peclet::flow::pc_unresolved",
1172 const long i = (
long)x + (
long)y * e.x + (
long)z * sz;
1173 const double dj =
df(i);
1176 const double sgn =
dj < 0.0 ? -1.0 : 1.0;
1177 const long st[3] = {1, sy, sz};
1180 for (
int q = 0; q < 3; ++q) {
1181 const double p = sgn * n[q];
1182 const int sq = (p > 0.0) ? 1 : ((p < 0.0) ? -1 : 0);
1183 const double ct =
sq == 0 ? 0.0 :
cn(i + (
long)
sq * st[q]);
1189 acc += Kokkos::fabs(
dj);
1197template <
class Gr
id>
1199 if (pcKappa_.extent(0) != n_)
1200 pcKappa_ =
CCField(
"pc_kappa", n_);
1210template <
class Gr
id>
1212 if (pcInDomain_.extent(0) == n_)
1214 pcInDomain_ =
CCField(
"pc_indomain", n_);
1215 Kokkos::deep_copy(pcInDomain_, 0.0);
1218 Kokkos::parallel_for(
1219 "peclet::flow::pc_indomain",
1222 f((
long)x + (
long)y * e.x + (
long)z * (
long)e.x * e.y) = 1.0;
1229template <
class Gr
id>
1236 scalarFillGhosts(sc);
1238 const long sy = e_.
x, sz = (
long)e_.
x * e_.
y;
1239 CCField mk = sc.dmask, dv = sc.dval, tg = pcTgam_;
1243 const double kapPresc = pcFitKappa_;
1245 const bool curvDist = pcCurvDist_ && pcKappa_.extent(0) == n_;
1247 const bool carry = pcPlaneDir_,
quad = pcQuadFit_;
1248 const long syl = sy,
szl = sz;
1249 Kokkos::parallel_for(
1250 "peclet::flow::pc_thermal_mask",
1253 const long i = (
long)x + (
long)y * e.x + (
long)z * sz;
1256 mk(i) =
on ? 1.0 : 0.0;
1270 for (
int kk = -1;
kk <= 1; ++
kk)
1271 for (
int jj = -1;
jj <= 1; ++
jj)
1272 for (
int ii = -1;
ii <= 1; ++
ii)
1273 st[vof::plicSt(
ii + 1,
jj + 1,
kk + 1)] = c(i +
ii +
jj * sy +
kk * sz);
1275 vof::mycNormal(st, m);
1279 double n[3] = {0.0, 0.0, 0.0};
1280 const double sMet = vof::vofPhysNormal(m,
gme, n);
1283 const double al = vof::plicAlpha(m[0], m[1], m[2], c(i));
1297 for (
int dz = -2; dz <= 2; ++dz)
1298 for (
int dy = -2; dy <= 2; ++dy)
1299 for (
int dx = -2; dx <= 2; ++dx) {
1300 if (dx == 0 && dy == 0 && dz == 0)
1308 const long j = i + dx + dy *
syl + dz *
szl;
1309 const double cj = c(j);
1316 }
else if (
cj >= 1.0 - pureEps && phi < 0.0) {
1344 if (pcCarryConserve_ && pcCarrySrc_.extent(0) == n_)
1348 pcMaskFresh_ =
true;
1351template <
class Gr
id>
1353 for (
int face = 0; face < 6; ++face) {
1354 if (bc_[face] == 0 || !touchesGlobalFace(face))
1356 const int a = face / 2, side = face % 2;
1357 const int t1 = (a + 1) % 3,
t2 = (a + 2) % 3;
1358 const int nt1 = (
t1 == 0) ? nx_ : (
t1 == 1) ? ny_ : nz_;
1359 const int nt2 = (
t2 == 0) ? nx_ : (
t2 == 1) ? ny_ : nz_;
1360 const int na = (a == 0) ? nx_ : (a == 1) ? ny_ : nz_;
1361 const long sx = 1, sy = e_.
x, sz = (
long)e_.
x * e_.
y;
1362 const long sa = (a == 0) ?
sx : (a == 1) ? sy : sz;
1363 const long st1 = (
t1 == 0) ?
sx : (
t1 == 1) ? sy : sz;
1364 const long st2 = (
t2 == 0) ?
sx : (
t2 == 1) ? sy : sz;
1365 const int aInner = (side == 0) ?
G : (
G + na - 1);
1366 const int dir = (side == 0) ? -1 : +1;
1367 Kokkos::parallel_for(
1371 for (
int L = 1; L <= 2; ++L)
1372 f(base + (
long)dir * L *
sa) = 0.0;
1378template <
class Gr
id>
1381 const long sx = 1, sy = e_.
x, sz = (
long)e_.
x * e_.
y;
1386 const bool useRcp = pcEnergy_;
1387 const double rcpG = pcRcpG_,
rcpL = pcRcpL_;
1390 Kokkos::parallel_reduce(
1391 "peclet::flow::pc_carry_deposit",
MD(
CCExec(), {
G,
G,
G}, {e.x -
G, e.y -
G, e.z -
G}),
1393 const long st[3] = {
sx, sy, sz};
1394 const long i = (
long)x + (
long)y * sy + (
long)z * sz;
1398 for (
int d = 0; d < 3; ++d)
1399 for (
int sg = -1; sg <= 1; sg += 2) {
1400 const long j = i + (
long)sg * st[d];
1404 const double dE =
rj * (T(j) - dv(j));
1408 const double devj = T(j) - tg(j);
1409 int chosen[3] = {0, 0, 0};
1410 double w[3] = {0.0, 0.0, 0.0},
wsum = 0.0;
1411 for (
int dd = 0; dd < 3; ++dd) {
1414 const int sL = (
nn[dd] > 0.0) ? -1 : +1;
1415 const double dl = T(j + (
long)
sL * st[dd]) - tg(j);
1416 const double dg = T(j - (
long)
sL * st[dd]) - tg(j);
1418 if (mk(j + (
long)s * st[dd]) > 0.5)
1421 w[dd] =
nn[dd] *
nn[dd];
1428 add += (w[d] /
wsum) *
dE;
1441 Kokkos::parallel_reduce(
1442 "peclet::flow::pc_carry_total",
MD(
CCExec(), {
G,
G,
G}, {e.x -
G, e.y -
G, e.z -
G}),
1444 const long i = (
long)x + (
long)y * sy + (
long)z * sz;
1448 tot +=
ri * (T(i) - dv(i));
1452 pcCarryDeposited_ =
dep;
1456template <
class Gr
id>
1459 const long sy = e_.
x, sz = (
long)e_.
x * e_.
y;
1462 Kokkos::parallel_for(
1463 "peclet::flow::pc_carry_apply",
1466 const long i = (
long)x + (
long)y * sy + (
long)z * sz;
1474template <
class Gr
id>
1477 const long sy = e_.
x, sz = (
long)e_.
x * e_.
y;
1480 const bool useRcp = sc.energy && sc.rcp.extent(0) == n_;
1482 const double Tsat = pcTsat_;
1486 Kokkos::parallel_reduce(
1487 "peclet::flow::pc_budget_pre",
MD(
CCExec(), {
G,
G,
G}, {e.x -
G, e.y -
G, e.z -
G}),
1488 KOKKOS_LAMBDA(
int x,
int y,
int z,
double& a1,
double& a2,
double&
a3,
double&
a4,
double&
a5,
1489 double&
a6,
double&
a7) {
1490 const long i = (
long)x + (
long)y * sy + (
long)z * sz;
1491 const double r =
useRcp ? rcp(i) : 1.0;
1492 const double h = r * (T(i) -
Tsat);
1493 const bool msk = mk(i) > 0.5;
1496 a4 += r * (
dvl(i) - T(i));
1502 const double cp =
cls(i);
1508 a5 += r * (
dvl(i) - T(i));
1514 Kokkos::parallel_reduce(
1515 "peclet::flow::pc_budget_counts",
MD(
CCExec(), {
G,
G,
G}, {e.x -
G, e.y -
G, e.z -
G}),
1516 KOKKOS_LAMBDA(
int x,
int y,
int z,
long& c1,
long& c2,
long& c3,
long&
c4,
long&
c5) {
1517 const long i = (
long)x + (
long)y * sy + (
long)z * sz;
1518 const bool msk = mk(i) > 0.5;
1521 const double cp =
cls(i);
1539 pcBudget_.
hOpen = hOpen;
1553template <
class Gr
id>
1556 const long sx = 1, sy = e_.
x, sz = (
long)e_.
x * e_.
y;
1561 const bool useRcp = sc.energy && sc.rcp.extent(0) == n_;
1562 const bool useK = sc.energy && sc.kcell.extent(0) == n_;
1565 const double Tsat = pcTsat_,
Dc = sc.D,
thMin = pcGfmThMin_,
thMax = pcGfmThMax_;
1566 const bool gfm = pcPlaneDir_ && pcGphi_.extent(0) == n_;
1569 const bool curvDist = pcCurvDist_ && pcKappa_.extent(0) == n_;
1573 double hOpen = 0, q = 0,
qb = 0;
1575 Kokkos::parallel_reduce(
1576 "peclet::flow::pc_budget_post",
MD(
CCExec(), {
G,
G,
G}, {e.x -
G, e.y -
G, e.z -
G}),
1577 KOKKOS_LAMBDA(
int x,
int y,
int z,
double& a1,
double& a2,
double&
a3) {
1578 const long i = (
long)x + (
long)y * sy + (
long)z * sz;
1579 const double r =
useRcp ? rcp(i) : 1.0;
1580 const bool msk = mk(i) > 0.5;
1581 clsOut(i) =
msk ? 1.0 : (c(i) >= 0.5 ? 2.0 : 0.0);
1584 a1 += r * (T(i) -
Tsat);
1587 const long st[3] = {
sx, sy, sz};
1588 const double kd =
useK ? kc(i) :
Dc;
1589 for (
int d = 0; d < 3; ++d)
1590 for (
int sgn = -1; sgn <= 1; sgn += 2) {
1591 const long j = i + (
long)sgn * st[d];
1594 const double of = (d == 0) ? ((sgn < 0) ? ox(i) : ox(i +
sx))
1595 : (d == 1) ? ((sgn < 0) ? oy(i) : oy(i + sy))
1596 : ((sgn < 0) ? oz(i) : oz(i + sz));
1600 const long jb = i - (
long)sgn * st[d];
1601 const double ofB = (d == 0) ? ((sgn < 0) ? ox(i +
sx) : ox(i))
1602 : (d == 1) ? ((sgn < 0) ? oy(i + sy) : oy(i))
1603 : ((sgn < 0) ? oz(i + sz) : oz(i));
1606 a2 +=
kd *
of * row.aGamma * (tg(j) - T(i));
1607 if (
behind && row.aBehind != 1.0) {
1608 const double kb =
useK ? 0.5 * (kc(i) + kc(
jb)) :
Dc;
1609 a3 += (row.aBehind - 1.0) * kb *
ofB * (T(
jb) - T(i));
1618 pcBudget_.
calls += 1;
1621template <
class Gr
id>
1623 if (!pcEnabled_ && !pcHasUser_)
1629 Kokkos::parallel_for(
1630 "peclet::flow::pc_div_source",
1633 const long i = (
long)x + (
long)y * e.x + (
long)z * (
long)e.x * e.y;
1643template <
class Gr
id>
1645 return Kokkos::fmax(pcInterfaceEps_, vofAdv_.
wispEps);
1648template <
class Gr
id>
1650 return Kokkos::fmax(pcPureEps_, vofAdv_.
wispEps);
1653template <
class Gr
id>
1656 throw std::runtime_error(std::string(
who) +
1657 ": phase change is not enabled (call "
1658 "enable_phase_change first)");
1661template <
class Gr
id>
1664 return std::chrono::duration<double>(std::chrono::steady_clock::now().
time_since_epoch()).count();
void vofExchangeScalar(CCField f)
The colour block's ghost policy WITHOUT the colour-specific rules (no solid-band fill,...
int phaseChangeArea() const
void setPhaseChangeGfmOrder(int order)
WO-P3g item 2: the order of the one-sided (ghost-fluid) Dirichlet row.
void setPhaseChangeEnergyMuscl(bool on)
MinMod-limited donor reconstruction of the face temperature in the consistent energy flux (vof/energy...
static constexpr int kVofG
bool phaseChangeEnergyMuscl() const
void setPhaseChangeThermalOff()
void pcZeroDomainGhosts(CCField f)
Zero the two ghost layers on every NON-periodic domain face this rank owns.
void pcCarryDeposit(ScalarField &sc)
WO-P3f — the enthalpy the per-cell Dirichlet overwrite destroys, returned to the phase it came from.
void scatterInner(CCField fld, const std::vector< double > &in)
void phaseChangeStep()
The in-step driver: everything applyPhaseChange does, at the head of step().
ScalarField & scalarField(const std::string &name)
PhaseChangeDiagnostics phaseChangeDiagnostics()
bool phaseChangeDepositFallback() const
void fillGhosts(CCField f)
void setMassFluxUniform(double v)
Prescribe a UNIFORM mass flux (P0), in mass per area per time.
void setPhaseChangeFitCurvature(double kappa)
WO-P3f INSTRUMENT: prescribe the interface curvature kappa = div(n) the one-sided fits use to correct...
void fillPropGhosts(CCField f, bool edges=false)
void pcScatterSource()
(2) Deposit each interfacial cell's source into its chosen pure-gas cell, as a GATHER (each receiving...
void pcApplyDivergenceSource(CCField div)
Subtract the phase-change (and any prescribed) divergence source from div_ so the deflated pressure s...
void setPhaseChangeMdotOperator(bool on)
WO-P3g item 1: take mdot from the energy operator's own interfacial flux instead of the one-sided lea...
double phaseChangeCarryDeposited() const
The carry ledger, physical: an energy with the consistent transport on, and a temperature times a vol...
CCField addField(const std::string &name)
void pcRegress(double dt)
(3) The regression: two Jacobi passes over the exchanged per-cell data, so the clip deficit is redist...
vof::VofInterfaceArea::Stats pcAreaCascadeCompute()
Run the cascade area driver on the (already bridged) g = 3 colour block.
SField pcAreaCascadeField() const
The area field the last pcAreaCascadeCompute filled (either driver).
double pcEffPureEps() const
…and the mirror statement: a cell the ADVECTOR treats as a pure phase is a pure phase here too,...
double phaseChangeQOrphan() const
void pcUpdateThermalMask()
void pcUpdateEnergyProps()
Refresh k(C) and (rho c_p)(C) on the G=2 block from the current colour.
void requireScalesPinned(const char *who, bool needRho) const
Under an armed extent, refuse a call that writes a dimensional value into a FIELD before the referenc...
double phaseChangeQOperator() const
The operator's interfacial heat (power), physical.
bool phaseChangeEnabled() const
void copyInner(CCField dst, C3 de, int dg, CCConst src, C3 se, int sg)
void setDivergenceSink(const std::vector< double > &w)
WO-P23: an AUTO-BALANCED sink region for the phase-change divergence source.
bool phaseChangeEnergy() const
bool phaseChangeMdotOperator() const
void setPhaseChangeThermal(const std::string &tname, double Tsat, double kg, double kl, double Rint)
P1: compute mdot each step from the registered scalar tname by the one-sided pure-cell weighted least...
void pcBudgetPost(ScalarField &sc)
WO-P3f, part 2: the state AFTER the energy solve, the heat the plane-anchored rows actually delivered...
void setPhaseChangePlaneDirichlet(bool on)
The PLANE-ANCHORED (ghost-fluid) Dirichlet condition, ON by default.
double vofInterfaceArea()
The summed interfacial area over the inner region, PHYSICAL (globally reduced under MPI) — the E7 gal...
void setPhaseChangeQuadraticFit(bool on)
The QUADRATIC one-sided gradient fit (T - T_G = G phi + Q phi^2) instead of the linear one.
void clearDivergenceSink()
double phaseChangeFitCurvature() const
double pcBandDivergence()
The BAND-EXTENDED LIQUID VELOCITY of VOF_PLAN §9 item 3, as a MEASUREMENT rather than a switch.
void pcBudgetPre(ScalarField &sc)
WO-P3f, the energy-budget instrument, part 1: the state BEFORE the energy solve, and the class-change...
void applyPhaseChange(double dtPhysArg)
Kinematic entry point (the P0a/P1 driver): build mdot/A_G/n from the current colour and temperature,...
void setPhaseChangeCarryConserve(bool on)
WO-P3f: make the per-cell Dirichlet overwrite ENTHALPY-CONSERVING.
void setPhaseChangeEnergyOff()
void pcBuildInterface()
(1) The interface build: for every inner interfacial cell reconstruct the PLIC plane from the canonic...
double pcLedgerEnergyToPhys() const
The factor from an internal phase-change ledger entry (a per-cell sum of rcp' dT) to the caller's uni...
void clearDivergenceSource()
void enablePhaseChange(double rhoG, double rhoL, double hlv)
Turn on phase change.
bool hasScalar(const std::string &name) const
void pcBuildInDomain()
WO-P3g — 1 on every cell that CARRIES A ROW in the energy solve, 0 otherwise.
int phaseChangeGfmOrder() const
void pcCarryApply(ScalarField &sc)
WO-P3f: consume pcCarrySrc_ into the energy solve's time base.
void pcUpdateCurvature()
The per-cell Dirichlet mask of the energy scalar: T = T_sat + mdot R_int in every interfacial cell,...
double pcEffInterfaceEps() const
The colour tolerance that decides which cells are INTERFACIAL, and it has to be at least the colour a...
void setVofWispEps(double eps)
WO-R2 item 4 — the wisp threshold on the advector's mixed-cell predicate and on the interface Courant...
bool phaseChangeBudget() const
bool phaseChangeQuadraticFit() const
void setPhaseChangeCurvatureDistance(bool on)
WO-P3g item 3: measure the GFM row's theta and the one-sided fits' sample distances to the CURVED int...
void setPhaseChangeEnergyOrder(int order)
WO-P3g — the SECOND-ORDER interfacial energy operator, as one package.
void requirePhaseChange(const char *who) const
int phaseChangeEnergyOrder() const
void setMassFlux(const std::vector< double > &v)
Prescribe a per-cell mass flux (P0), x-fastest over the inner region; units and call-order rule as se...
void setPhaseChangeEnergy(double rcpGas, double rcpLiquid)
Turn on the CONSISTENT energy transport (VOF_PLAN §9 item 6) for the scalar set_phase_change_thermal ...
double phaseChangeCarryLost() const
void scalarDirichletMask(const std::string &name)
Allocate (idempotently) the per-cell Dirichlet mask + value of a registered scalar.
bool phaseChangeCurvatureDistance() const
bool phaseChangePlaneDirichlet() const
bool phaseChangeCarryConserve() const
void pcRefreshUnits()
Re-derive the INTERNAL phase-change constants (pcRhoG_ ... pcRcpL_) from the caller's physical ones (...
void setPhaseChangeBudget(bool on)
WO-P3f: turn the ENERGY BUDGET instrument on.
PhaseChangeBudget phaseChangeBudgetValues() const
The budget, physical: energies and powers with the consistent transport on, temperature x volume (and...
void setDivergenceSource(const std::vector< double > &v)
A PRESCRIBED extra divergence source (1/time, physical; set_dt must precede it under an extent,...
void setPhaseChangeDepositFallback(bool on)
WO-P3f open item 6 / WO-P3g — the divergence source's 5^3 fallback target, as a setter (it was only r...
bool isCutcellScalar(const std::string &name) const
void setPhaseChangeArea(int mode)
WO-P3c — which geometry the interfacial AREA comes from.
Stats compute(SField c)
Compute the curvature over the inner region from a colour field on the SAME extended block.
void setPhaseRcp(double g, double l)
void extrema(double &lo, double &hi) const
Min/max of the transported temperature over the inner region (a boundedness read-out).
std::function< void(SField)> exchange
Ghost policy for the temperature — the halo/periodic exchange plus the block's non-periodic zero-grad...
void init(const WyAdvector &w, double rcpG, double rcpL)
bool energyMuscl
MinMod-limited donor reconstruction of the face temperature, instead of the DEFAULT plain donor-cell ...
double interfaceEps
The interfacial predicate's wisp threshold.
void init(int nx, int ny, int nz, int ghost)
Stats compute(SField c, int mode)
double pureEps
Purity tolerance of the height-function column walk – the same contract as VofCurvature::pureEps: set...
void init(int nx, int ny, int nz, int ghost)
Stats compute(SField c, int mode)
mode is an InterfaceAreaMode in [kAreaMcColour, kAreaMcPlicSplit].
SField colour() const
Colour field on the extended block (x-fastest).
double wispEps
WO-R2 item 4 — the wisp tolerance on the mixed-cell predicate (wyIsMixed(c, wispEps)),...
double pcGradSolve2(const PcGradFit &f)
The QUADRATIC one-sided fit (WO-P23): T - T_G = G phi + Q phi^2, returning G.
double pcMassFlux(double kg, double gradG, double kl, double gradL, double hlv)
mdot = (k_g dT_g/dn - k_l dT_l/dn)/h_lv with n the PLIC normal (liquid -> gas).
void pcGradAdd(PcGradFit &f, double w, double phi, double T, double Tg)
Add one pure-phase sample at normal distance phi with temperature T.
double pcCentreDistance(double mx, double my, double mz, double alpha)
Signed distance (in cells) from the CELL CENTRE to the PLIC plane, measured along n and POSITIVE ON T...
double pcRegressVolume(double mdot, double area, double dt, double rhoL)
Liquid volume (fraction of a cell) removed from an interfacial cell in one step: dV = mdot A_G dt / r...
bool pcPushWeights(const double n[3], double sgn, const bool avail[3], int step[3], double w[3])
The clip-and-redistribute allocation: which face neighbours a clipped cell's residue is pushed into,...
double pcDivSource(double mdot, double area, double rhoG, double rhoL)
The volumetric divergence source of one interfacial cell (1/s, cell volume = 1): S = mdot A_G (1/rho_...
PcGfmRow pcGfmRow(double theta, bool behind, int order)
double pcCurvedDistance(double phi, double dx, double dy, double dz, const double n[3], double kappa)
WO-P3f — the sample's distance to the CURVED interface, not to the tangent plane.
double pcCarriedValue(double Tg, double grad, double phic)
The plane-anchored (ghost-fluid) interfacial Dirichlet condition (WO-P23, the first item).
bool pcIsInterfacial(double c, double eps)
Is this a cell the regression acts on? The PLIC contract of advect_wy.hpp (wyIsMixed) with an explici...
double pcInterfaceTemperature(double Tsat, double mdot, double Rint)
Interfacial temperature under the Schrage-derived interfacial heat-transfer resistance (Bureš & Sato)...
double pcOffsetDistance(double phic, const double n[3], double dx, double dy, double dz)
Signed distance from the plane of the neighbour at INTEGER cell offset d, given the centre distance p...
double pcGfmThetaKAniso(double phiC, const double n[3], int d, double s, double kappa, double thetaMin, double thetaMax, const VofMetric &g)
The same on an ANISOTROPIC cell (Phase 3, flow/doc/anisotropic_vof.md §7, V5.3).
double pcOperatorMassFlux(double q, double coefSum, double area, double hlv, double Rint)
WO-P3g item 1 — mdot from the ENERGY OPERATOR's own interfacial flux.
double pcPhaseMix(double vg, double vl, double c)
Linear-in-C phase property (k, rho c_p), written so that C = 0 and C = 1 return the endpoint EXACTLY ...
@ kAreaMcColour
the C = 1/2 level set, whole triangles to the centroid's cell
@ kAreaPlic
rung P0/P1: plicArea on the MYC normal. The old numbers.
double plicAreaMetric(double mx, double my, double mz, double alpha, const VofMetric &g)
The PHYSICAL area of the PLIC polygon of an ANISOTROPIC cell, in hRef^2 (flow/doc/anisotropic_vof....
double pcGradSolve(const PcGradFit &f)
The fitted normal derivative dT/dn (0 if the side carried no usable sample).
double pcGradWeight(double dx, double dy, double dz, const double n[3])
Malan's collinearity weight for a sample at cell offset d: w = xi / |d| with xi = (d....
Kokkos::View< double *, CCMem > CCField
Kokkos::DefaultExecutionSpace CCExec
Kokkos::View< const double *, CCMem > CCConst
Kokkos::View< double *, SMem > SField
WO-P3f — the ENERGY BUDGET of the phase-change energy solve.
long nLeaveGas
interfacial -> gas (the bubble swallowing a cell)
double hMasked
the same over MASKED (interfacial) cells
double hOpenNew
the same, after the solve
double qGfm
heat INTO the unmasked set across the plane-anchored rows (W)
double dEoverwrite
sum rcp (dval - T) over masked cells: what the Dirichlet rows inject when they overwrite the transpor...
long nEnterLiquid
liquid -> interfacial transitions this step
double qBehind
WO-P3g: the second-order row's one-sided rescaling of the band BEHIND each interfacial face,...
long calls
how many energy solves have been instrumented
double eLeave
... and of the cells that LEFT it (carrying pcCarriedValue)
double dEoverwriteNew
the part of it on cells that were NOT masked last step
long nEnterGas
gas -> interfacial
double eEnter
sum rcp (T - T_sat) of the cells that JOINED the masked set
double hLiquid
sum rcp (T - T_sat) over PURE LIQUID cells, before the solve
long nMasked
size of the masked set
double hOpen
sum rcp (T - T_sat) over UNMASKED cells, before the solve
long nLeaveLiquid
interfacial -> liquid
long sourceCells
cells that RECEIVED a deposit
long deficitCells
cells that clipped at C = 0
double redistributed
|clip deficit| pushed into neighbours this step
long areaNone
WO-P3c: cells with NO cascade geometry (kept their MYC PLIC area)
double qOperator
WO-P3g: the heat (W) the operator's Dirichlet rows draw across the interface, i.e.
double mdotFit
WO-P3g: the AREA-WEIGHTED one-sided-fit mdot, kept as the diagnostic that item 1 replaced (0 unless t...
double areaOrphan
WO-P3d: area the JOINED sheet booked to cells that are not interfacial, i.e.
long areaPv
WO-P3c: cells whose area came from the PV paraboloid (tier 3)
long fallbackCells
interfacial cells whose +n walk found no pure gas cell
long areaHf
WO-P3c: cells whose area came from a height function (tiers 1/2a)
double sourceSum
sum of the deposited divergence source over inner cells (1/s)
double area
sum of the PLIC polygon areas over interfacial cells (h^2)
double removedVolume
sum of dV actually subtracted this step (+ = evaporated)
double unresolved
clip residue no neighbour could absorb (pushed anyway)
double qOrphan
... the part of it on interfacial cells with NO area, which the regression drops: the ONE non-conserv...
long excessCells
cells that clipped at C = 1
double areaToPhys() const
double powerToPhys() const
vof::VofMetric vofMetric() const
bool physical
an extent was given (the scales are armed)
double vol
V' = h_x' h_y' h_z' (cell volume in hRef^3)
double energyToPhys() const
An internal energy (rho c_p T summed over cells of volume hRef^3) to joules: the internal energy unit...
double curvToPhys() const
double mdotToPhys() const
double latentToInt() const
WO-P3g item 2 — the Gibou–Fedkiw second-order one-sided (ghost-fluid) row.
Accumulator for dT/dn fitted through the interface value: T - T_G = G * phi, so G = sum_j w_j phi_j (...
Per-call census over the inner region (LOCAL; a distributed caller sums it).
double area
the sum this rung exists to get right
static constexpr double U