flow 1.3.0
Kokkos cut-cell IBM incompressible Navier-Stokes solver + pnm pore extraction
Loading...
Searching...
No Matches
test_sdflow_colocated_mpi.cpp
Go to the documentation of this file.
1// cfd-gpu — the assembled multi-rank COLLOCATED solver step (collocated plan phase 5c).
2//
3// The collocated counterpart of test_sdflow_mpi: solves flow through a periodic
4// 2x2x2 sphere packing with Solver<Colocated> -- creeping (Stokes) under both aperture schemes and
5// then mode 9 with implicit advection -- two ways: single-rank on the full grid, and
6// distributed (each rank constructs the solver with its ORB block dims, calls initMpi, setSolid
7// with its LOCAL SDF block). The collocated approximate (MAC) projection runs multi-rank on exactly
8// the same transport-core halo machinery as the staggered solver: the cell-velocity halo feeds
9// centerToFace, the projected face field is halo-exchanged, the cut-cell pressure MG is MPI-folded.
10// The superficial velocity <u> (hence the permeability k = mu*<u>/F) is reduced over ranks and must
11// equal single-rank: np=1 bit-exact, np>1 to the MG-PCG reduction-order floor (the inner-product
12// Allreduce reorders the Krylov path). Build with -DCFD_MPI.
13#include <mpi.h>
14
15#include <cmath>
16#include <cstdio>
17#include <Kokkos_Core.hpp>
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 = 120;
28// The advecting config marches far fewer steps: at DT = 60 the implicit-FOU momentum solve costs
29// several times a Stokes step (CFL ~ 12), and this config exists to prove the PROJECTED FACE
30// FIELD'S HALO is decomposition-invariant, not to reach a steady state. A handful of steps is
31// enough -- the face field is rebuilt and re-exchanged every one of them, so a wrong ghost shows
32// up on the second. CI caps an MPI ctest at 1200 s; 120 advecting steps does not fit in it.
33static constexpr int STEPS_ADVECT = 20;
34static constexpr double RHO = 1.0, MU = 0.1, F = 1e-3, DT = 60.0;
35
36// global sphere-packing SDF (flat x-fastest, negative inside), 2x2x2 spheres, periodic
37// min-distance.
38static std::vector<double> packingSdf(double rfrac = 0.18) {
39 const double R = rfrac * N;
40 std::vector<double> sdf((std::size_t)N * N * N);
41 const double cs[2] = {0.25 * N, 0.75 * N};
42 for (int z = 0; z < N; ++z)
43 for (int y = 0; y < N; ++y)
44 for (int x = 0; x < N; ++x) {
45 double best = 1e30;
46 for (double sx : cs)
47 for (double sy : cs)
48 for (double sz : cs) {
49 auto wrap = [](double d) { return d - N * std::round(d / N); };
50 const double dx = wrap(x - sx), dy = wrap(y - sy), dz = wrap(z - sz);
51 best = std::min(best, std::sqrt(dx * dx + dy * dy + dz * dz) - R);
52 }
53 sdf[(std::size_t)x + (std::size_t)y * N + (std::size_t)z * N * N] = best;
54 }
55 return sdf;
56}
57
58static void configure(Colo& s, int faceInterp, bool advect) {
59 s.setRho(RHO);
60 s.setMu(MU);
61 s.setDt(DT);
62 s.setBodyForce(F, 0, 0);
63 // ADVECTING CONFIG: the advecting velocity is the PROJECTED face field uf_/vf_/wf_ of the
64 // previous projection (Solver::ufAdvVelocity), read at the control volume's faces -- INCLUDING
65 // the face on the block boundary, whose value comes from the halo exchange in
66 // projectCorrectVelocities. Nothing else in this suite tests that: the Stokes configs have no
67 // advecting velocity at all. So this config is the decomposition-invariance gate for the face
68 // field's ghosts. Implicit FOU (stable at DT=60) additionally puts the SAME velocity in the
69 // momentum OPERATOR, so an inconsistent ghost would show up in the matrix too.
70 s.setAdvection(advect);
71 if (advect)
72 s.setImplicitAdvection(true);
73 s.setVelocityIterations(80);
74 s.setPressureLevels(4);
75 s.setPressurePcg(true, 200, 1e-9);
76 if (faceInterp != 0)
77 s.setFaceInterp(faceInterp); // mode 9: throat-safe cutcell-ghost hybrid (aperture projection
78 // + gpCenterGrad predictor/correction) — local stencils only,
79 // must reproduce single-rank across ranks like mode 0
80}
81
82static double localUSum(Colo& s) {
83 auto u = s.getVelocity(0);
84 double sum = 0;
85 for (double v : u)
86 sum += v;
87 return sum;
88}
89
90int main(int argc, char** argv) {
91 MPI_Init(&argc, &argv);
92 Kokkos::initialize(argc, argv);
93 int fail = 0, size = 1, rank = 0;
94 {
95 MPI_Comm_rank(MPI_COMM_WORLD, &rank);
96 MPI_Comm_size(MPI_COMM_WORLD, &size);
97 const std::vector<double> gsdf = packingSdf();
98 const double gcells = (double)N * N * N;
99
100 // --- distributed solve ---
101 peclet::core::decomp::BlockDecomposer<3> dec(static_cast<std::size_t>(size), IVec<3>{N, N, N});
102 auto blk = dec.block(rank);
103 const int ox = (int)blk.origin[0], oy = (int)blk.origin[1], oz = (int)blk.origin[2];
104 const int lnx = (int)blk.size[0], lny = (int)blk.size[1], lnz = (int)blk.size[2];
105 std::vector<double> lsdf((std::size_t)lnx * lny * lnz);
106 for (int z = 0; z < lnz; ++z)
107 for (int y = 0; y < lny; ++y)
108 for (int x = 0; x < lnx; ++x)
109 lsdf[(std::size_t)x + (std::size_t)y * lnx + (std::size_t)z * lnx * lny] =
110 gsdf[(std::size_t)(x + ox) + (std::size_t)(y + oy) * N +
111 (std::size_t)(z + oz) * N * N];
112
113 // {face-interp mode, advection}: the two Stokes schemes, plus mode 9 (the production
114 // default) under advection.
115 const struct {
116 int mode;
117 bool advect;
118 } CONFIGS[] = {{0, false}, {9, false}, {9, true}};
119 for (const auto& cfg : CONFIGS) {
120 const int mode = cfg.mode;
121 Colo sd(lnx, lny, lnz);
122 sd.initMpi(N, N, N, MPI_COMM_WORLD);
123 configure(sd, mode, cfg.advect);
124 sd.setSolid(lsdf, /*cutcell_pressure=*/true);
125 const int steps = cfg.advect ? STEPS_ADVECT : STEPS;
126 for (int it = 0; it < steps; ++it)
127 sd.step();
128 double lsum = localUSum(sd), gsum = 0;
129 MPI_Allreduce(&lsum, &gsum, 1, MPI_DOUBLE, MPI_SUM, MPI_COMM_WORLD);
130 const double k_dist = MU * (gsum / gcells) / F;
131 const double div_dist = sd.maxOpenDivergence();
132
133 // --- single-rank reference (full grid) on rank 0 ---
134 double k_ref = 0.0;
135 if (rank == 0) {
136 Colo ref(N, N, N);
137 configure(ref, mode, cfg.advect);
138 ref.setSolid(gsdf, true);
139 for (int it = 0; it < steps; ++it)
140 ref.step();
141 double rsum = 0;
142 {
143 auto u = ref.getVelocity(0);
144 for (double v : u)
145 rsum += v;
146 }
147 k_ref = MU * (rsum / gcells) / F;
148 }
149 MPI_Bcast(&k_ref, 1, MPI_DOUBLE, 0, MPI_COMM_WORLD);
150
151 const double reld = std::fabs(k_dist - k_ref) / (std::fabs(k_ref) + 1e-30);
152 const double tol =
153 (size == 1) ? 1e-12 : 2e-5; // np=1 bit-exact; np>1 the MG-PCG reduction-order floor
154 if (rank == 0)
155 std::printf(
156 " [mode %d%s] k_dist=%.8e k_ref=%.8e rel=%.2e div=%.2e (np=%d, tol %.0e)\n", mode,
157 cfg.advect ? " advect" : "", k_dist, k_ref, reld, div_dist, size, tol);
158 if (reld > tol || !(div_dist < 1e-5))
159 fail = 1;
160 }
161 }
162 int totalFail = 0;
163 MPI_Allreduce(&fail, &totalFail, 1, MPI_INT, MPI_SUM, MPI_COMM_WORLD);
164 if (rank == 0) {
165 if (totalFail == 0)
166 std::printf("OK (np=%d): distributed collocated <u> == single-rank, every config\n", size);
167 else
168 std::fprintf(stderr, "FAILED (np=%d)\n", size);
169 }
170 Kokkos::finalize();
171 MPI_Finalize();
172 return totalFail == 0 ? 0 : 1;
173}
void setSolid(const std::vector< double > &sdfInner, bool cutcellPressure)
Host entry point: upload the inner SDF once and delegate.
int main()
Definition dbg_scene.cpp:13
flow — host-facing Kokkos IBM Navier-Stokes solver (drop-in flow-style API).
static constexpr int STEPS_ADVECT
static constexpr double RHO
static constexpr double MU
static constexpr int STEPS
static constexpr int N
static void configure(Colo &s, int faceInterp, bool advect)
static constexpr double DT
static constexpr double F
static double localUSum(Colo &s)
static std::vector< double > packingSdf(double rfrac=0.18)