57#include <Kokkos_Core.hpp>
72 std::fprintf(stderr, "CHECK failed: %s\n at %s:%d\n", #cond, __FILE__, __LINE__); \
77std::size_t idx(
int x,
int y,
int z,
int nx,
int ny) {
78 return (std::size_t)x + (std::size_t)y * nx + (std::size_t)z * (std::size_t)nx * ny;
83 const int n = 8,
g = 3;
84 const I3
e{
n + 2 *
g,
n + 2 *
g,
n + 2 *
g};
85 const long len = (long)
e.x *
e.y *
e.z;
88 peclet::flow::vof::buildOutsideMask(m, e, g, I3{0, 0, 0}, I3{
n,
n,
n},
true,
false,
false);
89 auto h = Kokkos::create_mirror_view(m);
90 Kokkos::deep_copy(h, m);
91 long marked = 0, wrong = 0;
92 for (
int z = 0;
z <
e.z; ++
z)
93 for (
int y = 0;
y <
e.y; ++
y)
94 for (
int x = 0;
x <
e.x; ++
x) {
95 const bool want = (
y -
g < 0 ||
y -
g >=
n) || (z - g < 0 || z - g >= n);
96 const bool got =
h(
L3(x, y, z, e)) != 0
u;
99 marked += got ? 1 : 0;
101 std::printf(
"A outside mask: %ld cells marked, %ld disagreements with the global predicate\n",
108void fluxInertGate() {
109 const int n = 6,
g = 3;
110 const I3
e{
n + 2 *
g,
n + 2 *
g,
n + 2 *
g};
111 const long len = (long)
e.x *
e.y *
e.z;
112 SField c(
"c", len),
mx(
"mx", len), my(
"my", len), mz(
"mz", len), al(
"al", len);
114 auto hc = Kokkos::create_mirror_view(c);
115 auto hm = Kokkos::create_mirror_view(mx);
116 auto hn = Kokkos::create_mirror_view(my);
117 auto ho = Kokkos::create_mirror_view(mz);
118 auto ha = Kokkos::create_mirror_view(al);
119 unsigned seed = 12345u;
120 auto rnd = [&seed]() {
121 seed = seed * 1103515245u + 12345u;
122 return (
double)((seed >> 16) & 0x7fff) / 32767.0;
124 for (
long i = 0;
i < len; ++
i) {
126 const double a0 = rnd() - 0.5,
a1 = rnd() - 0.5,
a2 = rnd() - 0.5;
127 const double s = std::fabs(a0) + std::fabs(a1) + std::fabs(a2) + 1
e-12;
131 ha(i) = peclet::flow::vof::plicAlpha(hm(i), hn(i), ho(i), hc(i));
133 Kokkos::deep_copy(c, hc);
134 Kokkos::deep_copy(mx, hm);
135 Kokkos::deep_copy(my, hn);
136 Kokkos::deep_copy(mz, ho);
137 Kokkos::deep_copy(al, ha);
138 Kokkos::deep_copy(zero, (
unsigned char)0);
141 for (
int d = 0;
d < 3; ++
d) {
142 const long sd =
d == 0 ? 1 : (
d == 1 ?
e.x : (long)
e.x *
e.y);
144 Kokkos::parallel_reduce(
147 {
e.x - 1,
e.y - 1,
e.z - 1}),
148 KOKKOS_LAMBDA(
int x,
int y,
int z,
long& acc) {
149 const long p =
L3(x, y, z, e);
150 for (
int k = -3;
k <= 3; ++
k) {
151 const double a = 0.07 *
k;
162 std::printf(
"B wyFaceFluxBc vs wyFaceFlux with an all-zero mask: %ld bitwise disagreements\n",
174void budgetGate(
int nz,
int nSlug,
int nAfter) {
175 const int nx = 32,
ny = 32;
176 const double W = 1.0,
dt = 0.2;
181 for (
int f = 0;
f < 4; ++
f)
182 s.setDomainBc(f, 1, 0, 0, 0);
183 s.setDomainBc(4, 2, 0.0, 0.0, W);
184 s.setDomainBc(5, 3, 0, 0, 0);
185 s.setPressureGeometry(std::vector<double>((std::size_t)nx * ny * nz, 10.0));
187 s.setVof(std::vector<double>((std::size_t)nx * ny * nz, 0.0));
188 s.setField(
"w", std::vector<double>((std::size_t)nx * ny * nz, W));
189 s.setVofInflow(4, 1.0);
190 s.setVofBackflow(5, 0.0);
192 double budget0 = 0.0, drift = 0.0, minC = 1.0, maxC = 0.0,
ledger = 0.0, peak = 0.0;
193 std::vector<double> peakProfile;
194 for (
int i = 0;
i < nSlug + nAfter; ++
i) {
196 s.setVofInflow(4, 0.0);
198 const auto d =
s.vofDiagnostics();
199 const auto v =
s.vofBcVolumes();
200 for (
int f = 0;
f < 6; ++
f)
202 const double budget =
d.sumC -
ledger;
205 drift = std::fmax(drift, std::fabs(budget - budget0));
206 minC = std::fmin(minC,
d.minC);
207 maxC = std::fmax(maxC,
d.maxC);
210 const auto C =
s.getVof();
211 peakProfile.assign(nz, 0.0);
212 for (
int z = 0;
z < nz; ++
z) {
214 for (
int y = 0;
y <
ny; ++
y)
215 for (
int x = 0;
x < nx; ++
x)
216 a += C[idx(x, y, z, nx, ny)];
217 peakProfile[
z] =
a / (nx *
ny);
222 int plateau = 0, edges = 0;
223 for (
double v : peakProfile) {
230 "C slug at its fullest: sum(C) %.6g over %d full planes + %d partial (expected "
231 "length %.4g cells)\n",
232 peak, plateau, edges, peak / (nx * ny));
234 const auto d =
s.vofDiagnostics();
235 const auto tot =
s.vofBcVolumesTotal();
236 const double injected =
tot[4];
237 const double rel = (injected > 0.0) ? drift / injected : drift;
239 "C budget: injected %.10g, left %.10g, remaining sum(C) %.3e; |budget drift| %.3e "
241 injected, -tot[5],
d.sumC, drift, rel);
242 std::printf(
"C C in [%.3e, %.17g]; max|div(open u)| of the prescribed field %.3e\n", minC, maxC,
243 s.maxOpenDivergence());
245 std::printf(
"C in %.10g vs out %.10g (relative closure %.3e)\n", injected, -tot[5],
246 std::fabs(injected + tot[5]) / injected);
248 CHECK(minC > -1e-12);
249 CHECK(maxC < 1.0 + 1e-12);
250 CHECK(
d.sumC < 1e-9 * injected);
251 CHECK(injected > 0.0);
252 CHECK(std::fabs(injected + tot[5]) / injected < 1e-9);
256void propGhostGate() {
257 const int nx = 8,
ny = 8, nz = 16;
258 const double rhoG = 1.0, rhoL = 1000.0;
263 for (
int f = 0;
f < 4; ++
f)
264 s.setDomainBc(f, 1, 0, 0, 0);
265 s.setDomainBc(4, 2, 0.0, 0.0, 0.5);
266 s.setDomainBc(5, 3, 0, 0, 0);
267 s.setPressureGeometry(std::vector<double>((std::size_t)nx * ny * nz, 10.0));
268 s.setPressureChebyshev(
true, 400, 1e-12);
270 s.setVof(std::vector<double>((std::size_t)nx * ny * nz, 0.0));
272 {rhoG, rhoL - rhoG, 0.0, 0.0});
273 s.setVofInflow(4, 1.0);
276 auto rv =
s.fieldView(
"rho");
277 auto h = Kokkos::create_mirror_view(rv);
278 Kokkos::deep_copy(h, rv);
279 const int G = 2, ex = nx + 2 *
G, ey =
ny + 2 *
G;
280 auto at = [&](
int x,
int y,
int z) {
return h(idx(x, y, z, ex, ey)); };
281 const double ghost = at(
G + nx / 2,
G + ny / 2,
G - 1);
282 const double inner = at(
G + nx / 2,
G + ny / 2,
G);
284 std::printf(
"D inflow -z: rho_ghost %.6g (want %.6g), rho_inner %.6g, rho_face %.6g\n", ghost,
286 CHECK(std::fabs(ghost - rhoL) < 1e-10 * rhoL);
288 std::printf(
"D the Neumann-copy face density would have been %.6g (ratio %.4g)\n", inner,
290 CHECK(face / inner > 100.0);
300 const int n = 8,
g = 3;
301 const I3
e{
n + 2 *
g,
n + 2 *
g,
n + 2 *
g};
302 const long len = (long)
e.x *
e.y *
e.z;
303 for (
int side = 0;
side < 2; ++
side) {
305 Kokkos::deep_copy(c, 1.0);
306 auto hw = Kokkos::create_mirror_view(w);
307 Kokkos::deep_copy(hw, 0.0);
310 const int fa = (
side == 0) ? (g - 1) : (
e.z -
g - 1);
311 for (
int y = 0;
y <
e.y; ++
y)
312 for (
int x = 0;
x <
e.x; ++
x)
313 hw(
L3(x, y, fa, e)) = (x <
e.x / 2) ? -0.3 : +0.3;
314 Kokkos::deep_copy(w, hw);
316 auto hc = Kokkos::create_mirror_view(c);
317 Kokkos::deep_copy(hc, c);
319 const int inwardHalf = (
side == 0) ? 1 : 0;
320 long wrong = 0, layers = 0;
321 const int lo = (
side == 0) ? 0 : (
e.
z -
g);
322 for (
int y = 0;
y <
e.y; ++
y)
323 for (
int x = 0;
x <
e.x; ++
x) {
324 const bool inward = (
x >=
e.x / 2) == (inwardHalf == 1);
325 for (
int k = 0;
k <
g; ++
k) {
326 const double v = hc(
L3(x, y, lo + k, e));
327 if (v != (inward ? 0.0 : 1.0))
329 if (inward && v == 0.0)
334 "E backflow side %d: %ld cells disagree with the rule, %ld ghost cells took the "
335 "backflow colour (all %d layers)\n",
336 side, wrong, layers, g);
338 CHECK(layers == (
long)(
e.x / 2) *
e.y * g);
343void outflowVarGate() {
344 const int n = 8,
g = 2;
346 const long len = (long)
e.x *
e.y *
e.z;
348 auto hp = Kokkos::create_mirror_view(phi);
349 auto hr = Kokkos::create_mirror_view(rho);
350 for (
long i = 0;
i < len; ++
i) {
351 hp(i) = 0.001 * (double)((i * 37) % 101);
354 Kokkos::deep_copy(phi, hp);
355 Kokkos::deep_copy(rho, hr);
356 Kokkos::deep_copy(f0, 0.0);
357 Kokkos::deep_copy(f1, 0.0);
360 auto h0 = Kokkos::create_mirror_view(f0);
361 auto h1 = Kokkos::create_mirror_view(f1);
362 Kokkos::deep_copy(h0, f0);
363 Kokkos::deep_copy(h1, f1);
365 for (
long i = 0;
i < len; ++
i)
366 if (!(h0(i) == h1(i)))
369 "F bcCorrectOutflowVar at uniform rho == rho0: %ld bitwise disagreements with "
370 "bcCorrectOutflow\n",
375 for (
long i = 0;
i < len; ++
i)
377 Kokkos::deep_copy(rho, hr);
378 Kokkos::deep_copy(f1, 0.0);
380 Kokkos::deep_copy(h1, f1);
382 const long sa = (long)
e.x *
e.y;
383 for (
int y = g;
y <
e.y -
g; ++
y)
384 for (
int x = g;
x <
e.x -
g; ++
x) {
385 const long bf = (long)x + (
long)
y *
e.x + (long)(
e.z - g) * sa;
387 worst = std::fmax(worst, std::fabs(h1(bf) / h0(bf) - 1.0 / 1000.0));
389 std::printf(
"F at rho == 1000 rho0 the correction is scaled by 1/1000 to %.3e\n", worst);
390 CHECK(worst < 1e-14);
399double outflowFaceProbe(
bool corr,
double ratio,
double& divOut,
double& uInner,
double& divMut) {
400 const int nx = 32,
ny = 4, nz = 16;
401 const double rhoL = 100.0, rhoG = rhoL /
ratio;
405 s.setDt(ratio > 1.0 ? 0.1 : 0.2);
406 s.setDomainBc(4, 1, 0, 0, 0);
407 s.setDomainBc(5, 1, 0, 0, 0);
408 s.setDomainBc(0, 2, 0.1, 0, 0);
409 s.setDomainBc(1, 3, 0, 0, 0);
410 s.setVelocityIterations(60);
411 s.setPressureLevels(6);
412 s.setPressureGeometry(std::vector<double>((std::size_t)nx * ny * nz, 1e30));
415 std::vector<double>
C((std::size_t)nx * ny * nz, 0.0);
416 for (
int z = 0;
z < nz / 2; ++
z)
417 for (
int y = 0;
y <
ny; ++
y)
418 for (
int x = 0;
x < nx; ++
x)
419 C[idx(x, y, z, nx, ny)] = 1.0;
422 {rhoG, rhoL - rhoG, 0.0, 0.0});
424 s.setPressureFcg(
true, 400, 1e-12);
425 s.setOutflowRhoCorrection(corr);
426 for (
int i = 0;
i < 5; ++
i)
428 auto uv =
s.fieldView(
"u");
429 auto h = Kokkos::create_mirror_view(uv);
430 Kokkos::deep_copy(h, uv);
431 const int G = 2, ex = nx + 2 *
G, ey =
ny + 2 *
G;
432 const double face =
h(idx(
G + nx,
G + ny / 2,
G + nz - 1, ex, ey));
433 uInner =
h(idx(
G + nx - 1,
G + ny / 2,
G + nz - 1, ex, ey));
434 divOut =
s.maxOpenDivergenceProjected();
435 divMut =
s.maxOpenDivergence();
439void outflowReachedGate() {
440 for (
double ratio : {1.0, 10.0}) {
441 double d0 = 0, d1 = 0,
i0 = 0,
i1 = 0, m0 = 0,
m1 = 0;
442 const double on = outflowFaceProbe(
true, ratio, d0, i0, m0);
443 const double off = outflowFaceProbe(
false, ratio, d1, i1, m1);
445 "F2 ratio %-6g outflow face u: with the 1/rho_f factor %.17g, without %.17g "
447 ratio, on, off, on - off);
449 "F2 ratio %-6g last inner u %.17g -> the face is %s the zero-gradient copy; "
450 "max|div| projected %.3e / %.3e, mutating diagnostic %.3e / %.3e\n",
451 ratio, i0, (on == i0) ?
"STILL" :
"NOT",
d0, d1, m0,
m1);
466 CHECK(d1 > 1e3 * d0);
475std::vector<double>
sphereSdf(
int nx,
int ny,
int nz,
const std::vector<Sph>& sp) {
476 std::vector<double>
f((std::size_t)nx * ny * nz, 1e30);
477 for (
int z = 0;
z < nz; ++
z)
478 for (
int y = 0;
y <
ny; ++
y)
479 for (
int x = 0;
x < nx; ++
x) {
481 for (
const Sph& q :
sp) {
482 const double dx =
x + 0.5 -
q.x,
dy =
y + 0.5 -
q.y,
dz =
z + 0.5 -
q.z;
483 d = std::fmin(d, std::sqrt(dx * dx + dy * dy + dz * dz) -
q.r);
485 f[idx(x, y, z, nx, ny)] =
d;
493void composedGate(
bool cutOutlet,
int nSlug,
int nAfter) {
494 const int nx = 20,
ny = 20, nz = 32;
495 const double W = 1.0;
500 for (
int f = 0;
f < 4; ++
f)
501 s.setDomainBc(f, 1, 0, 0, 0);
502 s.setDomainBc(4, 2, 0.0, 0.0, W);
503 s.setDomainBc(5, 3, 0, 0, 0);
505 std::vector<Sph>
sp{{6, 6, 12, 4.0}, {14, 14, 12, 4.0}, {6, 14, 21, 4.0}, {14, 6, 21, 4.0}};
507 sp.push_back({10, 10, (double)nz, 4.5});
509 s.setVelocityIterations(60);
510 s.setPressureLevels(4);
511 s.setPressureIterations(400);
519 s.setPressureExactResidual(
true);
520 for (
int i = 0;
i < 80; ++
i)
522 const double div =
s.maxOpenDivergenceProjected();
523 const long pit =
s.lastPressureIterations();
528 std::vector<double> c0((std::size_t)nx * ny * nz, 0.0);
529 for (
int z = nz / 2;
z < nz; ++
z)
530 for (
int y = 0;
y <
ny; ++
y)
531 for (
int x = 0;
x < nx; ++
x)
532 c0[idx(x, y, z, nx, ny)] = 1.0;
534 s.setVofInflow(4, 1.0);
535 s.setVofBackflow(5, 0.0);
536 const double vol0 =
s.vofDiagnostics().volume;
537 double budget0 = 0.0, drift = 0.0, solidSum = 0.0,
ledger = 0.0, clip = 0.0;
538 double mnF = 1e30, mxF = -1e30;
545 for (
int i = 0;
i < nSlug + nAfter; ++
i) {
546 const double c =
s.vofMaxCourant();
547 dtA = (
c > 0.0) ? std::fmin(0.25, dtA * 0.12 / c) : 0.25;
550 s.setVofInflow(4, 0.0);
552 const auto d =
s.vofDiagnostics();
553 const auto v =
s.vofBcVolumes();
554 for (
int f = 0;
f < 6; ++
f)
556 const double budget =
d.volume -
ledger;
559 drift = std::fmax(drift, std::fabs(budget - budget0));
560 solidSum = std::fmax(solidSum, std::fabs(
d.solidSumC));
561 clip +=
d.clippedVolume;
562 mnF = std::fmin(mnF,
d.minCFluid);
563 mxF = std::fmax(mxF,
d.maxCFluid);
565 const auto d1 =
s.vofDiagnostics();
566 const auto tot =
s.vofBcVolumesTotal();
567 const double injected =
tot[4];
568 const double rel = drift / (vol0 + injected);
570 "G %s: %d solid + %d cut cells, max|div(open u)| %.3e (pressure %ld), final dt %.4g\n"
571 " sum(eps_eff C) %.10g -> %.10g; injected %.10g, LEFT %.10g\n"
572 " |budget drift| %.3e (rel %.3e), max|sum C over solid| %.3e, clipped %.3e,\n"
573 " C over uncut fluid in [%.3e, %.17g]\n",
574 cutOutlet ?
"a sphere CUTS the outlet plane" :
"packing clear of both planes", d1.solidCells,
575 d1.cutCells,
div,
pit, dtA, vol0, d1.volume, injected, -
tot[5], drift,
rel, solidSum, clip,
579 CHECK(solidSum == 0.0);
580 CHECK(injected > 0.0);
581 CHECK(-tot[5] > 0.1 * vol0);
582 CHECK(mnF >= -1e-12);
583 CHECK(mxF <= 1.0 + 1e-12);
585 CHECK(d1.cutCells > 0);
590int main(
int argc,
char** argv) {
591 Kokkos::initialize(argc, argv);
596 outflowReachedGate();
599 const bool quick = std::getenv(
"PECLET_VOF_BC_QUICK") !=
nullptr;
600 budgetGate(quick ? 32 : 64, quick ? 40 : 100, quick ? 200 : 400);
601 composedGate(
false, quick ? 20 : 40, quick ? 120 : 300);
602 composedGate(
true, quick ? 20 : 40, quick ? 120 : 300);
609 std::fprintf(stderr,
"%d failure(s)\n",
failures);
flow — host-facing Kokkos IBM Navier-Stokes solver (drop-in flow-style API).
void zero(const ScalarVec &y)
double wyFaceFluxBc(double a, long p, long sd, int dir, const SF &c, const SF &mx, const SF &my, const SF &mz, const SF &alpha, const MF &outside, double eps=0.0)
WO-R sibling of wyFaceFlux for a face whose DONOR lies outside the global domain.
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 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,...
double wyFaceFlux(double a, long p, long sd, int dir, const SF &c, const SF &mx, const SF &my, const SF &mz, const SF &alpha, double eps=0.0)
Signed Eulerian donor-cell flux through the dir-face between cell p and cell p + sd,...
Kokkos::View< unsigned char *, SMem > UCField
void bcCorrectOutflow(BField f, BField phi, B3 ext, int g, int a, double wa=1.0)
long L3(int x, int y, int z, I3 e)
Kokkos::View< double *, BMem > BField
void bcCorrectOutflowVar(BField f, BField phi, BField rho, double rho0, B3 ext, int g, int a, bool harmonic, double wa=1.0)
Kokkos::DefaultExecutionSpace SExec
Kokkos::View< double *, SMem > SField
static std::vector< double > sphereSdf()
static std::vector< double > ledger(IbmSolver &s)