LCOV - code coverage report
Current view: top level - src/meshgenerators - StitchedMeshGenerator.C (source / functions) Hit Total Coverage
Test: idaholab/moose framework: 2bf808 Lines: 87 119 73.1 %
Date: 2025-07-17 01:28:37 Functions: 3 3 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 "StitchedMeshGenerator.h"
      11             : 
      12             : #include "CastUniquePointer.h"
      13             : #include "MooseUtils.h"
      14             : #include "MooseMeshUtils.h"
      15             : 
      16             : #include "libmesh/unstructured_mesh.h"
      17             : 
      18             : registerMooseObject("MooseApp", StitchedMeshGenerator);
      19             : 
      20             : InputParameters
      21       29036 : StitchedMeshGenerator::validParams()
      22             : {
      23       29036 :   InputParameters params = MeshGenerator::validParams();
      24             : 
      25       29036 :   MooseEnum algorithm("BINARY EXHAUSTIVE", "BINARY");
      26             : 
      27       29036 :   params.addRequiredParam<std::vector<MeshGeneratorName>>("inputs", "The input MeshGenerators.");
      28       87108 :   params.addParam<bool>(
      29       58072 :       "clear_stitched_boundary_ids", true, "Whether or not to clear the stitched boundary IDs");
      30       29036 :   params.addRequiredParam<std::vector<std::vector<std::string>>>(
      31             :       "stitch_boundaries_pairs",
      32             :       "Pairs of boundaries to be stitched together between the 1st mesh in inputs and each "
      33             :       "consecutive mesh");
      34       29036 :   params.addParam<MooseEnum>(
      35             :       "algorithm",
      36             :       algorithm,
      37             :       "Control the use of binary search for the nodes of the stitched surfaces.");
      38       87108 :   params.addParam<bool>("prevent_boundary_ids_overlap",
      39       58072 :                         true,
      40             :                         "Whether to re-number boundaries in stitched meshes to prevent merging of "
      41             :                         "unrelated boundaries");
      42       87108 :   params.addParam<bool>(
      43             :       "merge_boundaries_with_same_name",
      44       58072 :       true,
      45             :       "If the input meshes have boundaries with the same name (but different IDs), merge them");
      46       87108 :   params.addParam<bool>(
      47             :       "subdomain_remapping",
      48       58072 :       true,
      49             :       "Treat input subdomain names as primary, preserving them and remapping IDs as needed");
      50       87108 :   params.addParam<bool>(
      51       58072 :       "verbose_stitching", false, "Whether mesh stitching should have verbose output.");
      52       29036 :   params.addClassDescription(
      53             :       "Allows multiple mesh files to be stitched together to form a single mesh.");
      54             : 
      55       58072 :   return params;
      56       29036 : }
      57             : 
      58         242 : StitchedMeshGenerator::StitchedMeshGenerator(const InputParameters & parameters)
      59             :   : MeshGenerator(parameters),
      60         242 :     _mesh_ptrs(getMeshes("inputs")),
      61         242 :     _input_names(getParam<std::vector<MeshGeneratorName>>("inputs")),
      62         242 :     _clear_stitched_boundary_ids(getParam<bool>("clear_stitched_boundary_ids")),
      63         242 :     _stitch_boundaries_pairs(
      64             :         getParam<std::vector<std::vector<std::string>>>("stitch_boundaries_pairs")),
      65         242 :     _algorithm(parameters.get<MooseEnum>("algorithm")),
      66         242 :     _prevent_boundary_ids_overlap(getParam<bool>("prevent_boundary_ids_overlap")),
      67         484 :     _merge_boundaries_with_same_name(getParam<bool>("merge_boundaries_with_same_name"))
      68             : {
      69         242 : }
      70             : 
      71             : std::unique_ptr<MeshBase>
      72         237 : StitchedMeshGenerator::generate()
      73             : {
      74             :   // We put the first mesh in a local pointer
      75         237 :   std::unique_ptr<UnstructuredMesh> mesh = dynamic_pointer_cast<UnstructuredMesh>(*_mesh_ptrs[0]);
      76         237 :   if (!mesh) // This should never happen until libMesh implements on-the-fly-Elem mesh types
      77           0 :     mooseError("StitchedMeshGenerator is only implemented for unstructured meshes");
      78             : 
      79             :   // Reserve spaces for the other meshes (no need to store the first one another time)
      80         237 :   std::vector<std::unique_ptr<UnstructuredMesh>> meshes(_mesh_ptrs.size() - 1);
      81             : 
      82             :   // Read in all of the other meshes
      83         532 :   for (MooseIndex(_mesh_ptrs) i = 1; i < _mesh_ptrs.size(); ++i)
      84         295 :     meshes[i - 1] = dynamic_pointer_cast<UnstructuredMesh>(*_mesh_ptrs[i]);
      85             : 
      86             :   // Stitch all the meshes to the first one
      87         528 :   for (MooseIndex(meshes) i = 0; i < meshes.size(); i++)
      88             :   {
      89         295 :     auto boundary_pair = _stitch_boundaries_pairs[i];
      90             : 
      91             :     boundary_id_type first, second;
      92             : 
      93             :     try
      94             :     {
      95         295 :       first = MooseUtils::convert<boundary_id_type>(boundary_pair[0], true);
      96             :     }
      97         202 :     catch (...)
      98             :     {
      99         202 :       first = mesh->get_boundary_info().get_id_by_name(boundary_pair[0]);
     100             : 
     101         202 :       if (first == BoundaryInfo::invalid_id)
     102             :       {
     103           0 :         std::stringstream error;
     104             : 
     105           0 :         error << "Boundary " << boundary_pair[0] << " doesn't exist on mesh '" << _input_names[0]
     106           0 :               << "' in generator " << name() << "\n";
     107           0 :         error << "Boundary (sideset) names that do exist: \n";
     108           0 :         error << " ID : Name\n";
     109             : 
     110           0 :         auto & sideset_id_name_map = mesh->get_boundary_info().get_sideset_name_map();
     111             : 
     112           0 :         for (auto & ss_name_map_pair : sideset_id_name_map)
     113           0 :           error << " " << ss_name_map_pair.first << " : " << ss_name_map_pair.second << "\n";
     114             : 
     115           0 :         error << "\nBoundary (nodeset) names that do exist: \n";
     116           0 :         error << " ID : Name\n";
     117             : 
     118           0 :         auto & nodeset_id_name_map = mesh->get_boundary_info().get_nodeset_name_map();
     119             : 
     120           0 :         for (auto & ns_name_map_pair : nodeset_id_name_map)
     121           0 :           error << " " << ns_name_map_pair.first << " : " << ns_name_map_pair.second << "\n";
     122             : 
     123           0 :         paramError("stitch_boundaries_pairs", error.str());
     124           0 :       }
     125         202 :     }
     126             : 
     127             :     try
     128             :     {
     129         295 :       second = MooseUtils::convert<boundary_id_type>(boundary_pair[1], true);
     130             :     }
     131         210 :     catch (...)
     132             :     {
     133         210 :       second = meshes[i]->get_boundary_info().get_id_by_name(boundary_pair[1]);
     134             : 
     135         210 :       if (second == BoundaryInfo::invalid_id)
     136             :       {
     137           0 :         meshes[i]->print_info();
     138             : 
     139           0 :         std::stringstream error;
     140             : 
     141           0 :         error << "Boundary " << boundary_pair[1] << " doesn't exist on mesh '" << _input_names[i]
     142           0 :               << "' in generator " << name() << "\n";
     143           0 :         error << "Boundary (sideset) names that do exist: \n";
     144           0 :         error << " ID : Name\n";
     145             : 
     146           0 :         auto & sideset_id_name_map = meshes[i]->get_boundary_info().get_sideset_name_map();
     147             : 
     148           0 :         for (auto & ss_name_map_pair : sideset_id_name_map)
     149           0 :           error << " " << ss_name_map_pair.first << " : " << ss_name_map_pair.second << "\n";
     150             : 
     151           0 :         error << "\nBoundary (nodeset) names that do exist: \n";
     152           0 :         error << " ID : Name\n";
     153             : 
     154           0 :         auto & nodeset_id_name_map = mesh->get_boundary_info().get_nodeset_name_map();
     155             : 
     156           0 :         for (auto & ns_name_map_pair : nodeset_id_name_map)
     157           0 :           error << " " << ns_name_map_pair.first << " : " << ns_name_map_pair.second << "\n";
     158             : 
     159           0 :         paramError("stitch_boundaries_pairs", error.str());
     160           0 :       }
     161         210 :     }
     162             : 
     163         295 :     const bool use_binary_search = (_algorithm == "BINARY");
     164             : 
     165             :     // Meshes must be prepared to get the global boundary ids
     166         295 :     if (!mesh->is_prepared())
     167         168 :       mesh->prepare_for_use();
     168         295 :     if (!meshes[i]->is_prepared())
     169         214 :       meshes[i]->prepare_for_use();
     170             : 
     171             :     // Avoid chaotic boundary merging due to overlapping ids in meshes by simply renumbering the
     172             :     // boundaries in the meshes we are going to stitch onto the base mesh
     173             :     // This is only done if there is an overlap
     174         295 :     if (_prevent_boundary_ids_overlap)
     175             :     {
     176         210 :       const auto & base_mesh_bids = mesh->get_boundary_info().get_global_boundary_ids();
     177         210 :       const auto stitched_mesh_bids = meshes[i]->get_boundary_info().get_global_boundary_ids();
     178             : 
     179             :       // Check for an overlap
     180         210 :       bool overlap_found = false;
     181         954 :       for (const auto & bid : stitched_mesh_bids)
     182         744 :         if (base_mesh_bids.count(bid))
     183         712 :           overlap_found = true;
     184             : 
     185         210 :       if (overlap_found)
     186             :       {
     187         194 :         const auto max_boundary_id = *base_mesh_bids.rbegin();
     188         194 :         BoundaryID new_index = 1;
     189         938 :         for (const auto bid : stitched_mesh_bids)
     190             :         {
     191         744 :           const auto new_bid = max_boundary_id + (new_index++);
     192         744 :           meshes[i]->get_boundary_info().renumber_id(bid, new_bid);
     193         744 :           if (bid == second)
     194         194 :             second = new_bid;
     195             :         }
     196             :       }
     197         210 :     }
     198             :     else
     199             :     {
     200             :       // If we don't have renumbering, we can get into situations where the same boundary ID is
     201             :       // associated with different boundary names. In this case when we stitch, we assign the
     202             :       // boundary name of the first mesh to the ID everywhere on the domain. We throw a warning
     203             :       // here to warn the user if this is the case.
     204          85 :       const auto & base_mesh_bids = mesh->get_boundary_info().get_global_boundary_ids();
     205          85 :       const auto & other_mesh_bids = meshes[i]->get_boundary_info().get_global_boundary_ids();
     206             : 
     207             :       // We check if the same ID is present with different names
     208          85 :       std::set<boundary_id_type> bd_id_intersection;
     209          85 :       std::set_intersection(base_mesh_bids.begin(),
     210             :                             base_mesh_bids.end(),
     211             :                             other_mesh_bids.begin(),
     212             :                             other_mesh_bids.end(),
     213             :                             std::inserter(bd_id_intersection, bd_id_intersection.begin()));
     214             : 
     215         374 :       for (const auto bid : bd_id_intersection)
     216             :       {
     217         293 :         const auto & sideset_name_on_first_mesh = mesh->get_boundary_info().get_sideset_name(bid);
     218             :         const auto & sideset_name_on_second_mesh =
     219         293 :             meshes[i]->get_boundary_info().get_sideset_name(bid);
     220             : 
     221         293 :         if (sideset_name_on_first_mesh != sideset_name_on_second_mesh)
     222          82 :           mooseWarning(
     223             :               "Boundary ID ",
     224             :               bid,
     225             :               " corresponds to different boundary names on the input meshes! On the first "
     226             :               "mesh it corresponds to `",
     227             :               sideset_name_on_first_mesh,
     228             :               "` while on the second mesh it corresponds to `",
     229             :               sideset_name_on_second_mesh,
     230             :               "`. The final mesh will replace boundary `",
     231             :               sideset_name_on_second_mesh,
     232             :               "` with `",
     233             :               sideset_name_on_first_mesh,
     234             :               "`. To avoid this situation, use the `prevent_boundary_ids_overlap` parameter!");
     235             :       }
     236          81 :     }
     237             : 
     238         582 :     mesh->stitch_meshes(*meshes[i],
     239             :                         first,
     240             :                         second,
     241             :                         TOLERANCE,
     242         291 :                         _clear_stitched_boundary_ids,
     243         582 :                         getParam<bool>("verbose_stitching"),
     244             :                         use_binary_search,
     245             :                         /*enforce_all_nodes_match_on_boundaries=*/false,
     246             :                         /*merge_boundary_nodes_all_or_nothing=*/false,
     247         582 :                         getParam<bool>("subdomain_remapping"));
     248             : 
     249         291 :     if (_merge_boundaries_with_same_name)
     250         185 :       MooseMeshUtils::mergeBoundaryIDsWithSameName(*mesh);
     251         291 :   }
     252             : 
     253         233 :   mesh->set_isnt_prepared();
     254         466 :   return dynamic_pointer_cast<MeshBase>(mesh);
     255         233 : }

Generated by: LCOV version 1.14