flow 1.3.0
Kokkos cut-cell IBM incompressible Navier-Stokes solver + pnm pore extraction
Loading...
Searching...
No Matches
property_closures.hpp
Go to the documentation of this file.
1
15#ifndef PECLET_FLOW_PROPERTY_CLOSURES_HPP
16#define PECLET_FLOW_PROPERTY_CLOSURES_HPP
17
18#include <array>
19#include <Kokkos_Core.hpp>
20#include <string>
21#include <vector>
22
23#include "mac_cutcell.hpp"
24#include "policy.hpp"
25
26namespace peclet::flow {
27
28enum class ClosureKind {
29 LinearMix, // out = p0 + p1*in0 + p2*in1
30 BoussinesqForce, // out = p0*p1*p2*(in0 - p3) [rho0, g, beta, T0] -> buoyancy body force
31 ArrheniusMu, // out = p0*exp(p1*(1/in0 - 1/p2)) [mu_ref, B, Tref]
32 Table1D // out = piecewise-linear interp of (tabX, tabY) at in0
33};
34
35// A registered closure. `out`/`in0`/`in1` are resolved from the field registry at registration; a
36// later redistribution that reallocates fields must re-resolve them — which is why the three
37// registry NAMES are kept alongside the resolved handles. Without them a rebalance leaves a
38// closure writing into (and reading from) the previous block's allocation; see
39// `Solver::rebindFieldAliases`.
40struct Closure {
44 std::string outName, in0Name, in1Name; // registry keys, for re-resolution after a redistribute
45 std::array<double, 4> p{{0, 0, 0, 0}};
46 CCField tabX, tabY; // Table1D nodes (ascending tabX)
47 int nTab = 0;
48 // UNITS: the caller's parameters / table values, kept verbatim so a reference scale pinned after
49 // registration re-derives `p` / `tabY` (`Solver::closureRefreshUnits`); `p` and `tabY` hold the
50 // INTERNAL values the kernel uses, identical to these in cell units.
51 std::array<double, 4> pPhys{{0, 0, 0, 0}};
52 std::vector<double> tabYPhys;
53};
54
55// Apply one closure over the inner cells (ghosts untouched — refilled by the field's own exchange).
56inline void applyClosure(const Closure& cl, C3 e, int g) {
58 using MD = MDRange3<CCExec>;
59 const ClosureKind kind = cl.kind;
60 CCField out = cl.out;
61 CCConst in0 = cl.in0, in1 = cl.in1;
62 const double p0 = cl.p[0], p1 = cl.p[1], p2 = cl.p[2], p3 = cl.p[3];
63 const bool haveIn1 = (in1.data() != nullptr) && (in1.extent(0) == out.extent(0));
64 CCConst tx = cl.tabX, ty = cl.tabY;
65 const int nTab = cl.nTab;
66 Kokkos::parallel_for(
67 "peclet::flow::apply_closure", MD(space, {g, g, g}, {e.x - g, e.y - g, e.z - g}),
68 KOKKOS_LAMBDA(int x, int y, int z) {
69 const long i = (long)x + (long)y * e.x + (long)z * (long)e.x * e.y;
70 double v;
71 switch (kind) {
73 v = p0 + p1 * in0(i) + (haveIn1 ? p2 * in1(i) : 0.0);
74 break;
76 v = p0 * p1 * p2 * (in0(i) - p3);
77 break;
79 v = p0 * Kokkos::exp(p1 * (1.0 / in0(i) - 1.0 / p2));
80 break;
81 default: { // Table1D: clamped piecewise-linear interpolation
82 const double s = in0(i);
83 if (nTab <= 0) {
84 v = 0.0;
85 } else if (s <= tx(0)) {
86 v = ty(0);
87 } else if (s >= tx(nTab - 1)) {
88 v = ty(nTab - 1);
89 } else {
90 int lo = 0, hi = nTab - 1; // binary search for the bracketing node
91 while (hi - lo > 1) {
92 const int mid = (lo + hi) / 2;
93 if (tx(mid) <= s)
94 lo = mid;
95 else
96 hi = mid;
97 }
98 const double t = (s - tx(lo)) / (tx(hi) - tx(lo));
99 v = ty(lo) + t * (ty(hi) - ty(lo));
100 }
101 }
102 }
103 out(i) = v;
104 });
105}
106
107// Apply one closure on the GHOST BAND of a single domain face (axis `a`, side `side`), i.e. on the
108// `g` ghost layers outside the boundary — the cells `applyClosure` above deliberately leaves alone.
109//
110// WHY THIS EXISTS (WO-R item 5). The property ghost policy is a Neumann copy of the inner cell
111// (`IbmSolver::fillPropGhosts`). At a wall that is right: the ghost's job is to make the
112// wall-normal derivative vanish. At an INFLOW carrying a different phase it is wrong by up to the
113// density ratio: the face density in the momentum time term and in the projection coefficient at
114// the inlet face is the arithmetic mean of the inner cell and the ghost, so a liquid inlet next to
115// a gas interior would be given a face density half-way between the two phases *of the interior*,
116// not of the incoming fluid. Since rho and mu are CLOSURES of the colour field and the colour
117// ghost now carries the prescribed inflow value, the consistent fix is not a second BC rule but
118// the same closure evaluated at the ghost: rho_ghost = rho(C_inflow), by construction.
119//
120// This is a sibling kernel, not a widened `applyClosure`: the interior kernel is validated and its
121// range is what makes VoF/varRho/porous bit-identical when no VoF inflow colour is set. The caller
122// runs this only on faces that have one (`IbmSolver::vofBcPropGhosts`).
123inline void applyClosureFaceGhost(const Closure& cl, C3 e, int g, int a, int side) {
125 const ClosureKind kind = cl.kind;
126 CCField out = cl.out;
127 CCConst in0 = cl.in0, in1 = cl.in1;
128 const double p0c = cl.p[0], p1c = cl.p[1], p2c = cl.p[2], p3c = cl.p[3];
129 const bool haveIn1 = (in1.data() != nullptr) && (in1.extent(0) == out.extent(0));
130 CCConst tx = cl.tabX, ty = cl.tabY;
131 const int nTab = cl.nTab;
132 const int dims[3] = {e.x, e.y, e.z};
133 const long st[3] = {1, (long)e.x, (long)e.x * e.y};
134 const int b = (a + 1) % 3, c = (a + 2) % 3;
135 const long sa = st[a], sb = st[b], sc = st[c];
136 const int lo = (side == 0) ? 0 : (dims[a] - g);
137 using MD = MDRange2<CCExec>;
138 Kokkos::parallel_for(
139 "peclet::flow::apply_closure_face_ghost", MD(space, {0, 0}, {dims[b], dims[c]}),
140 KOKKOS_LAMBDA(int j0, int j1) {
141 const long base = (long)j0 * sb + (long)j1 * sc;
142 for (int k = 0; k < g; ++k) {
143 const long i = base + (long)(lo + k) * sa;
144 double v;
145 switch (kind) {
147 v = p0c + p1c * in0(i) + (haveIn1 ? p2c * in1(i) : 0.0);
148 break;
150 v = p0c * p1c * p2c * (in0(i) - p3c);
151 break;
153 v = p0c * Kokkos::exp(p1c * (1.0 / in0(i) - 1.0 / p2c));
154 break;
155 default: { // Table1D, same clamped piecewise-linear rule as applyClosure
156 const double sv = in0(i);
157 if (nTab <= 0) {
158 v = 0.0;
159 } else if (sv <= tx(0)) {
160 v = ty(0);
161 } else if (sv >= tx(nTab - 1)) {
162 v = ty(nTab - 1);
163 } else {
164 int l = 0, h = nTab - 1;
165 while (h - l > 1) {
166 const int mid = (l + h) / 2;
167 if (tx(mid) <= sv)
168 l = mid;
169 else
170 h = mid;
171 }
172 const double t = (sv - tx(l)) / (tx(h) - tx(l));
173 v = ty(l) + t * (ty(h) - ty(l));
174 }
175 }
176 }
177 out(i) = v;
178 }
179 });
180}
181
182} // namespace peclet::flow
183
184#endif // PECLET_FLOW_PROPERTY_CLOSURES_HPP
flow — portable (Kokkos) cut-cell pressure-operator face openness from an SDF.
void applyClosureFaceGhost(const Closure &cl, C3 e, int g, int a, int side)
Kokkos::View< double *, CCMem > CCField
Kokkos::DefaultExecutionSpace CCExec
void applyClosure(const Closure &cl, C3 e, int g)
Kokkos::View< const double *, CCMem > CCConst
flow — the multidimensional iteration policies: iteration order follows storage.
std::array< double, 4 > p
std::vector< double > tabYPhys
std::array< double, 4 > pPhys