LCOV - code coverage report
Current view: top level - src/meshgenerators - BreakMeshByBlockGenerator.C (source / functions) Hit Total Coverage
Test: idaholab/moose framework: #32971 (54bef8) with base c6cf66 Lines: 311 322 96.6 %
Date: 2026-05-29 20:35:17 Functions: 14 14 100.0 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : //* This file is part of the MOOSE framework
       2             : //* https://mooseframework.inl.gov
       3             : //*
       4             : //* All rights reserved, see COPYRIGHT for full restrictions
       5             : //* https://github.com/idaholab/moose/blob/master/COPYRIGHT
       6             : //*
       7             : //* Licensed under LGPL 2.1, please see LICENSE for details
       8             : //* https://www.gnu.org/licenses/lgpl-2.1.html
       9             : 
      10             : #include "BreakMeshByBlockGenerator.h"
      11             : #include "CastUniquePointer.h"
      12             : #include "MooseMeshUtils.h"
      13             : 
      14             : #include "libmesh/distributed_mesh.h"
      15             : #include "libmesh/elem.h"
      16             : #include "libmesh/partitioner.h"
      17             : #include "timpi/parallel_sync.h"
      18             : 
      19             : registerMooseObject("MooseApp", BreakMeshByBlockGenerator);
      20             : 
      21             : InputParameters
      22        3641 : BreakMeshByBlockGenerator::validParams()
      23             : {
      24        3641 :   InputParameters params = MeshGenerator::validParams();
      25        7282 :   params.addClassDescription(
      26             :       "Break the mesh at interfaces between blocks. New nodes will be generated so elements on "
      27             :       "each side of the break are no longer connected.");
      28             : 
      29       14564 :   params.addRequiredParam<MeshGeneratorName>("input", "The mesh we want to modify");
      30             : 
      31       14564 :   params.addParam<std::string>(
      32             :       "interface_name",
      33             :       "interface",
      34             :       "the name of the new interface. Cannot be used with `split_interface=true`");
      35       10923 :   params.addParam<bool>("split_interface",
      36        7282 :                         true,
      37             :                         "If true, it creates a "
      38             :                         "different interface for each block pair.");
      39       14564 :   params.addParam<std::vector<SubdomainName>>(
      40             :       "surrounding_blocks",
      41             :       "The list of subdomain names surrounding which interfaces will be generated.");
      42       14564 :   params.addParam<std::vector<std::vector<SubdomainName>>>(
      43             :       "block_pairs", "The list of subdomain pairs between which interfaces will be generated.");
      44       10923 :   params.addParam<bool>("add_transition_interface",
      45        7282 :                         false,
      46             :                         "If true and block is not empty, a special boundary named "
      47             :                         "interface_transition is generate between listed blocks and other blocks.");
      48       10923 :   params.addParam<bool>(
      49        7282 :       "split_transition_interface", false, "Whether to split the transition interface by blocks.");
      50       10923 :   params.addParam<bool>("add_interface_on_two_sides",
      51        7282 :                         true,
      52             :                         "Whether to add an additional interface boundary at the other side.");
      53       10923 :   params.addParam<BoundaryName>(
      54             :       "interface_transition_name",
      55             :       "interface_transition",
      56             :       "the name of the interface transition boundary created when blocks are provided");
      57             : 
      58       10923 :   params.addRelationshipManager("ElementSideNeighborLayers",
      59             :                                 Moose::RelationshipManagerType::GEOMETRIC,
      60         492 :                                 [](const InputParameters &, InputParameters & rm_params)
      61         984 :                                 { rm_params.set<unsigned short>("layers") = 1; });
      62             : 
      63        3641 :   return params;
      64           0 : }
      65             : 
      66         290 : BreakMeshByBlockGenerator::BreakMeshByBlockGenerator(const InputParameters & parameters)
      67             :   : MeshGenerator(parameters),
      68         580 :     _input(getMesh("input")),
      69         580 :     _interface_name(getParam<std::string>("interface_name")),
      70         580 :     _split_interface(getParam<bool>("split_interface")),
      71         580 :     _block_pairs_restricted(parameters.isParamSetByUser("block_pairs")),
      72         290 :     _surrounding_blocks_restricted(parameters.isParamSetByUser("surrounding_blocks")),
      73         580 :     _add_transition_interface(getParam<bool>("add_transition_interface")),
      74         580 :     _split_transition_interface(getParam<bool>("split_transition_interface")),
      75         580 :     _interface_transition_name(getParam<BoundaryName>("interface_transition_name")),
      76        1450 :     _add_interface_on_two_sides(getParam<bool>("add_interface_on_two_sides"))
      77             : {
      78        1260 :   if (getParam<bool>("split_interface") && _pars.isParamSetByUser("interface_name"))
      79           0 :     paramError("interface_name",
      80             :                "if split_interface == true, the new interface_name cannot be specified by the "
      81             :                "user. It will be automatically assigned");
      82         290 :   if (_block_pairs_restricted && _surrounding_blocks_restricted)
      83           0 :     paramError("block_pairs_restricted",
      84             :                "BreakMeshByBlockGenerator: 'surrounding_blocks' and 'block_pairs' can not be used "
      85             :                "at the same time.");
      86             : 
      87         290 :   if (!_add_transition_interface && _split_transition_interface)
      88           0 :     paramError("split_transition_interface",
      89             :                "BreakMeshByBlockGenerator cannot split the transition interface because "
      90             :                "add_transition_interface is false");
      91             : 
      92         290 :   if (_add_transition_interface && _block_pairs_restricted)
      93           0 :     paramError("add_transition_interface",
      94             :                "BreakMeshByBlockGenerator cannot split the transition interface when 'block_pairs' "
      95             :                "are specified.");
      96         290 : }
      97             : 
      98             : BoundaryName
      99        3654 : BreakMeshByBlockGenerator::generateBoundaryName(const MeshBase & mesh,
     100             :                                                 subdomain_id_type primaryBlockID,
     101             :                                                 subdomain_id_type secondaryBlockID)
     102             : {
     103        3654 :   std::string primary_block_name = mesh.subdomain_name(primaryBlockID);
     104        3654 :   std::string secondary_block_name = mesh.subdomain_name(secondaryBlockID);
     105        3654 :   if (primary_block_name.empty())
     106        3654 :     primary_block_name = "Block" + std::to_string(primaryBlockID);
     107        3654 :   if (secondary_block_name.empty())
     108        3654 :     secondary_block_name = "Block" + std::to_string(secondaryBlockID);
     109             : 
     110        7308 :   return primary_block_name + "_" + secondary_block_name;
     111        3654 : }
     112             : 
     113             : void
     114        3654 : BreakMeshByBlockGenerator::mapBoundaryIdAndBoundaryName(boundary_id_type boundaryID,
     115             :                                                         const std::string & boundaryName)
     116             : {
     117        3654 :   _bName_bID_set.insert(std::pair<std::string, int>(boundaryName, boundaryID));
     118        3654 : }
     119             : 
     120             : void
     121        3654 : BreakMeshByBlockGenerator::findBoundaryName(const MeshBase & mesh,
     122             :                                             subdomain_id_type primaryBlockID,
     123             :                                             subdomain_id_type secondaryBlockID,
     124             :                                             BoundaryName & boundaryName,
     125             :                                             boundary_id_type boundaryID,
     126             :                                             BoundaryInfo & boundary_info)
     127             : {
     128        3654 :   boundaryName = generateBoundaryName(mesh, primaryBlockID, secondaryBlockID);
     129             : 
     130             :   // check if the boundary name already exist
     131        3654 :   bool checkBoundaryAlreadyExist = false;
     132      122136 :   for (auto b : _bName_bID_set)
     133             :   {
     134      118482 :     if (b.first.compare(boundaryName) == 0)
     135             :     {
     136             :       mooseAssert(boundaryID == b.second, "Boundary with two inconsistent ids");
     137           0 :       checkBoundaryAlreadyExist = true;
     138             :     }
     139      118482 :   }
     140             : 
     141        3654 :   if (checkBoundaryAlreadyExist)
     142           0 :     return;
     143             :   else
     144             :   {
     145        3654 :     mapBoundaryIdAndBoundaryName(boundaryID, boundaryName);
     146             : 
     147        3654 :     boundary_info.sideset_name(boundaryID) = boundaryName;
     148             : 
     149        3654 :     return;
     150             :   }
     151             : }
     152             : 
     153             : std::unique_ptr<MeshBase>
     154         290 : BreakMeshByBlockGenerator::generate()
     155             : {
     156         290 :   std::unique_ptr<MeshBase> mesh = std::move(_input);
     157             : 
     158             :   // Make sure subdomain caches are up to date
     159         290 :   if (!mesh->preparation().has_cached_elem_data)
     160         121 :     mesh->cache_elem_data();
     161             : 
     162             :   // Max node id is used later to generate new unique node IDs
     163         290 :   auto max_node_id = mesh->max_node_id();
     164             :   // If the mesh is prepared, max_node_id is already the same on all processors so do not need to
     165             :   // communicate.
     166         290 :   if (!mesh->is_replicated() && !mesh->is_prepared())
     167          18 :     mesh->comm().max(max_node_id);
     168             : #if LIBMESH_ENABLE_UNIQUE_ID
     169         290 :   const auto max_unique_id = mesh->parallel_max_unique_id();
     170             : #endif
     171             : 
     172         290 :   BoundaryInfo & boundary_info = mesh->get_boundary_info();
     173             : 
     174             :   // Handle block restrictions
     175         290 :   if (_block_pairs_restricted)
     176             :   {
     177          76 :     for (const auto & block_name_pair :
     178         407 :          getParam<std::vector<std::vector<SubdomainName>>>("block_pairs"))
     179             :     {
     180         106 :       if (block_name_pair.size() != 2)
     181           0 :         paramError("block_pairs",
     182             :                    "Each row of 'block_pairs' must have a size of two (block names).");
     183             : 
     184             :       // check that the blocks exist in the mesh
     185         315 :       for (const auto & name : block_name_pair)
     186         212 :         if (!MooseMeshUtils::hasSubdomainName(*mesh, name))
     187           6 :           paramError("block_pairs", "The block '", name, "' was not found in the mesh");
     188             : 
     189         103 :       const auto block_pair = MooseMeshUtils::getSubdomainIDs(*mesh, block_name_pair);
     190         206 :       std::pair<SubdomainID, SubdomainID> pair = std::make_pair(
     191         206 :           std::min(block_pair[0], block_pair[1]), std::max(block_pair[0], block_pair[1]));
     192             : 
     193         103 :       _block_pairs.insert(pair);
     194             :       // Insert all SubdomainIDs from block_pair into _block_set (duplicates automatically
     195             :       // ignored)
     196         103 :       std::copy(block_pair.begin(), block_pair.end(), std::inserter(_block_set, _block_set.end()));
     197         103 :     }
     198             :   }
     199         214 :   else if (_surrounding_blocks_restricted)
     200             :   {
     201             :     // check that the blocks exist in the mesh
     202        1709 :     for (const auto & name : getParam<std::vector<SubdomainName>>("surrounding_blocks"))
     203        1553 :       if (!MooseMeshUtils::hasSubdomainName(*mesh, name))
     204           6 :         paramError("surrounding_blocks", "The block '", name, "' was not found in the mesh");
     205             : 
     206             :     const auto surrounding_block_ids = MooseMeshUtils::getSubdomainIDs(
     207         100 :         *mesh, getParam<std::vector<SubdomainName>>("surrounding_blocks"));
     208         100 :     std::copy(surrounding_block_ids.begin(),
     209             :               surrounding_block_ids.end(),
     210          50 :               std::inserter(_block_set, _block_set.end()));
     211          50 :   }
     212             : 
     213             :   // check that a boundary named _interface_transition_name does not already exist in the mesh
     214         314 :   if ((_block_pairs_restricted || _surrounding_blocks_restricted) && _add_transition_interface &&
     215          30 :       boundary_info.get_id_by_name(_interface_transition_name) != Moose::INVALID_BOUNDARY_ID)
     216           0 :     paramError("interface_transition_name",
     217             :                "BreakMeshByBlockGenerator the specified  interface transition boundary name "
     218             :                "already exits");
     219             : 
     220             :   // initialize the node to element map
     221         284 :   std::unordered_map<dof_id_type, std::vector<dof_id_type>> node_to_elem_map;
     222       21922 :   for (const auto & elem : mesh->active_element_ptr_range())
     223      125653 :     for (const auto n : make_range(elem->n_nodes()))
     224      104299 :       node_to_elem_map[elem->node_id(n)].push_back(elem->id());
     225             : 
     226             :   // Sync connected block info across all ranks
     227             :   // a map from a node id to the set of connected block ids
     228         284 :   const auto nodeid_to_connected_blocks = syncConnectedBlocks(node_to_elem_map, *mesh);
     229             : 
     230             :   // Main loop to duplicate nodes
     231       19359 :   for (const auto & [current_node_id, connected_elems] : node_to_elem_map)
     232             :   {
     233       19075 :     const auto * current_node = mesh->node_ptr(current_node_id);
     234       19075 :     if (!current_node)
     235           0 :       continue;
     236             : 
     237             :     // If the node is not connected to any blocks, skip it
     238       19075 :     auto it = nodeid_to_connected_blocks.find(current_node_id);
     239       19075 :     if (it == nodeid_to_connected_blocks.end())
     240           0 :       continue;
     241             : 
     242             :     // Get the set of blocks connected to the current node.
     243             :     // Make a local copy because we'll modify it (e.g., erase Elem::invalid_subdomain_id).
     244             :     // Preserve the original map's information (including invalid_subdomain_id) for global
     245             :     // synchronization/logic, but operate on the copy for duplication decisions.
     246       19075 :     auto connected_blocks = it->second;
     247             : 
     248             :     // Remove invalid subdomain if block pairs restriction is active
     249       19075 :     if (_block_pairs_restricted)
     250        2307 :       connected_blocks.erase(Elem::invalid_subdomain_id);
     251             : 
     252       19075 :     const unsigned int node_multiplicity = connected_blocks.size();
     253             : 
     254             :     // check if current_node need to be duplicated
     255       19075 :     if (node_multiplicity > 1)
     256             :     {
     257             :       // find reference_subdomain_id (e.g. the subdomain with lower id or the
     258             :       // Elem::invalid_subdomain_id)
     259        9226 :       auto subdomain_it = connected_blocks.begin();
     260             :       subdomain_id_type reference_subdomain_id =
     261       17876 :           !_block_pairs_restricted &&
     262        8650 :                   connected_blocks.find(Elem::invalid_subdomain_id) != connected_blocks.end()
     263       18452 :               ? Elem::invalid_subdomain_id
     264        6626 :               : *subdomain_it;
     265             : 
     266             :       // For the block_pairs option, the node that is only shared by specific block pairs will be
     267             :       // newly created.
     268        9226 :       bool should_create_new_node = true;
     269        9226 :       if (_block_pairs_restricted)
     270             :       {
     271             :         // Default to false; only set to true if this is exactly the pair boundary we want
     272         576 :         should_create_new_node = false;
     273             : 
     274             :         // Directly use the global info synchronized earlier by syncConnectedBlocks
     275             :         // Invalid subdomain is included for correct logic
     276         576 :         const auto & sets_blocks_for_this_node = it->second;
     277             : 
     278             :         // Check if this node is exactly at the boundary between two blocks
     279             :         // If it is a junction between more than two blocks, we do not split it
     280             :         // For the block_pairs option, only nodes shared by the specified block pairs are newly
     281             :         // created.
     282         576 :         if (sets_blocks_for_this_node.size() == 2)
     283             :         {
     284             :           // Get the two block IDs from the set
     285         513 :           auto set_blocks_for_this_node_it = sets_blocks_for_this_node.begin();
     286         513 :           subdomain_id_type block1 = *set_blocks_for_this_node_it;
     287         513 :           subdomain_id_type block2 = *std::next(set_blocks_for_this_node_it);
     288             : 
     289             :           // Check if this block pair is one of the user-specified pairs to split
     290         513 :           if (findBlockPairs(block1, block2))
     291         443 :             should_create_new_node = true;
     292             :         }
     293             :       }
     294             : 
     295             :       // multiplicity counter to keep track of how many nodes we added
     296        9226 :       unsigned int multiplicity_counter = node_multiplicity;
     297       71359 :       for (auto elem_id : connected_elems)
     298             :       {
     299             :         // all the duplicate nodes are added and assigned
     300       62146 :         if (multiplicity_counter == 0)
     301          13 :           break;
     302             : 
     303       62133 :         Elem * current_elem = mesh->elem_ptr(elem_id);
     304       62133 :         subdomain_id_type block_id = blockRestrictedElementSubdomainID(current_elem);
     305             : 
     306             :         // Work on duplicating the node only from the element that is not on the reference subdomain
     307       89102 :         if ((block_id != reference_subdomain_id) ||
     308       26969 :             (_block_pairs_restricted && findBlockPairs(reference_subdomain_id, block_id)))
     309             :         {
     310             :           // assign the newly added node to current_elem
     311       35164 :           Node * new_node = nullptr;
     312             : 
     313       35164 :           std::vector<boundary_id_type> node_boundary_ids;
     314             : 
     315      167595 :           for (const auto local_node_id : make_range(current_elem->n_nodes()))
     316      150695 :             if (current_elem->node_id(local_node_id) ==
     317      150695 :                 current_node->id()) // if current node == node on element
     318             :             {
     319       18264 :               if (should_create_new_node)
     320             :               {
     321             :                 // The maximum number of duplications per subdomain will not exceed the original
     322             :                 // number of nodes. Since max_node_id is always greater than the original node
     323             :                 // count, it is safe to use max_node_id as a stride to generate new unique node ID
     324             :                 // values.
     325             :                 // max_node_id > original node count > number of duplications per subdomain
     326       36116 :                 new_node = Node::build(*current_node,
     327       18058 :                                        mesh->is_replicated()
     328       15226 :                                            ? mesh->n_nodes()
     329        2832 :                                            : (current_elem->subdomain_id() + 1) * max_node_id +
     330        2832 :                                                  current_node->id())
     331       18058 :                                .release();
     332             : #if LIBMESH_ENABLE_UNIQUE_ID
     333       18058 :                 new_node->set_unique_id((current_elem->subdomain_id() + 1) * max_unique_id +
     334       18058 :                                         current_node->unique_id());
     335             : #endif
     336             :                 // We're duplicating nodes so that each subdomain elem has its own copy, so it
     337             :                 // seems natural to assign this new node the same proc id as corresponding
     338             :                 // subdomain elem
     339       18058 :                 new_node->processor_id() = current_elem->processor_id();
     340       18058 :                 mesh->add_node(new_node);
     341       18058 :                 current_elem->set_node(local_node_id, new_node);
     342             :                 // Add boundary info to the new node
     343       18058 :                 boundary_info.boundary_ids(current_node, node_boundary_ids);
     344       18058 :                 boundary_info.add_node(new_node, node_boundary_ids);
     345             :               }
     346       18264 :               multiplicity_counter--; // node created, update multiplicity counter
     347       18264 :               break; // once the proper node has been fixed in one element we can break the
     348             :                      // loop
     349             :             }
     350             : 
     351       35164 :           if (should_create_new_node)
     352             :           {
     353      493159 :             for (auto connected_elem_id : connected_elems)
     354             :             {
     355      458201 :               Elem * connected_elem = mesh->elem_ptr(connected_elem_id);
     356             : 
     357             :               // Assign the newly added node to other connected elements with the same
     358             :               // block_id
     359      458201 :               if (connected_elem->subdomain_id() == current_elem->subdomain_id() &&
     360             :                   connected_elem != current_elem)
     361             :               {
     362      745183 :                 for (const auto local_node_id : make_range(connected_elem->n_nodes()))
     363      607055 :                   if (connected_elem->node_id(local_node_id) ==
     364      607055 :                       current_node->id()) // if current node == node on element
     365             :                   {
     366       16900 :                     connected_elem->set_node(local_node_id, new_node);
     367       16900 :                     break;
     368             :                   }
     369             :               }
     370             :             }
     371             :           }
     372       35164 :         }
     373             :       }
     374             : 
     375             :       // create blocks pair and assign element side to new interface boundary map
     376       71380 :       for (auto elem_id : connected_elems)
     377             :       {
     378      872650 :         for (auto connected_elem_id : connected_elems)
     379             :         {
     380      810496 :           Elem * current_elem = mesh->elem_ptr(elem_id);
     381      810496 :           Elem * connected_elem = mesh->elem_ptr(connected_elem_id);
     382             : 
     383             :           // Early out if both elements are the same
     384      810496 :           if (current_elem == connected_elem)
     385       62154 :             continue;
     386             : 
     387      748342 :           subdomain_id_type curr_elem_subid = blockRestrictedElementSubdomainID(current_elem);
     388             :           subdomain_id_type connected_elem_subid =
     389      748342 :               blockRestrictedElementSubdomainID(connected_elem);
     390             : 
     391      748342 :           if (curr_elem_subid < connected_elem_subid)
     392             :           {
     393      227305 :             if (current_elem->has_neighbor(connected_elem))
     394             :             {
     395       35588 :               unsigned int side = current_elem->which_neighbor_am_i(connected_elem);
     396       35588 :               unsigned int connected_elem_side = connected_elem->which_neighbor_am_i(current_elem);
     397             : 
     398             :               // in this case we need to play a game to reorder the sides
     399             :               // Ensure consistent ordering: (min, max) for block (subdomain) IDs
     400       35588 :               bool need_to_switch = false;
     401       35588 :               if (_block_pairs_restricted || _surrounding_blocks_restricted)
     402             :               {
     403       19124 :                 connected_elem_subid = connected_elem->subdomain_id();
     404       19124 :                 if (curr_elem_subid > connected_elem_subid) // we need to switch the ids
     405             :                 {
     406        3466 :                   connected_elem_subid = current_elem->subdomain_id();
     407        3466 :                   curr_elem_subid = connected_elem->subdomain_id();
     408             : 
     409        3466 :                   side = connected_elem->which_neighbor_am_i(current_elem);
     410             : 
     411        3466 :                   connected_elem_side = current_elem->which_neighbor_am_i(connected_elem);
     412        3466 :                   need_to_switch = true;
     413             :                 }
     414             :               }
     415             : 
     416             :               std::pair<subdomain_id_type, subdomain_id_type> blocks_pair =
     417       35588 :                   std::make_pair(curr_elem_subid, connected_elem_subid);
     418             : 
     419             :               std::pair<subdomain_id_type, subdomain_id_type> blocks_pair2 =
     420       35588 :                   std::make_pair(connected_elem_subid, curr_elem_subid);
     421             : 
     422             :               auto add_boundary_sides =
     423       35382 :                   [&](const std::pair<subdomain_id_type, subdomain_id_type> & blocks_pair,
     424             :                       const std::pair<subdomain_id_type, subdomain_id_type> & blocks_pair2,
     425             :                       Elem * current_elem,
     426             :                       Elem * connected_elem,
     427             :                       unsigned int side,
     428             :                       unsigned int connected_elem_side,
     429             :                       bool need_to_switch)
     430             :               {
     431       35382 :                 _neighboring_block_list.insert(blocks_pair);
     432       35382 :                 _subid_pairs_to_sides[blocks_pair].emplace(
     433             :                     !need_to_switch ? current_elem : connected_elem, side);
     434       35382 :                 if (_add_interface_on_two_sides)
     435             :                 {
     436         851 :                   _neighboring_block_list.insert(blocks_pair2);
     437         851 :                   _subid_pairs_to_sides[blocks_pair2].emplace(
     438             :                       !need_to_switch ? connected_elem : current_elem, connected_elem_side);
     439             :                 }
     440       70970 :               };
     441             : 
     442       35588 :               if (_block_pairs_restricted)
     443             :               {
     444        1164 :                 if (findBlockPairs(blockRestrictedElementSubdomainID(current_elem),
     445        1164 :                                    blockRestrictedElementSubdomainID(connected_elem)))
     446         958 :                   add_boundary_sides(blocks_pair,
     447             :                                      blocks_pair2,
     448             :                                      current_elem,
     449             :                                      connected_elem,
     450             :                                      side,
     451             :                                      connected_elem_side,
     452             :                                      need_to_switch);
     453             :               }
     454             :               else
     455       34424 :                 add_boundary_sides(blocks_pair,
     456             :                                    blocks_pair2,
     457             :                                    current_elem,
     458             :                                    connected_elem,
     459             :                                    side,
     460             :                                    connected_elem_side,
     461             :                                    need_to_switch);
     462             :             }
     463             :           }
     464             :         }
     465             :       }
     466             : 
     467             :     } // end multiplicity check
     468       19075 :   } // end loop to duplicate nodes
     469             : 
     470         284 :   addInterface(*mesh);
     471             : 
     472             :   // We want to reset node processor ids, which may need to change
     473             :   // because of elements being disconnected, but we may have
     474             :   // completely disconnected some ghosted nodes on some processors
     475             :   // from all ghosted elements that should own them, in which case
     476             :   // libMesh on those processors may not be able to tell who to query
     477             :   // for an authoritative node processor id.  Let's just delete
     478             :   // orphaned nodes so we don't end up with invalid_processor_id on
     479             :   // some processors (confusing consistency checks).
     480         284 :   if (!mesh->is_serial())
     481          26 :     mesh->remove_orphaned_nodes();
     482         284 :   Partitioner::set_node_processor_ids(*mesh);
     483             : 
     484         284 :   mesh->unset_is_prepared();
     485             : 
     486         568 :   return dynamic_pointer_cast<MeshBase>(mesh);
     487         284 : }
     488             : 
     489             : void
     490         284 : BreakMeshByBlockGenerator::addInterface(MeshBase & mesh)
     491             : {
     492         284 :   BoundaryInfo & boundary_info = mesh.get_boundary_info();
     493             : 
     494             :   boundary_id_type boundary_id;
     495         284 :   boundary_id_type boundary_id_interface = Moose::INVALID_BOUNDARY_ID;
     496         284 :   boundary_id_type boundary_id_interface_transition = Moose::INVALID_BOUNDARY_ID;
     497             : 
     498         284 :   BoundaryName boundary_name;
     499             : 
     500         284 :   const std::set<boundary_id_type> & ids = boundary_info.get_boundary_ids();
     501         284 :   boundary_id_type new_boundaryID = ids.empty() ? 0 : *ids.rbegin() + 1;
     502             : 
     503             :   // Make sure the new boundary ID is the same on every processor
     504         284 :   mesh.comm().set_union(_neighboring_block_list);
     505         284 :   mesh.comm().max(new_boundaryID);
     506             :   mooseAssert(new_boundaryID >= 0, "Invalid new boundary ID computed.");
     507             : 
     508             :   // loop over boundary sides
     509             :   // All ranks will process the pairs in exactly the same order.
     510             :   std::vector<std::pair<subdomain_id_type, subdomain_id_type>> sorted_pairs(
     511         284 :       _neighboring_block_list.begin(), _neighboring_block_list.end());
     512             : 
     513        6270 :   for (auto & boundary_side : sorted_pairs)
     514             :   {
     515        5986 :     if (!(_block_pairs_restricted || _surrounding_blocks_restricted) ||
     516        4896 :         ((_block_pairs_restricted || _surrounding_blocks_restricted) && !_add_transition_interface))
     517             :     {
     518             :       // find the appropriate boundary name and id
     519             :       // given primary and secondary block ID
     520        3136 :       if (_split_interface)
     521             :       {
     522        1754 :         boundary_id = new_boundaryID;
     523        1754 :         findBoundaryName(mesh,
     524        1754 :                          boundary_side.first,
     525        1754 :                          boundary_side.second,
     526             :                          boundary_name,
     527             :                          boundary_id,
     528             :                          boundary_info);
     529             :       }
     530             :       else
     531             :       {
     532             :         // When _split_interface == false, all pairs share the same boundary_id_interface
     533             :         // (This would cause self-pairing)
     534        1382 :         boundary_name = _interface_name;
     535             :         // assign a unique boundary ID for the interface boundary
     536        2764 :         boundary_id_interface = boundary_id_interface == Moose::INVALID_BOUNDARY_ID
     537        1382 :                                     ? new_boundaryID
     538             :                                     : boundary_id_interface;
     539        1382 :         boundary_id = boundary_id_interface;
     540        1382 :         boundary_info.sideset_name(boundary_id_interface) = boundary_name;
     541             :       }
     542             :     }
     543             :     else // block resticted with transition boundary
     544             :     {
     545             : 
     546        5160 :       const bool interior_boundary = _block_set.find(boundary_side.first) != _block_set.end() &&
     547        2310 :                                      _block_set.find(boundary_side.second) != _block_set.end();
     548             : 
     549        2850 :       if ((_split_interface && interior_boundary) ||
     550        1650 :           (_split_transition_interface && !interior_boundary))
     551             :       {
     552        1900 :         boundary_id = new_boundaryID;
     553        1900 :         findBoundaryName(mesh,
     554        1900 :                          boundary_side.first,
     555        1900 :                          boundary_side.second,
     556             :                          boundary_name,
     557             :                          boundary_id,
     558             :                          boundary_info);
     559             :       }
     560         950 :       else if (interior_boundary)
     561             :       {
     562         600 :         boundary_name = _interface_name;
     563             :         // assign a unique boundary ID for the interface boundary
     564        1200 :         boundary_id_interface = boundary_id_interface == Moose::INVALID_BOUNDARY_ID
     565         600 :                                     ? new_boundaryID
     566             :                                     : boundary_id_interface;
     567             : 
     568         600 :         boundary_id = boundary_id_interface;
     569         600 :         boundary_info.sideset_name(boundary_id_interface) = boundary_name;
     570             :       }
     571             :       else
     572             :       {
     573             :         // assign a unique boundary ID for the interface transition boundary
     574         350 :         boundary_id_interface_transition =
     575         350 :             boundary_id_interface_transition == Moose::INVALID_BOUNDARY_ID
     576         350 :                 ? new_boundaryID
     577             :                 : boundary_id_interface_transition;
     578         350 :         boundary_id = boundary_id_interface_transition;
     579         350 :         boundary_info.sideset_name(boundary_id) = _interface_transition_name;
     580             :       }
     581             :     }
     582             : 
     583             :     // Map the block pair (subdomain_id_A, subdomain_id_B) to the generated boundary_id
     584        5986 :     _subid_pairs_to_boundary_id[boundary_side] = boundary_id;
     585             : 
     586             :     // loop over all the side belonging to each pair and add it to the proper interface
     587        5986 :     auto boundary_side_map = _subid_pairs_to_sides.find(boundary_side);
     588        5986 :     if (boundary_side_map != _subid_pairs_to_sides.end())
     589       15949 :       for (const auto & [elem, side] : boundary_side_map->second)
     590       10254 :         boundary_info.add_side(elem, side, boundary_id);
     591        5986 :     new_boundaryID++;
     592             :   }
     593             : 
     594             :   // Sync boundary info
     595         284 :   boundary_info.parallel_sync_side_ids();
     596         284 :   boundary_info.parallel_sync_node_ids();
     597             : 
     598             :   // Generate boundary_id pairs mapping based on _subid_pairs_to_sides
     599        6270 :   for (const auto & [sub_pair, boundary_id] : _subid_pairs_to_boundary_id)
     600             :   {
     601        5986 :     const auto rev_pair = std::make_pair(sub_pair.second, sub_pair.first);
     602             :     const bool has_reverse =
     603        5986 :         _subid_pairs_to_boundary_id.find(rev_pair) != _subid_pairs_to_boundary_id.end();
     604        5986 :     if (has_reverse)
     605             :       // Normal disconnected boundary pair: blockA_blockB <-> blockB_blockA
     606         144 :       mesh.add_disjoint_neighbor_boundary_pairs(
     607         144 :           boundary_id, _subid_pairs_to_boundary_id[rev_pair], RealVectorValue(0.0, 0.0, 0.0));
     608             :   }
     609         284 : }
     610             : 
     611             : subdomain_id_type
     612     1665160 : BreakMeshByBlockGenerator::blockRestrictedElementSubdomainID(const Elem * elem)
     613             : {
     614     1665160 :   subdomain_id_type elem_subdomain_id = elem->subdomain_id();
     615     1922309 :   if ((_block_pairs_restricted || _surrounding_blocks_restricted) &&
     616     1922309 :       (_block_set.find(elem_subdomain_id) == _block_set.end()))
     617       85426 :     elem_subdomain_id = Elem::invalid_subdomain_id;
     618             : 
     619     1665160 :   return elem_subdomain_id;
     620             : }
     621             : 
     622             : bool
     623        2781 : BreakMeshByBlockGenerator::findBlockPairs(subdomain_id_type block_one, subdomain_id_type block_two)
     624             : {
     625        4735 :   for (auto block_pair : _block_pairs)
     626        3355 :     if ((block_pair.first == block_one && block_pair.second == block_two) ||
     627        1954 :         (block_pair.first == block_two && block_pair.second == block_one))
     628        1401 :       return true;
     629        1380 :   return false;
     630             : }
     631             : 
     632             : std::unordered_map<dof_id_type, std::set<subdomain_id_type>>
     633         284 : BreakMeshByBlockGenerator::syncConnectedBlocks(
     634             :     const std::unordered_map<dof_id_type, std::vector<dof_id_type>> & node_to_elem_map,
     635             :     const MeshBase & mesh)
     636             : {
     637         284 :   std::unordered_map<dof_id_type, std::set<subdomain_id_type>> nodeid_to_connected_blocks;
     638             : 
     639             :   // Phase 0: Each rank computes its local connected blocks for nodes it holds
     640       19359 :   for (const auto & [node_id, elem_ids] : node_to_elem_map)
     641             :   {
     642       19075 :     std::set<subdomain_id_type> connected_blocks;
     643      123090 :     for (const dof_id_type elem_id : elem_ids)
     644             :     {
     645      104015 :       const Elem * current_elem = mesh.elem_ptr(elem_id);
     646      104015 :       subdomain_id_type block_id = blockRestrictedElementSubdomainID(current_elem);
     647      104015 :       nodeid_to_connected_blocks[node_id].insert(block_id);
     648             :     }
     649       19075 :   }
     650             : 
     651             :   // No synchronization needed for replicated meshes
     652         284 :   if (mesh.is_replicated())
     653         234 :     return nodeid_to_connected_blocks;
     654             : 
     655          50 :   auto & comm = mesh.comm();
     656          50 :   const auto mesh_pid = mesh.processor_id();
     657             : 
     658             :   // Phase 1: Ghost nodes push their connected blocks to the owner
     659             : 
     660             :   // (node_id, connected_blocks, ghost_pid)
     661             :   using NodeConnectedBlocksTuple =
     662             :       std::tuple<dof_id_type, std::vector<subdomain_id_type>, processor_id_type>;
     663          50 :   std::map<processor_id_type, std::vector<NodeConnectedBlocksTuple>> to_owner;
     664        2901 :   for (const auto & [node_id, blocks] : nodeid_to_connected_blocks)
     665             :   {
     666        2851 :     const Node * node = mesh.node_ptr(node_id);
     667        2851 :     if (node && node->processor_id() != mesh_pid)
     668        2802 :       to_owner[node->processor_id()].emplace_back(
     669        4203 :           node_id, std::vector<subdomain_id_type>(blocks.begin(), blocks.end()), mesh_pid);
     670             :   }
     671             : 
     672             :   // Owner receives and merges ghost info into its local map, and tracks subscribers
     673          50 :   std::unordered_map<dof_id_type, std::set<processor_id_type>> subscribers;
     674          50 :   Parallel::push_parallel_vector_data(
     675             :       comm,
     676             :       to_owner,
     677          50 :       [&](processor_id_type, const std::vector<NodeConnectedBlocksTuple> & recv_data)
     678             :       {
     679        1571 :         for (const auto & [node_id, blocks_vec, ghost_pid] : recv_data)
     680             :         {
     681        1401 :           subscribers[node_id].insert(ghost_pid);
     682        1401 :           nodeid_to_connected_blocks[node_id].insert(blocks_vec.begin(), blocks_vec.end());
     683             :         }
     684         170 :       });
     685             : 
     686             :   // Phase 2: Node owners broadcast complete connected blocks back to subscribers
     687          50 :   std::map<processor_id_type, std::vector<NodeConnectedBlocksPair>> from_owner;
     688         851 :   for (const auto & [node_id, sub_pids] : subscribers)
     689             :   {
     690         801 :     const Node * node = mesh.node_ptr(node_id);
     691         801 :     if (node && node->processor_id() == mesh_pid)
     692             :     {
     693         801 :       const auto & blocks_set = libmesh_map_find(nodeid_to_connected_blocks, node_id);
     694        2202 :       for (const auto pid : sub_pids)
     695        2802 :         from_owner[pid].emplace_back(
     696        4203 :             node_id, std::vector<subdomain_id_type>(blocks_set.begin(), blocks_set.end()));
     697             :     }
     698             :   }
     699             : 
     700          50 :   Parallel::push_parallel_vector_data(
     701             :       comm,
     702             :       from_owner,
     703          50 :       [&](processor_id_type, const std::vector<NodeConnectedBlocksPair> & recv_data)
     704             :       {
     705        1571 :         for (const auto & [node_id, blocks_vec] : recv_data)
     706        1401 :           nodeid_to_connected_blocks[node_id].insert(blocks_vec.begin(), blocks_vec.end());
     707         170 :       });
     708          50 :   return nodeid_to_connected_blocks;
     709          50 : }

Generated by: LCOV version 1.14