https://mooseframework.inl.gov
ContactAction.C
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 #include "ContactAction.h"
11 
12 #include "Factory.h"
13 #include "FEProblem.h"
14 #include "Conversion.h"
15 #include "AddVariableAction.h"
16 #include "MortarConstraintBase.h"
18 #include "NonlinearSystemBase.h"
19 #include "Parser.h"
21 
22 #include "NanoflannMeshAdaptor.h"
23 #include "PointListAdaptor.h"
24 
25 #include <set>
26 #include <algorithm>
27 #include <unordered_map>
28 #include <limits>
29 
30 #include "libmesh/petsc_nonlinear_solver.h"
31 #include "libmesh/string_to_enum.h"
32 
33 // Make newer nanoflann API compatible with older nanoflann versions
34 #if NANOFLANN_VERSION < 0x150
35 namespace nanoflann
36 {
37 typedef SearchParams SearchParameters;
38 
39 template <typename T, typename U>
40 using ResultItem = std::pair<T, U>;
41 }
42 #endif
43 
44 using NodeBoundaryIDInfo = std::pair<const Node *, BoundaryID>;
45 
46 // Counter for naming mortar auxiliary kernels
47 static unsigned int contact_mortar_auxkernel_counter = 0;
48 
49 // Counter for naming auxiliary kernels
50 static unsigned int contact_auxkernel_counter = 0;
51 
52 // Counter for naming nodal area user objects
53 static unsigned int contact_userobject_counter = 0;
54 
55 // Counter for distinct contact action objects
56 static unsigned int contact_action_counter = 0;
57 
58 // For mortar subdomains
59 registerMooseAction("ContactApp", ContactAction, "append_mesh_generator");
60 registerMooseAction("ContactApp", ContactAction, "add_aux_variable");
61 // For mortar Lagrange multiplier
62 registerMooseAction("ContactApp", ContactAction, "add_contact_aux_variable");
63 registerMooseAction("ContactApp", ContactAction, "add_mortar_variable");
64 registerMooseAction("ContactApp", ContactAction, "add_aux_kernel");
65 // For mortar constraint
66 registerMooseAction("ContactApp", ContactAction, "add_constraint");
67 registerMooseAction("ContactApp", ContactAction, "output_penetration_info_vars");
68 registerMooseAction("ContactApp", ContactAction, "add_user_object");
69 // For automatic generation of contact pairs
70 registerMooseAction("ContactApp", ContactAction, "post_mesh_prepared");
71 
74 {
77 
78  params.addParam<std::vector<BoundaryName>>(
79  "primary", "The list of boundary IDs referring to primary sidesets");
80  params.addParam<std::vector<BoundaryName>>(
81  "secondary", "The list of boundary IDs referring to secondary sidesets");
82  params.addParam<std::vector<BoundaryName>>(
83  "automatic_pairing_boundaries",
84  {},
85  "List of boundary IDs for sidesets that are automatically paired with any other boundary in "
86  "this list having a centroid-to-centroid distance less than the value specified in the "
87  "'automatic_pairing_distance' parameter. ");
88  params.addRangeCheckedParam<Real>(
89  "automatic_pairing_distance",
90  "automatic_pairing_distance>=0",
91  "The maximum distance the centroids of the boundaries provided in the "
92  "'automatic_pairing_boundaries' parameter can be to generate a contact pair automatically. "
93  "Due to numerical error in the determination of the centroids, it is encouraged that "
94  "the user adds a tolerance to this distance (e.g. extra 10%) to make sure no suitable "
95  "contact pair is missed. If the 'automatic_pairing_method = NODE' option is chosen instead, "
96  "this distance is recommended to be set to at least twice the minimum distance between "
97  "nodes of boundaries to be paired.");
98  params.addDeprecatedParam<MeshGeneratorName>(
99  "mesh",
100  "The mesh generator for mortar method",
101  "This parameter is not used anymore and can simply be removed");
102  params.addParam<VariableName>("secondary_gap_offset",
103  "Offset to gap distance from secondary side");
104  params.addParam<VariableName>("mapped_primary_gap_offset",
105  "Offset to gap distance mapped from primary side");
106  params.addParam<std::vector<VariableName>>(
107  "displacements",
108  {},
109  "The displacements appropriate for the simulation geometry and coordinate system");
110  params.addParam<Real>(
111  "penalty",
112  1e8,
113  "The penalty to apply. This can vary depending on the stiffness of your materials");
114  params.addParam<Real>(
115  "penalty_friction",
116  1e8,
117  "The penalty factor to apply in mortar penalty frictional constraints. It is applied to the "
118  "tangential accumulated slip to build the frictional force");
119  params.addRangeCheckedParam<Real>(
120  "penalty_multiplier",
121  1.0,
122  "penalty_multiplier > 0",
123  "The growth factor for the penalty applied at the end of each augmented "
124  "Lagrange update iteration (a value larger than one, e.g., 10, tends to speed up "
125  "convergence.)");
126  params.addRangeCheckedParam<Real>(
127  "penalty_multiplier_friction",
128  1.0,
129  "penalty_multiplier_friction > 0",
130  "The penalty growth factor between augmented Lagrange "
131  "iterations for penalizing relative slip distance if the node is under stick conditions.(a "
132  "value larger than one, e.g., 10, tends to speed up convergence.)");
133  params.addParam<Real>("friction_coefficient", 0, "The friction coefficient");
134  params.addParam<Real>("tension_release",
135  0.0,
136  "Tension release threshold. A node in contact "
137  "will not be released if its tensile load is below "
138  "this value. No tension release if negative.");
139  params.addParam<MooseEnum>("model", ContactAction::getModelEnum(), "The contact model to use");
140  params.addParam<Real>("tangential_tolerance",
141  "Tangential distance to extend edges of contact surfaces");
142  params.addParam<Real>("capture_tolerance",
143  0.0,
144  "Normal distance from surface within which nodes are captured. This "
145  "parameter is used for node-face and mortar formulations.");
146  params.addParam<Real>(
147  "normal_smoothing_distance",
148  "Distance from edge in parametric coordinates over which to smooth contact normal");
149 
150  params.addParam<bool>("normalize_penalty",
151  false,
152  "Whether to normalize the penalty parameter with the nodal area.");
153  params.addParam<bool>(
154  "primary_secondary_jacobian",
155  true,
156  "Whether to include Jacobian entries coupling primary and secondary nodes.");
157  params.addParam<bool>(
158  "ghost_whole_interface",
159  false,
160  "Whether to geometrically and algebraically ghost the entire primary side of the interface "
161  "for node-face contact constraints.");
162  params.addParam<Real>("al_penetration_tolerance",
163  "The tolerance of the penetration for augmented Lagrangian method.");
164  params.addParam<Real>("al_incremental_slip_tolerance",
165  "The tolerance of the incremental slip for augmented Lagrangian method.");
166  params.addRangeCheckedParam<Real>(
167  "max_penalty_multiplier",
168  1.0e3,
169  "max_penalty_multiplier >= 1.0",
170  "Maximum multiplier applied to penalty factors when adaptivity is used in an augmented "
171  "Lagrange setting. The penalty factor supplied by the user is used as a reference to "
172  "determine its maximum. If this multiplier is too large, the condition number of the system "
173  "to be solved may be negatively impacted.");
174  MooseEnum adaptivity_penalty_normal("SIMPLE BUSSETTA", "SIMPLE");
175  adaptivity_penalty_normal.addDocumentation(
176  "SIMPLE", "Keep multiplying by the penalty multiplier between AL iterations");
177  adaptivity_penalty_normal.addDocumentation(
178  "BUSSETTA",
179  "Modify the penalty using an algorithm from Bussetta et al, 2012, Comput Mech 49:259-275 "
180  "between AL iterations.");
181  params.addParam<MooseEnum>(
182  "adaptivity_penalty_normal",
183  adaptivity_penalty_normal,
184  "The augmented Lagrange update strategy used on the normal penalty coefficient.");
185  MooseEnum adaptivity_penalty_friction("SIMPLE FRICTION_LIMIT", "FRICTION_LIMIT");
186  adaptivity_penalty_friction.addDocumentation(
187  "SIMPLE", "Keep multiplying by the frictional penalty multiplier between AL iterations");
188  adaptivity_penalty_friction.addDocumentation(
189  "FRICTION_LIMIT",
190  "This strategy will be guided by the Coulomb limit and be less reliant on the initial "
191  "penalty factor provided by the user.");
192  params.addParam<MooseEnum>(
193  "adaptivity_penalty_friction",
194  adaptivity_penalty_friction,
195  "The augmented Lagrange update strategy used on the frictional penalty coefficient.");
196  params.addParam<Real>("al_frictional_force_tolerance",
197  "The tolerance of the frictional force for augmented Lagrangian method.");
198  params.addParam<Real>(
199  "c_normal",
200  1e6,
201  "Parameter for balancing the size of the gap and contact pressure for a mortar formulation. "
202  "This purely numerical "
203  "parameter affects convergence behavior and, in general, should be larger for stiffer "
204  "materials. It is recommended that the user tries out various orders of magnitude for this "
205  "parameter if the default value generates poor contact convergence.");
206  params.addParam<Real>(
207  "c_tangential", 1, "Numerical parameter for nonlinear mortar frictional constraints");
208  params.addParam<bool>("ping_pong_protection",
209  false,
210  "Whether to protect against ping-ponging, e.g. the oscillation of the "
211  "secondary node between two "
212  "different primary faces, by tying the secondary node to the "
213  "edge between the involved primary faces");
214  params.addParam<Real>(
215  "normal_lm_scaling",
216  1.,
217  "Scaling factor to apply to the normal LM variable for a mortar formulation");
218  params.addParam<Real>(
219  "tangential_lm_scaling",
220  1.,
221  "Scaling factor to apply to the tangential LM variable for a mortar formulation");
222  MooseEnum lm_space(getContactLMSpaceOptions(), "MATCH_DISPLACEMENT");
223  lm_space.addDocumentation(
224  "MATCH_DISPLACEMENT",
225  "Use the same finite element order as the displacement variables for generated mortar "
226  "Lagrange multiplier variables.");
227  lm_space.addDocumentation(
228  "LINEAR",
229  "Use first-order LAGRANGE generated mortar Lagrange multiplier variables, independent "
230  "of the displacement variable order.");
231  params.addParam<MooseEnum>(
232  "lm_space",
233  lm_space,
234  "Finite element space for mortar Lagrange multiplier variables generated by the "
235  "contact action. This parameter only applies to the 'mortar' contact formulation.");
236  params.addParam<bool>(
237  "normalize_c",
238  false,
239  "Whether to normalize c by weighting function norm for mortar contact. When unnormalized "
240  "the value of c effectively depends on element size since in the constraint we compare nodal "
241  "Lagrange Multiplier values to integrated gap values (LM nodal value is independent of "
242  "element size, where integrated values are dependent on element size).");
243  params.addClassDescription("Sets up all objects needed for mechanical contact enforcement");
244  params.addParam<bool>(
245  "use_dual",
246  "Whether to use the dual mortar approach within a mortar formulation. It is defaulted to "
247  "true for "
248  "weighted quantity approach, and to false for the legacy approach. To avoid instabilities "
249  "in the solution and obtain the full benefits of a variational enforcement,"
250  "use of dual mortar with weighted constraints is strongly recommended. This "
251  "input is only intended for advanced users.");
252  params.addParam<bool>(
253  "correct_edge_dropping",
254  false,
255  "Whether to enable correct edge dropping treatment for mortar constraints. When disabled "
256  "any Lagrange Multiplier degree of freedom on a secondary element without full primary "
257  "contributions will be set (strongly) to 0.");
259  params.addParam<bool>(
260  "generate_mortar_mesh",
261  true,
262  "Whether to generate the mortar mesh from the action. Typically this will be the case, but "
263  "one may also want to reuse an existing lower-dimensional mesh prior to a restart.");
264  params.addParam<MooseEnum>("automatic_pairing_method",
266  "The proximity method used for automatic pairing of boundaries.");
267  params.addParam<bool>(
268  "mortar_dynamics",
269  false,
270  "Whether to use constraints that account for the persistency condition, giving rise to "
271  "smoother normal contact pressure evolution. This flag should only be set to yes for dynamic "
272  "simulations using the Newmark-beta numerical integrator");
273  params.addParam<Real>(
274  "newmark_beta",
275  0.25,
276  "Newmark-beta beta parameter for its inclusion in the weighted gap update formula");
277  params.addParam<Real>(
278  "newmark_gamma",
279  0.5,
280  "Newmark-beta gamma parameter for its inclusion in the weighted gap update formula");
281  params.addCoupledVar("wear_depth",
282  "The name of the mortar auxiliary variable that is used to modify the "
283  "weighted gap definition");
284  params.addParam<std::vector<TagName>>(
285  "extra_vector_tags",
286  "The tag names for extra vectors that residual data should be saved into");
287  params.addParam<std::vector<TagName>>(
288  "absolute_value_vector_tags",
289  "The tags for the vectors this residual object should fill with the "
290  "absolute value of the residual contribution");
291  params.addParam<bool>(
292  "use_petrov_galerkin",
293  false,
294  "Whether to use the Petrov-Galerkin approach for the mortar-based constraints. If set to "
295  "true, we use the standard basis as the test function and dual basis as "
296  "the shape function for the interpolation of the Lagrange multiplier variable.");
297  params.addParam<bool>(
298  "debug_mesh",
299  false,
300  "Whether we are going to enable mortar segment mesh debug information. An exodus"
301  "file will be generated if the user sets this flag to true");
302  const auto mortar_constraint_params = MortarConstraintBase::validParams();
303  params.transferParam<MooseEnum>(mortar_constraint_params, "segment_quadrature");
304  params.transferParam<Real>(mortar_constraint_params, "minimum_projection_angle");
305  params.transferParam<MooseEnum>(mortar_constraint_params, "mortar_3d_subpatch_plane");
306  params.transferParam<MooseEnum>(mortar_constraint_params, "mortar_3d_qp_mapping");
307 
308  // Contact surface definition
309  params.addParamNamesToGroup("primary secondary displacements", "Contact Surface Definition");
310  // Automatic pairing
311  params.addParamNamesToGroup(
312  "automatic_pairing_boundaries automatic_pairing_distance automatic_pairing_method",
313  "Automatic Contact Pair Generation");
314  // Contact formulation and model
315  params.addParamNamesToGroup("formulation model", "Contact Formulation");
316  // Penalty parameters
317  params.addParamNamesToGroup(
318  "penalty penalty_friction penalty_multiplier penalty_multiplier_friction "
319  "max_penalty_multiplier normalize_penalty",
320  "Penalty Parameters");
321  // Augmented Lagrange settings
322  params.addParamNamesToGroup(
323  "al_penetration_tolerance al_incremental_slip_tolerance al_frictional_force_tolerance "
324  "adaptivity_penalty_normal adaptivity_penalty_friction",
325  "Augmented Lagrange");
326  // Friction
327  params.addParamNamesToGroup("friction_coefficient tension_release", "Friction");
328  // Mortar-specific parameters
329  params.addParamNamesToGroup("c_normal c_tangential normal_lm_scaling tangential_lm_scaling "
330  "lm_space "
331  "use_dual correct_edge_dropping normalize_c use_petrov_galerkin "
332  "generate_mortar_mesh segment_quadrature minimum_projection_angle "
333  "mortar_3d_subpatch_plane mortar_3d_qp_mapping wear_depth debug_mesh",
334  "Mortar");
335  // Mortar dynamics (Newmark-beta)
336  params.addParamNamesToGroup("mortar_dynamics newmark_beta newmark_gamma", "Mortar Dynamics");
337  // Gap and tolerance settings
338  params.addParamNamesToGroup(
339  "secondary_gap_offset mapped_primary_gap_offset capture_tolerance "
340  "tangential_tolerance normal_smoothing_distance normal_smoothing_method",
341  "Gap and Tolerance");
342  // Jacobian and solver options
343  params.addParamNamesToGroup("primary_secondary_jacobian ping_pong_protection", "Solver Options");
344  // Interface ghosting
345  params.addParamNamesToGroup("ghost_whole_interface", "Interface Ghosting");
346  // Residual vector tags
347  params.addParamNamesToGroup("extra_vector_tags absolute_value_vector_tags", "Residual Tags");
348 
349  return params;
350 }
351 
353  : Action(params),
354  _boundary_pairs(getParam<BoundaryName, BoundaryName>("primary", "secondary")),
355  _model(getParam<MooseEnum>("model").getEnum<ContactModel>()),
356  _formulation(getParam<MooseEnum>("formulation").getEnum<ContactFormulation>()),
357  _lm_space(getParam<MooseEnum>("lm_space").getEnum<ContactLMSpace>()),
358  _generate_mortar_mesh(getParam<bool>("generate_mortar_mesh")),
359  _mortar_dynamics(getParam<bool>("mortar_dynamics"))
360 {
361  // Check for automatic selection of contact pairs.
362  if (getParam<std::vector<BoundaryName>>("automatic_pairing_boundaries").size() > 1)
364  getParam<std::vector<BoundaryName>>("automatic_pairing_boundaries");
365 
366  if (_automatic_pairing_boundaries.size() > 0 && !isParamValid("automatic_pairing_distance"))
367  paramError("automatic_pairing_distance",
368  "For automatic selection of contact pairs (for particular geometries) in contact "
369  "action, 'automatic_pairing_distance' needs to be provided.");
370 
371  if (_automatic_pairing_boundaries.size() > 0 && !isParamValid("automatic_pairing_method"))
372  paramError("automatic_pairing_distance",
373  "For automatic selection of contact pairs (for particular geometries) in contact "
374  "action, 'automatic_pairing_method' needs to be provided.");
375 
376  if (_automatic_pairing_boundaries.size() > 0 && _boundary_pairs.size() != 0)
377  paramError("automatic_pairing_boundaries",
378  "If a boundary list is provided, primary and secondary surfaces will be identified "
379  "automatically. Therefore, one cannot provide an automatic pairing boundary list "
380  "and primary/secondary lists.");
381  else if (_automatic_pairing_boundaries.size() == 0 && _boundary_pairs.size() == 0)
382  paramError("primary",
383  "'primary' and 'secondary' surfaces or a list of boundaries for automatic pair "
384  "generation need to be provided.");
385 
386  // End of checks for automatic selection of contact pairs.
387 
388  if (_boundary_pairs.size() != 1 && _formulation == ContactFormulation::MORTAR)
389  paramError("formulation", "When using mortar, a vector of contact pairs cannot be used");
390 
391  if ((_formulation == ContactFormulation::MORTAR ||
392  _formulation == ContactFormulation::MORTAR_PENALTY) &&
393  params.isParamSetByUser("ghost_whole_interface"))
394  paramError("ghost_whole_interface",
395  "The 'ghost_whole_interface' parameter is only supported for node-face contact "
396  "formulations. Mortar contact always geometrically and algebraically ghosts the "
397  "interface.");
398 
399  if (_formulation == ContactFormulation::TANGENTIAL_PENALTY && _model != ContactModel::COULOMB)
400  paramError("formulation",
401  "The 'tangential_penalty' formulation can only be used with the 'coulomb' model");
402 
403  if (_formulation == ContactFormulation::MORTAR_PENALTY)
404  {
405  // Use dual basis functions for contact traction interpolation
406  if (isParamValid("use_dual"))
407  _use_dual = getParam<bool>("use_dual");
408  else
409  _use_dual = true;
410 
411  if (_model == ContactModel::GLUED)
412  paramError("model", "The 'mortar_penalty' formulation does not support glued contact");
413 
414  if (getParam<bool>("mortar_dynamics"))
415  paramError("mortar_dynamics",
416  "The 'mortar_penalty' formulation does not support implicit dynamic simulations");
417 
418  if (getParam<bool>("use_petrov_galerkin"))
419  paramError("use_petrov_galerkin",
420  "The 'mortar_penalty' formulation does not support usage of the Petrov-Galerkin "
421  "flag. The default (use_dual = true) behavior is such that contact tractions are "
422  "interpolated with dual bases whereas mortar or weighted contact quantities are "
423  "interpolated with Lagrange shape functions.");
424  }
425 
426  if (_formulation == ContactFormulation::MORTAR)
427  {
428  if (_model == ContactModel::GLUED)
429  paramError("model", "The 'mortar' formulation does not support glued contact (yet)");
430 
431  // use dual basis function for Lagrange multipliers?
432  if (isParamValid("use_dual"))
433  _use_dual = getParam<bool>("use_dual");
434  else
435  _use_dual = true;
436 
437  if (!getParam<bool>("mortar_dynamics"))
438  {
439  if (params.isParamSetByUser("newmark_beta"))
440  paramError("newmark_beta", "newmark_beta can only be used with the mortar_dynamics option");
441 
442  if (params.isParamSetByUser("newmark_gamma"))
443  paramError("newmark_gamma",
444  "newmark_gamma can only be used with the mortar_dynamics option");
445  }
446 
447  if (isParamSetByUser("penalty"))
448  paramError("penalty",
449  "The 'penalty' parameter is not used for the 'mortar' formulation which instead "
450  "uses Lagrange multipliers");
451  }
452  else
453  {
454  if (params.isParamSetByUser("correct_edge_dropping"))
455  paramError(
456  "correct_edge_dropping",
457  "The 'correct_edge_dropping' option can only be used with the 'mortar' formulation "
458  "(weighted)");
459  else if (params.isParamSetByUser("triangulation") &&
460  _formulation != ContactFormulation::MORTAR_PENALTY)
461  paramError("triangulation",
462  "The 'triangulation' option can only be used with mortar-based formulations.");
463  else if (params.isParamSetByUser("triangulate_triangles") &&
464  _formulation != ContactFormulation::MORTAR_PENALTY)
465  paramError("triangulate_triangles",
466  "The 'triangulate_triangles' option can only be used with mortar-based "
467  "formulations.");
468  else if (params.isParamSetByUser("minimum_projection_angle") &&
469  _formulation != ContactFormulation::MORTAR_PENALTY)
470  paramError("minimum_projection_angle",
471  "The 'minimum_projection_angle' option can only be used with mortar-based "
472  "formulations.");
473  else if (params.isParamSetByUser("mortar_3d_subpatch_plane") &&
474  _formulation != ContactFormulation::MORTAR_PENALTY)
475  paramError("mortar_3d_subpatch_plane",
476  "The 'mortar_3d_subpatch_plane' option can only be used with mortar-based "
477  "formulations.");
478  else if (params.isParamSetByUser("mortar_3d_qp_mapping") &&
479  _formulation != ContactFormulation::MORTAR_PENALTY)
480  paramError("mortar_3d_qp_mapping",
481  "The 'mortar_3d_qp_mapping' option can only be used with mortar-based "
482  "formulations.");
483  else if (params.isParamSetByUser("use_dual") &&
484  _formulation != ContactFormulation::MORTAR_PENALTY)
485  paramError("use_dual",
486  "The 'use_dual' option can only be used with the 'mortar' formulation");
487  else if (params.isParamSetByUser("c_normal"))
488  paramError("c_normal",
489  "The 'c_normal' option can only be used with the 'mortar' formulation");
490  else if (params.isParamSetByUser("c_tangential"))
491  paramError("c_tangential",
492  "The 'c_tangential' option can only be used with the 'mortar' formulation");
493  else if (params.isParamSetByUser("mortar_dynamics"))
494  paramError("mortar_dynamics",
495  "The 'mortar_dynamics' constraint option can only be used with the 'mortar' "
496  "formulation and in dynamic simulations using Newmark-beta");
497  else if (params.isParamSetByUser("segment_quadrature"))
498  paramError("segment_quadrature",
499  "The 'segment_quadrature' option can only be used with the "
500  "'mortar' formulation.");
501  else if (params.isParamSetByUser("lm_space"))
502  paramError("lm_space",
503  "The 'lm_space' option can only be used with the 'mortar' formulation.");
504  }
505 
506  if (_formulation == ContactFormulation::RANFS)
507  {
508  if (isParamValid("secondary_gap_offset"))
509  paramError("secondary_gap_offset",
510  "The 'secondary_gap_offset' option can only be used with the "
511  "'MechanicalContactConstraint'");
512  if (isParamValid("mapped_primary_gap_offset"))
513  paramError("mapped_primary_gap_offset",
514  "The 'mapped_primary_gap_offset' option can only be used with the "
515  "'MechanicalContactConstraint'");
516  }
517  else if (getParam<bool>("ping_pong_protection"))
518  paramError("ping_pong_protection",
519  "The 'ping_pong_protection' option can only be used with the 'ranfs' formulation");
520 
521  // Remove repeated pairs from input file.
523 }
524 
525 void
527 {
528  if (_boundary_pairs.size() == 0 && _automatic_pairing_boundaries.size() == 0)
529  paramError(
530  "primary",
531  "Number of contact pairs in the contact action is zero. Please revise your input file.");
532 
533  // Remove repeated interactions
534  std::vector<std::pair<BoundaryName, BoundaryName>> lean_boundary_pairs;
535 
536  for (const auto & [primary, secondary] : _boundary_pairs)
537  {
538  // Structured bindings are not capturable (primary_copy, secondary_copy)
539  auto it = std::find_if(lean_boundary_pairs.begin(),
540  lean_boundary_pairs.end(),
541  [&, primary_copy = primary, secondary_copy = secondary](
542  const std::pair<BoundaryName, BoundaryName> & lean_pair)
543  {
544  const bool match_one = lean_pair.second == secondary_copy &&
545  lean_pair.first == primary_copy;
546  const bool match_two = lean_pair.second == primary_copy &&
547  lean_pair.first == secondary_copy;
548  const bool exist = match_one || match_two;
549  return exist;
550  });
551 
552  if (it == lean_boundary_pairs.end())
553  lean_boundary_pairs.emplace_back(primary, secondary);
554  else
555  mooseInfo("Contact pair ",
556  primary,
557  "--",
558  secondary,
559  " has been removed from the contact interaction list due to "
560  "duplicates in the input file.");
561  }
562 
563  _boundary_pairs = lean_boundary_pairs;
564 }
565 
566 void
568 {
569  // proform problem checks/corrections once during the first feasible task
570  if (_current_task == "add_contact_aux_variable")
571  {
572  if (!_problem->getDisplacedProblem())
573  mooseError(
574  "Contact requires updated coordinates. Use the 'displacements = ...' parameter in the "
575  "Mesh block.");
576 
577  // It is risky to apply this optimization to contact problems
578  // since the problem configuration may be changed during Jacobian
579  // evaluation. We therefore turn it off for all contact problems so that
580  // PETSc-3.8.4 or higher will have the same behavior as PETSc-3.8.3.
581  if (!_problem->isSNESMFReuseBaseSetbyUser())
582  _problem->setSNESMFReuseBase(false, false);
583  }
584 
585  if (_formulation == ContactFormulation::MORTAR ||
586  _formulation == ContactFormulation::MORTAR_PENALTY)
588  else
590 
591  if (_current_task == "add_aux_kernel")
592  {
593  if (!_problem->getDisplacedProblem())
594  mooseError("Contact requires updated coordinates. Use the 'displacements = ...' line in the "
595  "Mesh block.");
596 
597  // Create auxiliary kernels for each contact pairs
598  for (const auto & contact_pair : _boundary_pairs)
599  {
600  const auto & [primary_name, secondary_name] = contact_pair;
601  if ((_formulation != ContactFormulation::MORTAR) &&
602  (_formulation != ContactFormulation::MORTAR_PENALTY))
603  {
604  InputParameters params = _factory.getValidParams("PenetrationAux");
605  params.applyParameters(parameters(),
606  {"secondary_gap_offset", "mapped_primary_gap_offset", "order"});
607 
608  std::vector<VariableName> displacements =
609  getParam<std::vector<VariableName>>("displacements");
610  const auto order = _problem->systemBaseNonlinear(/*nl_sys_num=*/0)
611  .system()
612  .variable_type(displacements[0])
613  .order.get_order();
614 
615  params.set<MooseEnum>("order") = Utility::enum_to_string<Order>(OrderWrapper{order});
616  params.set<ExecFlagEnum>("execute_on") = {EXEC_INITIAL, EXEC_LINEAR};
617  params.set<std::vector<BoundaryName>>("boundary") = {secondary_name};
618  params.set<BoundaryName>("paired_boundary") = primary_name;
619  params.set<AuxVariableName>("variable") = "penetration";
620  if (isParamValid("secondary_gap_offset"))
621  params.set<std::vector<VariableName>>("secondary_gap_offset") = {
622  getParam<VariableName>("secondary_gap_offset")};
623  if (isParamValid("mapped_primary_gap_offset"))
624  params.set<std::vector<VariableName>>("mapped_primary_gap_offset") = {
625  getParam<VariableName>("mapped_primary_gap_offset")};
626  params.set<bool>("use_displaced_mesh") = true;
627  std::string name = _name + "_contact_" + Moose::stringify(contact_auxkernel_counter++);
628 
629  _problem->addAuxKernel("PenetrationAux", name, params);
630  }
631  else
632  {
633  const auto type = "MortarUserObjectAux";
635  params.set<std::vector<BoundaryName>>("boundary") = {secondary_name};
636  params.set<AuxVariableName>("variable") = "gap";
637  params.set<bool>("use_displaced_mesh") = true; // Unnecessary as this object only operates
638  // on nodes, but we'll do it for consistency
639  params.set<MooseEnum>("contact_quantity") = "normal_gap";
640  const auto & [primary_id, secondary_id, uo_name] =
641  libmesh_map_find(_bnd_pair_to_mortar_info, contact_pair);
642  params.set<UserObjectName>("user_object") = uo_name;
643  std::string name = _name + "_contact_gap_" + std::to_string(primary_id) + "_" +
644  std::to_string(secondary_id);
645 
646  _problem->addAuxKernel(type, name, params);
647  }
648  }
649 
651 
652  const unsigned int ndisp = getParam<std::vector<VariableName>>("displacements").size();
653 
654  // Add MortarFrictionalPressureVectorAux
655  if (_formulation == ContactFormulation::MORTAR && _model == ContactModel::COULOMB && ndisp > 2)
656  {
657  {
658  InputParameters params = _factory.getValidParams("MortarFrictionalPressureVectorAux");
659 
660  params.set<BoundaryName>("primary_boundary") = _boundary_pairs[0].first;
661  params.set<BoundaryName>("secondary_boundary") = _boundary_pairs[0].second;
662  params.set<std::vector<BoundaryName>>("boundary") = {_boundary_pairs[0].second};
663  params.set<ExecFlagEnum>("execute_on", true) = {EXEC_NONLINEAR};
664 
665  std::string action_name = MooseUtils::shortName(name());
666  const std::string tangential_lagrange_multiplier_name = action_name + "_tangential_lm";
667  const std::string tangential_lagrange_multiplier_3d_name =
668  action_name + "_tangential_3d_lm";
669 
670  params.set<std::vector<VariableName>>("tangent_one") = {
671  tangential_lagrange_multiplier_name};
672  params.set<std::vector<VariableName>>("tangent_two") = {
673  tangential_lagrange_multiplier_3d_name};
674 
675  std::vector<std::string> disp_components({"x", "y", "z"});
676  unsigned component_index = 0;
677 
678  // Loop over three displacements
679  for (const auto & disp_component : disp_components)
680  {
681  params.set<AuxVariableName>("variable") = _name + "_tangent_" + disp_component;
682  params.set<unsigned int>("component") = component_index;
683 
684  std::string name = _name + "_mortar_frictional_pressure_" + disp_component + "_" +
686 
687  _problem->addAuxKernel("MortarFrictionalPressureVectorAux", name, params);
688  component_index++;
689  }
690  }
691  }
692  }
693 
694  if (_current_task == "add_contact_aux_variable")
695  {
696  std::vector<VariableName> displacements = getParam<std::vector<VariableName>>("displacements");
697  const auto order = _problem->systemBaseNonlinear(/*nl_sys_num=*/0)
698  .system()
699  .variable_type(displacements[0])
700  .order.get_order();
701  const auto mortar_lm_order =
702  _lm_space == ContactLMSpace::LINEAR ? static_cast<int>(FIRST) : order;
703  std::unique_ptr<InputParameters> current_params;
704  const auto create_aux_var_params =
705  [this, order, mortar_lm_order, &current_params]() -> InputParameters &
706  {
707  current_params = std::make_unique<InputParameters>(_factory.getValidParams("MooseVariable"));
708  // Node/face and mortar-penalty contact aux variables continue to follow the displacement
709  // order. Mortar LM contact aux variables live on the same contact surface as the generated LM
710  // field, so they use the selected generated LM space.
711  const auto aux_order = _formulation == ContactFormulation::MORTAR ? mortar_lm_order : order;
712  current_params->set<MooseEnum>("order") =
713  Utility::enum_to_string<Order>(OrderWrapper{aux_order});
714  current_params->set<MooseEnum>("family") = "LAGRANGE";
715  return *current_params;
716  };
717 
718  if ((_formulation != ContactFormulation::MORTAR) &&
719  (_formulation != ContactFormulation::MORTAR_PENALTY))
720  {
721  // Add penetration aux variable
722  _problem->addAuxVariable("MooseVariable", "penetration", create_aux_var_params());
723  // Add nodal area aux variable
724  _problem->addAuxVariable("MooseVariable", "nodal_area", create_aux_var_params());
725  }
726  else
727  _problem->addAuxVariable("MooseVariable", "gap", create_aux_var_params());
728 
729  // Add contact pressure aux variable
730  _problem->addAuxVariable("MooseVariable", "contact_pressure", create_aux_var_params());
731 
732  const unsigned int ndisp = getParam<std::vector<VariableName>>("displacements").size();
733 
734  // Add MortarFrictionalPressureVectorAux variables
735  if (_formulation == ContactFormulation::MORTAR && _model == ContactModel::COULOMB && ndisp > 2)
736  {
737  {
738  std::vector<std::string> disp_components({"x", "y", "z"});
739  // Loop over three displacements
740  for (const auto & disp_component : disp_components)
741  {
742  auto var_params = _factory.getValidParams("MooseVariable");
743  var_params.set<MooseEnum>("order") =
744  Utility::enum_to_string<Order>(OrderWrapper{mortar_lm_order});
745  var_params.set<MooseEnum>("family") = "LAGRANGE";
746 
747  _problem->addAuxVariable(
748  "MooseVariable", _name + "_tangent_" + disp_component, var_params);
749  }
750  }
751  }
752  }
753 
754  if (_current_task == "add_user_object" && (_formulation != ContactFormulation::MORTAR) &&
755  (_formulation != ContactFormulation::MORTAR_PENALTY))
756  {
757  auto var_params = _factory.getValidParams("NodalArea");
758 
759  // Get secondary_boundary_vector from possibly updated set from the
760  // ContactAction constructor cleanup
761  const auto actions = _awh.getActions<ContactAction>();
762 
763  std::vector<BoundaryName> secondary_boundary_vector;
764  for (const auto * const action : actions)
765  for (const auto j : index_range(action->_boundary_pairs))
766  secondary_boundary_vector.push_back(action->_boundary_pairs[j].second);
767 
768  var_params.set<std::vector<BoundaryName>>("boundary") = secondary_boundary_vector;
769  var_params.set<std::vector<VariableName>>("variable") = {"nodal_area"};
770 
771  mooseAssert(_problem, "Problem pointer is NULL");
772  var_params.set<ExecFlagEnum>("execute_on", true) = {EXEC_INITIAL, EXEC_TIMESTEP_BEGIN};
773  var_params.set<bool>("use_displaced_mesh") = true;
774 
775  _problem->addUserObject("NodalArea",
776  "nodal_area_object_" + Moose::stringify(contact_userobject_counter++),
777  var_params);
778  }
779 }
780 
781 void
783 {
784  // Increment counter for contact action objects
786 
787  if ((_formulation != ContactFormulation::MORTAR) &&
788  (_formulation != ContactFormulation::MORTAR_PENALTY))
789  {
790  // Add ContactPressureAux: Only one object for all contact pairs
791  const auto actions = _awh.getActions<ContactAction>();
792 
793  // Add auxiliary kernel if we are the last contact action object.
794  if (contact_action_counter == actions.size())
795  {
796  std::vector<BoundaryName> boundary_vector;
797  std::vector<BoundaryName> pair_boundary_vector;
798 
799  for (const auto * const action : actions)
800  for (const auto j : index_range(action->_boundary_pairs))
801  {
802  boundary_vector.push_back(action->_boundary_pairs[j].second);
803  pair_boundary_vector.push_back(action->_boundary_pairs[j].first);
804  }
805 
806  InputParameters params = _factory.getValidParams("ContactPressureAux");
807  params.applyParameters(parameters(), {"order"});
808 
809  std::vector<VariableName> displacements =
810  getParam<std::vector<VariableName>>("displacements");
811  const auto order = _problem->systemBaseNonlinear(/*nl_sys_num=*/0)
812  .system()
813  .variable_type(displacements[0])
814  .order.get_order();
815 
816  params.set<MooseEnum>("order") = Utility::enum_to_string<Order>(OrderWrapper{order});
817  params.set<std::vector<BoundaryName>>("boundary") = boundary_vector;
818  params.set<std::vector<BoundaryName>>("paired_boundary") = pair_boundary_vector;
819  params.set<AuxVariableName>("variable") = "contact_pressure";
820  params.addRequiredCoupledVar("nodal_area", "The nodal area");
821  params.set<std::vector<VariableName>>("nodal_area") = {"nodal_area"};
822  params.set<bool>("use_displaced_mesh") = true;
823 
824  std::string name = _name + "_contact_pressure";
825  params.set<ExecFlagEnum>("execute_on",
827  _problem->addAuxKernel("ContactPressureAux", name, params);
828  }
829  }
830  else
831  for (const auto & contact_pair : _boundary_pairs)
832  {
833  const auto & [_, secondary_name] = contact_pair;
834  const auto type = "MortarUserObjectAux";
836  params.set<std::vector<BoundaryName>>("boundary") = {secondary_name};
837  params.set<AuxVariableName>("variable") = "contact_pressure";
838  params.set<bool>("use_displaced_mesh") = true; // Unecessary as this object only operates on
839  // nodes, but we'll do it for consistency
840  params.set<MooseEnum>("contact_quantity") = "normal_pressure";
841  const auto & [primary_id, secondary_id, uo_name] =
842  libmesh_map_find(_bnd_pair_to_mortar_info, contact_pair);
843  params.set<UserObjectName>("user_object") = uo_name;
844  const std::string name = _name + "_contact_pressure" + std::to_string(primary_id) + "_" +
845  std::to_string(secondary_id);
846 
847  _problem->addAuxKernel(type, name, params);
848  }
849 }
850 
851 void
853 {
854  if (_formulation == ContactFormulation::MORTAR ||
855  _formulation == ContactFormulation::MORTAR_PENALTY)
856  {
857  auto params = MortarConstraintBase::validParams();
858  params.set<bool>("use_displaced_mesh") = true;
859  std::string action_name = MooseUtils::shortName(name());
860  const std::string primary_subdomain_name = action_name + "_primary_subdomain";
861  const std::string secondary_subdomain_name = action_name + "_secondary_subdomain";
862  params.set<BoundaryName>("primary_boundary") = _boundary_pairs[0].first;
863  params.set<BoundaryName>("secondary_boundary") = _boundary_pairs[0].second;
864  params.set<SubdomainName>("primary_subdomain") = primary_subdomain_name;
865  params.set<SubdomainName>("secondary_subdomain") = secondary_subdomain_name;
866  params.set<bool>("use_petrov_galerkin") = getParam<bool>("use_petrov_galerkin");
867  params.set<Real>("minimum_projection_angle") = getParam<Real>("minimum_projection_angle");
868  params.set<MooseEnum>("mortar_3d_subpatch_plane") =
869  getParam<MooseEnum>("mortar_3d_subpatch_plane");
870  addRelationshipManagers(input_rm_type, params);
871  }
872  else
873  {
874  const std::string constraint_type = _formulation == ContactFormulation::RANFS
875  ? "RANFSNormalMechanicalContact"
876  : "MechanicalContactConstraint";
877 
878  for (const auto & contact_pair : _boundary_pairs)
879  {
880  auto params = _factory.getValidParams(constraint_type);
881  params.set<bool>("use_displaced_mesh") = true;
882  params.set<bool>("ghost_whole_interface") = getParam<bool>("ghost_whole_interface");
883  params.set<BoundaryName>("primary") = contact_pair.first;
884  params.set<BoundaryName>("secondary") = contact_pair.second;
885  addRelationshipManagers(input_rm_type, params);
886  }
887  }
888 }
889 
890 void
892 {
893  std::string action_name = MooseUtils::shortName(name());
894 
895  std::vector<VariableName> displacements = getParam<std::vector<VariableName>>("displacements");
896  const unsigned int ndisp = displacements.size();
897 
898  // Definitions for mortar contact.
899  const std::string primary_subdomain_name = action_name + "_primary_subdomain";
900  const std::string secondary_subdomain_name = action_name + "_secondary_subdomain";
901  const std::string normal_lagrange_multiplier_name = action_name + "_normal_lm";
902  const std::string tangential_lagrange_multiplier_name = action_name + "_tangential_lm";
903  const std::string tangential_lagrange_multiplier_3d_name = action_name + "_tangential_3d_lm";
904  const std::string auxiliary_lagrange_multiplier_name = action_name + "_aux_lm";
905 
906  if (_current_task == "append_mesh_generator")
907  {
908  // Don't do mesh generators when recovering or when the user has requested for us not to
909  // (presumably because the lower-dimensional blocks are already in the mesh due to manual
910  // addition or because we are restarting)
913  {
914  const MeshGeneratorName primary_name = primary_subdomain_name + "_generator";
915  const MeshGeneratorName secondary_name = secondary_subdomain_name + "_generator";
916 
917  auto primary_params = _factory.getValidParams("LowerDBlockFromSidesetGenerator");
918  auto secondary_params = _factory.getValidParams("LowerDBlockFromSidesetGenerator");
919 
920  primary_params.set<SubdomainName>("new_block_name") = primary_subdomain_name;
921  secondary_params.set<SubdomainName>("new_block_name") = secondary_subdomain_name;
922 
923  primary_params.set<std::vector<BoundaryName>>("sidesets") = {_boundary_pairs[0].first};
924  secondary_params.set<std::vector<BoundaryName>>("sidesets") = {_boundary_pairs[0].second};
925 
926  _app.appendMeshGenerator("LowerDBlockFromSidesetGenerator", primary_name, primary_params);
927  _app.appendMeshGenerator("LowerDBlockFromSidesetGenerator", secondary_name, secondary_params);
928  }
929  }
930 
931  // Add the lagrange multiplier on the secondary subdomain.
932  const auto addLagrangeMultiplier =
933  [this, &secondary_subdomain_name, &displacements](const std::string & variable_name,
934  const Real scaling_factor,
935  const bool add_aux_lm,
936  const bool penalty_traction) //
937  {
938  InputParameters params = _factory.getValidParams("MooseVariableBase");
939 
940  // Allow the user to select "weighted" constraints and standard bases (use_dual = false) or
941  // "legacy" constraints and dual bases (use_dual = true). Unless it's for testing purposes,
942  // this combination isn't recommended
943  if (!add_aux_lm || penalty_traction)
944  params.set<bool>("use_dual") = _use_dual;
945 
946  mooseAssert(_problem->systemBaseNonlinear(/*nl_sys_num=*/0).hasVariable(displacements[0]),
947  "Displacement variable is missing");
948  const auto primal_type =
949  _problem->systemBaseNonlinear(/*nl_sys_num=*/0).system().variable_type(displacements[0]);
950 
951  // The lm_space option is only valid for the mortar Lagrange multiplier formulation. Mortar
952  // penalty traction variables continue to use the displacement order.
953  const int lm_order =
954  _formulation == ContactFormulation::MORTAR && _lm_space == ContactLMSpace::LINEAR
955  ? static_cast<int>(FIRST)
956  : primal_type.order.get_order();
957 
958  if (primal_type.family == LAGRANGE)
959  {
960  params.set<MooseEnum>("family") = Utility::enum_to_string<FEFamily>(primal_type.family);
961  params.set<MooseEnum>("order") = Utility::enum_to_string<Order>(OrderWrapper{lm_order});
962  }
963  else
964  mooseError("Invalid bases for mortar contact.");
965 
966  params.set<std::vector<SubdomainName>>("block") = {secondary_subdomain_name};
967  if (!(add_aux_lm || penalty_traction))
968  params.set<std::vector<Real>>("scaling") = {scaling_factor};
969 
970  auto fe_type = AddVariableAction::feType(params);
971  auto var_type = AddVariableAction::variableType(fe_type);
972  if (add_aux_lm || penalty_traction)
973  _problem->addAuxVariable(var_type, variable_name, params);
974  else
975  _problem->addVariable(var_type, variable_name, params);
976  };
977 
978  if (_current_task == "add_mortar_variable" && _formulation == ContactFormulation::MORTAR)
979  {
980  addLagrangeMultiplier(
981  normal_lagrange_multiplier_name, getParam<Real>("normal_lm_scaling"), false, false);
982 
983  if (_model == ContactModel::COULOMB)
984  {
985  addLagrangeMultiplier(tangential_lagrange_multiplier_name,
986  getParam<Real>("tangential_lm_scaling"),
987  false,
988  false);
989  if (ndisp > 2)
990  addLagrangeMultiplier(tangential_lagrange_multiplier_3d_name,
991  getParam<Real>("tangential_lm_scaling"),
992  false,
993  false);
994  }
995 
996  if (getParam<bool>("use_petrov_galerkin"))
997  addLagrangeMultiplier(auxiliary_lagrange_multiplier_name, 1.0, true, false);
998  }
999  else if (_current_task == "add_mortar_variable" &&
1000  _formulation == ContactFormulation::MORTAR_PENALTY)
1001  {
1002  if (_use_dual)
1003  addLagrangeMultiplier(auxiliary_lagrange_multiplier_name, 1.0, false, true);
1004  }
1005 
1006  if (_current_task == "add_user_object")
1007  {
1008  const auto register_mortar_uo_name = [this](const auto & bnd_pair, const auto & uo_prefix)
1009  {
1010  const auto & [primary_name, secondary_name] = bnd_pair;
1011  const auto primary_id = _mesh->getBoundaryID(primary_name);
1012  const auto secondary_id = _mesh->getBoundaryID(secondary_name);
1013  const auto uo_name = uo_prefix + name();
1014  _bnd_pair_to_mortar_info.emplace(bnd_pair, MortarInfo{primary_id, secondary_id, uo_name});
1015  return uo_name;
1016  };
1017 
1018  // check if the correct problem class is selected if AL parameters are provided
1019  if (_formulation == ContactFormulation::MORTAR_PENALTY &&
1020  !dynamic_cast<AugmentedLagrangianContactProblemInterface *>(_problem.get()))
1021  {
1022  const std::vector<std::string> params = {"penalty_multiplier",
1023  "penalty_multiplier_friction",
1024  "al_penetration_tolerance",
1025  "al_incremental_slip_tolerance",
1026  "al_frictional_force_tolerance"};
1027  for (const auto & param : params)
1028  if (parameters().isParamSetByUser(param))
1029  paramError(param,
1030  "Augmented Lagrange parameter was specified, but the selected problem type "
1031  "does not support Augmented Lagrange iterations.");
1032  }
1033 
1034  if (_model != ContactModel::COULOMB && _formulation == ContactFormulation::MORTAR)
1035  {
1036  auto uo_params = _factory.getValidParams("LMWeightedGapUserObject");
1037 
1038  uo_params.set<BoundaryName>("primary_boundary") = _boundary_pairs[0].first;
1039  uo_params.set<BoundaryName>("secondary_boundary") = _boundary_pairs[0].second;
1040  uo_params.set<SubdomainName>("primary_subdomain") = primary_subdomain_name;
1041  uo_params.set<SubdomainName>("secondary_subdomain") = secondary_subdomain_name;
1042  uo_params.set<std::vector<VariableName>>("disp_x") = {displacements[0]};
1043  uo_params.set<std::vector<VariableName>>("disp_y") = {displacements[1]};
1044  if (ndisp > 2)
1045  uo_params.set<std::vector<VariableName>>("disp_z") = {displacements[2]};
1046  uo_params.set<bool>("use_displaced_mesh") = true;
1047  uo_params.set<std::vector<VariableName>>("lm_variable") = {normal_lagrange_multiplier_name};
1048  uo_params.applySpecificParameters(parameters(),
1049  {"correct_edge_dropping",
1050  "triangulation",
1051  "triangulate_triangles",
1052  "minimum_projection_angle",
1053  "mortar_3d_subpatch_plane",
1054  "mortar_3d_qp_mapping",
1055  "use_petrov_galerkin",
1056  "debug_mesh"});
1057  if (getParam<bool>("use_petrov_galerkin"))
1058  uo_params.set<std::vector<VariableName>>("aux_lm") = {auxiliary_lagrange_multiplier_name};
1059 
1060  _problem->addUserObject("LMWeightedGapUserObject",
1061  register_mortar_uo_name(_boundary_pairs[0], "lm_weightedgap_object_"),
1062  uo_params);
1063  }
1064  else if (_model == ContactModel::COULOMB && _formulation == ContactFormulation::MORTAR)
1065  {
1066  auto uo_params = _factory.getValidParams("LMWeightedVelocitiesUserObject");
1067  uo_params.set<BoundaryName>("primary_boundary") = _boundary_pairs[0].first;
1068  uo_params.set<BoundaryName>("secondary_boundary") = _boundary_pairs[0].second;
1069  uo_params.set<SubdomainName>("primary_subdomain") = primary_subdomain_name;
1070  uo_params.set<SubdomainName>("secondary_subdomain") = secondary_subdomain_name;
1071  uo_params.set<std::vector<VariableName>>("disp_x") = {displacements[0]};
1072  uo_params.set<std::vector<VariableName>>("disp_y") = {displacements[1]};
1073  if (ndisp > 2)
1074  uo_params.set<std::vector<VariableName>>("disp_z") = {displacements[2]};
1075 
1076  uo_params.set<VariableName>("secondary_variable") = displacements[0];
1077  uo_params.set<bool>("use_displaced_mesh") = true;
1078  uo_params.set<std::vector<VariableName>>("lm_variable_normal") = {
1079  normal_lagrange_multiplier_name};
1080  uo_params.set<std::vector<VariableName>>("lm_variable_tangential_one") = {
1081  tangential_lagrange_multiplier_name};
1082  if (ndisp > 2)
1083  uo_params.set<std::vector<VariableName>>("lm_variable_tangential_two") = {
1084  tangential_lagrange_multiplier_3d_name};
1085  uo_params.applySpecificParameters(parameters(),
1086  {"correct_edge_dropping",
1087  "triangulation",
1088  "triangulate_triangles",
1089  "minimum_projection_angle",
1090  "mortar_3d_subpatch_plane",
1091  "mortar_3d_qp_mapping",
1092  "use_petrov_galerkin",
1093  "debug_mesh"});
1094  if (getParam<bool>("use_petrov_galerkin"))
1095  uo_params.set<std::vector<VariableName>>("aux_lm") = {auxiliary_lagrange_multiplier_name};
1096 
1097  const auto uo_name = _problem->addUserObject(
1098  "LMWeightedVelocitiesUserObject",
1099  register_mortar_uo_name(_boundary_pairs[0], "lm_weightedvelocities_object_"),
1100  uo_params);
1101  }
1102 
1103  if (_model != ContactModel::COULOMB && _formulation == ContactFormulation::MORTAR_PENALTY)
1104  {
1105  auto uo_params = _factory.getValidParams("PenaltyWeightedGapUserObject");
1106 
1107  uo_params.set<BoundaryName>("primary_boundary") = _boundary_pairs[0].first;
1108  uo_params.set<BoundaryName>("secondary_boundary") = _boundary_pairs[0].second;
1109  uo_params.set<SubdomainName>("primary_subdomain") = primary_subdomain_name;
1110  uo_params.set<SubdomainName>("secondary_subdomain") = secondary_subdomain_name;
1111  uo_params.set<std::vector<VariableName>>("disp_x") = {displacements[0]};
1112  uo_params.set<std::vector<VariableName>>("disp_y") = {displacements[1]};
1113 
1114  // AL parameters
1115  uo_params.applySpecificParameters(parameters(),
1116  {"correct_edge_dropping",
1117  "triangulation",
1118  "triangulate_triangles",
1119  "minimum_projection_angle",
1120  "mortar_3d_subpatch_plane",
1121  "mortar_3d_qp_mapping",
1122  "penalty",
1123  "debug_mesh",
1124  "max_penalty_multiplier",
1125  "adaptivity_penalty_normal"});
1126 
1127  if (isParamValid("al_penetration_tolerance"))
1128  uo_params.set<Real>("penetration_tolerance") = getParam<Real>("al_penetration_tolerance");
1129  if (isParamValid("penalty_multiplier"))
1130  uo_params.set<Real>("penalty_multiplier") = getParam<Real>("penalty_multiplier");
1131  // In the contact action, we force the physical value of the normal gap, which also normalizes
1132  // the penalty factor with the "area" around the node
1133  uo_params.set<bool>("use_physical_gap") = true;
1134 
1135  if (_use_dual)
1136  uo_params.set<std::vector<VariableName>>("aux_lm") = {auxiliary_lagrange_multiplier_name};
1137 
1138  if (ndisp > 2)
1139  uo_params.set<std::vector<VariableName>>("disp_z") = {displacements[2]};
1140  uo_params.set<bool>("use_displaced_mesh") = true;
1141 
1142  _problem->addUserObject(
1143  "PenaltyWeightedGapUserObject",
1144  register_mortar_uo_name(_boundary_pairs[0], "penalty_weightedgap_object_"),
1145  uo_params);
1146  _problem->haveADObjects(true);
1147  }
1148  else if (_model == ContactModel::COULOMB && _formulation == ContactFormulation::MORTAR_PENALTY)
1149  {
1150  auto uo_params = _factory.getValidParams("PenaltyFrictionUserObject");
1151  uo_params.set<BoundaryName>("primary_boundary") = _boundary_pairs[0].first;
1152  uo_params.set<BoundaryName>("secondary_boundary") = _boundary_pairs[0].second;
1153  uo_params.set<SubdomainName>("primary_subdomain") = primary_subdomain_name;
1154  uo_params.set<SubdomainName>("secondary_subdomain") = secondary_subdomain_name;
1155  uo_params.set<std::vector<VariableName>>("disp_x") = {displacements[0]};
1156  uo_params.set<bool>("correct_edge_dropping") = getParam<bool>("correct_edge_dropping");
1157  uo_params.set<std::vector<VariableName>>("disp_y") = {displacements[1]};
1158  if (ndisp > 2)
1159  uo_params.set<std::vector<VariableName>>("disp_z") = {displacements[2]};
1160 
1161  uo_params.set<VariableName>("secondary_variable") = displacements[0];
1162  uo_params.set<bool>("use_displaced_mesh") = true;
1163  uo_params.set<Real>("friction_coefficient") = getParam<Real>("friction_coefficient");
1164  uo_params.set<Real>("penalty") = getParam<Real>("penalty");
1165  uo_params.set<Real>("penalty_friction") = getParam<Real>("penalty_friction");
1166 
1167  // AL parameters
1168  uo_params.set<Real>("max_penalty_multiplier") = getParam<Real>("max_penalty_multiplier");
1169  uo_params.set<MooseEnum>("adaptivity_penalty_normal") =
1170  getParam<MooseEnum>("adaptivity_penalty_normal");
1171  uo_params.set<MooseEnum>("adaptivity_penalty_friction") =
1172  getParam<MooseEnum>("adaptivity_penalty_friction");
1173  if (isParamValid("al_penetration_tolerance"))
1174  uo_params.set<Real>("penetration_tolerance") = getParam<Real>("al_penetration_tolerance");
1175  if (isParamValid("penalty_multiplier"))
1176  uo_params.set<Real>("penalty_multiplier") = getParam<Real>("penalty_multiplier");
1177  if (isParamValid("penalty_multiplier_friction"))
1178  uo_params.set<Real>("penalty_multiplier_friction") =
1179  getParam<Real>("penalty_multiplier_friction");
1180 
1181  if (isParamValid("al_incremental_slip_tolerance"))
1182  uo_params.set<Real>("slip_tolerance") = getParam<Real>("al_incremental_slip_tolerance");
1183  // In the contact action, we force the physical value of the normal gap, which also normalizes
1184  // the penalty factor with the "area" around the node
1185  uo_params.set<bool>("use_physical_gap") = true;
1186 
1187  if (_use_dual)
1188  uo_params.set<std::vector<VariableName>>("aux_lm") = {auxiliary_lagrange_multiplier_name};
1189 
1190  uo_params.applySpecificParameters(parameters(),
1191  {"triangulation",
1192  "triangulate_triangles",
1193  "minimum_projection_angle",
1194  "mortar_3d_subpatch_plane",
1195  "mortar_3d_qp_mapping",
1196  "friction_coefficient",
1197  "penalty",
1198  "penalty_friction"});
1199 
1200  _problem->addUserObject(
1201  "PenaltyFrictionUserObject",
1202  register_mortar_uo_name(_boundary_pairs[0], "penalty_friction_object_"),
1203  uo_params);
1204  _problem->haveADObjects(true);
1205  }
1206  }
1207 
1208  if (_current_task == "add_constraint")
1209  {
1210  // Prepare problem for enforcement with Lagrange multipliers
1211  if (_model != ContactModel::COULOMB && _formulation == ContactFormulation::MORTAR)
1212  {
1213  std::string mortar_constraint_name;
1214 
1215  if (!_mortar_dynamics)
1216  mortar_constraint_name = "ComputeWeightedGapLMMechanicalContact";
1217  else
1218  mortar_constraint_name = "ComputeDynamicWeightedGapLMMechanicalContact";
1219 
1220  InputParameters params = _factory.getValidParams(mortar_constraint_name);
1221  if (_mortar_dynamics)
1222  params.applySpecificParameters(
1223  parameters(), {"newmark_beta", "newmark_gamma", "capture_tolerance", "wear_depth"});
1224 
1225  else // We need user objects for quasistatic constraints
1226  params.set<UserObjectName>("weighted_gap_uo") = "lm_weightedgap_object_" + name();
1227 
1228  params.set<BoundaryName>("primary_boundary") = _boundary_pairs[0].first;
1229  params.set<BoundaryName>("secondary_boundary") = _boundary_pairs[0].second;
1230  params.set<SubdomainName>("primary_subdomain") = primary_subdomain_name;
1231  params.set<SubdomainName>("secondary_subdomain") = secondary_subdomain_name;
1232  params.set<NonlinearVariableName>("variable") = normal_lagrange_multiplier_name;
1233  params.set<std::vector<VariableName>>("disp_x") = {displacements[0]};
1234  params.set<Real>("c") = getParam<Real>("c_normal");
1235 
1236  if (ndisp > 1)
1237  params.set<std::vector<VariableName>>("disp_y") = {displacements[1]};
1238  if (ndisp > 2)
1239  params.set<std::vector<VariableName>>("disp_z") = {displacements[2]};
1240 
1241  params.set<bool>("use_displaced_mesh") = true;
1242 
1244  {"correct_edge_dropping",
1245  "triangulation",
1246  "triangulate_triangles",
1247  "minimum_projection_angle",
1248  "mortar_3d_subpatch_plane",
1249  "mortar_3d_qp_mapping",
1250  "normalize_c",
1251  "extra_vector_tags",
1252  "absolute_value_vector_tags",
1253  "debug_mesh"});
1254 
1255  _problem->addConstraint(
1256  mortar_constraint_name, action_name + "_normal_lm_weighted_gap", params);
1257  _problem->haveADObjects(true);
1258  }
1259  // Add the tangential and normal Lagrange's multiplier constraints on the secondary boundary.
1260  else if (_model == ContactModel::COULOMB && _formulation == ContactFormulation::MORTAR)
1261  {
1262  std::string mortar_constraint_name;
1263 
1264  if (!_mortar_dynamics)
1265  mortar_constraint_name = "ComputeFrictionalForceLMMechanicalContact";
1266  else
1267  mortar_constraint_name = "ComputeDynamicFrictionalForceLMMechanicalContact";
1268 
1269  InputParameters params = _factory.getValidParams(mortar_constraint_name);
1270  if (_mortar_dynamics)
1271  params.applySpecificParameters(
1272  parameters(), {"newmark_beta", "newmark_gamma", "capture_tolerance", "wear_depth"});
1273  else
1274  { // We need user objects for quasistatic constraints
1275  params.set<UserObjectName>("weighted_gap_uo") = "lm_weightedvelocities_object_" + name();
1276  params.set<UserObjectName>("weighted_velocities_uo") =
1277  "lm_weightedvelocities_object_" + name();
1278  }
1279 
1280  params.set<bool>("correct_edge_dropping") = getParam<bool>("correct_edge_dropping");
1281  params.set<BoundaryName>("primary_boundary") = _boundary_pairs[0].first;
1282  params.set<BoundaryName>("secondary_boundary") = _boundary_pairs[0].second;
1283  params.set<SubdomainName>("primary_subdomain") = primary_subdomain_name;
1284  params.set<SubdomainName>("secondary_subdomain") = secondary_subdomain_name;
1285  params.set<bool>("use_displaced_mesh") = true;
1286  params.set<Real>("c_t") = getParam<Real>("c_tangential");
1287  params.set<Real>("c") = getParam<Real>("c_normal");
1288  params.set<bool>("normalize_c") = getParam<bool>("normalize_c");
1289  params.set<bool>("compute_primal_residuals") = false;
1290 
1291  params.set<MooseEnum>("segment_quadrature") = getParam<MooseEnum>("segment_quadrature");
1292 
1293  params.set<std::vector<VariableName>>("disp_x") = {displacements[0]};
1294 
1295  if (ndisp > 1)
1296  params.set<std::vector<VariableName>>("disp_y") = {displacements[1]};
1297  if (ndisp > 2)
1298  params.set<std::vector<VariableName>>("disp_z") = {displacements[2]};
1299 
1300  params.set<NonlinearVariableName>("variable") = normal_lagrange_multiplier_name;
1301  params.set<std::vector<VariableName>>("friction_lm") = {tangential_lagrange_multiplier_name};
1302 
1303  if (ndisp > 2)
1304  params.set<std::vector<VariableName>>("friction_lm_dir") = {
1305  tangential_lagrange_multiplier_3d_name};
1306 
1307  params.set<Real>("mu") = getParam<Real>("friction_coefficient");
1309  {"triangulation",
1310  "triangulate_triangles",
1311  "minimum_projection_angle",
1312  "mortar_3d_subpatch_plane",
1313  "mortar_3d_qp_mapping",
1314  "extra_vector_tags",
1315  "absolute_value_vector_tags",
1316  "debug_mesh"});
1317 
1318  _problem->addConstraint(mortar_constraint_name, action_name + "_tangential_lm", params);
1319  _problem->haveADObjects(true);
1320  }
1321 
1322  const auto addMechanicalContactConstraints =
1323  [this, &primary_subdomain_name, &secondary_subdomain_name, &displacements](
1324  const std::string & variable_name,
1325  const std::string & constraint_prefix,
1326  const std::string & constraint_type,
1327  const bool is_additional_frictional_constraint,
1328  const bool is_normal_constraint)
1329  {
1330  InputParameters params = _factory.getValidParams(constraint_type);
1331 
1332  params.set<bool>("correct_edge_dropping") = getParam<bool>("correct_edge_dropping");
1333  params.set<BoundaryName>("primary_boundary") = _boundary_pairs[0].first;
1334  params.set<BoundaryName>("secondary_boundary") = _boundary_pairs[0].second;
1335  params.set<SubdomainName>("primary_subdomain") = primary_subdomain_name;
1336  params.set<SubdomainName>("secondary_subdomain") = secondary_subdomain_name;
1337 
1338  if (_formulation == ContactFormulation::MORTAR)
1339  params.set<NonlinearVariableName>("variable") = variable_name;
1340 
1341  params.set<MooseEnum>("segment_quadrature") = getParam<MooseEnum>("segment_quadrature");
1342  params.set<bool>("use_displaced_mesh") = true;
1343  params.set<bool>("compute_lm_residuals") = false;
1344 
1345  // Additional displacement residual for frictional problem
1346  // The second frictional LM acts on a perpendicular direction.
1347  if (is_additional_frictional_constraint)
1348  params.set<MooseEnum>("direction") = "direction_2";
1350  {"triangulation",
1351  "triangulate_triangles",
1352  "minimum_projection_angle",
1353  "mortar_3d_subpatch_plane",
1354  "mortar_3d_qp_mapping",
1355  "extra_vector_tags",
1356  "absolute_value_vector_tags",
1357  "debug_mesh"});
1358 
1359  for (unsigned int i = 0; i < displacements.size(); ++i)
1360  {
1361  std::string constraint_name = constraint_prefix + Moose::stringify(i);
1362 
1363  params.set<VariableName>("secondary_variable") = displacements[i];
1364  params.set<MooseEnum>("component") = i;
1365 
1366  if (is_normal_constraint && _model != ContactModel::COULOMB &&
1367  _formulation == ContactFormulation::MORTAR)
1368  params.set<UserObjectName>("weighted_gap_uo") = "lm_weightedgap_object_" + name();
1369  else if (is_normal_constraint && _model == ContactModel::COULOMB &&
1370  _formulation == ContactFormulation::MORTAR)
1371  params.set<UserObjectName>("weighted_gap_uo") = "lm_weightedvelocities_object_" + name();
1372  else if (_formulation == ContactFormulation::MORTAR)
1373  params.set<UserObjectName>("weighted_velocities_uo") =
1374  "lm_weightedvelocities_object_" + name();
1375  else if (is_normal_constraint && _model != ContactModel::COULOMB &&
1376  _formulation == ContactFormulation::MORTAR_PENALTY)
1377  params.set<UserObjectName>("weighted_gap_uo") = "penalty_weightedgap_object_" + name();
1378  else if (is_normal_constraint && _model == ContactModel::COULOMB &&
1379  _formulation == ContactFormulation::MORTAR_PENALTY)
1380  params.set<UserObjectName>("weighted_gap_uo") = "penalty_friction_object_" + name();
1381  else if (_formulation == ContactFormulation::MORTAR_PENALTY)
1382  params.set<UserObjectName>("weighted_velocities_uo") =
1383  "penalty_friction_object_" + name();
1384 
1385  _problem->addConstraint(constraint_type, constraint_name, params);
1386  }
1387  _problem->haveADObjects(true);
1388  };
1389 
1390  // Add mortar mechanical contact constraint objects for primal variables
1391  addMechanicalContactConstraints(normal_lagrange_multiplier_name,
1392  action_name + "_normal_constraint_",
1393  "NormalMortarMechanicalContact",
1394  /* is_additional_frictional_constraint = */ false,
1395  /* is_normal_constraint = */ true);
1396 
1397  if (_model == ContactModel::COULOMB)
1398  {
1399  addMechanicalContactConstraints(tangential_lagrange_multiplier_name,
1400  action_name + "_tangential_constraint_",
1401  "TangentialMortarMechanicalContact",
1402  /* is_additional_frictional_constraint = */ false,
1403  /* is_normal_constraint = */ false);
1404  if (ndisp > 2)
1405  addMechanicalContactConstraints(tangential_lagrange_multiplier_3d_name,
1406  action_name + "_tangential_constraint_3d_",
1407  "TangentialMortarMechanicalContact",
1408  /* is_additional_frictional_constraint = */ true,
1409  /* is_normal_constraint = */ false);
1410  }
1411  }
1412 }
1413 
1414 void
1416 {
1417  if (_current_task == "post_mesh_prepared" && _automatic_pairing_boundaries.size() > 0)
1418  {
1419  if (getParam<MooseEnum>("automatic_pairing_method").getEnum<ProximityMethod>() ==
1420  ProximityMethod::NODE)
1422  else if (getParam<MooseEnum>("automatic_pairing_method").getEnum<ProximityMethod>() ==
1423  ProximityMethod::CENTROID)
1425  }
1426 
1427  if (_current_task != "add_constraint")
1428  return;
1429 
1430  std::string action_name = MooseUtils::shortName(name());
1431  std::vector<VariableName> displacements = getParam<std::vector<VariableName>>("displacements");
1432  const unsigned int ndisp = displacements.size();
1433 
1434  std::string constraint_type;
1435 
1436  if (_formulation == ContactFormulation::RANFS)
1437  constraint_type = "RANFSNormalMechanicalContact";
1438  else
1439  constraint_type = "MechanicalContactConstraint";
1440 
1441  InputParameters params = _factory.getValidParams(constraint_type);
1442 
1443  params.applyParameters(parameters(),
1444  {"displacements",
1445  "secondary_gap_offset",
1446  "mapped_primary_gap_offset",
1447  "primary",
1448  "secondary"});
1449 
1450  const auto order = _problem->systemBaseNonlinear(/*nl_sys_num=*/0)
1451  .system()
1452  .variable_type(displacements[0])
1453  .order.get_order();
1454 
1455  params.set<std::vector<VariableName>>("displacements") = displacements;
1456  params.set<bool>("use_displaced_mesh") = true;
1457  params.set<MooseEnum>("order") = Utility::enum_to_string<Order>(OrderWrapper{order});
1458 
1459  for (const auto & contact_pair : _boundary_pairs)
1460  {
1461  if (_formulation != ContactFormulation::RANFS)
1462  {
1463  params.set<std::vector<VariableName>>("nodal_area") = {"nodal_area"};
1464  params.set<BoundaryName>("boundary") = contact_pair.first;
1465  if (isParamValid("secondary_gap_offset"))
1466  params.set<std::vector<VariableName>>("secondary_gap_offset") = {
1467  getParam<VariableName>("secondary_gap_offset")};
1468  if (isParamValid("mapped_primary_gap_offset"))
1469  params.set<std::vector<VariableName>>("mapped_primary_gap_offset") = {
1470  getParam<VariableName>("mapped_primary_gap_offset")};
1471  }
1472 
1473  for (unsigned int i = 0; i < ndisp; ++i)
1474  {
1475  std::string name = action_name + "_constraint_" + Moose::stringify(contact_pair, "_") + "_" +
1476  Moose::stringify(i);
1477 
1478  if (_formulation == ContactFormulation::RANFS)
1479  params.set<MooseEnum>("component") = i;
1480  else
1481  params.set<unsigned int>("component") = i;
1482 
1483  params.set<BoundaryName>("primary") = contact_pair.first;
1484  params.set<BoundaryName>("secondary") = contact_pair.second;
1485  params.set<NonlinearVariableName>("variable") = displacements[i];
1486  params.set<std::vector<VariableName>>("primary_variable") = {displacements[i]};
1488  {"extra_vector_tags", "absolute_value_vector_tags"});
1489  _problem->addConstraint(constraint_type, name, params);
1490  }
1491  }
1492 }
1493 
1494 // Specialization for PointListAdaptor<MooseMesh::PeriodicNodeInfo>
1495 // Return node location from NodeBoundaryIDInfo pairs
1496 template <>
1497 inline const Point &
1499 {
1500  return *(item.first);
1501 }
1502 
1503 void
1505 {
1506  mooseInfo("The contact action is reading the list of boundaries and automatically pairs them "
1507  "if the distance between nodes is less than a specified distance.");
1508 
1509  if (!_mesh)
1510  mooseError("Failed to obtain mesh for automatically generating contact pairs.");
1511 
1512  if (!_mesh->getMesh().is_serial())
1513  paramError(
1514  "automatic_pairing_boundaries",
1515  "The generation of automatic contact pairs in the contact action requires a serial mesh.");
1516 
1517  // Create automatic_pairing_boundaries_id
1518  std::vector<BoundaryID> _automatic_pairing_boundaries_id;
1519  for (const auto & sideset_name : _automatic_pairing_boundaries)
1520  _automatic_pairing_boundaries_id.emplace_back(_mesh->getBoundaryID(sideset_name));
1521 
1522  // Vector of pairs node-boundary id
1523  std::vector<NodeBoundaryIDInfo> node_boundary_id_vector;
1524 
1525  // Data structures to hold the boundary nodes
1526  const ConstBndNodeRange & bnd_nodes = *_mesh->getBoundaryNodeRange();
1527 
1528  for (const auto & bnode : bnd_nodes)
1529  {
1530  const BoundaryID boundary_id = bnode->_bnd_id;
1531  const Node * node_ptr = bnode->_node;
1532 
1533  // Make sure node is on a boundary chosen for contact mechanics
1534  auto it = std::find(_automatic_pairing_boundaries_id.begin(),
1535  _automatic_pairing_boundaries_id.end(),
1536  boundary_id);
1537 
1538  if (it != _automatic_pairing_boundaries_id.end())
1539  node_boundary_id_vector.emplace_back(node_ptr, boundary_id);
1540  }
1541 
1542  // sort by increasing boundary id
1543  std::sort(node_boundary_id_vector.begin(),
1544  node_boundary_id_vector.end(),
1545  [](const NodeBoundaryIDInfo & first_pair, const NodeBoundaryIDInfo & second_pair)
1546  { return first_pair.second < second_pair.second; });
1547 
1548  // build kd-tree
1549  using KDTreeType = nanoflann::KDTreeSingleIndexAdaptor<
1550  nanoflann::L2_Simple_Adaptor<Real, PointListAdaptor<NodeBoundaryIDInfo>, Real, std::size_t>,
1552  LIBMESH_DIM,
1553  std::size_t>;
1554 
1555  // This parameter can be tuned. Others use '10'
1556  const unsigned int max_leaf_size = 20;
1557 
1558  // Build point list adaptor with all nodes-sidesets pairs for possible mechanical contact
1559  auto point_list = PointListAdaptor<NodeBoundaryIDInfo>(node_boundary_id_vector.begin(),
1560  node_boundary_id_vector.end());
1561  auto kd_tree = std::make_unique<KDTreeType>(
1562  LIBMESH_DIM, point_list, nanoflann::KDTreeSingleIndexAdaptorParams(max_leaf_size));
1563 
1564  if (!kd_tree)
1565  mooseError("Internal error. KDTree was not properly initialized in the contact action.");
1566 
1567  kd_tree->buildIndex();
1568 
1569  // data structures for kd-tree search
1570  nanoflann::SearchParameters search_params;
1571  std::vector<nanoflann::ResultItem<std::size_t, Real>> ret_matches;
1572 
1573  const auto radius_for_search = getParam<Real>("automatic_pairing_distance");
1574 
1575  // For all nodes
1576  for (const auto & pair : node_boundary_id_vector)
1577  {
1578  // clear result buffer
1579  ret_matches.clear();
1580 
1581  // position where we expect a periodic partner for the current node and boundary
1582  const Point search_point = *pair.first;
1583 
1584  // search at the expected point
1585  kd_tree->radiusSearch(
1586  &(search_point)(0), radius_for_search * radius_for_search, ret_matches, search_params);
1587 
1588  for (auto & match_pair : ret_matches)
1589  {
1590  const auto & match = node_boundary_id_vector[match_pair.first];
1591 
1592  //
1593  // If the proximity node identified belongs to a boundary in the input, add boundary pair
1594  //
1595 
1596  // Make sure node is on a boundary chosen for contact mechanics
1597  auto it = std::find(_automatic_pairing_boundaries_id.begin(),
1598  _automatic_pairing_boundaries_id.end(),
1599  match.second);
1600 
1601  // If nodes are on the same boundary, pass.
1602  if (match.second == pair.second)
1603  continue;
1604 
1605  // At this point we will likely create many repeated pairs because many nodal pairs may
1606  // fulfill the distance condition imposed by the automatic_pairing_distance user input
1607  // parameter.
1608  if (it != _automatic_pairing_boundaries_id.end())
1609  {
1610  const auto index_one = cast_int<int>(it - _automatic_pairing_boundaries_id.begin());
1611  auto it_other = std::find(_automatic_pairing_boundaries_id.begin(),
1612  _automatic_pairing_boundaries_id.end(),
1613  pair.second);
1614 
1615  mooseAssert(it_other != _automatic_pairing_boundaries_id.end(),
1616  "Error in contact action. Unable to find boundary ID for node proximity "
1617  "automatic pairing.");
1618 
1619  const auto index_two = cast_int<int>(it_other - _automatic_pairing_boundaries_id.begin());
1620 
1621  if (pair.second > match.second)
1622  _boundary_pairs.push_back(
1624  else
1625  _boundary_pairs.push_back(
1627  }
1628  }
1629  }
1630 
1631  // Let's remove likely repeated pairs
1633 
1634  mooseInfo(
1635  "The following boundary pairs were created by the contact action using nodal proximity: ");
1636  for (const auto & [primary, secondary] : _boundary_pairs)
1638  "Primary boundary ID: ", primary, " and secondary boundary ID: ", secondary, ".");
1639 }
1640 
1641 void
1643 {
1644  mooseInfo("The contact action is reading the list of boundaries and automatically pairs them "
1645  "if their centroids fall within a specified distance of each other.");
1646 
1647  if (!_mesh)
1648  mooseError("Failed to obtain mesh for automatically generating contact pairs.");
1649 
1650  if (!_mesh->getMesh().is_serial())
1651  paramError(
1652  "automatic_pairing_boundaries",
1653  "The generation of automatic contact pairs in the contact action requires a serial mesh.");
1654 
1655  // Compute centers of gravity for each sideset
1656  std::vector<std::pair<BoundaryName, Point>> automatic_pairing_boundaries_cog;
1657  const auto & sideset_ids = _mesh->meshSidesetIds();
1658 
1659  const auto & bnd_to_elem_map = _mesh->getBoundariesToActiveSemiLocalElemIds();
1660 
1661  for (const auto & sideset_name : _automatic_pairing_boundaries)
1662  {
1663  // If the sideset provided in the input file isn't in the mesh, error out.
1664  const auto find_set = sideset_ids.find(_mesh->getBoundaryID(sideset_name));
1665  if (find_set == sideset_ids.end())
1666  paramError("automatic_pairing_boundaries",
1667  sideset_name,
1668  " is not defined as a sideset in the mesh.");
1669 
1670  auto dofs_set = bnd_to_elem_map.find(_mesh->getBoundaryID(sideset_name));
1671 
1672  // Initialize data for sideset
1673  Point center_of_gravity(0, 0, 0);
1674  Real accumulated_sideset_area(0);
1675 
1676  // Pointer to lower-dimensional element on the sideset
1677  std::unique_ptr<const Elem> side_ptr;
1678  const std::unordered_set<dof_id_type> & bnd_elems = dofs_set->second;
1679 
1680  for (auto elem_id : bnd_elems)
1681  {
1682  const Elem * elem = _mesh->elemPtr(elem_id);
1683  unsigned int side = _mesh->sideWithBoundaryID(elem, _mesh->getBoundaryID(sideset_name));
1684 
1685  // update side_ptr
1686  elem->side_ptr(side_ptr, side);
1687 
1688  // area of the (linearized) side
1689  const auto side_area = side_ptr->volume();
1690 
1691  // position of the side
1692  const auto side_position = side_ptr->true_centroid();
1693 
1694  center_of_gravity += side_position * side_area;
1695  accumulated_sideset_area += side_area;
1696  }
1697 
1698  // Average each element's center of gravity (centroid) with its area
1699  center_of_gravity /= accumulated_sideset_area;
1700 
1701  // Add sideset-cog pair to vector
1702  automatic_pairing_boundaries_cog.emplace_back(sideset_name, center_of_gravity);
1703  }
1704 
1705  // Vectors of distances for each pair
1706  std::vector<std::pair<std::pair<BoundaryName, BoundaryName>, Real>> pairs_distances;
1707 
1708  // Assign distances to identify nearby pairs.
1709  for (std::size_t i = 0; i < automatic_pairing_boundaries_cog.size() - 1; i++)
1710  for (std::size_t j = i + 1; j < automatic_pairing_boundaries_cog.size(); j++)
1711  {
1712  const Point & distance_vector =
1713  automatic_pairing_boundaries_cog[i].second - automatic_pairing_boundaries_cog[j].second;
1714 
1715  if (automatic_pairing_boundaries_cog[i].first != automatic_pairing_boundaries_cog[j].first)
1716  {
1717  const Real distance = distance_vector.norm();
1718  const std::pair pair = std::make_pair(automatic_pairing_boundaries_cog[i].first,
1719  automatic_pairing_boundaries_cog[j].first);
1720  pairs_distances.emplace_back(std::make_pair(pair, distance));
1721  }
1722  }
1723 
1724  const auto automatic_pairing_distance = getParam<Real>("automatic_pairing_distance");
1725 
1726  // Loop over all pairs
1727  std::vector<std::pair<std::pair<BoundaryName, BoundaryName>, Real>> lean_pairs_distances;
1728  for (const auto & pair_distance : pairs_distances)
1729  if (pair_distance.second <= automatic_pairing_distance)
1730  {
1731  lean_pairs_distances.emplace_back(pair_distance);
1732  mooseInfoRepeated("Generating contact pair primary--secondary ",
1733  pair_distance.first.first,
1734  "--",
1735  pair_distance.first.second,
1736  ", with a relative distance of ",
1737  pair_distance.second);
1738  }
1739 
1740  // Create the boundary pairs (possibly with repeated pairs depending on user input)
1741  for (const auto & lean_pairs_distance : lean_pairs_distances)
1742  {
1743  // Make sure secondary surface's boundary ID is less than primary surface's boundary ID.
1744  // This is done to ensure some consistency in the boundary matching, which helps in defining
1745  // auxiliary kernels in the input file.
1746  if (_mesh->getBoundaryID(lean_pairs_distance.first.first) >
1747  _mesh->getBoundaryID(lean_pairs_distance.first.second))
1748  _boundary_pairs.push_back(
1749  {lean_pairs_distance.first.first, lean_pairs_distance.first.second});
1750  else
1751  _boundary_pairs.push_back(
1752  {lean_pairs_distance.first.second, lean_pairs_distance.first.first});
1753  }
1754 
1755  // Let's remove possibly repeated pairs
1757 }
1758 
1759 MooseEnum
1761 {
1762  return MooseEnum(getContactModelOptions(), "frictionless");
1763 }
1764 
1765 MooseEnum
1767 {
1768  return MooseEnum(getProximityMethodOptions());
1769 }
1770 
1771 MooseEnum
1773 {
1774  auto formulations = MooseEnum(getContactFormulationOptions(), "kinematic");
1775 
1776  formulations.addDocumentation(
1777  "ranfs",
1778  "Reduced Active Nonlinear Function Set scheme for node-on-face contact. Provides exact "
1779  "enforcement without Lagrange multipliers or penalty terms.");
1780  formulations.addDocumentation(
1781  "kinematic",
1782  "Kinematic contact constraint enforcement transfers the internal forces at secondary nodes "
1783  "to the corresponding primary face for node-on-face contact. Provides exact "
1784  "enforcement without Lagrange multipliers or penalty terms.");
1785  formulations.addDocumentation(
1786  "penalty",
1787  "Node-on-face penalty based contact constraint enforcement. Interpenetration is penalized. "
1788  "Enforcement depends on the penalty magnitude. High penalties can introduce ill conditioning "
1789  "of the system.");
1790  formulations.addDocumentation("augmented_lagrange",
1791  "Node-on-face augmented Lagrange penalty based contact constraint "
1792  "enforcement. Interpenetration is enforced up to a user specified "
1793  "tolerance, ill-conditioning is generally avoided. Requires an "
1794  "Augmented Lagrange Problem class to be used in the simulation.");
1795  formulations.addDocumentation(
1796  "tangential_penalty",
1797  "Node-on-face penalty based frictional contact constraint enforcement. Interpenetration and "
1798  "slip distance for sticking nodes are penalized. Enforcement depends on the penalty "
1799  "magnitudes. High penalties can introduce ill conditioning of the system.");
1800  formulations.addDocumentation(
1801  "mortar",
1802  "Mortar based contact constraint enforcement using Lagrange multipliers. Provides exact "
1803  "enforcement and a variationally consistent formulation. Lagrange multipliers introduce a "
1804  "saddle point character in the system matrix which can have a negative impact on scalability "
1805  "with iterative solvers");
1806  formulations.addDocumentation(
1807  "mortar_penalty",
1808  "Mortar and penalty based contact constraint enforcement. When using an Augmented Lagrange "
1809  "Problem class this provides normal (and tangential) contact constratint enforced up to a "
1810  "user specified tolerances. Without AL the enforcement depends on the penalty magnitudes. "
1811  "High penalties can introduce ill conditioning of the system.");
1812 
1813  return formulations;
1814 }
1815 
1816 MooseEnum
1818 {
1819  return MooseEnum("Constraint", "Constraint");
1820 }
1821 
1822 MooseEnum
1824 {
1825  return MooseEnum("edge_based nodal_normal_based", "");
1826 }
1827 
1830 {
1832 
1833  params.addParam<MooseEnum>("normal_smoothing_method",
1835  "Method to use to smooth normals");
1836  params.addParam<Real>(
1837  "normal_smoothing_distance",
1838  "Distance from edge in parametric coordinates over which to smooth contact normal");
1839 
1840  params.addParam<MooseEnum>(
1841  "formulation", ContactAction::getFormulationEnum(), "The contact formulation");
1842 
1843  params.addParam<MooseEnum>("model", ContactAction::getModelEnum(), "The contact model to use");
1844 
1845  return params;
1846 }
Action class for creating constraints, kernels, and user objects necessary for mechanical contact...
Definition: ContactAction.h:32
LAGRANGE
void mooseInfo(Args &&... args) const
std::vector< std::pair< BoundaryName, BoundaryName > > _boundary_pairs
Primary/Secondary boundary name pairs for mechanical contact.
Definition: ContactAction.h:82
std::vector< BoundaryName > _automatic_pairing_boundaries
List of all possible boundaries for contact for automatic pairing (optional)
Definition: ContactAction.h:85
bool isUltimateMaster() const
void addDeprecatedParam(const std::string &name, const T &value, const std::string &doc_string, const std::string &deprecation_message)
static MooseEnum getFormulationEnum()
Get contact formulation.
RelationshipManagerType
const std::string & _name
virtual void addRelationshipManagers(Moose::RelationshipManagerType input_rm_type) override
ActionWarehouse & _awh
void paramError(const std::string &param, Args... args) const
const T & getParam(const std::string &name) const
void addParam(const std::string &name, const std::initializer_list< typename T::value_type > &value, const std::string &doc_string)
void applySpecificParameters(const InputParameters &common, const std::vector< std::string > &include, bool allow_private=false)
Factory & _factory
static InputParameters commonParameters()
Define parameters used by multiple contact objects.
FIRST
const InputParameters & parameters() const
MooseApp & _app
static unsigned int contact_action_counter
Definition: ContactAction.C:56
T & set(const std::string &name, bool quiet_mode=false)
void removeRepeatedPairs()
Remove repeated contact pairs from _boundary_pairs.
const Point & getPoint(const PointObject &item) const
if(subdm)
InputParameters getValidParams(const std::string &name) const
static unsigned int contact_userobject_counter
Definition: ContactAction.C:53
void mooseInfoRepeated(Args &&... args)
void applyParameters(const InputParameters &common, const std::vector< std::string > &exclude={}, const bool allow_private=false)
static MooseEnum getSmoothingEnum()
Get smoothing type.
static MooseEnum getProximityMethod()
Get proximity method for automatic pairing.
const ContactFormulation _formulation
Contact formulation.
Definition: ContactAction.h:91
virtual void act() override
const ExecFlagType EXEC_TIMESTEP_END
void createSidesetPairsFromGeometry()
Create contact pairs between all boundaries whose centroids are within a user-specified distance of e...
Real distance(const Point &p)
InputParameters emptyInputParameters()
std::string shortName(const std::string &name)
static InputParameters validParams()
std::map< std::pair< BoundaryName, BoundaryName >, const MortarInfo > _bnd_pair_to_mortar_info
Map from boundary pair to mortar user object name.
static unsigned int contact_mortar_auxkernel_counter
Definition: ContactAction.C:47
const std::string & name() const
static InputParameters validParams()
void addMortarContact()
Generate mesh and other Moose objects for Mortar contact.
const bool _generate_mortar_mesh
Whether to generate the mortar mesh (useful in a restart simulation e.g.).
void addNodeFaceContact()
Generate constraints for node to face contact.
const ExecFlagType EXEC_TIMESTEP_BEGIN
boundary_id_type BoundaryID
static MooseEnum getSystemEnum()
Get contact system.
const std::string & type() const
const std::string & _current_task
static std::string variableType(const libMesh::FEType &fe_type, const bool is_fv=false, const bool is_array=false)
std::pair< const Node *, BoundaryID > NodeBoundaryIDInfo
Definition: ContactAction.C:44
const ExecFlagType EXEC_LINEAR
std::string stringify(const T &t)
const MeshGenerator & appendMeshGenerator(const std::string &type, const std::string &name, InputParameters params)
std::pair< T, U > ResultItem
void addRequiredCoupledVar(const std::string &name, const std::string &doc_string)
const bool _mortar_dynamics
Whether mortar dynamic contact constraints are to be used.
ContactAction(const InputParameters &params)
const ExecFlagType EXEC_NONLINEAR
bool isParamSetByUser(const std::string &name) const
void addContactPressureAuxKernel()
Add single contact pressure auxiliary kernel for various contact action objects.
registerMooseAction("ContactApp", ContactAction, "append_mesh_generator")
std::shared_ptr< MooseMesh > & _mesh
static libMesh::FEType feType(const InputParameters &params)
static MooseEnum getModelEnum()
Get contact model.
DIE A HORRIBLE DEATH HERE typedef LIBMESH_DEFAULT_SCALAR_TYPE Real
void createSidesetsFromNodeProximity()
Create contact pairs between all boundaries by determining that nodes on both boundaries are close en...
static unsigned int contact_auxkernel_counter
Definition: ContactAction.C:50
static InputParameters triangulationParams()
bool useMasterMesh() const
void mooseError(Args &&... args) const
void addClassDescription(const std::string &doc_string)
std::shared_ptr< FEProblemBase > & _problem
bool _use_dual
Whether to use the dual Mortar approach.
Definition: ContactAction.h:97
static const std::complex< double > j(0, 1)
Complex number "j" (also known as "i")
void addRangeCheckedParam(const std::string &name, const T &value, const std::string &parsed_function, const std::string &doc_string)
bool isParamValid(const std::string &name) const
std::vector< const T *> getActions()
const ContactModel _model
Contact model type enum.
Definition: ContactAction.h:88
bool isRecovering() const
SearchParams SearchParameters
const ContactLMSpace _lm_space
Finite element space to use for action-generated mortar Lagrange multiplier variables.
Definition: ContactAction.h:94
bool isParamSetByUser(const std::string &name) const
auto index_range(const T &sizable)
static InputParameters validParams()
Definition: ContactAction.C:73
const ExecFlagType EXEC_INITIAL