PeriDEM 0.3.0
PeriDEM -- Peridynamics-based high-fidelity model for granular media
Loading...
Searching...
No Matches
pd Namespace Reference

Data Structures

class  BrokenBondKnSelfContact
 Default: Kn * volj * capped_gap / R along yji with gap = R - Rc. More...
 
class  NoneSelfContact
 No force on broken bonds (Trask: broken → weight 0). More...
 
class  ReferenceGapSelfContact
 
class  SelfContact
 Intra-body force between nodes when the PD bond is inactive. More...
 

Functions

void computeForces (data::ModelData &data)
 Assemble nodal peridynamic force (constitutive response stays in material).
 
bool dofMpiEnabled (const data::ModelData &data)
 True when nodal DOF MPI is active (mpiSize > 1, MPI_Strategy=dof).
 
void setupDofPartition (data::ModelData &data)
 Metis-partition nodes on the PD neighbor graph and build ghost plans. Call after d_neighPd is ready and before (or while) building d_fPdCompNodes. Works for Single_Particle and Multi_Particle when MPI_Strategy is dof. No-op for single-rank runs or other strategies.
 
void exchangeGhostDisplacement (data::ModelData &data)
 Halo-exchange nodal displacements (and current x) for PD ghosts.
 
void exchangeGhostTheta (data::ModelData &data)
 Halo-exchange state-based dilatation d_thetaX for PD ghosts.
 
std::unique_ptr< SelfContactmakeSelfContact (const std::string &name)
 Build self-contact law named by Model.Self_Contact.
 

Function Documentation

◆ computeForces()

void pd::computeForces ( data::ModelData data)

Assemble nodal peridynamic force (constitutive response stays in material).

Definition at line 28 of file pdForce.cpp.

