pnm 1.0.3
peclet.pnm — pore-network extraction from SDF geometry (Kokkos)
Loading...
Searching...
No Matches
test_pnm_flow_mpi.cpp
Go to the documentation of this file.
1// peclet-pnm — distributed network-flow extraction vs the single-rank oracle.
2//
3// A sphere-lattice SDF + deterministic smooth MAC fields (u,v,w,p and openness — not a physical
4// flow; the bookkeeping only needs consistency) are extracted (a) via extract_network_flow_mpi
5// distributed over the ORB blocks and (b) via the single-rank extract_network_flow_k on rank 0.
6// The distributed result is global and identical on every rank; it must match the oracle: throat
7// list and pore ids exactly; Q/A/dp/pressures/residuals to accumulation-order tolerance (both
8// paths sum with atomics); pore positions to the CUDA FMA-contraction tolerance. Two configs:
9// with openness fields and fully open. np = 1, 2, 4, any Kokkos backend.
10#include <mpi.h>
11
12#include <algorithm>
13#include <array>
14#include <cmath>
15#include <cstdio>
16#include <Kokkos_Core.hpp>
17#include <vector>
18
19#include "../synthetic_sdf.hpp"
20#include "pore_extraction.hpp"
22
23using pnm::Pore;
24
25namespace {
26
27double fld(int c, int gx, int gy, int gz, std::array<int, 3> gd) {
28 const double x = 2.0 * M_PI * gx / gd[0], y = 2.0 * M_PI * gy / gd[1],
29 z = 2.0 * M_PI * gz / gd[2];
30 switch (c) {
31 case 0:
32 return std::sin(x) * std::cos(y) + 0.3 * std::cos(2 * z);
33 case 1:
34 return std::cos(x) * std::sin(z) - 0.2 * std::sin(y);
35 case 2:
36 return std::sin(y) * std::sin(z) + 0.1 * std::cos(x);
37 case 3:
38 return std::cos(x + y) + 0.5 * std::sin(z - x); // p
39 default: {
40 const double s = 0.5 + 0.5 * std::sin(x + 2 * y - z); // openness in [0,1]
41 return s < 0.02 ? 0.0 : s;
42 }
43 }
44}
45
46bool close(double a, double b, double tol) {
47 return std::fabs(a - b) <= tol;
48}
49
50int runConfig(const char* name, bool withOpen, MPI_Comm comm) {
51 const std::array<int, 3> gd{36, 30, 24};
52 const std::array<float, 3> org{0.5f, -1.0f, 2.0f};
53 const std::array<float, 3> spc{0.5f, 1.0f, 1.5f};
54 const std::array<double, 3> gp{1.0e-3, -2.0e-3, 0.5e-3};
55 int rank = 0;
56 MPI_Comm_rank(comm, &rank);
57
58 std::array<int, 3> bo{}, bs{};
59 pnm::mpi_block_of(gd, comm, bo, bs);
60 const std::size_t nl = std::size_t(bs[0]) * bs[1] * bs[2];
61 std::vector<float> sdfL(nl);
62 std::vector<double> fldL[7];
63 for (int c = 0; c < 7; ++c)
64 fldL[c].resize(nl);
65 for (int z = 0; z < bs[2]; ++z)
66 for (int y = 0; y < bs[1]; ++y)
67 for (int x = 0; x < bs[0]; ++x) {
68 const std::size_t i = (std::size_t(z) * bs[1] + y) * bs[0] + x;
69 const int gx = bo[0] + x, gy = bo[1] + y, gz = bo[2] + z;
70 sdfL[i] = pnm::test::sphereLatticeSdf(gx, gy, gz, gd);
71 for (int c = 0; c < 4; ++c)
72 fldL[c][i] = fld(c, gx, gy, gz, gd);
73 for (int c = 4; c < 7; ++c)
74 fldL[c][i] = fld(4, gx + c, gy, gz, gd); // three distinct openness fields
75 }
76 std::vector<double> none;
77 auto dist = pnm::extract_network_flow_mpi(sdfL, gd, org, spc, fldL[0], fldL[1], fldL[2], fldL[3],
78 withOpen ? fldL[4] : none, withOpen ? fldL[5] : none,
79 withOpen ? fldL[6] : none, gp, comm);
80
81 int fail = 0;
82 if (rank == 0) {
83 const std::size_t n = std::size_t(gd[0]) * gd[1] * gd[2];
84 std::vector<float> sdfG(n);
85 std::vector<double> fldG[7];
86 for (int c = 0; c < 7; ++c)
87 fldG[c].resize(n);
88 for (int z = 0; z < gd[2]; ++z)
89 for (int y = 0; y < gd[1]; ++y)
90 for (int x = 0; x < gd[0]; ++x) {
91 const std::size_t i = (std::size_t(z) * gd[1] + y) * gd[0] + x;
92 sdfG[i] = pnm::test::sphereLatticeSdf(x, y, z, gd);
93 for (int c = 0; c < 4; ++c)
94 fldG[c][i] = fld(c, x, y, z, gd);
95 for (int c = 4; c < 7; ++c)
96 fldG[c][i] = fld(4, x + c, y, z, gd);
97 }
98 auto orc = pnm::extract_network_flow_k(sdfG, gd, org, spc, fldG[0], fldG[1], fldG[2], fldG[3],
99 withOpen ? fldG[4] : none, withOpen ? fldG[5] : none,
100 withOpen ? fldG[6] : none, gp);
101 double qs = 1e-30;
102 for (double q : orc.throat_flow)
103 qs = std::max(qs, std::fabs(q));
104 if (dist.throats != orc.throats) {
105 std::printf("[%s] FAIL throat list: %zu vs %zu\n", name, dist.throats.size(),
106 orc.throats.size());
107 fail = 1;
108 } else {
109 for (std::size_t t = 0; t < orc.throats.size() && !fail; ++t) {
110 if (!close(dist.throat_flow[t], orc.throat_flow[t], 1e-10 * qs) ||
111 !close(dist.throat_area[t], orc.throat_area[t], 1e-10 * qs) ||
112 !close(dist.throat_dp[t], orc.throat_dp[t], 1e-8)) {
113 std::printf("[%s] FAIL throat %zu: Q %.15e vs %.15e A %.15e vs %.15e dp %g vs %g\n",
114 name, t, dist.throat_flow[t], orc.throat_flow[t], dist.throat_area[t],
115 orc.throat_area[t], dist.throat_dp[t], orc.throat_dp[t]);
116 fail = 1;
117 }
118 }
119 }
120 if (dist.pores.size() != orc.pores.size()) {
121 std::printf("[%s] FAIL pore count %zu vs %zu\n", name, dist.pores.size(), orc.pores.size());
122 fail = 1;
123 } else {
124 for (std::size_t k = 0; k < orc.pores.size() && !fail; ++k) {
125 const Pore &a = dist.pores[k], &b = orc.pores[k];
126 if (a.radius != b.radius || !close(a.x, b.x, 1e-5 * spc[0]) ||
127 !close(a.y, b.y, 1e-5 * spc[1]) || !close(a.z, b.z, 1e-5 * spc[2]) ||
128 !close(dist.pore_pressure[k], orc.pore_pressure[k], 1e-9) ||
129 !close(dist.pore_residual[k], orc.pore_residual[k], 1e-10 * qs)) {
130 std::printf("[%s] FAIL pore %zu (r %.9g vs %.9g, p %.15e vs %.15e)\n", name, k, a.radius,
131 b.radius, dist.pore_pressure[k], orc.pore_pressure[k]);
132 fail = 1;
133 }
134 }
135 }
136 if (!fail)
137 std::printf("[%s] PASS: %zu pores, %zu throats match the single-rank oracle\n", name,
138 orc.pores.size(), orc.throats.size());
139 }
140 MPI_Bcast(&fail, 1, MPI_INT, 0, comm);
141 return fail;
142}
143
144} // namespace
145
146int main(int argc, char** argv) {
147 MPI_Init(&argc, &argv);
148 int fail = 0;
149 {
150 Kokkos::ScopeGuard kokkos(argc, argv);
151 fail += runConfig("openness", true, MPI_COMM_WORLD);
152 fail += runConfig("fully_open", false, MPI_COMM_WORLD);
153 }
154 MPI_Finalize();
155 return fail ? 1 : 0;
156}
float sphereLatticeSdf(int gx, int gy, int gz, std::array< int, 3 > gd)
2x2x2 lattice of SOLID spheres in a periodic box (min-image distance); pores at the interstices.
NetworkFlow extract_network_flow_k(const std::vector< float > &sdf_h, std::array< int, 3 > resolution, std::array< float, 3 > origin, std::array< float, 3 > spacing, const std::vector< double > &u_h, const std::vector< double > &v_h, const std::vector< double > &w_h, const std::vector< double > &p_h, const std::vector< double > &ox_h, const std::vector< double > &oy_h, const std::vector< double > &oz_h, std::array< double, 3 > grad_p)
NetworkFlow extract_network_flow_mpi(const std::vector< float > &sdf_local, std::array< int, 3 > gdims, std::array< float, 3 > origin, std::array< float, 3 > spacing, const std::vector< double > &u_h, const std::vector< double > &v_h, const std::vector< double > &w_h, const std::vector< double > &p_h, const std::vector< double > &ox_h, const std::vector< double > &oy_h, const std::vector< double > &oz_h, std::array< double, 3 > grad_p, MPI_Comm comm)
void mpi_block_of(std::array< int, 3 > gdims, MPI_Comm comm, std::array< int, 3 > &offset, std::array< int, 3 > &size)
This rank's ORB block of the global grid (deterministic; same partition as flow/dem): offset = the bl...
peclet.pnm — portable (Kokkos) single-rank pore-network extraction from an SDF.
peclet.pnm — distributed (MPI) pore-network extraction on the core block decomposition.
int main(int argc, char **argv)