LCOV - code coverage report
Current view: top level - src/constraints - RANFSNormalMechanicalContact.C (source / functions) Hit Total Coverage
Test: idaholab/moose contact: 8601ad Lines: 115 137 83.9 %
Date: 2025-07-18 13:27:36 Functions: 10 11 90.9 %
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 "RANFSNormalMechanicalContact.h"
      11             : #include "PenetrationLocator.h"
      12             : #include "PenetrationInfo.h"
      13             : #include "SystemBase.h"
      14             : #include "Assembly.h"
      15             : #include "NearestNodeLocator.h"
      16             : #include "MooseVariableFE.h"
      17             : 
      18             : #include "libmesh/numeric_vector.h"
      19             : #include "libmesh/enum_fe_family.h"
      20             : 
      21             : registerMooseObject("ContactApp", RANFSNormalMechanicalContact);
      22             : 
      23             : InputParameters
      24         374 : RANFSNormalMechanicalContact::validParams()
      25             : {
      26         374 :   InputParameters params = NodeFaceConstraint::validParams();
      27         374 :   params.set<bool>("use_displaced_mesh") = true;
      28             : 
      29         748 :   MooseEnum component("x=0 y=1 z=2");
      30         748 :   params.addRequiredParam<MooseEnum>(
      31             :       "component", component, "The force component constraint that this object is supplying");
      32         748 :   params.addRequiredCoupledVar(
      33             :       "displacements",
      34             :       "The displacements appropriate for the simulation geometry and coordinate system");
      35         748 :   params.addParam<bool>("ping_pong_protection",
      36         748 :                         false,
      37             :                         "Whether to protect against ping-ponging, e.g. the oscillation of the "
      38             :                         "secondary node between two "
      39             :                         "different primary faces, by tying the secondary node to the "
      40             :                         "edge between the involved primary faces");
      41         748 :   params.addParam<Real>(
      42             :       "normal_smoothing_distance",
      43             :       "Distance from edge in parametric coordinates over which to smooth contact normal");
      44         374 :   params.addClassDescription("Applies the Reduced Active Nonlinear Function Set scheme in which "
      45             :                              "the secondary node's non-linear residual function is replaced by the "
      46             :                              "zero penetration constraint equation when the constraint is active");
      47         374 :   return params;
      48         374 : }
      49             : 
      50         196 : RANFSNormalMechanicalContact::RANFSNormalMechanicalContact(const InputParameters & parameters)
      51             :   : NodeFaceConstraint(parameters),
      52         196 :     _component(getParam<MooseEnum>("component")),
      53         196 :     _mesh_dimension(_mesh.dimension()),
      54         196 :     _residual_copy(_sys.residualGhosted()),
      55         196 :     _dof_number_to_value(coupledComponents("displacements")),
      56         196 :     _disp_coupling(coupledComponents("displacements")),
      57         784 :     _ping_pong_protection(getParam<bool>("ping_pong_protection"))
      58             : {
      59             :   // modern parameter scheme for displacements
      60        1176 :   for (unsigned int i = 0; i < coupledComponents("displacements"); ++i)
      61             :   {
      62         392 :     _vars.push_back(coupled("displacements", i));
      63         784 :     _var_objects.push_back(getVar("displacements", i));
      64             :   }
      65             : 
      66         588 :   for (auto & var : _var_objects)
      67         392 :     if (var->feType().family != LAGRANGE)
      68           0 :       mooseError("This object only works when the displacement variables use a Lagrange basis");
      69             : 
      70         196 :   if (_vars.size() != _mesh_dimension)
      71           0 :     mooseError("The number of displacement variables does not match the mesh dimension!");
      72             : 
      73         392 :   if (parameters.isParamValid("normal_smoothing_distance"))
      74         216 :     _penetration_locator.setNormalSmoothingDistance(getParam<Real>("normal_smoothing_distance"));
      75         196 : }
      76             : 
      77             : void
      78         196 : RANFSNormalMechanicalContact::initialSetup()
      79             : {
      80         196 :   auto system_coupling_matrix = _subproblem.couplingMatrix(_sys.number());
      81             : 
      82         588 :   for (MooseIndex(_vars) i = 0; i < _vars.size(); ++i)
      83        1176 :     for (MooseIndex(_vars) j = 0; j < _vars.size(); ++j)
      84         784 :       _disp_coupling(i, j) = (*system_coupling_matrix)(_vars[i], _vars[j]);
      85         196 : }
      86             : 
      87             : void
      88        1638 : RANFSNormalMechanicalContact::timestepSetup()
      89             : {
      90             :   _node_to_primary_elem_sequence.clear();
      91             :   _ping_pong_secondary_node_to_primary_node.clear();
      92        1638 : }
      93             : 
      94             : void
      95       44830 : RANFSNormalMechanicalContact::residualSetup()
      96             : {
      97             :   _node_to_contact_lm.clear();
      98             :   _node_to_tied_lm.clear();
      99       44830 :   NodeFaceConstraint::residualSetup();
     100       44830 : }
     101             : 
     102             : bool
     103       61954 : RANFSNormalMechanicalContact::overwriteSecondaryResidual()
     104             : {
     105       61954 :   if (_tie_nodes)
     106             :     return true;
     107             :   else
     108       61954 :     return _largest_component == static_cast<unsigned int>(_component);
     109             : }
     110             : 
     111             : bool
     112       93678 : RANFSNormalMechanicalContact::shouldApply()
     113             : {
     114             :   std::map<dof_id_type, PenetrationInfo *>::iterator found =
     115       93678 :       _penetration_locator._penetration_info.find(_current_node->id());
     116       93678 :   if (found != _penetration_locator._penetration_info.end())
     117             :   {
     118       93678 :     _pinfo = found->second;
     119       93678 :     if (_pinfo)
     120             :     {
     121             :       // We overwrite the secondary residual when constraints are active so we cannot use the
     122             :       // residual copy for determining the Lagrange multiplier when computing the Jacobian
     123       93678 :       if (!_subproblem.currentlyComputingJacobian())
     124             :       {
     125             :         // Build up residual vector corresponding to contact forces
     126             :         _res_vec.zero();
     127      249984 :         for (unsigned int i = 0; i < _mesh_dimension; ++i)
     128             :         {
     129      166656 :           dof_id_type dof_number = _current_node->dof_number(0, _vars[i], 0);
     130      166656 :           _res_vec(i) = _residual_copy(dof_number) / _var_objects[i]->scalingFactor();
     131             :         }
     132             : 
     133       83328 :         _node_to_contact_lm.insert(std::make_pair(_current_node->id(), _res_vec * _pinfo->_normal));
     134       83328 :         _node_to_tied_lm.insert(std::make_pair(_current_node->id(), _res_vec(_component)));
     135             :       }
     136             :       else
     137             :       {
     138             :         std::vector<dof_id_type> cols;
     139             :         std::vector<Number> values;
     140             : 
     141       31050 :         for (auto & d_to_v : _dof_number_to_value)
     142             :           d_to_v.clear();
     143             : 
     144             :         mooseAssert(_vars.size() == _dof_number_to_value.size() &&
     145             :                         _vars.size() == _var_objects.size(),
     146             :                     "Somehow the sizes of our variable containers got out of sync");
     147       31050 :         for (MooseIndex(_var_objects) i = 0; i < _var_objects.size(); ++i)
     148             :         {
     149       20700 :           auto secondary_dof_number = _current_node->dof_number(0, _vars[i], 0);
     150             : 
     151       20700 :           _jacobian->get_row(secondary_dof_number, cols, values);
     152             :           mooseAssert(cols.size() == values.size(),
     153             :                       "The size of the dof container and value container are different");
     154             : 
     155     1027564 :           for (MooseIndex(cols) j = 0; j < cols.size(); ++j)
     156             :             _dof_number_to_value[i].insert(
     157     1006864 :                 std::make_pair(cols[j], values[j] / _var_objects[i]->scalingFactor()));
     158             :         }
     159             :       }
     160             : 
     161             :       mooseAssert(_node_to_contact_lm.find(_current_node->id()) != _node_to_contact_lm.end(),
     162             :                   "The node " << _current_node->id()
     163             :                               << " should map to a contact lagrange multiplier");
     164             :       mooseAssert(_node_to_tied_lm.find(_current_node->id()) != _node_to_tied_lm.end(),
     165             :                   "The node " << _current_node->id()
     166             :                               << " should map to a tied lagrange multiplier");
     167       93678 :       _contact_lm = _node_to_contact_lm[_current_node->id()];
     168       93678 :       _tied_lm = _node_to_tied_lm[_current_node->id()];
     169             : 
     170             :       // Check to see whether we've locked a ping-ponging node
     171       93678 :       if (_ping_pong_secondary_node_to_primary_node.find(_current_node->id()) ==
     172             :           _ping_pong_secondary_node_to_primary_node.end())
     173             :       {
     174       93678 :         if (_contact_lm > -_pinfo->_distance)
     175             :         {
     176             :           // Ok, our math is telling us we should apply the constraint, but what if we are
     177             :           // ping-ponging back and forth between different primary faces?
     178             : 
     179             :           // This only works for a basic line search! Write assertion here
     180       57412 :           if (_subproblem.computingNonlinearResid())
     181             :           {
     182        6794 :             auto & primary_elem_sequence = _node_to_primary_elem_sequence[_current_node->id()];
     183             :             mooseAssert(
     184             :                 _current_primary == _pinfo->_elem,
     185             :                 "The current primary element and the PenetrationInfo object's element should "
     186             :                 "be the same");
     187        6794 :             primary_elem_sequence.push_back(_pinfo->_elem);
     188             : 
     189             :             // 5 here is a heuristic choice. In testing, generally speaking, if a node ping-pongs
     190             :             // back and forth 5 times then it will ping pong indefinitely. However, if it goes 3
     191             :             // times for example, it is not guaranteed to ping-pong indefinitely and the Newton
     192             :             // iteration may naturally resolve the correct face dofs that need to be involved in the
     193             :             // constraint.
     194        1632 :             if (_ping_pong_protection && primary_elem_sequence.size() >= 5 &&
     195           0 :                 _pinfo->_elem == *(primary_elem_sequence.rbegin() + 2) &&
     196           0 :                 _pinfo->_elem == *(primary_elem_sequence.rbegin() + 4) &&
     197        6794 :                 _pinfo->_elem != *(primary_elem_sequence.rbegin() + 1) &&
     198           0 :                 *(primary_elem_sequence.rbegin() + 1) == *(primary_elem_sequence.rbegin() + 3))
     199             :             {
     200             :               // Ok we are ping-ponging. Determine the primary node
     201             :               auto primary_node =
     202           0 :                   _penetration_locator._nearest_node.nearestNode(_current_node->id());
     203             : 
     204             :               // Sanity checks
     205             :               mooseAssert(_pinfo->_elem->get_node_index(primary_node) != libMesh::invalid_uint,
     206             :                           "The primary node is not on the current element");
     207             :               mooseAssert((*(primary_elem_sequence.rbegin() + 1))->get_node_index(primary_node) !=
     208             :                               libMesh::invalid_uint,
     209             :                           "The primary node is not on the other ping-ponging element");
     210             : 
     211             :               _ping_pong_secondary_node_to_primary_node.insert(
     212           0 :                   std::make_pair(_current_node->id(), primary_node));
     213             :             }
     214             :           }
     215             :         }
     216             :         else
     217             :           // We have not locked the node into contact nor is the gap smaller than the Lagrange
     218             :           // Multiplier so we should not apply
     219             :           return false;
     220             :       }
     221             : 
     222             :       // Determine whether we're going to apply the tied node equality constraint or the contact
     223             :       // inequality constraint
     224       57412 :       auto it = _ping_pong_secondary_node_to_primary_node.find(_current_node->id());
     225       57412 :       if (it != _ping_pong_secondary_node_to_primary_node.end())
     226             :       {
     227           0 :         _tie_nodes = true;
     228           0 :         _nearest_node = it->second;
     229           0 :         _primary_index = _current_primary->get_node_index(_nearest_node);
     230             :         mooseAssert(_primary_index != libMesh::invalid_uint,
     231             :                     "nearest node not a node on the current primary element");
     232             :       }
     233             :       else
     234             :       {
     235       57412 :         _distance = _pinfo->_distance;
     236             :         // Do this to make sure constraint equation has a positive on the diagonal
     237       57412 :         if (_pinfo->_normal(_component) > 0)
     238       42880 :           _distance *= -1;
     239       57412 :         _tie_nodes = false;
     240             : 
     241             :         // The contact constraint is active -> we're going to use our linear solve to ensure that
     242             :         // the gap is driven to zero. We only have one zero-penetration constraint per node, so we
     243             :         // choose to apply the zero penetration constraint only to the displacement component with
     244             :         // the largest magnitude normal
     245       57412 :         auto largest_component_magnitude = std::abs(_pinfo->_normal(0));
     246       57412 :         _largest_component = 0;
     247      114824 :         for (MooseIndex(_mesh_dimension) i = 1; i < _mesh_dimension; ++i)
     248             :         {
     249       57412 :           auto component_magnitude = std::abs(_pinfo->_normal(i));
     250       57412 :           if (component_magnitude > largest_component_magnitude)
     251             :           {
     252             :             largest_component_magnitude = component_magnitude;
     253       57412 :             _largest_component = i;
     254             :           }
     255             :         }
     256             :       }
     257             : 
     258       57412 :       return true;
     259             :     }
     260             :   }
     261             : 
     262             :   // If we're not applying the constraint then we can clear the node to primary elem sequence for
     263             :   // this node
     264           0 :   if (_node_to_primary_elem_sequence.find(_current_node->id()) !=
     265             :       _node_to_primary_elem_sequence.end())
     266           0 :     _node_to_primary_elem_sequence[_current_node->id()].clear();
     267             :   return false;
     268             : }
     269             : 
     270             : Real
     271      254270 : RANFSNormalMechanicalContact::computeQpResidual(Moose::ConstraintType type)
     272             : {
     273      254270 :   switch (type)
     274             :   {
     275       50854 :     case Moose::ConstraintType::Secondary:
     276             :     {
     277       50854 :       if (_tie_nodes)
     278           0 :         return (*_current_node - *_nearest_node)(_component);
     279             :       else
     280             :       {
     281       50854 :         if (_largest_component == static_cast<unsigned int>(_component))
     282             :         {
     283             :           mooseAssert(_pinfo->_normal(_component) != 0,
     284             :                       "We should be selecting the largest normal component, hence it should be "
     285             :                       "impossible for this normal component to be zero");
     286             : 
     287       25427 :           return _distance;
     288             :         }
     289             : 
     290             :         else
     291             :           // The normal points out of the primary face
     292       25427 :           return _contact_lm * -_pinfo->_normal(_component);
     293             :       }
     294             :     }
     295             : 
     296      203416 :     case Moose::ConstraintType::Primary:
     297             :     {
     298      203416 :       if (_tie_nodes)
     299             :       {
     300           0 :         if (_i == _primary_index)
     301           0 :           return _tied_lm;
     302             :         else
     303             :           return 0;
     304             :       }
     305             :       else
     306      203416 :         return _test_primary[_i][_qp] * _contact_lm * _pinfo->_normal(_component);
     307             :     }
     308             : 
     309             :     default:
     310             :       return 0;
     311             :   }
     312             : }
     313             : 
     314             : Real
     315      241580 : RANFSNormalMechanicalContact::computeQpJacobian(Moose::ConstraintJacobianType type)
     316             : {
     317      241580 :   switch (type)
     318             :   {
     319       26116 :     case Moose::ConstraintJacobianType::SecondarySecondary:
     320             :     {
     321       26116 :       if (_tie_nodes)
     322           0 :         return _phi_secondary[_j][_qp];
     323             : 
     324             :       // doing contact
     325             :       else
     326             :       {
     327             :         // corresponds to gap equation
     328       26116 :         if (_largest_component == static_cast<unsigned int>(_component))
     329             :           // _phi_secondary has been set such that it is 1 when _j corresponds to the degree of
     330             :           // freedom associated with the _current node and 0 otherwise
     331       13058 :           return std::abs(_pinfo->_normal(_component)) * _phi_secondary[_j][_qp];
     332             : 
     333             :         // corresponds to regular residual with Lagrange Multiplier applied
     334             :         else
     335             :         {
     336             :           Real ret_val = 0;
     337       39174 :           for (MooseIndex(_disp_coupling) i = 0; i < _disp_coupling.size(); ++i)
     338       26116 :             if (_disp_coupling(_component, i))
     339             :             {
     340             :               mooseAssert(
     341             :                   _dof_number_to_value[i].find(_connected_dof_indices[_j]) !=
     342             :                       _dof_number_to_value[i].end(),
     343             :                   "The connected dof index is not found in the _dof_number_to_value container. "
     344             :                   "This must mean that insufficient sparsity was allocated");
     345       25932 :               ret_val += -_pinfo->_normal(_component) * _pinfo->_normal(i) *
     346       25932 :                          _dof_number_to_value[i][_connected_dof_indices[_j]];
     347             :             }
     348       13058 :           return ret_val;
     349             :         }
     350             :       }
     351             :     }
     352             : 
     353       22200 :     case Moose::ConstraintJacobianType::SecondaryPrimary:
     354             :     {
     355       22200 :       if (_tie_nodes)
     356             :       {
     357           0 :         if (_primary_index == _j)
     358             :           return -1;
     359             : 
     360             :         // We're tying the secondary node to only one node on the primary side (specified by
     361             :         // _primary_index). If the current _j doesn't correspond to that tied primary node, then the
     362             :         // secondary residual doesn't depend on it
     363             :         else
     364           0 :           return 0;
     365             :       }
     366             :       else
     367             :       {
     368       22200 :         if (_largest_component == static_cast<unsigned int>(_component))
     369       11100 :           return -std::abs(_pinfo->_normal(_component)) * _phi_primary[_j][_qp];
     370             : 
     371             :         // If we're not applying the gap constraint equation on this _component, then we're
     372             :         // applying a Lagrange multiplier, and consequently there is no dependence of the secondary
     373             :         // residual on the primary dofs because the Lagrange multiplier is only a functon of the
     374             :         // secondary residuals
     375             :         else
     376             :           return 0;
     377             :       }
     378             :     }
     379             : 
     380      104464 :     case Moose::ConstraintJacobianType::PrimarySecondary:
     381             :     {
     382      104464 :       if (_tie_nodes)
     383             :       {
     384           0 :         if (_i == _primary_index)
     385             :         {
     386             :           mooseAssert(_dof_number_to_value[_component].find(_connected_dof_indices[_j]) !=
     387             :                           _dof_number_to_value[_component].end(),
     388             :                       "The connected dof index is not found in the _dof_number_to_value container. "
     389             :                       "This must mean that insufficient sparsity was allocated");
     390           0 :           return _dof_number_to_value[_component][_connected_dof_indices[_j]];
     391             :         }
     392             : 
     393             :         // We only apply the tied node Lagrange multiplier to the closest primary node
     394             :         else
     395             :           return 0;
     396             :       }
     397             :       else
     398             :       {
     399             :         Real ret_val = 0;
     400      313392 :         for (MooseIndex(_disp_coupling) i = 0; i < _disp_coupling.size(); ++i)
     401      208928 :           if (_disp_coupling(_component, i))
     402             :           {
     403             :             mooseAssert(
     404             :                 _dof_number_to_value[i].find(_connected_dof_indices[_j]) !=
     405             :                     _dof_number_to_value[i].end(),
     406             :                 "The connected dof index is not found in the _dof_number_to_value container. "
     407             :                 "This must mean that insufficient sparsity was allocated");
     408      207456 :             ret_val += _test_primary[_i][_qp] * _pinfo->_normal(_component) * _pinfo->_normal(i) *
     409      207456 :                        _dof_number_to_value[i][_connected_dof_indices[_j]];
     410             :           }
     411      104464 :         return ret_val;
     412             :       }
     413             :     }
     414             : 
     415             :       // The only primary-primary dependence would come from the dependence of the normal and also
     416             :       // the location of the integration (quadrature) points. We assume (valid or not) that this
     417             :       // dependence is weak
     418             :       // case MooseConstraintJacobianType::PrimaryPrimary
     419             : 
     420             :     default:
     421             :       return 0;
     422             :   }
     423             : }
     424             : 
     425             : void
     426        1008 : RANFSNormalMechanicalContact::computeSecondaryValue(NumericVector<Number> &)
     427             : {
     428        1008 : }
     429             : 
     430             : Real
     431           0 : RANFSNormalMechanicalContact::computeQpSecondaryValue()
     432             : {
     433           0 :   mooseError(
     434             :       "We overrode commputeSecondaryValue so computeQpSecondaryValue should never get called");
     435             : }

Generated by: LCOV version 1.14