28 {
29
30
31 util::io::log(3, " Computing peridynamic force \n");
32
33 const auto dim = data.d_modelDeck_p->d_dim;
34 const bool is_state = data.d_particlesListTypeAll[0]->getMaterial()->isStateActive();
35 const auto selfContact =
36 pd::makeSelfContact(data.d_modelDeck_p->d_selfContact);
37 const bool abs_stretch_break =
38 (data.d_modelDeck_p->d_bondBreak == "absolute_stretch");
39
40 // compute state-based helper quantities
41 if (is_state) {
42
43 tf::Executor executor(util::parallel::getNThreads());
44 tf::Taskflow taskflow;
45
46 taskflow.for_each_index(
47 (std::size_t) 0, data.d_fPdCompNodes.size(), (std::size_t) 1,
48 [&data, abs_stretch_break](std::size_t II) {
49 auto i = data.d_fPdCompNodes[II];
50
51 const auto rho = data.getDensity(i);
52 const auto &fix = data.d_fix[i];
53 const auto &ptId = data.getPtId(i);
54 auto &pi = data.getParticleFromAllList(ptId);
55
56 if (pi->d_material_p->isStateActive()) {
57
58 const double horizon = pi->getHorizon();
59 const double mesh_size = pi->getMeshSize();
60 const auto &xi = data.d_xRef[i];
61 const auto &ui = data.d_u[i];
62
63 // update bond state and compute thetax
64 const auto &m = data.d_mX[i];
65 double theta = 0.;
66
67 // upper and lower bound for volume correction
68 auto check_up = horizon + 0.5 * mesh_size;
69 auto check_low = horizon - 0.5 * mesh_size;
70
71 size_t k = 0;
72 for (size_t j : data.d_neighPd[i]) {
73
74 const auto &xj = data.d_xRef[j];
75 const auto &uj = data.d_u[j];
76 double rji = (xj - xi).length();
77 if (!(rji > 0.)) {
78 k += 1;
79 continue;
80 }
81 // double rji = std::sqrt(data.d_neighPdSqdDist[i][k]);
82 double change_length = (xj - xi + uj - ui).length() - rji;
83
84 // step 1: update the bond state
85 double s = change_length / rji;
86 double sc = pi->d_material_p->getSc(rji);
87
88 // get fracture state, modify, and set
89 auto fs = data.d_fracture_p->getBondState(i, k);
90 // Model.Bond_Break: tension (s > sc) or absolute_stretch (|s| > sc).
91 if (!fs) {
92 const bool broke = abs_stretch_break
93 ? util::isGreater(std::abs(s), sc + 1.0e-10)
94 : util::isGreater(s, sc + 1.0e-10);
95 if (broke)
96 fs = true;
97 }
98 data.d_fracture_p->setBondState(i, k, fs);
99
100 if (!fs) {
101
102 // get corrected volume of node j
103 auto volj = data.d_vol[j];
104
105 if (util::isGreater(rji, check_low))
106 volj *= (check_up - rji) / mesh_size;
107
108 theta += rji * change_length * pi->d_material_p->getInfFn(rji) *
109 volj;
110 } // if bond is not broken
111
112 k += 1;
113 } // loop over neighbors
114
115 data.d_thetaX[i] = 3. * theta / m;
116 } // if it is state-based
117 } // loop over nodes
118 ); // for_each
119
120 executor.run(taskflow).get();
121
122 // Ghost nodes need owner dilatations before the force pass.
124 }
125
126 // compute the internal forces
127 tf::Executor executor(util::parallel::getNThreads());
128 tf::Taskflow taskflow;
129
130 taskflow.for_each_index(
131 (std::size_t) 0, data.d_fPdCompNodes.size(), (std::size_t) 1,
132 [&data, selfContact = selfContact.get(), abs_stretch_break](std::size_t II) {
133 auto i = data.d_fPdCompNodes[II];
134
135 // local variable to hold force
136 util::Point force_i = util::Point();
137 double scalar_f = 0.;
138
139 // for damage
140 float Zi = 0.;
141 // Silling (2000) damage: phi = 1 - ∫ mu dV' / ∫ dV' over the horizon
142 // (reported by Silling 2003 and Trask). Bhattacharya & Lipton instead
143 // define damage as the broken-bond count fraction, kept separately.
144 double vol_all = 0.;
145 double vol_intact = 0.;
146 size_t n_bonds = 0;
147 size_t n_broken = 0;
148
149 const auto rhoi = data.getDensity(i);
150 const auto &ptIdi = data.getPtId(i);
151 auto &pi = data.getParticleFromAllList(ptIdi);
152
153 const double horizon = pi->getHorizon();
154 const double mesh_size = pi->getMeshSize();
155 const auto &xi = data.d_xRef[i];
156 const auto &ui = data.d_u[i];
157 const auto &mi = data.d_mX[i];
158 const auto &thetai = data.d_thetaX[i];
159
160 // upper and lower bound for volume correction
161 auto check_up = horizon + 0.5 * mesh_size;
162 auto check_low = horizon - 0.5 * mesh_size;
163
164 // loop over neighbors
165 {
166 size_t k = 0;
167 for (size_t j : data.d_neighPd[i]) {
168 auto fs = data.d_fracture_p->getBondState(i, k);
169 const auto &xj = data.d_xRef[j];
170 const auto &uj = data.d_u[j];
171 auto volj = data.d_vol[j];
172 double rji = (xj - xi).length();
173 if (!(rji > 0.)) {
174 k++;
175 continue;
176 }
177 double Sji = pi->d_material_p->getS(xj - xi, uj - ui);
178
179 if (!fs) {
180 const auto &mj = data.d_mX[j];
181 const auto &thetaj = data.d_thetaX[j];
182
183 // get corrected volume of node j
184 if (util::isGreater(rji, check_low))
185 volj *= (check_up - rji) / mesh_size;
186
187 // handle two cases differently
188 if (pi->d_material_p->isStateActive()) {
189
190 auto ef_i =
191 pi->d_material_p->getBondEF(rji, Sji, fs, mi, thetai);
192 auto ef_j =
193 pi->d_material_p->getBondEF(rji, Sji, fs, mj, thetaj);
194
195 // compute the contribution of bond force to force at i
196 scalar_f = (ef_i.second + ef_j.second) * volj;
197
198 force_i += scalar_f * pi->d_material_p->getBondForceDirection(
199 xj - xi, uj - ui);
200 } // if state-based
201 else {
202
203 // Bond-based materials (e.g. PMB) historically broke inside
204 // getBondEF using |s|>Sc. Honor Model.Bond_Break here instead:
205 // tension (default, literature PMB) vs absolute_stretch.
206 const double Sc = pi->d_material_p->getSc(rji);
207 const bool broke = abs_stretch_break
208 ? util::isGreater(std::abs(Sji), Sc + 1.0e-10)
209 : util::isGreater(Sji, Sc + 1.0e-10);
210 if (broke)
211 fs = true;
212 data.d_fracture_p->setBondState(i, k, fs);
213
214 if (!fs) {
215 auto ef =
216 pi->d_material_p->getBondEF(rji, Sji, fs, /*break_bonds=*/false);
217 scalar_f = ef.second * volj;
218 force_i += scalar_f * pi->d_material_p->getBondForceDirection(
219 xj - xi, uj - ui);
220 } else {
221 const auto yji = xj + uj - (xi + ui);
222 force_i +=
223 selfContact->force(yji, volj, pi->d_Kn, pi->d_Rc, rji);
224 }
225 } // if bond-based
226 } // if bond not broken
227 else {
228 const auto yji = xj + uj - (xi + ui);
229 force_i +=
230 selfContact->force(yji, volj, pi->d_Kn, pi->d_Rc, rji);
231 } // if bond is broken
232
233 // calculate damage
234 auto Sc = pi->d_material_p->getSc(rji);
235 if (util::isGreater(std::abs(Sji / Sc), Zi))
236 Zi = std::abs(Sji / Sc);
237
238 // damage function phi (bond-state weighted by neighbor volume)
239 vol_all += data.d_vol[j];
240 n_bonds++;
241 if (!data.d_fracture_p->getBondState(i, k))
242 vol_intact += data.d_vol[j];
243 else
244 n_broken++;
245
246 k++;
247 } // loop over neighbors
248
249 } // peridynamic force
250
251 // update force (we remove any force from
252 // previous steps and add peridynamics force)
253 data.d_f[i] = force_i;
254
255 data.d_Z[i] = Zi;
256 if (!data.d_phi.empty())
257 data.d_phi[i] =
258 (vol_all > 0.) ? static_cast<float>(1. - vol_intact / vol_all) : 0.f;
259 if (!data.d_phiBond.empty())
260 data.d_phiBond[i] =
261 (n_bonds > 0) ? static_cast<float>(double(n_broken) / double(n_bonds))
262 : 0.f;
263 }
264 ); // for_each
265
266 executor.run(taskflow).get();
267
268}
Definition contact.h:20
std::unique_ptr< SelfContact > makeSelfContact(const std::string &name)
Build self-contact law named by Model.Self_Contact.
void exchangeGhostTheta(data::ModelData &data)
Halo-exchange state-based dilatation d_thetaX for PD ghosts.
Definition pdMpi.cpp:317
void log(std::ostringstream &oss, bool screen_out=false, int printMpiRank=print_default_mpi_rank)
Global method to log the message.
Definition io.cpp:41
unsigned int getNThreads()
Get number of threads to be used by taskflow.

