LCOV - code coverage report
Current view: top level - src/meshgenerators - PatternedMeshGenerator.C (source / functions) Hit Total Coverage
Test: idaholab/moose framework: 2bf808 Lines: 125 135 92.6 %
Date: 2025-07-17 01:28:37 Functions: 4 4 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 "PatternedMeshGenerator.h"
      11             : 
      12             : #include "CastUniquePointer.h"
      13             : 
      14             : #include "libmesh/replicated_mesh.h"
      15             : #include "libmesh/distributed_mesh.h"
      16             : #include "libmesh/boundary_info.h"
      17             : #include "libmesh/mesh_modification.h"
      18             : #include "libmesh/mesh_tools.h"
      19             : #include "MooseMeshUtils.h"
      20             : 
      21             : registerMooseObject("MooseApp", PatternedMeshGenerator);
      22             : 
      23             : InputParameters
      24       14321 : PatternedMeshGenerator::validParams()
      25             : {
      26       14321 :   InputParameters params = MeshGenerator::validParams();
      27             : 
      28       14321 :   params.addRequiredParam<std::vector<MeshGeneratorName>>("inputs", "The input MeshGenerators.");
      29       14321 :   params.addRangeCheckedParam<Real>(
      30             :       "x_width", 0, "x_width>=0", "The tile width in the x direction");
      31       14321 :   params.addRangeCheckedParam<Real>(
      32             :       "y_width", 0, "y_width>=0", "The tile width in the y direction");
      33       14321 :   params.addRangeCheckedParam<Real>(
      34             :       "z_width", 0, "z_width>=0", "The tile width in the z direction");
      35             : 
      36             :   // Boundaries : user has to provide id or name for each boundary
      37             : 
      38             :   // x boundary names
      39       14321 :   params.addParam<BoundaryName>("left_boundary", "left", "name of the left (x) boundary");
      40       14321 :   params.addParam<BoundaryName>("right_boundary", "right", "name of the right (x) boundary");
      41             : 
      42             :   // y boundary names
      43       14321 :   params.addParam<BoundaryName>("top_boundary", "top", "name of the top (y) boundary");
      44       14321 :   params.addParam<BoundaryName>("bottom_boundary", "bottom", "name of the bottom (y) boundary");
      45             : 
      46       14321 :   params.addRequiredParam<std::vector<std::vector<unsigned int>>>(
      47             :       "pattern", "A double-indexed array starting with the upper-left corner");
      48             : 
      49       14321 :   params.addClassDescription("Creates a 2D mesh from a specified set of unique 'tiles' meshes and "
      50             :                              "a two-dimensional pattern.");
      51             : 
      52       14321 :   return params;
      53           0 : }
      54             : 
      55          28 : PatternedMeshGenerator::PatternedMeshGenerator(const InputParameters & parameters)
      56             :   : MeshGenerator(parameters),
      57          28 :     _input_names(getParam<std::vector<MeshGeneratorName>>("inputs")),
      58          28 :     _mesh_ptrs(getMeshes("inputs")),
      59          28 :     _pattern(getParam<std::vector<std::vector<unsigned int>>>("pattern")),
      60          28 :     _x_width(getParam<Real>("x_width")),
      61          28 :     _y_width(getParam<Real>("y_width")),
      62          84 :     _z_width(getParam<Real>("z_width"))
      63             : {
      64         204 :   for (MooseIndex(_pattern) i = 0; i < _pattern.size(); ++i)
      65        2272 :     for (MooseIndex(_pattern[i]) j = 0; j < _pattern[i].size(); ++j)
      66        2096 :       if (_pattern[i][j] >= _input_names.size())
      67           0 :         paramError("pattern",
      68           0 :                    "Index " + Moose::stringify(_pattern[i][j]) +
      69             :                        " is larger than the the maximum possible index, which is determined by the "
      70             :                        "number of MeshGenerators provided in inputs");
      71          28 : }
      72             : 
      73             : std::unique_ptr<MeshBase>
      74          28 : PatternedMeshGenerator::generate()
      75             : {
      76             :   // Reserve spaces for all the meshes
      77          28 :   _meshes.reserve(_input_names.size());
      78             : 
      79             :   // Getting the boundaries provided by the user
      80             :   const std::vector<BoundaryName> boundary_names = {getParam<BoundaryName>("left_boundary"),
      81             :                                                     getParam<BoundaryName>("right_boundary"),
      82             :                                                     getParam<BoundaryName>("top_boundary"),
      83         140 :                                                     getParam<BoundaryName>("bottom_boundary")};
      84             :   const std::vector<std::string> boundary_param_names = {
      85         140 :       "left_boundary", "right_boundary", "top_boundary", "bottom_boundary"};
      86             : 
      87             :   // IDs for each input (indexed by input mesh and then left/right/top/bottom)
      88             :   std::vector<std::vector<boundary_id_type>> input_bids(
      89          28 :       _input_names.size(), std::vector<boundary_id_type>(4, Moose::INVALID_BOUNDARY_ID));
      90          28 :   bool have_common_ids = true;
      91             : 
      92             :   // Using a vector of vectors instead of vector of sets to preserve insertion order
      93          28 :   std::vector<std::vector<boundary_id_type>> input_bids_unique(_input_names.size());
      94             : 
      95             :   // For an error check on the number of uniquely named boundaries to stitch
      96          28 :   size_t set_length = 0;
      97             : 
      98             :   // Given a boundary name (i.e., 'left_boundary'), this will give the correct
      99             :   // index to get the correct boundary id to later use for boundary stitching
     100          28 :   std::map<std::string, size_t> boundary_name_to_index_map;
     101             : 
     102             :   // Keep track of used boundary ids to generate new, unused ones later (if needed)
     103          28 :   std::set<boundary_id_type> all_boundary_ids;
     104             : 
     105             :   // Read in all of the meshes
     106          28 :   _meshes.resize(_input_names.size());
     107          76 :   for (const auto i : index_range(_input_names))
     108             :   {
     109          52 :     std::unique_ptr<ReplicatedMesh> mesh = dynamic_pointer_cast<ReplicatedMesh>(*_mesh_ptrs[i]);
     110          52 :     if (!mesh)
     111           0 :       paramError("inputs",
     112             :                  "The input mesh '",
     113           0 :                  _input_names[i],
     114             :                  "' is not a replicated mesh.\n\n",
     115           0 :                  type(),
     116             :                  " only works with inputs that are replicated.\n\n",
     117             :                  "Try running without distributed mesh.");
     118          52 :     _meshes[i] = dynamic_pointer_cast<ReplicatedMesh>(mesh);
     119             : 
     120             :     // List of boundary ids corresponsind to left/right/top/bottom boundary names
     121          52 :     const auto ids = MooseMeshUtils::getBoundaryIDs(*_meshes[i], boundary_names, false);
     122             :     mooseAssert(ids.size() == boundary_names.size(),
     123             :                 "Unexpected number of ids returned for MooseMeshUtils::getBoundaryIDs");
     124             : 
     125             :     // Keep track of indices of first instance of each unique boundary id
     126          52 :     std::map<boundary_id_type, size_t> seen_bid_to_index_map;
     127             : 
     128          52 :     size_t index = 0;
     129         248 :     for (const auto side : make_range(4))
     130             :     {
     131             :       // Check if the boundary has been initialized
     132         200 :       if (ids[side] == Moose::INVALID_BOUNDARY_ID)
     133          12 :         paramError("inputs",
     134             :                    "The '",
     135           4 :                    boundary_param_names[side],
     136             :                    "' parameter with value '",
     137           4 :                    boundary_names[side],
     138             :                    "' does not exist in input mesh '",
     139           4 :                    _input_names[i],
     140             :                    "'");
     141             : 
     142         196 :       input_bids[i][side] = ids[side];
     143             : 
     144             :       // We only do this when i == 0 because all input meshes should have the
     145             :       // same index map. Allowing different index maps for different input
     146             :       // meshes results in undefined behaviour when stitching
     147         196 :       if (i == 0)
     148             :       {
     149         100 :         if (std::count(input_bids_unique[i].begin(), input_bids_unique[i].end(), ids[side]) == 0)
     150             :         {
     151         100 :           input_bids_unique[i].push_back(ids[side]);
     152         100 :           seen_bid_to_index_map[ids[side]] = index;
     153         100 :           boundary_name_to_index_map[boundary_param_names[side]] = index++;
     154             :         }
     155             :         else
     156           0 :           boundary_name_to_index_map[boundary_param_names[side]] = seen_bid_to_index_map[ids[side]];
     157             :       }
     158             : 
     159             :       else // i > 0
     160             :       {
     161          96 :         if (ids[side] != input_bids[i - 1][side])
     162          32 :           have_common_ids = false;
     163             : 
     164          96 :         if (std::count(input_bids_unique[i].begin(), input_bids_unique[i].end(), ids[side]) == 0)
     165          96 :           input_bids_unique[i].push_back(ids[side]);
     166             :       }
     167             :     }
     168             : 
     169             :     // Error check on lengths of input_bids_unique
     170          48 :     if (i > 0 && set_length != input_bids_unique.size())
     171           0 :       mooseError(
     172             :           "Input meshes have incompatible boundary ids. This can occur when input meshes have "
     173             :           "the same boundary id for multiple boundaries, but in a way that is different "
     174             :           "between the meshes. Try assigning each left/right/top/bottom to its own boundary id.");
     175             : 
     176          48 :     set_length = input_bids_unique.size();
     177             : 
     178             :     // List of all boundary ids used in _meshes[i] so we don't reuse any existing boundary ids.
     179          48 :     const auto all_ids = _meshes[i]->get_boundary_info().get_boundary_ids();
     180             : 
     181             :     // Keep track of used IDs so we can later find IDs that are unused across all meshes
     182          48 :     all_boundary_ids.insert(all_ids.begin(), all_ids.end());
     183          48 :   }
     184             : 
     185             :   // Check if the user has provided the x, y and z widths.
     186             :   // If not (their value is 0 by default), compute them
     187          24 :   auto bbox = MeshTools::create_bounding_box(*_meshes[0]);
     188          24 :   if (_x_width == 0)
     189          24 :     _x_width = bbox.max()(0) - bbox.min()(0);
     190          24 :   if (_y_width == 0)
     191          24 :     _y_width = bbox.max()(1) - bbox.min()(1);
     192          24 :   if (_z_width == 0)
     193          24 :     _z_width = bbox.max()(2) - bbox.min()(2);
     194             : 
     195             :   // stitch_bids will hold boundary ids passed to mesh stitcher
     196          24 :   std::vector<boundary_id_type> stitch_bids;
     197             : 
     198          24 :   if (have_common_ids) // No need to change existing boundary ids
     199          16 :     stitch_bids = input_bids_unique[0];
     200             : 
     201             :   else // Need to make boundary ids common accross all inputs
     202             :   {
     203             :     // Generate previously unused boundary ids
     204          72 :     for (boundary_id_type id = 0; id != Moose::INVALID_BOUNDARY_ID; ++id)
     205          72 :       if (!all_boundary_ids.count(id))
     206             :       {
     207          32 :         stitch_bids.push_back(id);
     208             :         // It is okay to only use the 0th index here, since we ensure all entries in
     209             :         // input_bids_unique have the same size through the above error check.
     210          32 :         if (stitch_bids.size() == input_bids_unique[0].size())
     211           8 :           break;
     212             :       }
     213             : 
     214             :     // Make all inputs have common boundary ids
     215          24 :     for (const auto i : index_range(_meshes))
     216          80 :       for (const auto side : index_range(stitch_bids))
     217          64 :         MeshTools::Modification::change_boundary_id(
     218          64 :             *_meshes[i], input_bids_unique[i][side], stitch_bids[side]);
     219             :   }
     220             : 
     221             :   // Data structure that holds each row
     222          24 :   _row_meshes.resize(_pattern.size());
     223             : 
     224             :   // Aliases
     225          24 :   const boundary_id_type &left_bid(stitch_bids[boundary_name_to_index_map["left_boundary"]]),
     226          24 :       right_bid(stitch_bids[boundary_name_to_index_map["right_boundary"]]),
     227          24 :       top_bid(stitch_bids[boundary_name_to_index_map["top_boundary"]]),
     228          24 :       bottom_bid(stitch_bids[boundary_name_to_index_map["bottom_boundary"]]);
     229             : 
     230             :   // Build each row mesh
     231         152 :   for (MooseIndex(_pattern) i = 0; i < _pattern.size(); ++i)
     232        1552 :     for (MooseIndex(_pattern[i]) j = 0; j < _pattern[i].size(); ++j)
     233             :     {
     234        1424 :       Real deltax = j * _x_width, deltay = i * _y_width;
     235             : 
     236             :       // If this is the first cell of the row initialize the row mesh
     237        1424 :       if (j == 0)
     238             :       {
     239         128 :         auto clone = _meshes[_pattern[i][j]]->clone();
     240         128 :         _row_meshes[i] = dynamic_pointer_cast<ReplicatedMesh>(clone);
     241             : 
     242         128 :         MeshTools::Modification::translate(*_row_meshes[i], deltax, -deltay, 0);
     243             : 
     244         128 :         continue;
     245         128 :       }
     246             : 
     247        1296 :       ReplicatedMesh & cell_mesh = *_meshes[_pattern[i][j]];
     248             : 
     249             :       // Move the mesh into the right spot.  -i because we are starting at the top
     250        1296 :       MeshTools::Modification::translate(cell_mesh, deltax, -deltay, 0);
     251             : 
     252             :       // Subdomain map is aggregated on each row first. This retrieves a writable reference
     253        1296 :       auto & main_subdomain_map = _row_meshes[i]->set_subdomain_name_map();
     254             :       // Retrieve subdomain name map from the mesh to be stitched and merge into the row's
     255             :       // subdomain map
     256        1296 :       const auto & increment_subdomain_map = cell_mesh.get_subdomain_name_map();
     257        1296 :       mergeSubdomainNameMaps(main_subdomain_map, increment_subdomain_map);
     258             : 
     259        1296 :       _row_meshes[i]->stitch_meshes(cell_mesh,
     260             :                                     right_bid,
     261        1296 :                                     left_bid,
     262             :                                     TOLERANCE,
     263             :                                     /*clear_stitched_boundary_ids=*/true,
     264             :                                     /*verbose=*/false);
     265             : 
     266             :       // Undo the translation
     267        1296 :       MeshTools::Modification::translate(cell_mesh, -deltax, deltay, 0);
     268             :     }
     269             : 
     270             :   // Now stitch together the rows
     271             :   // We're going to stitch them all to row 0 (which is the real mesh)
     272         128 :   for (MooseIndex(_pattern) i = 1; i < _pattern.size(); i++)
     273             :   {
     274             :     // Get a writeable reference subdomain-name map for the main mesh to which the other rows are
     275             :     // stitched
     276         104 :     auto & main_subdomain_map = _row_meshes[0]->set_subdomain_name_map();
     277             :     // Retrieve subdomain name map from the mesh to be stitched and merge into the main
     278             :     // subdomain map
     279         104 :     const auto & increment_subdomain_map = _row_meshes[i]->get_subdomain_name_map();
     280         104 :     mergeSubdomainNameMaps(main_subdomain_map, increment_subdomain_map);
     281             : 
     282         104 :     _row_meshes[0]->stitch_meshes(*_row_meshes[i],
     283             :                                   bottom_bid,
     284             :                                   top_bid,
     285             :                                   TOLERANCE,
     286             :                                   /*clear_stitched_boundary_ids=*/true,
     287             :                                   /*verbose=*/false);
     288             :   }
     289             : 
     290             :   // Change boundary ids back to those of meshes[0] to not surprise user
     291          24 :   if (!have_common_ids)
     292          40 :     for (const auto side : index_range(stitch_bids))
     293          32 :       MeshTools::Modification::change_boundary_id(
     294          32 :           *_row_meshes[0], stitch_bids[side], input_bids_unique[0][side]);
     295             : 
     296          24 :   _row_meshes[0]->set_isnt_prepared();
     297          48 :   return dynamic_pointer_cast<MeshBase>(_row_meshes[0]);
     298         108 : }
     299             : 
     300             : void
     301        1400 : PatternedMeshGenerator::mergeSubdomainNameMaps(
     302             :     std::map<subdomain_id_type, std::string> & main_subdomain_map,
     303             :     const std::map<subdomain_id_type, std::string> & increment_subdomain_map)
     304             : {
     305             :   // Insert secondary subdomain map into main subdomain map
     306        1400 :   main_subdomain_map.insert(increment_subdomain_map.begin(), increment_subdomain_map.end());
     307             :   // Check if one SubdomainName is shared by more than one subdomain ids
     308        1400 :   std::set<SubdomainName> main_subdomain_map_name_list;
     309        1512 :   for (auto const & id_name_pair : main_subdomain_map)
     310             :   {
     311         112 :     const auto name_to_insert = id_name_pair.second;
     312         112 :     if (main_subdomain_map_name_list.find(name_to_insert) != main_subdomain_map_name_list.end())
     313           0 :       paramError("inputs",
     314           0 :                  "Two of the input meshes contain a subdomain with the name '" + name_to_insert +
     315             :                      "' which corresponds to two conflicting subdomain ids.");
     316         112 :     main_subdomain_map_name_list.emplace(name_to_insert);
     317         112 :   }
     318        1400 : }

Generated by: LCOV version 1.14