flow 0.4.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) { return RHO * (1.0 + 0.4 * std::sin(2.0 * M_PI * z / N)); }
55static double epsAt(int, int, int z) { return 0.6 + 0.3 * std::sin(2.0 * M_PI * z / N); }
56static double scalarAt(int x, int y, int z) {
57 auto wrap = [](double d) { return d - N * std::round(d / N); };
58 const double dx = wrap(x - 0.5 * N), dy = wrap(y - 0.5 * N), dz = wrap(z - 0.5 * N);
59 return std::exp(-(dx * dx + dy * dy + dz * dz) / (2.0 * 16.0));
60}
61
62// Extract this block from a global-field function (x-fastest local buffer).
63template <class Fn>
64static std::vector<double> blockOf(Fn f, int ox, int oy, int oz, int lnx, int lny, int lnz) {
65 std::vector<double> v((std::size_t)lnx * lny * lnz);
66 for (int z = 0; z < lnz; ++z)
67 for (int y = 0; y < lny; ++y)
68 for (int x = 0; x < lnx; ++x)
69 v[(std::size_t)x + (std::size_t)y * lnx + (std::size_t)z * lnx * lny] =
70 f(x + ox, y + oy, z + oz);
71 return v;
72}
73
74struct Config {
75 const char* name;
76 bool varMu = false, varRho = false, porous = false, scalar = false, scalarBc = false;
77};
78
79static void configure(IbmSolver& s, const Config& c, const std::vector<double>& lsdf, int ox,
80 int oy, int oz, int lnx, int lny, int lnz) {
81 s.setRho(RHO);
82 s.setMu(MU);
83 s.setDt(DT);
84 s.setBodyForce(F, 0, 0);
85 s.setAdvection(false);
86 s.setVelocityIterations(60);
87 s.setPressureLevels(4);
88 s.setPressurePcg(true, 200, 1e-9);
89 s.setSolid(lsdf, /*cutcell_pressure=*/true);
90 if (c.varMu) {
91 s.setPropertyMode(true, /*harmonic=*/true);
92 s.setField("mu", blockOf(muAt, ox, oy, oz, lnx, lny, lnz));
93 s.exchangeField("mu");
94 }
95 if (c.varRho) {
96 s.setDensityMode(true); // installs the Chebyshev pressure driver by default
97 s.setField("rho", blockOf(rhoAt, ox, oy, oz, lnx, lny, lnz));
98 s.exchangeField("rho");
99 }
100 if (c.porous) {
101 s.setPorousContinuity(true);
102 s.setField("eps", blockOf(epsAt, ox, oy, oz, lnx, lny, lnz));
103 s.exchangeField("eps");
104 }
105 if (c.scalar) {
106 s.addScalar("c", /*D=*/0.05, /*scheme=*/1, /*iters=*/40);
107 s.setField("c", blockOf(scalarAt, ox, oy, oz, lnx, lny, lnz));
108 s.exchangeField("c");
109 }
110 if (c.scalarBc) {
111 // Domain-BC scalar on the periodic flow (conjugated-transport style): hot/cold Dirichlet
112 // x-faces; y/z stay periodic. Drives a conduction+advection profile through the
113 // packing; validates the distributed per-face BC ownership (a rank applies a face's BC iff
114 // its block touches that global face).
115 s.addScalar("c", /*D=*/0.1, /*scheme=*/1, /*iters=*/40);
116 // x faces: the ORB splits x first, so at np>=2 some rank does NOT touch a BC face — the
117 // ownership test (touchesGlobalFace) is genuinely exercised, not trivially true.
118 s.setScalarBc("c", /*face=*/0, /*type=*/2, /*value=*/1.0);
119 s.setScalarBc("c", /*face=*/1, /*type=*/2, /*value=*/0.0);
120 s.exchangeField("c");
121 }
122}
123
124// Gather per-rank inner blocks (x-fastest, with per-rank origins/dims) into the global field on
125// rank 0.
126static std::vector<double> gatherGlobal(const std::vector<double>& local, int ox, int oy, int oz,
127 int lnx, int lny, int lnz, int rank, int size) {
128 std::vector<double> global;
129 if (rank == 0)
130 global.assign((std::size_t)N * N * N, 0.0);
131 for (int r = 0; r < size; ++r) {
132 int meta[6] = {ox, oy, oz, lnx, lny, lnz};
133 if (r == 0) {
134 if (rank == 0)
135 for (int z = 0; z < lnz; ++z)
136 for (int y = 0; y < lny; ++y)
137 std::memcpy(&global[(std::size_t)ox + (std::size_t)(y + oy) * N +
138 (std::size_t)(z + oz) * N * N],
139 &local[(std::size_t)y * lnx + (std::size_t)z * lnx * lny],
140 (std::size_t)lnx * sizeof(double));
141 continue;
142 }
143 if (rank == r) {
144 MPI_Send(meta, 6, MPI_INT, 0, 100 + r, MPI_COMM_WORLD);
145 MPI_Send(local.data(), (int)local.size(), MPI_DOUBLE, 0, 200 + r, MPI_COMM_WORLD);
146 } else if (rank == 0) {
147 MPI_Recv(meta, 6, MPI_INT, r, 100 + r, MPI_COMM_WORLD, MPI_STATUS_IGNORE);
148 std::vector<double> buf((std::size_t)meta[3] * meta[4] * meta[5]);
149 MPI_Recv(buf.data(), (int)buf.size(), MPI_DOUBLE, r, 200 + r, MPI_COMM_WORLD,
150 MPI_STATUS_IGNORE);
151 for (int z = 0; z < meta[5]; ++z)
152 for (int y = 0; y < meta[4]; ++y)
153 std::memcpy(&global[(std::size_t)meta[0] + (std::size_t)(y + meta[1]) * N +
154 (std::size_t)(z + meta[2]) * N * N],
155 &buf[(std::size_t)y * meta[3] + (std::size_t)z * meta[3] * meta[4]],
156 (std::size_t)meta[3] * sizeof(double));
157 }
158 }
159 return global;
160}
161
162// max |a-b| / max|b| over the field.
163static double relErr(const std::vector<double>& a, const std::vector<double>& b) {
164 double md = 0, mb = 0;
165 for (std::size_t i = 0; i < b.size(); ++i) {
166 md = std::max(md, std::fabs(a[i] - b[i]));
167 mb = std::max(mb, std::fabs(b[i]));
168 }
169 return md / (mb + 1e-300);
170}
171
172int main(int argc, char** argv) {
173 MPI_Init(&argc, &argv);
174 Kokkos::initialize(argc, argv);
175 int fail = 0;
176 {
177 int rank = 0, size = 1;
178 MPI_Comm_rank(MPI_COMM_WORLD, &rank);
179 MPI_Comm_size(MPI_COMM_WORLD, &size);
180 const std::vector<double> gsdf = packingSdf();
181
182 peclet::core::decomp::BlockDecomposer<3> dec(static_cast<std::size_t>(size), IVec<3>{N, N, N});
183 auto blk = dec.block(rank);
184 const int ox = (int)blk.origin[0], oy = (int)blk.origin[1], oz = (int)blk.origin[2];
185 const int lnx = (int)blk.size[0], lny = (int)blk.size[1], lnz = (int)blk.size[2];
186 std::vector<double> lsdf((std::size_t)lnx * lny * lnz);
187 for (int z = 0; z < lnz; ++z)
188 for (int y = 0; y < lny; ++y)
189 for (int x = 0; x < lnx; ++x)
190 lsdf[(std::size_t)x + (std::size_t)y * lnx + (std::size_t)z * lnx * lny] =
191 gsdf[(std::size_t)(x + ox) + (std::size_t)(y + oy) * N +
192 (std::size_t)(z + oz) * N * N];
193
194 const Config configs[] = {{"varmu", true, false, false, false, false},
195 {"varrho", false, true, false, false, false},
196 {"porous", false, false, true, false, false},
197 {"scalar", false, false, false, true, false},
198 {"scalarbc", false, false, false, false, true}};
199
200 for (const Config& c : configs) {
201 // --- distributed ---
202 IbmSolver sd(lnx, lny, lnz);
203 sd.initMpi(N, N, N, MPI_COMM_WORLD);
204 configure(sd, c, lsdf, ox, oy, oz, lnx, lny, lnz);
205 for (int it = 0; it < STEPS; ++it)
206 sd.step();
207 auto gu = gatherGlobal(sd.getVelocity(0), ox, oy, oz, lnx, lny, lnz, rank, size);
208 std::vector<double> gc;
209 if (c.scalar || c.scalarBc)
210 gc = gatherGlobal(sd.getField("c"), ox, oy, oz, lnx, lny, lnz, rank, size);
211
212 // --- single-rank reference on rank 0 (full grid, same config) ---
213 double eu = -1, ec = -1;
214 if (rank == 0) {
215 IbmSolver ref(N, N, N);
216 configure(ref, c, gsdf, 0, 0, 0, N, N, N);
217 for (int it = 0; it < STEPS; ++it)
218 ref.step();
219 eu = relErr(gu, ref.getVelocity(0));
220 const bool hasC = c.scalar || c.scalarBc;
221 if (hasC)
222 ec = relErr(gc, ref.getField("c"));
223 const double tol = (size == 1) ? 1e-12 : 5e-5;
224 const bool ok = eu <= tol && (!hasC || ec <= tol);
225 std::printf(" [%-8s np=%d] u rel=%.3e%s tol=%.0e %s\n", c.name, size, eu,
226 hasC ? (std::string(" c rel=") + std::to_string(ec)).c_str() : "", tol,
227 ok ? "OK" : "FAIL");
228 if (!ok)
229 fail = 1;
230 }
231 MPI_Bcast(&fail, 1, MPI_INT, 0, MPI_COMM_WORLD);
232 }
233 if (rank == 0)
234 std::printf("MULTIPHYSICS MPI (np=%d): %s\n", size, fail ? "FAIL" : "PASS");
235 }
236 Kokkos::finalize();
237 MPI_Finalize();
238 return fail;
239}
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)
int main(int argc, char **argv)
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)