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