LCOV - code coverage report
Current view: top level - src/mesh - boundary_info.C (source / functions) Hit Total Coverage
Test: libMesh/libmesh: #4270 (874c3a) with base 034308 Lines: 1090 1523 71.6 %
Date: 2025-10-10 17:07:18 Functions: 108 159 67.9 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : // The libMesh Finite Element Library.
       2             : // Copyright (C) 2002-2025 Benjamin S. Kirk, John W. Peterson, Roy H. Stogner
       3             : 
       4             : // This library is free software; you can redistribute it and/or
       5             : // modify it under the terms of the GNU Lesser General Public
       6             : // License as published by the Free Software Foundation; either
       7             : // version 2.1 of the License, or (at your option) any later version.
       8             : 
       9             : // This library is distributed in the hope that it will be useful,
      10             : // but WITHOUT ANY WARRANTY; without even the implied warranty of
      11             : // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
      12             : // Lesser General Public License for more details.
      13             : 
      14             : // You should have received a copy of the GNU Lesser General Public
      15             : // License along with this library; if not, write to the Free Software
      16             : // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
      17             : 
      18             : 
      19             : 
      20             : // Local includes
      21             : #include "libmesh/libmesh_config.h"
      22             : #include "libmesh/libmesh_logging.h"
      23             : #include "libmesh/boundary_info.h"
      24             : #include "libmesh/distributed_mesh.h"
      25             : #include "libmesh/elem.h"
      26             : #include "libmesh/mesh_communication.h"
      27             : #include "libmesh/mesh_serializer.h"
      28             : #include "libmesh/parallel.h"
      29             : #include "libmesh/partitioner.h"
      30             : #include "libmesh/remote_elem.h"
      31             : #include "libmesh/unstructured_mesh.h"
      32             : #include "libmesh/elem_side_builder.h"
      33             : 
      34             : // TIMPI includes
      35             : #include "timpi/parallel_sync.h"
      36             : 
      37             : // C++ includes
      38             : #include <iterator>  // std::distance
      39             : #include <algorithm> // std::max_element
      40             : 
      41             : namespace
      42             : {
      43             : 
      44             : // Templated helper function for removing a subset of keys from a
      45             : // multimap that further satisfy a given predicate on the
      46             : // corresponding values.
      47             : template <class Key, class T, class Pred>
      48     1218549 : void erase_if(std::multimap<Key,T> & map, Key k, Pred pred)
      49             : {
      50       59848 :   auto rng = map.equal_range(k);
      51       59848 :   auto it = rng.first;
      52     1484602 :   while (it != rng.second)
      53             :     {
      54      249645 :       if (pred(it->second))
      55       65051 :         it = map.erase(it);
      56             :       else
      57        8667 :         ++it;
      58             :     }
      59     1218549 : }
      60             : 
      61             : // Similar to the helper function above but doesn't take a key,
      62             : // instead it applies the predicate to every value in the map.
      63             : template <class Key, class T, class Pred>
      64        2272 : void erase_if(std::multimap<Key,T> & map, Pred pred)
      65             : {
      66          64 :   auto it = map.begin();
      67        6017 :   while (it != map.end())
      68             :     {
      69        3745 :       if (pred(it->second))
      70        1287 :         it = map.erase(it);
      71             :       else
      72         136 :         ++it;
      73             :     }
      74        2272 : }
      75             : 
      76             : // Helper func for renumber_id
      77             : template <typename Map, typename T>
      78        5964 : void renumber_name(Map & m, T old_id, T new_id)
      79             : {
      80        5964 :   if (const auto it = std::as_const(m).find(old_id);
      81         168 :       it != m.end())
      82             :     {
      83        3976 :       m[new_id] = it->second;
      84        3864 :       m.erase(it);
      85             :     }
      86        5964 : }
      87             : 
      88             : }
      89             : 
      90             : namespace libMesh
      91             : {
      92             : 
      93             : 
      94             : 
      95             : //------------------------------------------------------
      96             : // BoundaryInfo static member initializations
      97             : const boundary_id_type BoundaryInfo::invalid_id = -123;
      98             : 
      99             : 
     100             : 
     101             : //------------------------------------------------------
     102             : // BoundaryInfo functions
     103      329023 : BoundaryInfo::BoundaryInfo(MeshBase & m) :
     104             :   ParallelObject(m.comm()),
     105      309651 :   _mesh (&m),
     106      348379 :   _children_on_boundary(false)
     107             : {
     108      329023 : }
     109             : 
     110       23256 : BoundaryInfo & BoundaryInfo::operator=(const BoundaryInfo & other_boundary_info)
     111             : {
     112             :   // Overwrite any preexisting boundary info
     113       23256 :   this->clear();
     114             : 
     115             :   /**
     116             :    * We're going to attempt to pull _new_ pointers out of the mesh
     117             :    * assigned to this boundary info.
     118             :    *
     119             :    * This will only work if the mesh assigned to this BoundaryInfo is
     120             :    * the same mesh object as other_boundary_info _or_ was constructed
     121             :    * in exactly the same way (or constructed as a copy, or a refined
     122             :    * copy without renumbering, etc.).
     123             :    */
     124             : 
     125             :   // Copy node boundary info
     126      502350 :   for (const auto & [node, bid] : other_boundary_info._boundary_node_id)
     127      479094 :     _boundary_node_id.emplace(_mesh->node_ptr(node->id()), bid);
     128             : 
     129             :   // Copy edge boundary info
     130       23370 :   for (const auto & [elem, id_pair] : other_boundary_info._boundary_edge_id)
     131         114 :     _boundary_edge_id.emplace(_mesh->elem_ptr(elem->id()), id_pair);
     132             : 
     133             :   // Copy shellface boundary info
     134       23300 :   for (const auto & [elem, id_pair] : other_boundary_info._boundary_shellface_id)
     135          44 :     _boundary_shellface_id.emplace(_mesh->elem_ptr(elem->id()), id_pair);
     136             : 
     137             :   // Copy side boundary info
     138      540962 :   for (const auto & [elem, id_pair] : other_boundary_info._boundary_side_id)
     139      517706 :     _boundary_side_id.emplace(_mesh->elem_ptr(elem->id()), id_pair);
     140             : 
     141         772 :   _boundary_ids = other_boundary_info._boundary_ids;
     142         772 :   _global_boundary_ids = other_boundary_info._global_boundary_ids;
     143         772 :   _side_boundary_ids = other_boundary_info._side_boundary_ids;
     144         772 :   _node_boundary_ids = other_boundary_info._node_boundary_ids;
     145         772 :   _edge_boundary_ids = other_boundary_info._edge_boundary_ids;
     146         772 :   _shellface_boundary_ids = other_boundary_info._shellface_boundary_ids;
     147             : 
     148         772 :   _ss_id_to_name = other_boundary_info._ss_id_to_name;
     149         772 :   _ns_id_to_name = other_boundary_info._ns_id_to_name;
     150         772 :   _es_id_to_name = other_boundary_info._es_id_to_name;
     151             : 
     152       23256 :   return *this;
     153             : }
     154             : 
     155             : 
     156        6534 : bool BoundaryInfo::operator==(const BoundaryInfo & other_boundary_info) const
     157             : {
     158      293410 :   for (const auto & [other_node, bid] : other_boundary_info._boundary_node_id)
     159             :     {
     160      286876 :       const Node * node = this->_mesh->query_node_ptr(other_node->id());
     161      286876 :       if (!node)
     162           0 :         return false;
     163      286876 :       if (!this->has_boundary_id(node, bid))
     164           0 :         return false;
     165             :     }
     166      293134 :   for (const auto & [node, bid] : this->_boundary_node_id)
     167             :     {
     168             :       const Node * other_node =
     169      286635 :         other_boundary_info._mesh->query_node_ptr(node->id());
     170      286635 :       if (!other_node)
     171           2 :         return false;
     172      286635 :       if (!other_boundary_info.has_boundary_id(other_node, bid))
     173           2 :         return false;
     174             :     }
     175             : 
     176         188 :   auto compare_edges = [&](const Elem * elem,
     177             :                            const Elem * other_elem,
     178          60 :                            unsigned short int edge)
     179             :   {
     180         248 :     if (!elem)
     181           0 :       return false;
     182         248 :     if (!other_elem)
     183           0 :       return false;
     184             : 
     185          80 :     std::vector<boundary_id_type> our_edges, other_edges;
     186         248 :     this->edge_boundary_ids(elem, edge, our_edges);
     187         248 :     other_boundary_info.edge_boundary_ids(other_elem, edge, other_edges);
     188         288 :     if (our_edges.size() != other_edges.size())
     189           0 :       return false;
     190             : 
     191         248 :     std::sort(our_edges.begin(), our_edges.end());
     192         248 :     std::sort(other_edges.begin(), other_edges.end());
     193         496 :     for (auto i : index_range(our_edges))
     194         268 :       if (our_edges[i] != other_edges[i])
     195           0 :         return false;
     196          60 :     return true;
     197        6499 :   };
     198             : 
     199        6623 :   for (const auto & [other_elem, edge_id_pair] : other_boundary_info._boundary_edge_id)
     200             :     {
     201         124 :       const Elem * elem = this->_mesh->query_elem_ptr(other_elem->id());
     202         124 :       if (!compare_edges(elem, other_elem, edge_id_pair.first))
     203           0 :         return false;
     204             :     }
     205             : 
     206        6623 :   for (const auto & [elem, edge_id_pair] : this->_boundary_edge_id)
     207             :     {
     208         124 :       const Elem * other_elem = other_boundary_info._mesh->query_elem_ptr(elem->id());
     209         124 :       if (!compare_edges(elem, other_elem, edge_id_pair.first))
     210           0 :         return false;
     211             :     }
     212             : 
     213      204284 :   auto compare_sides = [&](const Elem * elem,
     214             :                            const Elem * other_elem,
     215       44784 :                            unsigned short int side)
     216             :   {
     217      249068 :     if (!elem)
     218           0 :       return false;
     219      249068 :     if (!other_elem)
     220           0 :       return false;
     221             : 
     222       76288 :     std::vector<boundary_id_type> our_sides, other_sides;
     223      249068 :     this->boundary_ids(elem, side, our_sides);
     224      249068 :     other_boundary_info.boundary_ids(other_elem, side, other_sides);
     225      262348 :     if (our_sides.size() != other_sides.size())
     226           0 :       return false;
     227             : 
     228      249068 :     std::sort(our_sides.begin(), our_sides.end());
     229      249068 :     std::sort(other_sides.begin(), other_sides.end());
     230      498136 :     for (auto i : index_range(our_sides))
     231      255708 :       if (our_sides[i] != other_sides[i])
     232           0 :         return false;
     233       44784 :     return true;
     234        6499 :   };
     235             : 
     236      131033 :   for (const auto & [other_elem, side_id_pair] : other_boundary_info._boundary_side_id)
     237             :     {
     238      124534 :       const Elem * elem = this->_mesh->query_elem_ptr(other_elem->id());
     239      124534 :       if (!compare_sides(elem, other_elem, side_id_pair.first))
     240           0 :         return false;
     241             :     }
     242             : 
     243      131033 :   for (const auto & [elem, side_id_pair] : this->_boundary_side_id)
     244             :     {
     245      124534 :       const Elem * other_elem = other_boundary_info._mesh->query_elem_ptr(elem->id());
     246      124534 :       if (!compare_sides(elem, other_elem, side_id_pair.first))
     247           0 :         return false;
     248             :     }
     249             : 
     250          72 :   auto compare_shellfaces = [&](const Elem * elem,
     251             :                                 const Elem * other_elem,
     252          24 :                                 unsigned short int shellface)
     253             :   {
     254          96 :     if (!elem)
     255           0 :       return false;
     256          96 :     if (!other_elem)
     257           0 :       return false;
     258             : 
     259          32 :     std::vector<boundary_id_type> our_shellfaces, other_shellfaces;
     260          96 :     this->shellface_boundary_ids(elem, shellface, our_shellfaces);
     261          96 :     other_boundary_info.shellface_boundary_ids(other_elem, shellface, other_shellfaces);
     262         112 :     if (our_shellfaces.size() != other_shellfaces.size())
     263           0 :       return false;
     264             : 
     265          96 :     std::sort(our_shellfaces.begin(), our_shellfaces.end());
     266          96 :     std::sort(other_shellfaces.begin(), other_shellfaces.end());
     267         192 :     for (auto i : index_range(our_shellfaces))
     268         104 :       if (our_shellfaces[i] != other_shellfaces[i])
     269           0 :         return false;
     270          24 :     return true;
     271        6499 :   };
     272             : 
     273        6547 :   for (const auto & [other_elem, shellface_id_pair] : other_boundary_info._boundary_shellface_id)
     274             :     {
     275          48 :       const Elem * elem = this->_mesh->query_elem_ptr(other_elem->id());
     276          48 :       if (!compare_shellfaces(elem, other_elem, shellface_id_pair.first))
     277           0 :         return false;
     278             :     }
     279             : 
     280        6547 :   for (const auto & [elem, shellface_id_pair] : this->_boundary_shellface_id)
     281             :     {
     282          48 :       const Elem * other_elem = other_boundary_info._mesh->query_elem_ptr(elem->id());
     283          48 :       if (!compare_shellfaces(elem, other_elem, shellface_id_pair.first))
     284           0 :         return false;
     285             :     }
     286             : 
     287        6499 :   if (_children_on_boundary != other_boundary_info._children_on_boundary)
     288           0 :     return false;
     289             : 
     290       38994 :   auto compare_sets = [](const auto & set1, const auto & set2)
     291             :   {
     292       38994 :     if (set1.size() != set2.size())
     293           0 :       return false;
     294      138152 :     for (boundary_id_type bid : set1)
     295        7574 :       if (!set2.count(bid))
     296           0 :         return false;
     297             : 
     298       38010 :     return true;
     299             :   };
     300             : 
     301        8083 :   if (!compare_sets(_boundary_ids,
     302       12206 :                     other_boundary_info._boundary_ids) ||
     303        6499 :       !compare_sets(_global_boundary_ids,
     304        6663 :                     other_boundary_info._global_boundary_ids) ||
     305        6499 :       !compare_sets(_edge_boundary_ids,
     306        6663 :                     other_boundary_info._edge_boundary_ids) ||
     307        6499 :       !compare_sets(_node_boundary_ids,
     308        6663 :                     other_boundary_info._node_boundary_ids) ||
     309        6499 :       !compare_sets(_shellface_boundary_ids,
     310       13954 :                     other_boundary_info._shellface_boundary_ids) ||
     311        6499 :       !compare_sets(_side_boundary_ids,
     312        6499 :                     other_boundary_info._side_boundary_ids))
     313           0 :     return false;
     314             : 
     315       19425 :   auto compare_maps = [](const auto & map1, const auto & map2)
     316             :   {
     317       19425 :     if (map1.size() != map2.size())
     318           0 :       return false;
     319       69921 :     for (const auto & pair : map1)
     320       99688 :       if (!map2.count(pair.first) ||
     321       50532 :           map2.at(pair.first) != pair.second)
     322           0 :         return false;
     323             : 
     324        2376 :     return true;
     325             :   };
     326             : 
     327        8083 :   if (!compare_maps(_ss_id_to_name,
     328       12170 :                     other_boundary_info._ss_id_to_name) ||
     329        6463 :       !compare_maps(_ns_id_to_name,
     330       12962 :                     other_boundary_info._ns_id_to_name) ||
     331        6463 :       !compare_maps(_es_id_to_name,
     332        6463 :                     other_boundary_info._es_id_to_name))
     333          36 :     return false;
     334             : 
     335         792 :   return true;
     336             : }
     337             : 
     338             : 
     339      619369 : BoundaryInfo::~BoundaryInfo() = default;
     340             : 
     341             : 
     342             : 
     343      972031 : void BoundaryInfo::clear()
     344             : {
     345       29348 :   _boundary_node_id.clear();
     346       29348 :   _boundary_side_id.clear();
     347       29348 :   _boundary_edge_id.clear();
     348       29348 :   _boundary_shellface_id.clear();
     349       29348 :   _boundary_ids.clear();
     350       29348 :   _side_boundary_ids.clear();
     351       29348 :   _node_boundary_ids.clear();
     352       29348 :   _edge_boundary_ids.clear();
     353       29348 :   _shellface_boundary_ids.clear();
     354       29348 :   _ss_id_to_name.clear();
     355       29348 :   _ns_id_to_name.clear();
     356       29348 :   _es_id_to_name.clear();
     357      972031 : }
     358             : 
     359             : 
     360             : 
     361      825743 : void BoundaryInfo::regenerate_id_sets()
     362             : {
     363       27116 :   const auto old_ss_id_to_name = _ss_id_to_name;
     364       27116 :   const auto old_ns_id_to_name = _ns_id_to_name;
     365       27116 :   const auto old_es_id_to_name = _es_id_to_name;
     366             : 
     367             :   // Clear the old caches
     368       13558 :   _boundary_ids.clear();
     369       13558 :   _side_boundary_ids.clear();
     370       13558 :   _node_boundary_ids.clear();
     371       13558 :   _edge_boundary_ids.clear();
     372       13558 :   _shellface_boundary_ids.clear();
     373       27084 :   _ss_id_to_name.clear();
     374       27084 :   _ns_id_to_name.clear();
     375       27084 :   _es_id_to_name.clear();
     376             : 
     377             :   // Loop over id maps to regenerate each set.
     378    17607574 :   for (const auto & pr : _boundary_node_id)
     379             :     {
     380    16781831 :       const boundary_id_type id = pr.second;
     381    16286609 :       _boundary_ids.insert(id);
     382    16286609 :       _node_boundary_ids.insert(id);
     383    16781831 :       if (const auto it = old_ns_id_to_name.find(id);
     384      495270 :           it != old_ns_id_to_name.end())
     385    14792758 :         _ns_id_to_name.emplace(id, it->second);
     386             :     }
     387             : 
     388      829326 :   for (const auto & pr : _boundary_edge_id)
     389             :     {
     390        3583 :       const boundary_id_type id = pr.second.second;
     391        3477 :       _boundary_ids.insert(id);
     392        3477 :       _edge_boundary_ids.insert(id);
     393        3583 :       if (const auto it = old_es_id_to_name.find(id);
     394         106 :           it != old_es_id_to_name.end())
     395        1456 :         _es_id_to_name.emplace(id, it->second);
     396             :     }
     397             : 
     398     8438359 :   for (const auto & pr : _boundary_side_id)
     399             :     {
     400     7612616 :       const boundary_id_type id = pr.second.second;
     401     7380716 :       _boundary_ids.insert(id);
     402     7380716 :       _side_boundary_ids.insert(id);
     403     7612616 :       if (const auto it = old_ss_id_to_name.find(id);
     404      232680 :           it != old_ss_id_to_name.end())
     405     5787917 :         _ss_id_to_name.emplace(id, it->second);
     406             :     }
     407             : 
     408     1031233 :   for (const auto & pr : _boundary_shellface_id)
     409             :     {
     410      205490 :       const boundary_id_type id = pr.second.second;
     411      192174 :       _boundary_ids.insert(id);
     412      192174 :       _shellface_boundary_ids.insert(id);
     413             :     }
     414             : 
     415             :   // Handle global data
     416       13558 :   _global_boundary_ids = _boundary_ids;
     417       13558 :   libmesh_assert(_mesh);
     418      825743 :   if (!_mesh->is_serial())
     419             :     {
     420      738011 :       _communicator.set_union(_ss_id_to_name);
     421      738011 :       _communicator.set_union(_ns_id_to_name);
     422      738011 :       _communicator.set_union(_es_id_to_name);
     423      738011 :       _communicator.set_union(_global_boundary_ids);
     424             :     }
     425      825743 : }
     426             : 
     427             : 
     428             : 
     429           0 : void BoundaryInfo::sync (UnstructuredMesh & boundary_mesh)
     430             : {
     431           0 :   std::set<boundary_id_type> request_boundary_ids(_boundary_ids);
     432           0 :   request_boundary_ids.insert(invalid_id);
     433           0 :   if (!_mesh->is_serial())
     434           0 :     this->comm().set_union(request_boundary_ids);
     435             : 
     436           0 :   this->sync(request_boundary_ids,
     437             :              boundary_mesh);
     438           0 : }
     439             : 
     440             : 
     441             : 
     442        1491 : void BoundaryInfo::sync (const std::set<boundary_id_type> & requested_boundary_ids,
     443             :                          UnstructuredMesh & boundary_mesh)
     444             : {
     445             :   // Call the 3 argument version of this function with a dummy value for the third set.
     446          84 :   std::set<subdomain_id_type> subdomains_relative_to;
     447        1449 :   subdomains_relative_to.insert(Elem::invalid_subdomain_id);
     448             : 
     449        1491 :   this->sync(requested_boundary_ids,
     450             :              boundary_mesh,
     451             :              subdomains_relative_to);
     452        1491 : }
     453             : 
     454             : 
     455             : 
     456        1917 : void BoundaryInfo::sync (const std::set<boundary_id_type> & requested_boundary_ids,
     457             :                          UnstructuredMesh & boundary_mesh,
     458             :                          const std::set<subdomain_id_type> & subdomains_relative_to)
     459             : {
     460         108 :   LOG_SCOPE("sync()", "BoundaryInfo");
     461             : 
     462        1917 :   boundary_mesh.clear();
     463             : 
     464             :   /**
     465             :    * Deleting 0 elements seems weird, but it's better encapsulating
     466             :    * than exposing a set_is_serial(false) capability that might be
     467             :    * easily misused.
     468             :    */
     469        1917 :   if (!_mesh->is_serial())
     470        1728 :     boundary_mesh.delete_remote_elements();
     471             : 
     472             :   /**
     473             :    * If the boundary_mesh is still serial, that means we *can't*
     474             :    * parallelize it, so to make sure we can construct it in full on
     475             :    * every processor we'll serialize the interior mesh.
     476             :    *
     477             :    * We'll use a temporary MeshSerializer here, but as soon as we
     478             :    * unserialize we'll be turning a bunch of interior_parent() links
     479             :    * into dangling pointers, and it won't be easy to tell which.  So
     480             :    * we'll keep around a distributed copy for that case, and query it
     481             :    * to fix up interior_parent() links as necessary.
     482             :    *
     483             :    * We'll also need to make sure to unserialize the mesh *before* we
     484             :    * prepare the boundary mesh for use, or the prepare_for_use() call
     485             :    * on a refined boundary mesh will happily notice that it can find
     486             :    * and restore some refined elements' interior_parent pointers, not
     487             :    * knowing that those interior parents are about to go remote.
     488             :    */
     489        1863 :   std::unique_ptr<MeshBase> mesh_copy;
     490        1917 :   if (boundary_mesh.is_serial() && !_mesh->is_serial())
     491         896 :     mesh_copy = _mesh->clone();
     492             : 
     493             :   auto serializer = std::make_unique<MeshSerializer>
     494        1971 :     (const_cast<MeshBase &>(*_mesh), boundary_mesh.is_serial());
     495             : 
     496             :   /**
     497             :    * Re-create the boundary mesh.
     498             :    */
     499             : 
     500        1917 :   boundary_mesh.set_n_partitions() = _mesh->n_partitions();
     501             : 
     502         108 :   std::map<dof_id_type, dof_id_type> node_id_map;
     503         108 :   std::map<std::pair<dof_id_type, unsigned char>, dof_id_type> side_id_map;
     504             : 
     505        1917 :   this->_find_id_maps(requested_boundary_ids, 0, &node_id_map, 0, &side_id_map, subdomains_relative_to);
     506             : 
     507             :   // Let's add all the boundary nodes we found to the boundary mesh
     508      282602 :   for (const auto & node : _mesh->node_ptr_range())
     509             :     {
     510      146285 :       dof_id_type node_id = node->id();
     511        6874 :       if (node_id_map.count(node_id))
     512             :         {
     513       24369 :           boundary_mesh.add_point(*node, node_id_map[node_id], node->processor_id());
     514             : 
     515             :           // Copy over all the node's boundary IDs to boundary_mesh
     516        2484 :           std::vector<boundary_id_type> node_boundary_ids;
     517       24369 :           this->boundary_ids(node, node_boundary_ids);
     518       42381 :           for (const auto & node_bid : node_boundary_ids)
     519       18012 :             boundary_mesh.get_boundary_info().add_node(node_id_map[node_id], node_bid);
     520             :         }
     521        1809 :     }
     522             : 
     523             :   // Add the elements. When syncing a boundary mesh, we also store the
     524             :   // parent side ids in addition to the interior_parent pointers,
     525             :   // since this information is frequently needed on boundary meshes.
     526        1917 :   this->add_elements(requested_boundary_ids,
     527             :                      boundary_mesh,
     528             :                      subdomains_relative_to,
     529             :                      /*store_parent_side_ids=*/true);
     530             : 
     531             :   // The new elements are currently using the interior mesh's nodes;
     532             :   // we want them to use the boundary mesh's nodes instead.
     533             : 
     534             :   // This side's Node pointers still point to the nodes of the original mesh.
     535             :   // We need to re-point them to the boundary mesh's nodes!  Since we copied *ALL* of
     536             :   // the original mesh's nodes over, we should be guaranteed to have the same ordering.
     537       28360 :   for (auto & new_elem : boundary_mesh.element_ptr_range())
     538             :     {
     539       52494 :       for (auto nn : new_elem->node_index_range())
     540             :         {
     541             :           // Get the correct node pointer, based on the id()
     542             :           Node * new_node =
     543       40934 :             boundary_mesh.node_ptr(node_id_map[new_elem->node_id(nn)]);
     544             : 
     545             :           // sanity check: be sure that the new Node exists and its
     546             :           // global id really matches
     547        2230 :           libmesh_assert (new_node);
     548        2230 :           libmesh_assert_equal_to (new_node->id(),
     549             :                                    node_id_map[new_elem->node_id(nn)]);
     550             : 
     551             :           // Assign the new node pointer
     552       38704 :           new_elem->set_node(nn, new_node);
     553             :         }
     554        1809 :     }
     555             : 
     556             :   // The new elements might have interior parent pointers aimed at
     557             :   // _mesh elements which are about to go remote, and we don't to
     558             :   // leave those pointers dangling.  Fix them up if needed.
     559        1917 :   if (mesh_copy.get())
     560             :     {
     561        7552 :       for (auto & new_elem : boundary_mesh.element_ptr_range())
     562             :         {
     563             :           const dof_id_type interior_parent_id =
     564        3328 :             new_elem->interior_parent()->id();
     565             : 
     566        3328 :           if (!mesh_copy->query_elem_ptr(interior_parent_id))
     567             :             new_elem->set_interior_parent
     568        2318 :               (const_cast<RemoteElem *>(remote_elem));
     569         448 :         }
     570             :     }
     571             : 
     572             :   // Don't repartition this mesh; we want it to stay in sync with the
     573             :   // interior partitioning.
     574        1917 :   boundary_mesh.partitioner().reset(nullptr);
     575             : 
     576             :   // Deserialize the interior mesh before the boundary mesh
     577             :   // prepare_for_use() can come to erroneous conclusions about which
     578             :   // of its elements are semilocal
     579          54 :   serializer.reset();
     580             : 
     581             :   // Make boundary_mesh nodes and elements contiguous
     582        1917 :   boundary_mesh.prepare_for_use();
     583             : 
     584             :   // and finally distribute element partitioning to the nodes
     585        1917 :   Partitioner::set_node_processor_ids(boundary_mesh);
     586        1917 : }
     587             : 
     588             : 
     589           0 : void BoundaryInfo::get_side_and_node_maps (UnstructuredMesh & boundary_mesh,
     590             :                                            std::map<dof_id_type, dof_id_type> & node_id_map,
     591             :                                            std::map<dof_id_type, unsigned char> & side_id_map,
     592             :                                            Real tolerance)
     593             : {
     594           0 :   LOG_SCOPE("get_side_and_node_maps()", "BoundaryInfo");
     595             : 
     596           0 :   node_id_map.clear();
     597           0 :   side_id_map.clear();
     598             : 
     599             :   // For building element sides without extraneous allocation
     600           0 :   ElemSideBuilder side_builder;
     601             :   // Pull objects out of the loop to reduce heap operations
     602             :   const Elem * interior_parent_side;
     603             : 
     604           0 :   for (const auto & boundary_elem : boundary_mesh.active_element_ptr_range())
     605             :     {
     606           0 :       const Elem * interior_parent = boundary_elem->interior_parent();
     607             : 
     608             :       // Find out which side of interior_parent boundary_elem corresponds to.
     609             :       // Use distance between average vertex location as a way to check.
     610           0 :       unsigned char interior_parent_side_index = 0;
     611           0 :       bool found_matching_sides = false;
     612           0 :       for (auto side : interior_parent->side_index_range())
     613             :         {
     614           0 :           interior_parent_side = &side_builder(*interior_parent, side);
     615           0 :           Real va_distance = (boundary_elem->vertex_average() - interior_parent_side->vertex_average()).norm();
     616             : 
     617           0 :           if (va_distance < (tolerance * boundary_elem->hmin()))
     618             :             {
     619           0 :               interior_parent_side_index = cast_int<unsigned char>(side);
     620           0 :               found_matching_sides = true;
     621           0 :               break;
     622             :             }
     623             :         }
     624             : 
     625           0 :       libmesh_error_msg_if(!found_matching_sides, "No matching side found within the specified tolerance");
     626             : 
     627           0 :       side_id_map[boundary_elem->id()] = interior_parent_side_index;
     628             : 
     629           0 :       for (auto local_node_index : boundary_elem->node_index_range())
     630             :         {
     631           0 :           dof_id_type boundary_node_id = boundary_elem->node_id(local_node_index);
     632           0 :           dof_id_type interior_node_id = interior_parent_side->node_id(local_node_index);
     633             : 
     634           0 :           node_id_map[interior_node_id] = boundary_node_id;
     635             :         }
     636           0 :     }
     637           0 : }
     638             : 
     639             : 
     640             : 
     641         568 : void BoundaryInfo::add_elements(const std::set<boundary_id_type> & requested_boundary_ids,
     642             :                                 UnstructuredMesh & boundary_mesh,
     643             :                                 bool store_parent_side_ids)
     644             : {
     645             :   // Call the 3 argument version of this function with a dummy value for the third arg.
     646          32 :   std::set<subdomain_id_type> subdomains_relative_to;
     647         552 :   subdomains_relative_to.insert(Elem::invalid_subdomain_id);
     648             : 
     649         568 :   this->add_elements(requested_boundary_ids,
     650             :                      boundary_mesh,
     651             :                      subdomains_relative_to,
     652             :                      store_parent_side_ids);
     653         568 : }
     654             : 
     655             : 
     656             : 
     657        2485 : void BoundaryInfo::add_elements(const std::set<boundary_id_type> & requested_boundary_ids,
     658             :                                 UnstructuredMesh & boundary_mesh,
     659             :                                 const std::set<subdomain_id_type> & subdomains_relative_to,
     660             :                                 bool store_parent_side_ids)
     661             : {
     662         140 :   LOG_SCOPE("add_elements()", "BoundaryInfo");
     663             : 
     664             :   // We're not prepared to mix serial and distributed meshes in this
     665             :   // method, so make sure their statuses match from the start.
     666             :   //
     667             :   // Specifically test *is_serial* here - we can handle a mix of
     668             :   // ReplicatedMesh and serialized DistributedMesh.
     669          70 :   libmesh_assert_equal_to(_mesh->is_serial(),
     670             :                           boundary_mesh.is_serial());
     671             : 
     672             :   // If the boundary mesh already has interior pointers pointing at
     673             :   // elements in a third mesh then we're in trouble
     674          70 :   libmesh_assert(&boundary_mesh.interior_mesh() == &boundary_mesh ||
     675             :                  &boundary_mesh.interior_mesh() == _mesh);
     676             : 
     677             :   // And now we're going to add interior pointers to elements from
     678             :   // this mesh
     679        2485 :   boundary_mesh.set_interior_mesh(*_mesh);
     680             : 
     681         140 :   std::map<std::pair<dof_id_type, unsigned char>, dof_id_type> side_id_map;
     682        2485 :   this->_find_id_maps(requested_boundary_ids,
     683             :                       0,
     684             :                       nullptr,
     685        2485 :                       boundary_mesh.max_elem_id(),
     686             :                       &side_id_map,
     687             :                       subdomains_relative_to);
     688             : 
     689             :   // We have to add sides *outside* any element loop, because if
     690             :   // boundary_mesh and _mesh are the same then those additions can
     691             :   // invalidate our element iterators.  So we just use the element
     692             :   // loop to make a list of sides to add.
     693             :   typedef std::vector<std::pair<dof_id_type, unsigned char>>
     694             :     side_container;
     695         140 :   side_container sides_to_add;
     696             : 
     697       99142 :   for (const auto & elem : _mesh->element_ptr_range())
     698             :     {
     699             :       // If the subdomains_relative_to container has the
     700             :       // invalid_subdomain_id, we fall back on the "old" behavior of
     701             :       // adding sides regardless of this Elem's subdomain. Otherwise,
     702             :       // if the subdomains_relative_to container doesn't contain the
     703             :       // current Elem's subdomain_id(), we won't add any sides from
     704             :       // it.
     705        8168 :       if (!subdomains_relative_to.count(Elem::invalid_subdomain_id) &&
     706        7232 :           !subdomains_relative_to.count(elem->subdomain_id()))
     707        5212 :         continue;
     708             : 
     709      216197 :       for (auto s : elem->side_index_range())
     710             :         {
     711        9496 :           bool add_this_side = false;
     712             : 
     713             :           // Find all the boundary side ids for this Elem side.
     714       18992 :           std::vector<boundary_id_type> bcids;
     715      169416 :           this->boundary_ids(elem, s, bcids);
     716             : 
     717      196841 :           for (const boundary_id_type bcid : bcids)
     718             :             {
     719             :               // if the user wants this id, we want this side
     720        2236 :               if (requested_boundary_ids.count(bcid))
     721             :                 {
     722         892 :                   add_this_side = true;
     723         892 :                   break;
     724             :                 }
     725             :             }
     726             : 
     727             :           // We may still want to add this side if the user called
     728             :           // sync() with no requested_boundary_ids. This corresponds
     729             :           // to the "old" style of calling sync() in which the entire
     730             :           // boundary was copied to the BoundaryMesh, and handles the
     731             :           // case where elements on the geometric boundary are not in
     732             :           // any sidesets.
     733        9496 :           if (requested_boundary_ids.count(invalid_id) &&
     734           0 :               elem->neighbor_ptr(s) == nullptr)
     735           0 :             add_this_side = true;
     736             : 
     737      169416 :           if (add_this_side)
     738       14824 :             sides_to_add.emplace_back(elem->id(), s);
     739             :         }
     740        2345 :     }
     741             : 
     742             : #ifdef LIBMESH_ENABLE_UNIQUE_ID
     743        2485 :   unique_id_type old_max_unique_id = boundary_mesh.parallel_max_unique_id();
     744             : #endif
     745             : 
     746             :   // Add an "extra" integer for storing the side index of the parent
     747             :   // Elem which each boundary Elem corresponds to. We do this once
     748             :   // before any Elems have been added.
     749        2485 :   unsigned int parent_side_index_tag = store_parent_side_ids ?
     750        4348 :     boundary_mesh.add_elem_integer("parent_side_index") : libMesh::invalid_uint;
     751             : 
     752       17309 :   for (const auto & [elem_id, s] : sides_to_add)
     753             :     {
     754       14824 :       Elem * elem = _mesh->elem_ptr(elem_id);
     755             : 
     756       15716 :       std::unique_ptr<Elem> side = elem->build_side_ptr(s);
     757             : 
     758       15716 :       side->processor_id() = elem->processor_id();
     759             : 
     760         892 :       const std::pair<dof_id_type, unsigned char> side_pair(elem_id, s);
     761             : 
     762         892 :       libmesh_assert(side_id_map.count(side_pair));
     763             : 
     764       14824 :       const dof_id_type new_side_id = side_id_map[side_pair];
     765             : 
     766         892 :       side->set_id(new_side_id);
     767             : 
     768             : #ifdef LIBMESH_ENABLE_UNIQUE_ID
     769       14824 :       side->set_unique_id(old_max_unique_id + new_side_id);
     770             : #endif
     771             : 
     772             :       // Add the side
     773       15716 :       Elem * new_elem = boundary_mesh.add_elem(std::move(side));
     774             : 
     775             :       // If requested, new_elem gets an "extra" integer equal to the
     776             :       // side id "s" of the interior_parent it corresponds to.
     777       14824 :       if (store_parent_side_ids)
     778       13040 :         new_elem->set_extra_integer(parent_side_index_tag, s);
     779             : 
     780             : #ifdef LIBMESH_ENABLE_AMR
     781        1784 :       new_elem->set_refinement_flag(elem->refinement_flag());
     782        1784 :       new_elem->set_p_refinement_flag(elem->p_refinement_flag());
     783             : 
     784             :       // Set parent links
     785       15716 :       if (elem->parent())
     786             :         {
     787         672 :           const std::pair<dof_id_type, unsigned char> parent_side_pair(elem->parent()->id(), s);
     788             : 
     789         336 :           libmesh_assert(side_id_map.count(parent_side_pair));
     790             : 
     791        4432 :           Elem * side_parent = boundary_mesh.elem_ptr(side_id_map[parent_side_pair]);
     792             : 
     793         336 :           libmesh_assert(side_parent);
     794             : 
     795         672 :           new_elem->set_parent(side_parent);
     796             : 
     797             :           // Figuring out which child we are of our parent
     798             :           // is a trick.  Due to libMesh child numbering
     799             :           // conventions, if we are an element on a vertex,
     800             :           // then we share that vertex with our parent, with
     801             :           // the same local index.
     802         336 :           bool found_child = false;
     803       13296 :           for (auto v : make_range(new_elem->n_vertices()))
     804       10208 :             if (new_elem->node_ptr(v) == side_parent->node_ptr(v))
     805             :               {
     806        4432 :                 side_parent->add_child(new_elem, v);
     807         336 :                 found_child = true;
     808             :               }
     809             : 
     810             :           // If we don't share any vertex with our parent,
     811             :           // then we're the fourth child (index 3) of a
     812             :           // triangle.
     813        4432 :           if (!found_child)
     814             :             {
     815           0 :               libmesh_assert_equal_to (new_elem->n_vertices(), 3);
     816           0 :               side_parent->add_child(new_elem, 3);
     817             :             }
     818             :         }
     819             : 
     820             :       // Set remote_elem child links if necessary.  Rather than
     821             :       // worrying about which interior child corresponds to which side
     822             :       // child we'll just set all null links to be remote and we'll
     823             :       // rely on our detection of actual semilocal children to
     824             :       // overwrite the links that shouldn't be remote.
     825         892 :       if (elem->has_children())
     826       16380 :         for (auto c : make_range(elem->n_children()))
     827       16486 :           if (elem->child_ptr(c) == remote_elem &&
     828        3382 :               elem->is_child_on_side(c, s))
     829             :             {
     830        6360 :               for (auto sc : make_range(new_elem->n_children()))
     831        2880 :                 if (!new_elem->raw_child_ptr(sc))
     832             :                   new_elem->add_child
     833        2720 :                     (const_cast<RemoteElem*>(remote_elem), sc);
     834             :             }
     835             : #endif
     836             : 
     837       14824 :       new_elem->set_interior_parent (elem);
     838             : 
     839             :       // On non-local elements on DistributedMesh we might have
     840             :       // RemoteElem neighbor links to construct
     841       14824 :       if (!_mesh->is_serial() &&
     842        8374 :           (elem->processor_id() != this->processor_id()))
     843             :         {
     844        5222 :           const unsigned short n_nodes = elem->n_nodes();
     845             : 
     846        5222 :           const unsigned short bdy_n_sides = new_elem->n_sides();
     847        5222 :           const unsigned short bdy_n_nodes = new_elem->n_nodes();
     848             : 
     849             :           // Check every interior side for a RemoteElem
     850       26054 :           for (auto interior_side : elem->side_index_range())
     851             :             {
     852             :               // Might this interior side have a RemoteElem that
     853             :               // needs a corresponding Remote on a boundary side?
     854       20832 :               if (elem->neighbor_ptr(interior_side) != remote_elem)
     855       16999 :                 continue;
     856             : 
     857             :               // Which boundary side?
     858        2944 :               for (unsigned short boundary_side = 0;
     859        6777 :                    boundary_side != bdy_n_sides; ++boundary_side)
     860             :                 {
     861             :                   // Look for matching node points.  This is safe in
     862             :                   // *this* context.
     863           0 :                   bool found_all_nodes = true;
     864        9963 :                   for (unsigned short boundary_node = 0;
     865       15972 :                        boundary_node != bdy_n_nodes; ++boundary_node)
     866             :                     {
     867       12907 :                       if (!new_elem->is_node_on_side(boundary_node,
     868           0 :                                                      boundary_side))
     869        6898 :                         continue;
     870             : 
     871           0 :                       bool found_this_node = false;
     872       31658 :                       for (unsigned short interior_node = 0;
     873       37667 :                            interior_node != n_nodes; ++interior_node)
     874             :                         {
     875       34723 :                           if (!elem->is_node_on_side(interior_node,
     876           0 :                                                      interior_side))
     877       21227 :                             continue;
     878             : 
     879           0 :                           if (new_elem->point(boundary_node) ==
     880           0 :                               elem->point(interior_node))
     881             :                             {
     882           0 :                               found_this_node = true;
     883           0 :                               break;
     884             :                             }
     885             :                         }
     886        6009 :                       if (!found_this_node)
     887             :                         {
     888           0 :                           found_all_nodes = false;
     889           0 :                           break;
     890             :                         }
     891             :                     }
     892             : 
     893        6009 :                   if (found_all_nodes)
     894             :                     {
     895             :                       new_elem->set_neighbor
     896        3065 :                         (boundary_side,
     897             :                          const_cast<RemoteElem *>(remote_elem));
     898           0 :                       break;
     899             :                     }
     900             :                 }
     901             :             }
     902             :         }
     903       13040 :     }
     904             : 
     905             :   // We haven't been bothering to keep unique ids consistent on ghost
     906             :   // elements or nodes, unless we're doing everything the same on
     907             :   // every processor.
     908        2485 :   if (!boundary_mesh.is_replicated())
     909        1841 :     MeshCommunication().make_node_unique_ids_parallel_consistent(boundary_mesh);
     910             : 
     911             :   // Make sure we didn't add ids inconsistently
     912             : #ifdef DEBUG
     913             : # ifdef LIBMESH_HAVE_RTTI
     914          70 :   DistributedMesh * parmesh = dynamic_cast<DistributedMesh *>(&boundary_mesh);
     915          70 :   if (parmesh)
     916          14 :     parmesh->libmesh_assert_valid_parallel_ids();
     917             : # endif
     918             : #endif
     919        2485 : }
     920             : 
     921             : 
     922             : 
     923      280626 : void BoundaryInfo::add_node(const dof_id_type node_id,
     924             :                             const boundary_id_type id)
     925             : {
     926      280626 :   const Node * node_ptr = _mesh->query_node_ptr(node_id);
     927             : 
     928             :   // The user could easily ask for an invalid node id, so let's throw
     929             :   // an easy-to-understand error message when this happens.
     930      280626 :   libmesh_error_msg_if(!node_ptr,
     931             :                        "BoundaryInfo::add_node(): Could not retrieve pointer for node "
     932             :                        << node_id
     933             :                        << ", no boundary id was added.");
     934             : 
     935      280626 :   this->add_node (node_ptr, id);
     936      280626 : }
     937             : 
     938             : 
     939             : 
     940    38704954 : void BoundaryInfo::add_node(const Node * node,
     941             :                             const boundary_id_type id)
     942             : {
     943    38704954 :   libmesh_error_msg_if(id == invalid_id,
     944             :                        "ERROR: You may not set a boundary ID of "
     945             :                        << invalid_id
     946             :                        << "\n That is reserved for internal use.");
     947             : 
     948             :   // Don't add the same ID twice
     949    57341354 :   for (const auto & pr : as_range(_boundary_node_id.equal_range(node)))
     950    40409105 :     if (pr.second == id)
     951        8696 :       return;
     952             : 
     953      452901 :   _boundary_node_id.emplace(node, id);
     954    16479348 :   _boundary_ids.insert(id);
     955    16479348 :   _node_boundary_ids.insert(id); // Also add this ID to the set of node boundary IDs
     956             : }
     957             : 
     958             : 
     959             : 
     960     6081933 : void BoundaryInfo::add_node(const Node * node,
     961             :                             const std::vector<boundary_id_type> & ids)
     962             : {
     963     6081933 :   if (ids.empty())
     964     5888776 :     return;
     965             : 
     966        1512 :   libmesh_assert(node);
     967             : 
     968             :   // Don't add the same ID twice
     969        1512 :   auto bounds = _boundary_node_id.equal_range(node);
     970             : 
     971             :   // The entries in the ids vector may be non-unique.  If we expected
     972             :   // *lots* of ids, it might be fastest to construct a std::set from
     973             :   // the entries, but for a small number of entries, which is more
     974             :   // typical, it is probably faster to copy the vector and do sort+unique.
     975             :   // http://stackoverflow.com/questions/1041620/whats-the-most-efficient-way-to-erase-duplicates-and-sort-a-vector
     976      194669 :   std::vector<boundary_id_type> unique_ids(ids.begin(), ids.end());
     977      193157 :   std::sort(unique_ids.begin(), unique_ids.end());
     978             :   std::vector<boundary_id_type>::iterator new_end =
     979      193157 :     std::unique(unique_ids.begin(), unique_ids.end());
     980             : 
     981      480786 :   for (auto & id : as_range(unique_ids.begin(), new_end))
     982             :     {
     983      287629 :       libmesh_error_msg_if(id == invalid_id,
     984             :                            "ERROR: You may not set a boundary ID of "
     985             :                            << invalid_id
     986             :                            << "\n That is reserved for internal use.");
     987             : 
     988        2400 :       bool already_inserted = false;
     989      460475 :       for (const auto & pr : as_range(bounds))
     990      423413 :         if (pr.second == id)
     991             :           {
     992        1356 :             already_inserted = true;
     993        1356 :             break;
     994             :           }
     995      287629 :       if (already_inserted)
     996      249211 :         continue;
     997             : 
     998        1044 :       _boundary_node_id.emplace(node, id);
     999       36018 :       _boundary_ids.insert(id);
    1000       36018 :       _node_boundary_ids.insert(id); // Also add this ID to the set of node boundary IDs
    1001             :     }
    1002             : }
    1003             : 
    1004             : 
    1005             : 
    1006           0 : void BoundaryInfo::clear_boundary_node_ids()
    1007             : {
    1008           0 :   _boundary_node_id.clear();
    1009           0 : }
    1010             : 
    1011       40336 : void BoundaryInfo::add_edge(const dof_id_type e,
    1012             :                             const unsigned short int edge,
    1013             :                             const boundary_id_type id)
    1014             : {
    1015       40336 :   this->add_edge (_mesh->elem_ptr(e), edge, id);
    1016       40336 : }
    1017             : 
    1018             : 
    1019             : 
    1020       44501 : void BoundaryInfo::add_edge(const Elem * elem,
    1021             :                             const unsigned short int edge,
    1022             :                             const boundary_id_type id)
    1023             : {
    1024        1282 :   libmesh_assert(elem);
    1025             : 
    1026             :   // Only add BCs for level-0 elements.
    1027        1282 :   libmesh_assert_equal_to (elem->level(), 0);
    1028             : 
    1029             :   // Only add BCs for edges that exist.
    1030        1282 :   libmesh_assert_less (edge, elem->n_edges());
    1031             : 
    1032       44501 :   libmesh_error_msg_if(id == invalid_id,
    1033             :                        "ERROR: You may not set a boundary ID of "
    1034             :                        << invalid_id
    1035             :                        << "\n That is reserved for internal use.");
    1036             : 
    1037             :   // Don't add the same ID twice
    1038      107407 :   for (const auto & pr : as_range(_boundary_edge_id.equal_range(elem)))
    1039       74506 :     if (pr.second.first == edge &&
    1040       11600 :         pr.second.second == id)
    1041         320 :       return;
    1042             : 
    1043      227211 :   _boundary_edge_id.emplace(elem, std::make_pair(edge, id));
    1044       31939 :   _boundary_ids.insert(id);
    1045       31939 :   _edge_boundary_ids.insert(id); // Also add this ID to the set of edge boundary IDs
    1046             : }
    1047             : 
    1048             : 
    1049             : 
    1050    21898445 : void BoundaryInfo::add_edge(const Elem * elem,
    1051             :                             const unsigned short int edge,
    1052             :                             const std::vector<boundary_id_type> & ids)
    1053             : {
    1054    21898445 :   if (ids.empty())
    1055    21898445 :     return;
    1056             : 
    1057           0 :   libmesh_assert(elem);
    1058             : 
    1059             :   // Only add BCs for level-0 elements.
    1060           0 :   libmesh_assert_equal_to (elem->level(), 0);
    1061             : 
    1062             :   // Only add BCs for edges that exist.
    1063           0 :   libmesh_assert_less (edge, elem->n_edges());
    1064             : 
    1065             :   // Don't add the same ID twice
    1066           0 :   auto bounds = _boundary_edge_id.equal_range(elem);
    1067             : 
    1068             :   // The entries in the ids vector may be non-unique.  If we expected
    1069             :   // *lots* of ids, it might be fastest to construct a std::set from
    1070             :   // the entries, but for a small number of entries, which is more
    1071             :   // typical, it is probably faster to copy the vector and do sort+unique.
    1072             :   // http://stackoverflow.com/questions/1041620/whats-the-most-efficient-way-to-erase-duplicates-and-sort-a-vector
    1073           0 :   std::vector<boundary_id_type> unique_ids(ids.begin(), ids.end());
    1074           0 :   std::sort(unique_ids.begin(), unique_ids.end());
    1075             :   std::vector<boundary_id_type>::iterator new_end =
    1076           0 :     std::unique(unique_ids.begin(), unique_ids.end());
    1077             : 
    1078           0 :   for (auto & id : as_range(unique_ids.begin(), new_end))
    1079             :     {
    1080           0 :       libmesh_error_msg_if(id == invalid_id,
    1081             :                            "ERROR: You may not set a boundary ID of "
    1082             :                            << invalid_id
    1083             :                            << "\n That is reserved for internal use.");
    1084             : 
    1085           0 :       bool already_inserted = false;
    1086           0 :       for (const auto & pr : as_range(bounds))
    1087           0 :         if (pr.second.first == edge &&
    1088           0 :             pr.second.second == id)
    1089             :           {
    1090           0 :             already_inserted = true;
    1091           0 :             break;
    1092             :           }
    1093           0 :       if (already_inserted)
    1094           0 :         continue;
    1095             : 
    1096           0 :       _boundary_edge_id.emplace(elem, std::make_pair(edge, id));
    1097           0 :       _boundary_ids.insert(id);
    1098           0 :       _edge_boundary_ids.insert(id); // Also add this ID to the set of edge boundary IDs
    1099             :     }
    1100             : }
    1101             : 
    1102             : 
    1103             : 
    1104       39936 : void BoundaryInfo::add_shellface(const dof_id_type e,
    1105             :                                  const unsigned short int shellface,
    1106             :                                  const boundary_id_type id)
    1107             : {
    1108       39936 :   this->add_shellface (_mesh->elem_ptr(e), shellface, id);
    1109       39936 : }
    1110             : 
    1111             : 
    1112             : 
    1113      537274 : void BoundaryInfo::add_shellface(const Elem * elem,
    1114             :                                  const unsigned short int shellface,
    1115             :                                  const boundary_id_type id)
    1116             : {
    1117       13316 :   libmesh_assert(elem);
    1118             : 
    1119             :   // Only add BCs for level-0 elements.
    1120       13316 :   libmesh_assert_equal_to (elem->level(), 0);
    1121             : 
    1122             :   // Shells only have 2 faces
    1123       13316 :   libmesh_assert_less(shellface, 2);
    1124             : 
    1125      537274 :   libmesh_error_msg_if(id == invalid_id,
    1126             :                        "ERROR: You may not set a boundary ID of "
    1127             :                        << invalid_id
    1128             :                        << "\n That is reserved for internal use.");
    1129             : 
    1130             :   // Don't add the same ID twice
    1131      805840 :   for (const auto & pr : as_range(_boundary_shellface_id.equal_range(elem)))
    1132      314230 :     if (pr.second.first == shellface &&
    1133       45664 :         pr.second.second == id)
    1134           0 :       return;
    1135             : 
    1136      491610 :   _boundary_shellface_id.emplace(elem, std::make_pair(shellface, id));
    1137      478294 :   _boundary_ids.insert(id);
    1138      478294 :   _shellface_boundary_ids.insert(id); // Also add this ID to the set of shellface boundary IDs
    1139             : }
    1140             : 
    1141             : 
    1142             : 
    1143     7244308 : void BoundaryInfo::add_shellface(const Elem * elem,
    1144             :                                  const unsigned short int shellface,
    1145             :                                  const std::vector<boundary_id_type> & ids)
    1146             : {
    1147     7244308 :   if (ids.empty())
    1148     7244308 :     return;
    1149             : 
    1150           0 :   libmesh_assert(elem);
    1151             : 
    1152             :   // Only add BCs for level-0 elements.
    1153           0 :   libmesh_assert_equal_to (elem->level(), 0);
    1154             : 
    1155             :   // Shells only have 2 faces
    1156           0 :   libmesh_assert_less(shellface, 2);
    1157             : 
    1158             :   // Don't add the same ID twice
    1159           0 :   auto bounds = _boundary_shellface_id.equal_range(elem);
    1160             : 
    1161             :   // The entries in the ids vector may be non-unique.  If we expected
    1162             :   // *lots* of ids, it might be fastest to construct a std::set from
    1163             :   // the entries, but for a small number of entries, which is more
    1164             :   // typical, it is probably faster to copy the vector and do sort+unique.
    1165             :   // http://stackoverflow.com/questions/1041620/whats-the-most-efficient-way-to-erase-duplicates-and-sort-a-vector
    1166           0 :   std::vector<boundary_id_type> unique_ids(ids.begin(), ids.end());
    1167           0 :   std::sort(unique_ids.begin(), unique_ids.end());
    1168             :   std::vector<boundary_id_type>::iterator new_end =
    1169           0 :     std::unique(unique_ids.begin(), unique_ids.end());
    1170             : 
    1171           0 :   for (auto & id : as_range(unique_ids.begin(), new_end))
    1172             :     {
    1173           0 :       libmesh_error_msg_if(id == invalid_id,
    1174             :                            "ERROR: You may not set a boundary ID of "
    1175             :                            << invalid_id
    1176             :                            << "\n That is reserved for internal use.");
    1177             : 
    1178           0 :       bool already_inserted = false;
    1179           0 :       for (const auto & pr : as_range(bounds))
    1180           0 :         if (pr.second.first == shellface &&
    1181           0 :             pr.second.second == id)
    1182             :           {
    1183           0 :             already_inserted = true;
    1184           0 :             break;
    1185             :           }
    1186           0 :       if (already_inserted)
    1187           0 :         continue;
    1188             : 
    1189           0 :       _boundary_shellface_id.emplace(elem, std::make_pair(shellface, id));
    1190           0 :       _boundary_ids.insert(id);
    1191           0 :       _shellface_boundary_ids.insert(id); // Also add this ID to the set of shellface boundary IDs
    1192             :     }
    1193             : }
    1194             : 
    1195             : 
    1196      100366 : void BoundaryInfo::add_side(const dof_id_type e,
    1197             :                             const unsigned short int side,
    1198             :                             const boundary_id_type id)
    1199             : {
    1200      100366 :   this->add_side (_mesh->elem_ptr(e), side, id);
    1201      100366 : }
    1202             : 
    1203             : 
    1204             : 
    1205    18672713 : void BoundaryInfo::add_side(const Elem * elem,
    1206             :                             const unsigned short int side,
    1207             :                             const boundary_id_type id)
    1208             : {
    1209      183520 :   libmesh_assert(elem);
    1210             : 
    1211             :   // Only add BCs for sides that exist.
    1212      183520 :   libmesh_assert_less (side, elem->n_sides());
    1213             : 
    1214    18672713 :   libmesh_error_msg_if(id == invalid_id, "ERROR: You may not set a boundary ID of "
    1215             :                        << invalid_id
    1216             :                        << "\n That is reserved for internal use.");
    1217             : 
    1218             :   // Don't add the same ID twice
    1219    23950070 :   for (const auto & pr : as_range(_boundary_side_id.equal_range(elem)))
    1220    16926326 :     if (pr.second.first == side &&
    1221    11649280 :         pr.second.second == id)
    1222        3486 :       return;
    1223             : 
    1224             : #ifdef LIBMESH_ENABLE_AMR
    1225             :   // Users try to mark boundary on child elements
    1226             :   // If this happens, we will allow users to remove
    1227             :   // side from child elements as well
    1228     7023744 :   if (elem->level())
    1229             :   {
    1230         632 :     _children_on_boundary = true;
    1231             : 
    1232             :     // Here we have to stop and check if we already have this boundary defined on the
    1233             :     // parent (if yes, no need to add)
    1234          32 :     std::vector<boundary_id_type> bd_ids;
    1235         632 :     this->boundary_ids(elem,side,bd_ids);
    1236             : 
    1237         632 :     if(std::find(bd_ids.begin(), bd_ids.end(), id) != bd_ids.end())
    1238         167 :       libmesh_not_implemented_msg("Trying to add boundary ID "
    1239             :                                   + std::to_string(id)
    1240             :                                   + " which already exists on the ancestors.");
    1241             :   }
    1242             : #endif
    1243             : 
    1244     7023709 :   _boundary_side_id.emplace(elem, std::make_pair(side, id));
    1245     6843677 :   _boundary_ids.insert(id);
    1246     6843677 :   _side_boundary_ids.insert(id); // Also add this ID to the set of side boundary IDs
    1247             : }
    1248             : 
    1249             : 
    1250             : 
    1251    14892988 : void BoundaryInfo::add_side(const Elem * elem,
    1252             :                             const unsigned short int side,
    1253             :                             const std::vector<boundary_id_type> & ids)
    1254             : {
    1255    14892988 :   if (ids.empty())
    1256    13449907 :     return;
    1257             : 
    1258       43680 :   libmesh_assert(elem);
    1259             : 
    1260             :   // Only add BCs for sides that exist.
    1261       43680 :   libmesh_assert_less (side, elem->n_sides());
    1262             : 
    1263             : #ifdef LIBMESH_ENABLE_AMR
    1264             :   // Users try to mark boundary on child elements
    1265             :   // If this happens, we will allow users to remove
    1266             :   // side from child elements as well
    1267     1443081 :   if (elem->level())
    1268             :   {
    1269          35 :     _children_on_boundary = true;
    1270             : 
    1271             :     // Here we have to stop and check if we already have this boundary defined on the
    1272             :     // parent (if yes, no need to add)
    1273           4 :     std::vector<boundary_id_type> bd_ids;
    1274          35 :     this->boundary_ids(elem,side,bd_ids);
    1275             : 
    1276          35 :     for (const auto id : ids)
    1277          35 :       if(std::find(bd_ids.begin(), bd_ids.end(), id) != bd_ids.end())
    1278         167 :         libmesh_not_implemented_msg("Trying to add boundary ID "
    1279             :                                     + std::to_string(id)
    1280             :                                     + " which already exists on the ancestors.");
    1281             :   }
    1282             : #endif
    1283             : 
    1284             :   // Don't add the same ID twice
    1285       43678 :   auto bounds = _boundary_side_id.equal_range(elem);
    1286             : 
    1287             :   // The entries in the ids vector may be non-unique.  If we expected
    1288             :   // *lots* of ids, it might be fastest to construct a std::set from
    1289             :   // the entries, but for a small number of entries, which is more
    1290             :   // typical, it is probably faster to copy the vector and do sort+unique.
    1291             :   // http://stackoverflow.com/questions/1041620/whats-the-most-efficient-way-to-erase-duplicates-and-sort-a-vector
    1292     1486724 :   std::vector<boundary_id_type> unique_ids(ids.begin(), ids.end());
    1293     1443046 :   std::sort(unique_ids.begin(), unique_ids.end());
    1294             :   std::vector<boundary_id_type>::iterator new_end =
    1295     1443046 :     std::unique(unique_ids.begin(), unique_ids.end());
    1296             : 
    1297     2886092 :   for (auto & id : as_range(unique_ids.begin(), new_end))
    1298             :     {
    1299     1443046 :       libmesh_error_msg_if(id == invalid_id,
    1300             :                            "ERROR: You may not set a boundary ID of "
    1301             :                            << invalid_id
    1302             :                            << "\n That is reserved for internal use.");
    1303             : 
    1304       43678 :       bool already_inserted = false;
    1305     1490113 :       for (const auto & pr : as_range(bounds))
    1306       47067 :         if (pr.second.first == side && pr.second.second == id)
    1307             :           {
    1308           0 :             already_inserted = true;
    1309           0 :             break;
    1310             :           }
    1311     1443046 :       if (already_inserted)
    1312           0 :         continue;
    1313             : 
    1314     1443046 :       _boundary_side_id.emplace(elem, std::make_pair(side, id));
    1315     1399368 :       _boundary_ids.insert(id);
    1316     1399368 :       _side_boundary_ids.insert(id); // Also add this ID to the set of side boundary IDs
    1317             :     }
    1318             : }
    1319             : 
    1320             : 
    1321             : 
    1322      573511 : bool BoundaryInfo::has_boundary_id(const Node * const node,
    1323             :                                    const boundary_id_type id) const
    1324             : {
    1325      841633 :   for (const auto & pr : as_range(_boundary_node_id.equal_range(node)))
    1326      841598 :     if (pr.second == id)
    1327       33700 :       return true;
    1328             : 
    1329           2 :   return false;
    1330             : }
    1331             : 
    1332             : 
    1333             : 
    1334   108640972 : void BoundaryInfo::boundary_ids (const Node * node,
    1335             :                                  std::vector<boundary_id_type> & vec_to_fill) const
    1336             : {
    1337             :   // Clear out any previous contents
    1338    29847138 :   vec_to_fill.clear();
    1339             : 
    1340   123448829 :   for (const auto & pr : as_range(_boundary_node_id.equal_range(node)))
    1341    14807857 :     vec_to_fill.push_back(pr.second);
    1342   108640972 : }
    1343             : 
    1344             : 
    1345             : 
    1346    65453702 : unsigned int BoundaryInfo::n_boundary_ids(const Node * node) const
    1347             : {
    1348      143736 :   auto pos = _boundary_node_id.equal_range(node);
    1349    65597438 :   return cast_int<unsigned int>(std::distance(pos.first, pos.second));
    1350             : }
    1351             : 
    1352             : 
    1353             : 
    1354   178460140 : void BoundaryInfo::edge_boundary_ids (const Elem * const elem,
    1355             :                                       const unsigned short int edge,
    1356             :                                       std::vector<boundary_id_type> & vec_to_fill) const
    1357             : {
    1358    24823255 :   libmesh_assert(elem);
    1359             : 
    1360             :   // Clear out any previous contents
    1361    24823255 :   vec_to_fill.clear();
    1362             : 
    1363             :   // Only query BCs for edges that exist.
    1364    24823255 :   libmesh_assert_less (edge, elem->n_edges());
    1365             : 
    1366             :   // Only level-0 elements store BCs.  If this is not a level-0
    1367             :   // element get its level-0 parent and infer the BCs.
    1368   178460140 :   const Elem * searched_elem = elem;
    1369             : #ifdef LIBMESH_ENABLE_AMR
    1370   178460140 :   if (elem->level() != 0)
    1371             :     {
    1372             :       // Find all the sides that contain edge. If one of those is a boundary
    1373             :       // side, then this must be a boundary edge. In that case, we just use the
    1374             :       // top-level parent.
    1375    10574109 :       bool found_boundary_edge = false;
    1376    70456278 :       for (auto side : elem->side_index_range())
    1377             :         {
    1378    57707285 :           if (elem->is_edge_on_side(edge,side))
    1379             :             {
    1380    18732023 :               if (elem->neighbor_ptr(side) == nullptr)
    1381             :                 {
    1382      885223 :                   searched_elem = elem->top_parent ();
    1383      666068 :                   found_boundary_edge = true;
    1384      666068 :                   break;
    1385             :                 }
    1386             :             }
    1387             :         }
    1388             : 
    1389    10574109 :       if (!found_boundary_edge)
    1390             :         {
    1391             :           // Child element is not on external edge, but it may have internal
    1392             :           // "boundary" IDs.  We will walk up the tree, at each level checking that
    1393             :           // the current child is actually on the same edge of the parent that is
    1394             :           // currently being searched for (i.e. that was passed in as "edge").
    1395    21279004 :           while (searched_elem->parent() != nullptr)
    1396             :             {
    1397    15498104 :               const Elem * parent = searched_elem->parent();
    1398    19912961 :               if (parent->is_child_on_edge(parent->which_child_am_i(searched_elem), edge) == false)
    1399    11818714 :                 return;
    1400     8094247 :               searched_elem = parent;
    1401             :             }
    1402             :         }
    1403             :     }
    1404             : #endif
    1405             : 
    1406             :   // Check each element in the range to see if its edge matches the requested edge.
    1407   166665658 :   for (const auto & pr : as_range(_boundary_edge_id.equal_range(searched_elem)))
    1408       24232 :     if (pr.second.first == edge)
    1409        2474 :       vec_to_fill.push_back(pr.second.second);
    1410             : }
    1411             : 
    1412             : 
    1413             : 
    1414    73741550 : unsigned int BoundaryInfo::n_edge_boundary_ids (const Elem * const elem,
    1415             :                                                 const unsigned short int edge) const
    1416             : {
    1417     1170932 :   std::vector<boundary_id_type> ids;
    1418    73741550 :   this->edge_boundary_ids(elem, edge, ids);
    1419    74327064 :   return cast_int<unsigned int>(ids.size());
    1420             : }
    1421             : 
    1422             : 
    1423             : 
    1424    45209273 : void BoundaryInfo::raw_edge_boundary_ids (const Elem * const elem,
    1425             :                                           const unsigned short int edge,
    1426             :                                           std::vector<boundary_id_type> & vec_to_fill) const
    1427             : {
    1428    24018666 :   libmesh_assert(elem);
    1429             : 
    1430             :   // Only query BCs for edges that exist.
    1431    24018666 :   libmesh_assert_less (edge, elem->n_edges());
    1432             : 
    1433             :   // Clear out any previous contents
    1434    24018666 :   vec_to_fill.clear();
    1435             : 
    1436             :   // Only level-0 elements store BCs.
    1437    45917111 :   if (elem->parent())
    1438    10374712 :     return;
    1439             : 
    1440             :   // Check each element in the range to see if its edge matches the requested edge.
    1441    34080752 :   for (const auto & pr : as_range(_boundary_edge_id.equal_range(elem)))
    1442        3384 :     if (pr.second.first == edge)
    1443         282 :       vec_to_fill.push_back(pr.second.second);
    1444             : }
    1445             : 
    1446             : 
    1447             : 
    1448    56631182 : void BoundaryInfo::shellface_boundary_ids (const Elem * const elem,
    1449             :                                            const unsigned short int shellface,
    1450             :                                            std::vector<boundary_id_type> & vec_to_fill) const
    1451             : {
    1452     9116036 :   libmesh_assert(elem);
    1453             : 
    1454             :   // Shells only have 2 faces
    1455     9116036 :   libmesh_assert_less(shellface, 2);
    1456             : 
    1457             :   // Clear out any previous contents
    1458     9116036 :   vec_to_fill.clear();
    1459             : 
    1460             :   // Only level-0 elements store BCs.  If this is not a level-0
    1461             :   // element get its level-0 parent and infer the BCs.
    1462    56631182 :   const Elem * searched_elem = elem;
    1463             : #ifdef LIBMESH_ENABLE_AMR
    1464    56631182 :   if (elem->level() != 0)
    1465             :     {
    1466    26817954 :       while (searched_elem->parent() != nullptr)
    1467             :         {
    1468    16452306 :           const Elem * parent = searched_elem->parent();
    1469    20561258 :           searched_elem = parent;
    1470             :         }
    1471             :     }
    1472             : #endif
    1473             : 
    1474             :   // Check each element in the range to see if its shellface matches the requested shellface.
    1475    57175382 :   for (const auto & pr : as_range(_boundary_shellface_id.equal_range(searched_elem)))
    1476      544200 :     if (pr.second.first == shellface)
    1477      272196 :       vec_to_fill.push_back(pr.second.second);
    1478    56631182 : }
    1479             : 
    1480             : 
    1481             : 
    1482    22855146 : unsigned int BoundaryInfo::n_shellface_boundary_ids (const Elem * const elem,
    1483             :                                                      const unsigned short int shellface) const
    1484             : {
    1485      384144 :   std::vector<boundary_id_type> ids;
    1486    22855146 :   this->shellface_boundary_ids(elem, shellface, ids);
    1487    23050546 :   return cast_int<unsigned int>(ids.size());
    1488             : }
    1489             : 
    1490             : 
    1491             : 
    1492    15866834 : void BoundaryInfo::raw_shellface_boundary_ids (const Elem * const elem,
    1493             :                                                const unsigned short int shellface,
    1494             :                                                std::vector<boundary_id_type> & vec_to_fill) const
    1495             : {
    1496     8869714 :   libmesh_assert(elem);
    1497             : 
    1498             :   // Shells only have 2 faces
    1499     8869714 :   libmesh_assert_less(shellface, 2);
    1500             : 
    1501             :   // Clear out any previous contents
    1502     8869714 :   vec_to_fill.clear();
    1503             : 
    1504             :   // Only level-0 elements store BCs.
    1505    16113990 :   if (elem->parent())
    1506     4496320 :     return;
    1507             : 
    1508             :   // Check each element in the range to see if its shellface matches the requested shellface.
    1509    11022700 :   for (const auto & pr : as_range(_boundary_shellface_id.equal_range(elem)))
    1510       66584 :     if (pr.second.first == shellface)
    1511       33292 :       vec_to_fill.push_back(pr.second.second);
    1512             : }
    1513             : 
    1514             : 
    1515             : 
    1516     7052439 : bool BoundaryInfo::has_boundary_id(const Elem * const elem,
    1517             :                                    const unsigned short int side,
    1518             :                                    const boundary_id_type id) const
    1519             : {
    1520      533419 :   std::vector<boundary_id_type> ids;
    1521     7052439 :   this->boundary_ids(elem, side, ids);
    1522     7843695 :   return (std::find(ids.begin(), ids.end(), id) != ids.end());
    1523             : }
    1524             : 
    1525             : 
    1526          82 : void BoundaryInfo::side_boundary_ids (const Elem * const elem,
    1527             :                                       std::vector<std::vector<boundary_id_type>> & vec_to_fill) const
    1528             : {
    1529           5 :   libmesh_assert(elem);
    1530             : 
    1531             :   // Clear out any previous contents
    1532           5 :   vec_to_fill.clear();
    1533             :   // We are going to gather boundary ids for each side
    1534          82 :   vec_to_fill.resize(elem->n_sides());
    1535             : 
    1536             :   // In most cases only level-0 elements store BCs.
    1537             :   // In certain applications (such as time-dependent domains), however, children
    1538             :   // need to store BCs too. This case is covered with the _children_on_boundary
    1539             :   // flag.
    1540          82 :   const Elem * searched_elem = elem;
    1541             : 
    1542             : #ifdef LIBMESH_ENABLE_AMR
    1543          82 :   if (elem->level() != 0)
    1544             :   {
    1545             :     // If we have children on the boundaries, we need to search for boundary IDs on the
    1546             :     // child and its ancestors too if they share the side.
    1547          70 :     if (_children_on_boundary)
    1548             :     {
    1549             :       // Loop over ancestors to check if they have boundary ids on the same side
    1550          35 :       std::vector<bool> search_on_side(elem->n_sides(), true);
    1551           2 :       bool keep_searching = true;
    1552         105 :       while (searched_elem && keep_searching)
    1553             :       {
    1554         245 :         for (const auto & pr : as_range(_boundary_side_id.equal_range(searched_elem)))
    1555             :         {
    1556         875 :           for (const auto side : make_range(elem->n_sides()))
    1557             :             // Here we need to check if the boundary id already exists
    1558         752 :             if (search_on_side[side] && pr.second.first == side &&
    1559         672 :                 std::find(vec_to_fill[side].begin(), vec_to_fill[side].end(), pr.second.second) ==
    1560          52 :                           vec_to_fill[side].end())
    1561         111 :               vec_to_fill[side].push_back(pr.second.second);
    1562             :         }
    1563             : 
    1564           8 :         const Elem * parent = searched_elem->parent();
    1565          70 :         const auto child_index = parent ? parent->which_child_am_i(searched_elem) : libMesh::invalid_uint;
    1566         350 :         for (const auto side : make_range(elem->n_sides()))
    1567             :           // If the parent doesn't exist or if the child is not on the correct side of the
    1568             :           // parent we are done checking the ancestors
    1569         320 :           if (search_on_side[side] &&
    1570         156 :                 (!parent || parent->is_child_on_side(child_index, side) == false))
    1571          16 :             search_on_side[side] = false;
    1572             : 
    1573          70 :         searched_elem = parent;
    1574             :         // if found what we needed on all sides, exit
    1575          70 :         keep_searching = *std::max_element(search_on_side.begin(), search_on_side.end());
    1576             :       }
    1577             : 
    1578           2 :       return;
    1579             :     }
    1580             : 
    1581             :     // Children not on boundaries case.
    1582             :     // It could be that a children is interior to the parent (search_on_side = false will handle that)
    1583             :     // However, since no children on boundaries, we know that it's either the top parent or nothing
    1584          35 :     std::vector<bool> search_on_side(elem->n_sides(), true);
    1585         175 :     for (const auto side : make_range(elem->n_sides()))
    1586             :     {
    1587             :       // If we don't have children on boundaries and we are on an external boundary,
    1588             :       // we will just use the top parent. search_on_side[side] = true works
    1589         148 :       if (elem->neighbor_ptr(side) == nullptr)
    1590          66 :         continue;
    1591             :       // Otherwise we loop over the ancestors and check if they have a different BC for us
    1592             :       else
    1593         111 :         while (searched_elem->parent() != nullptr)
    1594             :         {
    1595           2 :           const Elem * parent = searched_elem->parent();
    1596          37 :           if (search_on_side[side] && parent->is_child_on_side(parent->which_child_am_i(searched_elem), side) == false)
    1597           4 :             search_on_side[side] = false;
    1598          35 :           searched_elem = parent;
    1599             :         }
    1600             :     }
    1601             :     // Now search on the top parent, only if we need to (element is not deep inside the top parent)
    1602          37 :     if (*std::max_element(search_on_side.begin(), search_on_side.end()))
    1603         175 :       for (const auto & pr : as_range(_boundary_side_id.equal_range(elem->top_parent())))
    1604         148 :         if (search_on_side[pr.second.first])
    1605         111 :           vec_to_fill[pr.second.first].push_back(pr.second.second);
    1606           2 :     return;
    1607             :   }
    1608             : #endif
    1609             : 
    1610             :   // Check each element in the range to see if its side matches the requested side.
    1611          72 :   for (const auto & pr : as_range(_boundary_side_id.equal_range(searched_elem)))
    1612          65 :     vec_to_fill[pr.second.first].push_back(pr.second.second);
    1613             : }
    1614             : 
    1615    35330884 : void BoundaryInfo::boundary_ids (const Elem * const elem,
    1616             :                                  const unsigned short int side,
    1617             :                                  std::vector<boundary_id_type> & vec_to_fill) const
    1618             : {
    1619    18652270 :   libmesh_assert(elem);
    1620             : 
    1621             :   // Only query BCs for sides that exist.
    1622    18652270 :   libmesh_assert_less (side, elem->n_sides());
    1623             : 
    1624             :   // Clear out any previous contents
    1625    18652270 :   vec_to_fill.clear();
    1626             : 
    1627             :   // In most cases only level-0 elements store BCs.
    1628             :   // In certain applications (such as time-dependent domains), however, children
    1629             :   // need to store BCs too. This case is covered with the _children_on_boundary
    1630             :   // flag.
    1631    35330884 :   const Elem * searched_elem = elem;
    1632             : 
    1633             : #ifdef LIBMESH_ENABLE_AMR
    1634             : 
    1635    35330884 :   if (elem->level() != 0)
    1636             :   {
    1637             :     // If we have children on the boundaries, we need to search for boundary IDs on the
    1638             :     // child and its ancestors too if they share the side.
    1639    12733056 :     if (_children_on_boundary)
    1640             :     {
    1641             :       // Loop over ancestors to check if they have boundary ids on the same side
    1642        2756 :       while (searched_elem)
    1643             :       {
    1644        6567 :         for (const auto & pr : as_range(_boundary_side_id.equal_range(searched_elem)))
    1645             :           // Here we need to check if the boundary id already exists
    1646        4247 :           if (pr.second.first == side &&
    1647        2565 :               std::find(vec_to_fill.begin(), vec_to_fill.end(), pr.second.second) ==
    1648        2100 :               vec_to_fill.end())
    1649         767 :             vec_to_fill.push_back(pr.second.second);
    1650             : 
    1651             : 
    1652        1368 :         const Elem * parent = searched_elem->parent();
    1653             :         // If the parent doesn't exist or if the child is not on the correct side of the
    1654             :         // parent we are done checking the ancestors
    1655        2756 :         if (!parent || parent->is_child_on_side(parent->which_child_am_i(searched_elem), side) == false)
    1656    10625545 :           return;
    1657             : 
    1658        1037 :         searched_elem = parent;
    1659             :       }
    1660             : 
    1661           0 :       return;
    1662             :     }
    1663             : 
    1664             :     // If we don't have children on boundaries and we are on an external boundary,
    1665             :     // we just look for the top parent
    1666    13064604 :     if (elem->neighbor_ptr(side) == nullptr)
    1667      624848 :       searched_elem = elem->top_parent();
    1668             :     // Otherwise we loop over the ancestors and check if they have a different BC for us
    1669             :     else
    1670    22057544 :       while (searched_elem->parent() != nullptr)
    1671             :       {
    1672    14543963 :         const Elem * parent = searched_elem->parent();
    1673    20074515 :         if (parent->is_child_on_side(parent->which_child_am_i(searched_elem), side) == false)
    1674     7673563 :           return;
    1675             : 
    1676     9450689 :         searched_elem = parent;
    1677             :       }
    1678             :   }
    1679             : 
    1680             : #endif
    1681             : 
    1682             :   // Check each element in the range to see if its side matches the requested side.
    1683    43694360 :   for (const auto & pr : as_range(_boundary_side_id.equal_range(searched_elem)))
    1684    18989021 :     if (pr.second.first == side)
    1685     6488436 :       vec_to_fill.push_back(pr.second.second);
    1686             : }
    1687             : 
    1688             : 
    1689             : 
    1690             : 
    1691      178338 : unsigned int BoundaryInfo::n_boundary_ids (const Elem * const elem,
    1692             :                                            const unsigned short int side) const
    1693             : {
    1694        9936 :   std::vector<boundary_id_type> ids;
    1695      178338 :   this->boundary_ids(elem, side, ids);
    1696      184162 :   return cast_int<unsigned int>(ids.size());
    1697             : }
    1698             : 
    1699             : 
    1700   183076152 : unsigned int BoundaryInfo::n_raw_boundary_ids (const Elem * const elem,
    1701             :                                                const unsigned short int side) const
    1702             : {
    1703     1275418 :   std::vector<boundary_id_type> ids;
    1704   183076152 :   this->raw_boundary_ids(elem, side, ids);
    1705   183729687 :   return cast_int<unsigned int>(ids.size());
    1706             : }
    1707             : 
    1708             : 
    1709             : 
    1710   236062474 : void BoundaryInfo::raw_boundary_ids (const Elem * const elem,
    1711             :                                      const unsigned short int side,
    1712             :                                      std::vector<boundary_id_type> & vec_to_fill) const
    1713             : {
    1714    18498839 :   libmesh_assert(elem);
    1715             : 
    1716             :   // Only query BCs for sides that exist.
    1717    18498839 :   libmesh_assert_less (side, elem->n_sides());
    1718             : 
    1719             :   // Clear out any previous contents
    1720    18498839 :   vec_to_fill.clear();
    1721             : 
    1722             :   // Only level-0 elements store BCs.
    1723   237002671 :   if (elem->parent() && !_children_on_boundary)
    1724     8829840 :     return;
    1725             : 
    1726             :   // Check each element in the range to see if its side matches the requested side.
    1727   205532330 :   for (const auto & pr : as_range(_boundary_side_id.equal_range(elem)))
    1728    72091293 :     if (pr.second.first == side)
    1729    22759530 :       vec_to_fill.push_back(pr.second.second);
    1730             : }
    1731             : 
    1732             : 
    1733             : 
    1734     3622154 : void BoundaryInfo::copy_boundary_ids (const BoundaryInfo & old_boundary_info,
    1735             :                                       const Elem * const old_elem,
    1736             :                                       const Elem * const new_elem)
    1737             : {
    1738      123594 :   libmesh_assert_equal_to (old_elem->n_sides(), new_elem->n_sides());
    1739      123594 :   libmesh_assert_equal_to (old_elem->n_edges(), new_elem->n_edges());
    1740             : 
    1741      247188 :   std::vector<boundary_id_type> bndry_ids;
    1742             : 
    1743    18392387 :   for (auto s : old_elem->side_index_range())
    1744             :     {
    1745    14770233 :       old_boundary_info.raw_boundary_ids (old_elem, s, bndry_ids);
    1746    14770233 :       this->add_side (new_elem, s, bndry_ids);
    1747             :     }
    1748             : 
    1749    25520599 :   for (auto e : old_elem->edge_index_range())
    1750             :     {
    1751    21898445 :       old_boundary_info.raw_edge_boundary_ids (old_elem, e, bndry_ids);
    1752    21898445 :       this->add_edge (new_elem, e, bndry_ids);
    1753             :     }
    1754             : 
    1755    10866462 :   for (unsigned short sf=0; sf != 2; sf++)
    1756             :     {
    1757     7244308 :       old_boundary_info.raw_shellface_boundary_ids (old_elem, sf, bndry_ids);
    1758     7244308 :       this->add_shellface (new_elem, sf, bndry_ids);
    1759             :     }
    1760     3622154 : }
    1761             : 
    1762             : 
    1763             : 
    1764    51814004 : void BoundaryInfo::remove (const Node * node)
    1765             : {
    1766      224150 :   libmesh_assert(node);
    1767             : 
    1768             :   // Erase everything associated with node
    1769      224150 :   _boundary_node_id.erase (node);
    1770    51814004 : }
    1771             : 
    1772             : 
    1773             : 
    1774      105819 : void BoundaryInfo::remove_node (const Node * node,
    1775             :                                 const boundary_id_type id)
    1776             : {
    1777        3066 :   libmesh_assert(node);
    1778             : 
    1779             :   // Erase (node, id) entry from map.
    1780      105819 :   erase_if(_boundary_node_id, node,
    1781      179607 :            [id](decltype(_boundary_node_id)::mapped_type & val)
    1782      174357 :            {return val == id;});
    1783      105819 : }
    1784             : 
    1785             : 
    1786             : 
    1787    41381483 : void BoundaryInfo::remove (const Elem * elem)
    1788             : {
    1789      239469 :   libmesh_assert(elem);
    1790             : 
    1791             :   // Erase everything associated with elem
    1792      239469 :   _boundary_edge_id.erase (elem);
    1793      239469 :   _boundary_side_id.erase (elem);
    1794      239469 :   _boundary_shellface_id.erase (elem);
    1795    41381483 : }
    1796             : 
    1797             : 
    1798             : 
    1799      735288 : void BoundaryInfo::remove_edge (const Elem * elem,
    1800             :                                 const unsigned short int edge)
    1801             : {
    1802       37788 :   libmesh_assert(elem);
    1803             : 
    1804             :   // Only touch BCs for edges that exist.
    1805       37788 :   libmesh_assert_less (edge, elem->n_edges());
    1806             : 
    1807             :   // Only level 0 elements are stored in BoundaryInfo.
    1808       37788 :   libmesh_assert_equal_to (elem->level(), 0);
    1809             : 
    1810             :   // Erase (elem, edge, *) entries from map.
    1811      735288 :   erase_if(_boundary_edge_id, elem,
    1812           0 :            [edge](decltype(_boundary_edge_id)::mapped_type & pr)
    1813           0 :            {return pr.first == edge;});
    1814      735288 : }
    1815             : 
    1816             : 
    1817             : 
    1818           0 : void BoundaryInfo::remove_edge (const Elem * elem,
    1819             :                                 const unsigned short int edge,
    1820             :                                 const boundary_id_type id)
    1821             : {
    1822           0 :   libmesh_assert(elem);
    1823             : 
    1824             :   // Only touch BCs for edges that exist.
    1825           0 :   libmesh_assert_less (edge, elem->n_edges());
    1826             : 
    1827             :   // Only level 0 elements are stored in BoundaryInfo.
    1828           0 :   libmesh_assert_equal_to (elem->level(), 0);
    1829             : 
    1830             :   // Erase (elem, edge, id) entries from map.
    1831           0 :   erase_if(_boundary_edge_id, elem,
    1832           0 :            [edge, id](decltype(_boundary_edge_id)::mapped_type & pr)
    1833           0 :            {return pr.first == edge && pr.second == id;});
    1834           0 : }
    1835             : 
    1836             : 
    1837           0 : void BoundaryInfo::remove_shellface (const Elem * elem,
    1838             :                                      const unsigned short int shellface)
    1839             : {
    1840           0 :   libmesh_assert(elem);
    1841             : 
    1842             :   // Only level 0 elements are stored in BoundaryInfo.
    1843           0 :   libmesh_assert_equal_to (elem->level(), 0);
    1844             : 
    1845             :   // Shells only have 2 faces
    1846           0 :   libmesh_assert_less(shellface, 2);
    1847             : 
    1848             :   // Erase (elem, shellface, *) entries from map.
    1849           0 :   erase_if(_boundary_shellface_id, elem,
    1850           0 :            [shellface](decltype(_boundary_shellface_id)::mapped_type & pr)
    1851           0 :            {return pr.first == shellface;});
    1852           0 : }
    1853             : 
    1854             : 
    1855             : 
    1856           0 : void BoundaryInfo::remove_shellface (const Elem * elem,
    1857             :                                      const unsigned short int shellface,
    1858             :                                      const boundary_id_type id)
    1859             : {
    1860           0 :   libmesh_assert(elem);
    1861             : 
    1862             :   // Only level 0 elements are stored in BoundaryInfo.
    1863           0 :   libmesh_assert_equal_to (elem->level(), 0);
    1864             : 
    1865             :   // Shells only have 2 faces
    1866           0 :   libmesh_assert_less(shellface, 2);
    1867             : 
    1868             :   // Erase (elem, shellface, id) entries from map.
    1869           0 :   erase_if(_boundary_shellface_id, elem,
    1870           0 :            [shellface, id](decltype(_boundary_shellface_id)::mapped_type & pr)
    1871           0 :            {return pr.first == shellface && pr.second == id;});
    1872           0 : }
    1873             : 
    1874      371476 : void BoundaryInfo::remove_side (const Elem * elem,
    1875             :                                 const unsigned short int side)
    1876             : {
    1877       18784 :   libmesh_assert(elem);
    1878             : 
    1879             :   // Only touch BCs for sides that exist.
    1880       18784 :   libmesh_assert_less (side, elem->n_sides());
    1881             : 
    1882             :   // Erase (elem, side, *) entries from map.
    1883      371476 :   erase_if(_boundary_side_id, elem,
    1884       68806 :            [side](decltype(_boundary_side_id)::mapped_type & pr)
    1885       63727 :            {return pr.first == side;});
    1886      371476 : }
    1887             : 
    1888             : 
    1889             : 
    1890        6001 : void BoundaryInfo::remove_side (const Elem * elem,
    1891             :                                 const unsigned short int side,
    1892             :                                 const boundary_id_type id)
    1893             : {
    1894         212 :   libmesh_assert(elem);
    1895             : 
    1896             :   // Only touch BCs for sides that exist.
    1897         212 :   libmesh_assert_less (side, elem->n_sides());
    1898             : 
    1899             : #ifdef LIBMESH_ENABLE_AMR
    1900             :   // Here we have to stop and check if somebody tries to remove an ancestor's boundary ID
    1901             :   // through a child
    1902        6001 :   if (elem->level())
    1903             :   {
    1904           4 :     std::vector<boundary_id_type> bd_ids;
    1905          35 :     this->boundary_ids(elem,side,bd_ids);
    1906          35 :     if(std::find(bd_ids.begin(), bd_ids.end(), id) != bd_ids.end())
    1907             :     {
    1908           4 :       std::vector<boundary_id_type> raw_bd_ids;
    1909          35 :       this->raw_boundary_ids(elem, side, raw_bd_ids);
    1910          35 :       if(std::find(raw_bd_ids.begin(), raw_bd_ids.end(), id) == raw_bd_ids.end())
    1911         167 :         libmesh_not_implemented_msg("We cannot delete boundary ID "
    1912             :                                     + std::to_string(id) +
    1913             :                                     " using a child because it is inherited from an ancestor.");
    1914             :     }
    1915             :   }
    1916             : #endif
    1917             : 
    1918             :   // Erase (elem, side, id) entries from map.
    1919        5966 :   erase_if(_boundary_side_id, elem,
    1920       23606 :            [side, id](decltype(_boundary_side_id)::mapped_type & pr)
    1921       17640 :            {return pr.first == side && pr.second == id;});
    1922        5966 : }
    1923             : 
    1924             : 
    1925             : 
    1926         568 : void BoundaryInfo::remove_id (boundary_id_type id, const bool global)
    1927             : {
    1928             :   // Erase id from ids containers
    1929          16 :   _boundary_ids.erase(id);
    1930          16 :   _side_boundary_ids.erase(id);
    1931          16 :   _edge_boundary_ids.erase(id);
    1932          16 :   _shellface_boundary_ids.erase(id);
    1933          16 :   _node_boundary_ids.erase(id);
    1934          16 :   _ss_id_to_name.erase(id);
    1935          16 :   _ns_id_to_name.erase(id);
    1936          16 :   _es_id_to_name.erase(id);
    1937         568 :   if (global)
    1938           0 :     _global_boundary_ids.erase(id);
    1939             : 
    1940             :   // Erase (*, id) entries from map.
    1941         568 :   erase_if(_boundary_node_id,
    1942        2205 :            [id](decltype(_boundary_node_id)::mapped_type & val)
    1943        2079 :            {return val == id;});
    1944             : 
    1945             :   // Erase (*, *, id) entries from map.
    1946         568 :   erase_if(_boundary_edge_id,
    1947           0 :            [id](decltype(_boundary_edge_id)::mapped_type & pr)
    1948           0 :            {return pr.second == id;});
    1949             : 
    1950             :   // Erase (*, *, id) entries from map.
    1951         568 :   erase_if(_boundary_shellface_id,
    1952           0 :            [id](decltype(_boundary_shellface_id)::mapped_type & pr)
    1953           0 :            {return pr.second == id;});
    1954             : 
    1955             :   // Erase (*, *, id) entries from map.
    1956         568 :   erase_if(_boundary_side_id,
    1957        1540 :            [id](decltype(_boundary_side_id)::mapped_type & pr)
    1958        1452 :            {return pr.second == id;});
    1959         568 : }
    1960             : 
    1961             : 
    1962             : 
    1963        3692 : void BoundaryInfo::renumber_id (boundary_id_type old_id,
    1964             :                                 boundary_id_type new_id)
    1965             : {
    1966        3692 :   if (old_id == new_id)
    1967             :     {
    1968             :       // If the IDs are the same, this is a no-op.
    1969          48 :       return;
    1970             :     }
    1971             : 
    1972          56 :   bool found_node = false;
    1973       80132 :   for (auto & p : _boundary_node_id)
    1974       78144 :     if (p.second == old_id)
    1975             :       {
    1976             :         // If we already have this id on this node, we don't want to
    1977             :         // create a duplicate in our multimap
    1978       13164 :         this->remove_node(p.first, new_id);
    1979       13164 :         p.second = new_id;
    1980         456 :         found_node = true;
    1981             :       }
    1982        1988 :   if (found_node)
    1983             :     {
    1984          56 :       _node_boundary_ids.erase(old_id);
    1985        1500 :       _node_boundary_ids.insert(new_id);
    1986             :     }
    1987             : 
    1988          56 :   bool found_edge = false;
    1989        1988 :   for (auto & p : _boundary_edge_id)
    1990           0 :     if (p.second.second == old_id)
    1991             :       {
    1992             :         // If we already have this id on this edge, we don't want to
    1993             :         // create a duplicate in our multimap
    1994           0 :         this->remove_edge(p.first, p.second.first, new_id);
    1995           0 :         p.second.second = new_id;
    1996           0 :         found_edge = true;
    1997             :       }
    1998        1988 :   if (found_edge)
    1999             :     {
    2000           0 :       _edge_boundary_ids.erase(old_id);
    2001           0 :       _edge_boundary_ids.insert(new_id);
    2002             :     }
    2003             : 
    2004          56 :   bool found_shellface = false;
    2005        1988 :   for (auto & p : _boundary_shellface_id)
    2006           0 :     if (p.second.second == old_id)
    2007             :       {
    2008             :         // If we already have this id on this shellface, we don't want
    2009             :         // to create a duplicate in our multimap
    2010           0 :         this->remove_shellface(p.first, p.second.first, new_id);
    2011           0 :         p.second.second = new_id;
    2012           0 :         found_shellface = true;
    2013             :       }
    2014        1988 :   if (found_shellface)
    2015             :     {
    2016           0 :       _shellface_boundary_ids.erase(old_id);
    2017           0 :       _shellface_boundary_ids.insert(new_id);
    2018             :     }
    2019             : 
    2020          56 :   bool found_side = false;
    2021       37092 :   for (auto & p : _boundary_side_id)
    2022       35104 :     if (p.second.second == old_id)
    2023             :       {
    2024             :         // If we already have this id on this side, we don't want to
    2025             :         // create a duplicate in our multimap
    2026        5944 :         this->remove_side(p.first, p.second.first, new_id);
    2027        5944 :         p.second.second = new_id;
    2028         208 :         found_side = true;
    2029             :       }
    2030        1988 :   if (found_side)
    2031             :     {
    2032          56 :       _side_boundary_ids.erase(old_id);
    2033        1500 :       _side_boundary_ids.insert(new_id);
    2034             :     }
    2035             : 
    2036        1988 :   if (found_node || found_edge || found_shellface || found_side)
    2037             :     {
    2038          56 :       _boundary_ids.erase(old_id);
    2039        1500 :       _boundary_ids.insert(new_id);
    2040             :     }
    2041             : 
    2042        1988 :   renumber_name(_ss_id_to_name, old_id, new_id);
    2043        1988 :   renumber_name(_ns_id_to_name, old_id, new_id);
    2044        1988 :   renumber_name(_es_id_to_name, old_id, new_id);
    2045             : 
    2046        1988 :   this->libmesh_assert_valid_multimaps();
    2047             : }
    2048             : 
    2049             : 
    2050             : 
    2051          78 : unsigned int BoundaryInfo::side_with_boundary_id(const Elem * const elem,
    2052             :                                                  const boundary_id_type boundary_id_in) const
    2053             : {
    2054          78 :   const Elem * searched_elem = elem;
    2055             : 
    2056             :   // If we don't have a time-dependent domain, we can just go ahead and use the top parent
    2057             :   // (since only those contain boundary conditions). Otherwise, we keep the element
    2058          78 :   if (elem->level() != 0 && !_children_on_boundary)
    2059           0 :       searched_elem = elem->top_parent();
    2060             : 
    2061             :   // elem may have zero or multiple occurrences
    2062         117 :   for (const auto & pr : as_range(_boundary_side_id.equal_range(searched_elem)))
    2063             :   {
    2064             :       // if this is true we found the requested boundary_id
    2065             :       // of the element and want to return the side
    2066          78 :       if (pr.second.second == boundary_id_in)
    2067             :       {
    2068          39 :         unsigned int side = pr.second.first;
    2069             : 
    2070             :         // Here we branch out. If we don't allow time-dependent boundary domains,
    2071             :         // we need to check if our parents are consistent.
    2072          39 :         if (!_children_on_boundary)
    2073             :         {
    2074             :           // If we're on this external boundary then we share this
    2075             :           // external boundary id
    2076           0 :           if (elem->neighbor_ptr(side) == nullptr)
    2077           2 :             return side;
    2078             : 
    2079             :           // If we're on an internal boundary then we need to be sure
    2080             :           // it's the same internal boundary as our top_parent
    2081           0 :           const Elem * p = elem;
    2082             : 
    2083             : #ifdef LIBMESH_ENABLE_AMR
    2084             : 
    2085           0 :           while (p != nullptr)
    2086             :           {
    2087           0 :             const Elem * parent = p->parent();
    2088           0 :             if (parent && !parent->is_child_on_side(parent->which_child_am_i(p), side))
    2089           0 :               break;
    2090           0 :             p = parent;
    2091             :           }
    2092             : #endif
    2093             :           // We're on that side of our top_parent; return it
    2094           0 :           if (!p)
    2095           0 :             return side;
    2096             :         }
    2097             :         // Otherwise we need to check if the child's ancestors have something on
    2098             :         // the side of the child
    2099             :         else
    2100          39 :           return side;
    2101             :       }
    2102             :   }
    2103             : 
    2104             : #ifdef LIBMESH_ENABLE_AMR
    2105             :   // We might have instances (especially with moving boundary domains) when we
    2106             :   // query the paren't boundary ID on a child. We only do this till we find the
    2107             :   // the first side, for multiple sides see above.
    2108          39 :   if (_children_on_boundary && elem->level() != 0)
    2109             :   {
    2110          78 :     for (auto side : make_range(elem->n_sides()))
    2111             :     {
    2112           4 :       const Elem * p = elem;
    2113         164 :       while (p->parent() != nullptr)
    2114             :       {
    2115         117 :         const Elem * parent = p->parent();
    2116             : 
    2117             :         // First we make sure the parent shares this side
    2118         117 :         if (parent->is_child_on_side(parent->which_child_am_i(p), side))
    2119             :         {
    2120             :           // parent may have multiple boundary ids
    2121         351 :           for (const auto & pr : as_range(_boundary_side_id.equal_range(parent)))
    2122             :             // if this is true we found the requested boundary_id
    2123             :             // of the element and want to return the side
    2124         273 :             if (pr.second.first == side && pr.second.second == boundary_id_in)
    2125           2 :               return side;
    2126             : 
    2127           8 :           p = parent;
    2128             :         }
    2129             :         // If the parent is not on the same side, other ancestors won't be on the same side either
    2130             :         else
    2131           0 :           break;
    2132             :       }
    2133             :     }
    2134             :   }
    2135             : #endif
    2136             : 
    2137             :   // if we get here, we found elem in the data structure but not
    2138             :   // the requested boundary id, so return the default value
    2139           0 :   return libMesh::invalid_uint;
    2140             : }
    2141             : 
    2142             : 
    2143             : std::vector<unsigned int>
    2144       63117 : BoundaryInfo::sides_with_boundary_id(const Elem * const elem,
    2145             :                                      const boundary_id_type boundary_id_in) const
    2146             : {
    2147       25630 :   std::vector<unsigned int> returnval;
    2148             : 
    2149       63117 :   const Elem * searched_elem = elem;
    2150       63117 :   if (elem->level() != 0 && !_children_on_boundary)
    2151       49065 :     searched_elem = elem->top_parent();
    2152             : 
    2153             :   // elem may have zero or multiple occurrences
    2154      177178 :   for (const auto & pr : as_range(_boundary_side_id.equal_range(searched_elem)))
    2155             :   {
    2156             :       // if this is true we found the requested boundary_id
    2157             :       // of the element and want to return the side
    2158      114061 :       if (pr.second.second == boundary_id_in)
    2159             :       {
    2160       61741 :         unsigned int side = pr.second.first;
    2161             : 
    2162             :         // Here we branch out. If we don't allow time-dependent boundary domains,
    2163             :         // we need to check if our parents are consistent.
    2164       61741 :         if (!_children_on_boundary)
    2165             :         {
    2166             :           // If we're on this external boundary then we share this
    2167             :           // external boundary id
    2168       80731 :           if (elem->neighbor_ptr(side) == nullptr)
    2169             :             {
    2170       61741 :               returnval.push_back(side);
    2171       61741 :               continue;
    2172             :             }
    2173             : 
    2174             :           // If we're on an internal boundary then we need to be sure
    2175             :           // it's the same internal boundary as our top_parent
    2176           0 :           const Elem * p = elem;
    2177             : 
    2178             : #ifdef LIBMESH_ENABLE_AMR
    2179             : 
    2180           0 :           while (p != nullptr)
    2181             :           {
    2182           0 :             const Elem * parent = p->parent();
    2183           0 :             if (parent && !parent->is_child_on_side(parent->which_child_am_i(p), side))
    2184           0 :               break;
    2185           0 :             p = parent;
    2186             :           }
    2187             : #endif
    2188             :           // We're on that side of our top_parent; return it
    2189           0 :           if (!p)
    2190           0 :             returnval.push_back(side);
    2191             :         }
    2192             :         // Otherwise we trust what we got and return the side
    2193             :         else
    2194           0 :           returnval.push_back(side);
    2195             :       }
    2196             :   }
    2197             : 
    2198             : #ifdef LIBMESH_ENABLE_AMR
    2199             :   // We might have instances (especially with moving boundary domains) when we
    2200             :   // query the parent boundary ID on a child.
    2201       63117 :   if (_children_on_boundary && elem->level() != 0)
    2202             :   {
    2203         255 :     for (auto side : make_range(elem->n_sides()))
    2204             :     {
    2205           8 :       const Elem * p = elem;
    2206         318 :       while (p->parent() != nullptr)
    2207             :       {
    2208         306 :         const Elem * parent = p->parent();
    2209             :         // First we make sure the parent shares this side
    2210         306 :         if (parent->is_child_on_side(parent->which_child_am_i(p), side))
    2211             :         {
    2212             :           // parent may have multiple boundary ids
    2213         306 :           for (const auto & pr : as_range(_boundary_side_id.equal_range(parent)))
    2214             :           {
    2215             :             // if this is true we found the requested boundary_id
    2216             :             // of the element and want to add the side to the vector. We
    2217             :             // also need to check if the side is already in the vector. This might
    2218             :             // happen if the child inherits the boundary from the parent.
    2219         212 :             if (pr.second.first == side && pr.second.second == boundary_id_in &&
    2220         208 :                 std::find(returnval.begin(), returnval.end(), side) == returnval.end())
    2221         102 :               returnval.push_back(side);
    2222             :           }
    2223             :         }
    2224             :         // If the parent is not on the same side, other ancestors won't be on the same side either
    2225             :         else
    2226           8 :           break;
    2227           8 :         p = parent;
    2228             :       }
    2229             :     }
    2230             :   }
    2231             : #endif
    2232             : 
    2233       88747 :   return returnval;
    2234             : }
    2235             : 
    2236             : void
    2237        6870 : BoundaryInfo::build_node_boundary_ids(std::vector<boundary_id_type> & b_ids) const
    2238             : {
    2239         624 :   b_ids.clear();
    2240             : 
    2241     1694264 :   for (const auto & pr : _boundary_node_id)
    2242             :     {
    2243     1687394 :       boundary_id_type id = pr.second;
    2244             : 
    2245     1687394 :       if (std::find(b_ids.begin(),b_ids.end(),id) == b_ids.end())
    2246       26914 :         b_ids.push_back(id);
    2247             :     }
    2248        6870 : }
    2249             : 
    2250             : void
    2251        6870 : BoundaryInfo::build_side_boundary_ids(std::vector<boundary_id_type> & b_ids) const
    2252             : {
    2253         624 :   b_ids.clear();
    2254             : 
    2255      993094 :   for (const auto & pr : _boundary_side_id)
    2256             :     {
    2257      986224 :       boundary_id_type id = pr.second.second;
    2258             : 
    2259      986224 :       if (std::find(b_ids.begin(),b_ids.end(),id) == b_ids.end())
    2260       27864 :         b_ids.push_back(id);
    2261             :     }
    2262        6870 : }
    2263             : 
    2264             : void
    2265        6870 : BoundaryInfo::build_shellface_boundary_ids(std::vector<boundary_id_type> & b_ids) const
    2266             : {
    2267         624 :   b_ids.clear();
    2268             : 
    2269       86742 :   for (const auto & pr :_boundary_shellface_id)
    2270             :     {
    2271       79872 :       boundary_id_type id = pr.second.second;
    2272             : 
    2273       79872 :       if (std::find(b_ids.begin(),b_ids.end(),id) == b_ids.end())
    2274         192 :         b_ids.push_back(id);
    2275             :     }
    2276        6870 : }
    2277             : 
    2278             : #ifdef LIBMESH_ENABLE_AMR
    2279             : void
    2280    15966219 : BoundaryInfo::transfer_boundary_ids_from_children(const Elem * const parent)
    2281             : {
    2282             :   // this is only needed when we allow boundary to be associated with children elements
    2283             :   // also, we only transfer the parent's boundary ids when we are actually coarsen the child element
    2284    15966243 :   if (!_children_on_boundary ||
    2285         386 :       !(!parent->active() && parent->refinement_flag() == Elem::COARSEN_INACTIVE))
    2286      839464 :     return;
    2287             : 
    2288             :   // We assume that edges can be divided ito two pieces, while triangles and
    2289             :   // quads can be divided into four smaller areas. This is double because we'll need
    2290             :   // to convert the ratio of the children with given boundary id to a double.
    2291          82 :   const double number_of_sides_on_children = std::pow(2, parent->dim()-1);
    2292             : 
    2293             :   // In this case the input argument elem is the parent element. We need to check all of its sides
    2294             :   // to grab any potential boundary ids.
    2295         410 :   for (unsigned int side_i = 0; side_i < parent->n_sides(); ++side_i)
    2296             :   {
    2297             :     // An temporary storage to count how many times the children's boundaries occur. the general
    2298             :     // consensus is that if the boundary occurs more than once we propagate upon coarsening. Otherwise,
    2299             :     // it will get deleted.
    2300          32 :     std::map<unsigned short int, unsigned short int> boundary_counts;
    2301             : 
    2302        1640 :     for (const auto & child_i : make_range(parent->n_children()))
    2303             :     {
    2304             :       // We only need to check the children which share the side
    2305        1312 :       if (parent->is_child_on_side(child_i, side_i))
    2306             :       {
    2307             :         // Fetching the boundary tags on the child's side
    2308         902 :         for (const auto & pr : as_range(_boundary_side_id.equal_range(parent->child_ptr(child_i))))
    2309             :         {
    2310             :           // Making sure we are on the same boundary
    2311         246 :           if (pr.second.first == side_i)
    2312         123 :             ++boundary_counts[pr.second.second];
    2313             :         }
    2314             :       }
    2315             :     }
    2316             : 
    2317             :     // This is where the decision is made. If 50% of the children have the tags,
    2318             :     // we propagate them upwards upon coarsening. Otherwise, they are deleted.
    2319         410 :     for (const auto & boundary : boundary_counts)
    2320          82 :       if (boundary.second / number_of_sides_on_children > 0.5)
    2321          41 :         this->add_side(parent, side_i, boundary.first);
    2322             :   }
    2323             : 
    2324         410 :   for (const auto & child_i : make_range(parent->n_children()))
    2325         328 :     this->remove(parent->child_ptr(child_i));
    2326             : }
    2327             : #endif
    2328             : 
    2329        8552 : std::size_t BoundaryInfo::n_boundary_conds () const
    2330             : {
    2331             :   // in serial we know the number of bcs from the
    2332             :   // size of the container
    2333        8552 :   if (_mesh->is_serial())
    2334        4949 :     return _boundary_side_id.size();
    2335             : 
    2336             :   // in parallel we need to sum the number of local bcs
    2337          42 :   parallel_object_only();
    2338             : 
    2339        3603 :   std::size_t nbcs=0;
    2340             : 
    2341       14722 :   for (const auto & pr : _boundary_side_id)
    2342       11159 :     if (pr.first->processor_id() == this->processor_id())
    2343        5272 :       nbcs++;
    2344             : 
    2345        3603 :   this->comm().sum (nbcs);
    2346             : 
    2347        3603 :   return nbcs;
    2348             : }
    2349             : 
    2350       32258 : std::size_t BoundaryInfo::n_edge_conds () const
    2351             : {
    2352             :   // in serial we know the number of nodesets from the
    2353             :   // size of the container
    2354       32258 :   if (_mesh->is_serial())
    2355        6568 :     return _boundary_edge_id.size();
    2356             : 
    2357             :   // in parallel we need to sum the number of local nodesets
    2358          26 :   parallel_object_only();
    2359             : 
    2360       25690 :   std::size_t n_edge_bcs=0;
    2361             : 
    2362       27510 :   for (const auto & pr : _boundary_edge_id)
    2363        1820 :     if (pr.first->processor_id() == this->processor_id())
    2364         792 :       n_edge_bcs++;
    2365             : 
    2366       25690 :   this->comm().sum (n_edge_bcs);
    2367             : 
    2368       25690 :   return n_edge_bcs;
    2369             : }
    2370             : 
    2371             : 
    2372        1496 : std::size_t BoundaryInfo::n_shellface_conds () const
    2373             : {
    2374             :   // in serial we know the number of nodesets from the
    2375             :   // size of the container
    2376        1496 :   if (_mesh->is_serial())
    2377         394 :     return _boundary_shellface_id.size();
    2378             : 
    2379             :   // in parallel we need to sum the number of local nodesets
    2380           4 :   parallel_object_only();
    2381             : 
    2382        1102 :   std::size_t n_shellface_bcs=0;
    2383             : 
    2384       42736 :   for (const auto & pr : _boundary_shellface_id)
    2385       41634 :     if (pr.first->processor_id() == this->processor_id())
    2386       26640 :       n_shellface_bcs++;
    2387             : 
    2388        1102 :   this->comm().sum (n_shellface_bcs);
    2389             : 
    2390        1102 :   return n_shellface_bcs;
    2391             : }
    2392             : 
    2393             : 
    2394        4265 : std::size_t BoundaryInfo::n_nodeset_conds () const
    2395             : {
    2396             :   // in serial we know the number of nodesets from the
    2397             :   // size of the container
    2398        4265 :   if (_mesh->is_serial())
    2399        3227 :     return _boundary_node_id.size();
    2400             : 
    2401             :   // in parallel we need to sum the number of local nodesets
    2402           4 :   parallel_object_only();
    2403             : 
    2404        1038 :   std::size_t n_nodesets=0;
    2405             : 
    2406       15235 :   for (const auto & pr : _boundary_node_id)
    2407       14253 :     if (pr.first->processor_id() == this->processor_id())
    2408        5600 :       n_nodesets++;
    2409             : 
    2410        1038 :   this->comm().sum (n_nodesets);
    2411             : 
    2412        1038 :   return n_nodesets;
    2413             : }
    2414             : 
    2415             : 
    2416             : 
    2417             : #ifdef LIBMESH_ENABLE_DEPRECATED
    2418           0 : void BoundaryInfo::build_node_list (std::vector<dof_id_type> & nl,
    2419             :                                     std::vector<boundary_id_type> & il) const
    2420             : {
    2421             :   libmesh_deprecated();
    2422             : 
    2423             :   // Call the non-deprecated version of this function.
    2424           0 :   auto bc_tuples = this->build_node_list();
    2425             : 
    2426             :   // Clear the input vectors, just in case they were used for
    2427             :   // something else recently...
    2428           0 :   nl.clear();
    2429           0 :   il.clear();
    2430             : 
    2431             :   // Reserve the size, then use push_back
    2432           0 :   nl.reserve (bc_tuples.size());
    2433           0 :   il.reserve (bc_tuples.size());
    2434             : 
    2435           0 :   for (const auto & t : bc_tuples)
    2436             :     {
    2437           0 :       nl.push_back(std::get<0>(t));
    2438           0 :       il.push_back(std::get<1>(t));
    2439             :     }
    2440           0 : }
    2441             : #endif
    2442             : 
    2443             : 
    2444             : std::vector<BoundaryInfo::NodeBCTuple>
    2445       27330 : BoundaryInfo::build_node_list(NodeBCTupleSortBy sort_by) const
    2446             : {
    2447         984 :   std::vector<NodeBCTuple> bc_tuples;
    2448       27330 :   bc_tuples.reserve(_boundary_node_id.size());
    2449             : 
    2450     2153165 :   for (const auto & [node, bid] : _boundary_node_id)
    2451     2125835 :     bc_tuples.emplace_back(node->id(), bid);
    2452             : 
    2453             :   // This list is currently in memory address (arbitrary) order, so
    2454             :   // sort, using the specified ordering, to make it consistent on all procs.
    2455       27330 :   if (sort_by == NodeBCTupleSortBy::NODE_ID)
    2456       27330 :     std::sort(bc_tuples.begin(), bc_tuples.end());
    2457           0 :   else if (sort_by == NodeBCTupleSortBy::BOUNDARY_ID)
    2458           0 :     std::sort(bc_tuples.begin(), bc_tuples.end(),
    2459           0 :               [](const NodeBCTuple & left, const NodeBCTuple & right)
    2460           0 :               {return std::get<1>(left) < std::get<1>(right);});
    2461             : 
    2462       27330 :   return bc_tuples;
    2463             : }
    2464             : 
    2465             : 
    2466             : void
    2467          71 : BoundaryInfo::build_node_list_from_side_list()
    2468             : {
    2469             :   // If we're on a distributed mesh, even the owner of a node is not
    2470             :   // guaranteed to be able to properly assign its new boundary id(s)!
    2471             :   // Nodal neighbors are not always ghosted, and a nodal neighbor
    2472             :   // might have a boundary side.
    2473          71 :   const bool mesh_is_serial = _mesh->is_serial();
    2474             : 
    2475             :   typedef std::set<std::pair<dof_id_type, boundary_id_type>> set_type;
    2476             :   typedef std::vector<std::pair<dof_id_type, boundary_id_type>> vec_type;
    2477             : 
    2478           4 :   const processor_id_type my_proc_id = this->processor_id();
    2479           2 :   std::unordered_map<processor_id_type, set_type> nodes_to_push;
    2480           2 :   std::unordered_map<processor_id_type, vec_type> node_vecs_to_push;
    2481             : 
    2482             :   // For avoiding extraneous element side construction
    2483           2 :   ElemSideBuilder side_builder;
    2484             :   // Pull objects out of the loop to reduce heap operations
    2485             :   const Elem * side;
    2486             : 
    2487             :   // Loop over the side list
    2488         586 :   for (const auto & [elem, id_pair] : _boundary_side_id)
    2489             :     {
    2490             :       // Don't add remote sides
    2491         515 :       if (elem->is_remote())
    2492           0 :         continue;
    2493             : 
    2494             :       // Need to loop over the sides of any possible children
    2495          80 :       std::vector<const Elem *> family;
    2496             : #ifdef LIBMESH_ENABLE_AMR
    2497         515 :       elem->active_family_tree_by_side (family, id_pair.first);
    2498             : #else
    2499             :       family.push_back(elem);
    2500             : #endif
    2501             : 
    2502        1030 :       for (const auto & cur_elem : family)
    2503             :         {
    2504         515 :           side = &side_builder(*cur_elem, id_pair.first);
    2505             : 
    2506             :           // Add each node node on the side with the side's boundary id
    2507        1545 :           for (auto i : side->node_index_range())
    2508             :             {
    2509        1030 :               const boundary_id_type bcid = id_pair.second;
    2510        1110 :               this->add_node(side->node_ptr(i), bcid);
    2511        1030 :               if (!mesh_is_serial)
    2512             :                 {
    2513             :                   const processor_id_type proc_id =
    2514         750 :                     side->node_ptr(i)->processor_id();
    2515         750 :                   if (proc_id != my_proc_id)
    2516         430 :                     nodes_to_push[proc_id].emplace(side->node_id(i), bcid);
    2517             :                 }
    2518             :             }
    2519             :         }
    2520             :     }
    2521             : 
    2522             :   // If we're on a serial mesh then we're done.
    2523          71 :   if (mesh_is_serial)
    2524           2 :     return;
    2525             : 
    2526             :   // Otherwise we need to push ghost node bcids to their owners, then
    2527             :   // pull ghost node bcids from their owners.
    2528             : 
    2529         230 :   for (auto & [proc_id, s] : nodes_to_push)
    2530             :     {
    2531         166 :       node_vecs_to_push[proc_id].assign(s.begin(), s.end());
    2532           0 :       s.clear();
    2533             :     }
    2534             : 
    2535             :   auto nodes_action_functor =
    2536         166 :     [this]
    2537             :     (processor_id_type,
    2538         303 :      const vec_type & received_nodes)
    2539             :     {
    2540         469 :       for (const auto & [dof_id, bndry_id] : received_nodes)
    2541         303 :         this->add_node(_mesh->node_ptr(dof_id), bndry_id);
    2542          64 :     };
    2543             : 
    2544             :   Parallel::push_parallel_vector_data
    2545          64 :     (this->comm(), node_vecs_to_push, nodes_action_functor);
    2546             : 
    2547             :   // At this point we should know all the BCs for our own nodes; now
    2548             :   // we need BCs for ghost nodes.
    2549             :   std::unordered_map<processor_id_type, std::vector<dof_id_type>>
    2550           0 :     node_ids_requested;
    2551             : 
    2552             :   // Determine what nodes we need to request
    2553        2270 :   for (const auto & node : _mesh->node_ptr_range())
    2554             :     {
    2555        1071 :       const processor_id_type pid = node->processor_id();
    2556        1071 :       if (pid != my_proc_id)
    2557         783 :         node_ids_requested[pid].push_back(node->id());
    2558          64 :     }
    2559             : 
    2560             :   typedef std::vector<boundary_id_type> datum_type;
    2561             : 
    2562             :   auto node_bcid_gather_functor =
    2563         300 :     [this]
    2564             :     (processor_id_type,
    2565             :      const std::vector<dof_id_type> & ids,
    2566         783 :      std::vector<datum_type> & data)
    2567             :     {
    2568           0 :       const std::size_t query_size = ids.size();
    2569         300 :       data.resize(query_size);
    2570             : 
    2571        1083 :       for (std::size_t i=0; i != query_size; ++i)
    2572         783 :         this->boundary_ids(_mesh->node_ptr(ids[i]), data[i]);
    2573         364 :     };
    2574             : 
    2575             :   auto node_bcid_action_functor =
    2576         300 :     [this]
    2577             :     (processor_id_type,
    2578             :      const std::vector<dof_id_type> & ids,
    2579         783 :      const std::vector<datum_type> & data)
    2580             :     {
    2581        1083 :       for (auto i : index_range(ids))
    2582         783 :         this->add_node(_mesh->node_ptr(ids[i]), data[i]);
    2583          64 :     };
    2584             : 
    2585           0 :   datum_type * datum_type_ex = nullptr;
    2586             :   Parallel::pull_parallel_vector_data
    2587          64 :     (this->comm(), node_ids_requested, node_bcid_gather_functor,
    2588             :      node_bcid_action_functor, datum_type_ex);
    2589             : }
    2590             : 
    2591           0 : void BoundaryInfo::parallel_sync_side_ids()
    2592             : {
    2593             :   // we need BCs for ghost elements.
    2594             :   std::unordered_map<processor_id_type, std::vector<dof_id_type>>
    2595           0 :     elem_ids_requested;
    2596             : 
    2597             :   // Determine what elements we need to request
    2598           0 :   for (const auto & elem : _mesh->element_ptr_range())
    2599             :   {
    2600           0 :     const processor_id_type pid = elem->processor_id();
    2601           0 :     if (pid != this->processor_id())
    2602           0 :       elem_ids_requested[pid].push_back(elem->id());
    2603           0 :   }
    2604             : 
    2605             :   typedef std::vector<std::pair<unsigned short int, boundary_id_type>> datum_type;
    2606             : 
    2607             :   // gather the element ID, side, and boundary_id_type for the ghost elements
    2608             :   auto elem_id_gather_functor =
    2609           0 :     [this]
    2610             :     (processor_id_type,
    2611             :      const std::vector<dof_id_type> & ids,
    2612           0 :      std::vector<datum_type> & data)
    2613             :   {
    2614           0 :     data.resize(ids.size());
    2615           0 :     for (auto i : index_range(ids))
    2616             :     {
    2617           0 :       Elem * elem = _mesh->elem_ptr(ids[i]);
    2618           0 :       for (const auto & pr : as_range(_boundary_side_id.equal_range(elem)))
    2619           0 :         data[i].push_back(std::make_pair(pr.second.first, pr.second.second));
    2620             :     }
    2621           0 :   };
    2622             :   // update the _boundary_side_id on this processor
    2623             :   auto elem_id_action_functor =
    2624           0 :     [this]
    2625             :     (processor_id_type,
    2626             :      const std::vector<dof_id_type> & ids,
    2627           0 :      std::vector<datum_type> & data)
    2628             :   {
    2629           0 :       for (auto i : index_range(ids))
    2630             :       {
    2631           0 :         Elem * elem = _mesh->elem_ptr(ids[i]);
    2632             :         //clear boundary sides for this element
    2633           0 :         _boundary_side_id.erase(elem);
    2634             :         // update boundary sides for it
    2635           0 :         for (const auto & [side_id, bndry_id] : data[i])
    2636           0 :           _boundary_side_id.insert(std::make_pair(elem, std::make_pair(side_id, bndry_id)));
    2637             :       }
    2638           0 :   };
    2639             : 
    2640             : 
    2641           0 :   datum_type * datum_type_ex = nullptr;
    2642             :   Parallel::pull_parallel_vector_data
    2643           0 :     (this->comm(), elem_ids_requested, elem_id_gather_functor,
    2644             :      elem_id_action_functor, datum_type_ex);
    2645           0 : }
    2646             : 
    2647           0 : void BoundaryInfo::parallel_sync_node_ids()
    2648             : {
    2649             :   // we need BCs for ghost nodes.
    2650             :   std::unordered_map<processor_id_type, std::vector<dof_id_type>>
    2651           0 :     node_ids_requested;
    2652             : 
    2653             :   // Determine what nodes we need to request
    2654           0 :   for (const auto & node : _mesh->node_ptr_range())
    2655             :   {
    2656           0 :     const processor_id_type pid = node->processor_id();
    2657           0 :     if (pid != this->processor_id())
    2658           0 :       node_ids_requested[pid].push_back(node->id());
    2659           0 :   }
    2660             : 
    2661             :   typedef std::vector<boundary_id_type> datum_type;
    2662             : 
    2663             :   // gather the node ID and boundary_id_type for the ghost nodes
    2664             :   auto node_id_gather_functor =
    2665           0 :   [this]
    2666             :   (processor_id_type,
    2667             :     const std::vector<dof_id_type> & ids,
    2668           0 :     std::vector<datum_type> & data)
    2669             :     {
    2670           0 :       data.resize(ids.size());
    2671           0 :       for (auto i : index_range(ids))
    2672             :       {
    2673           0 :         Node * node = _mesh->node_ptr(ids[i]);
    2674           0 :         for (const auto & pr : as_range(_boundary_node_id.equal_range(node)))
    2675           0 :         data[i].push_back(pr.second);
    2676             :       }
    2677           0 :     };
    2678             : 
    2679             :     // update the _boundary_node_id on this processor
    2680             :     auto node_id_action_functor =
    2681           0 :       [this]
    2682             :       (processor_id_type,
    2683             :        const std::vector<dof_id_type> & ids,
    2684           0 :        std::vector<datum_type> & data)
    2685             :     {
    2686           0 :         for (auto i : index_range(ids))
    2687             :         {
    2688           0 :           Node * node = _mesh->node_ptr(ids[i]);
    2689             :           //clear boundary node
    2690           0 :           _boundary_node_id.erase(node);
    2691             :           // update boundary node
    2692           0 :           for (const auto & pr : data[i])
    2693           0 :             _boundary_node_id.insert(std::make_pair(node, pr));
    2694             :         }
    2695           0 :     };
    2696             : 
    2697             : 
    2698           0 :     datum_type * datum_type_ex = nullptr;
    2699             :     Parallel::pull_parallel_vector_data
    2700           0 :       (this->comm(), node_ids_requested, node_id_gather_functor,
    2701             :        node_id_action_functor, datum_type_ex);
    2702           0 : }
    2703             : 
    2704           0 : void BoundaryInfo::build_side_list_from_node_list(const std::set<boundary_id_type> & nodeset_list)
    2705             : {
    2706             :   // Check for early return
    2707           0 :   if (_boundary_node_id.empty())
    2708             :     {
    2709           0 :       libMesh::out << "No boundary node IDs have been added: cannot build side list!" << std::endl;
    2710           0 :       return;
    2711             :     }
    2712             : 
    2713             :   // For avoiding extraneous element side construction
    2714           0 :   ElemSideBuilder side_builder;
    2715             :   // Pull objects out of the loop to reduce heap operations
    2716             :   const Elem * side_elem;
    2717             : 
    2718           0 :   for (const auto & elem : _mesh->active_element_ptr_range())
    2719           0 :     for (auto side : elem->side_index_range())
    2720             :       {
    2721           0 :         side_elem = &side_builder(*elem, side);
    2722             : 
    2723             :         // map from nodeset_id to count for that ID
    2724           0 :         std::map<boundary_id_type, unsigned> nodesets_node_count;
    2725             : 
    2726             :         // For each nodeset that this node is a member of, increment the associated
    2727             :         // nodeset ID count
    2728           0 :         for (const auto & node : side_elem->node_ref_range())
    2729           0 :           for (const auto & pr : as_range(_boundary_node_id.equal_range(&node)))
    2730           0 :             if (nodeset_list.empty() || nodeset_list.count(pr.second))
    2731           0 :               nodesets_node_count[pr.second]++;
    2732             : 
    2733             :         // Now check to see what nodeset_counts have the correct
    2734             :         // number of nodes in them.  For any that do, add this side to
    2735             :         // the sideset, making sure the sideset inherits the
    2736             :         // nodeset's name, if there is one.
    2737           0 :         for (const auto & pr : nodesets_node_count)
    2738           0 :           if (pr.second == side_elem->n_nodes())
    2739             :             {
    2740           0 :               add_side(elem, side, pr.first);
    2741             : 
    2742             :               // Let the sideset inherit any non-empty name from the nodeset
    2743           0 :               std::string & nset_name = nodeset_name(pr.first);
    2744             : 
    2745           0 :               if (nset_name != "")
    2746           0 :                 sideset_name(pr.first) = nset_name;
    2747             :             }
    2748           0 :       } // end for side
    2749             : }
    2750             : 
    2751             : 
    2752             : 
    2753             : 
    2754             : #ifdef LIBMESH_ENABLE_DEPRECATED
    2755         213 : void BoundaryInfo::build_side_list (std::vector<dof_id_type> & el,
    2756             :                                     std::vector<unsigned short int> & sl,
    2757             :                                     std::vector<boundary_id_type> & il) const
    2758             : {
    2759             :   libmesh_deprecated();
    2760             : 
    2761             :   // Call the non-deprecated version of this function.
    2762         219 :   auto bc_tuples = this->build_side_list();
    2763             : 
    2764             :   // Clear the input vectors, just in case they were used for
    2765             :   // something else recently...
    2766           6 :   el.clear();
    2767           6 :   sl.clear();
    2768           6 :   il.clear();
    2769             : 
    2770             :   // Reserve the size, then use push_back
    2771         219 :   el.reserve (bc_tuples.size());
    2772         219 :   sl.reserve (bc_tuples.size());
    2773         219 :   il.reserve (bc_tuples.size());
    2774             : 
    2775         703 :   for (const auto & t : bc_tuples)
    2776             :     {
    2777         490 :       el.push_back(std::get<0>(t));
    2778         490 :       sl.push_back(std::get<1>(t));
    2779         490 :       il.push_back(std::get<2>(t));
    2780             :     }
    2781         213 : }
    2782             : #endif
    2783             : 
    2784             : 
    2785             : std::vector<BoundaryInfo::BCTuple>
    2786       22388 : BoundaryInfo::build_side_list(BCTupleSortBy sort_by) const
    2787             : {
    2788         846 :   std::vector<BCTuple> bc_triples;
    2789       22388 :   bc_triples.reserve(_boundary_side_id.size());
    2790             : 
    2791      633374 :   for (const auto & [elem, id_pair] : _boundary_side_id)
    2792      610986 :     bc_triples.emplace_back(elem->id(), id_pair.first, id_pair.second);
    2793             : 
    2794             :   // bc_triples is currently in whatever order the Elem pointers in
    2795             :   // the _boundary_side_id multimap are in, and in particular might be
    2796             :   // in different orders on different processors. To avoid this
    2797             :   // inconsistency, we'll sort using the default operator< for tuples.
    2798       22388 :   if (sort_by == BCTupleSortBy::ELEM_ID)
    2799       22388 :     std::sort(bc_triples.begin(), bc_triples.end());
    2800           0 :   else if (sort_by == BCTupleSortBy::SIDE_ID)
    2801           0 :     std::sort(bc_triples.begin(), bc_triples.end(),
    2802           0 :               [](const BCTuple & left, const BCTuple & right)
    2803           0 :               {return std::get<1>(left) < std::get<1>(right);});
    2804           0 :   else if (sort_by == BCTupleSortBy::BOUNDARY_ID)
    2805           0 :     std::sort(bc_triples.begin(), bc_triples.end(),
    2806           0 :               [](const BCTuple & left, const BCTuple & right)
    2807           0 :               {return std::get<2>(left) < std::get<2>(right);});
    2808             : 
    2809       22388 :   return bc_triples;
    2810             : }
    2811             : 
    2812             : 
    2813             : 
    2814             : #ifdef LIBMESH_ENABLE_DEPRECATED
    2815           0 : void BoundaryInfo::build_active_side_list (std::vector<dof_id_type> & el,
    2816             :                                            std::vector<unsigned short int> & sl,
    2817             :                                            std::vector<boundary_id_type> & il) const
    2818             : {
    2819             :   libmesh_deprecated();
    2820             : 
    2821             :   // Call the non-deprecated version of this function.
    2822           0 :   auto bc_tuples = this->build_active_side_list();
    2823             : 
    2824             :   // Clear the input vectors, just in case they were used for
    2825             :   // something else recently...
    2826           0 :   el.clear();
    2827           0 :   sl.clear();
    2828           0 :   il.clear();
    2829             : 
    2830             :   // Reserve the size, then use push_back
    2831           0 :   el.reserve (bc_tuples.size());
    2832           0 :   sl.reserve (bc_tuples.size());
    2833           0 :   il.reserve (bc_tuples.size());
    2834             : 
    2835           0 :   for (const auto & t : bc_tuples)
    2836             :     {
    2837           0 :       el.push_back(std::get<0>(t));
    2838           0 :       sl.push_back(std::get<1>(t));
    2839           0 :       il.push_back(std::get<2>(t));
    2840             :     }
    2841           0 : }
    2842             : #endif
    2843             : 
    2844             : 
    2845             : std::vector<BoundaryInfo::BCTuple>
    2846           0 : BoundaryInfo::build_active_side_list () const
    2847             : {
    2848           0 :   std::vector<BCTuple> bc_triples;
    2849           0 :   bc_triples.reserve(_boundary_side_id.size());
    2850             : 
    2851           0 :   for (const auto & [elem, id_pair] : _boundary_side_id)
    2852             :     {
    2853             :       // Don't add remote sides
    2854           0 :       if (elem->is_remote())
    2855           0 :         continue;
    2856             : 
    2857             :       // Loop over the sides of possible children
    2858           0 :       std::vector<const Elem *> family;
    2859             : #ifdef LIBMESH_ENABLE_AMR
    2860           0 :       elem->active_family_tree_by_side(family, id_pair.first);
    2861             : #else
    2862             :       family.push_back(elem);
    2863             : #endif
    2864             : 
    2865             :       // Populate the list items
    2866           0 :       for (const auto & f : family)
    2867           0 :         bc_triples.emplace_back(f->id(), id_pair.first, id_pair.second);
    2868             :     }
    2869             : 
    2870             :   // This list is currently in memory address (arbitrary) order, so
    2871             :   // sort to make it consistent on all procs.
    2872           0 :   std::sort(bc_triples.begin(), bc_triples.end());
    2873             : 
    2874           0 :   return bc_triples;
    2875             : }
    2876             : 
    2877             : 
    2878             : #ifdef LIBMESH_ENABLE_DEPRECATED
    2879           0 : void BoundaryInfo::build_edge_list (std::vector<dof_id_type> & el,
    2880             :                                     std::vector<unsigned short int> & sl,
    2881             :                                     std::vector<boundary_id_type> & il) const
    2882             : {
    2883             :   libmesh_deprecated();
    2884             : 
    2885             :   // Call the non-deprecated version of this function.
    2886           0 :   auto bc_tuples = this->build_edge_list();
    2887             : 
    2888             :   // Clear the input vectors, just in case they were used for
    2889             :   // something else recently...
    2890           0 :   el.clear();
    2891           0 :   sl.clear();
    2892           0 :   il.clear();
    2893             : 
    2894             :   // Reserve the size, then use push_back
    2895           0 :   el.reserve (bc_tuples.size());
    2896           0 :   sl.reserve (bc_tuples.size());
    2897           0 :   il.reserve (bc_tuples.size());
    2898             : 
    2899           0 :   for (const auto & t : bc_tuples)
    2900             :     {
    2901           0 :       el.push_back(std::get<0>(t));
    2902           0 :       sl.push_back(std::get<1>(t));
    2903           0 :       il.push_back(std::get<2>(t));
    2904             :     }
    2905           0 : }
    2906             : #endif
    2907             : 
    2908             : 
    2909             : std::vector<BoundaryInfo::BCTuple>
    2910        4862 : BoundaryInfo::build_edge_list() const
    2911             : {
    2912         354 :   std::vector<BCTuple> bc_triples;
    2913        4862 :   bc_triples.reserve(_boundary_edge_id.size());
    2914             : 
    2915       22778 :   for (const auto & [elem, id_pair] : _boundary_edge_id)
    2916       17916 :     bc_triples.emplace_back(elem->id(), id_pair.first, id_pair.second);
    2917             : 
    2918             :   // This list is currently in memory address (arbitrary) order, so
    2919             :   // sort to make it consistent on all procs.
    2920        4862 :   std::sort(bc_triples.begin(), bc_triples.end());
    2921             : 
    2922        4862 :   return bc_triples;
    2923             : }
    2924             : 
    2925             : 
    2926             : #ifdef LIBMESH_ENABLE_DEPRECATED
    2927           0 : void BoundaryInfo::build_shellface_list (std::vector<dof_id_type> & el,
    2928             :                                          std::vector<unsigned short int> & sl,
    2929             :                                          std::vector<boundary_id_type> & il) const
    2930             : {
    2931             :   libmesh_deprecated();
    2932             : 
    2933             :   // Call the non-deprecated version of this function.
    2934           0 :   auto bc_tuples = this->build_shellface_list();
    2935             : 
    2936             :   // Clear the input vectors, just in case they were used for
    2937             :   // something else recently...
    2938           0 :   el.clear();
    2939           0 :   sl.clear();
    2940           0 :   il.clear();
    2941             : 
    2942             :   // Reserve the size, then use push_back
    2943           0 :   el.reserve (bc_tuples.size());
    2944           0 :   sl.reserve (bc_tuples.size());
    2945           0 :   il.reserve (bc_tuples.size());
    2946             : 
    2947           0 :   for (const auto & t : bc_tuples)
    2948             :     {
    2949           0 :       el.push_back(std::get<0>(t));
    2950           0 :       sl.push_back(std::get<1>(t));
    2951           0 :       il.push_back(std::get<2>(t));
    2952             :     }
    2953           0 : }
    2954             : #endif
    2955             : 
    2956             : 
    2957             : std::vector<BoundaryInfo::BCTuple>
    2958        4784 : BoundaryInfo::build_shellface_list() const
    2959             : {
    2960         350 :   std::vector<BCTuple> bc_triples;
    2961        4784 :   bc_triples.reserve(_boundary_shellface_id.size());
    2962             : 
    2963       44720 :   for (const auto & [elem, id_pair] : _boundary_shellface_id)
    2964       39936 :     bc_triples.emplace_back(elem->id(), id_pair.first, id_pair.second);
    2965             : 
    2966             :   // This list is currently in memory address (arbitrary) order, so
    2967             :   // sort to make it consistent on all procs.
    2968        4784 :   std::sort(bc_triples.begin(), bc_triples.end());
    2969             : 
    2970        4784 :   return bc_triples;
    2971             : }
    2972             : 
    2973             : 
    2974           0 : void BoundaryInfo::print_info(std::ostream & out_stream) const
    2975             : {
    2976             :   // Print out the nodal BCs
    2977           0 :   if (!_boundary_node_id.empty())
    2978             :     {
    2979           0 :       out_stream << "Nodal Boundary conditions:" << std::endl
    2980           0 :                  << "--------------------------" << std::endl
    2981           0 :                  << "  (Node No., ID)               " << std::endl;
    2982             : 
    2983           0 :       for (const auto & [node, bndry_id] : _boundary_node_id)
    2984           0 :         out_stream << "  (" << node->id()
    2985           0 :                    << ", "  << bndry_id
    2986           0 :                    << ")"  << std::endl;
    2987             :     }
    2988             : 
    2989             :   // Print out the element edge BCs
    2990           0 :   if (!_boundary_edge_id.empty())
    2991             :     {
    2992           0 :       out_stream << std::endl
    2993           0 :                  << "Edge Boundary conditions:" << std::endl
    2994           0 :                  << "-------------------------" << std::endl
    2995           0 :                  << "  (Elem No., Edge No., ID)      " << std::endl;
    2996             : 
    2997           0 :       for (const auto & [elem, id_pair] : _boundary_edge_id)
    2998           0 :         out_stream << "  (" << elem->id()
    2999           0 :                    << ", "  << id_pair.first
    3000           0 :                    << ", "  << id_pair.second
    3001           0 :                    << ")"   << std::endl;
    3002             :     }
    3003             : 
    3004             :   // Print out the element shellface BCs
    3005           0 :   if (!_boundary_shellface_id.empty())
    3006             :     {
    3007           0 :       out_stream << std::endl
    3008           0 :                  << "Shell-face Boundary conditions:" << std::endl
    3009           0 :                  << "-------------------------" << std::endl
    3010           0 :                  << "  (Elem No., Shell-face No., ID)      " << std::endl;
    3011             : 
    3012           0 :       for (const auto & [elem, id_pair] : _boundary_shellface_id)
    3013           0 :         out_stream << "  (" << elem->id()
    3014           0 :                    << ", "  << id_pair.first
    3015           0 :                    << ", "  << id_pair.second
    3016           0 :                    << ")"   << std::endl;
    3017             :     }
    3018             : 
    3019             :   // Print out the element side BCs
    3020           0 :   if (!_boundary_side_id.empty())
    3021             :     {
    3022           0 :       out_stream << std::endl
    3023           0 :                  << "Side Boundary conditions:" << std::endl
    3024           0 :                  << "-------------------------" << std::endl
    3025           0 :                  << "  (Elem No., Side No., ID)      " << std::endl;
    3026             : 
    3027           0 :       for (const auto & [elem, id_pair] : _boundary_side_id)
    3028           0 :         out_stream << "  (" << elem->id()
    3029           0 :                    << ", "  << id_pair.first
    3030           0 :                    << ", "  << id_pair.second
    3031           0 :                    << ")"   << std::endl;
    3032             :     }
    3033           0 : }
    3034             : 
    3035             : 
    3036             : 
    3037           0 : void BoundaryInfo::print_summary(std::ostream & out_stream) const
    3038             : {
    3039             :   // Print out the nodal BCs
    3040           0 :   if (!_boundary_node_id.empty())
    3041             :     {
    3042           0 :       out_stream << "Nodal Boundary conditions:" << std::endl
    3043           0 :                  << "--------------------------" << std::endl
    3044           0 :                  << "  (ID, number of nodes)   " << std::endl;
    3045             : 
    3046           0 :       std::map<boundary_id_type, std::size_t> ID_counts;
    3047             : 
    3048           0 :       for (const auto & pr : _boundary_node_id)
    3049           0 :         ID_counts[pr.second]++;
    3050             : 
    3051           0 :       for (const auto & [bndry_id, cnt] : ID_counts)
    3052           0 :         out_stream << "  (" << bndry_id
    3053           0 :                    << ", "  << cnt
    3054           0 :                    << ")"  << std::endl;
    3055             :     }
    3056             : 
    3057             :   // Print out the element edge BCs
    3058           0 :   if (!_boundary_edge_id.empty())
    3059             :     {
    3060           0 :       out_stream << std::endl
    3061           0 :                  << "Edge Boundary conditions:" << std::endl
    3062           0 :                  << "-------------------------" << std::endl
    3063           0 :                  << "  (ID, number of edges)   " << std::endl;
    3064             : 
    3065           0 :       std::map<boundary_id_type, std::size_t> ID_counts;
    3066             : 
    3067           0 :       for (const auto & pr : _boundary_edge_id)
    3068           0 :         ID_counts[pr.second.second]++;
    3069             : 
    3070           0 :       for (const auto & [bndry_id, cnt] : ID_counts)
    3071           0 :         out_stream << "  (" << bndry_id
    3072           0 :                    << ", "  << cnt
    3073           0 :                    << ")"  << std::endl;
    3074             :     }
    3075             : 
    3076             : 
    3077             :   // Print out the element edge BCs
    3078           0 :   if (!_boundary_shellface_id.empty())
    3079             :     {
    3080           0 :       out_stream << std::endl
    3081           0 :                  << "Shell-face Boundary conditions:" << std::endl
    3082           0 :                  << "-------------------------" << std::endl
    3083           0 :                  << "  (ID, number of shellfaces)   " << std::endl;
    3084             : 
    3085           0 :       std::map<boundary_id_type, std::size_t> ID_counts;
    3086             : 
    3087           0 :       for (const auto & pr : _boundary_shellface_id)
    3088           0 :         ID_counts[pr.second.second]++;
    3089             : 
    3090           0 :       for (const auto & [bndry_id, cnt] : ID_counts)
    3091           0 :         out_stream << "  (" << bndry_id
    3092           0 :                    << ", "  << cnt
    3093           0 :                    << ")"  << std::endl;
    3094             :     }
    3095             : 
    3096             :   // Print out the element side BCs
    3097           0 :   if (!_boundary_side_id.empty())
    3098             :     {
    3099           0 :       out_stream << std::endl
    3100           0 :                  << "Side Boundary conditions:" << std::endl
    3101           0 :                  << "-------------------------" << std::endl
    3102           0 :                  << "  (ID, number of sides)   " << std::endl;
    3103             : 
    3104           0 :       std::map<boundary_id_type, std::size_t> ID_counts;
    3105             : 
    3106           0 :       for (const auto & pr : _boundary_side_id)
    3107           0 :         ID_counts[pr.second.second]++;
    3108             : 
    3109           0 :       for (const auto & [bndry_id, cnt] : ID_counts)
    3110           0 :         out_stream << "  (" << bndry_id
    3111           0 :                    << ", "  << cnt
    3112           0 :                    << ")"  << std::endl;
    3113             :     }
    3114           0 : }
    3115             : 
    3116             : 
    3117       31587 : const std::string & BoundaryInfo::get_sideset_name(boundary_id_type id) const
    3118             : {
    3119       31587 :   static const std::string empty_string;
    3120       31587 :   if (const auto it = _ss_id_to_name.find(id);
    3121        1754 :       it == _ss_id_to_name.end())
    3122         241 :     return empty_string;
    3123             :   else
    3124       26496 :     return it->second;
    3125             : }
    3126             : 
    3127             : 
    3128     1264589 : std::string & BoundaryInfo::sideset_name(boundary_id_type id)
    3129             : {
    3130     1264589 :   return _ss_id_to_name[id];
    3131             : }
    3132             : 
    3133       25691 : const std::string & BoundaryInfo::get_nodeset_name(boundary_id_type id) const
    3134             : {
    3135       25691 :   static const std::string empty_string;
    3136       25691 :   if (const auto it = _ns_id_to_name.find(id);
    3137        1517 :       it == _ns_id_to_name.end())
    3138          40 :     return empty_string;
    3139             :   else
    3140       25218 :     return it->second;
    3141             : }
    3142             : 
    3143     1264356 : std::string & BoundaryInfo::nodeset_name(boundary_id_type id)
    3144             : {
    3145     1264356 :   return _ns_id_to_name[id];
    3146             : }
    3147             : 
    3148         269 : const std::string & BoundaryInfo::get_edgeset_name(boundary_id_type id) const
    3149             : {
    3150         269 :   static const std::string empty_string;
    3151         269 :   if (const auto it = _es_id_to_name.find(id);
    3152          23 :       it == _es_id_to_name.end())
    3153          21 :     return empty_string;
    3154             :   else
    3155          24 :     return it->second;
    3156             : }
    3157             : 
    3158             : 
    3159         714 : std::string & BoundaryInfo::edgeset_name(boundary_id_type id)
    3160             : {
    3161         714 :   return _es_id_to_name[id];
    3162             : }
    3163             : 
    3164        2240 : boundary_id_type BoundaryInfo::get_id_by_name(std::string_view name) const
    3165             : {
    3166             :   // Search sidesets
    3167        5600 :   for (const auto & [ss_id, ss_name] : _ss_id_to_name)
    3168        5536 :     if (ss_name == name)
    3169        2240 :       return ss_id;
    3170             : 
    3171             :   // Search nodesets
    3172           0 :   for (const auto & [ns_id, ns_name] : _ns_id_to_name)
    3173           0 :     if (ns_name == name)
    3174           0 :       return ns_id;
    3175             : 
    3176             :   // Search edgesets
    3177           0 :   for (const auto & [es_id, es_name] : _es_id_to_name)
    3178           0 :     if (es_name == name)
    3179           0 :       return es_id;
    3180             : 
    3181             :   // If we made it here without returning, we don't have a sideset,
    3182             :   // nodeset, or edgeset by the requested name, so return invalid_id
    3183           0 :   return invalid_id;
    3184             : }
    3185             : 
    3186             : 
    3187             : 
    3188        4402 : void BoundaryInfo::_find_id_maps(const std::set<boundary_id_type> & requested_boundary_ids,
    3189             :                                  dof_id_type first_free_node_id,
    3190             :                                  std::map<dof_id_type, dof_id_type> * node_id_map,
    3191             :                                  dof_id_type first_free_elem_id,
    3192             :                                  std::map<std::pair<dof_id_type, unsigned char>, dof_id_type> * side_id_map,
    3193             :                                  const std::set<subdomain_id_type> & subdomains_relative_to)
    3194             : {
    3195             :   // We'll do the same modulus trick that DistributedMesh uses to avoid
    3196             :   // id conflicts between different processors
    3197             :   dof_id_type
    3198        4402 :     next_node_id = first_free_node_id + this->processor_id(),
    3199        4402 :     next_elem_id = first_free_elem_id + this->processor_id();
    3200             : 
    3201             :   // For avoiding extraneous element side construction
    3202         248 :   ElemSideBuilder side_builder;
    3203             :   // Pull objects out of the loop to reduce heap operations
    3204             :   const Elem * side;
    3205             : 
    3206             :   // We'll pass through the mesh once first to build
    3207             :   // the maps and count boundary nodes and elements.
    3208             :   // To find local boundary nodes, we have to examine all elements
    3209             :   // here rather than just local elements, because it's possible to
    3210             :   // have a local boundary node that's not on a local boundary
    3211             :   // element, e.g. at the tip of a triangle.
    3212             : 
    3213             :   // We'll loop through two different ranges here: first all elements,
    3214             :   // looking for local nodes, and second through unpartitioned
    3215             :   // elements, looking for all remaining nodes.
    3216        4526 :   const MeshBase::const_element_iterator end_el = _mesh->elements_end();
    3217         124 :   bool hit_end_el = false;
    3218             :   const MeshBase::const_element_iterator end_unpartitioned_el =
    3219        4526 :     _mesh->pid_elements_end(DofObject::invalid_processor_id);
    3220             : 
    3221       13670 :   for (MeshBase::const_element_iterator el = _mesh->elements_begin();
    3222       97548 :        !hit_end_el || (el != end_unpartitioned_el); ++el)
    3223             :     {
    3224       92806 :       if ((el == end_el) && !hit_end_el)
    3225             :         {
    3226             :           // Note that we're done with local nodes and just looking
    3227             :           // for remaining unpartitioned nodes
    3228         124 :           hit_end_el = true;
    3229             : 
    3230             :           // Join up the local results from other processors
    3231        4402 :           if (side_id_map)
    3232        4402 :             this->comm().set_union(*side_id_map);
    3233        4402 :           if (node_id_map)
    3234        1917 :             this->comm().set_union(*node_id_map);
    3235             : 
    3236             :           // Finally we'll pass through any unpartitioned elements to add them
    3237             :           // to the maps and counts.
    3238        4402 :           next_node_id = first_free_node_id + this->n_processors();
    3239        4402 :           next_elem_id = first_free_elem_id + this->n_processors();
    3240             : 
    3241        8680 :           el = _mesh->pid_elements_begin(DofObject::invalid_processor_id);
    3242        4402 :           if (el == end_unpartitioned_el)
    3243         124 :             break;
    3244             :         }
    3245             : 
    3246       88404 :       const Elem * elem = *el;
    3247             : 
    3248             :       // If the subdomains_relative_to container has the
    3249             :       // invalid_subdomain_id, we fall back on the "old" behavior of
    3250             :       // adding sides regardless of this Elem's subdomain. Otherwise,
    3251             :       // if the subdomains_relative_to container doesn't contain the
    3252             :       // current Elem's subdomain_id(), we won't add any sides from
    3253             :       // it.
    3254       15390 :       if (!subdomains_relative_to.count(Elem::invalid_subdomain_id) &&
    3255       88518 :           !subdomains_relative_to.count(elem->subdomain_id()))
    3256       10424 :         continue;
    3257             : 
    3258      370038 :       for (auto s : elem->side_index_range())
    3259             :         {
    3260       15220 :           bool add_this_side = false;
    3261             : 
    3262             :           // Find all the boundary side ids for this Elem side.
    3263       30440 :           std::vector<boundary_id_type> bcids;
    3264      292874 :           this->boundary_ids(elem, s, bcids);
    3265             : 
    3266      341224 :           for (const boundary_id_type bcid : bcids)
    3267             :             {
    3268             :               // if the user wants this id, we want this side
    3269        3854 :               if (requested_boundary_ids.count(bcid))
    3270             :                 {
    3271        1642 :                   add_this_side = true;
    3272        1642 :                   break;
    3273             :                 }
    3274             :             }
    3275             : 
    3276             :           // We may still want to add this side if the user called
    3277             :           // sync() with no requested_boundary_ids. This corresponds
    3278             :           // to the "old" style of calling sync() in which the entire
    3279             :           // boundary was copied to the BoundaryMesh, and handles the
    3280             :           // case where elements on the geometric boundary are not in
    3281             :           // any sidesets.
    3282       15220 :           if (requested_boundary_ids.count(invalid_id) &&
    3283           0 :               elem->neighbor_ptr(s) == nullptr)
    3284           0 :             add_this_side = true;
    3285             : 
    3286      292874 :           if (add_this_side)
    3287             :             {
    3288             :               // We only assign ids for our own and for
    3289             :               // unpartitioned elements
    3290       29506 :               if (side_id_map &&
    3291       19654 :                   ((elem->processor_id() == this->processor_id()) ||
    3292         821 :                    (elem->processor_id() ==
    3293             :                     DofObject::invalid_processor_id)))
    3294             :                 {
    3295        1642 :                   std::pair<dof_id_type, unsigned char> side_pair(elem->id(), s);
    3296         821 :                   libmesh_assert (!side_id_map->count(side_pair));
    3297        9852 :                   (*side_id_map)[side_pair] = next_elem_id;
    3298       10673 :                   next_elem_id += this->n_processors() + 1;
    3299             :                 }
    3300             : 
    3301       27864 :               side = &side_builder(*elem, s);
    3302      110580 :               for (auto n : side->node_index_range())
    3303             :                 {
    3304        4882 :                   const Node & node = side->node_ref(n);
    3305             : 
    3306             :                   // In parallel we don't know enough to number
    3307             :                   // others' nodes ourselves.
    3308       87598 :                   if (!hit_end_el &&
    3309        9764 :                       (node.processor_id() != this->processor_id()))
    3310       53424 :                     continue;
    3311             : 
    3312       29292 :                   dof_id_type node_id = node.id();
    3313       29292 :                   if (node_id_map && !node_id_map->count(node_id))
    3314             :                     {
    3315        7452 :                       (*node_id_map)[node_id] = next_node_id;
    3316        8073 :                       next_node_id += this->n_processors() + 1;
    3317             :                     }
    3318             :                 }
    3319             :             }
    3320             :         }
    3321             :     }
    3322             : 
    3323             :   // FIXME: ought to renumber side/node_id_map image to be contiguous
    3324             :   // to save memory, also ought to reserve memory
    3325        4402 : }
    3326             : 
    3327        1349 : void BoundaryInfo::clear_stitched_boundary_side_ids (const boundary_id_type sideset_id,
    3328             :                                                      const boundary_id_type other_sideset_id,
    3329             :                                                      const bool clear_nodeset_data)
    3330             : {
    3331          38 :   auto end_it = _boundary_side_id.end();
    3332          38 :   auto it = _boundary_side_id.begin();
    3333             : 
    3334             :   // This predicate checks to see whether the pred_pr triplet's boundary ID matches sideset_id
    3335             :   // (other_sideset_id) *and* whether there is a boundary information triplet on the other side of
    3336             :   // the face whose boundary ID matches the other_sideset_id (sideset_id). We return a pair where
    3337             :   // first is a boolean indicating our condition is true or false, and second is an iterator to the
    3338             :   // neighboring triplet if our condition is true
    3339             :   auto predicate =
    3340       70216 :       [sideset_id, other_sideset_id](
    3341             :           const std::pair<const Elem *, std::pair<unsigned short int, boundary_id_type>> & pred_pr,
    3342             :           const std::multimap<const Elem *, std::pair<unsigned short int, boundary_id_type>> &
    3343        7293 :               pred_container) {
    3344       74408 :         const Elem & elem = *pred_pr.first;
    3345       74408 :         const auto elem_side = pred_pr.second.first;
    3346       74408 :         const Elem * const other_elem = elem.neighbor_ptr(elem_side);
    3347       74408 :         if (!other_elem)
    3348        1880 :           return std::make_pair(false, pred_container.end());
    3349             : 
    3350        7668 :         const auto elem_side_bnd_id = pred_pr.second.second;
    3351         216 :         auto other_elem_side_bnd_id = BoundaryInfo::invalid_id;
    3352        7668 :         if (elem_side_bnd_id == sideset_id)
    3353         154 :           other_elem_side_bnd_id = other_sideset_id;
    3354        2811 :         else if (elem_side_bnd_id == other_sideset_id)
    3355          62 :           other_elem_side_bnd_id = sideset_id;
    3356             :         else
    3357           0 :           return std::make_pair(false, pred_container.end());
    3358             : 
    3359        7668 :         const auto other_elem_side = other_elem->which_neighbor_am_i(&elem);
    3360             :         const typename std::decay<decltype(pred_container)>::type::value_type other_sideset_info(
    3361         432 :             other_elem, std::make_pair(other_elem_side, other_elem_side_bnd_id));
    3362         432 :         auto other_range = pred_container.equal_range(other_elem);
    3363         216 :         libmesh_assert_msg(
    3364             :             other_range.first != other_range.second,
    3365             :             "No matching sideset information for other element in boundary information");
    3366        7668 :         auto other_it = std::find(other_range.first, other_range.second, other_sideset_info);
    3367         216 :         libmesh_assert_msg(
    3368             :             other_it != pred_container.end(),
    3369             :             "No matching sideset information for other element in boundary information");
    3370         216 :         return std::make_pair(true, other_it);
    3371        1349 :       };
    3372             : 
    3373       75757 :   for (; it != end_it;)
    3374             :   {
    3375       76504 :     auto pred_result = predicate(*it, _boundary_side_id);
    3376       74408 :     if (pred_result.first)
    3377             :     {
    3378             :       // First erase associated nodeset information.  Do it from both
    3379             :       // sides, so we get any higher-order nodes if we're looking at
    3380             :       // them from a lower-order side, and so we only remove the two
    3381             :       // boundary ids used for stitching.
    3382        7668 :       if (clear_nodeset_data)
    3383             :       {
    3384        7668 :         const Elem & elem = *it->first;
    3385        7668 :         const Elem & neigh = *pred_result.second->first;
    3386        7668 :         const auto elem_side = it->second.first;
    3387        7668 :         const boundary_id_type neigh_side = pred_result.second->second.first;
    3388        7668 :         const auto elem_bcid = it->second.second;
    3389        7668 :         const boundary_id_type neigh_bcid = pred_result.second->second.second;
    3390             : 
    3391       54219 :         for (const auto local_node_num : elem.nodes_on_side(elem_side))
    3392       47641 :           this->remove_node(elem.node_ptr(local_node_num), elem_bcid);
    3393             : 
    3394       54204 :         for (const auto local_node_num : neigh.nodes_on_side(neigh_side))
    3395       47624 :           this->remove_node(neigh.node_ptr(local_node_num), neigh_bcid);
    3396             :       }
    3397             : 
    3398             :       // Now erase the sideset information for our element and its
    3399             :       // neighbor, together.  This is safe since a multimap doesn't
    3400             :       // invalidate iterators.
    3401        7452 :       _boundary_side_id.erase(pred_result.second);
    3402        7452 :       it = _boundary_side_id.erase(it);
    3403             :     }
    3404             :     else
    3405        1880 :       ++it;
    3406             :   }
    3407             : 
    3408             :   // Removing stitched-away boundary ids might have removed an id
    3409             :   // *entirely*, so we need to recompute boundary id sets to check
    3410             :   // for that.
    3411        1349 :   this->regenerate_id_sets();
    3412        1349 :   this->libmesh_assert_valid_multimaps();
    3413        1349 : }
    3414             : 
    3415             : const std::set<boundary_id_type> &
    3416         568 : BoundaryInfo::get_global_boundary_ids() const
    3417             : {
    3418          16 :   libmesh_assert(_mesh && _mesh->is_prepared());
    3419         568 :   return _global_boundary_ids;
    3420             : }
    3421             : 
    3422             : void
    3423        3337 : BoundaryInfo::libmesh_assert_valid_multimaps() const
    3424             : {
    3425             : #ifndef NDEBUG
    3426         376 :   auto verify_multimap = [](const auto & themap) {
    3427       12716 :     for (const auto & [key, val] : themap)
    3428             :       {
    3429       12340 :         auto range = themap.equal_range(key);
    3430             : 
    3431       12340 :         int count = 0;
    3432       38020 :         for (auto it = range.first; it != range.second; ++it)
    3433       25680 :           if (it->second == val)
    3434       12340 :             ++count;
    3435             : 
    3436       12340 :         libmesh_assert(count == 1);
    3437             :       }
    3438         376 :   };
    3439             : 
    3440          94 :   verify_multimap(this->_boundary_node_id);
    3441          94 :   verify_multimap(this->_boundary_edge_id);
    3442          94 :   verify_multimap(this->_boundary_shellface_id);
    3443          94 :   verify_multimap(this->_boundary_side_id);
    3444             : #else
    3445        3243 :   return;
    3446             : #endif
    3447          94 : }
    3448             : 
    3449             : } // namespace libMesh

Generated by: LCOV version 1.14