flow
Kokkos cut-cell IBM incompressible Navier-Stokes solver + pnm pore extraction
Loading...
Searching...
No Matches
test_distributed_pressure.cpp
Go to the documentation of this file.
1// cfd-gpu — distributed (MPI) cut-cell pressure Poisson + the distributed reductions.
2//
3// Solves the (singular, periodic) cut-cell pressure system A phi = b, A = -div(open grad), by
4// Conjugate Gradients with the field ORB-decomposed over MPI ranks. This exercises the two pieces
5// the multi-rank sdflow pressure solve needs beyond the grid halo: (1) the halo'd matvec
6// (GridHalo.exchange before applyCutcellOp) and (2) the GLOBAL reductions -- every dot product, the
7// residual max, and the constant-null-space mean removal are a local Kokkos parallel_reduce
8// followed by MPI_Allreduce. The CG with distributed reductions is algebraically identical to
9// single-rank CG (same operator, same global inner products), so a fixed iteration count must match
10// a serial reference to ~1e-12. Validates the distributed Krylov + reduction pattern the CutcellMG
11// / MG-PCG / Chebyshev pressure drivers will use. Any backend.
12#include <mpi.h>
13
14#include <array>
15#include <cmath>
16#include <cstdio>
17#include <Kokkos_Core.hpp>
18
19#include "mac_pressure.hpp" // buildCutcellOp, applyCutcellOp, CCField, C3
20#include "peclet/core/common/types.hpp"
21#include "peclet/core/common/view.hpp"
22#include "peclet/core/decomp/block_decomposer.hpp"
23#include "peclet/core/halo/grid_halo.hpp"
24#include "peclet/core/halo/grid_halo_topology.hpp"
25
26using peclet::core::Index;
27using peclet::core::IVec;
28using peclet::core::decomp::BlockDecomposer;
29using peclet::core::halo::GridHalo;
30using peclet::core::halo::GridHaloTopology;
35using OpV = Kokkos::View<double*, peclet::flow::CCMem>;
36
37static constexpr int kDim = 3, G = 1, CGIT = 40;
38
39static double source(int gx, int gy, int gz, IVec<kDim> gs) {
40 return std::sin(2.0 * M_PI * gx / gs[0]) +
41 std::cos(4.0 * M_PI * gy / gs[1]) * std::sin(2.0 * M_PI * gz / gs[2]);
42}
43
44// ---- distributed reductions over inner cells (local Kokkos reduce + MPI_Allreduce) ----
45struct Reduce {
47 MPI_Comm comm;
48 double dot(CCConst a, CCConst b) const {
49 CCExec sp;
50 double s = 0;
51 C3 ee = e;
52 Kokkos::parallel_reduce(
53 "dot",
54 Kokkos::MDRangePolicy<CCExec, Kokkos::Rank<3>>(sp, {G, G, G},
55 {ee.x - G, ee.y - G, ee.z - G}),
56 KOKKOS_LAMBDA(int x, int y, int z, double& acc) {
57 const long i = (long)x + (long)y * ee.x + (long)z * (long)ee.x * ee.y;
58 acc += a(i) * b(i);
59 },
60 s);
61 double g = 0;
62 MPI_Allreduce(&s, &g, 1, MPI_DOUBLE, MPI_SUM, comm);
63 return g;
64 }
65 double maxabs(CCConst a) const {
66 CCExec sp;
67 double m = 0;
68 C3 ee = e;
69 Kokkos::parallel_reduce(
70 "max",
71 Kokkos::MDRangePolicy<CCExec, Kokkos::Rank<3>>(sp, {G, G, G},
72 {ee.x - G, ee.y - G, ee.z - G}),
73 KOKKOS_LAMBDA(int x, int y, int z, double& acc) {
74 const long i = (long)x + (long)y * ee.x + (long)z * (long)ee.x * ee.y;
75 const double v = Kokkos::fabs(a(i));
76 if (v > acc)
77 acc = v;
78 },
79 Kokkos::Max<double>(m));
80 double g = 0;
81 MPI_Allreduce(&m, &g, 1, MPI_DOUBLE, MPI_MAX, comm);
82 return g;
83 }
84 void removeMean(CCField f, long globalCells) const {
85 CCExec sp;
86 double s = 0;
87 C3 ee = e;
88 Kokkos::parallel_reduce(
89 "meansum",
90 Kokkos::MDRangePolicy<CCExec, Kokkos::Rank<3>>(sp, {G, G, G},
91 {ee.x - G, ee.y - G, ee.z - G}),
92 KOKKOS_LAMBDA(int x, int y, int z, double& acc) {
93 const long i = (long)x + (long)y * ee.x + (long)z * (long)ee.x * ee.y;
94 acc += f(i);
95 },
96 s);
97 double g = 0;
98 MPI_Allreduce(&s, &g, 1, MPI_DOUBLE, MPI_SUM, comm);
99 const double mean = g / (double)globalCells;
100 CCField ff = f;
101 Kokkos::parallel_for(
102 "meansub",
103 Kokkos::MDRangePolicy<CCExec, Kokkos::Rank<3>>(sp, {G, G, G},
104 {ee.x - G, ee.y - G, ee.z - G}),
105 KOKKOS_LAMBDA(int x, int y, int z) {
106 const long i = (long)x + (long)y * ee.x + (long)z * (long)ee.x * ee.y;
107 ff(i) -= mean;
108 });
109 sp.fence();
110 }
111};
112
113static void axpy(CCField y, double a, CCConst x) { // y += a x
114 CCExec sp;
115 CCField yy = y;
116 std::size_t n = y.extent(0);
117 Kokkos::parallel_for(
118 "axpy", Kokkos::RangePolicy<CCExec>(sp, 0, n),
119 KOKKOS_LAMBDA(std::size_t i) { yy(i) += a * x(i); });
120 sp.fence();
121}
122static void aypx(CCField y, double a, CCConst x) { // y = x + a y
123 CCExec sp;
124 CCField yy = y;
125 std::size_t n = y.extent(0);
126 Kokkos::parallel_for(
127 "aypx", Kokkos::RangePolicy<CCExec>(sp, 0, n),
128 KOKKOS_LAMBDA(std::size_t i) { yy(i) = x(i) + a * yy(i); });
129 sp.fence();
130}
131
132// Conjugate Gradients (mean-removed, singular periodic operator). exchange() supplies the matvec
133// ghosts.
134template <class Exch>
135static void cg(CCField x, CCConst b, OpV AC, OpV AW, OpV AE, OpV AS, OpV AN, OpV AB, OpV AT, C3 e,
136 Exch&& exchange, const Reduce& red, long gCells, int iters) {
137 const std::size_t n = x.extent(0);
138 CCField r("r", n), p("p", n), Ap("Ap", n);
139 auto matvec = [&](CCField y, CCField v) {
140 exchange(v);
141 applyCutcellOp(y, CCConst(v), AC, AW, AE, AS, AN, AB, AT, e, G);
142 red.removeMean(y, gCells);
143 };
144 Kokkos::deep_copy(x, 0.0);
145 Kokkos::deep_copy(r, b);
146 red.removeMean(r, gCells);
147 Kokkos::deep_copy(p, r);
148 double rr = red.dot(CCConst(r), CCConst(r));
149 for (int it = 0; it < iters; ++it) {
150 matvec(Ap, p);
151 const double pAp = red.dot(CCConst(p), CCConst(Ap));
152 if (pAp <= 1e-300)
153 break;
154 const double alpha = rr / pAp;
155 axpy(x, alpha, CCConst(p));
156 axpy(r, -alpha, CCConst(Ap));
157 red.removeMean(r, gCells);
158 const double rrn = red.dot(CCConst(r), CCConst(r));
159 aypx(p, rrn / rr, CCConst(r));
160 rr = rrn;
161 }
162}
163
164int main(int argc, char** argv) {
165 MPI_Init(&argc, &argv);
166 Kokkos::initialize(argc, argv);
167 int fail = 0, size = 1, rank = 0;
168 {
169 MPI_Comm_rank(MPI_COMM_WORLD, &rank);
170 MPI_Comm_size(MPI_COMM_WORLD, &size);
171 IVec<kDim> gs{24, 20, 16};
172 std::array<bool, kDim> periodic{true, true, true};
173 const long gCells = (long)gs[0] * gs[1] * gs[2];
174
175 // ---- distributed CG ----
176 BlockDecomposer<kDim> dec(static_cast<std::size_t>(size), gs);
177 GridHaloTopology<kDim> halo;
178 halo.buildTopology(dec, rank, G, periodic, MPI_COMM_WORLD);
179 const auto& idx = halo.indexer();
180 const Index n = idx.numCellsInclGhost();
181 const auto eg = idx.sizeInclGhost(), og = idx.originInclGhost();
182 const C3 e{(int)eg[0], (int)eg[1], (int)eg[2]};
183 GridHalo<double> dev;
184 dev.init(halo);
185 Reduce red{e, MPI_COMM_WORLD};
186
187 CCField ox("ox", n), oy("oy", n),
188 oz("oz", n); // all-fluid openness (periodic Laplacian operator)
189 Kokkos::deep_copy(ox, 1.0);
190 Kokkos::deep_copy(oy, 1.0);
191 Kokkos::deep_copy(oz, 1.0);
192 OpV AC("AC", n), AW("AW", n), AE("AE", n), AS("AS", n), AN("AN", n), AB("AB", n), AT("AT", n);
193 peclet::flow::buildCutcellOp(AC, AW, AE, AS, AN, AB, AT, CCConst(ox), CCConst(oy), CCConst(oz),
194 e, G, 1.0, 1.0, 1.0);
195
196 CCField b("b", n), x("x", n);
197 auto hb = Kokkos::create_mirror_view(b);
198 for (Index c = 0; c < n; ++c)
199 hb(c) = 0.0;
200 idx.forEachInner([&](const IVec<kDim>& l) {
201 hb(idx.localMdToLocal(l)) = source(l[0] + og[0], l[1] + og[1], l[2] + og[2], gs);
202 });
203 Kokkos::deep_copy(b, hb);
204 cg(
205 x, CCConst(b), AC, AW, AE, AS, AN, AB, AT, e, [&](CCField v) { dev.exchange(v); }, red,
206 gCells, CGIT);
207 auto hx = Kokkos::create_mirror_view(x);
208 Kokkos::deep_copy(hx, x);
209
210 // ---- serial reference CG on the full global grid (every rank, redundant; compare its own
211 // block) ----
212 const C3 ge{(int)gs[0] + 2 * G, (int)gs[1] + 2 * G, (int)gs[2] + 2 * G};
213 const Index gn = (Index)ge.x * ge.y * ge.z;
214 CCField gox("gox", gn), goy("goy", gn), goz("goz", gn);
215 Kokkos::deep_copy(gox, 1.0);
216 Kokkos::deep_copy(goy, 1.0);
217 Kokkos::deep_copy(goz, 1.0);
218 OpV gAC("gAC", gn), gAW("gAW", gn), gAE("gAE", gn), gAS("gAS", gn), gAN("gAN", gn),
219 gAB("gAB", gn), gAT("gAT", gn);
220 peclet::flow::buildCutcellOp(gAC, gAW, gAE, gAS, gAN, gAB, gAT, CCConst(gox), CCConst(goy),
221 CCConst(goz), ge, G, 1.0, 1.0, 1.0);
222 CCField gb("gb", gn), gx("gx", gn);
223 auto hgb = Kokkos::create_mirror_view(gb);
224 for (Index c = 0; c < gn; ++c)
225 hgb(c) = 0.0;
226 for (int z = 0; z < gs[2]; ++z)
227 for (int y = 0; y < gs[1]; ++y)
228 for (int xx = 0; xx < gs[0]; ++xx)
229 hgb((long)(xx + G) + (long)(y + G) * ge.x + (long)(z + G) * (long)ge.x * ge.y) =
230 source(xx, y, z, gs);
231 Kokkos::deep_copy(gb, hgb);
232 Reduce gred{ge, MPI_COMM_SELF}; // serial: SELF comm -> Allreduce is a no-op
233 // serial periodic ghost exchange for the matvec
234 auto serialFill = [&](CCField f) {
235 CCExec sp;
236 long st[3] = {1, ge.x, (long)ge.x * ge.y};
237 int dims[3] = {ge.x, ge.y, ge.z};
238 int N3[3] = {(int)gs[0], (int)gs[1], (int)gs[2]};
239 for (int a = 0; a < 3; ++a) {
240 const int bb = (a + 1) % 3, cc = (a + 2) % 3;
241 const long sa = st[a], sb = st[bb], sc = st[cc];
242 const int N = N3[a];
243 CCField ff = f;
244 Kokkos::parallel_for(
245 "sfill",
246 Kokkos::MDRangePolicy<CCExec, Kokkos::Rank<2>>(sp, {0, 0}, {dims[bb], dims[cc]}),
247 KOKKOS_LAMBDA(int p0, int p1) {
248 const long base = (long)p0 * sb + (long)p1 * sc;
249 for (int gl = 0; gl < G; ++gl) {
250 ff(base + (long)gl * sa) = ff(base + (long)(gl + N) * sa);
251 ff(base + (long)(G + N + gl) * sa) = ff(base + (long)(G + gl) * sa);
252 }
253 });
254 sp.fence();
255 }
256 };
257 cg(gx, CCConst(gb), gAC, gAW, gAE, gAS, gAN, gAB, gAT, ge, serialFill, gred, gCells, CGIT);
258 auto hgx = Kokkos::create_mirror_view(gx);
259 Kokkos::deep_copy(hgx, gx);
260
261 double maxdiff = 0.0;
262 idx.forEachInner([&](const IVec<kDim>& l) {
263 const int gxk = l[0] + og[0], gyk = l[1] + og[1], gzk = l[2] + og[2];
264 const double a = hx(idx.localMdToLocal(l));
265 const double r =
266 hgx((long)(gxk + G) + (long)(gyk + G) * ge.x + (long)(gzk + G) * (long)ge.x * ge.y);
267 const double d = std::fabs(a - r);
268 if (d > maxdiff)
269 maxdiff = d;
270 });
271 if (maxdiff > 1e-11) {
272 ++fail;
273 std::fprintf(stderr, "[rank %d] max|distributed - serial CG| = %.3e\n", rank, maxdiff);
274 }
275 }
276 int totalFail = 0;
277 MPI_Allreduce(&fail, &totalFail, 1, MPI_INT, MPI_SUM, MPI_COMM_WORLD);
278 if (rank == 0) {
279 if (totalFail == 0)
280 std::printf("OK (np=%d): distributed cut-cell pressure CG == serial reference\n", size);
281 else
282 std::fprintf(stderr, "FAILED (np=%d): %d ranks differ\n", size, totalFail);
283 }
284 Kokkos::finalize();
285 MPI_Finalize();
286 return totalFail == 0 ? 0 : 1;
287}
flow — portable (Kokkos) cut-cell pressure operator + Chorin projection.
void buildCutcellOp(OpV AC, OpV AW, OpV AE, OpV AS, OpV AN, OpV AB, OpV AT, CCConst ox, CCConst oy, CCConst oz, C3 e, int g, double gfx, double gfy, double gfz)
Kokkos::View< double *, CCMem > CCField
Kokkos::DefaultExecutionSpace CCExec
Kokkos::View< const double *, CCMem > CCConst
double maxabs(CCConst a) const
double dot(CCConst a, CCConst b) const
void removeMean(CCField f, long globalCells) const
static constexpr double AC
static constexpr int kDim
int main(int argc, char **argv)
static void aypx(CCField y, double a, CCConst x)
Kokkos::View< double *, peclet::flow::CCMem > OpV
static void axpy(CCField y, double a, CCConst x)
static void cg(CCField x, CCConst b, OpV AC, OpV AW, OpV AE, OpV AS, OpV AN, OpV AB, OpV AT, C3 e, Exch &&exchange, const Reduce &red, long gCells, int iters)
static constexpr int CGIT
static constexpr int G
static double source(int gx, int gy, int gz, IVec< kDim > gs)
static constexpr int N