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

Generated by: LCOV version 1.14