References exchangeGhostTheta(), util::parallel::getNThreads(), util::io::log(), and makeSelfContact().

Referenced by PeriDEMModel::computeForces().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ dofMpiEnabled()

bool pd::dofMpiEnabled ( const data::ModelData data)

True when nodal DOF MPI is active (mpiSize > 1, MPI_Strategy=dof).

Definition at line 29 of file pdMpi.cpp.

29 {
30 return data.d_pdDofMpi;
31}

◆ exchangeGhostDisplacement()

void pd::exchangeGhostDisplacement ( data::ModelData data)

Halo-exchange nodal displacements (and current x) for PD ghosts.

Definition at line 286 of file pdMpi.cpp.

286 {
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}
void exchangePoints(data::ModelData &data, std::vector< util::Point > &field)
Definition pdMpi.cpp:130
void syncAllOwnedPoints(data::ModelData &data, std::vector< util::Point > &field)
Definition pdMpi.cpp:113
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
int mpiSize()
Get size (number) of processors.
static void refreshVMagFromV(data::ModelData &data)
Definition pdMpi.cpp:279

References anonymous_namespace{pdMpi.cpp}::exchangePoints(), util::parallel::mpiSize(), refreshVMagFromV(), anonymous_namespace{pdMpi.cpp}::syncAllOwnedPoints(), and util::methods::timeDiff().

Referenced by PeriDEMModel::computeForces().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ exchangeGhostTheta()

void pd::exchangeGhostTheta ( data::ModelData data)

Halo-exchange state-based dilatation d_thetaX for PD ghosts.

Definition at line 317 of file pdMpi.cpp.

317 {
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}
void exchangeDoubles(data::ModelData &data, std::vector< double > &field)
Definition pdMpi.cpp:184
int mpiRank()
get rank (id) of this processor
MPI_Comm mpiComm()
Get MPI comm.

References anonymous_namespace{pdMpi.cpp}::exchangeDoubles(), util::parallel::mpiComm(), util::parallel::mpiRank(), and util::methods::timeDiff().

Referenced by computeForces().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ makeSelfContact()

std::unique_ptr< SelfContact > pd::makeSelfContact ( const std::string &  name)

Build self-contact law named by Model.Self_Contact.

Definition at line 68 of file selfContact.cpp.

68 {
69 if (name == "broken_bond_kn")
70 return std::make_unique<BrokenBondKnSelfContact>();
71 if (name == "reference_gap")
72 return std::make_unique<ReferenceGapSelfContact>();
73 if (name == "none")
74 return std::make_unique<NoneSelfContact>();
75
76 throw std::runtime_error(
77 "Unknown Model.Self_Contact '" + name +
78 "'. Supported: broken_bond_kn, reference_gap, none.");
79}

Referenced by computeForces(), and main().

Here is the caller graph for this function:

◆ setupDofPartition()

void pd::setupDofPartition ( data::ModelData data)

Metis-partition nodes on the PD neighbor graph and build ghost plans. Call after d_neighPd is ready and before (or while) building d_fPdCompNodes. Works for Single_Particle and Multi_Particle when MPI_Strategy is dof. No-op for single-rank runs or other strategies.

Definition at line 233 of file pdMpi.cpp.

233 {
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}
void buildGhostPlan(data::ModelData &data)
Definition pdMpi.cpp:40
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).
void print(const T &msg, int nt=print_default_tab, int printMpiRank=print_default_mpi_rank)
Prints formatted information.
Definition io.h:128
bool isMpiEnabled()
Function to check if MPI is enabled.

References anonymous_namespace{pdMpi.cpp}::buildGhostPlan(), util::parallel::isMpiEnabled(), mesh::metisGraphPartition(), util::parallel::mpiComm(), util::parallel::mpiRank(), util::parallel::mpiSize(), util::io::print(), and particle::resolvedMpiStrategy().

Referenced by PeriDEMModel::init().

Here is the call graph for this function:
Here is the caller graph for this function: