flow 1.3.0
Kokkos cut-cell IBM incompressible Navier-Stokes solver + pnm pore extraction
Loading...
Searching...
No Matches
interface_area_field.hpp
Go to the documentation of this file.
1
22#ifndef PECLET_FLOW_VOF_INTERFACE_AREA_FIELD_HPP
23#define PECLET_FLOW_VOF_INTERFACE_AREA_FIELD_HPP
24
25#include <Kokkos_Core.hpp>
26#include <stdexcept>
27
28#include "mac_stencils.hpp"
29#include "policy.hpp"
30#include "vof/advect_wy.hpp"
31#include "vof/curvature.hpp"
32#include "vof/curvature_field.hpp" // vofIsInterface
34
35namespace peclet::flow::vof {
36
39 public:
41 struct Stats {
42 long interfacial = 0;
43 long hf = 0;
44 long hfMixed = 0;
45 long pv = 0;
46 long noEstimate = 0;
47 double area = 0.0;
48 };
49
50 void init(int nx, int ny, int nz, int ghost) {
51 if (ghost < kHfColumn / 2)
52 throw std::invalid_argument("peclet::flow::vof::VofInterfaceArea: ghost width must be >= 3");
53 if (nx < 1 || ny < 1 || nz < 1)
54 throw std::invalid_argument("peclet::flow::vof::VofInterfaceArea: empty block");
55 n_ = I3{nx, ny, nz};
56 g_ = ghost;
57 e_ = I3{nx + 2 * ghost, ny + 2 * ghost, nz + 2 * ghost};
58 len_ = static_cast<long>(e_.x) * e_.y * e_.z;
59 mx_ = SField("vof::area::mx", len_);
60 my_ = SField("vof::area::my", len_);
61 mz_ = SField("vof::area::mz", len_);
62 alpha_ = SField("vof::area::alpha", len_);
63 area_ = SField("vof::area::A", len_);
64 branch_ = SField("vof::area::branch", len_);
65 }
66
67 bool ready() const { return area_.extent(0) != 0; }
68 SField area() const { return area_; }
69 SField branch() const { return branch_; }
70
71 // ---- tunables: the SAME knobs and defaults the curvature cascade runs with ----------------
73 VofMetric metric;
75 double monoTol = 1e-6;
80 double pureEps = 0.0;
81 double cosMin = 0.2;
85 double interfaceEps = 0.0;
86
87 Stats compute(SField c, int mode) {
88 if (!ready())
89 throw std::runtime_error("peclet::flow::vof::VofInterfaceArea::compute: init() not called");
91 heightPass(c, mode);
92 fallbackPass(c, mode);
93 return census();
94 }
95
96 // public for the same nvcc reason `VofCurvature`'s passes are.
98 const I3 e = e_, n = n_;
99 const int g = g_, gr = kPvHalf;
100 const long sy = e_.x, sz = static_cast<long>(e_.x) * e_.y;
101 SField mx = mx_, my = my_, mz = mz_, al = alpha_;
102 const double ieps = interfaceEps;
103 Kokkos::parallel_for(
104 "vof::area::planes",
105 MDRange3<SExec>(SExec(), {g - gr, g - gr, g - gr},
106 {g + n.x + gr, g + n.y + gr, g + n.z + gr}),
107 KOKKOS_LAMBDA(int x, int y, int z) {
108 const long i = L3(x, y, z, e);
109 if (!vofIsInterface(c(i), ieps)) {
110 mx(i) = 0.0;
111 my(i) = 0.0;
112 mz(i) = 0.0;
113 al(i) = 0.0;
114 return;
115 }
116 wyReconstructCell(c, i, sy, sz, mx, my, mz, al);
117 });
118 Kokkos::fence();
119 }
120
125 void heightPass(SField c, int mode) {
126 const I3 e = e_, n = n_;
127 const int g = g_;
128 const long st[3] = {1, e_.x, static_cast<long>(e_.x) * e_.y};
129 SField mx = mx_, my = my_, mz = mz_, al = alpha_, ar = area_, br = branch_;
130 const double mtol = monoTol, ieps = interfaceEps;
131 const double peps = pureEps;
132 const VofMetric gm = metric; // `g` is the ghost width in this scope
133 const int md = mode;
134 Kokkos::parallel_for(
135 "vof::area::hf", MDRange3<SExec>(SExec(), {g, g, g}, {g + n.x, g + n.y, g + n.z}),
136 KOKKOS_LAMBDA(int x, int y, int z) {
137 const long i = L3(x, y, z, e);
138 ar(i) = 0.0;
139 if (!vofIsInterface(c(i), ieps)) {
140 br(i) = static_cast<double>(kCurvNone);
141 return;
142 }
143 const double am[3] = {Kokkos::fabs(mx(i)), Kokkos::fabs(my(i)), Kokkos::fabs(mz(i))};
144 int ord[3] = {0, 1, 2};
145 for (int p = 1; p < 3; ++p)
146 for (int q = p; q > 0 && am[ord[q]] > am[ord[q - 1]]; --q) {
147 const int t = ord[q];
148 ord[q] = ord[q - 1];
149 ord[q - 1] = t;
150 }
151 for (int t = 0; t < 3; ++t) {
152 const int d = ord[t];
153 const int d1 = (d + 1) % 3, d2 = (d + 2) % 3;
154 const long sd = st[d], s1 = st[d1], s2 = st[d2];
155 double hh[9];
156 int orient0 = 0;
157 bool ok = true;
158 for (int q = 0; q < 3 && ok; ++q)
159 for (int p = 0; p < 3 && ok; ++p) {
160 const long base = i + (p - 1) * s1 + (q - 1) * s2;
161 double col[kHfColumn];
162 for (int k = 0; k < kHfColumn; ++k)
163 col[k] = c(base + (k - kHfColumn / 2) * sd);
164 double h;
165 int orient;
166 if (!hfColumnHeight(col, kHfColumn, h, orient, mtol, peps)) {
167 ok = false;
168 break;
169 }
170 if (orient0 == 0)
171 orient0 = orient;
172 else if (orient != orient0) {
173 ok = false;
174 break;
175 }
176 hh[p + 3 * q] = h;
177 }
178 if (!ok)
179 continue;
180 if (md == kAreaFootprint) {
181 ar(i) = hfFootprintArea(hh, gm, d);
182 } else {
183 double ns[3];
184 hfSurfaceNormal(hh, d, gm, ns);
185 ar(i) = interfaceAreaFromNormal(md, mx(i), my(i), mz(i), al(i), c(i), ns, gm);
186 }
187 br(i) = static_cast<double>(t == 0 ? kCurvHf : kCurvHfMixed);
188 return;
189 }
190 br(i) = -1.0;
191 });
192 Kokkos::fence();
193 }
194
197 void fallbackPass(SField c, int mode) {
198 const I3 e = e_, n = n_;
199 const int g = g_, gr = kPvHalf;
200 SField mx = mx_, my = my_, mz = mz_, al = alpha_, ar = area_, br = branch_;
201 const double dW = weightWidth * metric.maxH(), cmin = cosMin, ieps = interfaceEps;
202 const VofMetric gm = metric; // `g` is the ghost width in this scope
203 const int md = mode;
204 Kokkos::parallel_for(
205 "vof::area::pv", MDRange3<SExec>(SExec(), {g, g, g}, {g + n.x, g + n.y, g + n.z}),
206 KOKKOS_LAMBDA(int x, int y, int z) {
207 const long i = L3(x, y, z, e);
208 if (br(i) >= 0.0)
209 return;
210 const double m0 = mx(i), m1 = my(i), m2 = mz(i);
211 const double mi[3] = {m0, m1, m2};
212 double nn[3] = {0.0, 0.0, 0.0};
213 // PHYSICAL frame (Phase 3, V2.4); identity at the unit metric.
214 if (!(vofPhysNormalInv(mi, gm, nn) > 0.0)) {
215 ar(i) = 0.0;
216 br(i) = static_cast<double>(kCurvNoEstimate);
217 return;
218 }
219 double t1[3], t2[3];
220 curvFrame(nn, t1, t2);
221 double v[8][3], ctr[3], area;
222 const int nv = plicPolygon(m0, m1, m2, al(i), v);
224 const double org[3] = {ctr[0] - 0.5, ctr[1] - 0.5, ctr[2] - 0.5};
225 PvFit fit;
226 pvFitInit(fit);
227 for (int oz = -gr; oz <= gr; ++oz)
228 for (int oy = -gr; oy <= gr; ++oy)
229 for (int ox = -gr; ox <= gr; ++ox) {
230 const long j = L3(x + ox, y + oy, z + oz, e);
231 if (!vofIsInterface(c(j), ieps))
232 continue;
233 const double off[3] = {static_cast<double>(ox), static_cast<double>(oy),
234 static_cast<double>(oz)};
235 pvFitAdd(fit, mx(j), my(j), mz(j), al(j), off, org, t1, t2, nn, dW, cmin, gm);
236 }
237 double a[6];
238 bool red = false;
239 if (!pvFitSolve(fit, a, red)) {
240 // no cascade geometry at all: keep the MYC PLIC area rather than emit a zero. Loud in
241 // the census, exactly as `kCurvNoEstimate` is in the curvature.
242 ar(i) = plicAreaMetric(m0, m1, m2, al(i), gm);
243 br(i) = static_cast<double>(kCurvNoEstimate);
244 return;
245 }
246 double ns[3];
247 pvSurfaceNormal(a, t1, t2, nn, ns);
248 // The PV branch has no column and therefore no tiling footprint of its own; under
249 // `kAreaFootprint` it takes variant B (the plane rebuilt on the paraboloid's normal),
250 // which is the closest thing to it that a single cell can supply. The branch census
251 // says how much of the total that is.
253 al(i), c(i), ns, gm);
254 br(i) = static_cast<double>(red ? kCurvPvReduced : kCurvPv);
255 });
256 Kokkos::fence();
257 }
258
259 Stats census() const {
260 const I3 e = e_, n = n_;
261 const int g = g_;
262 SField br = branch_, ar = area_;
263 Stats s;
264 Kokkos::parallel_reduce(
265 "vof::area::census", MDRange3<SExec>(SExec(), {g, g, g}, {g + n.x, g + n.y, g + n.z}),
266 KOKKOS_LAMBDA(int x, int y, int z, long& ni, long& n1, long& n2, long& n3, long& n4,
267 double& acc) {
268 const long i = L3(x, y, z, e);
269 const int b = static_cast<int>(br(i));
270 if (b == kCurvNone)
271 return;
272 ++ni;
273 acc += ar(i);
274 if (b == kCurvHf)
275 ++n1;
276 else if (b == kCurvHfMixed)
277 ++n2;
278 else if (b == kCurvPv || b == kCurvPvReduced)
279 ++n3;
280 else
281 ++n4;
282 },
283 s.interfacial, s.hf, s.hfMixed, s.pv, s.noEstimate, s.area);
284 Kokkos::fence();
285 return s;
286 }
287
288 private:
289 I3 n_{0, 0, 0}, e_{0, 0, 0};
290 int g_ = 0;
291 long len_ = 0;
292 SField mx_, my_, mz_, alpha_, area_, branch_;
293};
294
295} // namespace peclet::flow::vof
296
297#endif // PECLET_FLOW_VOF_INTERFACE_AREA_FIELD_HPP
flow — VoF rung V1: Weymouth & Yue (2010) directionally-split, exactly volume-conserving geometric ad...
Interfacial area per cell from the V3 curvature cascade's own geometry.
void fallbackPass(SField c, int mode)
Tier 3: the PLIC-volumetric paraboloid fit, whose GRADIENT at the origin is the accurate normal here ...
double interfaceEps
The interfacial predicate's wisp threshold.
VofMetric metric
The anisotropic cell metric (Phase 3); {1,1,1} == the pre-Phase-3 arithmetic.
void init(int nx, int ny, int nz, int ghost)
void heightPass(SField c, int mode)
Tiers 1 and 2a: the height function in the preferred direction, then the other two,...
double pureEps
Purity tolerance of the height-function column walk – the same contract as VofCurvature::pureEps: set...
flow — thin shim: the container-free VoF height-function / paraboloid curvature kernels (rung V3) now...
flow — VoF rung V3 (WO-O): the view-level driver for the curvature cascade.
flow — WO-P3c: the interfacial AREA taken from the CURVATURE cascade's geometry.
flow — portable (Kokkos) MAC stencil operators: Red-Black Gauss-Seidel smoothers + divergence.
bool vofIsInterface(double c, double eps)
wyIsMixed with a wisp threshold: the cell carries an interface only while eps < C < 1 - eps.
void wyReconstructCell(const SF &c, long i, long sy, long sz, SF mx, SF my, SF mz, SF alpha)
PLIC reconstruction of one cell: MYC normal from the 3^3 colour stencil + the analytic plane offset.
@ kAreaFootprint
variant C: the height function's OWN footprint x its own metric.
@ kAreaNormal
variant B: plicArea rebuilt on the cascade's normal.
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....
void pvSurfaceNormal(const double a[6], const double t1[3], const double t2[3], const double nn[3], double m[3])
The unit normal of the fitted paraboloid z = a0 + a1 x + a2 y + ... AT THE ORIGIN of its own frame (t...
double interfaceAreaFromNormal(int mode, double mx, double my, double mz, double alpha, double c, const double ns[3], const VofMetric &g)
The projection axis of kAreaMetric is the one ns is most aligned with — the column direction on tiers...
double hfFootprintArea(const double h[9])
Variant C — the height function's own footprint times its own metric (kAreaFootprint).
void hfSurfaceNormal(const double h[9], int d, double m[3])
The unit surface normal implied by the SAME 3x3 patch of heights, in world components.
long L3(int x, int y, int z, I3 e)
Kokkos::DefaultExecutionSpace SExec
Kokkos::View< double *, SMem > SField
flow — the multidimensional iteration policies: iteration order follows storage.
Per-call census over the inner region (LOCAL; a distributed caller sums it).
long hf
tier 1: HF in the preferred (largest |n_d|) direction
long pv
tier 3: PV paraboloid fit (full or reduced model)
long hfMixed
tier 2a: HF in one of the other two directions
long noEstimate
no cascade geometry at all — the cell keeps its MYC PLIC area
double area
the sum this rung exists to get right