LCOV - code coverage report
Current view: top level - src/constraints - NodalFrictionalConstraint.C (source / functions) Hit Total Coverage
Test: idaholab/moose solid_mechanics: f45d79 Lines: 112 142 78.9 %
Date: 2025-07-25 05:00:39 Functions: 7 8 87.5 %
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             : // MOOSE includes
      11             : #include "NodalFrictionalConstraint.h"
      12             : #include "NodalConstraintUtils.h"
      13             : #include "MooseMesh.h"
      14             : #include "Assembly.h"
      15             : #include "SystemBase.h"
      16             : 
      17             : #include "libmesh/null_output_iterator.h"
      18             : #include "libmesh/parallel.h"
      19             : #include "libmesh/parallel_elem.h"
      20             : #include "libmesh/parallel_node.h"
      21             : 
      22             : // C++ includes
      23             : #include <limits.h>
      24             : 
      25             : using namespace libMesh;
      26             : 
      27             : registerMooseObject("SolidMechanicsApp", NodalFrictionalConstraint);
      28             : 
      29             : InputParameters
      30          24 : NodalFrictionalConstraint::validParams()
      31             : {
      32          24 :   InputParameters params = NodalConstraint::validParams();
      33          24 :   params.addClassDescription("Frictional nodal constraint for contact");
      34          48 :   params.addRequiredParam<BoundaryName>("boundary", "The primary boundary");
      35          48 :   params.addRequiredParam<BoundaryName>("secondary", "The secondary boundary");
      36          48 :   params.addRequiredParam<Real>("friction_coefficient",
      37             :                                 "Friction coefficient for slippage in the normal direction");
      38          48 :   params.addRequiredParam<Real>("normal_force",
      39             :                                 "Normal force used together with friction_coefficient to compute "
      40             :                                 "the normal frictional capacity.");
      41          48 :   params.addRequiredParam<Real>("tangential_penalty",
      42             :                                 "Stiffness of the spring in the tangential direction.");
      43          24 :   return params;
      44           0 : }
      45             : 
      46          12 : NodalFrictionalConstraint::NodalFrictionalConstraint(const InputParameters & parameters)
      47             :   : NodalConstraint(parameters),
      48          24 :     _primary_boundary_id(getParam<BoundaryName>("boundary")),
      49          12 :     _secondary_boundary_id(getParam<BoundaryName>("secondary")),
      50          24 :     _normal_force(getParam<Real>("normal_force")),
      51          24 :     _tangential_penalty(getParam<Real>("tangential_penalty")),
      52          24 :     _friction_coefficient(getParam<Real>("friction_coefficient")),
      53          12 :     _u_secondary_old(_var.dofValuesOldNeighbor()),
      54          24 :     _u_primary_old(_var.dofValuesOld())
      55             : {
      56          12 :   if (_var.number() != _var_secondary.number())
      57           0 :     paramError("variable_secondary",
      58             :                "Primary variable must be identical to secondary "
      59             :                "variable. Different variables are currently not supported.");
      60             : 
      61          12 :   updateConstrainedNodes();
      62             : 
      63          24 :   MooseEnum temp_formulation = getParam<MooseEnum>("formulation");
      64          12 :   if (temp_formulation == "penalty")
      65          12 :     _formulation = Moose::Penalty;
      66           0 :   else if (temp_formulation == "kinematic")
      67           0 :     mooseError("NodalFrictionalConstraint: Kinematic formulation is currently not supported for "
      68             :                "this constraint.");
      69             :   else
      70           0 :     mooseError("Formulation must be set to Penalty.");
      71          12 : }
      72             : 
      73             : void
      74           0 : NodalFrictionalConstraint::meshChanged()
      75             : {
      76           0 :   updateConstrainedNodes();
      77           0 : }
      78             : 
      79             : void
      80          12 : NodalFrictionalConstraint::updateConstrainedNodes()
      81             : {
      82          12 :   _primary_node_vector.clear();
      83          12 :   _connected_nodes.clear();
      84          12 :   _primary_conn.clear();
      85             : 
      86             :   std::vector<dof_id_type> secondary_nodelist =
      87          12 :       _mesh.getNodeList(_mesh.getBoundaryID(_secondary_boundary_id));
      88             :   std::vector<dof_id_type> primary_nodelist =
      89          12 :       _mesh.getNodeList(_mesh.getBoundaryID(_primary_boundary_id));
      90             : 
      91             :   // Fill in _connected_nodes, which defines secondary nodes in the base class
      92          24 :   for (auto in : secondary_nodelist)
      93             :   {
      94          12 :     if (_mesh.nodeRef(in).processor_id() == _subproblem.processor_id())
      95           8 :       _connected_nodes.push_back(in);
      96             :   }
      97             : 
      98             :   // Fill in _primary_node_vector, which defines secondary nodes in the base class
      99          36 :   for (auto in : primary_nodelist)
     100          24 :     _primary_node_vector.push_back(in);
     101             : 
     102          12 :   const auto & node_to_elem_map = _mesh.nodeToElemMap();
     103          12 :   std::vector<std::vector<dof_id_type>> elems(_primary_node_vector.size());
     104             : 
     105             :   // Add elements connected to primary node to Ghosted Elements.
     106             : 
     107             :   // On a distributed mesh, these elements might have already been
     108             :   // remoted, in which case we need to gather them back first.
     109          12 :   if (!_mesh.getMesh().is_serial())
     110             :   {
     111             :     std::set<Elem *, CompareElemsByLevel> primary_elems_to_ghost;
     112             :     std::set<Node *> nodes_to_ghost;
     113             : 
     114           0 :     for (unsigned int i = 0; i < primary_nodelist.size(); ++i)
     115             :     {
     116             :       auto node_to_elem_pair = node_to_elem_map.find(_primary_node_vector[i]);
     117             : 
     118             :       bool found_elems = (node_to_elem_pair != node_to_elem_map.end());
     119             : 
     120             : #ifndef NDEBUG
     121             :       bool someone_found_elems = found_elems;
     122             :       _mesh.getMesh().comm().max(someone_found_elems);
     123             :       mooseAssert(someone_found_elems, "Missing entry in node to elem map");
     124             : #endif
     125             : 
     126           0 :       if (found_elems)
     127             :       {
     128           0 :         for (auto id : node_to_elem_pair->second)
     129             :         {
     130           0 :           Elem * elem = _mesh.queryElemPtr(id);
     131           0 :           if (elem)
     132             :           {
     133           0 :             primary_elems_to_ghost.insert(elem);
     134             : 
     135           0 :             const unsigned int n_nodes = elem->n_nodes();
     136           0 :             for (unsigned int n = 0; n != n_nodes; ++n)
     137           0 :               nodes_to_ghost.insert(elem->node_ptr(n));
     138             :           }
     139             :         }
     140             :       }
     141             :     }
     142             : 
     143             :     // Send nodes first since elements need them
     144           0 :     _mesh.getMesh().comm().allgather_packed_range(&_mesh.getMesh(),
     145             :                                                   nodes_to_ghost.begin(),
     146             :                                                   nodes_to_ghost.end(),
     147             :                                                   null_output_iterator<Node>());
     148             : 
     149           0 :     _mesh.getMesh().comm().allgather_packed_range(&_mesh.getMesh(),
     150             :                                                   primary_elems_to_ghost.begin(),
     151             :                                                   primary_elems_to_ghost.end(),
     152             :                                                   null_output_iterator<Elem>());
     153             : 
     154           0 :     _mesh.update(); // Rebuild node_to_elem_map
     155             : 
     156             :     // Find elems again now that we know they're there
     157           0 :     const auto & new_node_to_elem_map = _mesh.nodeToElemMap();
     158             :     auto node_to_elem_pair = new_node_to_elem_map.find(_primary_node_vector[0]);
     159             :     bool found_elems = (node_to_elem_pair != new_node_to_elem_map.end());
     160             : 
     161           0 :     if (!found_elems)
     162           0 :       mooseError("Colundn't find any elements connected to primary node.");
     163             : 
     164           0 :     for (unsigned int i = 0; i < _primary_node_vector.size(); ++i)
     165           0 :       elems[i] = node_to_elem_pair->second;
     166             :   }
     167             :   else // serial mesh
     168             :   {
     169          36 :     for (unsigned int i = 0; i < _primary_node_vector.size(); ++i)
     170             :     {
     171             :       auto node_to_elem_pair = node_to_elem_map.find(_primary_node_vector[i]);
     172             :       bool found_elems = (node_to_elem_pair != node_to_elem_map.end());
     173             : 
     174          24 :       if (!found_elems)
     175           0 :         mooseError("Couldn't find any elements connected to primary node");
     176             : 
     177          24 :       elems[i] = node_to_elem_pair->second;
     178             :     }
     179             :   }
     180             : 
     181          36 :   for (unsigned int i = 0; i < _primary_node_vector.size(); ++i)
     182             :   {
     183          24 :     if (elems[i].size() == 0)
     184           0 :       mooseError("Couldn't find any elements connected to primary node");
     185             : 
     186          48 :     for (unsigned int j = 0; j < elems[i].size(); ++j)
     187          24 :       _subproblem.addGhostedElem(elems[i][j]);
     188             :   }
     189             : 
     190             :   // Cache map between secondary node and primary node
     191             :   _connected_nodes.clear();
     192             :   _primary_conn.clear();
     193          24 :   for (unsigned int j = 0; j < secondary_nodelist.size(); ++j)
     194             :   {
     195          12 :     if (_mesh.nodeRef(secondary_nodelist[j]).processor_id() == _subproblem.processor_id())
     196             :     {
     197           8 :       Node & secondary_node = _mesh.nodeRef(secondary_nodelist[j]);
     198          16 :       for (unsigned int i = 0; i < _primary_node_vector.size(); ++i)
     199             :       {
     200          16 :         Node & primary_node = _mesh.nodeRef(_primary_node_vector[i]);
     201          16 :         Real d = (secondary_node - primary_node).norm();
     202          16 :         if (MooseUtils::absoluteFuzzyEqual(d, 0.0))
     203             :         {
     204           8 :           _primary_conn.push_back(i);
     205           8 :           _connected_nodes.push_back(secondary_nodelist[j]);
     206             :           break;
     207             :         }
     208             :       }
     209             :     }
     210             :   }
     211             : 
     212          12 :   _console << "total secondary nodes, primary nodes: " << _primary_conn.size() << ", "
     213          12 :            << _primary_node_vector.size() << '\n';
     214          24 : }
     215             : 
     216             : void
     217         622 : NodalFrictionalConstraint::computeResidual(NumericVector<Number> &
     218             :                                            /*residual*/)
     219             : {
     220         622 :   const auto & primarydof = _var.dofIndices();
     221             :   const auto & secondarydof = _var.dofIndicesNeighbor();
     222         622 :   std::vector<Number> re(primarydof.size());
     223         622 :   std::vector<Number> neighbor_re(secondarydof.size());
     224             : 
     225         622 :   for (_i = 0; _i < secondarydof.size(); ++_i)
     226             :   {
     227         622 :     _j = _primary_conn[_i];
     228         622 :     re[_j] += computeQpResidual(Moose::Primary);
     229         622 :     neighbor_re[_i] += computeQpResidual(Moose::Secondary);
     230         622 :     break;
     231             :   }
     232         622 :   addResiduals(_assembly, re, primarydof, _var.scalingFactor());
     233         622 :   addResiduals(_assembly, neighbor_re, secondarydof, _var.scalingFactor());
     234         622 : }
     235             : 
     236             : Real
     237        1244 : NodalFrictionalConstraint::computeQpResidual(Moose::ConstraintType type)
     238             : {
     239             :   // check whether the tangential spring is already in the yielded state
     240        1244 :   Real old_force = (_u_secondary_old[_i] - _u_primary_old[_j]) * _tangential_penalty;
     241        1244 :   if (MooseUtils::absoluteFuzzyGreaterThan(std::abs(old_force),
     242        1244 :                                            _friction_coefficient * _normal_force))
     243         476 :     old_force = _friction_coefficient * _normal_force * old_force / std::abs(old_force);
     244             : 
     245             :   Real current_force =
     246        1244 :       ((_u_secondary[_i] - _u_secondary_old[_i]) - (_u_primary[_j] - _u_primary_old[_j])) *
     247             :           _tangential_penalty +
     248        1244 :       old_force;
     249        1244 :   if (MooseUtils::absoluteFuzzyGreaterThan(std::abs(current_force),
     250             :                                            _friction_coefficient * _normal_force))
     251         908 :     current_force = _friction_coefficient * _normal_force * current_force / std::abs(current_force);
     252             : 
     253        1244 :   switch (type)
     254             :   {
     255             :     case Moose::Secondary:
     256             :       return current_force;
     257         622 :     case Moose::Primary:
     258         622 :       return -current_force;
     259             :   }
     260           0 :   return 0;
     261             : }
     262             : 
     263             : void
     264         122 : NodalFrictionalConstraint::computeJacobian(SparseMatrix<Number> & /*jacobian*/)
     265             : {
     266             :   // Calculate Jacobian entries and cache those entries along with the row and column indices
     267         122 :   std::vector<dof_id_type> secondarydof = _var.dofIndicesNeighbor();
     268         122 :   std::vector<dof_id_type> primarydof = _var.dofIndices();
     269             : 
     270         122 :   DenseMatrix<Number> Kee(primarydof.size(), primarydof.size());
     271         122 :   DenseMatrix<Number> Ken(primarydof.size(), secondarydof.size());
     272         122 :   DenseMatrix<Number> Kne(secondarydof.size(), primarydof.size());
     273         122 :   DenseMatrix<Number> Knn(secondarydof.size(), secondarydof.size());
     274             : 
     275             :   Kee.zero();
     276             :   Ken.zero();
     277             :   Kne.zero();
     278             :   Knn.zero();
     279             : 
     280         244 :   for (_i = 0; _i < secondarydof.size(); ++_i)
     281             :   {
     282         122 :     _j = _primary_conn[_i];
     283         122 :     Kee(_j, _j) += computeQpJacobian(Moose::PrimaryPrimary);
     284         122 :     Ken(_j, _i) += computeQpJacobian(Moose::PrimarySecondary);
     285         122 :     Kne(_i, _j) += computeQpJacobian(Moose::SecondaryPrimary);
     286         122 :     Knn(_i, _i) += computeQpJacobian(Moose::SecondarySecondary);
     287             :   }
     288         122 :   addJacobian(_assembly, Kee, primarydof, primarydof, _var.scalingFactor());
     289         122 :   addJacobian(_assembly, Ken, primarydof, secondarydof, _var.scalingFactor());
     290         122 :   addJacobian(_assembly, Kne, secondarydof, primarydof, _var.scalingFactor());
     291         122 :   addJacobian(_assembly, Knn, secondarydof, secondarydof, _var.scalingFactor());
     292         244 : }
     293             : 
     294             : Real
     295         488 : NodalFrictionalConstraint::computeQpJacobian(Moose::ConstraintJacobianType type)
     296             : {
     297         488 :   Real jac = _tangential_penalty;
     298             : 
     299             :   // set jacobian to zero if spring has yielded
     300         488 :   Real old_force = (_u_secondary_old[_i] - _u_primary_old[_j]) * _tangential_penalty;
     301         488 :   if (MooseUtils::absoluteFuzzyGreaterThan(std::abs(old_force),
     302         488 :                                            _friction_coefficient * _normal_force))
     303         296 :     old_force = _friction_coefficient * _normal_force * old_force / std::abs(old_force);
     304             : 
     305             :   Real current_force =
     306         488 :       ((_u_secondary[_i] - _u_secondary_old[_i]) - (_u_primary[_j] - _u_primary_old[_j])) *
     307             :           _tangential_penalty +
     308         488 :       old_force;
     309         488 :   if (MooseUtils::absoluteFuzzyGreaterThan(std::abs(current_force),
     310             :                                            _friction_coefficient * _normal_force))
     311             :     jac = 0.0;
     312             : 
     313         488 :   switch (type)
     314             :   {
     315             :     case Moose::SecondarySecondary:
     316             :       return jac;
     317         122 :     case Moose::SecondaryPrimary:
     318         122 :       return -jac;
     319             :     case Moose::PrimaryPrimary:
     320             :       return jac;
     321         122 :     case Moose::PrimarySecondary:
     322         122 :       return -jac;
     323           0 :     default:
     324           0 :       mooseError("Invalid type");
     325             :   }
     326             :   return 0.;
     327             : }

Generated by: LCOV version 1.14