flow 1.3.0
Kokkos cut-cell IBM incompressible Navier-Stokes solver + pnm pore extraction
Loading...
Searching...
No Matches
test_porous_redistribute_mpi.cpp
Go to the documentation of this file.
1// flow — a mid-run redistribute on the POROUS (volume-averaged) continuity must not look like a
2// jump in the porosity.
3//
4// The porous projection enforces d(eps)/dt + div(eps u) = 0 with d(eps)/dt = (eps^{n+1} -
5// eps^n)/dt, and eps^n (`epsPrev_`) is state that no registry field carries. `redistribute`
6// re-seeds it from the migrated eps^{n+1} (d(eps)/dt = 0 across the move, which leaves the
7// projection RHS as it was). It used to re-seed it inside `resizeForBlock`, BEFORE the migrated
8// fields were scattered into their new buffers -- so whenever a rank's block changed size it copied
9// a freshly zeroed eps, and the first projection after the move saw d(eps)/dt = eps/dt: a CfdDem
10// rebalance at np = 2 and 4 put the pressure off by 2.2e+02 and the velocity by 2.4 (|u| <= 0.07).
11//
12// Here: Stokes flow through a sphere packing with a static, z-varying porosity, run distributed on
13// the equal-cell ORB for half the steps, REDISTRIBUTED onto a weighted ORB (every block changes
14// size at np >= 2) and run to the end. The final velocity must match a full-grid single-rank
15// reference that never redistributed, pointwise, to the solver's reduction floor (np = 1: the
16// weighted "ORB" is the one block, bit for bit). Build with -DPECLET_FLOW_MPI.
17#include <mpi.h>
18
19#include <cmath>
20#include <cstdio>
21#include <cstring>
22#include <Kokkos_Core.hpp>
23#include <vector>
24
25#include "flow_ibm.hpp"
26#include "peclet/core/common/types.hpp"
27#include "peclet/core/decomp/block_decomposer.hpp"
28
29using peclet::core::IVec;
30using peclet::core::decomp::BlockDecomposer;
32
33static constexpr int N = 32, STEPS = 20;
34static constexpr double RHO = 1.0, MU = 0.1, F = 1e-3, DT = 20.0;
35
36static std::vector<double> packingSdf(double rfrac = 0.18) {
37 const double R = rfrac * N;
38 std::vector<double> sdf((std::size_t)N * N * N);
39 const double cs[2] = {0.25 * N, 0.75 * N};
40 for (int z = 0; z < N; ++z)
41 for (int y = 0; y < N; ++y)
42 for (int x = 0; x < N; ++x) {
43 double best = 1e30;
44 for (double sx : cs)
45 for (double sy : cs)
46 for (double sz : cs) {
47 auto wrap = [](double d) { return d - N * std::round(d / N); };
48 const double dx = wrap(x - sx), dy = wrap(y - sy), dz = wrap(z - sz);
49 best = std::min(best, std::sqrt(dx * dx + dy * dy + dz * dz) - R);
50 }
51 sdf[(std::size_t)x + (std::size_t)y * N + (std::size_t)z * N * N] = best;
52 }
53 return sdf;
54}
55
56static double epsAt(int x, int, int z) {
57 return 0.6 + 0.25 * std::sin(2.0 * M_PI * z / N) + 0.05 * std::cos(2.0 * M_PI * x / N);
58}
59
60// This rank's block of a global field (x-fastest local buffer).
61static std::vector<double> blockOf(const std::vector<double>& g, const BlockDecomposer<3>& dec,
62 int rank) {
63 const auto b = dec.block(rank);
64 const int ox = (int)b.origin[0], oy = (int)b.origin[1], oz = (int)b.origin[2];
65 const int lnx = (int)b.size[0], lny = (int)b.size[1], lnz = (int)b.size[2];
66 std::vector<double> l((std::size_t)lnx * lny * lnz);
67 for (int z = 0; z < lnz; ++z)
68 for (int y = 0; y < lny; ++y)
69 for (int x = 0; x < lnx; ++x)
70 l[(std::size_t)x + (std::size_t)y * lnx + (std::size_t)z * lnx * lny] =
71 g[(std::size_t)(x + ox) + (std::size_t)(y + oy) * N + (std::size_t)(z + oz) * N * N];
72 return l;
73}
74
75static void configure(IbmSolver& s, const std::vector<double>& lsdf,
76 const std::vector<double>& leps) {
77 s.setRho(RHO);
78 s.setMu(MU);
79 s.setDt(DT);
80 s.setBodyForce(F, 0, 0);
81 s.setAdvection(false);
84 s.setPressurePcg(true, 200, 1e-10);
85 s.setSolid(lsdf, /*cutcell_pressure=*/true);
86 s.setPorousContinuity(true);
87 s.setField("eps", leps);
88 s.exchangeField("eps");
89 s.syncPorousPrev(); // eps^n = eps^{n+1}: no d(eps)/dt on the first step
90}
91
92// The global field on rank 0 from every rank's inner block of `dec`.
93static std::vector<double> gatherGlobal(const std::vector<double>& local,
94 const BlockDecomposer<3>& dec, int rank, int size) {
95 std::vector<double> global;
96 if (rank == 0)
97 global.assign((std::size_t)N * N * N, 0.0);
98 for (int r = 0; r < size; ++r) {
99 const auto b = dec.block(r);
100 const int ox = (int)b.origin[0], oy = (int)b.origin[1], oz = (int)b.origin[2];
101 const int lnx = (int)b.size[0], lny = (int)b.size[1], lnz = (int)b.size[2];
102 std::vector<double> buf;
103 if (r == rank)
104 buf = local;
105 if (r != 0) {
106 if (rank == r)
107 MPI_Send(buf.data(), (int)buf.size(), MPI_DOUBLE, 0, 300 + r, MPI_COMM_WORLD);
108 else if (rank == 0) {
109 buf.resize((std::size_t)lnx * lny * lnz);
110 MPI_Recv(buf.data(), (int)buf.size(), MPI_DOUBLE, r, 300 + r, MPI_COMM_WORLD,
112 }
113 }
114 if (rank == 0)
115 for (int z = 0; z < lnz; ++z)
116 for (int y = 0; y < lny; ++y)
117 std::memcpy(
118 &global[(std::size_t)ox + (std::size_t)(y + oy) * N + (std::size_t)(z + oz) * N * N],
119 &buf[(std::size_t)y * lnx + (std::size_t)z * lnx * lny],
120 (std::size_t)lnx * sizeof(double));
121 }
122 return global;
123}
124
125int main(int argc, char** argv) {
126 MPI_Init(&argc, &argv);
127 Kokkos::initialize(argc, argv);
128 int fail = 0, size = 1, rank = 0;
129 {
132 const std::vector<double> gsdf = packingSdf();
133 std::vector<double> geps((std::size_t)N * N * N);
134 for (int z = 0; z < N; ++z)
135 for (int y = 0; y < N; ++y)
136 for (int x = 0; x < N; ++x)
137 geps[(std::size_t)x + (std::size_t)y * N + (std::size_t)z * N * N] = epsAt(x, y, z);
138
139 // D1 = the equal-cell ORB; D2 = a weighted ORB (a heavy low-x, low-z corner) whose blocks all
140 // differ in size from D1's at np >= 2 -- the case that reallocates every field buffer.
141 BlockDecomposer<3> D1((std::size_t)size, IVec<3>{N, N, N});
142 std::vector<peclet::core::Real> w((std::size_t)N * N * N, 1.0);
143 for (int z = 0; z < N; ++z)
144 for (int y = 0; y < N; ++y)
145 for (int x = 0; x < N; ++x)
146 if (x < N / 3 || z < N / 4)
147 w[(std::size_t)x + (std::size_t)y * N + (std::size_t)z * N * N] = 5.0;
148 BlockDecomposer<3> D2((std::size_t)size, IVec<3>{N, N, N}, w);
149 int resized = 0;
150 for (int k = 0; k < 3; ++k)
151 resized |= D1.block(rank).size[k] != D2.block(rank).size[k];
152
153 const auto b1 = D1.block(rank);
154 IbmSolver sd((int)b1.size[0], (int)b1.size[1], (int)b1.size[2]);
155 sd.initMpi(D1, MPI_COMM_WORLD);
156 configure(sd, blockOf(gsdf, D1, rank), blockOf(geps, D1, rank));
157 for (int it = 0; it < STEPS / 2; ++it)
158 sd.step();
159 sd.redistribute(D2);
160 for (int it = STEPS / 2; it < STEPS; ++it)
161 sd.step();
162 const std::vector<double> ud = gatherGlobal(sd.getVelocity(0), D2, rank, size);
163 const std::vector<double> wd = gatherGlobal(sd.getVelocity(2), D2, rank, size);
164 int anyResized = 0;
166
167 if (rank == 0) {
168 IbmSolver ref(N, N, N);
169 configure(ref, gsdf, geps);
170 for (int it = 0; it < STEPS; ++it)
171 ref.step();
172 const std::vector<double> ur = ref.getVelocity(0), wr = ref.getVelocity(2);
173 double du = 0, dw = 0, mu = 0;
174 for (std::size_t i = 0; i < ur.size(); ++i) {
175 du = std::max(du, std::fabs(ud[i] - ur[i]));
176 dw = std::max(dw, std::fabs(wd[i] - wr[i]));
177 mu = std::max(mu, std::fabs(ur[i]));
178 }
179 const double rel = std::max(du, dw) / mu;
180 const double tol = (size == 1) ? 0.0 : 1e-6; // np = 1 bit-exact; np > 1 the reduction floor
181 std::printf(
182 " np=%d: blocks resized %s; max|du|=%.3e max|dw|=%.3e (max|u| %.3e) rel=%.2e"
183 " (tol %.0e)\n",
184 size, anyResized ? "yes" : "no", du, dw, mu, rel, tol);
185 if (!(rel <= tol) || (size > 1 && !anyResized))
186 fail = 1;
187 }
188 }
189 int totalFail = 0;
191 if (rank == 0)
192 std::printf(totalFail == 0 ? "OK (np=%d): a porous redistribute leaves the solution unchanged\n"
193 : "FAILED (np=%d)\n",
194 size);
195 Kokkos::finalize();
196 MPI_Finalize();
197 return totalFail == 0 ? 0 : 1;
198}
void setSolid(const std::vector< double > &sdfInner, bool cutcellPressure)
Host entry point: upload the inner SDF once and delegate.
void exchangeField(const std::string &name)
void setAdvection(bool on)
void setVelocityIterations(int it)
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 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).
static void configure(IbmSolver &s, const std::vector< double > &lsdf, const std::vector< double > &leps)
static double epsAt(int x, int, int z)
static constexpr double RHO
static constexpr double MU
static constexpr int STEPS
static constexpr int N
static std::vector< double > blockOf(const std::vector< double > &g, const BlockDecomposer< 3 > &dec, int rank)
static constexpr double DT
static constexpr double F
static std::vector< double > gatherGlobal(const std::vector< double > &local, const BlockDecomposer< 3 > &dec, int rank, int size)
static std::vector< double > packingSdf(double rfrac=0.18)