flow
Kokkos cut-cell IBM incompressible Navier-Stokes solver + pnm pore extraction
Loading...
Searching...
No Matches
test_redistribute_mpi.cpp
Go to the documentation of this file.
1// Dynamic load balancing: Solver::redistribute moves the distributed solver's state to a NEW ORB
2// partition mid-simulation. Stokes flow through a sphere packing is run distributed on the default
3// (equal-cell) decomposition for half the steps, then REDISTRIBUTED to a weighted decomposition
4// (block boundaries move) and run to completion. The permeability must still match the single-rank
5// reference to the same tolerance as a never-redistributed distributed run — proving the migration
6// (bit-exact field movement) + the rebuild (halo / openness / IBM / MG from the migrated SDF) are
7// correct. Build with -DPECLET_FLOW_MPI.
8#include <mpi.h>
9
10#include <cmath>
11#include <cstdio>
12#include <Kokkos_Core.hpp>
13#include <vector>
14
15#include "flow_ibm.hpp"
16#include "peclet/core/common/types.hpp"
17#include "peclet/core/decomp/block_decomposer.hpp"
18
19using peclet::core::IVec;
20using peclet::core::decomp::BlockDecomposer;
22
23static constexpr int N = 32, STEPS = 120;
24static constexpr double RHO = 1.0, MU = 0.1, F = 1e-3, DT = 60.0;
25
26static std::vector<double> packingSdf(double rfrac = 0.18) {
27 const double R = rfrac * N;
28 std::vector<double> sdf((std::size_t)N * N * N);
29 const double cs[2] = {0.25 * N, 0.75 * N};
30 for (int z = 0; z < N; ++z)
31 for (int y = 0; y < N; ++y)
32 for (int x = 0; x < N; ++x) {
33 double best = 1e30;
34 for (double sx : cs)
35 for (double sy : cs)
36 for (double sz : cs) {
37 auto wrap = [](double d) { return d - N * std::round(d / N); };
38 const double dx = wrap(x - sx), dy = wrap(y - sy), dz = wrap(z - sz);
39 best = std::min(best, std::sqrt(dx * dx + dy * dy + dz * dz) - R);
40 }
41 sdf[(std::size_t)x + (std::size_t)y * N + (std::size_t)z * N * N] = best;
42 }
43 return sdf;
44}
45
46static void configure(IbmSolver& s) {
47 s.setRho(RHO);
48 s.setMu(MU);
49 s.setDt(DT);
50 s.setBodyForce(F, 0, 0);
51 s.setAdvection(false);
52 s.setVelocityIterations(80);
53 // Pure RB-GS pressure (levels=1): decomposition-agnostic, so redistribute onto a WEIGHTED
54 // partition works (the geometric coarse levels assume clean-coarsening; weighted-ORB co-decomp +
55 // multilevel MG is the GraphAMG follow-up).
56 s.setPressureLevels(1);
57 s.setPressurePcg(true, 600, 1e-10);
58}
59
60// local SDF block for a decomposition's block on this rank.
61static std::vector<double> localSdf(const std::vector<double>& gsdf, const BlockDecomposer<3>& dec,
62 int rank) {
63 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 gsdf[(std::size_t)(x + ox) + (std::size_t)(y + oy) * N + (std::size_t)(z + oz) * N * N];
72 return l;
73}
74
75static double localUSum(IbmSolver& s) {
76 auto u = s.getVelocity(0);
77 double sum = 0;
78 for (double v : u)
79 sum += v;
80 return sum;
81}
82
83int main(int argc, char** argv) {
84 MPI_Init(&argc, &argv);
85 Kokkos::initialize(argc, argv);
86 int fail = 0, size = 1, rank = 0;
87 {
88 MPI_Comm_rank(MPI_COMM_WORLD, &rank);
89 MPI_Comm_size(MPI_COMM_WORLD, &size);
90 const std::vector<double> gsdf = packingSdf();
91 const double gcells = (double)N * N * N;
92
93 // D1 = default equal-cell ORB; D2 = weighted ORB (heavy low-x half -> boundaries move).
94 BlockDecomposer<3> D1((std::size_t)size, IVec<3>{N, N, N});
95 std::vector<peclet::core::Real> w((std::size_t)N * N * N, 1.0);
96 for (int z = 0; z < N; ++z)
97 for (int y = 0; y < N; ++y)
98 for (int x = 0; x < N; ++x)
99 if (x < N / 2)
100 w[(std::size_t)x + (std::size_t)y * N + (std::size_t)z * N * N] = 6.0;
101 BlockDecomposer<3> D2((std::size_t)size, IVec<3>{N, N, N}, w);
102
103 // distributed on D1, half the steps, REDISTRIBUTE to D2, finish.
104 auto b1 = D1.block(rank);
105 IbmSolver sd((int)b1.size[0], (int)b1.size[1], (int)b1.size[2]);
106 sd.initMpi(D1, MPI_COMM_WORLD);
107 configure(sd);
108 sd.setSolid(localSdf(gsdf, D1, rank), true);
109 for (int it = 0; it < STEPS / 2; ++it)
110 sd.step();
111 double uPre = 0, uPost = 0;
112 {
113 double l = localUSum(sd);
114 MPI_Allreduce(&l, &uPre, 1, MPI_DOUBLE, MPI_SUM, MPI_COMM_WORLD);
115 }
116 sd.redistribute(D2);
117 {
118 double l = localUSum(sd);
119 MPI_Allreduce(&l, &uPost, 1, MPI_DOUBLE, MPI_SUM, MPI_COMM_WORLD);
120 }
121 if (rank == 0)
122 std::printf(" global-u sum pre=%.12e post=%.12e |d|=%.2e (redistribute data movement)\n",
123 uPre, uPost, std::fabs(uPre - uPost));
124 for (int it = STEPS / 2; it < STEPS; ++it)
125 sd.step();
126 double lsum = localUSum(sd), gsum = 0;
127 MPI_Allreduce(&lsum, &gsum, 1, MPI_DOUBLE, MPI_SUM, MPI_COMM_WORLD);
128 const double k_dist = MU * (gsum / gcells) / F;
129 const double div_dist = sd.maxOpenDivergence();
130
131 // single-rank reference (validated path) on rank 0.
132 double k_ref = 0.0;
133 if (rank == 0) {
134 IbmSolver ref(N, N, N);
135 configure(ref);
136 ref.setSolid(gsdf, true);
137 for (int it = 0; it < STEPS; ++it)
138 ref.step();
139 double rsum = 0;
140 for (double v : ref.getVelocity(0))
141 rsum += v;
142 k_ref = MU * (rsum / gcells) / F;
143 }
144 MPI_Bcast(&k_ref, 1, MPI_DOUBLE, 0, MPI_COMM_WORLD);
145
146 const double reld = std::fabs(k_dist - k_ref) / (std::fabs(k_ref) + 1e-30);
147 const double tol = (size == 1) ? 1e-12 : 2e-5; // np=1 bit-exact; np>1 MG-PCG reduction floor
148 if (rank == 0)
149 std::printf(
150 " k_dist=%.8e k_ref=%.8e rel=%.2e div=%.2e (np=%d, tol %.0e, redistributed)\n",
151 k_dist, k_ref, reld, div_dist, size, tol);
152 if (reld > tol || !(div_dist < 1e-5))
153 fail = 1;
154 }
155 int totalFail = 0;
156 MPI_Allreduce(&fail, &totalFail, 1, MPI_INT, MPI_SUM, MPI_COMM_WORLD);
157 if (rank == 0)
158 std::printf(totalFail == 0 ? "OK (np=%d): mid-run redistribute preserves the solution\n"
159 : "FAILED (np=%d)\n",
160 size);
161 Kokkos::finalize();
162 MPI_Finalize();
163 return totalFail == 0 ? 0 : 1;
164}
flow — host-facing Kokkos IBM Navier-Stokes solver (drop-in flow-style API).
static std::vector< double > localSdf(const std::vector< double > &gsdf, const BlockDecomposer< 3 > &dec, int rank)
int main(int argc, char **argv)
static constexpr double RHO
static constexpr double MU
static double localUSum(IbmSolver &s)
static constexpr int STEPS
static constexpr int N
static constexpr double DT
static constexpr double F
static void configure(IbmSolver &s)
static std::vector< double > packingSdf(double rfrac=0.18)