flow 1.3.0
Kokkos cut-cell IBM incompressible Navier-Stokes solver + pnm pore extraction
Loading...
Searching...
No Matches
test_graphamg_mpi.cpp
Go to the documentation of this file.
1// Decomposition-agnostic pressure solve via the agglomerated GraphAMG bottom solve.
2//
3// The geometric coarse hierarchy needs a cleanly-coarsening (equal-weight) ORB, so under a WEIGHTED
4// decomposition only pure RB-GS (nLevels==1) is available -- and RB-GS alone is NOT
5// mesh-independent. With set_pressure_graph_amg(true), the coarsest level (== the whole grid at
6// nLevels==1) is solved by a mesh-agnostic smoothed-aggregation AMG on the operator gathered to
7// rank 0: decomposition-agnostic AND mesh-independent. This test runs Stokes flow through a sphere
8// packing on a WEIGHTED ORB and checks the permeability equals the single-rank reference (np=1
9// bit-exact-ish; np>1 to the reduction-order floor), for BOTH the RB-GS and the GraphAMG bottom.
10// Build with -DPECLET_FLOW_MPI.
11#include <mpi.h>
12
13#include <cmath>
14#include <cstdio>
15#include <Kokkos_Core.hpp>
16#include <vector>
17
18#include "flow_ibm.hpp"
19#include "peclet/core/common/types.hpp"
20#include "peclet/core/decomp/block_decomposer.hpp"
21
22using peclet::core::IVec;
23using peclet::core::decomp::BlockDecomposer;
25
26static constexpr int N = 32, STEPS = 100;
27static constexpr double RHO = 1.0, MU = 0.1, F = 1e-3, DT = 60.0;
28
29static std::vector<double> packingSdf(double rfrac = 0.18) {
30 const double R = rfrac * N;
31 std::vector<double> sdf((std::size_t)N * N * N);
32 const double cs[2] = {0.25 * N, 0.75 * N};
33 for (int z = 0; z < N; ++z)
34 for (int y = 0; y < N; ++y)
35 for (int x = 0; x < N; ++x) {
36 double best = 1e30;
37 for (double sx : cs)
38 for (double sy : cs)
39 for (double sz : cs) {
40 auto wrap = [](double d) { return d - N * std::round(d / N); };
41 const double dx = wrap(x - sx), dy = wrap(y - sy), dz = wrap(z - sz);
42 best = std::min(best, std::sqrt(dx * dx + dy * dy + dz * dz) - R);
43 }
44 sdf[(std::size_t)x + (std::size_t)y * N + (std::size_t)z * N * N] = best;
45 }
46 return sdf;
47}
48
49static void configure(IbmSolver& s, bool amg, int levels) {
50 s.setRho(RHO);
51 s.setMu(MU);
52 s.setDt(DT);
53 s.setBodyForce(F, 0, 0);
54 s.setAdvection(false);
56 s.setPressureLevels(levels); // 1 = pure RB-GS geometrically (decomposition-agnostic)
57 s.setPressureGraphAmg(amg); // agglomerated redundant algebraic coarse solve at the bottom
58 s.setPressurePcg(true, 400, 1e-9);
59}
60
61static std::vector<double> localSdf(const std::vector<double>& g, 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 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 double perm(const std::vector<double>& gsdf, const BlockDecomposer<3>& dec, int rank,
76 bool amg, int levels, MPI_Comm comm) {
77 auto b = dec.block(rank);
78 IbmSolver s((int)b.size[0], (int)b.size[1], (int)b.size[2]);
79 s.initMpi(dec, comm);
80 configure(s, amg, levels);
81 s.setSolid(localSdf(gsdf, dec, rank), true);
82 for (int it = 0; it < STEPS; ++it)
83 s.step();
84 double lsum = 0;
85 for (double v : s.getVelocity(0))
86 lsum += v;
87 double gsum = 0;
89 return MU * (gsum / ((double)N * N * N)) / F;
90}
91
92int main(int argc, char** argv) {
93 MPI_Init(&argc, &argv);
94 Kokkos::initialize(argc, argv);
95 int fail = 0, size = 1, rank = 0;
96 {
99 const std::vector<double> gsdf = packingSdf();
100
101 // WEIGHTED ORB (heavy low-x): the geometric multilevel path is unavailable; GraphAMG bottom
102 // makes the (nLevels==1) solve mesh-agnostic on it.
103 std::vector<peclet::core::Real> w((std::size_t)N * N * N, 1.0);
104 for (int z = 0; z < N; ++z)
105 for (int y = 0; y < N; ++y)
106 for (int x = 0; x < N; ++x)
107 if (x < N / 2)
108 w[(std::size_t)x + (std::size_t)y * N + (std::size_t)z * N * N] = 6.0;
109 BlockDecomposer<3> dec((std::size_t)size, IVec<3>{N, N, N}, w);
110
111 const double k_amg = perm(gsdf, dec, rank, /*amg=*/true, /*levels=*/1, MPI_COMM_WORLD);
112
113 // single-rank reference with the same GraphAMG solve.
114 double k_ref = 0.0;
115 if (rank == 0) {
116 IbmSolver ref(N, N, N);
117 configure(ref, /*amg=*/true, /*levels=*/1);
118 ref.setSolid(gsdf, true);
119 for (int it = 0; it < STEPS; ++it)
120 ref.step();
121 double rs = 0;
122 for (double v : ref.getVelocity(0))
123 rs += v;
124 k_ref = MU * (rs / ((double)N * N * N)) / F;
125 }
127
128 const double reld = std::fabs(k_amg - k_ref) / (std::fabs(k_ref) + 1e-30);
129 const double tol = (size == 1) ? 1e-9 : 2e-4;
130 if (rank == 0)
131 std::printf(" GraphAMG on weighted ORB: k=%.8e ref=%.8e rel=%.2e (np=%d, tol %.0e)\n",
132 k_amg, k_ref, reld, size, tol);
133 if (!(reld < tol) || !std::isfinite(k_amg))
134 fail = 1;
135
136 // MULTI-LEVEL agglomerated bottom: the regular (equal-weight ORB) geometric hierarchy with
137 // the redundant GraphAMG replacing the distributed coarsest level — the large-np scaling
138 // configuration (no tiny latency-bound coarse level, no rank-0 gather).
139 BlockDecomposer<3> deq((std::size_t)size, IVec<3>{N, N, N});
140 const double k_ml = perm(gsdf, deq, rank, /*amg=*/true, /*levels=*/3, MPI_COMM_WORLD);
141 double k_mlref = 0.0;
142 if (rank == 0) {
143 IbmSolver ref(N, N, N);
144 configure(ref, /*amg=*/true, /*levels=*/3);
145 ref.setSolid(gsdf, true);
146 for (int it = 0; it < STEPS; ++it)
147 ref.step();
148 double rs = 0;
149 for (double v : ref.getVelocity(0))
150 rs += v;
151 k_mlref = MU * (rs / ((double)N * N * N)) / F;
152 }
154 const double reld2 = std::fabs(k_ml - k_mlref) / (std::fabs(k_mlref) + 1e-30);
155 if (rank == 0)
156 std::printf(
157 " GraphAMG bottom, 3-level hierarchy: k=%.8e ref=%.8e rel=%.2e (np=%d, tol %.0e)\n",
158 k_ml, k_mlref, reld2, size, tol);
159 if (!(reld2 < tol) || !std::isfinite(k_ml))
160 fail = 1;
161 }
162 int tf = 0;
164 if (rank == 0)
165 std::printf(tf == 0 ? "OK (np=%d): decomposition-agnostic GraphAMG pressure solve\n"
166 : "FAILED (np=%d)\n",
167 size);
168 Kokkos::finalize();
169 MPI_Finalize();
170 return tf == 0 ? 0 : 1;
171}
void setSolid(const std::vector< double > &sdfInner, bool cutcellPressure)
Host entry point: upload the inner SDF once and delegate.
void setPressureGraphAmg(bool on)
void setAdvection(bool on)
void setVelocityIterations(int it)
void setPressureLevels(int levels)
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 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, bool amg, int levels)
static double perm(const std::vector< double > &gsdf, const BlockDecomposer< 3 > &dec, int rank, bool amg, int levels, MPI_Comm comm)
static constexpr double RHO
static constexpr double MU
static constexpr int STEPS
static constexpr int N
static constexpr double DT
static constexpr double F
static std::vector< double > localSdf(const std::vector< double > &g, const BlockDecomposer< 3 > &dec, int rank)
static std::vector< double > packingSdf(double rfrac=0.18)