flow
Kokkos cut-cell IBM incompressible Navier-Stokes solver + pnm pore extraction
Loading...
Searching...
No Matches
test_distributed_diffusion.cpp
Go to the documentation of this file.
1// cfd-gpu — distributed (MPI) Kokkos grid-halo integration: the foundational pattern for the
2// multi-rank Kokkos sdflow.
3//
4// Solves a periodic implicit-diffusion system ((idiag)I - beta*Lap) u = b by Red-Black
5// Gauss-Seidel, with the field decomposed over MPI ranks (ORB) and the per-colour ghost exchange
6// done by transport-core's portable GPU-resident halo (GridHalo) instead of a single-process
7// periodic fill. The cfd CCField and peclet::core::View<double> are the SAME Kokkos type
8// (x-fastest, default memory space), so the solver field exchanges directly. The distributed RB-GS
9// is algebraically identical to the single-rank sweep (the halo supplies the same neighbour values
10// + the GLOBAL red-black parity via the block's global origin), so the multi-rank result must equal
11// a serial reference to machine precision. Validates the consumption pattern (replace fillGhosts
12// with halo.exchange) the full sdflow MPI port will follow. Runs on any backend.
13#include <mpi.h>
14
15#include <array>
16#include <cmath>
17#include <cstdio>
18#include <Kokkos_Core.hpp>
19#include <vector>
20
21#include "mac_stencils.hpp" // peclet::flow::diffSmoothColor, I3, SField
22#include "peclet/core/common/types.hpp"
23#include "peclet/core/common/view.hpp"
24#include "peclet/core/decomp/block_decomposer.hpp"
25#include "peclet/core/halo/grid_halo.hpp"
26#include "peclet/core/halo/grid_halo_topology.hpp"
27
28using peclet::core::Index;
29using peclet::core::IVec;
30using peclet::core::wrap;
31using peclet::core::decomp::BlockDecomposer;
32using peclet::core::halo::GridHalo;
33using peclet::core::halo::GridHaloTopology;
37
38static constexpr int kDim = 3;
39static constexpr int G = 1; // diffusion stencil reach
40static constexpr double IDIAG = 1.0, BETA = 0.2, AC = IDIAG + 6.0 * BETA;
41static constexpr int SWEEPS = 60;
42
43// deterministic periodic source over the global grid.
44static double source(int gx, int gy, int gz, IVec<kDim> gs) {
45 return std::sin(2.0 * M_PI * gx / gs[0]) * std::cos(2.0 * M_PI * gy / gs[1]) +
46 0.5 * std::sin(2.0 * M_PI * gz / gs[2]);
47}
48
49// periodic ghost fill (3 axes, width G) of a single-block extended field on device.
50static void periodicFill(SField f, I3 e) {
52 long st[3] = {1, e.x, (long)e.x * e.y};
53 int dims[3] = {e.x, e.y, e.z};
54 int N3[3] = {e.x - 2 * G, e.y - 2 * G, e.z - 2 * G};
55 for (int a = 0; a < 3; ++a) {
56 const int b = (a + 1) % 3, c = (a + 2) % 3;
57 const long sa = st[a], sb = st[b], sc = st[c];
58 const int N = N3[a];
59 Kokkos::parallel_for(
60 "ref_pfill",
61 Kokkos::MDRangePolicy<peclet::flow::SExec, Kokkos::Rank<2>>(sp, {0, 0}, {dims[b], dims[c]}),
62 KOKKOS_LAMBDA(int p0, int p1) {
63 const long base = (long)p0 * sb + (long)p1 * sc;
64 for (int gl = 0; gl < G; ++gl) {
65 f(base + (long)gl * sa) = f(base + (long)(gl + N) * sa);
66 f(base + (long)(G + N + gl) * sa) = f(base + (long)(G + gl) * sa);
67 }
68 });
69 sp.fence();
70 }
71}
72
73int main(int argc, char** argv) {
74 MPI_Init(&argc, &argv);
75 Kokkos::initialize(argc, argv);
76 int fail = 0, size = 1, rank = 0;
77 {
78 MPI_Comm_rank(MPI_COMM_WORLD, &rank);
79 MPI_Comm_size(MPI_COMM_WORLD, &size);
80 IVec<kDim> gs{24, 20, 16};
81 std::array<bool, kDim> periodic{true, true, true};
82
83 // ---- distributed solve ----
84 BlockDecomposer<kDim> dec(static_cast<std::size_t>(size), gs);
85 GridHaloTopology<kDim> halo;
86 halo.buildTopology(dec, rank, G, periodic, MPI_COMM_WORLD);
87 const auto& idx = halo.indexer();
88 const Index n = idx.numCellsInclGhost();
89 const auto eg = idx.sizeInclGhost(), og = idx.originInclGhost();
90 const I3 e{(int)eg[0], (int)eg[1], (int)eg[2]}, ogv{(int)og[0], (int)og[1], (int)og[2]};
91
92 SField u("u", n), b("b", n);
93 auto hb = Kokkos::create_mirror_view(b);
94 auto hu = Kokkos::create_mirror_view(u);
95 for (Index c = 0; c < n; ++c) {
96 hb(c) = 0.0;
97 hu(c) = 0.0;
98 }
99 idx.forEachInner([&](const IVec<kDim>& l) {
100 hb(idx.localMdToLocal(l)) = source(l[0] + og[0], l[1] + og[1], l[2] + og[2], gs);
101 });
102 Kokkos::deep_copy(b, hb);
103 Kokkos::deep_copy(u, hu);
104
105 GridHalo<double> dev;
106 dev.init(halo);
107 SField empty;
108 for (int k = 0; k < SWEEPS; ++k)
109 for (int color = 0; color < 2; ++color) {
110 dev.exchange(
111 u); // cross-rank + periodic ghost exchange (replaces the single-process fillGhosts)
112 diffSmoothColor(u, b, e, ogv, G, BETA, AC, color, empty);
113 }
114 Kokkos::deep_copy(hu, u);
115
116 // ---- serial reference on the full global grid (every rank, redundant; compare its own block)
117 // ----
118 const I3 ge{(int)gs[0] + 2 * G, (int)gs[1] + 2 * G, (int)gs[2] + 2 * G}, geo{-G, -G, -G};
119 const Index gn = (Index)ge.x * ge.y * ge.z;
120 SField ur("ur", gn), br("br", gn);
121 auto hbr = Kokkos::create_mirror_view(br);
122 for (Index c = 0; c < gn; ++c)
123 hbr(c) = 0.0;
124 for (int z = 0; z < gs[2]; ++z)
125 for (int y = 0; y < gs[1]; ++y)
126 for (int x = 0; x < gs[0]; ++x)
127 hbr((long)(x + G) + (long)(y + G) * ge.x + (long)(z + G) * (long)ge.x * ge.y) =
128 source(x, y, z, gs);
129 Kokkos::deep_copy(br, hbr);
130 Kokkos::deep_copy(ur, 0.0);
131 for (int k = 0; k < SWEEPS; ++k)
132 for (int color = 0; color < 2; ++color) {
133 periodicFill(ur, ge);
134 diffSmoothColor(ur, br, ge, geo, G, BETA, AC, color, empty);
135 }
136 auto hur = Kokkos::create_mirror_view(ur);
137 Kokkos::deep_copy(hur, ur);
138
139 // compare this rank's inner block against the global reference.
140 double maxdiff = 0.0;
141 idx.forEachInner([&](const IVec<kDim>& l) {
142 const int gx = l[0] + og[0], gy = l[1] + og[1], gz = l[2] + og[2];
143 const double a = hu(idx.localMdToLocal(l));
144 const double r =
145 hur((long)(gx + G) + (long)(gy + G) * ge.x + (long)(gz + G) * (long)ge.x * ge.y);
146 const double d = std::fabs(a - r);
147 if (d > maxdiff)
148 maxdiff = d;
149 });
150 if (maxdiff > 1e-12) {
151 ++fail;
152 std::fprintf(stderr, "[rank %d] max|distributed - serial| = %.3e\n", rank, maxdiff);
153 }
154 }
155
156 int totalFail = 0;
157 MPI_Allreduce(&fail, &totalFail, 1, MPI_INT, MPI_SUM, MPI_COMM_WORLD);
158 if (rank == 0) {
159 if (totalFail == 0)
160 std::printf("OK (np=%d): distributed RB-GS diffusion == serial reference\n", size);
161 else
162 std::fprintf(stderr, "FAILED (np=%d): %d ranks differ\n", size, totalFail);
163 }
164 Kokkos::finalize();
165 MPI_Finalize();
166 return totalFail == 0 ? 0 : 1;
167}
flow — portable (Kokkos) MAC stencil operators: Red-Black Gauss-Seidel smoothers + divergence.
void diffSmoothColor(SField c, SConst b, I3 e, I3 og, int g, double beta, double Ac, int color, SConst dcorr)
Kokkos::DefaultExecutionSpace SExec
Kokkos::View< double *, SMem > SField
static constexpr int kDim
int main(int argc, char **argv)
static constexpr double IDIAG
static constexpr int SWEEPS
static constexpr double AC
static constexpr int G
static double source(int gx, int gy, int gz, IVec< kDim > gs)
static constexpr double BETA
static void periodicFill(SField f, I3 e)
static constexpr int N