PeriDEM 0.3.0
PeriDEM -- Peridynamics-based high-fidelity model for granular media
Loading...
Searching...
No Matches
contact::Contact Class Reference

Contact neighbor search and force assembly. Pair law and damping are separate types on this object. More...

#include <contact.h>

Collaboration diagram for contact::Contact:

Public Member Functions

 Contact ()
 
virtual ~Contact ()=default
 
void setup (data::ModelData &data)
 
bool updateSearchParameters (data::ModelData &data)
 
void updateNeighborlist (data::ModelData &data)
 
void computeForces (data::ModelData &data)
 
void setPairForce (std::unique_ptr< PairForce > p)
 
void setDamping (std::unique_ptr< Damping > d)
 
void setWallContact (std::unique_ptr< WallContact > w)
 

Data Fields

std::unique_ptr< PairForce > d_pairForce
 
std::unique_ptr< Damping > d_damping
 
std::unique_ptr< WallContact > d_wallContact
 
bool d_useNodeDamping = true
 Apply node-level damping inside the pair assembly loop.
 

Detailed Description

Contact neighbor search and force assembly. Pair law and damping are separate types on this object.

Definition at line 28 of file contact.h.

Constructor & Destructor Documentation

◆ Contact()

contact::Contact::Contact ( )

Definition at line 39 of file contact.cpp.

40 : d_pairForce(std::make_unique<PairForce>()),
41 d_damping(std::make_unique<Damping>()),
42 d_wallContact(std::make_unique<MeshedWallContact>()) {}
std::unique_ptr< PairForce > d_pairForce
Definition contact.h:44
std::unique_ptr< Damping > d_damping
Definition contact.h:45
std::unique_ptr< WallContact > d_wallContact
Definition contact.h:46

◆ ~Contact()

virtual contact::Contact::~Contact ( )
virtualdefault

Member Function Documentation

◆ computeForces()

void contact::Contact::computeForces ( data::ModelData &  data)

Definition at line 451 of file contact.cpp.

