LCOV - code coverage report
Current view: top level - src/userobjects - PenaltyWeightedGapUserObject.C (source / functions) Hit Total Coverage
Test: idaholab/moose contact: 8601ad Lines: 156 171 91.2 %
Date: 2025-07-18 13:27:36 Functions: 20 20 100.0 %
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 "PenaltyWeightedGapUserObject.h"
      11             : #include "AugmentedLagrangianContactProblem.h"
      12             : #include "MooseVariableFE.h"
      13             : #include "SystemBase.h"
      14             : 
      15             : registerMooseObject("ContactApp", PenaltyWeightedGapUserObject);
      16             : 
      17             : const unsigned int PenaltyWeightedGapUserObject::_no_iterations = 0;
      18             : 
      19             : InputParameters
      20         684 : PenaltyWeightedGapUserObject::validParams()
      21             : {
      22         684 :   InputParameters params = WeightedGapUserObject::validParams();
      23         684 :   params.addClassDescription("Computes the mortar normal contact force via a penalty approach.");
      24        1368 :   params.addRequiredParam<Real>("penalty", "The penalty factor");
      25        2052 :   params.addRangeCheckedParam<Real>(
      26             :       "penalty_multiplier",
      27        1368 :       1.0,
      28             :       "penalty_multiplier > 0",
      29             :       "The penalty growth factor between augmented Lagrange "
      30             :       "iterations if weighted gap does not get closed fast enough. For frictional simulations, "
      31             :       "values smaller than 100 are recommended, e.g. 5.");
      32        1368 :   params.addParam<bool>(
      33             :       "use_physical_gap",
      34        1368 :       false,
      35             :       "Whether to use the physical normal gap (not scaled by mortar integration) and normalize the "
      36             :       "penalty coefficient with a representative lower-dimensional volume assigned to the node in "
      37             :       "the contacting boundary. This parameter is defaulted to 'true' in the contact action.");
      38        1368 :   params.addRangeCheckedParam<Real>(
      39             :       "penetration_tolerance",
      40             :       "penetration_tolerance > 0",
      41             :       "Acceptable penetration distance at which augmented Lagrange iterations can be stopped");
      42        2052 :   params.addRangeCheckedParam<Real>(
      43             :       "max_penalty_multiplier",
      44        1368 :       1.0e3,
      45             :       "max_penalty_multiplier >= 1.0",
      46             :       "Maximum multiplier applied to penalty factors when adaptivity is used in an augmented "
      47             :       "Lagrange setting. The penalty factor supplied by the user is used as a reference.");
      48        1368 :   MooseEnum adaptivity_penalty_normal("SIMPLE BUSSETTA", "SIMPLE");
      49        1368 :   adaptivity_penalty_normal.addDocumentation(
      50             :       "SIMPLE", "Keep multiplying by the penalty multiplier between AL iterations");
      51        1368 :   adaptivity_penalty_normal.addDocumentation(
      52             :       "BUSSETTA",
      53             :       "Modify the penalty using an algorithm from Bussetta et al, 2012, Comput Mech 49:259-275 "
      54             :       "between AL iterations.");
      55        1368 :   params.addParam<MooseEnum>(
      56             :       "adaptivity_penalty_normal",
      57             :       adaptivity_penalty_normal,
      58             :       "The augmented Lagrange update strategy used on the normal penalty coefficient.");
      59        1368 :   params.addCoupledVar(
      60             :       "aux_lm",
      61             :       "Auxiliary variable that is utilized together with the "
      62             :       "penalty approach to interpolate the resulting contact tractions using dual bases.");
      63        1368 :   params.addParamNamesToGroup("penalty_multiplier penetration_tolerance max_penalty_multiplier",
      64             :                               "Augmented Lagrange");
      65             : 
      66         684 :   return params;
      67         684 : }
      68             : 
      69         342 : PenaltyWeightedGapUserObject::PenaltyWeightedGapUserObject(const InputParameters & parameters)
      70             :   : WeightedGapUserObject(parameters),
      71             :     AugmentedLagrangeInterface(this),
      72         342 :     _penalty(getParam<Real>("penalty")),
      73         684 :     _penalty_multiplier(getParam<Real>("penalty_multiplier")),
      74         342 :     _penetration_tolerance(
      75         756 :         isParamValid("penetration_tolerance") ? getParam<Real>("penetration_tolerance") : 0.0),
      76         342 :     _augmented_lagrange_problem(
      77         342 :         dynamic_cast<AugmentedLagrangianContactProblemInterface *>(&_fe_problem)),
      78         342 :     _lagrangian_iteration_number(_augmented_lagrange_problem
      79         342 :                                      ? _augmented_lagrange_problem->getLagrangianIterationNumber()
      80             :                                      : _no_iterations),
      81         342 :     _dt(_fe_problem.dt()),
      82         684 :     _use_physical_gap(getParam<bool>("use_physical_gap")),
      83         388 :     _aux_lm_var(isCoupled("aux_lm") ? getVar("aux_lm", 0) : nullptr),
      84         684 :     _max_penalty_multiplier(getParam<Real>("max_penalty_multiplier")),
      85         342 :     _adaptivity_normal(
      86         684 :         getParam<MooseEnum>("adaptivity_penalty_normal").getEnum<AdaptivityNormalPenalty>())
      87             : {
      88         700 :   auto check_type = [this](const auto & var, const auto & var_name)
      89             :   {
      90         700 :     if (!var.isNodal())
      91           0 :       paramError(var_name,
      92             :                  "The displacement variables must have degrees of freedom exclusively on "
      93             :                  "nodes, e.g. they should probably be of finite element type 'Lagrange'.");
      94        1042 :   };
      95         342 :   check_type(*_disp_x_var, "disp_x");
      96         342 :   check_type(*_disp_y_var, "disp_y");
      97         342 :   if (_has_disp_z)
      98          16 :     check_type(*_disp_z_var, "disp_z");
      99             : 
     100         684 :   if (!_augmented_lagrange_problem == isParamValid("penetration_tolerance"))
     101           0 :     paramError("penetration_tolerance",
     102             :                "This parameter must be supplied if and only if an augmented Lagrange problem "
     103             :                "object is used.");
     104         342 : }
     105             : 
     106             : const VariableTestValue &
     107         171 : PenaltyWeightedGapUserObject::test() const
     108             : {
     109         171 :   return _aux_lm_var ? _aux_lm_var->phiLower() : _disp_x_var->phiLower();
     110             : }
     111             : 
     112             : const ADVariableValue &
     113     8673472 : PenaltyWeightedGapUserObject::contactPressure() const
     114             : {
     115     8673472 :   return _contact_pressure;
     116             : }
     117             : 
     118             : void
     119       29775 : PenaltyWeightedGapUserObject::selfInitialize()
     120             : {
     121      162846 :   for (auto & dof_pn : _dof_to_normal_pressure)
     122      133071 :     dof_pn.second = 0.0;
     123       29775 : }
     124             : 
     125             : void
     126       13362 : PenaltyWeightedGapUserObject::initialize()
     127             : {
     128       13362 :   WeightedGapUserObject::initialize();
     129       13362 :   selfInitialize();
     130       13362 : }
     131             : 
     132             : void
     133       29775 : PenaltyWeightedGapUserObject::selfFinalize()
     134             : {
     135             :   // compute new normal pressure for each node
     136      164440 :   for (const auto & [dof_object, wgap] : _dof_to_weighted_gap)
     137             :   {
     138      134665 :     auto penalty = findValue(_dof_to_local_penalty, dof_object, _penalty);
     139             : 
     140             :     // If _use_physical_gap is true we "normalize" the penalty parameter with the surrounding area.
     141      134665 :     auto gap = _use_physical_gap ? adPhysicalGap(wgap) / wgap.second : wgap.first;
     142             : 
     143             :     const auto lagrange_multiplier =
     144      219373 :         _augmented_lagrange_problem ? _dof_to_lagrange_multiplier[dof_object] : 0.0;
     145             : 
     146             :     // keep the negative normal pressure (compressive per convention here)
     147      134665 :     auto normal_pressure = std::min(0.0, penalty * gap + lagrange_multiplier);
     148             : 
     149             :     // we switch conventins here and consider positive normal pressure as compressive
     150      269330 :     _dof_to_normal_pressure[dof_object] = -normal_pressure;
     151             :   }
     152       29775 : }
     153             : 
     154             : void
     155       13362 : PenaltyWeightedGapUserObject::finalize()
     156             : {
     157       13362 :   WeightedGapUserObject::finalize();
     158       13362 :   selfFinalize();
     159       13362 : }
     160             : 
     161             : Real
     162      129696 : PenaltyWeightedGapUserObject::getNormalContactPressure(const Node * const node) const
     163             : {
     164      129696 :   const auto it = _dof_to_normal_pressure.find(_subproblem.mesh().nodePtr(node->id()));
     165             : 
     166      129696 :   if (it != _dof_to_normal_pressure.end())
     167        4136 :     return MetaPhysicL::raw_value(it->second);
     168             :   else
     169             :     return 0.0;
     170             : }
     171             : 
     172             : Real
     173        1296 : PenaltyWeightedGapUserObject::getNormalLagrangeMultiplier(const Node * const node) const
     174             : {
     175        1296 :   const auto it = _dof_to_lagrange_multiplier.find(_subproblem.mesh().nodePtr(node->id()));
     176             : 
     177        1296 :   if (it != _dof_to_lagrange_multiplier.end())
     178        1136 :     return -MetaPhysicL::raw_value(it->second);
     179             :   else
     180             :     return 0.0;
     181             : }
     182             : 
     183             : void
     184      209292 : PenaltyWeightedGapUserObject::reinit()
     185             : {
     186      209292 :   _contact_pressure.resize(_qrule_msm->n_points());
     187      760996 :   for (const auto qp : make_range(_qrule_msm->n_points()))
     188      551704 :     _contact_pressure[qp] = 0.0;
     189             : 
     190      760996 :   for (const auto i : make_range(_test->size()))
     191             :   {
     192      551704 :     const Node * const node = _lower_secondary_elem->node_ptr(i);
     193             : 
     194     2187592 :     for (const auto qp : make_range(_qrule_msm->n_points()))
     195     3271776 :       _contact_pressure[qp] += (*_test)[i][qp] * _dof_to_normal_pressure[node];
     196             :   }
     197      209292 : }
     198             : 
     199             : void
     200        1990 : PenaltyWeightedGapUserObject::selfTimestepSetup()
     201             : {
     202             :   // Let's not clear the LMs for improved performance in
     203             :   // nonlinear problems
     204             : 
     205             :   // reset penalty
     206        1990 :   for (auto & dof_lp : _dof_to_local_penalty)
     207           0 :     dof_lp.second = _penalty;
     208             : 
     209             :   // clear previous gap
     210        1990 :   for (auto & dof_pg : _dof_to_previous_gap)
     211           0 :     dof_pg.second = 0.0;
     212             : 
     213             :   // save old timestep
     214        1990 :   _dt_old = _dt;
     215        1990 : }
     216             : 
     217             : void
     218        1062 : PenaltyWeightedGapUserObject::timestepSetup()
     219             : {
     220        1062 :   selfTimestepSetup();
     221        1062 : }
     222             : 
     223             : bool
     224         143 : PenaltyWeightedGapUserObject::isAugmentedLagrangianConverged()
     225             : {
     226         143 :   Real max_positive_gap = 0.0;
     227         143 :   Real min_negative_gap = 0.0;
     228             : 
     229             :   // Get maximum gap to ascertain whether we are converged.
     230        6446 :   for (auto & [dof_object, wgap] : _dof_to_weighted_gap)
     231             :   {
     232        6303 :     const auto gap = physicalGap(wgap);
     233             :     {
     234             :       // Check condition for nodes that are active
     235       12255 :       if (gap < 0 || _dof_to_lagrange_multiplier[dof_object] < 0.0)
     236             :       {
     237         495 :         max_positive_gap = std::max(max_positive_gap, gap);
     238         495 :         min_negative_gap = std::min(min_negative_gap, gap);
     239             :       }
     240             :     }
     241             :   }
     242             : 
     243             :   // Communicate the extreme gap values in parallel.
     244             :   std::vector<Real> recv;
     245         143 :   if (this->_communicator.rank() == 0)
     246          88 :     recv.resize(this->_communicator.size());
     247         143 :   this->_communicator.gather(
     248         143 :       0, -min_negative_gap > max_positive_gap ? min_negative_gap : max_positive_gap, recv);
     249             : 
     250         143 :   if (this->_communicator.rank() == 0)
     251             :   {
     252          88 :     min_negative_gap = *std::min_element(recv.begin(), recv.end());
     253          88 :     max_positive_gap = *std::max_element(recv.begin(), recv.end());
     254             : 
     255             :     // report the gap value with the largest magnitude
     256          88 :     const Real reported_gap =
     257          88 :         -min_negative_gap > max_positive_gap ? min_negative_gap : max_positive_gap;
     258          88 :     if (std::abs(reported_gap) > _penetration_tolerance)
     259             :     {
     260          16 :       mooseInfoRepeated("Penetration tolerance fail max_gap = ",
     261             :                         reported_gap,
     262             :                         " (gap_tol=",
     263          16 :                         _penetration_tolerance,
     264             :                         "). Iteration number is: ",
     265             :                         _lagrangian_iteration_number,
     266             :                         ".");
     267          16 :       return false;
     268             :     }
     269             :     else
     270          72 :       mooseInfoRepeated("Penetration tolerance success max_gap = ",
     271             :                         reported_gap,
     272             :                         " (gap_tol=",
     273          72 :                         _penetration_tolerance,
     274             :                         "). Iteration number is: ",
     275             :                         _lagrangian_iteration_number,
     276             :                         ".");
     277             :   }
     278             : 
     279             :   return true;
     280             : }
     281             : 
     282             : void
     283         117 : PenaltyWeightedGapUserObject::augmentedLagrangianSetup()
     284             : {
     285             :   // Loop over all nodes for which a gap has been computed
     286        5274 :   for (auto & [dof_object, wgap] : _dof_to_weighted_gap)
     287             :   {
     288             :     const Real gap = physicalGap(wgap);
     289             :     // Store previous augmented lagrange iteration gap
     290        5157 :     _dof_to_previous_gap[dof_object] = gap;
     291             :   }
     292         117 : }
     293             : 
     294             : void
     295         117 : PenaltyWeightedGapUserObject::updateAugmentedLagrangianMultipliers()
     296             : {
     297        5274 :   for (const auto & [dof_object, wgap] : _dof_to_weighted_gap)
     298             :   {
     299             :     auto & penalty = _dof_to_local_penalty[dof_object];
     300        5157 :     if (penalty == 0.0)
     301        1146 :       penalty = _penalty;
     302             : 
     303        5157 :     const auto gap = getNormalGap(static_cast<const Node *>(dof_object));
     304             :     auto & lagrange_multiplier = _dof_to_lagrange_multiplier[dof_object];
     305             : 
     306             :     const auto possible_normalization =
     307        5157 :         (_use_physical_gap ? libmesh_map_find(_dof_to_weighted_gap, dof_object).second : 1.0);
     308             : 
     309             :     // Update penalty (the factor of 1/4 is suggested in the literature, the limit on AL iteration
     310             :     // caps the penalty increase)
     311             :     // Before we were updating the LM before adapting the penalty factor
     312        5157 :     if (lagrange_multiplier + gap * penalty / possible_normalization <= 0)
     313         405 :       lagrange_multiplier += gap * penalty / possible_normalization;
     314             :     else
     315        4752 :       lagrange_multiplier = 0.0;
     316             : 
     317        5157 :     const auto previous_gap = _dof_to_previous_gap[dof_object];
     318        5157 :     Real eval_tn = 0;
     319             : 
     320        5157 :     if (_adaptivity_normal == AdaptivityNormalPenalty::SIMPLE)
     321             :     {
     322           0 :       if (std::abs(gap) > 0.25 * std::abs(previous_gap) && std::abs(gap) > _penetration_tolerance)
     323           0 :         penalty *= _penalty_multiplier;
     324             :     }
     325        5157 :     else if (_adaptivity_normal == AdaptivityNormalPenalty::BUSSETTA)
     326        5157 :       bussettaAdaptivePenalty(previous_gap, gap, penalty, eval_tn);
     327             : 
     328             :     // Heuristics to bound the penalty factor
     329        5157 :     if (penalty < _penalty)
     330           0 :       penalty = _penalty;
     331        5157 :     else if (penalty > _max_penalty_multiplier * _penalty)
     332           0 :       penalty = _max_penalty_multiplier * _penalty;
     333             :   }
     334         117 : }
     335             : 
     336             : void
     337        5157 : PenaltyWeightedGapUserObject::bussettaAdaptivePenalty(const Real previous_gap,
     338             :                                                       const Real gap,
     339             :                                                       Real & penalty,
     340             :                                                       Real & eval_tn)
     341             : {
     342             :   // Positive gaps means no contact
     343        5157 :   if (previous_gap > 0.0)
     344             :   {
     345        3776 :     penalty = _penalty;
     346        3776 :     eval_tn = 0.0;
     347             :   }
     348             :   else
     349             :   {
     350        1381 :     if (previous_gap * gap < 0)
     351          32 :       eval_tn = 0.0;
     352             :     else
     353        1349 :       eval_tn = penalty * previous_gap;
     354             : 
     355        1381 :     adaptiveNormalPenalty(previous_gap, gap, penalty);
     356             :   }
     357        5157 : }
     358             : 
     359             : void
     360        1381 : PenaltyWeightedGapUserObject::adaptiveNormalPenalty(const Real previous_gap,
     361             :                                                     const Real gap,
     362             :                                                     Real & penalty)
     363             : {
     364        1381 :   const bool condition_one = std::abs(std::abs(previous_gap / gap) - 1.0) < 0.01;
     365        1381 :   const bool condition_two = std::abs(gap) > 1.01 * std::abs(previous_gap);
     366             : 
     367        1381 :   if (previous_gap * gap < 0)
     368             :   {
     369          32 :     if (previous_gap > _penetration_tolerance)
     370           0 :       penalty = std::abs(penalty * previous_gap / gap * (std::abs(gap) + _penetration_tolerance) /
     371           0 :                          (gap - previous_gap));
     372             :     else
     373          32 :       penalty = std::abs(penalty * previous_gap / 10.0 / gap);
     374             :   }
     375        1349 :   else if (std::abs(gap) > _penetration_tolerance)
     376             :   {
     377        1146 :     if (std::abs(gap - previous_gap) >
     378        2382 :         std::max(gap / 10.0, std::max(previous_gap / 10.0, 5.0 * _penetration_tolerance)))
     379        1146 :       penalty *= 10.0;
     380           0 :     else if (condition_one && gap < 10.0 * _penetration_tolerance)
     381           0 :       penalty *= MathUtils::pow(std::sqrt(std::abs(gap) / _penetration_tolerance - 1.0) + 1.0, 2);
     382           0 :     else if (condition_two)
     383           0 :       penalty *= 10.0 * previous_gap / gap;
     384             :     else
     385           0 :       penalty *= std::sqrt(std::abs(gap) / _penetration_tolerance - 1.0) + 1.0;
     386             :   }
     387        1381 : }

Generated by: LCOV version 1.14