PeriDEM 0.3.0
PeriDEM -- Peridynamics-based high-fidelity model for granular media
Loading...
Searching...
No Matches
contact.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 "contact.h"
12#include "damping.h"
13#include "policy.h"
14#include "wallContact.h"
15
16#include "data/modelData.h"
17#include "util/io.h"
20#include "nsearch/nsearch.h"
21#include "util/function.h"
22#include "util/matrix.h"
23#include "util/vecMethods.h"
24#include "util/point.h"
25#include "util/parallelUtil.h"
26#include "inp/input.h"
27
28#include <atomic>
29#include <cmath>
30#include <chrono>
31#include <format>
32#include <memory>
33#include <vector>
34
35#include <taskflow/taskflow/taskflow.hpp>
36#include <taskflow/taskflow/algorithm/for_each.hpp>
37
39 : d_pairForce(std::make_unique<PairForce>()),
40 d_damping(std::make_unique<Damping>()),
41 d_wallContact(std::make_unique<MeshedWallContact>()) {}
42
44
45
46 // loop over all particle zones and get minimum value of mesh size
47 size_t c = 0;
48 for (const auto *p : data.d_particlesListTypeAll) {
49
50 auto h = p->getMeshSize();
51 if (c == 0) {
52 data.d_hMin = h;
53 data.d_hMax = h;
54 c++;
55 }
56
57 if (util::isGreater(data.d_hMin, h))
58 data.d_hMin = h;
59 if (util::isGreater(h, data.d_hMax))
60 data.d_hMax = h;
61 }
62
63 util::io::log(1, std::format("{}: Contact setup\n hmin = {:.6f}, hmax = {:.6f} \n",
64 data.d_name, data.d_hMin, data.d_hMax));
65
66 data.d_maxContactR = 0.;
67
68 auto &contactDeck = data.d_particleDeck_p->d_contactDeck;
69
70 // Select pair / damping / wall implementations from deck (defaults = current).
71 setPairForce(makePairForce(contactDeck.d_frictionLaw));
72 setDamping(makeDamping(contactDeck.d_dampingLaw));
73 d_useNodeDamping = usesNodeDamping(contactDeck.d_dampingLaw);
74 setWallContact(makeWallContact(data.d_modelDeck_p->d_wallContact));
75 util::io::log(1, std::format(
76 " Damping_Law = {}, Friction_Law = {}, Correct_Volume = {}\n"
77 " Bond_Break = {}, Self_Contact = {}, Wall_Contact = {}\n",
78 contactDeck.d_dampingLaw, contactDeck.d_frictionLaw,
79 contactDeck.d_correctVolume ? "true" : "false",
80 data.d_modelDeck_p->d_bondBreak, data.d_modelDeck_p->d_selfContact,
81 data.d_modelDeck_p->d_wallContact));
82
83 // Pair bulk modulus from the two contact groups' materials.
84 // JSON Contact.K is optional (usually absent).
85 std::vector<double> bulk(contactDeck.d_data.size(), -1.);
86 for (const auto *p : data.d_particlesListTypeAll) {
87 if (p->getMaterial() == nullptr)
88 continue;
89 const auto cid = p->getGroupId("contact_id");
90 if (cid >= bulk.size())
91 continue;
92 bulk[cid] = p->getMaterial()->computeMaterialProperties(data.dimension()).d_K;
93 }
94
95 for (size_t i = 0; i < contactDeck.d_data.size(); i++) {
96 for (size_t j = 0; j < contactDeck.d_data.size(); j++) {
97
98 inp::ContactPairDeck *deck = &(contactDeck.d_data[i][j]);
99
100 if (deck->d_computeContactR)
101 deck->d_contactR *= data.d_hMin;
102
103 if (data.d_maxContactR < deck->d_contactR)
104 data.d_maxContactR = deck->d_contactR;
105
106 if (bulk[i] > 0. && bulk[j] > 0.)
107 deck->d_K = util::equivalentMass(bulk[i], bulk[j]);
108
109 // Kn
110 deck->d_Kn *= deck->d_KnFactor;
111
112 // Beta n
113 double log_e = std::log(deck->d_eps);
114 deck->d_betan =
115 deck->d_betanFactor *
116 (-2. * log_e * std::sqrt(1. / (M_PI * M_PI + log_e * log_e)));
117
118 util::io::log(1, std::format(" contact_radius = {:.6f}, hmin = {:.6f}, Kn = {:5.3e}, "
119 "Vmax = {:5.3e}, "
120 "betan = {:7.5f}, mu = {:.4f}, kappa = {:5.3e}\n",
121 deck->d_contactR, data.d_hMin, deck->d_Kn, deck->d_vMax,
122 deck->d_betan, deck->d_mu, deck->d_K));
123 }
124 }
125
126}
127
129
130
131 // initialize parameters
132 if (data.d_contNeighUpdateInterval == 0 and
133 util::isLess(data.d_contNeighSearchRadius, 1.e-16)) {
134 data.d_contNeighUpdateInterval = data.d_particleDeck_p->d_pNeighDeck.d_neighUpdateInterval;
135 data.d_contNeighTimestepCounter = data.d_n % data.d_contNeighUpdateInterval;
136 data.d_contNeighSearchRadius = data.d_maxContactR * data.d_particleDeck_p->d_pNeighDeck.d_sFactor;
137 }
138
139 // at data.d_n = 0, this function will be called twice because updateContactNeighborlist() will be
140 // called twice: one inside init() and second inside computeForces()
141 // so to match data.d_n and data.d_contNeighTimestepCounter in the initial stage of simulation, we need to handle the special case
142 if (data.d_n == 0) {
143 data.appendKeyData("update_contact_neigh_search_params_init_call_count", 1);
144
145 if (int(data.getKeyData("update_contact_neigh_search_params_init_call_count")) == 1)
146 return true;
147
148 if (int(data.getKeyData("update_contact_neigh_search_params_init_call_count")) == 2) {
149 data.d_contNeighTimestepCounter++;
150 return (data.d_contNeighTimestepCounter - 1) % data.d_contNeighUpdateInterval == 0;
151 }
152 }
153
154 // handle case of restart
155 if (data.d_modelDeck_p->d_isRestartActive and data.d_n == data.d_restartDeck_p->d_step) {
156 // assign correct value for restart step
157 data.d_contNeighTimestepCounter = data.d_n % data.d_contNeighUpdateInterval;
158 }
159
160 if (data.d_contNeighUpdateInterval == 1) {
161 // further optimization of parameters is not possible
162 data.d_contNeighSearchRadius = data.d_maxContactR;
163
164 // update counter and return condition for contact search
165 data.d_contNeighTimestepCounter++;
166 return (data.d_contNeighTimestepCounter - 1) % data.d_contNeighUpdateInterval == 0;
167 }
168
169 // check if we should proceed with parameter update
170 // param update is done at smaller interval than the search itself to avoid
171 // scenarios where particles suddenly move with a high velocity
172 size_t update_param_interval =
173 data.d_contNeighUpdateInterval > 5 ? size_t(
174 0.2 * data.d_contNeighUpdateInterval) : 1;
175
176 // check if we ought to update search parameters; if not, return
177 if (data.d_contNeighTimestepCounter > 0 and data.d_contNeighTimestepCounter % update_param_interval != 0) {
178 // update counter and return condition for contact search
179 data.d_contNeighTimestepCounter++;
180 return (data.d_contNeighTimestepCounter - 1) % data.d_contNeighUpdateInterval == 0;
181 }
182
183 // first update the maximum velocity in all particles
184 for (auto &pi : data.d_particlesListTypeAll) {
185 auto max_v_node = util::methods::maxIndex(data.d_vMag,
186 pi->d_globStart, pi->d_globEnd);
187
188 if (max_v_node > pi->d_globEnd or max_v_node < pi->d_globStart) {
189 std::cerr << std::format("Error: max_v_node = {} for "
190 "particle of id = {} is not in the limit.\n",
191 max_v_node, pi->getId())
192 << "Particle info = \n"
193 << pi->printStr()
194 << "\n\n Magnitude of velocity = "
195 << data.d_vMag[max_v_node] << "\n";
196 exit(EXIT_FAILURE);
197 }
198
199 data.d_maxVelocityParticlesListTypeAll[pi->getId()]
200 = data.d_vMag[max_v_node];
201 }
202
203 // find max velocity among all particles
204 data.d_maxVelocity = util::methods::max(data.d_maxVelocityParticlesListTypeAll);
205
206 // now we find the best parameters for contact search
207 auto up_interval_old = data.d_contNeighUpdateInterval;
208
209 // TO ensure that in data.d_neighUpdateInterval time steps, the search radius is above the
210 // distance traveled by object with velocity data.d_maxVelocity
211 // also multiply by a safety factor
212 double safety_factor = data.d_particleDeck_p->d_pNeighDeck.d_sFactor > 5 ? data.d_particleDeck_p->d_pNeighDeck.d_sFactor : 10;
213 auto max_search_r_from_contact_R = data.d_particleDeck_p->d_pNeighDeck.d_sFactor * data.d_maxContactR;
214 if (!std::isfinite(data.d_maxVelocity) || data.d_maxVelocity < 0.)
215 data.d_maxVelocity = 0.;
216 auto max_search_r = data.d_maxVelocity * data.d_currentDt
217 * data.d_particleDeck_p->d_pNeighDeck.d_neighUpdateInterval
218 * safety_factor;
219 if (!std::isfinite(max_search_r) || max_search_r < 0.)
220 max_search_r = 0.;
221
222
223 if (util::isGreater(max_search_r, max_search_r_from_contact_R )) {
224
225 data.d_contNeighUpdateInterval = size_t(data.d_maxContactR/(data.d_maxVelocity * data.d_currentDt));
226 if (up_interval_old > data.d_contNeighUpdateInterval) {
227 // issue warning
228 util::io::log(2, std::format("Warning: Contact search radius based on velocity is greater than "
229 "the max contact radius.\n"
230 "Warning: Adjusting contact neighborlist update interval.\n"
231 "{:>13} = {:4.6e}, time step = {}, "
232 "velocity-based r = {:4.6e}, max contact r = {:4.6e}\n",
233 "Time", data.d_time, data.d_n, max_search_r, max_search_r_from_contact_R), data.d_n % data.d_infoN == 0, 3);
234 }
235
236 data.d_contNeighSearchRadius = max_search_r_from_contact_R;
237 // reset time step counter for contact so that the contact list is updated in the current time step
238 // and the update cycle starts from the current time step
239 data.d_contNeighTimestepCounter = 0;
240
241 if (data.d_contNeighUpdateInterval < 1) {
242 data.d_contNeighUpdateInterval = 1;
243 data.d_contNeighSearchRadius = data.d_maxContactR;
244 }
245 }
246 else {
247 // update search radius
248 data.d_contNeighSearchRadius = data.d_contNeighUpdateInterval < 2 ? data.d_maxContactR : max_search_r_from_contact_R;
249 }
250
251 if (up_interval_old > data.d_contNeighUpdateInterval) {
252 util::io::log(2, std::format(" Contact neighbor parameters: \n"
253 " {:48s} = {:d}\n"
254 " {:48s} = {:d}\n"
255 " {:48s} = {:d}\n"
256 " {:48s} = {:4.6e}\n"
257 " {:48s} = {:4.6e}\n"
258 " {:48s} = {:4.6e}\n"
259 " {:48s} = {:4.6e}\n"
260 " {:48s} = {:4.6e}\n"
261 " {:48s} = {:4.6e}\n",
262 "time step", data.d_n,
263 "contact neighbor update interval",
264 data.d_contNeighUpdateInterval,
265 "contact neighbor update time step counter",
266 data.d_contNeighTimestepCounter,
267 "search radius", data.d_contNeighSearchRadius,
268 "max contact radius", data.d_maxContactR,
269 "search radius factor", data.d_particleDeck_p->d_pNeighDeck.d_sFactor,
270 "max search r from velocity", max_search_r,
271 "max search r from contact r", max_search_r_from_contact_R,
272 "max velocity", data.d_maxVelocity), data.d_n % data.d_infoN == 0, 3);
273 }
274
275 // update counter and return condition for contact search
276 data.d_contNeighTimestepCounter++;
277 return (data.d_contNeighTimestepCounter - 1) % data.d_contNeighUpdateInterval == 0;
278
279}
280
282
283
284 auto update = updateSearchParameters(data);
285
286 if (!update)
287 return;
288
289 // update contact neighborlist
290
291 // update the point cloud (make sure that data.d_x is updated along with displacement)
292 using steady_clock = std::chrono::steady_clock;
293 const bool mpi_prune =
295 data.d_mpiIncludeInContactCloud.size() == data.d_particlesListTypeAll.size();
296
297 // Particle-MPI: pruned cloud (owned + ghost grains + walls).
298 // DOF-MPI: full cloud — nodes of one grain may sit on many ranks.
299 std::vector<util::Point> local_cloud;
300 std::vector<size_t> local_to_global;
301 std::vector<size_t> local_pt_id;
302 std::unique_ptr<nsearch::NFlannSearchKd<3>> local_tree;
303
304 const bool use_full_cloud = data.d_pdDofMpi;
305 const bool use_prune = mpi_prune && !use_full_cloud;
306
307 double pt_cloud_update_time = 0.;
308 if (use_prune) {
309 local_cloud.reserve(data.d_x.size() / static_cast<size_t>(std::max(
311 1024);
312 for (auto *p : data.d_particlesListTypeAll) {
313 if (!data.d_mpiIncludeInContactCloud[p->getId()])
314 continue;
315 for (size_t i = 0; i < p->getNumNodes(); ++i) {
316 const size_t g = p->getNodeId(i);
317 local_to_global.push_back(g);
318 local_pt_id.push_back(p->getId());
319 local_cloud.push_back(data.d_x[g]);
320 }
321 }
322 local_tree = std::make_unique<nsearch::NFlannSearchKd<3>>(local_cloud, 0);
323 pt_cloud_update_time = local_tree->setInputCloud();
324 } else {
325 pt_cloud_update_time = data.d_nsearch_p->setInputCloud();
326 }
327 data.setKeyData("pt_cloud_update_time", pt_cloud_update_time);
328 data.appendKeyData("tree_compute_time", pt_cloud_update_time);
329 data.appendKeyData("avg_tree_update_time", pt_cloud_update_time/data.d_infoN);
330 data.setKeyData("contact_cloud_node_count",
331 static_cast<double>(use_prune ? local_cloud.size()
332 : data.d_x.size()));
333
334 if (data.d_neighC.size() != data.d_x.size())
335 data.d_neighC.resize(data.d_x.size());
336
337 {
338 tf::Executor executor(util::parallel::getNThreads());
339 tf::Taskflow taskflow;
340
341 // Only query owned grain + wall nodes (d_fContCompNodes). Remote grains
342 // stay in the search cloud as neighbors but are not search origins.
343 const auto &query_nodes = data.d_fContCompNodes;
344 const bool use_local = use_prune;
345 taskflow.for_each_index((std::size_t) 0, query_nodes.size(), (std::size_t) 1,
346 [&data, &query_nodes, use_local, &local_to_global,
347 &local_pt_id, &local_tree](std::size_t II) {
348 const size_t i = query_nodes[II];
349
350 if (data.d_contNeighSearchRadius <= 0. ||
351 !std::isfinite(data.d_contNeighSearchRadius))
352 return;
353
354 const auto &pi = data.d_ptId[i];
355 const auto &pi_particle = data.d_particlesListTypeAll[pi];
356
357 // Walls still search: contact reaction on the plate/cup needs neighC on
358 // wall nodes. Skip only when forces are not computed on this body.
359 bool perform_search_based_on_particle = true;
360 if (pi_particle->d_allDofsConstrained or !pi_particle->d_computeForce)
361 perform_search_based_on_particle = false;
362
363 if (perform_search_based_on_particle) {
364
365 std::vector<size_t> neighs;
366 std::vector<double> sqr_dist;
367
368 data.d_neighC[i].clear();
369
370 size_t n = 0;
371 if (use_local) {
372 n = local_tree->radiusSearchExcludeTag(
373 data.d_x[i], data.d_contNeighSearchRadius, neighs, sqr_dist,
374 data.d_ptId[i], local_pt_id);
375 for (auto &li : neighs)
376 li = local_to_global[li];
377 } else {
378 n = data.d_nsearch_p->radiusSearchExcludeTag(
379 data.d_x[i], data.d_contNeighSearchRadius, neighs, sqr_dist,
380 data.d_ptId[i], data.d_ptId);
381 }
382
383 if (n > 0) {
384 for (auto neigh: neighs) {
385 if (neigh != i)
386 data.d_neighC[i].push_back(neigh);
387 }
388 }
389 }
390 }); // for_each
391
392 executor.run(taskflow).get();
393 }
394
395
396 // handle particle-wall neighborlist (based on the data.d_neighC that we already computed)
397 data.d_neighWallNodes.resize(data.d_particlesListTypeAll.size());
398 data.d_neighWallNodesDistance.resize(data.d_particlesListTypeAll.size());
399 data.d_neighWallNodesCondensed.resize(data.d_particlesListTypeAll.size());
400
401 for (auto &pi : data.d_particlesListTypeParticle) {
402 // Particle-MPI: grain owner. DOF-MPI: every rank may own nodes of any grain.
403 if (!data.d_pdDofMpi && !particle::isLocallyOwned(*pi))
404 continue;
405
406 data.d_neighWallNodes[pi->getId()].resize(pi->getNumNodes());
407 data.d_neighWallNodesDistance[pi->getId()].resize(pi->getNumNodes());
408
409 // get all wall nodes that are within contact distance to the nodes of this particle
410 {
411 tf::Executor executor(util::parallel::getNThreads());
412 tf::Taskflow taskflow;
413
414 taskflow.for_each_index((std::size_t) 0,
415 pi->getNumNodes(),
416 (std::size_t) 1,
417 [&data, &pi](std::size_t i) {
418
419 auto i_glob = pi->getNodeId(i);
420 auto yi = data.d_x[i_glob];
421
422 const std::vector<size_t> &neighs = data.d_neighC[i_glob];
423
424 data.d_neighWallNodes[pi->getId()][i].clear();
425 data.d_neighWallNodesDistance[pi->getId()][i].clear();
426
427 for (const auto &j_id: neighs) {
428
429 auto &ptIdj = data.d_ptId[j_id];
430 auto &pj = data.getParticleFromAllList(
431 ptIdj);
432
433 // we are only interested in nodes from wall
434 if (pj->isWall()) {
435 data.d_neighWallNodes[pi->getId()][i].push_back(j_id);
436 //data.d_neighWallNodesDistance[pi->getId()][i].push_back(Rji);
437 }
438 }
439 }
440 ); // for_each
441
442 executor.run(taskflow).get();
443 }
444 } // loop over particles
445
446
447}
448
450
451 util::io::log(3, " Computing normal contact force \n");
452
453 auto *pair = d_pairForce.get();
454 const bool use_node_damping = d_useNodeDamping;
455 const bool skip_meshed_wall =
456 (d_wallContact && d_wallContact->skipsMeshedGrainWall());
457 const bool correct_volume =
458 data.d_particleDeck_p->d_contactDeck.d_correctVolume;
459 pair->beginStep();
460
461 // Diagnostics across contact assembly (max over active pairs this step).
462 std::atomic<double> max_pen{0.};
463 std::atomic<double> max_fij{0.};
464 std::atomic<double> min_rji{1.e300};
465 std::atomic<size_t> n_active{0};
466 std::atomic<size_t> max_neigh{0};
467
468 // Wall force deposits from many grain nodes touch the same wall dof.
469 // Keep this loop single-threaded under MPI to avoid races on d_f[wall].
470 const unsigned n_workers =
472 tf::Executor executor(n_workers);
473 tf::Taskflow taskflow;
474
475 taskflow.for_each_index((std::size_t) 0,
476 data.d_fContCompNodes.size(),
477 (std::size_t) 1,
478 [&data, pair, use_node_damping, correct_volume,
479 skip_meshed_wall,
480 &max_pen, &max_fij, &min_rji, &n_active,
481 &max_neigh](std::size_t II) {
482
483 auto i = data.d_fContCompNodes[II];
484
485 util::Point force_i = util::Point();
486 util::Point damp_i = util::Point();
487
488 const auto &ptIdi = data.getPtId(i);
489 auto &pi = data.getParticleFromAllList(ptIdi);
490
491 // Under MPI, do not search from walls — grain
492 // nodes deposit Newton-III / volume-scaled forces
493 // onto walls (avoids incomplete wall clouds and
494 // double-counting).
495 if (pi->isWall() && util::parallel::isMpiEnabled())
496 return;
497
498 const auto &yi = data.d_x[i];
499 const auto &vi = data.d_v[i];
500 const double voli = data.d_vol[i];
501 const double hi = pi->getMeshSize();
502 const std::vector<size_t> &neighs = data.d_neighC[i];
503 {
504 size_t nn = neighs.size();
505 size_t prev = max_neigh.load(std::memory_order_relaxed);
506 while (nn > prev &&
507 !max_neigh.compare_exchange_weak(
508 prev, nn, std::memory_order_relaxed)) {
509 }
510 }
511
512 for (const auto &j_id: neighs) {
513 if (j_id == i)
514 continue;
515
516 const auto &ptIdj = data.d_ptId[j_id];
517 if (ptIdj == ptIdi)
518 continue;
519
520 auto &pj = data.getParticleFromAllList(ptIdj);
521 if (pi->isWall() and pj->isWall())
522 continue;
523
524 // Analytical walls: skip meshed grain–wall pairs
525 // (handled by WallContact::apply).
526 if (skip_meshed_wall &&
527 (pi->isWall() || pj->isWall()))
528 continue;
529
530 const auto &contact =
531 data.d_particleDeck_p->d_contactDeck.getContact(
532 pi->getGroupId("contact_id"),
533 pj->getGroupId("contact_id"));
534
535 const auto yji = data.d_x[j_id] - yi;
536 const double Rji = yji.length();
537 if (Rji > 0. && util::isLess(Rji, contact.d_contactR)) {
538 const double pen = contact.d_contactR - Rji;
539 double prev_pen =
540 max_pen.load(std::memory_order_relaxed);
541 while (pen > prev_pen &&
542 !max_pen.compare_exchange_weak(
543 prev_pen, pen,
544 std::memory_order_relaxed)) {
545 }
546 double prev_r =
547 min_rji.load(std::memory_order_relaxed);
548 while (Rji < prev_r &&
549 !min_rji.compare_exchange_weak(
550 prev_r, Rji,
551 std::memory_order_relaxed)) {
552 }
553 n_active.fetch_add(1, std::memory_order_relaxed);
554 }
555
556 // Partial Vj near Rc when Correct_Volume=true
557 // (default). false → full Vj (main DEM contact).
558 const double volj_raw = data.d_vol[j_id];
559 const double volj =
560 correct_volume
562 volj_raw, Rji, contact.d_contactR,
563 hi)
564 : volj_raw;
565
566 Pair p{contact,
567 yi, data.d_x[j_id],
568 vi, data.d_v[j_id],
569 i, j_id,
570 ptIdi, ptIdj,
571 voli, volj,
572 pi->getDensity(), pj->getDensity(),
573 data.d_currentDt,
574 pi->isWall(), pj->isWall()};
575 const util::Point fs = pair->springForce(p);
576 util::Point fd;
577 if (use_node_damping)
578 fd = pair->nodeDampingForce(p);
579 const double fij_mag = (fs + fd).length();
580 if (fij_mag > 0.) {
581 double prev_f =
582 max_fij.load(std::memory_order_relaxed);
583 while (fij_mag > prev_f &&
584 !max_fij.compare_exchange_weak(
585 prev_f, fij_mag,
586 std::memory_order_relaxed)) {
587 }
588 }
589 force_i += fs;
590 damp_i += fd;
591 // Under MPI, walls do not search (see above), so
592 // deposit Newton-III onto the wall here. Serial
593 // still searches from walls — depositing as well
594 // would double-count and can blow up at contact.
595 if (!pi->isWall() && pj->isWall() &&
597 // fs is force density on i (∝ Vj used above).
598 // Deposit Newton-III onto the wall in density
599 // form using grain/wall volume ratio.
600 const double scale =
601 (volj_raw > 0.) ? (voli / volj_raw) : 0.;
602 data.d_f[j_id] -= scale * fs;
603 }
604 }
605
606 data.d_f[i] += force_i + damp_i;
607 }
608 );
609
610 executor.run(taskflow).get();
611 pair->endStep();
612
613 if (d_wallContact)
614 d_wallContact->apply(data, pair, use_node_damping);
615
616 // Log contact diagnostics near plate onset / whenever pairs go deep.
617 const bool periodic = (data.d_infoN > 0 && data.d_n % data.d_infoN == 0);
618 const bool deep = max_pen.load() > 0.25 * (data.d_hMin > 0. ? data.d_hMin : 1.);
619 const bool hot = max_fij.load() > 1.e2;
620 if (periodic || deep || hot) {
621 const double mr = min_rji.load();
622 // Always emit (bypass debug-level gate); also mirror to stdout.
624 1,
625 std::format(" CONTACT_DIAG n={} t={:.6e} n_active={} max_neigh={} "
626 "max_pen={:.6e} min_Rji={:.6e} max_|fij|={:.6e}\n",
627 data.d_n, data.d_time, n_active.load(), max_neigh.load(),
628 max_pen.load(), (mr < 1.e300 ? mr : -1.), max_fij.load()),
629 false, -1, true);
630 }
631
632 if (d_damping)
633 d_damping->apply(data);
634}
void updateNeighborlist(data::ModelData &data)
Definition contact.cpp:281
void computeForces(data::ModelData &data)
Definition contact.cpp:449
void setup(data::ModelData &data)
Definition contact.cpp:43
bool updateSearchParameters(data::ModelData &data)
Definition contact.cpp:128
A class to store model data.
Definition modelData.h:50
std::unique_ptr< Damping > makeDamping(const std::string &name)
Build COM damping object, or nullptr when the law has no COM term.
Definition policy.cpp:29
std::unique_ptr< PairForce > makePairForce(const std::string &friction_law)
Build pair-force object from friction-law name.
Definition policy.cpp:17
std::unique_ptr< WallContact > makeWallContact(const std::string &name)
double correctedContactVolume(double volj, double Rji, double Rc, double h)
Definition pairForce.cpp:19
bool usesNodeDamping(const std::string &damping_law)
True for node / com_and_node.
Definition policy.cpp:43
Definition contact.h:20
bool isLocallyOwned(const BaseParticle &p)
True if this rank updates / assembles forces for the particle. Walls are replicated on every rank....
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
T max(const std::vector< T > &data)
Returns the maximum from list of data.
Definition vecMethods.h:74
size_t maxIndex(const std::vector< T > &data)
Returns the index corresponding to maximum from list of data.
Definition vecMethods.h:38
bool isMpiEnabled()
Function to check if MPI is enabled.
unsigned int getNThreads()
Get number of threads to be used by taskflow.
int mpiSize()
Get size (number) of processors.
bool isGreater(const double &a, const double &b)
Returns true if a > b.
Definition function.cpp:15
double equivalentMass(const double &m1, const double &m2)
Compute harmonic mean of m1 and m2.
Definition function.cpp:127
bool isLess(const double &a, const double &b)
Returns true if a < b.
Definition function.cpp:20
One node-node contact pair. Assembly fills this; the law uses it.
Definition pairForce.h:27
Structure to read and store particle-particle contact related input data.
double d_contactR
contact radius
double d_eps
parameters for normal damping force
bool d_computeContactR
Flag that indicates whether contact radius is to be computed.
double d_betan
parameters for normal damping force
double d_betanFactor
parameters for normal damping force
double d_vMax
parameters for normal force
double d_KnFactor
parameters for normal force
double d_Kn
parameters for normal force
double d_mu
parameters for frictional force
double d_K
parameters for frictional force
A structure to represent 3d vectors.
Definition point.h:30