451 {
452
453 util::io::log(3, " Computing normal contact force \n");
454
455 auto *pair = d_pairForce.get();
456 const bool use_node_damping = d_useNodeDamping;
457 const bool skip_meshed_wall =
458 (d_wallContact && d_wallContact->skipsMeshedGrainWall());
459 const bool correct_volume =
460 data.d_particleDeck_p->d_contactDeck.d_correctVolume;
461 pair->beginStep();
462
463 // Diagnostics across contact assembly (max over active pairs this step).
464 std::atomic<double> max_pen{0.};
465 std::atomic<double> max_fij{0.};
466 std::atomic<double> min_rji{1.e300};
467 std::atomic<size_t> n_active{0};
468 std::atomic<size_t> max_neigh{0};
469
470 // Wall force deposits from many grain nodes touch the same wall dof.
471 // Keep this loop single-threaded under MPI to avoid races on d_f[wall].
472 const unsigned n_workers =
474 tf::Executor executor(n_workers);
475 tf::Taskflow taskflow;
476
477 taskflow.for_each_index((std::size_t) 0,
478 data.d_fContCompNodes.size(),
479 (std::size_t) 1,
480 [&data, pair, use_node_damping, correct_volume,
481 skip_meshed_wall,
482 &max_pen, &max_fij, &min_rji, &n_active,
483 &max_neigh](std::size_t II) {
484
485 auto i = data.d_fContCompNodes[II];
486
487 util::Point force_i = util::Point();
488 util::Point damp_i = util::Point();
489
490 const auto &ptIdi = data.getPtId(i);
491 auto &pi = data.getParticleFromAllList(ptIdi);
492
493 // Under MPI, do not search from walls — grain
494 // nodes deposit Newton-III / volume-scaled forces
495 // onto walls (avoids incomplete wall clouds and
496 // double-counting).
497 if (pi->isWall() && util::parallel::isMpiEnabled())
498 return;
499
500 const auto &yi = data.d_x[i];
501 const auto &vi = data.d_v[i];
502 const double voli = data.d_vol[i];
503 const double hi = pi->getMeshSize();
504 const std::vector<size_t> &neighs = data.d_neighC[i];
505 {
506 size_t nn = neighs.size();
507 size_t prev = max_neigh.load(std::memory_order_relaxed);
508 while (nn > prev &&
509 !max_neigh.compare_exchange_weak(
510 prev, nn, std::memory_order_relaxed)) {
511 }
512 }
513
514 for (const auto &j_id: neighs) {
515 if (j_id == i)
516 continue;
517
518 const auto &ptIdj = data.d_ptId[j_id];
519 if (ptIdj == ptIdi)
520 continue;
521
522 auto &pj = data.getParticleFromAllList(ptIdj);
523 if (pi->isWall() and pj->isWall())
524 continue;
525
526 // Analytical walls: skip meshed grain–wall pairs
527 // (handled by WallContact::apply).
528 if (skip_meshed_wall &&
529 (pi->isWall() || pj->isWall()))
530 continue;
531
532 const auto &contact =
533 data.d_particleDeck_p->d_contactDeck.getContact(
534 pi->getGroupId("contact_id"),
535 pj->getGroupId("contact_id"));
536
537 const auto yji = data.d_x[j_id] - yi;
538 const double Rji = yji.length();
539 if (Rji > 0. && util::isLess(Rji, contact.d_contactR)) {
540 const double pen = contact.d_contactR - Rji;
541 double prev_pen =
542 max_pen.load(std::memory_order_relaxed);
543 while (pen > prev_pen &&
544 !max_pen.compare_exchange_weak(
545 prev_pen, pen,
546 std::memory_order_relaxed)) {
547 }
548 double prev_r =
549 min_rji.load(std::memory_order_relaxed);
550 while (Rji < prev_r &&
551 !min_rji.compare_exchange_weak(
552 prev_r, Rji,
553 std::memory_order_relaxed)) {
554 }
555 n_active.fetch_add(1, std::memory_order_relaxed);
556 }
557
558 // Partial Vj near Rc when Correct_Volume=true
559 // (default). false → full Vj (main DEM contact).
560 const double volj_raw = data.d_vol[j_id];
561 const double volj =
562 correct_volume
564 volj_raw, Rji, contact.d_contactR,
565 hi)
566 : volj_raw;
567
568 Pair p{contact,
569 yi, data.d_x[j_id],
570 vi, data.d_v[j_id],
571 i, j_id,
572 ptIdi, ptIdj,
573 voli, volj,
574 pi->getDensity(), pj->getDensity(),
575 data.d_currentDt,
576 pi->isWall(), pj->isWall()};
577 const util::Point fs = pair->springForce(p);
578 util::Point fd;
579 if (use_node_damping)
580 fd = pair->nodeDampingForce(p);
581 const double fij_mag = (fs + fd).length();
582 if (fij_mag > 0.) {
583 double prev_f =
584 max_fij.load(std::memory_order_relaxed);
585 while (fij_mag > prev_f &&
586 !max_fij.compare_exchange_weak(
587 prev_f, fij_mag,
588 std::memory_order_relaxed)) {
589 }
590 }
591 force_i += fs;
592 damp_i += fd;
593 // Under MPI, walls do not search (see above), so
594 // deposit Newton-III onto the wall here. Serial
595 // still searches from walls — depositing as well
596 // would double-count and can blow up at contact.
597 if (!pi->isWall() && pj->isWall() &&
599 // fs is force density on i (∝ Vj used above).
600 // Deposit Newton-III onto the wall in density
601 // form using grain/wall volume ratio.
602 const double scale =
603 (volj_raw > 0.) ? (voli / volj_raw) : 0.;
604 data.d_f[j_id] -= scale * fs;
605 }
606 }
607
608 data.d_f[i] += force_i + damp_i;
609 }
610 );
611
612 executor.run(taskflow).get();
613 pair->endStep();
614
615 if (d_wallContact)
616 d_wallContact->apply(data, pair, use_node_damping);
617
618 // Log contact diagnostics near plate onset / whenever pairs go deep.
619 const bool periodic = (data.d_infoN > 0 && data.d_n % data.d_infoN == 0);
620 const bool deep = max_pen.load() > 0.25 * (data.d_hMin > 0. ? data.d_hMin : 1.);
621 const bool hot = max_fij.load() > 1.e2;
622 if (periodic || deep || hot) {
623 const double mr = min_rji.load();
624 // Always emit (bypass debug-level gate); also mirror to stdout.
626 1,
627 std::format(" CONTACT_DIAG n={} t={:.6e} n_active={} max_neigh={} "
628 "max_pen={:.6e} min_Rji={:.6e} max_|fij|={:.6e}\n",
629 data.d_n, data.d_time, n_active.load(), max_neigh.load(),
630 max_pen.load(), (mr < 1.e300 ? mr : -1.), max_fij.load()),
631 false, -1, true);
632 }
633
634 if (d_damping)
635 d_damping->apply(data);
636}
bool d_useNodeDamping
Apply node-level damping inside the pair assembly loop.
Definition contact.h:48
double correctedContactVolume(double volj, double Rji, double Rc, double h)
Definition pairForce.cpp:19
Definition contact.h:20
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
bool isMpiEnabled()
Function to check if MPI is enabled.
unsigned int getNThreads()
Get number of threads to be used by taskflow.
bool isLess(const double &a, const double &b)
Returns true if a < b.
Definition function.cpp:22
A structure to represent 3d vectors.
Definition point.h:30

