LCOV - code coverage report
Current view: top level - src/meshgenerators - PatchSidesetGenerator.C (source / functions) Hit Total Coverage
Test: idaholab/moose heat_transfer: #31405 (292dce) with base fef103 Lines: 145 153 94.8 %
Date: 2025-09-04 07:53:51 Functions: 7 7 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 "PatchSidesetGenerator.h"
      11             : #include "InputParameters.h"
      12             : #include "MooseTypes.h"
      13             : #include "CastUniquePointer.h"
      14             : #include "MooseUtils.h"
      15             : #include "MooseMeshUtils.h"
      16             : 
      17             : #include "libmesh/distributed_mesh.h"
      18             : #include "libmesh/elem.h"
      19             : #include "libmesh/linear_partitioner.h"
      20             : #include "libmesh/centroid_partitioner.h"
      21             : #include "libmesh/parmetis_partitioner.h"
      22             : #include "libmesh/hilbert_sfc_partitioner.h"
      23             : #include "libmesh/morton_sfc_partitioner.h"
      24             : #include "libmesh/enum_elem_type.h"
      25             : 
      26             : #include <set>
      27             : #include <limits>
      28             : #include "libmesh/mesh_tools.h"
      29             : 
      30             : registerMooseObject("HeatTransferApp", PatchSidesetGenerator);
      31             : 
      32             : InputParameters
      33        1054 : PatchSidesetGenerator::validParams()
      34             : {
      35        1054 :   InputParameters params = MeshGenerator::validParams();
      36             : 
      37        2108 :   params.addRequiredParam<MeshGeneratorName>("input", "The mesh we want to modify");
      38        2108 :   params.addRequiredParam<BoundaryName>("boundary",
      39             :                                         "The boundary that will be divided into patches");
      40        2108 :   params.addRequiredRangeCheckedParam<unsigned int>(
      41             :       "n_patches", "n_patches>0", "Number of patches");
      42             : 
      43        1054 :   MooseEnum partitioning = MooseMesh::partitioning(); // default MOOSE partitioning
      44        1054 :   partitioning += "grid";                             // ...but also add our own
      45        2108 :   params.addParam<MooseEnum>(
      46             :       "partitioner",
      47             :       partitioning,
      48             :       "Specifies a mesh partitioner to use when splitting the mesh for a parallel computation.");
      49             : 
      50        2108 :   MooseEnum direction("x y z radial");
      51        2108 :   params.addParam<MooseEnum>("centroid_partitioner_direction",
      52             :                              direction,
      53             :                              "Specifies the sort direction if using the centroid partitioner. "
      54             :                              "Available options: x, y, z, radial");
      55             : 
      56        2108 :   params.addParamNamesToGroup("partitioner centroid_partitioner_direction", "Partitioning");
      57             : 
      58        1054 :   params.addClassDescription(
      59             :       "Divides the given sideset into smaller patches of roughly equal size.");
      60             : 
      61        1054 :   return params;
      62        1054 : }
      63             : 
      64         527 : PatchSidesetGenerator::PatchSidesetGenerator(const InputParameters & parameters)
      65             :   : MeshGenerator(parameters),
      66         527 :     _input(getMesh("input")),
      67        1054 :     _n_patches(getParam<unsigned int>("n_patches")),
      68        1054 :     _sideset_name(getParam<BoundaryName>("boundary")),
      69        1581 :     _partitioner_name(getParam<MooseEnum>("partitioner"))
      70             : {
      71         527 : }
      72             : 
      73             : std::unique_ptr<MeshBase>
      74         392 : PatchSidesetGenerator::generate()
      75             : {
      76         392 :   std::unique_ptr<MeshBase> mesh = std::move(_input);
      77             : 
      78         784 :   _mesh->errorIfDistributedMesh("PatchSidesetGenerator");
      79             : 
      80             :   // Get a reference to our BoundaryInfo object for later use
      81             :   BoundaryInfo & boundary_info = mesh->get_boundary_info();
      82             : 
      83             :   // get dimensionality
      84         392 :   _dim = mesh->mesh_dimension() - 1;
      85             : 
      86             :   // get a list of all sides; vector of tuples (elem, loc_side, side_set)
      87         392 :   auto side_list = boundary_info.build_active_side_list();
      88             : 
      89             :   // check if the provided sideset name is actually a sideset id
      90             :   // if _sideset_name can be converted to integer it's interpreted
      91             :   // as sideset id
      92         392 :   std::stringstream ss;
      93         392 :   ss << _sideset_name;
      94         392 :   ss >> _sideset;
      95         392 :   if (ss.fail())
      96          88 :     _sideset = boundary_info.get_id_by_name(_sideset_name);
      97             : 
      98             :   // make sure that _sideset exists
      99         392 :   if (_sideset == BoundaryInfo::invalid_id)
     100           0 :     paramError("sideset_name", "Not a valid boundary");
     101             : 
     102             :   // create a dim - 1 dimensional mesh
     103         392 :   auto boundary_mesh = buildReplicatedMesh(mesh->mesh_dimension() - 1);
     104         784 :   boundary_mesh->set_mesh_dimension(mesh->mesh_dimension() - 1);
     105         392 :   boundary_mesh->set_spatial_dimension(mesh->mesh_dimension());
     106             : 
     107             :   // nodes in the new mesh by boundary_node_id (index)
     108             :   std::vector<Node *> boundary_nodes;
     109             :   // a map from the node numbering on the volumetric mesh to the numbering
     110             :   // on the boundary_mesh
     111             :   std::map<dof_id_type, dof_id_type> mesh_node_id_to_boundary_node_id;
     112             :   // a local counter keeping track of how many entries have been added to boundary_nodes
     113             :   dof_id_type boundary_node_id = 0;
     114             :   // a map from new element id in the boundary mesh to the element id/side/sideset
     115             :   // tuple it came from
     116             :   std::map<dof_id_type, std::tuple<dof_id_type, unsigned short int, boundary_id_type>>
     117             :       boundary_elem_to_mesh_elem;
     118      115820 :   for (auto & side : side_list)
     119             :   {
     120      115428 :     if (std::get<2>(side) == _sideset)
     121             :     {
     122             :       // the original volumetric mesh element
     123       16206 :       const Elem * elem = mesh->elem_ptr(std::get<0>(side));
     124             : 
     125             :       // the boundary element
     126       16206 :       std::unique_ptr<const Elem> boundary_elem = elem->side_ptr(std::get<1>(side));
     127             : 
     128             :       // an array that saves the boundary node ids of this elem in the right order
     129       16206 :       std::vector<dof_id_type> bnd_elem_node_ids(boundary_elem->n_nodes());
     130             : 
     131             :       // loop through the nodes in boundary_elem
     132       78038 :       for (MooseIndex(boundary_elem->n_nodes()) j = 0; j < boundary_elem->n_nodes(); ++j)
     133             :       {
     134             :         const Node * node = boundary_elem->node_ptr(j);
     135             : 
     136             :         // Is this node a new node?
     137       61832 :         if (mesh_node_id_to_boundary_node_id.find(node->id()) ==
     138             :             mesh_node_id_to_boundary_node_id.end())
     139             :         {
     140             :           // yes, it is new, need to add it to the mesh_node_id_to_boundary_node_id map
     141       18346 :           mesh_node_id_to_boundary_node_id.insert(
     142       18346 :               std::pair<dof_id_type, dof_id_type>(node->id(), boundary_node_id));
     143             : 
     144             :           // this adds this node to the boundary mesh and puts it at the right position
     145             :           // in the boundary_nodes array
     146       18346 :           Point pt(*node);
     147       18346 :           boundary_nodes.push_back(boundary_mesh->add_point(pt, boundary_node_id));
     148             : 
     149             :           // keep track of the boundary node for setting up the element
     150       18346 :           bnd_elem_node_ids[j] = boundary_node_id;
     151             : 
     152             :           // increment the boundary_node_id counter
     153       18346 :           ++boundary_node_id;
     154             :         }
     155             :         else
     156       43486 :           bnd_elem_node_ids[j] = mesh_node_id_to_boundary_node_id.find(node->id())->second;
     157             :       }
     158             : 
     159             :       // all nodes for this element have been added, so we can add the element to the
     160             :       // boundary mesh
     161       16206 :       Elem * new_bnd_elem = boundaryElementHelper(*boundary_mesh, boundary_elem->type());
     162             : 
     163             :       // keep track of these new boundary elements in boundary_elem_to_mesh_elem
     164       16206 :       boundary_elem_to_mesh_elem.insert(
     165       16206 :           std::pair<dof_id_type, std::tuple<dof_id_type, unsigned short int, boundary_id_type>>(
     166             :               new_bnd_elem->id(), side));
     167             : 
     168             :       // set the nodes & subdomain_id of the new element by looping over the
     169             :       // boundary_elem and then inserting its nodes into new_bnd_elem in the
     170             :       // same order
     171       78038 :       for (MooseIndex(boundary_elem->n_nodes()) j = 0; j < boundary_elem->n_nodes(); ++j)
     172             :       {
     173       61832 :         dof_id_type old_node_id = boundary_elem->node_ptr(j)->id();
     174       61832 :         if (mesh_node_id_to_boundary_node_id.find(old_node_id) ==
     175             :             mesh_node_id_to_boundary_node_id.end())
     176           0 :           mooseError("Node id", old_node_id, " not linked to new node id.");
     177       61832 :         dof_id_type new_node_id = mesh_node_id_to_boundary_node_id.find(old_node_id)->second;
     178       61832 :         new_bnd_elem->set_node(j, boundary_nodes[new_node_id]);
     179             :       }
     180       16206 :     }
     181             :   }
     182             : 
     183             :   // partition the boundary mesh
     184         392 :   boundary_mesh->prepare_for_use();
     185         392 :   _n_boundary_mesh_elems = boundary_mesh->n_elem();
     186         392 :   if (_partitioner_name == "grid")
     187          15 :     partition(*boundary_mesh);
     188             :   else
     189             :   {
     190         754 :     auto partitioner_enum = getParam<MooseEnum>("partitioner");
     191         377 :     MooseMesh::setPartitioner(*boundary_mesh, partitioner_enum, false, _pars, *this);
     192         375 :     boundary_mesh->partition(_n_patches);
     193         375 :   }
     194             : 
     195             :   // make sure every partition has at least one element; if not rename and adjust _n_patches
     196         390 :   checkPartitionAndCompress(*boundary_mesh);
     197             : 
     198             :   // prepare sideset names and boundary_ids added to mesh
     199             :   std::vector<BoundaryName> sideset_names =
     200         390 :       sidesetNameHelper(boundary_info.get_sideset_name(_sideset));
     201             : 
     202             :   std::vector<boundary_id_type> boundary_ids =
     203         390 :       MooseMeshUtils::getBoundaryIDs(*mesh, sideset_names, true);
     204             : 
     205             :   mooseAssert(sideset_names.size() == _n_patches,
     206             :               "sideset_names must have as many entries as user-requested number of patches.");
     207             :   mooseAssert(boundary_ids.size() == _n_patches,
     208             :               "boundary_ids must have as many entries as user-requested number of patches.");
     209             : 
     210             :   // loop through all elements in the boundary mesh and assign the side of
     211             :   // the _original_ element to the new sideset
     212       30692 :   for (const auto & elem : boundary_mesh->active_element_ptr_range())
     213             :   {
     214       14956 :     if (boundary_elem_to_mesh_elem.find(elem->id()) == boundary_elem_to_mesh_elem.end())
     215           0 :       mooseError("Element in the boundary mesh with id ",
     216           0 :                  elem->id(),
     217             :                  " not found in boundary_elem_to_mesh_elem.");
     218             : 
     219       14956 :     auto side = boundary_elem_to_mesh_elem.find(elem->id())->second;
     220             : 
     221             :     mooseAssert(elem->processor_id() < boundary_ids.size(),
     222             :                 "Processor id larger than number of patches.");
     223       14956 :     boundary_info.add_side(
     224       14956 :         std::get<0>(side), std::get<1>(side), boundary_ids[elem->processor_id()]);
     225         390 :   }
     226             : 
     227             :   // make sure new boundary names are set
     228        1519 :   for (MooseIndex(boundary_ids.size()) j = 0; j < boundary_ids.size(); ++j)
     229             :   {
     230        1129 :     boundary_info.sideset_name(boundary_ids[j]) = sideset_names[j];
     231        1129 :     boundary_info.nodeset_name(boundary_ids[j]) = sideset_names[j];
     232             :   }
     233             : 
     234         390 :   return mesh;
     235         780 : }
     236             : 
     237             : void
     238          15 : PatchSidesetGenerator::partition(MeshBase & mesh)
     239             : {
     240          15 :   if (_partitioner_name == "grid")
     241             :   {
     242             :     // Figure out the physical bounds of the given mesh
     243          15 :     auto bounding_box = MeshTools::create_bounding_box(mesh);
     244             :     const auto & min = bounding_box.min();
     245             :     const auto & max = bounding_box.max();
     246             :     const auto & delta = max - min;
     247             : 
     248             :     // set number of elements
     249          15 :     std::vector<unsigned int> nelems(3);
     250          15 :     if (_dim == 1)
     251             :     {
     252             :       // find the largest component in delta
     253             :       unsigned int largest_id = 0;
     254             :       Real largest = delta(0);
     255          30 :       for (unsigned int j = 1; j < 3; ++j)
     256          20 :         if (largest < delta(j))
     257             :         {
     258             :           largest = delta(j);
     259             :           largest_id = j;
     260             :         }
     261             : 
     262             :       // set nelems now
     263          10 :       nelems = {1, 1, 1};
     264          10 :       nelems[largest_id] = _n_patches;
     265             :     }
     266             :     else
     267             :     {
     268             :       // find the smallest component in delta
     269             :       unsigned int smallest_id = 0;
     270             :       Real smallest = delta(0);
     271          15 :       for (unsigned int j = 1; j < 3; ++j)
     272          10 :         if (smallest > delta(j))
     273             :         {
     274             :           smallest = delta(j);
     275             :           smallest_id = j;
     276             :         }
     277             : 
     278             :       // store the ids for the two larger dimensions
     279             :       unsigned int id1 = 1, id2 = 2;
     280           5 :       if (smallest_id == 1)
     281             :         id1 = 0;
     282           5 :       else if (smallest_id == 2)
     283             :         id2 = 0;
     284             : 
     285             :       // set number of elements
     286           5 :       nelems[smallest_id] = 1;
     287           5 :       nelems[id1] = std::round(std::sqrt(delta(id1) / delta(id2) * _n_patches));
     288           5 :       nelems[id2] = std::round(std::sqrt(delta(id2) / delta(id1) * _n_patches));
     289           5 :       unsigned int final_n_patches = nelems[id1] * nelems[id2];
     290             :       // We need to check if the number of requested patches and the number of
     291             :       // actually created patches matches. If the two do not match, then a warning
     292             :       // is printed.
     293           5 :       if (_n_patches != final_n_patches)
     294             :       {
     295           5 :         _console << "Note: For creating radiation patches for boundary " << _sideset
     296           5 :                  << " using grid partitioner number of patches was changed from " << _n_patches
     297           5 :                  << " to " << final_n_patches << std::endl;
     298           5 :         _n_patches = final_n_patches;
     299             :       }
     300             :     }
     301             : 
     302          15 :     const Real dx = delta(0) / nelems[0];
     303          15 :     const Real dy = delta(1) / nelems[1];
     304          15 :     const Real dz = delta(2) / nelems[2];
     305        3755 :     for (auto & elem_ptr : mesh.active_element_ptr_range())
     306             :     {
     307        3725 :       const Point centroid = elem_ptr->vertex_average();
     308             :       processor_id_type proc_id;
     309        3725 :       const unsigned int ix = std::floor((centroid(0) - min(0)) / dx);
     310        3725 :       const unsigned int iy = std::floor((centroid(1) - min(1)) / dy);
     311        3725 :       const unsigned int iz = std::floor((centroid(2) - min(2)) / dz);
     312        3725 :       proc_id = ix + iy * nelems[0] + iz * nelems[0] * nelems[1];
     313        3725 :       elem_ptr->processor_id() = proc_id;
     314          15 :     }
     315          15 :   }
     316             :   else
     317           0 :     mooseError("Partitioner ", _partitioner_name, " not recognized.");
     318          15 : }
     319             : 
     320             : void
     321         390 : PatchSidesetGenerator::checkPartitionAndCompress(MeshBase & mesh)
     322             : {
     323             :   std::set<processor_id_type> processor_ids;
     324       30692 :   for (auto & elem_ptr : mesh.active_element_ptr_range())
     325       15346 :     processor_ids.insert(elem_ptr->processor_id());
     326             : 
     327         390 :   if (processor_ids.size() == _n_patches)
     328             :     return;
     329             : 
     330             :   // at least one partition does not have an elem assigned to it
     331             :   // adjust _n_patches
     332           5 :   _console << "Some partitions for side set " << _sideset
     333           5 :            << " are empty. Adjusting number of patches from " << _n_patches << " to "
     334           5 :            << processor_ids.size() << std::endl;
     335           5 :   _n_patches = processor_ids.size();
     336             : 
     337             :   // create a vector and sort it
     338             :   std::vector<processor_id_type> processor_ids_vec;
     339         255 :   for (auto & p : processor_ids)
     340         250 :     processor_ids_vec.push_back(p);
     341           5 :   std::sort(processor_ids_vec.begin(), processor_ids_vec.end());
     342             : 
     343             :   // now remap the processor ids
     344             :   std::map<processor_id_type, processor_id_type> processor_id_remap;
     345         255 :   for (MooseIndex(processor_ids_vec.size()) j = 0; j < processor_ids_vec.size(); ++j)
     346         250 :     processor_id_remap[processor_ids_vec[j]] = j;
     347             : 
     348         310 :   for (auto & elem_ptr : mesh.active_element_ptr_range())
     349             :   {
     350         300 :     processor_id_type p = elem_ptr->processor_id();
     351             :     const auto & it = processor_id_remap.find(p);
     352         300 :     if (it == processor_id_remap.end())
     353           0 :       mooseError("Parition id ", p, " not in processor_id_remap.");
     354         300 :     elem_ptr->processor_id() = it->second;
     355           5 :   }
     356           5 : }
     357             : 
     358             : std::vector<BoundaryName>
     359         390 : PatchSidesetGenerator::sidesetNameHelper(const std::string & base_name) const
     360             : {
     361             :   std::vector<BoundaryName> rv;
     362        1519 :   for (unsigned int j = 0; j < _n_patches; ++j)
     363             :   {
     364        1129 :     std::stringstream ss;
     365        1129 :     ss << base_name << "_" << j;
     366        1129 :     rv.push_back(ss.str());
     367        1129 :   }
     368         390 :   return rv;
     369           0 : }
     370             : 
     371             : Elem *
     372       16206 : PatchSidesetGenerator::boundaryElementHelper(MeshBase & mesh, libMesh::ElemType type) const
     373             : {
     374       16206 :   std::unique_ptr<Elem> elem = libMesh::Elem::build(type);
     375       16206 :   if (elem->dim() < 3)
     376       32412 :     return mesh.add_elem(std::move(elem));
     377             : 
     378           0 :   mooseError("Unsupported element type (libMesh elem_type enum): ", type);
     379       16206 : }

Generated by: LCOV version 1.14