LCOV - code coverage report
Current view: top level - src/constraints - ComputeFrictionalForceCartesianLMMechanicalContact.C (source / functions) Hit Total Coverage
Test: idaholab/moose contact: 8601ad Lines: 154 158 97.5 %
Date: 2025-07-18 13:27:36 Functions: 9 9 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 "ComputeFrictionalForceCartesianLMMechanicalContact.h"
      11             : #include "MortarContactUtils.h"
      12             : #include "DisplacedProblem.h"
      13             : #include "Assembly.h"
      14             : #include "metaphysicl/dualsemidynamicsparsenumberarray.h"
      15             : #include "metaphysicl/parallel_dualnumber.h"
      16             : #include "metaphysicl/parallel_dynamic_std_array_wrapper.h"
      17             : #include "metaphysicl/parallel_semidynamicsparsenumberarray.h"
      18             : #include "timpi/parallel_sync.h"
      19             : 
      20             : registerMooseObject("ContactApp", ComputeFrictionalForceCartesianLMMechanicalContact);
      21             : 
      22             : namespace
      23             : {
      24             : const InputParameters &
      25          71 : assignVarsInParamsFriction(const InputParameters & params_in)
      26             : {
      27             :   InputParameters & ret = const_cast<InputParameters &>(params_in);
      28          71 :   const auto & disp_x_name = ret.get<std::vector<VariableName>>("disp_x");
      29          71 :   if (disp_x_name.size() != 1)
      30           0 :     mooseError("We require that the disp_x parameter have exactly one coupled name");
      31             : 
      32             :   // We do this so we don't get any variable errors during MortarConstraint(Base) construction
      33         142 :   ret.set<VariableName>("secondary_variable") = disp_x_name[0];
      34          71 :   ret.set<VariableName>("primary_variable") = disp_x_name[0];
      35             : 
      36          71 :   return ret;
      37             : }
      38             : }
      39             : 
      40             : InputParameters
      41         142 : ComputeFrictionalForceCartesianLMMechanicalContact::validParams()
      42             : {
      43         142 :   InputParameters params = ComputeWeightedGapCartesianLMMechanicalContact::validParams();
      44         142 :   params.addClassDescription("Computes mortar frictional forces.");
      45         284 :   params.addParam<Real>("c_t", 1e0, "Numerical parameter for tangential constraints");
      46         284 :   params.addParam<Real>(
      47             :       "epsilon",
      48         284 :       1.0e-7,
      49             :       "Minimum value of contact pressure that will trigger frictional enforcement");
      50         284 :   params.addRangeCheckedParam<Real>(
      51             :       "mu", "mu > 0", "The friction coefficient for the Coulomb friction law");
      52         142 :   return params;
      53           0 : }
      54             : 
      55          71 : ComputeFrictionalForceCartesianLMMechanicalContact::
      56          71 :     ComputeFrictionalForceCartesianLMMechanicalContact(const InputParameters & parameters)
      57             :   : ComputeWeightedGapCartesianLMMechanicalContact(assignVarsInParamsFriction(parameters)),
      58         142 :     _c_t(getParam<Real>("c_t")),
      59          71 :     _secondary_x_dot(adCoupledDot("disp_x")),
      60          71 :     _primary_x_dot(adCoupledNeighborValueDot("disp_x")),
      61          71 :     _secondary_y_dot(adCoupledDot("disp_y")),
      62          71 :     _primary_y_dot(adCoupledNeighborValueDot("disp_y")),
      63          71 :     _secondary_z_dot(_has_disp_z ? &adCoupledDot("disp_z") : nullptr),
      64          71 :     _primary_z_dot(_has_disp_z ? &adCoupledNeighborValueDot("disp_z") : nullptr),
      65         142 :     _mu(getParam<Real>("mu")),
      66         213 :     _epsilon(getParam<Real>("epsilon"))
      67             : {
      68          71 : }
      69             : 
      70             : void
      71      440324 : ComputeFrictionalForceCartesianLMMechanicalContact::computeQpProperties()
      72             : {
      73      440324 :   ComputeWeightedGapCartesianLMMechanicalContact::computeQpProperties();
      74             : 
      75             :   // Trim derivatives
      76             :   const auto & primary_ip_lowerd_map = amg().getPrimaryIpToLowerElementMap(
      77      440324 :       *_lower_primary_elem, *_lower_primary_elem->interior_parent(), *_lower_secondary_elem);
      78             :   const auto & secondary_ip_lowerd_map =
      79      440324 :       amg().getSecondaryIpToLowerElementMap(*_lower_secondary_elem);
      80             : 
      81      440324 :   std::array<const MooseVariable *, 3> var_array{{_disp_x_var, _disp_y_var, _disp_z_var}};
      82             :   std::array<ADReal, 3> primary_disp_dot{
      83      440324 :       {_primary_x_dot[_qp], _primary_y_dot[_qp], _has_disp_z ? (*_primary_z_dot)[_qp] : 0}};
      84             :   std::array<ADReal, 3> secondary_disp_dot{
      85      440324 :       {_secondary_x_dot[_qp], _secondary_y_dot[_qp], _has_disp_z ? (*_secondary_z_dot)[_qp] : 0}};
      86             : 
      87      440324 :   trimInteriorNodeDerivatives(primary_ip_lowerd_map, var_array, primary_disp_dot, false);
      88      440324 :   trimInteriorNodeDerivatives(secondary_ip_lowerd_map, var_array, secondary_disp_dot, true);
      89             : 
      90             :   const ADReal & prim_x_dot = primary_disp_dot[0];
      91             :   const ADReal & prim_y_dot = primary_disp_dot[1];
      92             :   const ADReal * prim_z_dot = nullptr;
      93      440324 :   if (_has_disp_z)
      94             :     prim_z_dot = &primary_disp_dot[2];
      95             : 
      96             :   const ADReal & sec_x_dot = secondary_disp_dot[0];
      97             :   const ADReal & sec_y_dot = secondary_disp_dot[1];
      98             :   const ADReal * sec_z_dot = nullptr;
      99      440324 :   if (_has_disp_z)
     100             :     sec_z_dot = &secondary_disp_dot[2];
     101             : 
     102             :   // Build relative velocity vector
     103             :   ADRealVectorValue relative_velocity;
     104             : 
     105      440324 :   if (_has_disp_z)
     106      110592 :     relative_velocity = {sec_x_dot - prim_x_dot, sec_y_dot - prim_y_dot, *sec_z_dot - *prim_z_dot};
     107             :   else
     108      659464 :     relative_velocity = {sec_x_dot - prim_x_dot, sec_y_dot - prim_y_dot, 0.0};
     109             : 
     110      880648 :   _qp_tangential_velocity_nodal = relative_velocity * (_JxW_msm[_qp] * _coord[_qp]);
     111      440324 : }
     112             : 
     113             : void
     114     1101832 : ComputeFrictionalForceCartesianLMMechanicalContact::computeQpIProperties()
     115             : {
     116     1101832 :   ComputeWeightedGapCartesianLMMechanicalContact::computeQpIProperties();
     117             : 
     118     1101832 :   const auto & nodal_tangents = amg().getNodalTangents(*_lower_secondary_elem);
     119             :   // Get the _dof_to_weighted_tangential_velocity map
     120             :   const DofObject * const dof =
     121     1101832 :       _lm_vars[0]->isNodal() ? static_cast<const DofObject *>(_lower_secondary_elem->node_ptr(_i))
     122           0 :                              : static_cast<const DofObject *>(_lower_secondary_elem);
     123             : 
     124             :   _dof_to_weighted_tangential_velocity[dof][0] +=
     125     2203664 :       _test[_i][_qp] * _qp_tangential_velocity_nodal * nodal_tangents[0][_i];
     126             : 
     127             :   // Get the _dof_to_weighted_tangential_velocity map for a second direction
     128     1101832 :   if (_has_disp_z)
     129             :     _dof_to_weighted_tangential_velocity[dof][1] +=
     130      884736 :         _test[_i][_qp] * _qp_tangential_velocity_nodal * nodal_tangents[1][_i];
     131     1101832 : }
     132             : 
     133             : void
     134        5334 : ComputeFrictionalForceCartesianLMMechanicalContact::residualSetup()
     135             : {
     136        5334 :   ComputeWeightedGapCartesianLMMechanicalContact::residualSetup();
     137             :   _dof_to_weighted_tangential_velocity.clear();
     138        5334 : }
     139             : 
     140             : void
     141        3950 : ComputeFrictionalForceCartesianLMMechanicalContact::post()
     142             : {
     143        3950 :   Moose::Mortar::Contact::communicateGaps(
     144        3950 :       _dof_to_weighted_gap, _mesh, _nodal, _normalize_c, _communicator, false);
     145        3950 :   Moose::Mortar::Contact::communicateVelocities(
     146        3950 :       _dof_to_weighted_tangential_velocity, _mesh, _nodal, _communicator, false);
     147             : 
     148             :   // Enforce frictional complementarity constraints
     149       64312 :   for (const auto & pr : _dof_to_weighted_tangential_velocity)
     150             :   {
     151       60362 :     const DofObject * const dof = pr.first;
     152             : 
     153       60362 :     if (dof->processor_id() != this->processor_id())
     154        1064 :       continue;
     155             : 
     156             :     auto & weighted_gap_pr = _dof_to_weighted_gap[dof];
     157       59298 :     _weighted_gap_ptr = &weighted_gap_pr.first;
     158       59298 :     _normalization_ptr = &weighted_gap_pr.second;
     159       59298 :     _tangential_vel_ptr[0] = &(pr.second[0]);
     160             : 
     161       59298 :     if (_has_disp_z)
     162        1728 :       _tangential_vel_ptr[1] = &(pr.second[1]);
     163             : 
     164       59298 :     enforceConstraintOnDof(dof);
     165             :   }
     166        3950 : }
     167             : 
     168             : void
     169        1384 : ComputeFrictionalForceCartesianLMMechanicalContact::incorrectEdgeDroppingPost(
     170             :     const std::unordered_set<const Node *> & inactive_lm_nodes)
     171             : {
     172        1384 :   Moose::Mortar::Contact::communicateGaps(
     173        1384 :       _dof_to_weighted_gap, _mesh, _nodal, _normalize_c, _communicator, false);
     174        1384 :   Moose::Mortar::Contact::communicateVelocities(
     175        1384 :       _dof_to_weighted_tangential_velocity, _mesh, _nodal, _communicator, false);
     176             : 
     177             :   // Enforce frictional complementarity constraints
     178       93490 :   for (const auto & pr : _dof_to_weighted_tangential_velocity)
     179             :   {
     180       92106 :     const DofObject * const dof = pr.first;
     181             : 
     182             :     // If node inactive, skip
     183       92106 :     if ((inactive_lm_nodes.find(static_cast<const Node *>(dof)) != inactive_lm_nodes.end()) ||
     184             :         (dof->processor_id() != this->processor_id()))
     185        2292 :       continue;
     186             : 
     187       89814 :     _weighted_gap_ptr = &_dof_to_weighted_gap[dof].first;
     188       89814 :     _normalization_ptr = &_dof_to_weighted_gap[dof].second;
     189       89814 :     _tangential_vel_ptr[0] = &pr.second[0];
     190             : 
     191       89814 :     if (_has_disp_z)
     192           0 :       _tangential_vel_ptr[1] = &pr.second[1];
     193             : 
     194       89814 :     enforceConstraintOnDof(dof);
     195             :   }
     196        1384 : }
     197             : 
     198             : void
     199      149112 : ComputeFrictionalForceCartesianLMMechanicalContact::enforceConstraintOnDof(
     200             :     const DofObject * const dof)
     201             : {
     202      149112 :   const auto & weighted_gap = *_weighted_gap_ptr;
     203      149112 :   const Real c = _normalize_c ? _c / *_normalization_ptr : _c;
     204      149112 :   const Real c_t = _normalize_c ? _c_t / *_normalization_ptr : _c_t;
     205             : 
     206      149112 :   const auto dof_index_x = dof->dof_number(_sys.number(), _lm_vars[0]->number(), 0);
     207      149112 :   const auto dof_index_y = dof->dof_number(_sys.number(), _lm_vars[1]->number(), 0);
     208      149112 :   const Real scaling_factor_x = _lm_vars[0]->scalingFactor();
     209      149112 :   const Real scaling_factor_y = _lm_vars[1]->scalingFactor();
     210             :   Real scaling_factor_z = 1;
     211             : 
     212      149112 :   ADReal lm_x = (*_sys.currentSolution())(dof_index_x);
     213      149112 :   ADReal lm_y = (*_sys.currentSolution())(dof_index_y);
     214             : 
     215             :   Moose::derivInsert(lm_x.derivatives(), dof_index_x, 1.);
     216             :   Moose::derivInsert(lm_y.derivatives(), dof_index_y, 1.);
     217             : 
     218             :   dof_id_type dof_index_z(-1);
     219             :   ADReal lm_z;
     220      149112 :   if (_has_disp_z)
     221             :   {
     222        1728 :     dof_index_z = dof->dof_number(_sys.number(), _lm_vars[2]->number(), 0);
     223        1728 :     lm_z = (*_sys.currentSolution())(dof_index_z);
     224             :     Moose::derivInsert(lm_z.derivatives(), dof_index_z, 1.);
     225        1728 :     scaling_factor_z = _lm_vars[2]->scalingFactor();
     226             :   }
     227             : 
     228             :   ADReal normal_pressure_value =
     229      149112 :       lm_x * _dof_to_normal_vector[dof](0) + lm_y * _dof_to_normal_vector[dof](1);
     230             :   ADReal tangential_pressure_value =
     231      149112 :       lm_x * _dof_to_tangent_vectors[dof][0](0) + lm_y * _dof_to_tangent_vectors[dof][0](1);
     232             : 
     233             :   ADReal tangential_pressure_value_dir;
     234             : 
     235      149112 :   if (_has_disp_z)
     236             :   {
     237        1728 :     normal_pressure_value += lm_z * _dof_to_normal_vector[dof](2);
     238        1728 :     tangential_pressure_value += lm_z * _dof_to_tangent_vectors[dof][0](2);
     239        1728 :     tangential_pressure_value_dir = lm_x * _dof_to_tangent_vectors[dof][1](0) +
     240        1728 :                                     lm_y * _dof_to_tangent_vectors[dof][1](1) +
     241        1728 :                                     lm_z * _dof_to_tangent_vectors[dof][1](2);
     242             :   }
     243             : 
     244      149112 :   ADReal normal_dof_residual = std::min(normal_pressure_value, weighted_gap * c);
     245             :   ADReal tangential_dof_residual;
     246             :   ADReal tangential_dof_residual_dir;
     247             : 
     248      149112 :   const ADReal & tangential_vel = *_tangential_vel_ptr[0];
     249             : 
     250             :   // Primal-dual active set strategy (PDASS)
     251      149112 :   if (!_has_disp_z)
     252             :   {
     253      147384 :     if (normal_pressure_value.value() < _epsilon)
     254       99147 :       tangential_dof_residual = tangential_pressure_value;
     255             :     else
     256             :     {
     257             :       const auto term_1 =
     258       96474 :           std::max(_mu * (normal_pressure_value + c * weighted_gap),
     259       96474 :                    std::abs(tangential_pressure_value + c_t * tangential_vel * _dt)) *
     260             :           tangential_pressure_value;
     261       48237 :       const auto term_2 = _mu * std::max(0.0, normal_pressure_value + c * weighted_gap) *
     262       96474 :                           (tangential_pressure_value + c_t * tangential_vel * _dt);
     263             : 
     264       48237 :       tangential_dof_residual = term_1 - term_2;
     265             :     }
     266             :   }
     267             :   else
     268             :   {
     269        1728 :     if (normal_pressure_value.value() < _epsilon)
     270             :     {
     271         300 :       tangential_dof_residual = tangential_pressure_value;
     272         300 :       tangential_dof_residual_dir = tangential_pressure_value_dir;
     273             :     }
     274             :     else
     275             :     {
     276             :       const Real epsilon_sqrt = 1.0e-48;
     277             : 
     278        1428 :       const auto lamdba_plus_cg = normal_pressure_value + c * weighted_gap;
     279             : 
     280             :       std::array<ADReal, 2> lambda_t_plus_ctu;
     281        2856 :       lambda_t_plus_ctu[0] = tangential_pressure_value + c_t * (*_tangential_vel_ptr[0]) * _dt;
     282        2856 :       lambda_t_plus_ctu[1] = tangential_pressure_value_dir + c_t * (*_tangential_vel_ptr[1]) * _dt;
     283             : 
     284             :       const auto term_1_x =
     285        2856 :           std::max(_mu * lamdba_plus_cg,
     286        1428 :                    std::sqrt(lambda_t_plus_ctu[0] * lambda_t_plus_ctu[0] +
     287        1428 :                              lambda_t_plus_ctu[1] * lambda_t_plus_ctu[1] + epsilon_sqrt)) *
     288             :           tangential_pressure_value;
     289             : 
     290             :       const auto term_1_y =
     291        1428 :           std::max(_mu * lamdba_plus_cg,
     292        1428 :                    std::sqrt(lambda_t_plus_ctu[0] * lambda_t_plus_ctu[0] +
     293        1428 :                              lambda_t_plus_ctu[1] * lambda_t_plus_ctu[1] + epsilon_sqrt)) *
     294             :           tangential_pressure_value_dir;
     295             : 
     296        1428 :       const auto term_2_x = _mu * std::max(0.0, lamdba_plus_cg) * lambda_t_plus_ctu[0];
     297             : 
     298        2856 :       const auto term_2_y = _mu * std::max(0.0, lamdba_plus_cg) * lambda_t_plus_ctu[1];
     299             : 
     300        1428 :       tangential_dof_residual = term_1_x - term_2_x;
     301        1428 :       tangential_dof_residual_dir = term_1_y - term_2_y;
     302             :     }
     303             :   }
     304             : 
     305             :   // Compute the friction coefficient (constant or function)
     306             : 
     307             :   // Get index for normal constraint.
     308             :   // We do this to get a decent Jacobian structure, which is key for the use of iterative solvers.
     309             :   // Using old normal vector to avoid changes in the Jacobian structure within one time step
     310             : 
     311             :   Real ny, nz;
     312             :   // Intially, use the current normal vector
     313      149112 :   if (_dof_to_old_normal_vector[dof].norm() < TOLERANCE)
     314             :   {
     315       20745 :     ny = _dof_to_normal_vector[dof](1);
     316       20745 :     nz = _dof_to_normal_vector[dof](2);
     317             :   }
     318             :   else
     319             :   {
     320      128367 :     ny = _dof_to_old_normal_vector[dof](1);
     321      128367 :     nz = _dof_to_old_normal_vector[dof](2);
     322             :   }
     323             : 
     324             :   unsigned int component_normal = 0;
     325             : 
     326             :   // Consider constraint orientation to improve Jacobian structure
     327      149112 :   const Real threshold_for_Jacobian = _has_disp_z ? 1.0 / std::sqrt(3.0) : 1.0 / std::sqrt(2.0);
     328             : 
     329      149112 :   if (std::abs(ny) > threshold_for_Jacobian)
     330             :     component_normal = 1;
     331       61036 :   else if (std::abs(nz) > threshold_for_Jacobian)
     332             :     component_normal = 2;
     333             : 
     334      298224 :   addResidualsAndJacobian(
     335             :       _assembly,
     336      149112 :       std::array<ADReal, 1>{{normal_dof_residual}},
     337      298224 :       std::array<dof_id_type, 1>{{component_normal == 0
     338       89804 :                                       ? dof_index_x
     339             :                                       : (component_normal == 1 ? dof_index_y : dof_index_z)}},
     340             :       component_normal == 0 ? scaling_factor_x
     341             :                             : (component_normal == 1 ? scaling_factor_y : scaling_factor_z));
     342             : 
     343      149112 :   addResidualsAndJacobian(
     344             :       _assembly,
     345      149112 :       std::array<ADReal, 1>{{tangential_dof_residual}},
     346      298224 :       std::array<dof_id_type, 1>{
     347      149112 :           {(component_normal == 0 || component_normal == 2) ? dof_index_y : dof_index_x}},
     348      149112 :       (component_normal == 0 || component_normal == 2) ? scaling_factor_y : scaling_factor_x);
     349             : 
     350      149112 :   if (_has_disp_z)
     351        3456 :     addResidualsAndJacobian(
     352             :         _assembly,
     353        1728 :         std::array<ADReal, 1>{{tangential_dof_residual_dir}},
     354        1728 :         std::array<dof_id_type, 1>{
     355             :             {(component_normal == 0 || component_normal == 1) ? dof_index_z : dof_index_x}},
     356             :         (component_normal == 0 || component_normal == 1) ? scaling_factor_z : scaling_factor_x);
     357      449064 : }

Generated by: LCOV version 1.14