References contact::correctedContactVolume(), util::parallel::getNThreads(), util::isLess(), util::parallel::isMpiEnabled(), and util::io::log().

Here is the call graph for this function:

◆ setDamping()

void contact::Contact::setDamping ( std::unique_ptr< Damping >  d)
inline

Definition at line 39 of file contact.h.

39{ d_damping = std::move(d); }

References d_damping.

◆ setPairForce()

void contact::Contact::setPairForce ( std::unique_ptr< PairForce >  p)
inline

Definition at line 38 of file contact.h.

38{ d_pairForce = std::move(p); }

References d_pairForce.

◆ setup()

void contact::Contact::setup ( data::ModelData &  data)

Definition at line 44 of file contact.cpp.

44 {
45
46
47 // loop over all particle zones and get minimum value of mesh size
48 size_t c = 0;
49 for (const auto *p : data.d_particlesListTypeAll) {
50
51 auto h = p->getMeshSize();
52 if (c == 0) {
53 data.d_hMin = h;
54 data.d_hMax = h;
55 c++;
56 }
57
58 if (util::isGreater(data.d_hMin, h))
59 data.d_hMin = h;
60 if (util::isGreater(h, data.d_hMax))
61 data.d_hMax = h;
62 }
63
64 util::io::log(1, std::format("{}: Contact setup\n hmin = {:.6f}, hmax = {:.6f} \n",
65 data.d_name, data.d_hMin, data.d_hMax));
66
67 data.d_maxContactR = 0.;
68
69 auto &contactDeck = data.d_particleDeck_p->d_contactDeck;
70
71 // Select pair / damping / wall implementations from deck (defaults = current).
72 setPairForce(makePairForce(contactDeck.d_frictionLaw));
73 setDamping(makeDamping(contactDeck.d_dampingLaw));
74 d_useNodeDamping = usesNodeDamping(contactDeck.d_dampingLaw);
75 setWallContact(makeWallContact(data.d_modelDeck_p->d_wallContact));
76 util::io::log(1, std::format(
77 " Damping_Law = {}, Friction_Law = {}, Correct_Volume = {}\n"
78 " Bond_Break = {}, Self_Contact = {}, Wall_Contact = {}\n",
79 contactDeck.d_dampingLaw, contactDeck.d_frictionLaw,
80 contactDeck.d_correctVolume ? "true" : "false",
81 data.d_modelDeck_p->d_bondBreak, data.d_modelDeck_p->d_selfContact,
82 data.d_modelDeck_p->d_wallContact));
83
84 // Pair bulk modulus from the two contact groups' materials.
85 // JSON Contact.K is optional (usually absent).
86 std::vector<double> bulk(contactDeck.d_data.size(), -1.);
87 for (const auto *p : data.d_particlesListTypeAll) {
88 if (p->getMaterial() == nullptr)
89 continue;
90 const auto cid = p->getGroupId("contact_id");
91 if (cid >= bulk.size())
92 continue;
93 bulk[cid] = p->getMaterial()->computeMaterialProperties(data.dimension()).d_K;
94 }
95
96 for (size_t i = 0; i < contactDeck.d_data.size(); i++) {
97 for (size_t j = 0; j < contactDeck.d_data.size(); j++) {
98
99 inp::ContactPairDeck *deck = &(contactDeck.d_data[i][j]);
100
101 if (deck->d_computeContactR)
102 deck->d_contactR *= data.d_hMin;
103
104 if (data.d_maxContactR < deck->d_contactR)
105 data.d_maxContactR = deck->d_contactR;
106
107 if (bulk[i] > 0. && bulk[j] > 0.)
108 deck->d_K = util::equivalentMass(bulk[i], bulk[j]);
109
110 // Kn
111 deck->d_Kn *= deck->d_KnFactor;
112
113 // Beta n
114 double log_e = std::log(deck->d_eps);
115 deck->d_betan =
116 deck->d_betanFactor *
117 (-2. * log_e * std::sqrt(1. / (M_PI * M_PI + log_e * log_e)));
118
119 util::io::log(1, std::format(" contact_radius = {:.6f}, hmin = {:.6f}, Kn = {:5.3e}, "
120 "Vmax = {:5.3e}, "
121 "betan = {:7.5f}, mu = {:.4f}, kappa = {:5.3e}\n",
122 deck->d_contactR, data.d_hMin, deck->d_Kn, deck->d_vMax,
123 deck->d_betan, deck->d_mu, deck->d_K));
124 }
125 }
126
127}
void setWallContact(std::unique_ptr< WallContact > w)
Definition contact.h:40
void setPairForce(std::unique_ptr< PairForce > p)
Definition contact.h:38
void setDamping(std::unique_ptr< Damping > d)
Definition contact.h:39
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)
bool usesNodeDamping(const std::string &damping_law)
True for node / com_and_node.
Definition policy.cpp:43
bool isGreater(const double &a, const double &b)
Returns true if a > b.
Definition function.cpp:17
double equivalentMass(const double &m1, const double &m2)
Compute harmonic mean of m1 and m2.
Definition function.cpp:131
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

