LCOV - code coverage report
Current view: top level - src/actions - ContactAction.C (source / functions) Hit Total Coverage
Test: idaholab/moose contact: 8601ad Lines: 704 739 95.3 %
Date: 2025-07-18 13:27:36 Functions: 18 19 94.7 %
Legend: Lines: hit not hit

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

Generated by: LCOV version 1.14