LCOV - code coverage report
Current view: top level - src/constraints - NodalStickConstraint.C (source / functions) Hit Total Coverage
Test: idaholab/moose solid_mechanics: #32971 (54bef8) with base c6cf66 Lines: 99 138 71.7 %
Date: 2026-05-29 20:40:07 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 "NodalStickConstraint.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", NodalStickConstraint);
      28             : 
      29             : InputParameters
      30         128 : NodalStickConstraint::validParams()
      31             : {
      32         128 :   InputParameters params = NodalConstraint::validParams();
      33         128 :   params.addClassDescription("Sticky nodal constraint for contact");
      34         256 :   params.addRequiredParam<BoundaryName>("boundary", "The primary boundary");
      35         256 :   params.addRequiredParam<BoundaryName>("secondary", "The secondary boundary");
      36         256 :   params.addRequiredParam<Real>("penalty", "Stiffness of the spring.");
      37         128 :   return params;
      38           0 : }
      39             : 
      40          64 : NodalStickConstraint::NodalStickConstraint(const InputParameters & parameters)
      41             :   : NodalConstraint(parameters),
      42         128 :     _primary_boundary_id(getParam<BoundaryName>("boundary")),
      43          64 :     _secondary_boundary_id(getParam<BoundaryName>("secondary")),
      44         192 :     _penalty(getParam<Real>("penalty"))
      45             : {
      46          64 :   if (_var.number() != _var_secondary.number())
      47           0 :     paramError("variable_secondary",
      48             :                "Primary variable must be identical to secondary variable. "
      49             :                "Different variables are currently not supported.");
      50             : 
      51          64 :   updateConstrainedNodes();
      52          64 : }
      53             : 
      54             : void
      55           0 : NodalStickConstraint::meshChanged()
      56             : {
      57           0 :   updateConstrainedNodes();
      58           0 : }
      59             : 
      60             : void
      61          64 : NodalStickConstraint::updateConstrainedNodes()
      62             : {
      63          64 :   _primary_node_vector.clear();
      64          64 :   _connected_nodes.clear();
      65          64 :   _primary_conn.clear();
      66             : 
      67             :   std::vector<dof_id_type> secondary_nodelist =
      68          64 :       _mesh.getNodeList(_mesh.getBoundaryID(_secondary_boundary_id));
      69             :   std::vector<dof_id_type> primary_nodelist =
      70          64 :       _mesh.getNodeList(_mesh.getBoundaryID(_primary_boundary_id));
      71             : 
      72             :   // Fill in _connected_nodes, which defines secondary nodes in the base class
      73         128 :   for (auto in : secondary_nodelist)
      74             :   {
      75          64 :     if (_mesh.nodeRef(in).processor_id() == _subproblem.processor_id())
      76          48 :       _connected_nodes.push_back(in);
      77             :   }
      78             : 
      79             :   // Fill in _primary_node_vector, which defines secondary nodes in the base class
      80         192 :   for (auto in : primary_nodelist)
      81         128 :     _primary_node_vector.push_back(in);
      82             : 
      83          64 :   const auto & node_to_elem_map = _mesh.nodeToElemMap();
      84          64 :   std::vector<std::vector<dof_id_type>> elems(_primary_node_vector.size());
      85             : 
      86             :   // Add elements connected to primary node to Ghosted Elements.
      87             : 
      88             :   // On a distributed mesh, these elements might have already been
      89             :   // remoted, in which case we need to gather them back first.
      90          64 :   if (!_mesh.getMesh().is_serial())
      91             :   {
      92             :     std::set<Elem *, CompareElemsByLevel> primary_elems_to_ghost;
      93             :     std::set<Node *> nodes_to_ghost;
      94             : 
      95           0 :     for (unsigned int i = 0; i < primary_nodelist.size(); ++i)
      96             :     {
      97             :       auto node_to_elem_pair = node_to_elem_map.find(_primary_node_vector[i]);
      98             : 
      99             :       bool found_elems = (node_to_elem_pair != node_to_elem_map.end());
     100             : 
     101             : #ifndef NDEBUG
     102             :       bool someone_found_elems = found_elems;
     103             :       _mesh.getMesh().comm().max(someone_found_elems);
     104             :       mooseAssert(someone_found_elems, "Missing entry in node to elem map");
     105             : #endif
     106             : 
     107           0 :       if (found_elems)
     108             :       {
     109           0 :         for (auto id : node_to_elem_pair->second)
     110             :         {
     111           0 :           Elem * elem = _mesh.queryElemPtr(id);
     112           0 :           if (elem)
     113             :           {
     114           0 :             primary_elems_to_ghost.insert(elem);
     115             : 
     116           0 :             const unsigned int n_nodes = elem->n_nodes();
     117           0 :             for (unsigned int n = 0; n != n_nodes; ++n)
     118           0 :               nodes_to_ghost.insert(elem->node_ptr(n));
     119             :           }
     120             :         }
     121             :       }
     122             :     }
     123             : 
     124             :     // Send nodes first since elements need them
     125           0 :     _mesh.getMesh().comm().allgather_packed_range(&_mesh.getMesh(),
     126             :                                                   nodes_to_ghost.begin(),
     127             :                                                   nodes_to_ghost.end(),
     128             :                                                   null_output_iterator<Node>());
     129             : 
     130           0 :     _mesh.getMesh().comm().allgather_packed_range(&_mesh.getMesh(),
     131             :                                                   primary_elems_to_ghost.begin(),
     132             :                                                   primary_elems_to_ghost.end(),
     133             :                                                   null_output_iterator<Elem>());
     134             : 
     135           0 :     _mesh.update(); // Rebuild node_to_elem_map
     136             : 
     137             :     // Find elems again now that we know they're there
     138           0 :     const auto & new_node_to_elem_map = _mesh.nodeToElemMap();
     139             :     auto node_to_elem_pair = new_node_to_elem_map.find(_primary_node_vector[0]);
     140             :     bool found_elems = (node_to_elem_pair != new_node_to_elem_map.end());
     141             : 
     142           0 :     if (!found_elems)
     143           0 :       mooseError("Colundn't find any elements connected to primary node.");
     144             : 
     145           0 :     for (unsigned int i = 0; i < _primary_node_vector.size(); ++i)
     146           0 :       elems[i] = node_to_elem_pair->second;
     147             :   }
     148             :   else // serial mesh
     149             :   {
     150         192 :     for (unsigned int i = 0; i < _primary_node_vector.size(); ++i)
     151             :     {
     152             :       auto node_to_elem_pair = node_to_elem_map.find(_primary_node_vector[i]);
     153             :       bool found_elems = (node_to_elem_pair != node_to_elem_map.end());
     154             : 
     155         128 :       if (!found_elems)
     156           0 :         mooseError("Couldn't find any elements connected to primary node");
     157             : 
     158         128 :       elems[i] = node_to_elem_pair->second;
     159             :     }
     160             :   }
     161             : 
     162         192 :   for (unsigned int i = 0; i < _primary_node_vector.size(); ++i)
     163             :   {
     164         128 :     if (elems[i].size() == 0)
     165           0 :       mooseError("Couldn't find any elements connected to primary node");
     166             : 
     167         256 :     for (unsigned int j = 0; j < elems[i].size(); ++j)
     168         128 :       _subproblem.addGhostedElem(elems[i][j]);
     169             :   }
     170             : 
     171             :   // Cache map between secondary node and primary node
     172          64 :   _connected_nodes.clear();
     173          64 :   _primary_conn.clear();
     174         128 :   for (unsigned int j = 0; j < secondary_nodelist.size(); ++j)
     175             :   {
     176          64 :     if (_mesh.nodeRef(secondary_nodelist[j]).processor_id() == _subproblem.processor_id())
     177             :     {
     178          48 :       Node & secondary_node = _mesh.nodeRef(secondary_nodelist[j]);
     179          96 :       for (unsigned int i = 0; i < _primary_node_vector.size(); ++i)
     180             :       {
     181          96 :         Node & primary_node = _mesh.nodeRef(_primary_node_vector[i]);
     182          96 :         Real d = (secondary_node - primary_node).norm();
     183          96 :         if (MooseUtils::absoluteFuzzyEqual(d, 0.0))
     184             :         {
     185          48 :           _primary_conn.push_back(i);
     186          48 :           _connected_nodes.push_back(secondary_nodelist[j]);
     187             :           break;
     188             :         }
     189             :       }
     190             :     }
     191             :   }
     192          64 : }
     193             : 
     194             : void
     195         658 : NodalStickConstraint::computeJacobian(const SparseMatrix<Number> & jacobian)
     196             : {
     197             :   // Calculate Jacobian enteries and cache those entries along with the row and column indices
     198         658 :   std::vector<dof_id_type> secondarydof = _var.dofIndicesNeighbor();
     199        1316 :   std::vector<dof_id_type> primarydof = _var.dofIndices();
     200             : 
     201         658 :   DenseMatrix<Number> Kee(primarydof.size(), primarydof.size());
     202         658 :   DenseMatrix<Number> Ken(primarydof.size(), secondarydof.size());
     203         658 :   DenseMatrix<Number> Kne(secondarydof.size(), primarydof.size());
     204         658 :   DenseMatrix<Number> Knn(secondarydof.size(), secondarydof.size());
     205             : 
     206             :   Kee.zero();
     207             :   Ken.zero();
     208             :   Kne.zero();
     209             :   Knn.zero();
     210             : 
     211        1316 :   for (_i = 0; _i < secondarydof.size(); ++_i)
     212             :   {
     213         658 :     _j = _primary_conn[_i];
     214         658 :     switch (_formulation)
     215             :     {
     216         658 :       case Moose::Penalty:
     217         658 :         Kee(_j, _j) += computeQpJacobian(Moose::PrimaryPrimary);
     218         658 :         Ken(_j, _i) += computeQpJacobian(Moose::PrimarySecondary);
     219         658 :         Kne(_i, _j) += computeQpJacobian(Moose::SecondaryPrimary);
     220         658 :         Knn(_i, _i) += computeQpJacobian(Moose::SecondarySecondary);
     221         658 :         break;
     222           0 :       case Moose::Kinematic:
     223           0 :         Kee(_j, _j) = 0.;
     224           0 :         Ken(_j, _i) += jacobian(secondarydof[_i], primarydof[_j]);
     225           0 :         Kne(_i, _j) += -jacobian(secondarydof[_i], primarydof[_j]) +
     226           0 :                        computeQpJacobian(Moose::SecondaryPrimary);
     227           0 :         Knn(_i, _i) += -jacobian(secondarydof[_i], secondarydof[_i]) +
     228           0 :                        computeQpJacobian(Moose::SecondarySecondary);
     229           0 :         break;
     230             :     }
     231             :   }
     232         658 :   addJacobian(_assembly, Kee, primarydof, primarydof, _var.scalingFactor());
     233         658 :   addJacobian(_assembly, Ken, primarydof, secondarydof, _var.scalingFactor());
     234         658 :   addJacobian(_assembly, Kne, secondarydof, primarydof, _var.scalingFactor());
     235         658 :   addJacobian(_assembly, Knn, secondarydof, secondarydof, _var.scalingFactor());
     236         658 : }
     237             : 
     238             : void
     239        3108 : NodalStickConstraint::computeResidual(const NumericVector<Number> & residual)
     240             : {
     241        3108 :   std::vector<dof_id_type> primarydof = _var.dofIndices();
     242        3108 :   std::vector<dof_id_type> secondarydof = _var.dofIndicesNeighbor();
     243        3108 :   DenseVector<Number> re(primarydof.size());
     244        3108 :   DenseVector<Number> neighbor_re(secondarydof.size());
     245             : 
     246             :   re.zero();
     247             :   neighbor_re.zero();
     248        6216 :   for (_i = 0; _i < secondarydof.size(); ++_i)
     249             :   {
     250        3108 :     _j = _primary_conn[_i];
     251        3108 :     switch (_formulation)
     252             :     {
     253        3108 :       case Moose::Penalty:
     254        3108 :         re(_j) += computeQpResidual(Moose::Primary) * _var.scalingFactor();
     255        3108 :         neighbor_re(_i) += computeQpResidual(Moose::Secondary) * _var.scalingFactor();
     256        3108 :         break;
     257           0 :       case Moose::Kinematic:
     258             :         // Transfer the current residual of the secondary node to the primary nodes
     259           0 :         Real res = residual(secondarydof[_i]);
     260           0 :         re(_j) += res;
     261           0 :         neighbor_re(_i) += -res + computeQpResidual(Moose::Secondary);
     262           0 :         break;
     263             :     }
     264             :   }
     265             :   // We've already applied scaling
     266        3108 :   addResiduals(_assembly, re, primarydof, /*scaling_factor=*/1);
     267        3108 :   addResiduals(_assembly, neighbor_re, secondarydof, /*scaling_fator=*/1);
     268        3108 : }
     269             : 
     270             : Real
     271        6216 : NodalStickConstraint::computeQpResidual(Moose::ConstraintType type)
     272             : {
     273        6216 :   switch (type)
     274             :   {
     275        3108 :     case Moose::Secondary:
     276        3108 :       return (_u_secondary[_i] - _u_primary[_j]) * _penalty;
     277        3108 :     case Moose::Primary:
     278        3108 :       return (_u_primary[_j] - _u_secondary[_i]) * _penalty;
     279             :   }
     280             :   return 0.;
     281             : }
     282             : 
     283             : Real
     284        2632 : NodalStickConstraint::computeQpJacobian(Moose::ConstraintJacobianType type)
     285             : {
     286        2632 :   switch (type)
     287             :   {
     288         658 :     case Moose::SecondarySecondary:
     289         658 :       return _penalty;
     290         658 :     case Moose::SecondaryPrimary:
     291         658 :       return -_penalty;
     292         658 :     case Moose::PrimaryPrimary:
     293         658 :       return _penalty;
     294         658 :     case Moose::PrimarySecondary:
     295         658 :       return -_penalty;
     296           0 :     default:
     297           0 :       mooseError("Invalid type");
     298             :   }
     299             :   return 0.;
     300             : }

Generated by: LCOV version 1.14