49 for (
const auto *p :
data.d_particlesListTypeAll) {
51 auto h = p->getMeshSize();
64 util::io::log(1, std::format(
"{}: Contact setup\n hmin = {:.6f}, hmax = {:.6f} \n",
67 data.d_maxContactR = 0.;
69 auto &contactDeck =
data.d_particleDeck_p->d_contactDeck;
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));
86 std::vector<double> bulk(contactDeck.d_data.size(), -1.);
87 for (
const auto *p :
data.d_particlesListTypeAll) {
88 if (p->getMaterial() ==
nullptr)
90 const auto cid = p->getGroupId(
"contact_id");
91 if (cid >= bulk.size())
93 bulk[cid] = p->getMaterial()->computeMaterialProperties(
data.dimension()).d_K;
96 for (
size_t i = 0; i < contactDeck.d_data.size(); i++) {
97 for (
size_t j = 0; j < contactDeck.d_data.size(); j++) {
107 if (bulk[i] > 0. && bulk[j] > 0.)
114 double log_e = std::log(deck->
d_eps);
117 (-2. * log_e * std::sqrt(1. / (M_PI * M_PI + log_e * log_e)));
119 util::io::log(1, std::format(
" contact_radius = {:.6f}, hmin = {:.6f}, Kn = {:5.3e}, "
121 "betan = {:7.5f}, mu = {:.4f}, kappa = {:5.3e}\n",
133 if (
data.d_contNeighUpdateInterval == 0 and
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;
144 data.appendKeyData(
"update_contact_neigh_search_params_init_call_count", 1);
146 if (
int(
data.getKeyData(
"update_contact_neigh_search_params_init_call_count")) == 1)
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;
156 if (
data.d_modelDeck_p->d_isRestartActive and
data.d_n ==
data.d_restartDeck_p->d_step) {
158 data.d_contNeighTimestepCounter =
data.d_n %
data.d_contNeighUpdateInterval;
161 if (
data.d_contNeighUpdateInterval == 1) {
163 data.d_contNeighSearchRadius =
data.d_maxContactR;
166 data.d_contNeighTimestepCounter++;
167 return (
data.d_contNeighTimestepCounter - 1) %
data.d_contNeighUpdateInterval == 0;
173 size_t update_param_interval =
174 data.d_contNeighUpdateInterval > 5 ? size_t(
175 0.2 *
data.d_contNeighUpdateInterval) : 1;
178 if (
data.d_contNeighTimestepCounter > 0 and
data.d_contNeighTimestepCounter % update_param_interval != 0) {
180 data.d_contNeighTimestepCounter++;
181 return (
data.d_contNeighTimestepCounter - 1) %
data.d_contNeighUpdateInterval == 0;
185 for (
auto &pi :
data.d_particlesListTypeAll) {
187 pi->d_globStart, pi->d_globEnd);
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"
197 <<
"\n\n Magnitude of velocity = "
198 <<
data.d_vMag[max_v_node] <<
"\n");
201 data.d_maxVelocityParticlesListTypeAll[pi->getId()]
202 =
data.d_vMag[max_v_node];
209 auto up_interval_old =
data.d_contNeighUpdateInterval;
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
221 if (!std::isfinite(max_search_r) || max_search_r < 0.)
227 data.d_contNeighUpdateInterval = size_t(
data.d_maxContactR/(
data.d_maxVelocity *
data.d_currentDt));
228 if (up_interval_old >
data.d_contNeighUpdateInterval) {
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);
238 data.d_contNeighSearchRadius = max_search_r_from_contact_R;
241 data.d_contNeighTimestepCounter = 0;
243 if (
data.d_contNeighUpdateInterval < 1) {
244 data.d_contNeighUpdateInterval = 1;
245 data.d_contNeighSearchRadius =
data.d_maxContactR;
250 data.d_contNeighSearchRadius =
data.d_contNeighUpdateInterval < 2 ?
data.d_maxContactR : max_search_r_from_contact_R;
253 if (up_interval_old >
data.d_contNeighUpdateInterval) {
254 util::io::log(2, std::format(
" Contact neighbor parameters: \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);
278 data.d_contNeighTimestepCounter++;
279 return (
data.d_contNeighTimestepCounter - 1) %
data.d_contNeighUpdateInterval == 0;
286 auto update = updateSearchParameters(
data);
294 using steady_clock = std::chrono::steady_clock;
295 const bool mpi_prune =
297 data.d_mpiIncludeInContactCloud.size() ==
data.d_particlesListTypeAll.size();
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;
306 const bool use_full_cloud =
data.d_pdDofMpi;
307 const bool use_prune = mpi_prune && !use_full_cloud;
309 double pt_cloud_update_time = 0.;
311 local_cloud.reserve(
data.d_x.size() /
static_cast<size_t>(std::max(
314 for (
auto *p :
data.d_particlesListTypeAll) {
315 if (!
data.d_mpiIncludeInContactCloud[p->getId()])
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]);
324 local_tree = std::make_unique<nsearch::NFlannSearchKd<3>>(local_cloud, 0);
325 pt_cloud_update_time = local_tree->setInputCloud();
327 pt_cloud_update_time =
data.d_nsearch_p->setInputCloud();
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()
336 if (
data.d_neighC.size() !=
data.d_x.size())
337 data.d_neighC.resize(
data.d_x.size());
341 tf::Taskflow taskflow;
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];
352 if (data.d_contNeighSearchRadius <= 0. ||
353 !std::isfinite(data.d_contNeighSearchRadius))
356 const auto &pi = data.d_ptId[i];
357 const auto &pi_particle = data.d_particlesListTypeAll[pi];
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;
365 if (perform_search_based_on_particle) {
367 std::vector<size_t> neighs;
368 std::vector<double> sqr_dist;
370 data.d_neighC[i].clear();
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];
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);
386 for (auto neigh: neighs) {
388 data.d_neighC[i].push_back(neigh);
394 executor.run(taskflow).get();
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());
403 for (
auto &pi :
data.d_particlesListTypeParticle) {
408 data.d_neighWallNodes[pi->getId()].resize(pi->getNumNodes());
409 data.d_neighWallNodesDistance[pi->getId()].resize(pi->getNumNodes());
414 tf::Taskflow taskflow;
416 taskflow.for_each_index((std::size_t) 0,
419 [&
data, &pi](std::size_t i) {
421 auto i_glob = pi->getNodeId(i);
422 auto yi = data.d_x[i_glob];
424 const std::vector<size_t> &neighs = data.d_neighC[i_glob];
426 data.d_neighWallNodes[pi->getId()][i].clear();
427 data.d_neighWallNodesDistance[pi->getId()][i].clear();
429 for (const auto &j_id: neighs) {
431 auto &ptIdj = data.d_ptId[j_id];
432 auto &pj = data.getParticleFromAllList(
437 data.d_neighWallNodes[pi->getId()][i].push_back(j_id);
444 executor.run(taskflow).get();
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;
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};
472 const unsigned n_workers =
474 tf::Executor executor(n_workers);
475 tf::Taskflow taskflow;
477 taskflow.for_each_index((std::size_t) 0,
478 data.d_fContCompNodes.size(),
480 [&
data, pair, use_node_damping, correct_volume,
482 &max_pen, &max_fij, &min_rji, &n_active,
483 &max_neigh](std::size_t II) {
485 auto i = data.d_fContCompNodes[II];
487 util::Point force_i = util::Point();
488 util::Point damp_i = util::Point();
490 const auto &ptIdi = data.getPtId(i);
491 auto &pi = data.getParticleFromAllList(ptIdi);
497 if (pi->isWall() && util::parallel::isMpiEnabled())
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];
506 size_t nn = neighs.size();
507 size_t prev = max_neigh.load(std::memory_order_relaxed);
509 !max_neigh.compare_exchange_weak(
510 prev, nn, std::memory_order_relaxed)) {
514 for (
const auto &j_id: neighs) {
518 const auto &ptIdj =
data.d_ptId[j_id];
522 auto &pj =
data.getParticleFromAllList(ptIdj);
523 if (pi->isWall() and pj->isWall())
528 if (skip_meshed_wall &&
529 (pi->isWall() || pj->isWall()))
533 data.d_particleDeck_p->d_contactDeck.getContact(
534 pi->getGroupId(
"contact_id"),
535 pj->getGroupId(
"contact_id"));
537 const auto yji =
data.d_x[j_id] - yi;
538 const double Rji = yji.length();
540 const double pen =
contact.d_contactR - Rji;
542 max_pen.load(std::memory_order_relaxed);
543 while (pen > prev_pen &&
544 !max_pen.compare_exchange_weak(
546 std::memory_order_relaxed)) {
549 min_rji.load(std::memory_order_relaxed);
550 while (Rji < prev_r &&
551 !min_rji.compare_exchange_weak(
553 std::memory_order_relaxed)) {
555 n_active.fetch_add(1, std::memory_order_relaxed);
560 const double volj_raw =
data.d_vol[j_id];
564 volj_raw, Rji,
contact.d_contactR,
574 pi->getDensity(), pj->getDensity(),
576 pi->isWall(), pj->isWall()};
579 if (use_node_damping)
580 fd = pair->nodeDampingForce(p);
581 const double fij_mag = (fs + fd).length();
584 max_fij.load(std::memory_order_relaxed);
585 while (fij_mag > prev_f &&
586 !max_fij.compare_exchange_weak(
588 std::memory_order_relaxed)) {
597 if (!pi->isWall() && pj->isWall() &&
603 (volj_raw > 0.) ? (voli / volj_raw) : 0.;
604 data.d_f[j_id] -= scale * fs;
608 data.d_f[i] += force_i + damp_i;
612 executor.run(taskflow).get();
616 d_wallContact->apply(
data, pair, use_node_damping);
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();
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()),
635 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....