flow 1.3.0
Kokkos cut-cell IBM incompressible Navier-Stokes solver + pnm pore extraction
Loading...
Searching...
No Matches
test_multiphysics_mpi.cpp
Go to the documentation of this file.
1// flow — multi-rank validation of the MULTIPHYSICS layers on the assembled IbmSolver.
2//
3// The core incompressible solve is MPI-validated (test_sdflow_mpi); the layers on top — variable
4// viscosity (setPropertyMode), variable density (setDensityMode, Chebyshev driver), the porous
5// volume-averaged continuity (setPorousContinuity), and scalar transport (addScalar, periodic) —
6// were structurally MPI-ready but had no multi-rank test. Each config here runs the distributed
7// solver (ORB blocks, initMpi) against a full-grid single-rank reference on rank 0 and compares
8// the final velocity field (and the scalar field) POINTWISE. np=1 must be bit-exact; np>1 to the
9// solver reduction floor (MG-PCG dot-product Allreduce reorder; Chebyshev is reduction-free but
10// shares the residual-norm checks).
11#include <mpi.h>
12
13#include <cmath>
14#include <cstdio>
15#include <cstring>
16#include <Kokkos_Core.hpp>
17#include <string>
18#include <vector>
19
20#include "flow_ibm.hpp"
21#include "peclet/core/common/types.hpp"
22#include "peclet/core/decomp/block_decomposer.hpp"
23
24using peclet::core::IVec;
26
27static constexpr int N = 32, STEPS = 30;
28static constexpr double RHO = 1.0, MU = 0.1, F = 1e-3, DT = 20.0;
29
30static std::vector<double> packingSdf(double rfrac = 0.18) {
31 const double R = rfrac * N;
32 std::vector<double> sdf((std::size_t)N * N * N);
33 const double cs[2] = {0.25 * N, 0.75 * N};
34 for (int z = 0; z < N; ++z)
35 for (int y = 0; y < N; ++y)
36 for (int x = 0; x < N; ++x) {
37 double best = 1e30;
38 for (double sx : cs)
39 for (double sy : cs)
40 for (double sz : cs) {
41 auto wrap = [](double d) { return d - N * std::round(d / N); };
42 const double dx = wrap(x - sx), dy = wrap(y - sy), dz = wrap(z - sz);
43 best = std::min(best, std::sqrt(dx * dx + dy * dy + dz * dz) - R);
44 }
45 sdf[(std::size_t)x + (std::size_t)y * N + (std::size_t)z * N * N] = best;
46 }
47 return sdf;
48}
49
50// Smooth global property fields (periodic-compatible).
51static double muAt(int x, int y, int) {
52 return MU * (1.2 + 0.5 * std::sin(2.0 * M_PI * x / N) * std::cos(2.0 * M_PI * y / N));
53}
54static double rhoAt(int, int, int z) {
55 return RHO * (1.0 + 0.4 * std::sin(2.0 * M_PI * z / N));
56}
57static double epsAt(int, int, int z) {
58 return 0.6 + 0.3 * std::sin(2.0 * M_PI * z / N);
59}
60static double scalarAt(int x, int y, int z) {
61 auto wrap = [](double d) { return d - N * std::round(d / N); };
62 const double dx = wrap(x - 0.5 * N), dy = wrap(y - 0.5 * N), dz = wrap(z - 0.5 * N);
63 return std::exp(-(dx * dx + dy * dy + dz * dz) / (2.0 * 16.0));
64}
65
66// Extract this block from a global-field function (x-fastest local buffer).
67template <class Fn>
68static std::vector<double> blockOf(Fn f, int ox, int oy, int oz, int lnx, int lny, int lnz) {
69 std::vector<double> v((std::size_t)lnx * lny * lnz);
70 for (int z = 0; z < lnz; ++z)
71 for (int y = 0; y < lny; ++y)
72 for (int x = 0; x < lnx; ++x)
73 v[(std::size_t)x + (std::size_t)y * lnx + (std::size_t)z * lnx * lny] =
74 f(x + ox, y + oy, z + oz);
75 return v;
76}
77
78struct Config {
79 const char* name;
80 bool varMu = false, varRho = false, porous = false, scalar = false, scalarBc = false;
81};
82
83static void configure(IbmSolver& s, const Config& c, const std::vector<double>& lsdf, int ox,
84 int oy, int oz, int lnx, int lny, int lnz) {
85 s.setRho(RHO);
86 s.setMu(MU);
87 s.setDt(DT);
88 s.setBodyForce(F, 0, 0);
89 s.setAdvection(false);
92 s.setPressurePcg(true, 200, 1e-9);
93 s.setSolid(lsdf, /*cutcell_pressure=*/true);
94 if (c.varMu) {
95 s.setPropertyMode(true, /*harmonic=*/true);
96 s.setField("mu", blockOf(muAt, ox, oy, oz, lnx, lny, lnz));
97 s.exchangeField("mu");
98 }
99 if (c.varRho) {
100 s.setDensityMode(true); // installs the Chebyshev pressure driver by default
101 s.setField("rho", blockOf(rhoAt, ox, oy, oz, lnx, lny, lnz));
102 s.exchangeField("rho");
103 }
104 if (c.porous) {
105 s.setPorousContinuity(true);
106 s.setField("eps", blockOf(epsAt, ox, oy, oz, lnx, lny, lnz));
107 s.exchangeField("eps");
108 }
109 if (c.scalar) {
110 s.addScalar("c", /*D=*/0.05, /*scheme=*/1, /*iters=*/40);
111 s.setField("c", blockOf(scalarAt, ox, oy, oz, lnx, lny, lnz));
112 s.exchangeField("c");
113 }
114 if (c.scalarBc) {
115 // Domain-BC scalar on the periodic flow (conjugated-transport style): hot/cold Dirichlet
116 // x-faces; y/z stay periodic. Drives a conduction+advection profile through the
117 // packing; validates the distributed per-face BC ownership (a rank applies a face's BC iff
118 // its block touches that global face).
119 s.addScalar("c", /*D=*/0.1, /*scheme=*/1, /*iters=*/40);
120 // x faces: the ORB splits x first, so at np>=2 some rank does NOT touch a BC face — the
121 // ownership test (touchesGlobalFace) is genuinely exercised, not trivially true.
122 s.setScalarBc("c", /*face=*/0, /*type=*/2, /*value=*/1.0);
123 s.setScalarBc("c", /*face=*/1, /*type=*/2, /*value=*/0.0);
124 s.exchangeField("c");
125 }
126}
127
128// Gather per-rank inner blocks (x-fastest, with per-rank origins/dims) into the global field on
129// rank 0.
130static std::vector<double> gatherGlobal(const std::vector<double>& local, int ox, int oy, int oz,
131 int lnx, int lny, int lnz, int rank, int size) {
132 std::vector<double> global;
133 if (rank == 0)
134 global.assign((std::size_t)N * N * N, 0.0);
135 for (int r = 0; r < size; ++r) {
136 int meta[6] = {ox, oy, oz, lnx, lny, lnz};
137 if (r == 0) {
138 if (rank == 0)
139 for (int z = 0; z < lnz; ++z)
140 for (int y = 0; y < lny; ++y)
141 std::memcpy(&global[(std::size_t)ox + (std::size_t)(y + oy) * N +
142 (std::size_t)(z + oz) * N * N],
143 &local[(std::size_t)y * lnx + (std::size_t)z * lnx * lny],
144 (std::size_t)lnx * sizeof(double));
145 continue;
146 }
147 if (rank == r) {
148 MPI_Send(meta, 6, MPI_INT, 0, 100 + r, MPI_COMM_WORLD);
149 MPI_Send(local.data(), (int)local.size(), MPI_DOUBLE, 0, 200 + r, MPI_COMM_WORLD);
150 } else if (rank == 0) {
152 std::vector<double> buf((std::size_t)meta[3] * meta[4] * meta[5]);
153 MPI_Recv(buf.data(), (int)buf.size(), MPI_DOUBLE, r, 200 + r, MPI_COMM_WORLD,
155 for (int z = 0; z < meta[5]; ++z)
156 for (int y = 0; y < meta[4]; ++y)
157 std::memcpy(&global[(std::size_t)meta[0] + (std::size_t)(y + meta[1]) * N +
158 (std::size_t)(z + meta[2]) * N * N],
159 &buf[(std::size_t)y * meta[3] + (std::size_t)z * meta[3] * meta[4]],
160 (std::size_t)meta[3] * sizeof(double));
161 }
162 }
163 return global;
164}
165
166// max |a-b| / max|b| over the field.
167static double relErr(const std::vector<double>& a, const std::vector<double>& b) {
168 double md = 0, mb = 0;
169 for (std::size_t i = 0; i < b.size(); ++i) {
170 md = std::max(md, std::fabs(a[i] - b[i]));
171 mb = std::max(mb, std::fabs(b[i]));
172 }
173 return md / (mb + 1e-300);
174}
175
176int main(int argc, char** argv) {
177 MPI_Init(&argc, &argv);
178 Kokkos::initialize(argc, argv);
179 int fail = 0;
180 {
181 int rank = 0, size = 1;
184 const std::vector<double> gsdf = packingSdf();
185
186 peclet::core::decomp::BlockDecomposer<3> dec(static_cast<std::size_t>(size), IVec<3>{N, N, N});
187 auto blk = dec.block(rank);
188 const int ox = (int)blk.origin[0], oy = (int)blk.origin[1], oz = (int)blk.origin[2];
189 const int lnx = (int)blk.size[0], lny = (int)blk.size[1], lnz = (int)blk.size[2];
190 std::vector<double> lsdf((std::size_t)lnx * lny * lnz);
191 for (int z = 0; z < lnz; ++z)
192 for (int y = 0; y < lny; ++y)
193 for (int x = 0; x < lnx; ++x)
194 lsdf[(std::size_t)x + (std::size_t)y * lnx + (std::size_t)z * lnx * lny] =
195 gsdf[(std::size_t)(x + ox) + (std::size_t)(y + oy) * N +
196 (std::size_t)(z + oz) * N * N];
197
198 const Config configs[] = {{"varmu", true, false, false, false, false},
199 {"varrho", false, true, false, false, false},
200 {"porous", false, false, true, false, false},
201 {"scalar", false, false, false, true, false},
202 {"scalarbc", false, false, false, false, true}};
203
204 for (const Config& c : configs) {
205 // --- distributed ---
207 sd.initMpi(N, N, N, MPI_COMM_WORLD);
208 configure(sd, c, lsdf, ox, oy, oz, lnx, lny, lnz);
209 for (int it = 0; it < STEPS; ++it)
210 sd.step();
211 auto gu = gatherGlobal(sd.getVelocity(0), ox, oy, oz, lnx, lny, lnz, rank, size);
212 std::vector<double> gc;
213 if (c.scalar || c.scalarBc)
214 gc = gatherGlobal(sd.getField("c"), ox, oy, oz, lnx, lny, lnz, rank, size);
215
216 // --- single-rank reference on rank 0 (full grid, same config) ---
217 double eu = -1, ec = -1;
218 if (rank == 0) {
219 IbmSolver ref(N, N, N);
220 configure(ref, c, gsdf, 0, 0, 0, N, N, N);
221 for (int it = 0; it < STEPS; ++it)
222 ref.step();
223 eu = relErr(gu, ref.getVelocity(0));
224 const bool hasC = c.scalar || c.scalarBc;
225 if (hasC)
226 ec = relErr(gc, ref.getField("c"));
227 const double tol = (size == 1) ? 1e-12 : 5e-5;
228 const bool ok = eu <= tol && (!hasC || ec <= tol);
229 std::printf(" [%-8s np=%d] u rel=%.3e%s tol=%.0e %s\n", c.name, size, eu,
230 hasC ? (std::string(" c rel=") + std::to_string(ec)).c_str() : "", tol,
231 ok ? "OK" : "FAIL");
232 if (!ok)
233 fail = 1;
234 }
236 }
237 if (rank == 0)
238 std::printf("MULTIPHYSICS MPI (np=%d): %s\n", size, fail ? "FAIL" : "PASS");
239 }
240 Kokkos::finalize();
241 MPI_Finalize();
242 return fail;
243}
void setSolid(const std::vector< double > &sdfInner, bool cutcellPressure)
Host entry point: upload the inner SDF once and delegate.
std::vector< double > getField(const std::string &name)
void setPropertyMode(bool variable, bool harmonic)
void exchangeField(const std::string &name)
void setAdvection(bool on)
void addScalar(const std::string &name, double D, int scheme, int iters, bool cutcell=false)
void setVelocityIterations(int it)
void setScalarBc(const std::string &name, int face, int type, double value)
void setPressureLevels(int levels)
void setField(const std::string &name, const std::vector< double > &v)
void setBodyForce(double fx, double fy, double fz)
Body force per unit volume, in the caller's units (e.g. a mean pressure gradient, or rho*g).
std::vector< double > getVelocity(int c)
void setPressurePcg(bool on, int maxit, double rtol)
void setDensityMode(bool variable)
void setMu(double m)
Dynamic viscosity, in the caller's units.
void setPorousContinuity(bool on)
void setRho(double r)
Fluid density, in the caller's units.
int main()
Definition dbg_scene.cpp:13
flow — host-facing Kokkos IBM Navier-Stokes solver (drop-in flow-style API).
const char * name
static std::vector< double > blockOf(Fn f, int ox, int oy, int oz, int lnx, int lny, int lnz)
static std::vector< double > gatherGlobal(const std::vector< double > &local, int ox, int oy, int oz, int lnx, int lny, int lnz, int rank, int size)
static constexpr double RHO
static constexpr double MU
static double rhoAt(int, int, int z)
static constexpr int STEPS
static constexpr int N
static constexpr double DT
static double muAt(int x, int y, int)
static constexpr double F
static double scalarAt(int x, int y, int z)
static void configure(IbmSolver &s, const Config &c, const std::vector< double > &lsdf, int ox, int oy, int oz, int lnx, int lny, int lnz)
static std::vector< double > packingSdf(double rfrac=0.18)
static double relErr(const std::vector< double > &a, const std::vector< double > &b)
static double epsAt(int, int, int z)