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< SelfContact > makeSelfContact (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, dim](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 // dilatation: 3/m in 3D, 2/m in 2D (Yang et al. 2024, eqs. 8-9)
116 data.d_thetaX[i] = double(dim) * theta / m;
117 } // if it is state-based
118 } // loop over nodes
119 ); // for_each
120
121 executor.run(taskflow).get();
122
123 // Ghost nodes need owner dilatations before the force pass.
125 }
126
127 // strain energy density per node
128 if (data.d_e.size() != data.d_x.size())
129 data.d_e.resize(data.d_x.size(), 0.f);
130
131 // compute the internal forces
132 tf::Executor executor(util::parallel::getNThreads());
133 tf::Taskflow taskflow;
134
135 taskflow.for_each_index(
136 (std::size_t) 0, data.d_fPdCompNodes.size(), (std::size_t) 1,
137 [&data, selfContact = selfContact.get(), abs_stretch_break](std::size_t II) {
138 auto i = data.d_fPdCompNodes[II];
139
140 // local variable to hold force
141 util::Point force_i = util::Point();
142 double scalar_f = 0.;
143 double energy_i = 0.;
144
145 // for damage
146 float Zi = 0.;
147 // Silling (2000) damage: phi = 1 - ∫ mu dV' / ∫ dV' over the horizon
148 // (reported by Silling 2003 and Trask). Bhattacharya & Lipton instead
149 // define damage as the broken-bond count fraction, kept separately.
150 double vol_all = 0.;
151 double vol_intact = 0.;
152 size_t n_bonds = 0;
153 size_t n_broken = 0;
154
155 const auto rhoi = data.getDensity(i);
156 const auto &ptIdi = data.getPtId(i);
157 auto &pi = data.getParticleFromAllList(ptIdi);
158
159 const double horizon = pi->getHorizon();
160 const double mesh_size = pi->getMeshSize();
161 const auto &xi = data.d_xRef[i];
162 const auto &ui = data.d_u[i];
163 const auto &mi = data.d_mX[i];
164 const auto &thetai = data.d_thetaX[i];
165
166 // upper and lower bound for volume correction
167 auto check_up = horizon + 0.5 * mesh_size;
168 auto check_low = horizon - 0.5 * mesh_size;
169
170 // loop over neighbors
171 {
172 size_t k = 0;
173 for (size_t j : data.d_neighPd[i]) {
174 auto fs = data.d_fracture_p->getBondState(i, k);
175 const auto &xj = data.d_xRef[j];
176 const auto &uj = data.d_u[j];
177 auto volj = data.d_vol[j];
178 double rji = (xj - xi).length();
179 if (!(rji > 0.)) {
180 k++;
181 continue;
182 }
183 double Sji = pi->d_material_p->getS(xj - xi, uj - ui);
184
185 if (!fs) {
186 const auto &mj = data.d_mX[j];
187 const auto &thetaj = data.d_thetaX[j];
188
189 // get corrected volume of node j
190 if (util::isGreater(rji, check_low))
191 volj *= (check_up - rji) / mesh_size;
192
193 // handle two cases differently
194 if (pi->d_material_p->isStateActive()) {
195
196 auto ef_i =
197 pi->d_material_p->getBondEF(rji, Sji, fs, mi, thetai);
198 auto ef_j =
199 pi->d_material_p->getBondEF(rji, Sji, fs, mj, thetaj);
200
201 // compute the contribution of bond force to force at i
202 scalar_f = (ef_i.second + ef_j.second) * volj;
203 energy_i += ef_i.first * volj;
204
205 force_i += scalar_f * pi->d_material_p->getBondForceDirection(
206 xj - xi, uj - ui);
207 } // if state-based
208 else {
209
210 // Bond-based materials (e.g. PMB) historically broke inside
211 // getBondEF using |s|>Sc. Honor Model.Bond_Break here instead:
212 // tension (default, literature PMB) vs absolute_stretch.
213 const double Sc = pi->d_material_p->getBreakSc(rji);
214 const bool broke = abs_stretch_break
215 ? util::isGreater(std::abs(Sji), Sc + 1.0e-10)
216 : util::isGreater(Sji, Sc + 1.0e-10);
217 if (broke)
218 fs = true;
219 data.d_fracture_p->setBondState(i, k, fs);
220
221 if (!fs) {
222 auto ef =
223 pi->d_material_p->getBondEF(rji, Sji, fs, /*break_bonds=*/false);
224 scalar_f = ef.second * volj;
225 energy_i += ef.first * volj;
226 force_i += scalar_f * pi->d_material_p->getBondForceDirection(
227 xj - xi, uj - ui);
228 } else {
229 const auto yji = xj + uj - (xi + ui);
230 force_i +=
231 selfContact->force(yji, volj, pi->d_Kn, pi->d_Rc, rji);
232 }
233 } // if bond-based
234 } // if bond not broken
235 else {
236 const auto yji = xj + uj - (xi + ui);
237 force_i +=
238 selfContact->force(yji, volj, pi->d_Kn, pi->d_Rc, rji);
239 } // if bond is broken
240
241 // calculate damage
242 auto Sc = pi->d_material_p->getSc(rji);
243 if (util::isGreater(std::abs(Sji / Sc), Zi))
244 Zi = std::abs(Sji / Sc);
245
246 // damage function phi (bond-state weighted by neighbor volume)
247 vol_all += data.d_vol[j];
248 n_bonds++;
249 if (!data.d_fracture_p->getBondState(i, k))
250 vol_intact += data.d_vol[j];
251 else
252 n_broken++;
253
254 k++;
255 } // loop over neighbors
256
257 } // peridynamic force
258
259 // update force (we remove any force from
260 // previous steps and add peridynamics force)
261 data.d_f[i] = force_i;
262 if (pi->d_material_p->isStateActive())
263 energy_i += pi->d_material_p->getDilatationEnergyDensity(thetai);
264 data.d_e[i] = static_cast<float>(energy_i);
265
266 data.d_Z[i] = Zi;
267 if (!data.d_phi.empty())
268 data.d_phi[i] =
269 (vol_all > 0.) ? static_cast<float>(1. - vol_intact / vol_all) : 0.f;
270 if (!data.d_phiBond.empty())
271 data.d_phiBond[i] =
272 (n_bonds > 0) ? static_cast<float>(double(n_broken) / double(n_bonds))
273 : 0.f;
274 }
275 ); // for_each
276
277 executor.run(taskflow).get();
278
279 // total strain energy over the nodes computed here
280 double te = 0.;
281 for (auto i : data.d_fPdCompNodes)
282 te += double(data.d_e[i]) * data.d_vol[i];
283 data.d_te = static_cast<float>(te);
284}
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:309
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:171
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: