LCOV - code coverage report
Current view: top level - src/constraints - ComputeWeightedGapCartesianLMMechanicalContact.C (source / functions) Hit Total Coverage
Test: idaholab/moose contact: #31730 (e8b711) with base e0c998 Lines: 161 173 93.1 %
Date: 2025-10-29 16:50:33 Functions: 13 14 92.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 "ComputeWeightedGapCartesianLMMechanicalContact.h"
      11             : #include "DisplacedProblem.h"
      12             : #include "MortarContactUtils.h"
      13             : #include "Assembly.h"
      14             : #include "AutomaticMortarGeneration.h"
      15             : #include "metaphysicl/dualsemidynamicsparsenumberarray.h"
      16             : #include "metaphysicl/parallel_dualnumber.h"
      17             : #include "metaphysicl/parallel_dynamic_std_array_wrapper.h"
      18             : #include "metaphysicl/parallel_semidynamicsparsenumberarray.h"
      19             : #include "timpi/parallel_sync.h"
      20             : 
      21             : registerMooseObject("ContactApp", ComputeWeightedGapCartesianLMMechanicalContact);
      22             : 
      23             : namespace
      24             : {
      25             : const InputParameters &
      26         147 : assignVarsInParamsCartesianWeightedGap(const InputParameters & params_in)
      27             : {
      28             :   InputParameters & ret = const_cast<InputParameters &>(params_in);
      29         147 :   const auto & disp_x_name = ret.get<std::vector<VariableName>>("disp_x");
      30         147 :   if (disp_x_name.size() != 1)
      31           0 :     mooseError("We require that the disp_x parameter have exactly one coupled name");
      32             : 
      33             :   // We do this so we don't get any variable errors during MortarConstraint(Base) construction
      34         294 :   ret.set<VariableName>("secondary_variable") = disp_x_name[0];
      35         147 :   ret.set<VariableName>("primary_variable") = disp_x_name[0];
      36             : 
      37         147 :   return ret;
      38             : }
      39             : }
      40             : 
      41             : InputParameters
      42         294 : ComputeWeightedGapCartesianLMMechanicalContact::validParams()
      43             : {
      44         294 :   InputParameters params = ADMortarConstraint::validParams();
      45         294 :   params.addClassDescription("Computes the weighted gap that will later be used to enforce the "
      46             :                              "zero-penetration mechanical contact conditions");
      47         294 :   params.suppressParameter<VariableName>("secondary_variable");
      48         294 :   params.suppressParameter<VariableName>("primary_variable");
      49         588 :   params.addRequiredCoupledVar("disp_x", "The x displacement variable");
      50         588 :   params.addRequiredCoupledVar("disp_y", "The y displacement variable");
      51         588 :   params.addCoupledVar("disp_z", "The z displacement variable");
      52         588 :   params.addParam<Real>(
      53         588 :       "c", 1e6, "Parameter for balancing the size of the gap and contact pressure");
      54         588 :   params.addRequiredCoupledVar("lm_x",
      55             :                                "Mechanical contact Lagrange multiplier along the x Cartesian axis");
      56         588 :   params.addRequiredCoupledVar(
      57             :       "lm_y", "Mechanical contact Lagrange multiplier along the y Cartesian axis.");
      58         588 :   params.addCoupledVar("lm_z",
      59             :                        "Mechanical contact Lagrange multiplier along the z Cartesian axis.");
      60         294 :   params.set<bool>("interpolate_normals") = false;
      61         588 :   params.addParam<bool>(
      62             :       "normalize_c",
      63         588 :       false,
      64             :       "Whether to normalize c by weighting function norm. When unnormalized "
      65             :       "the value of c effectively depends on element size since in the constraint we compare nodal "
      66             :       "Lagrange Multiplier values to integrated gap values (LM nodal value is independent of "
      67             :       "element size, where integrated values are dependent on element size).");
      68         294 :   params.set<bool>("use_displaced_mesh") = true;
      69         294 :   return params;
      70           0 : }
      71             : 
      72         147 : ComputeWeightedGapCartesianLMMechanicalContact::ComputeWeightedGapCartesianLMMechanicalContact(
      73         147 :     const InputParameters & parameters)
      74             :   : ADMortarConstraint(assignVarsInParamsCartesianWeightedGap(parameters)),
      75         147 :     _secondary_disp_x(adCoupledValue("disp_x")),
      76         147 :     _primary_disp_x(adCoupledNeighborValue("disp_x")),
      77         147 :     _secondary_disp_y(adCoupledValue("disp_y")),
      78         147 :     _primary_disp_y(adCoupledNeighborValue("disp_y")),
      79         147 :     _has_disp_z(isCoupled("disp_z")),
      80         147 :     _secondary_disp_z(_has_disp_z ? &adCoupledValue("disp_z") : nullptr),
      81         147 :     _primary_disp_z(_has_disp_z ? &adCoupledNeighborValue("disp_z") : nullptr),
      82         294 :     _c(getParam<Real>("c")),
      83         294 :     _normalize_c(getParam<bool>("normalize_c")),
      84         147 :     _nodal(getVar("disp_x", 0)->feType().family == LAGRANGE),
      85         147 :     _disp_x_var(getVar("disp_x", 0)),
      86         147 :     _disp_y_var(getVar("disp_y", 0)),
      87         314 :     _disp_z_var(_has_disp_z ? getVar("disp_z", 0) : nullptr)
      88             : {
      89         147 :   _lm_vars.push_back(getVar("lm_x", 0));
      90         147 :   _lm_vars.push_back(getVar("lm_y", 0));
      91             : 
      92         294 :   if (isParamValid("lm_z") ^ _has_disp_z)
      93           0 :     paramError("lm_z",
      94             :                "In three-dimensions, both the Z Lagrange multiplier and the Z displacement need to "
      95             :                "be provided");
      96             : 
      97         147 :   if (_has_disp_z)
      98          40 :     _lm_vars.push_back(getVar("lm_z", 0));
      99             : 
     100         294 :   if (!getParam<bool>("use_displaced_mesh"))
     101           0 :     paramError(
     102             :         "use_displaced_mesh",
     103             :         "'use_displaced_mesh' must be true for the ComputeWeightedGapLMMechanicalContact object");
     104             : 
     105         461 :   for (const auto i : index_range(_lm_vars))
     106         314 :     if (!_lm_vars[i]->isNodal())
     107           0 :       if (_lm_vars[i]->feType().order != static_cast<Order>(0))
     108           0 :         mooseError("Normal contact constraints only support elemental variables of CONSTANT order");
     109         147 : }
     110             : 
     111             : ADReal
     112           0 : ComputeWeightedGapCartesianLMMechanicalContact::computeQpResidual(Moose::MortarType)
     113             : {
     114           0 :   mooseError("We should never call computeQpResidual for ComputeWeightedGapLMMechanicalContact");
     115             : }
     116             : 
     117             : void
     118     1036908 : ComputeWeightedGapCartesianLMMechanicalContact::computeQpProperties()
     119             : {
     120             :   // Trim interior node variable derivatives
     121             :   const auto & primary_ip_lowerd_map = amg().getPrimaryIpToLowerElementMap(
     122     1036908 :       *_lower_primary_elem, *_lower_primary_elem->interior_parent(), *_lower_secondary_elem);
     123             :   const auto & secondary_ip_lowerd_map =
     124     1036908 :       amg().getSecondaryIpToLowerElementMap(*_lower_secondary_elem);
     125             : 
     126     1036908 :   std::array<const MooseVariable *, 3> var_array{{_disp_x_var, _disp_y_var, _disp_z_var}};
     127             :   std::array<ADReal, 3> primary_disp{
     128     1036908 :       {_primary_disp_x[_qp], _primary_disp_y[_qp], _has_disp_z ? (*_primary_disp_z)[_qp] : 0}};
     129     1036908 :   std::array<ADReal, 3> secondary_disp{{_secondary_disp_x[_qp],
     130     1036908 :                                         _secondary_disp_y[_qp],
     131     1036908 :                                         _has_disp_z ? (*_secondary_disp_z)[_qp] : 0}};
     132             : 
     133     1036908 :   trimInteriorNodeDerivatives(primary_ip_lowerd_map, var_array, primary_disp, false);
     134     1036908 :   trimInteriorNodeDerivatives(secondary_ip_lowerd_map, var_array, secondary_disp, true);
     135             : 
     136             :   const ADReal & prim_x = primary_disp[0];
     137             :   const ADReal & prim_y = primary_disp[1];
     138             :   const ADReal * prim_z = nullptr;
     139     1036908 :   if (_has_disp_z)
     140             :     prim_z = &primary_disp[2];
     141             : 
     142             :   const ADReal & sec_x = secondary_disp[0];
     143             :   const ADReal & sec_y = secondary_disp[1];
     144             :   const ADReal * sec_z = nullptr;
     145     1036908 :   if (_has_disp_z)
     146             :     sec_z = &secondary_disp[2];
     147             : 
     148             :   // Compute gap vector
     149     1036908 :   ADRealVectorValue gap_vec = _phys_points_primary[_qp] - _phys_points_secondary[_qp];
     150             : 
     151     1036908 :   gap_vec(0).derivatives() = prim_x.derivatives() - sec_x.derivatives();
     152     1036908 :   gap_vec(1).derivatives() = prim_y.derivatives() - sec_y.derivatives();
     153     1036908 :   if (_has_disp_z)
     154      430080 :     gap_vec(2).derivatives() = prim_z->derivatives() - sec_z->derivatives();
     155             : 
     156             :   // Compute integration point quantities
     157     1036908 :   _qp_gap_nodal = gap_vec * (_JxW_msm[_qp] * _coord[_qp]);
     158             : 
     159             :   // To do normalization of constraint coefficient (c_n)
     160     1036908 :   _qp_factor = _JxW_msm[_qp] * _coord[_qp];
     161     1036908 : }
     162             : 
     163             : void
     164     2933976 : ComputeWeightedGapCartesianLMMechanicalContact::computeQpIProperties()
     165             : {
     166             :   mooseAssert(_normals.size() == _lower_secondary_elem->n_nodes(),
     167             :               "Making sure that _normals is the expected size");
     168             : 
     169             :   // Get the _dof_to_weighted_gap map
     170     2933976 :   const DofObject * dof = _lm_vars[0]->isNodal()
     171     2933976 :                               ? static_cast<const DofObject *>(_lower_secondary_elem->node_ptr(_i))
     172           0 :                               : static_cast<const DofObject *>(_lower_secondary_elem);
     173             : 
     174     5867952 :   _dof_to_weighted_gap[dof].first += _test[_i][_qp] * _qp_gap_nodal * _normals[_i];
     175             : 
     176     2933976 :   if (_normalize_c)
     177           0 :     _dof_to_weighted_gap[dof].second += _test[_i][_qp] * _qp_factor;
     178     2933976 : }
     179             : 
     180             : void
     181        1942 : ComputeWeightedGapCartesianLMMechanicalContact::timestepSetup()
     182             : {
     183             :   _dof_to_old_normal_vector.clear();
     184             : 
     185       14450 :   for (auto & map_pr : _dof_to_normal_vector)
     186             :     _dof_to_old_normal_vector.emplace(map_pr);
     187        1942 : }
     188             : 
     189             : void
     190       27810 : ComputeWeightedGapCartesianLMMechanicalContact::residualSetup()
     191             : {
     192             :   _dof_to_weighted_gap.clear();
     193             :   _dof_to_normal_vector.clear();
     194             :   _dof_to_tangent_vectors.clear();
     195       27810 : }
     196             : 
     197             : void
     198        7598 : ComputeWeightedGapCartesianLMMechanicalContact::jacobianSetup()
     199             : {
     200        7598 :   residualSetup();
     201        7598 : }
     202             : 
     203             : void
     204     1232802 : ComputeWeightedGapCartesianLMMechanicalContact::computeResidual(const Moose::MortarType mortar_type)
     205             : {
     206     1232802 :   if (mortar_type != Moose::MortarType::Lower)
     207             :     return;
     208             : 
     209             :   for (const auto i : index_range(_lm_vars))
     210             :   {
     211             :     mooseAssert(_lm_vars[i], "LM variable is null");
     212             :     libmesh_ignore(i);
     213             :   }
     214             : 
     215     1447842 :   for (_qp = 0; _qp < _qrule_msm->n_points(); _qp++)
     216             :   {
     217     1036908 :     computeQpProperties();
     218     3970884 :     for (_i = 0; _i < _test.size(); ++_i)
     219             :     {
     220     2933976 :       computeQpIProperties();
     221             : 
     222             :       // Get the _dof_to_weighted_gap map
     223             :       const DofObject * dof =
     224     2933976 :           _lm_vars[0]->isNodal()
     225     2933976 :               ? static_cast<const DofObject *>(_lower_secondary_elem->node_ptr(_i))
     226           0 :               : static_cast<const DofObject *>(_lower_secondary_elem);
     227             :       // We do not interpolate geometry, so just match the local node _i with the corresponding _i
     228     2933976 :       _dof_to_normal_vector[dof] = _normals[_i];
     229     2933976 :       const auto & nodal_tangents = amg().getNodalTangents(*_lower_secondary_elem);
     230     2933976 :       _dof_to_tangent_vectors[dof][0] = nodal_tangents[0][_i];
     231     2933976 :       _dof_to_tangent_vectors[dof][1] = nodal_tangents[1][_i];
     232             :     }
     233             :   }
     234             : }
     235             : 
     236             : void
     237      495654 : ComputeWeightedGapCartesianLMMechanicalContact::computeJacobian(const Moose::MortarType mortar_type)
     238             : {
     239             :   // During "computeResidual" and "computeJacobian" we are actually just computing properties on the
     240             :   // mortar segment element mesh. We are *not* actually assembling into the residual/Jacobian. For
     241             :   // the zero-penetration constraint, the property of interest is the map from node to weighted gap.
     242             :   // Computation of the properties proceeds identically for residual and Jacobian evaluation hence
     243             :   // why we simply call computeResidual here. We will assemble into the residual/Jacobian later from
     244             :   // the post() method
     245      495654 :   computeResidual(mortar_type);
     246      495654 : }
     247             : 
     248             : void
     249        1165 : ComputeWeightedGapCartesianLMMechanicalContact::post()
     250             : {
     251        1165 :   Moose::Mortar::Contact::communicateGaps(
     252        1165 :       _dof_to_weighted_gap, _mesh, _nodal, _normalize_c, _communicator, false);
     253             : 
     254        9942 :   for (const auto & pr : _dof_to_weighted_gap)
     255             :   {
     256        8777 :     if (pr.first->processor_id() != this->processor_id())
     257         212 :       continue;
     258             : 
     259        8565 :     _weighted_gap_ptr = &pr.second.first;
     260        8565 :     _normalization_ptr = &pr.second.second;
     261             : 
     262        8565 :     enforceConstraintOnDof(pr.first);
     263             :   }
     264        1165 : }
     265             : 
     266             : void
     267       20572 : ComputeWeightedGapCartesianLMMechanicalContact::incorrectEdgeDroppingPost(
     268             :     const std::unordered_set<const Node *> & inactive_lm_nodes)
     269             : {
     270       20572 :   Moose::Mortar::Contact::communicateGaps(
     271       20572 :       _dof_to_weighted_gap, _mesh, _nodal, _normalize_c, _communicator, false);
     272             : 
     273       77145 :   for (const auto & pr : _dof_to_weighted_gap)
     274             :   {
     275       56573 :     if ((inactive_lm_nodes.find(static_cast<const Node *>(pr.first)) != inactive_lm_nodes.end()) ||
     276       56573 :         (pr.first->processor_id() != this->processor_id()))
     277           0 :       continue;
     278             : 
     279       56573 :     _weighted_gap_ptr = &pr.second.first;
     280       56573 :     _normalization_ptr = &pr.second.second;
     281             : 
     282       56573 :     enforceConstraintOnDof(pr.first);
     283             :   }
     284       20572 : }
     285             : 
     286             : void
     287       65138 : ComputeWeightedGapCartesianLMMechanicalContact::enforceConstraintOnDof(const DofObject * const dof)
     288             : {
     289       65138 :   const auto & weighted_gap = *_weighted_gap_ptr;
     290       65138 :   const Real c = _normalize_c ? _c / *_normalization_ptr : _c;
     291             : 
     292       65138 :   const auto dof_index_x = dof->dof_number(_sys.number(), _lm_vars[0]->number(), 0);
     293       65138 :   const auto dof_index_y = dof->dof_number(_sys.number(), _lm_vars[1]->number(), 0);
     294       65138 :   const Real scaling_factor_x = _lm_vars[0]->scalingFactor();
     295       65138 :   const Real scaling_factor_y = _lm_vars[1]->scalingFactor();
     296             :   Real scaling_factor_z = 1;
     297             : 
     298       65138 :   ADReal lm_x = (*_sys.currentSolution())(dof_index_x);
     299       65138 :   ADReal lm_y = (*_sys.currentSolution())(dof_index_y);
     300             : 
     301             :   Moose::derivInsert(lm_x.derivatives(), dof_index_x, 1.);
     302             :   Moose::derivInsert(lm_y.derivatives(), dof_index_y, 1.);
     303             : 
     304             :   dof_id_type dof_index_z(-1);
     305             :   ADReal lm_z;
     306       65138 :   if (_has_disp_z)
     307             :   {
     308        4064 :     dof_index_z = dof->dof_number(_sys.number(), _lm_vars[2]->number(), 0);
     309        4064 :     lm_z = (*_sys.currentSolution())(dof_index_z);
     310             :     Moose::derivInsert(lm_z.derivatives(), dof_index_z, 1.);
     311        4064 :     scaling_factor_z = _lm_vars[2]->scalingFactor();
     312             :   }
     313             : 
     314             :   ADReal normal_pressure_value =
     315       65138 :       lm_x * _dof_to_normal_vector[dof](0) + lm_y * _dof_to_normal_vector[dof](1);
     316             :   ADReal tangential_pressure_value =
     317       65138 :       lm_x * _dof_to_tangent_vectors[dof][0](0) + lm_y * _dof_to_tangent_vectors[dof][0](1);
     318             : 
     319             :   ADReal tangential_pressure_value_dir;
     320             : 
     321       65138 :   if (_has_disp_z)
     322             :   {
     323        4064 :     normal_pressure_value += lm_z * _dof_to_normal_vector[dof](2);
     324        4064 :     tangential_pressure_value += lm_z * _dof_to_tangent_vectors[dof][0](2);
     325        4064 :     tangential_pressure_value_dir = lm_x * _dof_to_tangent_vectors[dof][1](0) +
     326        4064 :                                     lm_y * _dof_to_tangent_vectors[dof][1](1) +
     327        4064 :                                     lm_z * _dof_to_tangent_vectors[dof][1](2);
     328             :   }
     329             : 
     330       65138 :   ADReal normal_dof_residual = std::min(normal_pressure_value, weighted_gap * c);
     331       65138 :   ADReal tangential_dof_residual = tangential_pressure_value;
     332             : 
     333             :   // Get index for normal constraint.
     334             :   // We do this to get a decent Jacobian structure, which is key for the use of iterative solvers.
     335             :   // Using old normal vector to avoid changes in the Jacobian structure within one time step
     336             : 
     337             :   Real ny, nz;
     338             :   // Intially, use the current normal vector
     339       65138 :   if (_dof_to_old_normal_vector[dof].norm() < TOLERANCE)
     340             :   {
     341        3660 :     ny = _dof_to_normal_vector[dof](1);
     342        3660 :     nz = _dof_to_normal_vector[dof](2);
     343             :   }
     344             :   else
     345             :   {
     346       61478 :     ny = _dof_to_old_normal_vector[dof](1);
     347       61478 :     nz = _dof_to_old_normal_vector[dof](2);
     348             :   }
     349             :   unsigned int component_normal = 0;
     350       65138 :   if (std::abs(ny) > 0.57735)
     351             :     component_normal = 1;
     352        8565 :   else if (std::abs(nz) > 0.57735)
     353             :     component_normal = 2;
     354             : 
     355             :   libmesh_ignore(component_normal);
     356             : 
     357      130276 :   addResidualsAndJacobian(
     358             :       _assembly,
     359      130276 :       std::array<ADReal, 1>{{normal_dof_residual}},
     360       65138 :       std::array<dof_id_type, 1>{{component_normal == 0
     361       60637 :                                       ? dof_index_x
     362             :                                       : (component_normal == 1 ? dof_index_y : dof_index_z)}},
     363             :       component_normal == 0 ? scaling_factor_x
     364             :                             : (component_normal == 1 ? scaling_factor_y : scaling_factor_z));
     365             : 
     366       65138 :   addResidualsAndJacobian(
     367             :       _assembly,
     368      130276 :       std::array<ADReal, 1>{{tangential_dof_residual}},
     369       65138 :       std::array<dof_id_type, 1>{
     370       65138 :           {(component_normal == 0 || component_normal == 2) ? dof_index_y : dof_index_x}},
     371       65138 :       (component_normal == 0 || component_normal == 2) ? scaling_factor_y : scaling_factor_x);
     372             : 
     373       65138 :   if (_has_disp_z)
     374        8128 :     addResidualsAndJacobian(
     375             :         _assembly,
     376        8128 :         std::array<ADReal, 1>{{tangential_pressure_value_dir}},
     377        8128 :         std::array<dof_id_type, 1>{
     378             :             {(component_normal == 0 || component_normal == 1) ? dof_index_z : dof_index_x}},
     379             :         (component_normal == 0 || component_normal == 1) ? scaling_factor_z : scaling_factor_x);
     380       65138 : }

Generated by: LCOV version 1.14