References inp::ContactPairDeck::d_betan, inp::ContactPairDeck::d_betanFactor, inp::ContactPairDeck::d_computeContactR, inp::ContactPairDeck::d_contactR, inp::ContactPairDeck::d_eps, inp::ContactPairDeck::d_K, inp::ContactPairDeck::d_Kn, inp::ContactPairDeck::d_KnFactor, inp::ContactPairDeck::d_mu, inp::ContactPairDeck::d_vMax, util::equivalentMass(), util::isGreater(), util::io::log(), contact::makeDamping(), contact::makePairForce(), contact::makeWallContact(), and contact::usesNodeDamping().

Here is the call graph for this function:

◆ setWallContact()

void contact::Contact::setWallContact ( std::unique_ptr< WallContact >  w)
inline

Definition at line 40 of file contact.h.

40 {
41 d_wallContact = std::move(w);
42 }

References d_wallContact.

◆ updateNeighborlist()

void contact::Contact::updateNeighborlist ( data::ModelData &  data)

Definition at line 283 of file contact.cpp.

283 {
284
285
286 auto update = updateSearchParameters(data);
287
288 if (!update)
289 return;
290
291 // update contact neighborlist
292
293 // update the point cloud (make sure that data.d_x is updated along with displacement)
294 using steady_clock = std::chrono::steady_clock;
295 const bool mpi_prune =
297 data.d_mpiIncludeInContactCloud.size() == data.d_particlesListTypeAll.size();
298
299 // Particle-MPI: pruned cloud (owned + ghost grains + walls).
300 // DOF-MPI: full cloud — nodes of one grain may sit on many ranks.
301 std::vector<util::Point> local_cloud;
302 std::vector<size_t> local_to_global;
303 std::vector<size_t> local_pt_id;
304 std::unique_ptr<nsearch::NFlannSearchKd<3>> local_tree;
305
306 const bool use_full_cloud = data.d_pdDofMpi;
307 const bool use_prune = mpi_prune && !use_full_cloud;
308
309 double pt_cloud_update_time = 0.;
310 if (use_prune) {
311 local_cloud.reserve(data.d_x.size() / static_cast<size_t>(std::max(
313 1024);
314 for (auto *p : data.d_particlesListTypeAll) {
315 if (!data.d_mpiIncludeInContactCloud[p->getId()])
316 continue;
317 for (size_t i = 0; i < p->getNumNodes(); ++i) {
318 const size_t g = p->getNodeId(i);
319 local_to_global.push_back(g);
320 local_pt_id.push_back(p->getId());
321 local_cloud.push_back(data.d_x[g]);
322 }
323 }
324 local_tree = std::make_unique<nsearch::NFlannSearchKd<3>>(local_cloud, 0);
325 pt_cloud_update_time = local_tree->setInputCloud();
326 } else {
327 pt_cloud_update_time = data.d_nsearch_p->setInputCloud();
328 }
329 data.setKeyData("pt_cloud_update_time", pt_cloud_update_time);
330 data.appendKeyData("tree_compute_time", pt_cloud_update_time);
331 data.appendKeyData("avg_tree_update_time", pt_cloud_update_time/data.d_infoN);
332 data.setKeyData("contact_cloud_node_count",
333 static_cast<double>(use_prune ? local_cloud.size()
334 : data.d_x.size()));
335
336 if (data.d_neighC.size() != data.d_x.size())
337 data.d_neighC.resize(data.d_x.size());
338
339 {
340 tf::Executor executor(util::parallel::getNThreads());
341 tf::Taskflow taskflow;
342
343 // Only query owned grain + wall nodes (d_fContCompNodes). Remote grains
344 // stay in the search cloud as neighbors but are not search origins.
345 const auto &query_nodes = data.d_fContCompNodes;
346 const bool use_local = use_prune;
347 taskflow.for_each_index((std::size_t) 0, query_nodes.size(), (std::size_t) 1,
348 [&data, &query_nodes, use_local, &local_to_global,
349 &local_pt_id, &local_tree](std::size_t II) {
350 const size_t i = query_nodes[II];
351
352 if (data.d_contNeighSearchRadius <= 0. ||
353 !std::isfinite(data.d_contNeighSearchRadius))
354 return;
355
356 const auto &pi = data.d_ptId[i];
357 const auto &pi_particle = data.d_particlesListTypeAll[pi];
358
359 // Walls still search: contact reaction on the plate/cup needs neighC on
360 // wall nodes. Skip only when forces are not computed on this body.
361 bool perform_search_based_on_particle = true;
362 if (pi_particle->d_allDofsConstrained or !pi_particle->d_computeForce)
363 perform_search_based_on_particle = false;
364
365 if (perform_search_based_on_particle) {
366
367 std::vector<size_t> neighs;
368 std::vector<double> sqr_dist;
369
370 data.d_neighC[i].clear();
371
372 size_t n = 0;
373 if (use_local) {
374 n = local_tree->radiusSearchExcludeTag(
375 data.d_x[i], data.d_contNeighSearchRadius, neighs, sqr_dist,
376 data.d_ptId[i], local_pt_id);
377 for (auto &li : neighs)
378 li = local_to_global[li];
379 } else {
380 n = data.d_nsearch_p->radiusSearchExcludeTag(
381 data.d_x[i], data.d_contNeighSearchRadius, neighs, sqr_dist,
382 data.d_ptId[i], data.d_ptId);
383 }
384
385 if (n > 0) {
386 for (auto neigh: neighs) {
387 if (neigh != i)
388 data.d_neighC[i].push_back(neigh);
389 }
390 }
391 }
392 }); // for_each
393
394 executor.run(taskflow).get();
395 }
396
397
398 // handle particle-wall neighborlist (based on the data.d_neighC that we already computed)
399 data.d_neighWallNodes.resize(data.d_particlesListTypeAll.size());
400 data.d_neighWallNodesDistance.resize(data.d_particlesListTypeAll.size());
401 data.d_neighWallNodesCondensed.resize(data.d_particlesListTypeAll.size());
402
403 for (auto &pi : data.d_particlesListTypeParticle) {
404 // Particle-MPI: grain owner. DOF-MPI: every rank may own nodes of any grain.
405 if (!data.d_pdDofMpi && !particle::isLocallyOwned(*pi))
406 continue;
407
408 data.d_neighWallNodes[pi->getId()].resize(pi->getNumNodes());
409 data.d_neighWallNodesDistance[pi->getId()].resize(pi->getNumNodes());
410
411 // get all wall nodes that are within contact distance to the nodes of this particle
412 {
413 tf::Executor executor(util::parallel::getNThreads());
414 tf::Taskflow taskflow;
415
416 taskflow.for_each_index((std::size_t) 0,
417 pi->getNumNodes(),
418 (std::size_t) 1,
419 [&data, &pi](std::size_t i) {
420
421 auto i_glob = pi->getNodeId(i);
422 auto yi = data.d_x[i_glob];
423
424 const std::vector<size_t> &neighs = data.d_neighC[i_glob];
425
426 data.d_neighWallNodes[pi->getId()][i].clear();
427 data.d_neighWallNodesDistance[pi->getId()][i].clear();
428
429 for (const auto &j_id: neighs) {
430
431 auto &ptIdj = data.d_ptId[j_id];
432 auto &pj = data.getParticleFromAllList(
433 ptIdj);
434
435 // we are only interested in nodes from wall
436 if (pj->isWall()) {
437 data.d_neighWallNodes[pi->getId()][i].push_back(j_id);
438 //data.d_neighWallNodesDistance[pi->getId()][i].push_back(Rji);
439 }
440 }
441 }
442 ); // for_each
443
444 executor.run(taskflow).get();
445 }
446 } // loop over particles
447
448
449}
bool updateSearchParameters(data::ModelData &data)
Definition contact.cpp:129
bool isLocallyOwned(const BaseParticle &p)
True if this rank updates / assembles forces for the particle. Walls are replicated on every rank....
int mpiSize()
Get size (number) of processors.

References util::parallel::getNThreads(), util::parallel::isMpiEnabled(), and util::parallel::mpiSize().

Here is the call graph for this function:

◆ updateSearchParameters()

bool contact::Contact::updateSearchParameters ( data::ModelData &  data)

Definition at line 129 of file contact.cpp.

129 {
130
131
132 // initialize parameters
133 if (data.d_contNeighUpdateInterval == 0 and
134 util::isLess(data.d_contNeighSearchRadius, 1.e-16)) {
135 data.d_contNeighUpdateInterval = data.d_particleDeck_p->d_pNeighDeck.d_neighUpdateInterval;
136 data.d_contNeighTimestepCounter = data.d_n % data.d_contNeighUpdateInterval;
137 data.d_contNeighSearchRadius = data.d_maxContactR * data.d_particleDeck_p->d_pNeighDeck.d_sFactor;
138 }
139
140 // at data.d_n = 0, this function will be called twice because updateContactNeighborlist() will be
141 // called twice: one inside init() and second inside computeForces()
142 // so to match data.d_n and data.d_contNeighTimestepCounter in the initial stage of simulation, we need to handle the special case
143 if (data.d_n == 0) {
144 data.appendKeyData("update_contact_neigh_search_params_init_call_count", 1);
145
146 if (int(data.getKeyData("update_contact_neigh_search_params_init_call_count")) == 1)
147 return true;
148
149 if (int(data.getKeyData("update_contact_neigh_search_params_init_call_count")) == 2) {
150 data.d_contNeighTimestepCounter++;
151 return (data.d_contNeighTimestepCounter - 1) % data.d_contNeighUpdateInterval == 0;
152 }
153 }
154
155 // handle case of restart
156 if (data.d_modelDeck_p->d_isRestartActive and data.d_n == data.d_restartDeck_p->d_step) {
157 // assign correct value for restart step
158 data.d_contNeighTimestepCounter = data.d_n % data.d_contNeighUpdateInterval;
159 }
160
161 if (data.d_contNeighUpdateInterval == 1) {
162 // further optimization of parameters is not possible
163 data.d_contNeighSearchRadius = data.d_maxContactR;
164
165 // update counter and return condition for contact search
166 data.d_contNeighTimestepCounter++;
167 return (data.d_contNeighTimestepCounter - 1) % data.d_contNeighUpdateInterval == 0;
168 }
169
170 // check if we should proceed with parameter update
171 // param update is done at smaller interval than the search itself to avoid
172 // scenarios where particles suddenly move with a high velocity
173 size_t update_param_interval =
174 data.d_contNeighUpdateInterval > 5 ? size_t(
175 0.2 * data.d_contNeighUpdateInterval) : 1;
176
177 // check if we ought to update search parameters; if not, return
178 if (data.d_contNeighTimestepCounter > 0 and data.d_contNeighTimestepCounter % update_param_interval != 0) {
179 // update counter and return condition for contact search
180 data.d_contNeighTimestepCounter++;
181 return (data.d_contNeighTimestepCounter - 1) % data.d_contNeighUpdateInterval == 0;
182 }
183
184 // first update the maximum velocity in all particles
185 for (auto &pi : data.d_particlesListTypeAll) {
186 auto max_v_node = util::methods::maxIndex(data.d_vMag,
187 pi->d_globStart, pi->d_globEnd);
188
189 if (max_v_node > pi->d_globEnd or max_v_node < pi->d_globStart) {
190 throw std::runtime_error(
192 << std::format("Error: max_v_node = {} for "
193 "particle of id = {} is not in the limit.\n",
194 max_v_node, pi->getId())
195 << "Particle info = \n"
196 << pi->printStr()
197 << "\n\n Magnitude of velocity = "
198 << data.d_vMag[max_v_node] << "\n");
199 }
200
201 data.d_maxVelocityParticlesListTypeAll[pi->getId()]
202 = data.d_vMag[max_v_node];
203 }
204
205 // find max velocity among all particles
206 data.d_maxVelocity = util::methods::max(data.d_maxVelocityParticlesListTypeAll);
207
208 // now we find the best parameters for contact search
209 auto up_interval_old = data.d_contNeighUpdateInterval;
210
211 // TO ensure that in data.d_neighUpdateInterval time steps, the search radius is above the
212 // distance traveled by object with velocity data.d_maxVelocity
213 // also multiply by a safety factor
214 double safety_factor = data.d_particleDeck_p->d_pNeighDeck.d_sFactor > 5 ? data.d_particleDeck_p->d_pNeighDeck.d_sFactor : 10;
215 auto max_search_r_from_contact_R = data.d_particleDeck_p->d_pNeighDeck.d_sFactor * data.d_maxContactR;
216 if (!std::isfinite(data.d_maxVelocity) || data.d_maxVelocity < 0.)
217 data.d_maxVelocity = 0.;
218 auto max_search_r = data.d_maxVelocity * data.d_currentDt
219 * data.d_particleDeck_p->d_pNeighDeck.d_neighUpdateInterval
220 * safety_factor;
221 if (!std::isfinite(max_search_r) || max_search_r < 0.)
222 max_search_r = 0.;
223
224
225 if (util::isGreater(max_search_r, max_search_r_from_contact_R )) {
226
227 data.d_contNeighUpdateInterval = size_t(data.d_maxContactR/(data.d_maxVelocity * data.d_currentDt));
228 if (up_interval_old > data.d_contNeighUpdateInterval) {
229 // issue warning
230 util::io::log(2, std::format("Warning: Contact search radius based on velocity is greater than "
231 "the max contact radius.\n"
232 "Warning: Adjusting contact neighborlist update interval.\n"
233 "{:>13} = {:4.6e}, time step = {}, "
234 "velocity-based r = {:4.6e}, max contact r = {:4.6e}\n",
235 "Time", data.d_time, data.d_n, max_search_r, max_search_r_from_contact_R), data.d_n % data.d_infoN == 0, 3);
236 }
237
238 data.d_contNeighSearchRadius = max_search_r_from_contact_R;
239 // reset time step counter for contact so that the contact list is updated in the current time step
240 // and the update cycle starts from the current time step
241 data.d_contNeighTimestepCounter = 0;
242
243 if (data.d_contNeighUpdateInterval < 1) {
244 data.d_contNeighUpdateInterval = 1;
245 data.d_contNeighSearchRadius = data.d_maxContactR;
246 }
247 }
248 else {
249 // update search radius
250 data.d_contNeighSearchRadius = data.d_contNeighUpdateInterval < 2 ? data.d_maxContactR : max_search_r_from_contact_R;
251 }
252
253 if (up_interval_old > data.d_contNeighUpdateInterval) {
254 util::io::log(2, std::format(" Contact neighbor parameters: \n"
255 " {:48s} = {:d}\n"
256 " {:48s} = {:d}\n"
257 " {:48s} = {:d}\n"
258 " {:48s} = {:4.6e}\n"
259 " {:48s} = {:4.6e}\n"
260 " {:48s} = {:4.6e}\n"
261 " {:48s} = {:4.6e}\n"
262 " {:48s} = {:4.6e}\n"
263 " {:48s} = {:4.6e}\n",
264 "time step", data.d_n,
265 "contact neighbor update interval",
266 data.d_contNeighUpdateInterval,
267 "contact neighbor update time step counter",
268 data.d_contNeighTimestepCounter,
269 "search radius", data.d_contNeighSearchRadius,
270 "max contact radius", data.d_maxContactR,
271 "search radius factor", data.d_particleDeck_p->d_pNeighDeck.d_sFactor,
272 "max search r from velocity", max_search_r,
273 "max search r from contact r", max_search_r_from_contact_R,
274 "max velocity", data.d_maxVelocity), data.d_n % data.d_infoN == 0, 3);
275 }
276
277 // update counter and return condition for contact search
278 data.d_contNeighTimestepCounter++;
279 return (data.d_contNeighTimestepCounter - 1) % data.d_contNeighUpdateInterval == 0;
280
281}
Collects a message with stream syntax for use in an exception.
Definition io.h:52
T max(const std::vector< T > &data)
Returns the maximum from list of data.
Definition vecMethods.h:76
size_t maxIndex(const std::vector< T > &data)
Returns the index corresponding to maximum from list of data.
Definition vecMethods.h:40

References util::isGreater(), util::isLess(), util::io::log(), util::methods::max(), and util::methods::maxIndex().

Here is the call graph for this function:

Field Documentation

◆ d_damping

std::unique_ptr<Damping> contact::Contact::d_damping

Definition at line 45 of file contact.h.

Referenced by setDamping().

◆ d_pairForce

std::unique_ptr<PairForce> contact::Contact::d_pairForce

Definition at line 44 of file contact.h.

Referenced by setPairForce().

◆ d_useNodeDamping

bool contact::Contact::d_useNodeDamping = true

Apply node-level damping inside the pair assembly loop.

Definition at line 48 of file contact.h.

◆ d_wallContact

std::unique_ptr<WallContact> contact::Contact::d_wallContact

Definition at line 46 of file contact.h.

Referenced by setWallContact().


The documentation for this class was generated from the following files: