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

Generated by: LCOV version 1.14