flow 1.3.0
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", peclet::flow::MDRange3<CCExec>(sp, {G, G, G}, {ee.x - G, ee.y - G, ee.z - G}),
54 KOKKOS_LAMBDA(int x, int y, int z, double& acc) {
55 const long i = (long)x + (long)y * ee.x + (long)z * (long)ee.x * ee.y;
56 acc += a(i) * b(i);
57 },
58 s);
59 double g = 0;
60 MPI_Allreduce(&s, &g, 1, MPI_DOUBLE, MPI_SUM, comm);
61 return g;
62 }
63 double maxabs(CCConst a) const {
64 CCExec sp;
65 double m = 0;
66 C3 ee = e;
67 Kokkos::parallel_reduce(
68 "max", peclet::flow::MDRange3<CCExec>(sp, {G, G, G}, {ee.x - G, ee.y - G, ee.z - G}),
69 KOKKOS_LAMBDA(int x, int y, int z, double& acc) {
70 const long i = (long)x + (long)y * ee.x + (long)z * (long)ee.x * ee.y;
71 const double v = Kokkos::fabs(a(i));
72 if (v > acc)
73 acc = v;
74 },
75 Kokkos::Max<double>(m));
76 double g = 0;
77 MPI_Allreduce(&m, &g, 1, MPI_DOUBLE, MPI_MAX, comm);
78 return g;
79 }
80 void removeMean(CCField f, long globalCells) const {
81 CCExec sp;
82 double s = 0;
83 C3 ee = e;
84 Kokkos::parallel_reduce(
85 "meansum", peclet::flow::MDRange3<CCExec>(sp, {G, G, G}, {ee.x - G, ee.y - G, ee.z - G}),
86 KOKKOS_LAMBDA(int x, int y, int z, double& acc) {
87 const long i = (long)x + (long)y * ee.x + (long)z * (long)ee.x * ee.y;
88 acc += f(i);
89 },
90 s);
91 double g = 0;
92 MPI_Allreduce(&s, &g, 1, MPI_DOUBLE, MPI_SUM, comm);
93 const double mean = g / (double)globalCells;
94 CCField ff = f;
95 Kokkos::parallel_for(
96 "meansub", peclet::flow::MDRange3<CCExec>(sp, {G, G, G}, {ee.x - G, ee.y - G, ee.z - G}),
97 KOKKOS_LAMBDA(int x, int y, int z) {
98 const long i = (long)x + (long)y * ee.x + (long)z * (long)ee.x * ee.y;
99 ff(i) -= mean;
100 });
101 sp.fence();
102 }
103};
104
105static void axpy(CCField y, double a, CCConst x) { // y += a x
106 CCExec sp;
107 CCField yy = y;
108 std::size_t n = y.extent(0);
109 Kokkos::parallel_for(
110 "axpy", Kokkos::RangePolicy<CCExec>(sp, 0, n),
111 KOKKOS_LAMBDA(std::size_t i) { yy(i) += a * x(i); });
112 sp.fence();
113}
114static void aypx(CCField y, double a, CCConst x) { // y = x + a y
115 CCExec sp;
116 CCField yy = y;
117 std::size_t n = y.extent(0);
118 Kokkos::parallel_for(
119 "aypx", Kokkos::RangePolicy<CCExec>(sp, 0, n),
120 KOKKOS_LAMBDA(std::size_t i) { yy(i) = x(i) + a * yy(i); });
121 sp.fence();
122}
123
124// Conjugate Gradients (mean-removed, singular periodic operator). exchange() supplies the matvec
125// ghosts.
126template <class Exch>
127static void cg(CCField x, CCConst b, OpV AC, OpV AW, OpV AE, OpV AS, OpV AN, OpV AB, OpV AT, C3 e,
128 Exch&& exchange, const Reduce& red, long gCells, int iters) {
129 const std::size_t n = x.extent(0);
130 CCField r("r", n), p("p", n), Ap("Ap", n);
131 auto matvec = [&](CCField y, CCField v) {
132 exchange(v);
133 applyCutcellOp(y, CCConst(v), AC, AW, AE, AS, AN, AB, AT, e, G);
134 red.removeMean(y, gCells);
135 };
136 Kokkos::deep_copy(x, 0.0);
137 Kokkos::deep_copy(r, b);
138 red.removeMean(r, gCells);
139 Kokkos::deep_copy(p, r);
140 double rr = red.dot(CCConst(r), CCConst(r));
141 for (int it = 0; it < iters; ++it) {
142 matvec(Ap, p);
143 const double pAp = red.dot(CCConst(p), CCConst(Ap));
144 if (pAp <= 1e-300)
145 break;
146 const double alpha = rr / pAp;
147 axpy(x, alpha, CCConst(p));
148 axpy(r, -alpha, CCConst(Ap));
149 red.removeMean(r, gCells);
150 const double rrn = red.dot(CCConst(r), CCConst(r));
151 aypx(p, rrn / rr, CCConst(r));
152 rr = rrn;
153 }
154}
155
156int main(int argc, char** argv) {
157 MPI_Init(&argc, &argv);
158 Kokkos::initialize(argc, argv);
159 int fail = 0, size = 1, rank = 0;
160 {
161 MPI_Comm_rank(MPI_COMM_WORLD, &rank);
162 MPI_Comm_size(MPI_COMM_WORLD, &size);
163 IVec<kDim> gs{24, 20, 16};
164 std::array<bool, kDim> periodic{true, true, true};
165 const long gCells = (long)gs[0] * gs[1] * gs[2];
166
167 // ---- distributed CG ----
168 BlockDecomposer<kDim> dec(static_cast<std::size_t>(size), gs);
169 GridHaloTopology<kDim> halo;
170 halo.buildTopology(dec, rank, G, periodic, MPI_COMM_WORLD);
171 const auto& idx = halo.indexer();
172 const Index n = idx.numCellsInclGhost();
173 const auto eg = idx.sizeInclGhost(), og = idx.originInclGhost();
174 const C3 e{(int)eg[0], (int)eg[1], (int)eg[2]};
175 GridHalo<double> dev;
176 dev.init(halo);
177 Reduce red{e, MPI_COMM_WORLD};
178
179 CCField ox("ox", n), oy("oy", n),
180 oz("oz", n); // all-fluid openness (periodic Laplacian operator)
181 Kokkos::deep_copy(ox, 1.0);
182 Kokkos::deep_copy(oy, 1.0);
183 Kokkos::deep_copy(oz, 1.0);
184 OpV AC("AC", n), AW("AW", n), AE("AE", n), AS("AS", n), AN("AN", n), AB("AB", n), AT("AT", n);
185 peclet::flow::buildCutcellOp(AC, AW, AE, AS, AN, AB, AT, CCConst(ox), CCConst(oy), CCConst(oz),
186 e, G, 1.0, 1.0, 1.0);
187
188 CCField b("b", n), x("x", n);
189 auto hb = Kokkos::create_mirror_view(b);
190 for (Index c = 0; c < n; ++c)
191 hb(c) = 0.0;
192 idx.forEachInner([&](const IVec<kDim>& l) {
193 hb(idx.localMdToLocal(l)) = source(l[0] + og[0], l[1] + og[1], l[2] + og[2], gs);
194 });
195 Kokkos::deep_copy(b, hb);
196 cg(
197 x, CCConst(b), AC, AW, AE, AS, AN, AB, AT, e, [&](CCField v) { dev.exchange(v); }, red,
198 gCells, CGIT);
199 auto hx = Kokkos::create_mirror_view(x);
200 Kokkos::deep_copy(hx, x);
201
202 // ---- serial reference CG on the full global grid (every rank, redundant; compare its own
203 // block) ----
204 const C3 ge{(int)gs[0] + 2 * G, (int)gs[1] + 2 * G, (int)gs[2] + 2 * G};
205 const Index gn = (Index)ge.x * ge.y * ge.z;
206 CCField gox("gox", gn), goy("goy", gn), goz("goz", gn);
207 Kokkos::deep_copy(gox, 1.0);
208 Kokkos::deep_copy(goy, 1.0);
209 Kokkos::deep_copy(goz, 1.0);
210 OpV gAC("gAC", gn), gAW("gAW", gn), gAE("gAE", gn), gAS("gAS", gn), gAN("gAN", gn),
211 gAB("gAB", gn), gAT("gAT", gn);
212 peclet::flow::buildCutcellOp(gAC, gAW, gAE, gAS, gAN, gAB, gAT, CCConst(gox), CCConst(goy),
213 CCConst(goz), ge, G, 1.0, 1.0, 1.0);
214 CCField gb("gb", gn), gx("gx", gn);
215 auto hgb = Kokkos::create_mirror_view(gb);
216 for (Index c = 0; c < gn; ++c)
217 hgb(c) = 0.0;
218 for (int z = 0; z < gs[2]; ++z)
219 for (int y = 0; y < gs[1]; ++y)
220 for (int xx = 0; xx < gs[0]; ++xx)
221 hgb((long)(xx + G) + (long)(y + G) * ge.x + (long)(z + G) * (long)ge.x * ge.y) =
222 source(xx, y, z, gs);
223 Kokkos::deep_copy(gb, hgb);
224 Reduce gred{ge, MPI_COMM_SELF}; // serial: SELF comm -> Allreduce is a no-op
225 // serial periodic ghost exchange for the matvec
226 auto serialFill = [&](CCField f) {
227 CCExec sp;
228 long st[3] = {1, ge.x, (long)ge.x * ge.y};
229 int dims[3] = {ge.x, ge.y, ge.z};
230 int N3[3] = {(int)gs[0], (int)gs[1], (int)gs[2]};
231 for (int a = 0; a < 3; ++a) {
232 const int bb = (a + 1) % 3, cc = (a + 2) % 3;
233 const long sa = st[a], sb = st[bb], sc = st[cc];
234 const int N = N3[a];
235 CCField ff = f;
236 Kokkos::parallel_for(
237 "sfill", peclet::flow::MDRange2<CCExec>(sp, {0, 0}, {dims[bb], dims[cc]}),
238 KOKKOS_LAMBDA(int p0, int p1) {
239 const long base = (long)p0 * sb + (long)p1 * sc;
240 for (int gl = 0; gl < G; ++gl) {
241 ff(base + (long)gl * sa) = ff(base + (long)(gl + N) * sa);
242 ff(base + (long)(G + N + gl) * sa) = ff(base + (long)(G + gl) * sa);
243 }
244 });
245 sp.fence();
246 }
247 };
248 cg(gx, CCConst(gb), gAC, gAW, gAE, gAS, gAN, gAB, gAT, ge, serialFill, gred, gCells, CGIT);
249 auto hgx = Kokkos::create_mirror_view(gx);
250 Kokkos::deep_copy(hgx, gx);
251
252 double maxdiff = 0.0;
253 idx.forEachInner([&](const IVec<kDim>& l) {
254 const int gxk = l[0] + og[0], gyk = l[1] + og[1], gzk = l[2] + og[2];
255 const double a = hx(idx.localMdToLocal(l));
256 const double r =
257 hgx((long)(gxk + G) + (long)(gyk + G) * ge.x + (long)(gzk + G) * (long)ge.x * ge.y);
258 const double d = std::fabs(a - r);
259 if (d > maxdiff)
260 maxdiff = d;
261 });
262 if (maxdiff > 1e-11) {
263 ++fail;
264 std::fprintf(stderr, "[rank %d] max|distributed - serial CG| = %.3e\n", rank, maxdiff);
265 }
266 }
267 int totalFail = 0;
268 MPI_Allreduce(&fail, &totalFail, 1, MPI_INT, MPI_SUM, MPI_COMM_WORLD);
269 if (rank == 0) {
270 if (totalFail == 0)
271 std::printf("OK (np=%d): distributed cut-cell pressure CG == serial reference\n", size);
272 else
273 std::fprintf(stderr, "FAILED (np=%d): %d ranks differ\n", size, totalFail);
274 }
275 Kokkos::finalize();
276 MPI_Finalize();
277 return totalFail == 0 ? 0 : 1;
278}
int main()
Definition dbg_scene.cpp:13
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 int N
static constexpr double AC
static constexpr int kDim
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 double mean(const std::vector< double > &a)