LCOV - code coverage report
Current view: top level - src/relationshipmanagers - AugmentSparsityOnInterface.C (source / functions) Hit Total Coverage
Test: idaholab/moose framework: #33390 (250e9c) with base 846a5c Lines: 152 168 90.5 %
Date: 2026-07-31 18:15:22 Functions: 10 11 90.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             : // App includes
      11             : #include "AugmentSparsityOnInterface.h"
      12             : #include "Executioner.h"
      13             : #include "FEProblemBase.h"
      14             : #include "MooseApp.h"
      15             : 
      16             : // libMesh includes
      17             : #include "libmesh/elem.h"
      18             : #include "libmesh/mesh_base.h"
      19             : #include "libmesh/boundary_info.h"
      20             : 
      21             : registerMooseObject("MooseApp", AugmentSparsityOnInterface);
      22             : 
      23             : using namespace libMesh;
      24             : 
      25             : InputParameters
      26       23537 : AugmentSparsityOnInterface::validParams()
      27             : {
      28       23537 :   InputParameters params = RelationshipManager::validParams();
      29       94148 :   params.addRequiredParam<BoundaryName>("primary_boundary",
      30             :                                         "The name of the primary boundary sideset.");
      31       94148 :   params.addRequiredParam<BoundaryName>("secondary_boundary",
      32             :                                         "The name of the secondary boundary sideset.");
      33       94148 :   params.addRequiredParam<SubdomainName>("primary_subdomain",
      34             :                                          "The name of the primary lower dimensional subdomain.");
      35       94148 :   params.addRequiredParam<SubdomainName>("secondary_subdomain",
      36             :                                          "The name of the secondary lower dimensional subdomain.");
      37       70611 :   params.addParam<bool>(
      38             :       "ghost_point_neighbors",
      39       47074 :       false,
      40             :       "Whether we should ghost point neighbors of secondary lower-dimensional elements and "
      41             :       "also their interior parents and mortar interface couples for applications such as mortar "
      42             :       "nodal auxiliary kernels and nodal-normal contact Jacobians.");
      43       70611 :   params.addParam<bool>(
      44             :       "ghost_higher_d_neighbors",
      45       47074 :       false,
      46             :       "Whether we should ghost higher-dimensional neighbors. This is necessary when we are doing "
      47             :       "second order mortar with finite volume primal variables, because in order for the method to "
      48             :       "be second order we must use cell gradients, which couples in the neighbor cells.");
      49             : 
      50             :   // We want to wait until our mortar mesh has been built before trying to delete remote elements.
      51             :   // And our mortar mesh cannot be built until the entire mesh has been generated. By setting this
      52             :   // parameter to false we will make sure that any prepare_for_use calls during the mesh generation
      53             :   // phase will not delete remote elements *and* we will set a flag on the moose mesh saying that we
      54             :   // need to delete remote elements after the addition of late geometric ghosting functors
      55             :   // (including this ghosting functor)
      56       23537 :   params.set<bool>("attach_geometric_early") = false;
      57       23537 :   return params;
      58           0 : }
      59             : 
      60        8580 : AugmentSparsityOnInterface::AugmentSparsityOnInterface(const InputParameters & params)
      61             :   : RelationshipManager(params),
      62        8580 :     _primary_boundary_name(getParam<BoundaryName>("primary_boundary")),
      63       17160 :     _secondary_boundary_name(getParam<BoundaryName>("secondary_boundary")),
      64       17160 :     _primary_subdomain_name(getParam<SubdomainName>("primary_subdomain")),
      65       17160 :     _secondary_subdomain_name(getParam<SubdomainName>("secondary_subdomain")),
      66        8580 :     _is_coupling_functor(isType(Moose::RelationshipManagerType::COUPLING)),
      67       17160 :     _ghost_point_neighbors(getParam<bool>("ghost_point_neighbors")),
      68       25740 :     _ghost_higher_d_neighbors(getParam<bool>("ghost_higher_d_neighbors"))
      69             : {
      70        8580 : }
      71             : 
      72        3288 : AugmentSparsityOnInterface::AugmentSparsityOnInterface(const AugmentSparsityOnInterface & other)
      73             :   : RelationshipManager(other),
      74        3288 :     _primary_boundary_name(other._primary_boundary_name),
      75        3288 :     _secondary_boundary_name(other._secondary_boundary_name),
      76        3288 :     _primary_subdomain_name(other._primary_subdomain_name),
      77        3288 :     _secondary_subdomain_name(other._secondary_subdomain_name),
      78        3288 :     _is_coupling_functor(other._is_coupling_functor),
      79        3288 :     _ghost_point_neighbors(other._ghost_point_neighbors),
      80        3288 :     _ghost_higher_d_neighbors(other._ghost_higher_d_neighbors)
      81             : {
      82        3288 : }
      83             : 
      84             : void
      85        3288 : AugmentSparsityOnInterface::internalInitWithMesh(const MeshBase &)
      86             : {
      87        3288 : }
      88             : 
      89             : std::string
      90           0 : AugmentSparsityOnInterface::getInfo() const
      91             : {
      92           0 :   std::ostringstream oss;
      93           0 :   oss << "AugmentSparsityOnInterface";
      94           0 :   return oss.str();
      95           0 : }
      96             : 
      97             : void
      98      490732 : AugmentSparsityOnInterface::ghostMortarInterfaceCouplings(
      99             :     const processor_id_type p,
     100             :     const Elem * const elem,
     101             :     map_type & coupled_elements,
     102             :     const AutomaticMortarGeneration & amg) const
     103             : {
     104             :   // Look up elem in the mortar_interface_coupling data structure.
     105      490732 :   const auto & mic = amg.mortarInterfaceCoupling();
     106      490732 :   auto find_it = mic.find(elem->id());
     107      490732 :   if (find_it == mic.end())
     108      396837 :     return;
     109             : 
     110       93895 :   const auto & coupled_set = find_it->second;
     111             : 
     112      596279 :   for (const auto coupled_elem_id : coupled_set)
     113             :   {
     114             :     // Coupling data can precede a mesh rebuild, so IDs absent from the current mesh are ignored.
     115      502384 :     const Elem * const coupled_elem = _mesh->query_elem_ptr(coupled_elem_id);
     116      502384 :     if (coupled_elem && coupled_elem->processor_id() != p)
     117      402817 :       coupled_elements.emplace(coupled_elem, _null_mat);
     118             :   }
     119             : }
     120             : 
     121             : void
     122         350 : AugmentSparsityOnInterface::ghostLowerDSecondaryElemPointNeighbors(
     123             :     const processor_id_type p,
     124             :     const Elem * const query_elem,
     125             :     map_type & coupled_elements,
     126             :     const SubdomainID secondary_subdomain_id,
     127             :     const AutomaticMortarGeneration & amg) const
     128             : {
     129             :   // Node ownership can follow a higher-dimensional parent, so the query may be a secondary face,
     130             :   // its interior parent, or a paired primary parent. Start from every associated secondary face so
     131             :   // the ghosting graph contains the complete remote nodal-normal support in either row direction.
     132         350 :   std::vector<const Elem *> secondary_lower_elem_candidates;
     133         350 :   if (query_elem->subdomain_id() == secondary_subdomain_id)
     134         105 :     secondary_lower_elem_candidates.push_back(query_elem);
     135             : 
     136             :   // Coupling functors can run before mortar data is rebuilt after mesh refinement. Use the
     137             :   // relationship manager's current mesh for face topology instead of AMG-owned element pointers.
     138        1400 :   for (const auto side : query_elem->side_index_range())
     139        1050 :     if (const Elem * const lower_elem = _moose_mesh->getLowerDElem(query_elem, side);
     140        1050 :         lower_elem && lower_elem->subdomain_id() == secondary_subdomain_id)
     141         105 :       secondary_lower_elem_candidates.push_back(lower_elem);
     142             : 
     143         350 :   const auto & mic = amg.mortarInterfaceCoupling();
     144         350 :   if (const auto find_it = mic.find(query_elem->id()); find_it != mic.end())
     145        1190 :     for (const auto coupled_elem_id : find_it->second)
     146         910 :       if (const Elem * const coupled_elem = _mesh->query_elem_ptr(coupled_elem_id);
     147         910 :           coupled_elem && coupled_elem->subdomain_id() == secondary_subdomain_id)
     148         245 :         secondary_lower_elem_candidates.push_back(coupled_elem);
     149             : 
     150         350 :   std::set<dof_id_type> secondary_lower_elems_handled;
     151         805 :   for (const Elem * const candidate : secondary_lower_elem_candidates)
     152             :   {
     153         455 :     if (candidate->subactive())
     154           0 :       continue;
     155             : 
     156             :     // Resolve inactive ancestors to the active faces used by the current coupling graph.
     157         455 :     std::vector<const Elem *> active_secondary_lower_elems;
     158         455 :     candidate->active_family_tree(active_secondary_lower_elems);
     159         910 :     for (const Elem * const secondary_lower_elem : active_secondary_lower_elems)
     160             :     {
     161         910 :       if (secondary_lower_elem->subdomain_id() != secondary_subdomain_id ||
     162         910 :           !secondary_lower_elems_handled.insert(secondary_lower_elem->id()).second)
     163         105 :         continue;
     164             : 
     165         350 :       std::set<const Elem *> secondary_lower_elem_point_neighbors;
     166         350 :       secondary_lower_elem->find_point_neighbors(secondary_lower_elem_point_neighbors);
     167         350 :       secondary_lower_elem_point_neighbors.insert(secondary_lower_elem);
     168             : 
     169        1190 :       for (const Elem * const neigh : secondary_lower_elem_point_neighbors)
     170             :       {
     171         840 :         if (!neigh->active() || neigh->subdomain_id() != secondary_subdomain_id)
     172           0 :           continue;
     173             : 
     174             :         // Ghosting functors return only elements not owned by the requesting processor.
     175         840 :         if (neigh->processor_id() != p)
     176         350 :           coupled_elements.emplace(neigh, _null_mat);
     177             : 
     178             :         // Every point-neighbor face contributes to the smoothed nodal normal, even when it has no
     179             :         // mortar segment. Its interior parent therefore contributes displacement derivatives.
     180         840 :         const Elem * const interior_parent = neigh->interior_parent();
     181             :         mooseAssert(interior_parent,
     182             :                     "A lower-dimensional secondary mortar element must have an interior parent.");
     183         840 :         if (interior_parent->processor_id() != p)
     184         350 :           coupled_elements.emplace(interior_parent, _null_mat);
     185             : 
     186         840 :         ghostMortarInterfaceCouplings(p, neigh, coupled_elements, amg);
     187             :       }
     188         350 :     }
     189         455 :   }
     190         350 : }
     191             : 
     192             : void
     193       33516 : AugmentSparsityOnInterface::ghostHigherDNeighbors(const processor_id_type p,
     194             :                                                   const Elem * const query_elem,
     195             :                                                   map_type & coupled_elements,
     196             :                                                   const BoundaryID secondary_boundary_id,
     197             :                                                   const SubdomainID secondary_subdomain_id,
     198             :                                                   const AutomaticMortarGeneration & amg) const
     199             : {
     200             :   // The coupling is this for second order FV: secondary lower dimensional elem dofs will depend on
     201             :   // higher-d secondary elem neighbor primal dofs and higher-d primary elem neighbor primal dofs.
     202             :   // Higher dimensional primal dofs only depend on the secondary lower dimensional LM dofs for the
     203             :   // current FV gap heat transfer use cases
     204             : 
     205       33516 :   if (query_elem->subdomain_id() != secondary_subdomain_id)
     206       33138 :     return;
     207             : 
     208         378 :   const BoundaryInfo & binfo = _mesh->get_boundary_info();
     209         378 :   const auto which_side = query_elem->interior_parent()->which_side_am_i(query_elem);
     210         378 :   if (!binfo.has_boundary_id(query_elem->interior_parent(), which_side, secondary_boundary_id))
     211           0 :     return;
     212             : 
     213         378 :   const auto & mic = amg.mortarInterfaceCoupling();
     214         378 :   auto find_it = mic.find(query_elem->id());
     215         378 :   if (find_it == mic.end())
     216             :     // Perhaps no projection onto primary
     217           0 :     return;
     218             : 
     219         378 :   const auto & lower_d_coupled_set = find_it->second;
     220        1512 :   for (const auto coupled_elem_id : lower_d_coupled_set)
     221             :   {
     222        1134 :     auto * const coupled_elem = _mesh->elem_ptr(coupled_elem_id);
     223        1134 :     if (coupled_elem->dim() == query_elem->dim())
     224             :       // lower-d-elem
     225         378 :       continue;
     226             : 
     227         756 :     std::vector<const Elem *> active_neighbors;
     228             : 
     229        3780 :     for (auto s : coupled_elem->side_index_range())
     230             :     {
     231        3024 :       const Elem * const neigh = coupled_elem->neighbor_ptr(s);
     232             : 
     233        3024 :       if (!neigh || neigh == remote_elem)
     234         828 :         continue;
     235             : 
     236             :       // With any kind of neighbor, we need to couple to all the
     237             :       // active descendants on our side.
     238        2196 :       neigh->active_family_tree_by_neighbor(active_neighbors, coupled_elem);
     239             : 
     240        4392 :       for (const auto & neighbor : active_neighbors)
     241        2196 :         if (neighbor->processor_id() != p)
     242             :         {
     243             :           mooseAssert(
     244             :               neighbor->subdomain_id() != secondary_subdomain_id,
     245             :               "Ensure that we aren't missing potential erasures from the secondary-to-msms map");
     246        2196 :           coupled_elements.emplace(neighbor, _null_mat);
     247             :         }
     248             :     }
     249         756 :   }
     250             : }
     251             : 
     252             : void
     253      255167 : AugmentSparsityOnInterface::operator()(const MeshBase::const_element_iterator & range_begin,
     254             :                                        const MeshBase::const_element_iterator & range_end,
     255             :                                        const processor_id_type p,
     256             :                                        map_type & coupled_elements)
     257             : {
     258             :   // We ask the user to pass boundary names instead of ids to our constraint object.  However, We
     259             :   // are unable to get the boundary ids from boundary names until we've attached the MeshBase object
     260             :   // to the MooseMesh
     261      255167 :   const bool generating_mesh = !_moose_mesh->getMeshPtr();
     262             :   const auto primary_boundary_id = generating_mesh
     263      255167 :                                        ? Moose::INVALID_BOUNDARY_ID
     264      255167 :                                        : _moose_mesh->getBoundaryID(_primary_boundary_name);
     265             :   const auto secondary_boundary_id = generating_mesh
     266      255167 :                                          ? Moose::INVALID_BOUNDARY_ID
     267      255167 :                                          : _moose_mesh->getBoundaryID(_secondary_boundary_name);
     268             :   const auto primary_subdomain_id = generating_mesh
     269      255167 :                                         ? Moose::INVALID_BLOCK_ID
     270      255167 :                                         : _moose_mesh->getSubdomainID(_primary_subdomain_name);
     271             :   const auto secondary_subdomain_id = generating_mesh
     272      255167 :                                           ? Moose::INVALID_BLOCK_ID
     273      255167 :                                           : _moose_mesh->getSubdomainID(_secondary_subdomain_name);
     274             : 
     275             :   const AutomaticMortarGeneration * const amg =
     276      510334 :       _app.getExecutioner() ? &_app.getExecutioner()->feProblem().getMortarInterface(
     277      255167 :                                   std::make_pair(primary_boundary_id, secondary_boundary_id),
     278           0 :                                   std::make_pair(primary_subdomain_id, secondary_subdomain_id),
     279      255167 :                                   _use_displaced_mesh)
     280      255167 :                             : nullptr;
     281             : 
     282             :   // If we're on a dynamic mesh or we have not yet constructed the mortar mesh, we need to ghost the
     283             :   // entire interface because we don't know a priori what elements will project onto what. We *do
     284             :   // not* add the whole interface if we are a coupling functor because it is very expensive. This is
     285             :   // because when building the sparsity pattern, we call through to the ghosting functors with one
     286             :   // element at a time (and then below we do a loop over all the mesh's active elements). It's
     287             :   // perhaps faster in this case to deal with mallocs coming out of MatSetValues, especially if the
     288             :   // mesh displacements are relatively small
     289      255167 :   if ((!amg || _use_displaced_mesh) && !_is_coupling_functor)
     290             :   {
     291      127048 :     for (const Elem * const elem : _mesh->active_element_ptr_range())
     292             :     {
     293       63088 :       if (generating_mesh)
     294             :       {
     295             :         // We are still generating the mesh, so it's possible we don't even have the right boundary
     296             :         // ids created yet! So we actually ghost all boundary elements and all lower dimensional
     297             :         // elements who have parents on a boundary
     298           0 :         if (elem->on_boundary())
     299           0 :           coupled_elements.insert(std::make_pair(elem, _null_mat));
     300           0 :         else if (const Elem * const ip = elem->interior_parent())
     301             :         {
     302           0 :           if (ip->on_boundary())
     303           0 :             coupled_elements.insert(std::make_pair(elem, _null_mat));
     304             :         }
     305             :       }
     306             :       else
     307             :       {
     308             :         // We've finished generating our mesh so we can be selective and only ghost elements lying
     309             :         // in our lower-dimensional subdomains and their interior parents
     310             : 
     311             :         mooseAssert(primary_boundary_id != Moose::INVALID_BOUNDARY_ID,
     312             :                     "Primary boundary id should exist by now.");
     313             :         mooseAssert(secondary_boundary_id != Moose::INVALID_BOUNDARY_ID,
     314             :                     "Secondary boundary id should exist by now.");
     315             :         mooseAssert(primary_subdomain_id != Moose::INVALID_BLOCK_ID,
     316             :                     "Primary subdomain id should exist by now.");
     317             :         mooseAssert(secondary_subdomain_id != Moose::INVALID_BLOCK_ID,
     318             :                     "Secondary subdomain id should exist by now.");
     319             : 
     320             :         // Higher-dimensional boundary elements
     321       63088 :         const BoundaryInfo & binfo = _mesh->get_boundary_info();
     322             : 
     323      258564 :         for (auto side : elem->side_index_range())
     324      292112 :           if ((elem->processor_id() != p) &&
     325       96636 :               (binfo.has_boundary_id(elem, side, primary_boundary_id) ||
     326       94656 :                binfo.has_boundary_id(elem, side, secondary_boundary_id)))
     327        4080 :             coupled_elements.insert(std::make_pair(elem, _null_mat));
     328             : 
     329             :         // Lower dimensional subdomain elements
     330       92560 :         if ((elem->processor_id() != p) && (elem->subdomain_id() == primary_subdomain_id ||
     331       29472 :                                             elem->subdomain_id() == secondary_subdomain_id))
     332             :         {
     333        3864 :           coupled_elements.insert(std::make_pair(elem, _null_mat));
     334             : 
     335             : #ifndef NDEBUG
     336             :           // let's do some safety checks
     337             :           const Elem * const ip = elem->interior_parent();
     338             :           mooseAssert(ip,
     339             :                       "We should have set interior parents for all of our lower-dimensional mortar "
     340             :                       "subdomains");
     341             :           auto side = ip->which_side_am_i(elem);
     342             :           auto bnd_id = elem->subdomain_id() == primary_subdomain_id ? primary_boundary_id
     343             :                                                                      : secondary_boundary_id;
     344             :           mooseAssert(_mesh->get_boundary_info().has_boundary_id(ip, side, bnd_id),
     345             :                       "The interior parent for the lower-dimensional element does not lie on the "
     346             :                       "boundary");
     347             : #endif
     348             :         }
     349             :       }
     350         872 :     }
     351         872 :   }
     352             :   // For a static mesh (or for determining a sparsity pattern approximation on a displaced mesh) we
     353             :   // can just ghost the coupled elements determined during mortar mesh generation
     354      254295 :   else if (amg)
     355             :   {
     356     1234079 :     for (const Elem * const elem : as_range(range_begin, range_end))
     357             :     {
     358      489892 :       ghostMortarInterfaceCouplings(p, elem, coupled_elements, *amg);
     359             : 
     360      489892 :       if (_ghost_point_neighbors)
     361         350 :         ghostLowerDSecondaryElemPointNeighbors(
     362             :             p, elem, coupled_elements, secondary_subdomain_id, *amg);
     363      489892 :       if (_ghost_higher_d_neighbors)
     364       33516 :         ghostHigherDNeighbors(
     365             :             p, elem, coupled_elements, secondary_boundary_id, secondary_subdomain_id, *amg);
     366      254295 :     } // end for loop over input range
     367             :   } // end if amg
     368      255167 : }
     369             : 
     370             : bool
     371       43953 : AugmentSparsityOnInterface::operator>=(const RelationshipManager & other) const
     372             : {
     373       43953 :   if (auto asoi = dynamic_cast<const AugmentSparsityOnInterface *>(&other))
     374             :   {
     375       37006 :     if (_primary_boundary_name == asoi->_primary_boundary_name &&
     376       29380 :         _secondary_boundary_name == asoi->_secondary_boundary_name &&
     377       29380 :         _primary_subdomain_name == asoi->_primary_subdomain_name &&
     378       14690 :         _secondary_subdomain_name == asoi->_secondary_subdomain_name &&
     379       14690 :         _ghost_point_neighbors >= asoi->_ghost_point_neighbors &&
     380       37006 :         _ghost_higher_d_neighbors >= asoi->_ghost_higher_d_neighbors && baseGreaterEqual(*asoi))
     381        6446 :       return true;
     382             :   }
     383       37507 :   return false;
     384             : }
     385             : 
     386             : std::unique_ptr<GhostingFunctor>
     387        3288 : AugmentSparsityOnInterface::clone() const
     388             : {
     389        3288 :   return _app.getFactory().copyConstruct(*this);
     390             : }

Generated by: LCOV version 1.14