flow 1.3.0
Kokkos cut-cell IBM incompressible Navier-Stokes solver + pnm pore extraction
Loading...
Searching...
No Matches
test_scalar_cutcell_geometry_mpi.cpp
Go to the documentation of this file.
1// Cut-cell scalar geometry record under MPI (doc/scalar_ibm_design.md §2.4-§2.5, §3; WO-2).
2//
3// np = 1, 2, 4 on the production ORB, two configurations — an off-centre sphere in a periodic box,
4// and a sphere cut by a -x WALL with +-x walls and +-y 'slip' faces (constant extension and mirror
5// of the SDF ghost band):
6// * the §2.5 prerequisite on the MPI path: sdf_ carries BOTH ghost layers after the GridHalo
7// exchange + extendSdfDomainGhosts (every face-slab ghost sample of layers 1 and 2 equals the
8// global SDF at the expected index — the neighbour's, the periodic image, the extension or the
9// mirror);
10// * the record, by global index, is BITWISE equal to a single-rank build of the same global
11// problem: kappa, the three snapped apertures (inner faces and the high face at ext - G), the
12// unknown flags, and every facet (alpha, normal, centroid, body, probe distance, rung,
13// weights, lattice offsets);
14// * the census summed over ranks equals the single-rank census.
15#include <mpi.h>
16
17#include <cmath>
18#include <cstdio>
19#include <cstring>
20#include <Kokkos_Core.hpp>
21#include <map>
22#include <vector>
23
24#include "flow_ibm.hpp"
25#include "peclet/core/decomp/block_decomposer.hpp"
26
28namespace scg = peclet::flow::scg;
29static constexpr int G = IbmSolver::G;
30static constexpr int N = 32;
31
32static int failures = 0;
33#define CHECK(cond) \
34 do { \
35 if (!(cond)) { \
36 std::fprintf(stderr, "CHECK failed: %s\n at %s:%d\n", #cond, __FILE__, __LINE__); \
37 ++failures; \
38 } \
39 } while (0)
40
41static bool same(double a, double b) {
42 return std::memcmp(&a, &b, sizeof(double)) == 0;
43}
44
45static int expectIndex(int i, int n, int bcLo, int bcHi) {
46 if (i >= 0 && i < n)
47 return i;
48 const int t = i < 0 ? bcLo : bcHi;
49 if (t == 0)
50 return ((i % n) + n) % n;
51 if (t == 4)
52 return i < 0 ? -1 - i : 2 * n - 1 - i;
53 return i < 0 ? 0 : n - 1;
54}
55
56static std::vector<double> sphereSdf(const double c[3], double R) {
57 std::vector<double> s((std::size_t)N * N * N);
58 for (int z = 0; z < N; ++z)
59 for (int y = 0; y < N; ++y)
60 for (int x = 0; x < N; ++x) {
61 const double dx = x - c[0], dy = y - c[1], dz = z - c[2];
62 s[(std::size_t)x + (std::size_t)y * N + (std::size_t)z * N * N] =
63 std::sqrt(dx * dx + dy * dy + dz * dz) - R;
64 }
65 return s;
66}
67
68struct Host { // host copies of one record
69 int ex, ey, ez;
70 Kokkos::View<double*, Kokkos::HostSpace> sdf, kappa, sax, say, saz, unknown;
71 std::vector<long> fcell;
72 std::vector<double> fdat; // per facet: alpha, n[3], c[3], sF, w[8] (16)
73 std::vector<int> fint; // per facet: body, rung, off[8] decoded to dx,dy,dz (26)
74 std::map<long, std::vector<long>> byCell; // extended cell index -> facets
75};
76
77static Host hostRecord(IbmSolver& s, int nx, int ny, int nz) {
78 Host h;
79 h.ex = nx + 2 * G;
80 h.ey = ny + 2 * G;
81 h.ez = nz + 2 * G;
82 const auto& r = s.scalarCutGeometry();
83 auto H = Kokkos::HostSpace();
84 h.sdf = Kokkos::create_mirror_view_and_copy(H, s.fieldView("sdf"));
85 h.kappa = Kokkos::create_mirror_view_and_copy(H, r.kappa);
86 h.sax = Kokkos::create_mirror_view_and_copy(H, r.sax);
87 h.say = Kokkos::create_mirror_view_and_copy(H, r.say);
88 h.saz = Kokkos::create_mirror_view_and_copy(H, r.saz);
89 h.unknown = Kokkos::create_mirror_view_and_copy(H, r.unknown);
90 auto cell = Kokkos::create_mirror_view_and_copy(H, r.fac.cell);
91 auto alpha = Kokkos::create_mirror_view_and_copy(H, r.fac.alpha);
92 auto nrm = Kokkos::create_mirror_view_and_copy(H, r.fac.normal);
93 auto cen = Kokkos::create_mirror_view_and_copy(H, r.fac.centroid);
94 auto body = Kokkos::create_mirror_view_and_copy(H, r.fac.body);
95 auto sF = Kokkos::create_mirror_view_and_copy(H, r.fac.sF);
96 auto off = Kokkos::create_mirror_view_and_copy(H, r.fac.offF);
97 auto w = Kokkos::create_mirror_view_and_copy(H, r.fac.wF);
98 auto rung = Kokkos::create_mirror_view_and_copy(H, r.fac.rungF);
99 const long sxy = (long)h.ex * h.ey;
100 for (long f = 0; f < r.fac.n; ++f) {
101 h.fcell.push_back(cell(f));
102 h.byCell[cell(f)].push_back(f);
103 h.fdat.push_back(alpha(f));
104 for (int d = 0; d < 3; ++d)
105 h.fdat.push_back(nrm(f, d));
106 for (int d = 0; d < 3; ++d)
107 h.fdat.push_back(cen(f, d));
108 h.fdat.push_back(sF(f));
109 for (int k = 0; k < 8; ++k)
110 h.fdat.push_back(w(f, k));
111 h.fint.push_back(body(f));
112 h.fint.push_back((int)rung(f));
113 for (int k = 0; k < 8; ++k) { // decode the linear offset (|d| <= 2 < ex/2)
114 const long o = off(f, k);
115 const long dz = std::lround((double)o / (double)sxy);
116 const long rem = o - dz * sxy;
117 const long dy = std::lround((double)rem / (double)h.ex);
118 const long dx = rem - dy * h.ex;
119 h.fint.push_back((int)dx);
120 h.fint.push_back((int)dy);
121 h.fint.push_back((int)dz);
122 }
123 }
124 return h;
125}
126
127static void runCase(const char* label, const double c[3], double R, const int bc[6], int rank,
128 int size) {
129 const std::vector<double> gsdf = sphereSdf(c, R);
130 peclet::core::decomp::BlockDecomposer<3> dec =
131 peclet::flow::CutcellMG::decomposition(static_cast<std::size_t>(size), N, N, N);
132 const auto blk = dec.block(rank);
133 const int ox = (int)blk.origin[0], oy = (int)blk.origin[1], oz = (int)blk.origin[2];
134 const int lnx = (int)blk.size[0], lny = (int)blk.size[1], lnz = (int)blk.size[2];
135 std::vector<double> lsdf((std::size_t)lnx * lny * lnz);
136 for (int z = 0; z < lnz; ++z)
137 for (int y = 0; y < lny; ++y)
138 for (int x = 0; x < lnx; ++x)
139 lsdf[(std::size_t)x + (std::size_t)y * lnx + (std::size_t)z * lnx * lny] =
140 gsdf[(std::size_t)(x + ox) + (std::size_t)(y + oy) * N + (std::size_t)(z + oz) * N * N];
141
142 IbmSolver sd(lnx, lny, lnz);
143 for (int f = 0; f < 6; ++f)
144 if (bc[f] != 0)
145 sd.setDomainBc(f, bc[f], 0.0, 0.0, 0.0);
146 sd.initMpi(N, N, N, MPI_COMM_WORLD);
147 sd.setSolid(lsdf, false);
148 IbmSolver sr(N, N, N); // the single-rank reference of the same global problem
149 for (int f = 0; f < 6; ++f)
150 if (bc[f] != 0)
151 sr.setDomainBc(f, bc[f], 0.0, 0.0, 0.0);
152 sr.setSolid(gsdf, false);
153
154 const Host D = hostRecord(sd, lnx, lny, lnz);
155 const Host Rf = hostRecord(sr, N, N, N);
156 const long dsy = D.ex, dsz = (long)D.ex * D.ey, rsy = Rf.ex, rsz = (long)Rf.ex * Rf.ey;
157 auto gIndex = [&](int lx, int ly, int lz) { // local extended -> reference extended
158 return (long)(lx - G + ox + G) + (long)(ly - G + oy + G) * rsy + (long)(lz - G + oz + G) * rsz;
159 };
160
161 // (1) sdf_ ghost layers 1-2 on every face slab of this rank's block
162 long ghostBad = 0, ghostChecked = 0;
163 const int ln[3] = {lnx, lny, lnz}, org[3] = {ox, oy, oz};
164 for (int a = 0; a < 3; ++a)
165 for (int side = 0; side < 2; ++side)
166 for (int layer = 1; layer <= G; ++layer) {
167 const int b = (a + 1) % 3, cc = (a + 2) % 3;
168 for (int jb = 0; jb < ln[b]; ++jb)
169 for (int jc = 0; jc < ln[cc]; ++jc) {
170 int l3[3];
171 l3[a] = side ? ln[a] - 1 + layer : -layer;
172 l3[b] = jb;
173 l3[cc] = jc;
174 int g3[3];
175 for (int d = 0; d < 3; ++d)
176 g3[d] = expectIndex(l3[d] + org[d], N, bc[2 * d], bc[2 * d + 1]);
177 const double got =
178 D.sdf((long)(l3[0] + G) + (long)(l3[1] + G) * dsy + (long)(l3[2] + G) * dsz);
179 ++ghostChecked;
180 if (!same(
181 got,
182 gsdf[(std::size_t)g3[0] + (std::size_t)g3[1] * N + (std::size_t)g3[2] * N * N]))
183 ++ghostBad;
184 }
185 }
186
187 // (2) per-cell fields, inner cells, plus the high face at ext - G on each axis
188 long cellBad = 0, faceBad = 0, facetBad = 0, facetCount = 0;
189 for (int z = G; z < lnz + G; ++z)
190 for (int y = G; y < lny + G; ++y)
191 for (int x = G; x < lnx + G; ++x) {
192 const long i = x + y * dsy + z * dsz, j = gIndex(x, y, z);
193 if (!same(D.kappa(i), Rf.kappa(j)) || !same(D.unknown(i), Rf.unknown(j)) ||
194 !same(D.sax(i), Rf.sax(j)) || !same(D.say(i), Rf.say(j)) || !same(D.saz(i), Rf.saz(j)))
195 ++cellBad;
196 if (x == lnx + G - 1 && (!same(D.sax(i + 1), Rf.sax(gIndex(x + 1, y, z)))))
197 ++faceBad;
198 if (y == lny + G - 1 && (!same(D.say(i + dsy), Rf.say(gIndex(x, y + 1, z)))))
199 ++faceBad;
200 if (z == lnz + G - 1 && (!same(D.saz(i + dsz), Rf.saz(gIndex(x, y, z + 1)))))
201 ++faceBad;
202 // (3) facets of this cell
203 auto itD = D.byCell.find(i);
204 auto itR = Rf.byCell.find(j);
205 const std::size_t nD = itD == D.byCell.end() ? 0 : itD->second.size();
206 const std::size_t nR = itR == Rf.byCell.end() ? 0 : itR->second.size();
207 if (nD != nR) {
208 ++facetBad;
209 continue;
210 }
211 for (std::size_t k = 0; k < nD; ++k) {
212 const long fd = itD->second[k], fr = itR->second[k];
213 ++facetCount;
214 bool ok = true;
215 for (int q = 0; q < 16; ++q)
216 ok = ok && same(D.fdat[fd * 16 + q], Rf.fdat[fr * 16 + q]);
217 for (int q = 0; q < 26; ++q)
218 ok = ok && D.fint[fd * 26 + q] == Rf.fint[fr * 26 + q];
219 if (!ok)
220 ++facetBad;
221 }
222 }
223
224 long loc[6] = {ghostBad, ghostChecked, cellBad, faceBad, facetBad, facetCount}, glob[6];
225 MPI_Allreduce(loc, glob, 6, MPI_LONG, MPI_SUM, MPI_COMM_WORLD);
226 const auto cD = sd.scalarCutCensus();
227 const auto cR = sr.scalarCutCensus();
228 const bool censusOk = cD.numUnknowns == cR.numUnknowns && cD.numCutCells == cR.numCutCells &&
229 cD.numFacets == cR.numFacets && cD.numTwoSided == cR.numTwoSided &&
230 cD.numThinSolid == cR.numThinSolid && cD.numSealed == cR.numSealed &&
231 cD.rungs[0] == cR.rungs[0] && cD.rungs[1] == cR.rungs[1] &&
232 cD.rungs[2] == cR.rungs[2] && cD.rungs[3] == cR.rungs[3];
233 if (rank == 0)
234 std::printf(
235 " np=%d %-22s: ghosts %ld/%ld stale, cells %ld, high faces %ld, facets %ld/%ld "
236 "differ; census facets %ld (R0 %ld) unknowns %ld sealed %ld vs single-rank %ld "
237 "%ld %ld -> %s\n",
238 size, label, glob[0], glob[1], glob[2], glob[3], glob[4], glob[5], cD.numFacets,
239 cD.rungs[0], cD.numUnknowns, cD.numSealed, cR.numFacets, cR.numUnknowns, cR.numSealed,
240 censusOk ? "equal" : "DIFFER");
241 CHECK(glob[0] == 0 && glob[1] > 0);
242 CHECK(glob[2] == 0 && glob[3] == 0 && glob[4] == 0);
243 CHECK(glob[5] == cR.numFacets);
244 CHECK(censusOk);
245}
246
247int main(int argc, char** argv) {
248 MPI_Init(&argc, &argv);
249 int rank = 0, size = 1;
250 MPI_Comm_rank(MPI_COMM_WORLD, &rank);
251 MPI_Comm_size(MPI_COMM_WORLD, &size);
252 Kokkos::initialize(argc, argv);
253 {
254 const int periodic[6] = {0, 0, 0, 0, 0, 0};
255 const int walls[6] = {1, 1, 4, 4, 0, 0};
256 const double c1[3] = {15.3, 16.1, 14.7}, c2[3] = {2.2, 16.4, 15.6};
257 runCase("periodic sphere", c1, 9.3, periodic, rank, size);
258 runCase("x walls + y slip", c2, 7.7, walls, rank, size);
259 }
260 Kokkos::finalize();
261 int all = 0;
262 MPI_Allreduce(&failures, &all, 1, MPI_INT, MPI_SUM, MPI_COMM_WORLD);
263 if (rank == 0)
264 std::printf(all ? "%d failure(s)\n" : "OK\n", all);
265 MPI_Finalize();
266 return all ? 1 : 0;
267}
void setSolid(const std::vector< double > &sdfInner, bool cutcellPressure)
Host entry point: upload the inner SDF once and delegate.
void setDomainBc(int face, int type, double vx, double vy, double vz)
scg::ScalarCutCensus scalarCutCensus()
static constexpr int G
Definition flow_ibm.hpp:80
int main()
Definition dbg_scene.cpp:13
flow — host-facing Kokkos IBM Navier-Stokes solver (drop-in flow-style API).
Definition jc.py:1
Solver< Staggered > IbmSolver
std::vector< double > fdat
Kokkos::View< double *, Kokkos::HostSpace > saz
std::vector< long > fcell
Kokkos::View< double *, Kokkos::HostSpace > unknown
Kokkos::View< double *, Kokkos::HostSpace > kappa
Kokkos::View< double *, Kokkos::HostSpace > sdf
Kokkos::View< double *, Kokkos::HostSpace > sax
std::vector< int > fint
std::map< long, std::vector< long > > byCell
Kokkos::View< double *, Kokkos::HostSpace > say
static std::vector< double > sphereSdf()
static long l3(int x, int y, int z, C3 e)
Definition test_ibm.cpp:15
static int expectIndex(int i, int n, int bcLo, int bcHi)
static Host hostRecord(IbmSolver &s, int nx, int ny, int nz)
static void runCase(const char *label, const double c[3], double R, const int bc[6], int rank, int size)
static constexpr int N
static constexpr int G
#define CHECK(cond)
static bool same(double a, double b)