PeriDEM 0.3.0
PeriDEM -- Peridynamics-based high-fidelity model for granular media
Loading...
Searching...
No Matches
pdMpi.cpp
Go to the documentation of this file.
1/*
2 * -------------------------------------------
3 * Copyright (c) 2021 - 2026 Prashant K. Jha
4 * -------------------------------------------
5 * PeriDEM https://github.com/prashjha/PeriDEM
6 *
7 * Distributed under the Boost Software License, Version 1.0. (See accompanying
8 * file LICENSE)
9 */
10
11#include "pdMpi.h"
12
13#include "data/modelData.h"
14#include "inp/input.h"
18#include "util/io.h"
19#include "util/parallelUtil.h"
20#include "util/point.h"
21#include "util/vecMethods.h"
22
23#include <algorithm>
24#include <chrono>
25#include <format>
26#include <stdexcept>
27#include <vector>
28
30 return data.d_pdDofMpi;
31}
32
33namespace {
34
35void uniqueSorted(std::vector<int> &v) {
36 std::sort(v.begin(), v.end());
37 v.erase(std::unique(v.begin(), v.end()), v.end());
38}
39
41 MPI_Comm comm = util::parallel::mpiComm();
42 const int size = util::parallel::mpiSize();
43 const int rank = util::parallel::mpiRank();
44 const size_t n_nodes = data.d_pdNodePartition.size();
45
46 std::vector<std::vector<int>> need_from(static_cast<size_t>(size));
47 for (size_t i = 0; i < n_nodes; ++i) {
48 if (static_cast<int>(data.d_pdNodePartition[i]) != rank)
49 continue;
50 for (size_t j : data.d_neighPd[i]) {
51 const int own = static_cast<int>(data.d_pdNodePartition[j]);
52 if (own != rank)
53 need_from[static_cast<size_t>(own)].push_back(static_cast<int>(j));
54 }
55 }
56 for (auto &v : need_from)
57 uniqueSorted(v);
58
59 std::vector<int> sendcounts(static_cast<size_t>(size), 0);
60 std::vector<int> recvcounts(static_cast<size_t>(size), 0);
61 for (int r = 0; r < size; ++r)
62 sendcounts[static_cast<size_t>(r)] =
63 static_cast<int>(need_from[static_cast<size_t>(r)].size());
64 MPI_Alltoall(sendcounts.data(), 1, MPI_INT, recvcounts.data(), 1, MPI_INT,
65 comm);
66
67 std::vector<int> sdispls(static_cast<size_t>(size), 0);
68 std::vector<int> rdispls(static_cast<size_t>(size), 0);
69 int send_total = 0;
70 int recv_total = 0;
71 for (int r = 0; r < size; ++r) {
72 sdispls[static_cast<size_t>(r)] = send_total;
73 rdispls[static_cast<size_t>(r)] = recv_total;
74 send_total += sendcounts[static_cast<size_t>(r)];
75 recv_total += recvcounts[static_cast<size_t>(r)];
76 }
77
78 std::vector<int> sendbuf(static_cast<size_t>(send_total));
79 for (int r = 0; r < size; ++r) {
80 const auto &v = need_from[static_cast<size_t>(r)];
81 std::copy(v.begin(), v.end(),
82 sendbuf.begin() + sdispls[static_cast<size_t>(r)]);
83 }
84 std::vector<int> recvbuf(static_cast<size_t>(recv_total));
85 MPI_Alltoallv(sendbuf.data(), sendcounts.data(), sdispls.data(), MPI_INT,
86 recvbuf.data(), recvcounts.data(), rdispls.data(), MPI_INT,
87 comm);
88
89 data.d_pdGhostNeedFrom = std::move(need_from);
90 data.d_pdGhostServeTo.assign(static_cast<size_t>(size), {});
91 for (int r = 0; r < size; ++r) {
92 auto &dst = data.d_pdGhostServeTo[static_cast<size_t>(r)];
93 dst.resize(static_cast<size_t>(recvcounts[static_cast<size_t>(r)]));
94 for (int k = 0; k < recvcounts[static_cast<size_t>(r)]; ++k) {
95 const int g = recvbuf[static_cast<size_t>(rdispls[static_cast<size_t>(r)] + k)];
96 if (g < 0 || static_cast<size_t>(g) >= n_nodes)
97 throw std::runtime_error("pdMpi: bad node id in ghost plan");
98 if (static_cast<int>(data.d_pdNodePartition[static_cast<size_t>(g)]) !=
99 rank)
100 throw std::runtime_error("pdMpi: serve node not owned here");
101 dst[static_cast<size_t>(k)] = g;
102 }
103 }
104
105 size_t n_ghost = 0;
106 for (const auto &v : data.d_pdGhostNeedFrom)
107 n_ghost += v.size();
108 data.setKeyData("pd_mpi_ghost_nodes", static_cast<double>(n_ghost));
109}
110
113void syncAllOwnedPoints(data::ModelData &data, std::vector<util::Point> &field) {
114 const int rank = util::parallel::mpiRank();
115 const size_t n = field.size();
116 std::vector<double> buf(3 * n, 0.);
117 for (size_t i = 0; i < n; ++i) {
118 if (static_cast<int>(data.d_pdNodePartition[i]) != rank)
119 continue;
120 buf[3 * i + 0] = field[i].d_x;
121 buf[3 * i + 1] = field[i].d_y;
122 buf[3 * i + 2] = field[i].d_z;
123 }
124 MPI_Allreduce(MPI_IN_PLACE, buf.data(), static_cast<int>(3 * n), MPI_DOUBLE,
125 MPI_SUM, util::parallel::mpiComm());
126 for (size_t i = 0; i < n; ++i)
127 field[i] = util::Point(buf[3 * i], buf[3 * i + 1], buf[3 * i + 2]);
128}
129
130void exchangePoints(data::ModelData &data, std::vector<util::Point> &field) {
131 MPI_Comm comm = util::parallel::mpiComm();
132 const int size = util::parallel::mpiSize();
133
134 std::vector<std::vector<double>> send(static_cast<size_t>(size));
135 for (int r = 0; r < size; ++r) {
136 for (int id : data.d_pdGhostServeTo[static_cast<size_t>(r)]) {
137 const auto &p = field[static_cast<size_t>(id)];
138 send[static_cast<size_t>(r)].push_back(p.d_x);
139 send[static_cast<size_t>(r)].push_back(p.d_y);
140 send[static_cast<size_t>(r)].push_back(p.d_z);
141 }
142 }
143
144 std::vector<int> sendcounts(static_cast<size_t>(size), 0);
145 std::vector<int> recvcounts(static_cast<size_t>(size), 0);
146 for (int r = 0; r < size; ++r)
147 sendcounts[static_cast<size_t>(r)] =
148 static_cast<int>(send[static_cast<size_t>(r)].size());
149 MPI_Alltoall(sendcounts.data(), 1, MPI_INT, recvcounts.data(), 1, MPI_INT,
150 comm);
151
152 std::vector<int> sdispls(static_cast<size_t>(size), 0);
153 std::vector<int> rdispls(static_cast<size_t>(size), 0);
154 int send_total = 0;
155 int recv_total = 0;
156 for (int r = 0; r < size; ++r) {
157 sdispls[static_cast<size_t>(r)] = send_total;
158 rdispls[static_cast<size_t>(r)] = recv_total;
159 send_total += sendcounts[static_cast<size_t>(r)];
160 recv_total += recvcounts[static_cast<size_t>(r)];
161 }
162
163 std::vector<double> sendbuf(static_cast<size_t>(send_total));
164 for (int r = 0; r < size; ++r) {
165 const auto &v = send[static_cast<size_t>(r)];
166 std::copy(v.begin(), v.end(),
167 sendbuf.begin() + sdispls[static_cast<size_t>(r)]);
168 }
169 std::vector<double> recvbuf(static_cast<size_t>(recv_total));
170 MPI_Alltoallv(sendbuf.data(), sendcounts.data(), sdispls.data(), MPI_DOUBLE,
171 recvbuf.data(), recvcounts.data(), rdispls.data(), MPI_DOUBLE,
172 comm);
173
174 for (int r = 0; r < size; ++r) {
175 size_t off = static_cast<size_t>(rdispls[static_cast<size_t>(r)]);
176 for (int id : data.d_pdGhostNeedFrom[static_cast<size_t>(r)]) {
177 field[static_cast<size_t>(id)] =
178 util::Point(recvbuf[off], recvbuf[off + 1], recvbuf[off + 2]);
179 off += 3;
180 }
181 }
182}
183
184void exchangeDoubles(data::ModelData &data, std::vector<double> &field) {
185 MPI_Comm comm = util::parallel::mpiComm();
186 const int size = util::parallel::mpiSize();
187
188 std::vector<std::vector<double>> send(static_cast<size_t>(size));
189 for (int r = 0; r < size; ++r) {
190 for (int id : data.d_pdGhostServeTo[static_cast<size_t>(r)])
191 send[static_cast<size_t>(r)].push_back(field[static_cast<size_t>(id)]);
192 }
193
194 std::vector<int> sendcounts(static_cast<size_t>(size), 0);
195 std::vector<int> recvcounts(static_cast<size_t>(size), 0);
196 for (int r = 0; r < size; ++r)
197 sendcounts[static_cast<size_t>(r)] =
198 static_cast<int>(send[static_cast<size_t>(r)].size());
199 MPI_Alltoall(sendcounts.data(), 1, MPI_INT, recvcounts.data(), 1, MPI_INT,
200 comm);
201
202 std::vector<int> sdispls(static_cast<size_t>(size), 0);
203 std::vector<int> rdispls(static_cast<size_t>(size), 0);
204 int send_total = 0;
205 int recv_total = 0;
206 for (int r = 0; r < size; ++r) {
207 sdispls[static_cast<size_t>(r)] = send_total;
208 rdispls[static_cast<size_t>(r)] = recv_total;
209 send_total += sendcounts[static_cast<size_t>(r)];
210 recv_total += recvcounts[static_cast<size_t>(r)];
211 }
212
213 std::vector<double> sendbuf(static_cast<size_t>(send_total));
214 for (int r = 0; r < size; ++r) {
215 const auto &v = send[static_cast<size_t>(r)];
216 std::copy(v.begin(), v.end(),
217 sendbuf.begin() + sdispls[static_cast<size_t>(r)]);
218 }
219 std::vector<double> recvbuf(static_cast<size_t>(recv_total));
220 MPI_Alltoallv(sendbuf.data(), sendcounts.data(), sdispls.data(), MPI_DOUBLE,
221 recvbuf.data(), recvcounts.data(), rdispls.data(), MPI_DOUBLE,
222 comm);
223
224 for (int r = 0; r < size; ++r) {
225 size_t off = static_cast<size_t>(rdispls[static_cast<size_t>(r)]);
226 for (int id : data.d_pdGhostNeedFrom[static_cast<size_t>(r)])
227 field[static_cast<size_t>(id)] = recvbuf[off++];
228 }
229}
230
231} // namespace
232
234 data.d_pdDofMpi = false;
235 data.d_pdGrainAligned = false;
236 data.d_pdNodePartition.clear();
237 data.d_pdGhostNeedFrom.clear();
238 data.d_pdGhostServeTo.clear();
239
241 return;
242 const std::string strategy = particle::resolvedMpiStrategy(data);
243 if (strategy != "dof")
244 return;
245 const int size = util::parallel::mpiSize();
246 if (size <= 1)
247 return;
248 if (data.d_neighPd.empty() || data.d_neighPd.size() != data.d_x.size())
249 return;
250
251 data.d_pdDofMpi = true;
252 data.d_pdGrainAligned = false; // DOF-MPI = node owners, never whole-grain brick
253 const int rank = util::parallel::mpiRank();
254 const size_t n_nodes = data.d_x.size();
255
256 // Partition nodes across ranks (graph partition). Walls / Multi_Particle do
257 // not change the mode: DOF-MPI always distributes nodes.
258 if (rank == 0) {
259 mesh::metisGraphPartition("metis_kway", data.d_neighPd,
260 data.d_pdNodePartition,
261 static_cast<size_t>(size));
262 } else {
263 data.d_pdNodePartition.assign(n_nodes, 0);
264 }
265 MPI_Bcast(data.d_pdNodePartition.data(), static_cast<int>(n_nodes),
266 MPI_UNSIGNED_LONG, 0, util::parallel::mpiComm());
267
268 size_t n_owned = 0;
269 for (size_t i = 0; i < n_nodes; ++i)
270 if (static_cast<int>(data.d_pdNodePartition[i]) == rank)
271 ++n_owned;
272
274
275 util::io::print(std::format(
276 "DOF-MPI: rank {}/{} owns {}/{} nodes\n", rank, size, n_owned, n_nodes));
277}
278
280 if (data.d_vMag.size() != data.d_v.size())
281 data.d_vMag.resize(data.d_v.size(), 0.);
282 for (size_t i = 0; i < data.d_v.size(); ++i)
283 data.d_vMag[i] = data.d_v[i].length();
284}
285
287 if (!data.d_pdDofMpi)
288 return;
289 using clock = std::chrono::steady_clock;
290 const auto t0 = clock::now();
291
292 // DOF-MPI: each rank owns a subset of nodes. Before contact (and for PD
293 // neighbors outside the local halo), every rank needs a consistent full
294 // copy of u and v on Multi_Particle. Single_Particle has no inter-body
295 // contact — PD halo exchange is enough.
296 // Always refresh d_vMag after rewriting d_v (contact search uses vMag).
297 if (data.d_input_p && data.d_input_p->isMultiParticle()) {
300 for (size_t i = 0; i < data.d_x.size(); ++i)
301 data.d_x[i] = data.d_xRef[i] + data.d_u[i];
303 } else {
306 for (int r = 0; r < util::parallel::mpiSize(); ++r) {
307 for (int id : data.d_pdGhostNeedFrom[static_cast<size_t>(r)]) {
308 const size_t i = static_cast<size_t>(id);
309 data.d_x[i] = data.d_xRef[i] + data.d_u[i];
310 }
311 }
312 }
313 data.appendKeyData("pd_mpi_disp_exchange_time",
314 util::methods::timeDiff(t0, clock::now()));
315}
316
318 if (!data.d_pdDofMpi)
319 return;
320 if (data.d_thetaX.empty())
321 return;
322 using clock = std::chrono::steady_clock;
323 const auto t0 = clock::now();
324 if (data.d_input_p && data.d_input_p->isMultiParticle()) {
325 const int rank = util::parallel::mpiRank();
326 const size_t n = data.d_thetaX.size();
327 std::vector<double> buf(n, 0.);
328 for (size_t i = 0; i < n; ++i) {
329 if (static_cast<int>(data.d_pdNodePartition[i]) == rank)
330 buf[i] = data.d_thetaX[i];
331 }
332 MPI_Allreduce(MPI_IN_PLACE, buf.data(), static_cast<int>(n), MPI_DOUBLE,
333 MPI_SUM, util::parallel::mpiComm());
334 data.d_thetaX.swap(buf);
335 } else {
336 exchangeDoubles(data, data.d_thetaX);
337 }
338 data.appendKeyData("pd_mpi_theta_exchange_time",
339 util::methods::timeDiff(t0, clock::now()));
340}
A class to store model data.
Definition modelData.h:50
void buildGhostPlan(data::ModelData &data)
Definition pdMpi.cpp:40
void exchangePoints(data::ModelData &data, std::vector< util::Point > &field)
Definition pdMpi.cpp:130
void exchangeDoubles(data::ModelData &data, std::vector< double > &field)
Definition pdMpi.cpp:184
void syncAllOwnedPoints(data::ModelData &data, std::vector< util::Point > &field)
Definition pdMpi.cpp:113
void uniqueSorted(std::vector< int > &v)
Definition pdMpi.cpp:35
Definition contact.h:20
void metisGraphPartition(std::string partitionMethod, const std::vector< std::vector< size_t > > &nodeNeighs, std::vector< size_t > &nodePartition, size_t nPartitions)
Partitions the nodes based on node neighborlist supplied. Function first creates a graph with nodes a...
std::string resolvedMpiStrategy(const data::ModelData &data)
Resolved MPI strategy: none|particle|dof (auto expanded).
bool dofMpiEnabled(const data::ModelData &data)
True when nodal DOF MPI is active (mpiSize > 1, MPI_Strategy=dof).
Definition pdMpi.cpp:29
void setupDofPartition(data::ModelData &data)
Metis-partition nodes on the PD neighbor graph and build ghost plans. Call after d_neighPd is ready a...
Definition pdMpi.cpp:233
void exchangeGhostTheta(data::ModelData &data)
Halo-exchange state-based dilatation d_thetaX for PD ghosts.
Definition pdMpi.cpp:317
void exchangeGhostDisplacement(data::ModelData &data)
Halo-exchange nodal displacements (and current x) for PD ghosts.
Definition pdMpi.cpp:286
void print(const T &msg, int nt=print_default_tab, int printMpiRank=print_default_mpi_rank)
Prints formatted information.
Definition io.h:128
float timeDiff(std::chrono::steady_clock::time_point begin, std::chrono::steady_clock::time_point end, std::string unit="microseconds")
Returns difference between two times.
Definition vecMethods.h:304
bool isMpiEnabled()
Function to check if MPI is enabled.
int mpiSize()
Get size (number) of processors.
int mpiRank()
get rank (id) of this processor
MPI_Comm mpiComm()
Get MPI comm.
static void refreshVMagFromV(data::ModelData &data)
Definition pdMpi.cpp:279
A structure to represent 3d vectors.
Definition point.h:30