https://mooseframework.inl.gov
Loading...
Searching...
No Matches
MortarContactUtils.h
Go to the documentation of this file.
1//* This file is part of the MOOSE framework
2//* https://mooseframework.inl.gov
3//*
4//* All rights reserved, see COPYRIGHT for full restrictions
5//* https://github.com/idaholab/moose/blob/master/COPYRIGHT
6//*
7//* Licensed under LGPL 2.1, please see LICENSE for details
8//* https://www.gnu.org/licenses/lgpl-2.1.html
9
10#pragma once
11
12#include "FEProblemBase.h"
13#include "ADReal.h"
14#include "RankTwoTensor.h"
15#include "MooseMesh.h"
16#include "MathUtils.h"
17
18#include "libmesh/dof_object.h"
19
20#include "metaphysicl/parallel_dualnumber.h"
21#include "metaphysicl/parallel_semidynamicsparsenumberarray.h"
22
23#include "timpi/parallel_sync.h"
24#include "timpi/communicator.h"
25#include "libmesh/data_type.h"
26#include "libmesh/parallel_algebra.h"
27#include "timpi/parallel_sync.h"
28
29#include <utility>
30#include <algorithm>
31#include <array>
32#include <cmath>
33#include <unordered_map>
34#include <vector>
35
36namespace TIMPI
37{
38
39template <>
41{
42public:
43 explicit StandardType(const ADRankTwoTensor * example = nullptr)
44 : DataType(StandardType<ADReal>(example ? &((*example)(0, 0)) : nullptr),
45 LIBMESH_DIM * LIBMESH_DIM)
46 {
47 }
48
49 inline ~StandardType() { this->free(); }
50
51 static const bool is_fixed_type = true;
52};
53
54} // namespace TIMPI
55
56namespace Moose
57{
58namespace Mortar
59{
60namespace Contact
61{
63{
64 ONE,
65 TWO
66};
67
75template <typename T>
76T
77augmentedNormalPressure(const T & normal_pressure, const T & scaled_normal_gap)
78{
79 return normal_pressure - scaled_normal_gap;
80}
81
88template <typename T>
89T
90coulombFrictionRadius(const T & friction_coefficient, const T & augmented_normal_pressure)
91{
92 return friction_coefficient * std::max(T(0), augmented_normal_pressure);
93}
94
102template <typename T, std::size_t N>
103std::array<T, N>
104projectToClosedSphere(const std::array<T, N> & vector, const T & radius)
105{
106 const T norm = MathUtils::norm(vector);
107 if (norm <= radius)
108 return vector;
109
110 std::array<T, N> projection;
111 for (const auto i : index_range(projection))
112 projection[i] = radius * vector[i] / norm;
113 return projection;
114}
115
125template <typename T, std::size_t N>
126std::array<T, N>
127alartCurnierFrictionResidual(const std::array<T, N> & tangential_pressure,
128 const std::array<T, N> & augmented_tangential_pressure,
129 const T & radius)
130{
131 const auto projection = projectToClosedSphere(augmented_tangential_pressure, radius);
132 std::array<T, N> residual;
133 for (const auto i : index_range(residual))
134 residual[i] = tangential_pressure[i] - projection[i];
135 return residual;
136}
137
152template <typename T, std::size_t N>
153std::array<T, N>
154alartCurnierFrictionResidual(const std::array<T, N> & tangential_pressure,
155 const std::array<T, N> & augmented_tangential_pressure,
156 const T & radius,
157 const T & normal_pressure,
158 const T & epsilon)
159{
160 if (normal_pressure < epsilon)
161 return tangential_pressure;
162
163 return alartCurnierFrictionResidual(tangential_pressure, augmented_tangential_pressure, radius);
164}
165
178template <typename T, std::size_t N>
179std::array<T, N>
180hueberStadlerWohlmuthFrictionResidual(const std::array<T, N> & tangential_pressure,
181 const std::array<T, N> & augmented_tangential_pressure,
182 const T & radius)
183{
184 const T augmented_norm = MathUtils::norm(augmented_tangential_pressure);
185 const T weight = std::max(radius, augmented_norm);
186 if (weight == 0)
187 return tangential_pressure;
188
189 std::array<T, N> residual;
190 for (const auto i : index_range(residual))
191 residual[i] = weight * tangential_pressure[i] - radius * augmented_tangential_pressure[i];
192 return residual;
193}
194
220template <typename T, std::size_t N>
221std::array<T, N>
222hueberStadlerWohlmuthFrictionResidual(const std::array<T, N> & tangential_pressure,
223 const std::array<T, N> & augmented_tangential_pressure,
224 const T & radius,
225 const T & normal_pressure,
226 const T & epsilon)
227{
228 if (normal_pressure < epsilon)
229 return tangential_pressure;
230
232 tangential_pressure, augmented_tangential_pressure, radius);
233}
234
256template <typename T, std::size_t N>
257std::array<T, N>
258frictionalContactResidual(const std::array<T, N> & tangential_pressure,
259 const std::array<T, N> & tangential_velocity,
260 const T & c_t,
261 const T & dt,
262 const T & normal_pressure,
263 const T & scaled_normal_gap,
264 const T & friction_coefficient,
265 const T & epsilon,
266 const FrictionProjectionDegree projection_degree)
267{
268 std::array<T, N> augmented_tangential_pressure;
269 for (const auto i : index_range(augmented_tangential_pressure))
270 augmented_tangential_pressure[i] = tangential_pressure[i] + c_t * tangential_velocity[i] * dt;
271
272 const auto radius = coulombFrictionRadius(
273 friction_coefficient, augmentedNormalPressure(normal_pressure, scaled_normal_gap));
274
275 switch (projection_degree)
276 {
279 tangential_pressure, augmented_tangential_pressure, radius, normal_pressure, epsilon);
282 tangential_pressure, augmented_tangential_pressure, radius, normal_pressure, epsilon);
283 default:
284 mooseError("Unhandled projection degree");
285 }
286}
287
296template <typename T>
297inline void
298communicateVelocities(std::unordered_map<const DofObject *, T> & dof_map,
299 const MooseMesh & mesh,
300 const bool nodal,
301 const Parallel::Communicator & communicator,
302 const bool send_data_back)
303{
304 libmesh_parallel_only(communicator);
305 const auto our_proc_id = communicator.rank();
306
307 // We may have weighted velocity information that should go to other processes that own the dofs
308 using Datum = std::pair<dof_id_type, T>;
309 std::unordered_map<processor_id_type, std::vector<Datum>> push_data;
310
311 for (auto & pr : dof_map)
312 {
313 const auto * const dof_object = pr.first;
314 const auto proc_id = dof_object->processor_id();
315 if (proc_id == our_proc_id)
316 continue;
317
318 push_data[proc_id].push_back(std::make_pair(dof_object->id(), std::move(pr.second)));
319 }
320
321 const auto & lm_mesh = mesh.getMesh();
322 std::unordered_map<processor_id_type, std::vector<const DofObject *>>
323 pid_to_dof_object_for_sending_back;
324
325 auto action_functor =
326 [nodal, our_proc_id, &lm_mesh, &dof_map, &pid_to_dof_object_for_sending_back, send_data_back](
327 const processor_id_type pid, const std::vector<Datum> & sent_data)
328 {
329 mooseAssert(pid != our_proc_id, "We do not send messages to ourself here");
330 libmesh_ignore(our_proc_id);
331
332 for (auto & pr : sent_data)
333 {
334 const auto dof_id = pr.first;
335 const auto * const dof_object = nodal ? cast_ptr<const DofObject *>(lm_mesh.node_ptr(dof_id))
336 : cast_ptr<const DofObject *>(lm_mesh.elem_ptr(dof_id));
337 mooseAssert(dof_object, "This should be non-null");
338
339 if (send_data_back)
340 pid_to_dof_object_for_sending_back[pid].push_back(dof_object);
341
342 dof_map[dof_object][0] += pr.second[0];
343 dof_map[dof_object][1] += pr.second[1];
344 }
345 };
346
347 TIMPI::push_parallel_vector_data(communicator, push_data, action_functor);
348
349 // Now send data back if requested
350 if (!send_data_back)
351 return;
352
353 std::unordered_map<processor_id_type, std::vector<Datum>> push_back_data;
354
355 for (const auto & [pid, dof_objects] : pid_to_dof_object_for_sending_back)
356 {
357 auto & pid_send_data = push_back_data[pid];
358 pid_send_data.reserve(dof_objects.size());
359 for (const DofObject * const dof_object : dof_objects)
360 {
361 const auto & [tangent_one, tangent_two] = libmesh_map_find(dof_map, dof_object);
362 pid_send_data.push_back({dof_object->id(), {tangent_one, tangent_two}});
363 }
364 }
365
366 auto sent_back_action_functor =
367 [nodal, our_proc_id, &lm_mesh, &dof_map](const processor_id_type libmesh_dbg_var(pid),
368 const std::vector<Datum> & sent_data)
369 {
370 mooseAssert(pid != our_proc_id, "We do not send messages to ourself here");
371 libmesh_ignore(our_proc_id);
372
373 for (auto & [dof_id, tangents] : sent_data)
374 {
375 const auto * const dof_object = nodal ? cast_ptr<const DofObject *>(lm_mesh.node_ptr(dof_id))
376 : cast_ptr<const DofObject *>(lm_mesh.elem_ptr(dof_id));
377 mooseAssert(dof_object, "This should be non-null");
378 auto & [our_tangent_one, our_tangent_two] = dof_map[dof_object];
379 our_tangent_one = tangents[0];
380 our_tangent_two = tangents[1];
381 }
382 };
383
384 TIMPI::push_parallel_vector_data(communicator, push_back_data, sent_back_action_functor);
385}
386
395inline void
396communicateR2T(std::unordered_map<const DofObject *, ADRankTwoTensor> & dof_map_adr2t,
397 const MooseMesh & mesh,
398 const bool nodal,
399 const Parallel::Communicator & communicator,
400 const bool send_data_back)
401{
402 libmesh_parallel_only(communicator);
403 const auto our_proc_id = communicator.rank();
404
405 // We may have weighted velocity information that should go to other processes that own the dofs
406 using Datum = std::pair<dof_id_type, ADRankTwoTensor>;
407 std::unordered_map<processor_id_type, std::vector<Datum>> push_data;
408
409 for (auto & pr : dof_map_adr2t)
410 {
411 const auto * const dof_object = pr.first;
412 const auto proc_id = dof_object->processor_id();
413 if (proc_id == our_proc_id)
414 continue;
415
416 push_data[proc_id].push_back(std::make_pair(dof_object->id(), std::move(pr.second)));
417 }
418
419 const auto & lm_mesh = mesh.getMesh();
420 std::unordered_map<processor_id_type, std::vector<const DofObject *>>
421 pid_to_dof_object_for_sending_back;
422
423 auto action_functor =
424 [nodal,
425 our_proc_id,
426 &lm_mesh,
427 &dof_map_adr2t,
428 &pid_to_dof_object_for_sending_back,
429 send_data_back](const processor_id_type pid, const std::vector<Datum> & sent_data)
430 {
431 mooseAssert(pid != our_proc_id, "We do not send messages to ourself here");
432 libmesh_ignore(our_proc_id);
433
434 for (auto & pr : sent_data)
435 {
436 const auto dof_id = pr.first;
437 const auto * const dof_object = nodal ? cast_ptr<const DofObject *>(lm_mesh.node_ptr(dof_id))
438 : cast_ptr<const DofObject *>(lm_mesh.elem_ptr(dof_id));
439 mooseAssert(dof_object, "This should be non-null");
440
441 if (send_data_back)
442 pid_to_dof_object_for_sending_back[pid].push_back(dof_object);
443
444 for (const auto i : make_range(3))
445 for (const auto j : make_range(3))
446 dof_map_adr2t[dof_object](i, j) += pr.second(i, j);
447 }
448 };
449
450 TIMPI::push_parallel_vector_data(communicator, push_data, action_functor);
451
452 // Now send data back if requested
453 if (!send_data_back)
454 return;
455
456 std::unordered_map<processor_id_type, std::vector<Datum>> push_back_data;
457
458 for (const auto & [pid, dof_objects] : pid_to_dof_object_for_sending_back)
459 {
460 auto & pid_send_data = push_back_data[pid];
461 pid_send_data.reserve(dof_objects.size());
462 for (const DofObject * const dof_object : dof_objects)
463 {
464 const auto & r2t = libmesh_map_find(dof_map_adr2t, dof_object);
465 pid_send_data.push_back({dof_object->id(), r2t});
466 }
467 }
468
469 auto sent_back_action_functor =
470 [nodal, our_proc_id, &lm_mesh, &dof_map_adr2t](const processor_id_type libmesh_dbg_var(pid),
471 const std::vector<Datum> & sent_data)
472 {
473 mooseAssert(pid != our_proc_id, "We do not send messages to ourself here");
474 libmesh_ignore(our_proc_id);
475
476 for (auto & [dof_id, r2t_sent] : sent_data)
477 {
478 const auto * const dof_object = nodal ? cast_ptr<const DofObject *>(lm_mesh.node_ptr(dof_id))
479 : cast_ptr<const DofObject *>(lm_mesh.elem_ptr(dof_id));
480 mooseAssert(dof_object, "This should be non-null");
481 auto & r2t = dof_map_adr2t[dof_object];
482 r2t = r2t_sent;
483 }
484 };
485
486 TIMPI::push_parallel_vector_data(communicator, push_back_data, sent_back_action_functor);
487}
488
489template <typename T>
490void
491communicateRealObject(std::unordered_map<const DofObject *, T> & dof_to_adreal,
492 const MooseMesh & mesh,
493 const bool nodal,
494 const Parallel::Communicator & communicator,
495 const bool send_data_back)
496{
497 libmesh_parallel_only(communicator);
498 const auto our_proc_id = communicator.rank();
499
500 // We may have weighted gap information that should go to other processes that own the dofs
501 using Datum = std::tuple<dof_id_type, T>;
502 std::unordered_map<processor_id_type, std::vector<Datum>> push_data;
503
504 for (auto & pr : dof_to_adreal)
505 {
506 const auto * const dof_object = pr.first;
507 const auto proc_id = dof_object->processor_id();
508 if (proc_id == our_proc_id)
509 continue;
510
511 push_data[proc_id].push_back(std::make_tuple(dof_object->id(), std::move(pr.second)));
512 }
513
514 const auto & lm_mesh = mesh.getMesh();
515 std::unordered_map<processor_id_type, std::vector<const DofObject *>>
516 pid_to_dof_object_for_sending_back;
517
518 auto action_functor =
519 [nodal,
520 our_proc_id,
521 &lm_mesh,
522 &dof_to_adreal,
523 &pid_to_dof_object_for_sending_back,
524 send_data_back](const processor_id_type pid, const std::vector<Datum> & sent_data)
525 {
526 mooseAssert(pid != our_proc_id, "We do not send messages to ourself here");
527 libmesh_ignore(our_proc_id);
528
529 for (auto & [dof_id, weighted_gap] : sent_data)
530 {
531 const auto * const dof_object = nodal ? cast_ptr<const DofObject *>(lm_mesh.node_ptr(dof_id))
532 : cast_ptr<const DofObject *>(lm_mesh.elem_ptr(dof_id));
533 mooseAssert(dof_object, "This should be non-null");
534 if (send_data_back)
535 pid_to_dof_object_for_sending_back[pid].push_back(dof_object);
536 auto & our_adreal = dof_to_adreal[dof_object];
537 our_adreal += weighted_gap;
538 }
539 };
540
541 TIMPI::push_parallel_vector_data(communicator, push_data, action_functor);
542
543 // Now send data back if requested
544 if (!send_data_back)
545 return;
546
547 std::unordered_map<processor_id_type, std::vector<Datum>> push_back_data;
548
549 for (const auto & [pid, dof_objects] : pid_to_dof_object_for_sending_back)
550 {
551 auto & pid_send_data = push_back_data[pid];
552 pid_send_data.reserve(dof_objects.size());
553 for (const DofObject * const dof_object : dof_objects)
554 {
555 const auto & our_adreal = libmesh_map_find(dof_to_adreal, dof_object);
556 pid_send_data.push_back(std::make_tuple(dof_object->id(), our_adreal));
557 }
558 }
559
560 auto sent_back_action_functor =
561 [nodal, our_proc_id, &lm_mesh, &dof_to_adreal](const processor_id_type libmesh_dbg_var(pid),
562 const std::vector<Datum> & sent_data)
563 {
564 mooseAssert(pid != our_proc_id, "We do not send messages to ourself here");
565 libmesh_ignore(our_proc_id);
566
567 for (auto & [dof_id, adreal] : sent_data)
568 {
569 const auto * const dof_object = nodal ? cast_ptr<const DofObject *>(lm_mesh.node_ptr(dof_id))
570 : cast_ptr<const DofObject *>(lm_mesh.elem_ptr(dof_id));
571 mooseAssert(dof_object, "This should be non-null");
572 auto & our_adreal = dof_to_adreal[dof_object];
573 our_adreal = adreal;
574 }
575 };
576 TIMPI::push_parallel_vector_data(communicator, push_back_data, sent_back_action_functor);
577}
578
590void communicateGaps(
591 std::unordered_map<const DofObject *, std::pair<ADReal, Real>> & dof_to_weighted_gap,
592 const MooseMesh & mesh,
593 bool nodal,
594 bool normalize_c,
595 const Parallel::Communicator & communicator,
596 bool send_data_back);
597}
598}
599}
DualNumber< Real, DNDerivativeType, true > ADReal
const double T
void mooseError(Args &&... args)
static const bool is_fixed_type
StandardType(const ADRankTwoTensor *example=nullptr)
MeshBase & mesh
auto norm(const T &value)
std::array< T, N > alartCurnierFrictionResidual(const std::array< T, N > &tangential_pressure, const std::array< T, N > &augmented_tangential_pressure, const T &radius)
Return the degree-one Alart-Curnier friction residual p_t - Proj_{B_radius}(q_t).
std::array< T, N > projectToClosedSphere(const std::array< T, N > &vector, const T &radius)
Project vector onto a closed sphere centered at the origin with radius radius.
T coulombFrictionRadius(const T &friction_coefficient, const T &augmented_normal_pressure)
Return the nonnegative Coulomb friction radius.
std::array< T, N > frictionalContactResidual(const std::array< T, N > &tangential_pressure, const std::array< T, N > &tangential_velocity, const T &c_t, const T &dt, const T &normal_pressure, const T &scaled_normal_gap, const T &friction_coefficient, const T &epsilon, const FrictionProjectionDegree projection_degree)
Compute the epsilon-gated frictional residual for a mortar contact node.
void communicateR2T(std::unordered_map< const DofObject *, ADRankTwoTensor > &dof_map_adr2t, const MooseMesh &mesh, const bool nodal, const Parallel::Communicator &communicator, const bool send_data_back)
This function is used to communicate velocities across processes.
void communicateRealObject(std::unordered_map< const DofObject *, T > &dof_to_adreal, const MooseMesh &mesh, const bool nodal, const Parallel::Communicator &communicator, const bool send_data_back)
std::array< T, N > hueberStadlerWohlmuthFrictionResidual(const std::array< T, N > &tangential_pressure, const std::array< T, N > &augmented_tangential_pressure, const T &radius)
Return the degree-two Hueber-Stadler-Wohlmuth friction residual max(radius, ||q_t||) p_t - radius q_t...
void communicateVelocities(std::unordered_map< const DofObject *, T > &dof_map, const MooseMesh &mesh, const bool nodal, const Parallel::Communicator &communicator, const bool send_data_back)
This function is used to communicate velocities across processes.
void communicateGaps(std::unordered_map< const DofObject *, std::pair< ADReal, Real > > &dof_to_weighted_gap, const MooseMesh &mesh, bool nodal, bool normalize_c, const Parallel::Communicator &communicator, bool send_data_back)
This function is used to communicate gaps across processes.
T augmentedNormalPressure(const T &normal_pressure, const T &scaled_normal_gap)
Return the augmented normal pressure p_n - C_n g_bar.
void push_parallel_vector_data(const Communicator &comm, MapToVectors &&data, const ActionFunctor &act_on_data)
const Real radius