48 for (
const auto *p :
data.d_particlesListTypeAll) {
50 auto h = p->getMeshSize();
63 util::io::log(1, std::format(
"{}: Contact setup\n hmin = {:.6f}, hmax = {:.6f} \n",
66 data.d_maxContactR = 0.;
68 auto &contactDeck =
data.d_particleDeck_p->d_contactDeck;
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));
85 std::vector<double> bulk(contactDeck.d_data.size(), -1.);
86 for (
const auto *p :
data.d_particlesListTypeAll) {
87 if (p->getMaterial() ==
nullptr)
89 const auto cid = p->getGroupId(
"contact_id");
90 if (cid >= bulk.size())
92 bulk[cid] = p->getMaterial()->computeMaterialProperties(
data.dimension()).d_K;
95 for (
size_t i = 0; i < contactDeck.d_data.size(); i++) {
96 for (
size_t j = 0; j < contactDeck.d_data.size(); j++) {
106 if (bulk[i] > 0. && bulk[j] > 0.)
113 double log_e = std::log(deck->
d_eps);
116 (-2. * log_e * std::sqrt(1. / (M_PI * M_PI + log_e * log_e)));
118 util::io::log(1, std::format(
" contact_radius = {:.6f}, hmin = {:.6f}, Kn = {:5.3e}, "
120 "betan = {:7.5f}, mu = {:.4f}, kappa = {:5.3e}\n",
132 if (
data.d_contNeighUpdateInterval == 0 and
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;
143 data.appendKeyData(
"update_contact_neigh_search_params_init_call_count", 1);
145 if (
int(
data.getKeyData(
"update_contact_neigh_search_params_init_call_count")) == 1)
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;
155 if (
data.d_modelDeck_p->d_isRestartActive and
data.d_n ==
data.d_restartDeck_p->d_step) {
157 data.d_contNeighTimestepCounter =
data.d_n %
data.d_contNeighUpdateInterval;
160 if (
data.d_contNeighUpdateInterval == 1) {
162 data.d_contNeighSearchRadius =
data.d_maxContactR;
165 data.d_contNeighTimestepCounter++;
166 return (
data.d_contNeighTimestepCounter - 1) %
data.d_contNeighUpdateInterval == 0;
172 size_t update_param_interval =
173 data.d_contNeighUpdateInterval > 5 ? size_t(
174 0.2 *
data.d_contNeighUpdateInterval) : 1;
177 if (
data.d_contNeighTimestepCounter > 0 and
data.d_contNeighTimestepCounter % update_param_interval != 0) {
179 data.d_contNeighTimestepCounter++;
180 return (
data.d_contNeighTimestepCounter - 1) %
data.d_contNeighUpdateInterval == 0;
184 for (
auto &pi :
data.d_particlesListTypeAll) {
186 pi->d_globStart, pi->d_globEnd);
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"
194 <<
"\n\n Magnitude of velocity = "
195 <<
data.d_vMag[max_v_node] <<
"\n";
199 data.d_maxVelocityParticlesListTypeAll[pi->getId()]
200 =
data.d_vMag[max_v_node];
207 auto up_interval_old =
data.d_contNeighUpdateInterval;
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
219 if (!std::isfinite(max_search_r) || max_search_r < 0.)
225 data.d_contNeighUpdateInterval = size_t(
data.d_maxContactR/(
data.d_maxVelocity *
data.d_currentDt));
226 if (up_interval_old >
data.d_contNeighUpdateInterval) {
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);
236 data.d_contNeighSearchRadius = max_search_r_from_contact_R;
239 data.d_contNeighTimestepCounter = 0;
241 if (
data.d_contNeighUpdateInterval < 1) {
242 data.d_contNeighUpdateInterval = 1;
243 data.d_contNeighSearchRadius =
data.d_maxContactR;
248 data.d_contNeighSearchRadius =
data.d_contNeighUpdateInterval < 2 ?
data.d_maxContactR : max_search_r_from_contact_R;
251 if (up_interval_old >
data.d_contNeighUpdateInterval) {
252 util::io::log(2, std::format(
" Contact neighbor parameters: \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);
276 data.d_contNeighTimestepCounter++;
277 return (
data.d_contNeighTimestepCounter - 1) %
data.d_contNeighUpdateInterval == 0;
284 auto update = updateSearchParameters(
data);
292 using steady_clock = std::chrono::steady_clock;
293 const bool mpi_prune =
295 data.d_mpiIncludeInContactCloud.size() ==
data.d_particlesListTypeAll.size();
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;
304 const bool use_full_cloud =
data.d_pdDofMpi;
305 const bool use_prune = mpi_prune && !use_full_cloud;
307 double pt_cloud_update_time = 0.;
309 local_cloud.reserve(
data.d_x.size() /
static_cast<size_t>(std::max(
312 for (
auto *p :
data.d_particlesListTypeAll) {
313 if (!
data.d_mpiIncludeInContactCloud[p->getId()])
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]);
322 local_tree = std::make_unique<nsearch::NFlannSearchKd<3>>(local_cloud, 0);
323 pt_cloud_update_time = local_tree->setInputCloud();
325 pt_cloud_update_time =
data.d_nsearch_p->setInputCloud();
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()
334 if (
data.d_neighC.size() !=
data.d_x.size())
335 data.d_neighC.resize(
data.d_x.size());
339 tf::Taskflow taskflow;
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];
350 if (data.d_contNeighSearchRadius <= 0. ||
351 !std::isfinite(data.d_contNeighSearchRadius))
354 const auto &pi = data.d_ptId[i];
355 const auto &pi_particle = data.d_particlesListTypeAll[pi];
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;
363 if (perform_search_based_on_particle) {
365 std::vector<size_t> neighs;
366 std::vector<double> sqr_dist;
368 data.d_neighC[i].clear();
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];
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);
384 for (auto neigh: neighs) {
386 data.d_neighC[i].push_back(neigh);
392 executor.run(taskflow).get();
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());
401 for (
auto &pi :
data.d_particlesListTypeParticle) {
406 data.d_neighWallNodes[pi->getId()].resize(pi->getNumNodes());
407 data.d_neighWallNodesDistance[pi->getId()].resize(pi->getNumNodes());
412 tf::Taskflow taskflow;
414 taskflow.for_each_index((std::size_t) 0,
417 [&
data, &pi](std::size_t i) {
419 auto i_glob = pi->getNodeId(i);
420 auto yi = data.d_x[i_glob];
422 const std::vector<size_t> &neighs = data.d_neighC[i_glob];
424 data.d_neighWallNodes[pi->getId()][i].clear();
425 data.d_neighWallNodesDistance[pi->getId()][i].clear();
427 for (const auto &j_id: neighs) {
429 auto &ptIdj = data.d_ptId[j_id];
430 auto &pj = data.getParticleFromAllList(
435 data.d_neighWallNodes[pi->getId()][i].push_back(j_id);
442 executor.run(taskflow).get();
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;
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};
470 const unsigned n_workers =
472 tf::Executor executor(n_workers);
473 tf::Taskflow taskflow;
475 taskflow.for_each_index((std::size_t) 0,
476 data.d_fContCompNodes.size(),
478 [&
data, pair, use_node_damping, correct_volume,
480 &max_pen, &max_fij, &min_rji, &n_active,
481 &max_neigh](std::size_t II) {
483 auto i = data.d_fContCompNodes[II];
485 util::Point force_i = util::Point();
486 util::Point damp_i = util::Point();
488 const auto &ptIdi = data.getPtId(i);
489 auto &pi = data.getParticleFromAllList(ptIdi);
495 if (pi->isWall() && util::parallel::isMpiEnabled())
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];
504 size_t nn = neighs.size();
505 size_t prev = max_neigh.load(std::memory_order_relaxed);
507 !max_neigh.compare_exchange_weak(
508 prev, nn, std::memory_order_relaxed)) {
512 for (
const auto &j_id: neighs) {
516 const auto &ptIdj =
data.d_ptId[j_id];
520 auto &pj =
data.getParticleFromAllList(ptIdj);
521 if (pi->isWall() and pj->isWall())
526 if (skip_meshed_wall &&
527 (pi->isWall() || pj->isWall()))
531 data.d_particleDeck_p->d_contactDeck.getContact(
532 pi->getGroupId(
"contact_id"),
533 pj->getGroupId(
"contact_id"));
535 const auto yji =
data.d_x[j_id] - yi;
536 const double Rji = yji.length();
538 const double pen =
contact.d_contactR - Rji;
540 max_pen.load(std::memory_order_relaxed);
541 while (pen > prev_pen &&
542 !max_pen.compare_exchange_weak(
544 std::memory_order_relaxed)) {
547 min_rji.load(std::memory_order_relaxed);
548 while (Rji < prev_r &&
549 !min_rji.compare_exchange_weak(
551 std::memory_order_relaxed)) {
553 n_active.fetch_add(1, std::memory_order_relaxed);
558 const double volj_raw =
data.d_vol[j_id];
562 volj_raw, Rji,
contact.d_contactR,
572 pi->getDensity(), pj->getDensity(),
574 pi->isWall(), pj->isWall()};
577 if (use_node_damping)
578 fd = pair->nodeDampingForce(p);
579 const double fij_mag = (fs + fd).length();
582 max_fij.load(std::memory_order_relaxed);
583 while (fij_mag > prev_f &&
584 !max_fij.compare_exchange_weak(
586 std::memory_order_relaxed)) {
595 if (!pi->isWall() && pj->isWall() &&
601 (volj_raw > 0.) ? (voli / volj_raw) : 0.;
602 data.d_f[j_id] -= scale * fs;
606 data.d_f[i] += force_i + damp_i;
610 executor.run(taskflow).get();
614 d_wallContact->apply(
data, pair, use_node_damping);
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();
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()),
633 d_damping->apply(
data);
bool isLocallyOwned(const BaseParticle &p)
True if this rank updates / assembles forces for the particle. Walls are replicated on every rank....