LCOV - code coverage report
Current view: top level - src/constraints - EqualValueBoundaryConstraint.C (source / functions) Hit Total Coverage
Test: idaholab/moose framework: #32971 (54bef8) with base c6cf66 Lines: 120 134 89.6 %
Date: 2026-05-29 20:35:17 Functions: 11 11 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             : // MOOSE includes
      11             : #include "EqualValueBoundaryConstraint.h"
      12             : #include "MooseMesh.h"
      13             : 
      14             : #include "libmesh/null_output_iterator.h"
      15             : #include "libmesh/parallel.h"
      16             : #include "libmesh/parallel_elem.h"
      17             : #include "libmesh/parallel_node.h"
      18             : 
      19             : namespace // Anonymous namespace for helpers
      20             : {
      21             : /**
      22             :  * Specific weak ordering for Elem *'s to be used in a set.
      23             :  * We use the id, but first sort by level.  This guarantees
      24             :  * when traversing the set from beginning to end the lower
      25             :  * level (parent) elements are encountered first.
      26             :  *
      27             :  * This was swiped from libMesh mesh_communication.C, and ought to be
      28             :  * replaced with libMesh::CompareElemIdsByLevel just as soon as I refactor to
      29             :  * create that - @roystgnr
      30             :  */
      31             : struct CompareElemsByLevel
      32             : {
      33           9 :   bool operator()(const Elem * a, const Elem * b) const
      34             :   {
      35             :     libmesh_assert(a);
      36             :     libmesh_assert(b);
      37           9 :     const unsigned int al = a->level(), bl = b->level();
      38           9 :     const dof_id_type aid = a->id(), bid = b->id();
      39             : 
      40           9 :     return (al == bl) ? aid < bid : al < bl;
      41             :   }
      42             : };
      43             : 
      44             : } // anonymous namespace
      45             : 
      46             : registerMooseObject("MooseApp", EqualValueBoundaryConstraint);
      47             : 
      48             : InputParameters
      49        3138 : EqualValueBoundaryConstraint::validParams()
      50             : {
      51        3138 :   InputParameters params = NodalConstraint::validParams();
      52        6276 :   params.addClassDescription(
      53             :       "Constraint for enforcing that variables on each side of a boundary are equivalent.");
      54       12552 :   params.addParam<unsigned int>(
      55             :       "primary",
      56             :       "The ID of the primary node. If no ID is provided, first node of secondary set is chosen.");
      57       12552 :   params.addParam<Point>("primary_node_coord", "Coordinates of the primary node to locate.");
      58       12552 :   params.addParam<std::vector<unsigned int>>("secondary_node_ids", "The IDs of the secondary node");
      59       12552 :   params.addParam<BoundaryName>("secondary", "The boundary ID associated with the secondary side");
      60        9414 :   params.addRequiredParam<Real>("penalty", "The penalty used for the boundary term");
      61        3138 :   return params;
      62           0 : }
      63             : 
      64          43 : EqualValueBoundaryConstraint::EqualValueBoundaryConstraint(const InputParameters & parameters)
      65          86 :   : NodalConstraint(parameters), _penalty(getParam<Real>("penalty"))
      66             : {
      67          43 :   updateConstrainedNodes();
      68          34 : }
      69             : 
      70             : void
      71          18 : EqualValueBoundaryConstraint::meshChanged()
      72             : {
      73          18 :   updateConstrainedNodes();
      74          18 : }
      75             : 
      76             : void
      77          61 : EqualValueBoundaryConstraint::updateConstrainedNodes()
      78             : {
      79          61 :   populateSecondaryNodes();
      80          61 :   pickPrimaryNode();
      81          52 :   ghostPrimary();
      82          52 : }
      83             : 
      84             : void
      85          61 : EqualValueBoundaryConstraint::populateSecondaryNodes()
      86             : {
      87             :   // user provided nothing
      88         360 :   if (!isParamValid("secondary_node_ids") && !isParamValid("secondary"))
      89           0 :     paramError("secondary", "Either secondary or secondary_node_ids must be provided.");
      90             : 
      91             :   // user provided conflicting params
      92         189 :   if (isParamValid("secondary_node_ids") && isParamValid("secondary"))
      93           0 :     paramError("secondary",
      94             :                "Both 'secondary' and 'secondary_node_ids' parameters are set. They are mutually "
      95             :                "exclusive.");
      96             : 
      97             :   // Fill in _connected_nodes, which defines local secondary nodes in the base class
      98             :   //
      99             :   // Note that later on when we pick the primary node from the secondary node set, we
     100             :   // will make sure to remove it from _connected_nodes
     101          61 :   _connected_nodes.clear();
     102             : 
     103             :   // user provided boundary name
     104         183 :   if (isParamValid("secondary"))
     105             :   {
     106         116 :     const auto & secondary_bnd = getParam<BoundaryName>("secondary");
     107          58 :     const auto & secondary_nodes = _mesh.getNodeList(_mesh.getBoundaryID(secondary_bnd));
     108             : 
     109         672 :     for (const auto & nid : secondary_nodes)
     110         614 :       if (_mesh.nodeRef(nid).processor_id() == _subproblem.processor_id())
     111         490 :         _connected_nodes.push_back(nid);
     112             :   }
     113             :   // user provided node ids
     114           9 :   else if (isParamValid("secondary_node_ids"))
     115             :   {
     116           6 :     const auto & secondary_node_ids = getParam<std::vector<unsigned int>>("secondary_node_ids");
     117          21 :     for (const auto & nid : secondary_node_ids)
     118          33 :       if (_mesh.queryNodePtr(nid) &&
     119          15 :           _mesh.nodeRef(nid).processor_id() == _subproblem.processor_id())
     120          15 :         _connected_nodes.push_back(nid);
     121             :   }
     122          61 : }
     123             : 
     124             : void
     125          61 : EqualValueBoundaryConstraint::pickPrimaryNode()
     126             : {
     127             :   // user provided nothing
     128         269 :   if (isParamValid("primary") && isParamValid("primary_node_coord"))
     129           3 :     mooseError(
     130             :         "Both 'primary' and 'primary_node_coord' parameters are set. They are mutually exclusive.");
     131             : 
     132          58 :   dof_id_type primary_node_id = Node::invalid_id;
     133             : 
     134             :   // user provided primary node coordinates
     135         174 :   if (isParamValid("primary_node_coord"))
     136          18 :     primary_node_id = getPrimaryNodeIDByCoord();
     137             :   // user provided primary node id
     138         120 :   else if (isParamValid("primary"))
     139         120 :     primary_node_id = getParam<unsigned int>("primary");
     140             :   // no primary node provided, so we pick one from secondary nodes
     141             :   else
     142             :   {
     143             :     // If the user provided secondary node ids, set primary node to first one
     144           0 :     if (isParamValid("secondary_node_ids"))
     145             :     {
     146           0 :       if (_connected_nodes.size())
     147           0 :         primary_node_id = _connected_nodes[0];
     148             :     }
     149             :     // otherwise, we pick the minimum node id from the secondary node set
     150             :     else
     151             :     {
     152           0 :       if (_connected_nodes.size())
     153           0 :         primary_node_id = (*std::min_element(_connected_nodes.begin(), _connected_nodes.end()));
     154           0 :       _mesh.comm().min(primary_node_id);
     155             :     }
     156             :   }
     157             : 
     158             :   mooseAssert(primary_node_id != Node::invalid_id, "We should have found a primary node");
     159             : 
     160             :   // Remove primary node from secondary nodes
     161          52 :   auto it = std::find(_connected_nodes.begin(), _connected_nodes.end(), primary_node_id);
     162          52 :   if (it != _connected_nodes.end())
     163          40 :     _connected_nodes.erase(it);
     164             : 
     165             :   // Store primary node id
     166          52 :   _primary_node_vector.resize(1);
     167          52 :   _primary_node_vector[0] = primary_node_id;
     168          52 : }
     169             : 
     170             : dof_id_type
     171          18 : EqualValueBoundaryConstraint::getPrimaryNodeIDByCoord() const
     172             : {
     173          18 :   const Real eps = libMesh::TOLERANCE;
     174          18 :   std::unordered_set<dof_id_type> local_primary_node_ids;
     175          36 :   const auto & primary_node_coord = getParam<Point>("primary_node_coord");
     176             : 
     177             :   // Gather local candidates
     178         476 :   for (const auto & bnd_node : *_mesh.getBoundaryNodeRange())
     179             :   {
     180         458 :     if ((*(bnd_node->_node) - primary_node_coord).norm() < eps)
     181             :     {
     182             :       // The primary node we found should belong to the secondary node set
     183             :       //
     184             :       // Note that we do not immediately break once a match is found because in theory, although it
     185             :       // is extremely unlikely, there could be multiple coinciding nodes on the secondary boundary
     186             :       // that match the provided coordinates. In that case, we would like to emit a meaning error
     187             :       // message.
     188          30 :       if (std::find(_connected_nodes.begin(), _connected_nodes.end(), bnd_node->_node->id()) !=
     189          60 :           _connected_nodes.end())
     190          27 :         local_primary_node_ids.insert(bnd_node->_node->id());
     191             :     }
     192             :   }
     193             : 
     194             :   // Gather all candidates from all ranks
     195             :   const std::vector<dof_id_type> local_node_vec(local_primary_node_ids.begin(),
     196          18 :                                                 local_primary_node_ids.end());
     197          18 :   std::vector<std::vector<dof_id_type>> gathered_node_vecs;
     198          18 :   _mesh.comm().allgather(local_node_vec, gathered_node_vecs);
     199             : 
     200             :   // Deduplicate
     201          18 :   std::unordered_set<dof_id_type> global_primary_node_ids;
     202          42 :   for (const auto & vec : gathered_node_vecs)
     203          24 :     global_primary_node_ids.insert(vec.begin(), vec.end());
     204             : 
     205             :   // Make sure we found one and exactly one
     206          18 :   if (global_primary_node_ids.size() == 0)
     207           3 :     mooseError("Couldn't find a node ID for the specified primary_node_coord.");
     208          15 :   else if (global_primary_node_ids.size() > 1)
     209           3 :     mooseError("Multiple nodes found for the specified primary_node_coord.");
     210             : 
     211          24 :   return *global_primary_node_ids.begin();
     212          12 : }
     213             : 
     214             : void
     215          52 : EqualValueBoundaryConstraint::ghostPrimary()
     216             : {
     217          52 :   const auto & node_to_elem_map = _mesh.nodeToElemMap();
     218          52 :   auto node_to_elem_pair = node_to_elem_map.find(_primary_node_vector[0]);
     219          52 :   bool found_elems = (node_to_elem_pair != node_to_elem_map.end());
     220             : 
     221             :   // Add elements connected to primary node to Ghosted Elements.
     222             : 
     223             :   // On a distributed mesh, these elements might have already been
     224             :   // remoted, in which case we need to gather them back first.
     225          52 :   if (!_mesh.getMesh().is_serial())
     226             :   {
     227             : #ifndef NDEBUG
     228             :     bool someone_found_elems = found_elems;
     229             :     _mesh.getMesh().comm().max(someone_found_elems);
     230             :     mooseAssert(someone_found_elems, "Missing entry in node to elem map");
     231             : #endif
     232             : 
     233           4 :     std::set<Elem *, CompareElemsByLevel> primary_elems_to_ghost;
     234           4 :     std::set<Node *> nodes_to_ghost;
     235           4 :     if (found_elems)
     236             :     {
     237          11 :       for (dof_id_type id : node_to_elem_pair->second)
     238             :       {
     239           7 :         Elem * elem = _mesh.queryElemPtr(id);
     240           7 :         if (elem)
     241             :         {
     242           7 :           primary_elems_to_ghost.insert(elem);
     243             : 
     244           7 :           const unsigned int n_nodes = elem->n_nodes();
     245          35 :           for (unsigned int n = 0; n != n_nodes; ++n)
     246          28 :             nodes_to_ghost.insert(elem->node_ptr(n));
     247             :         }
     248             :       }
     249             :     }
     250             : 
     251             :     // Send nodes first since elements need them
     252           4 :     _mesh.getMesh().comm().allgather_packed_range(&_mesh.getMesh(),
     253             :                                                   nodes_to_ghost.begin(),
     254             :                                                   nodes_to_ghost.end(),
     255             :                                                   libMesh::null_output_iterator<Node>());
     256             : 
     257           4 :     _mesh.getMesh().comm().allgather_packed_range(&_mesh.getMesh(),
     258             :                                                   primary_elems_to_ghost.begin(),
     259             :                                                   primary_elems_to_ghost.end(),
     260             :                                                   libMesh::null_output_iterator<Elem>());
     261             : 
     262             :     // After allgather_packed_range(), rebuild internal connectivity.
     263             :     // This updates ghost nodes/elements across processors and reconstructs node_to_elem_map.
     264           4 :     _mesh.update();
     265             : 
     266             :     // Find elems again now that we know they're there
     267           4 :     const auto & new_node_to_elem_map = _mesh.nodeToElemMap();
     268           4 :     node_to_elem_pair = new_node_to_elem_map.find(_primary_node_vector[0]);
     269           4 :     found_elems = (node_to_elem_pair != new_node_to_elem_map.end());
     270           4 :   }
     271             : 
     272          52 :   if (!found_elems)
     273           0 :     mooseError("Couldn't find any elements connected to primary node");
     274             : 
     275          52 :   const std::vector<dof_id_type> & elems = node_to_elem_pair->second;
     276             : 
     277          52 :   if (elems.size() == 0)
     278           0 :     mooseError("Couldn't find any elements connected to primary node");
     279          52 :   _subproblem.addGhostedElem(elems[0]);
     280          52 : }
     281             : 
     282             : Real
     283        5532 : EqualValueBoundaryConstraint::computeQpResidual(Moose::ConstraintType type)
     284             : {
     285        5532 :   switch (type)
     286             :   {
     287        2766 :     case Moose::Secondary:
     288        2766 :       return (_u_secondary[_i] - _u_primary[_j]) * _penalty;
     289        2766 :     case Moose::Primary:
     290        2766 :       return (_u_primary[_j] - _u_secondary[_i]) * _penalty;
     291             :   }
     292           0 :   return 0.;
     293             : }
     294             : 
     295             : Real
     296        2376 : EqualValueBoundaryConstraint::computeQpJacobian(Moose::ConstraintJacobianType type)
     297             : {
     298        2376 :   switch (type)
     299             :   {
     300         594 :     case Moose::SecondarySecondary:
     301         594 :       return _penalty;
     302         594 :     case Moose::SecondaryPrimary:
     303         594 :       return -_penalty;
     304         594 :     case Moose::PrimaryPrimary:
     305         594 :       return _penalty;
     306         594 :     case Moose::PrimarySecondary:
     307         594 :       return -_penalty;
     308           0 :     default:
     309           0 :       mooseError("Unsupported type");
     310             :       break;
     311             :   }
     312             :   return 0.;
     313             : }

Generated by: LCOV version 1.14