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

Generated by: LCOV version 1.14