LCOV - code coverage report
Current view: top level - src/meshgenerators - CombinerGenerator.C (source / functions) Hit Total Coverage
Test: idaholab/moose framework: #32971 (54bef8) with base c6cf66 Lines: 100 110 90.9 %
Date: 2026-05-29 20:35:17 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 "CombinerGenerator.h"
      11             : 
      12             : #include "CastUniquePointer.h"
      13             : #include "MooseUtils.h"
      14             : #include "DelimitedFileReader.h"
      15             : #include "MooseMeshUtils.h"
      16             : 
      17             : #include "libmesh/replicated_mesh.h"
      18             : #include "libmesh/unstructured_mesh.h"
      19             : #include "libmesh/mesh_modification.h"
      20             : #include "libmesh/point.h"
      21             : #include "libmesh/node.h"
      22             : 
      23             : registerMooseObject("MooseApp", CombinerGenerator);
      24             : 
      25             : InputParameters
      26        4475 : CombinerGenerator::validParams()
      27             : {
      28        4475 :   InputParameters params = MeshGenerator::validParams();
      29             : 
      30        8950 :   params.addClassDescription(
      31             :       "Combine multiple meshes (or copies of one mesh) together into one (disjoint) mesh.  Can "
      32             :       "optionally translate those meshes before combining them.");
      33             : 
      34       17900 :   params.addRequiredParam<std::vector<MeshGeneratorName>>(
      35             :       "inputs",
      36             :       "The input MeshGenerators.  This can either be N generators or 1 generator.  If only 1 is "
      37             :       "given then 'positions' must also be given.");
      38             : 
      39       17900 :   params.addParam<std::vector<Point>>(
      40             :       "positions",
      41             :       "The (optional) position of each given mesh.  If N 'inputs' were given then this must either "
      42             :       "be left blank or N positions must be given.  If 1 input was given then this MUST be "
      43             :       "provided.");
      44             : 
      45       17900 :   params.addParam<std::vector<FileName>>(
      46             :       "positions_file", "Alternative way to provide the position of each given mesh.");
      47             : 
      48       13425 :   params.addParam<bool>("avoid_merging_subdomains",
      49        8950 :                         false,
      50             :                         "Whether to prevent merging subdomains by offsetting ids. The first mesh "
      51             :                         "in the input will keep the same subdomains ids, the others will have "
      52             :                         "offsets. All subdomain names will remain valid");
      53        8950 :   params.addParam<bool>("avoid_merging_boundaries",
      54        8950 :                         false,
      55             :                         "Whether to prevent merging sidesets by offsetting ids. The first mesh "
      56             :                         "in the input will keep the same boundary ids, the others will have "
      57             :                         "offsets. All boundary names will remain valid");
      58             : 
      59        4475 :   return params;
      60           0 : }
      61             : 
      62         707 : CombinerGenerator::CombinerGenerator(const InputParameters & parameters)
      63             :   : MeshGenerator(parameters),
      64         707 :     _meshes(getMeshes("inputs")),
      65        1414 :     _input_names(getParam<std::vector<MeshGeneratorName>>("inputs")),
      66        1414 :     _avoid_merging_subdomains(getParam<bool>("avoid_merging_subdomains")),
      67        2828 :     _avoid_merging_boundaries(getParam<bool>("avoid_merging_boundaries"))
      68             : {
      69         707 :   if (_input_names.empty())
      70           0 :     paramError("input_names", "You need to specify at least one MeshGenerator as an input.");
      71             : 
      72        2753 :   if (isParamValid("positions") && isParamValid("positions_file"))
      73           0 :     mooseError("Both 'positions' and 'positions_file' cannot be specified simultaneously in "
      74             :                "CombinerGenerator ",
      75           0 :                _name);
      76             : 
      77         707 :   if (_input_names.size() == 1)
      78         392 :     if (!isParamValid("positions") && !isParamValid("positions_file"))
      79           0 :       paramError("positions",
      80             :                  "If only one input mesh is given, then 'positions' or 'positions_file' must also "
      81             :                  "be supplied");
      82         707 : }
      83             : 
      84             : void
      85         689 : CombinerGenerator::fillPositions()
      86             : {
      87        2067 :   if (isParamValid("positions"))
      88             :   {
      89         630 :     _positions = getParam<std::vector<Point>>("positions");
      90             : 
      91             :     // the check in the constructor wont catch error where the user sets positions = ''
      92         315 :     if ((_input_names.size() == 1) && _positions.empty())
      93           6 :       paramError("positions", "If only one input mesh is given, then 'positions' cannot be empty.");
      94             : 
      95         312 :     if (_input_names.size() != 1)
      96         207 :       if (_positions.size() && (_input_names.size() != _positions.size()))
      97           6 :         paramError(
      98             :             "positions",
      99             :             "If more than one input mesh is provided then the number of positions provided must "
     100             :             "exactly match the number of input meshes.");
     101             :   }
     102        1122 :   else if (isParamValid("positions_file"))
     103             :   {
     104          52 :     std::vector<FileName> positions_file = getParam<std::vector<FileName>>("positions_file");
     105             : 
     106             :     // the check in the constructor wont catch error where the user sets positions_file = ''
     107          26 :     if ((_input_names.size() == 1) && positions_file.empty())
     108           6 :       paramError("positions_file",
     109             :                  "If only one input mesh is given, then 'positions_file' cannot be empty.");
     110             : 
     111          43 :     for (const auto & f : positions_file)
     112             :     {
     113          23 :       MooseUtils::DelimitedFileReader file(f, &_communicator);
     114          23 :       file.setFormatFlag(MooseUtils::DelimitedFileReader::FormatFlag::ROWS);
     115          23 :       file.read();
     116             : 
     117          23 :       const std::vector<Point> & data = file.getDataAsPoints();
     118             : 
     119          23 :       if (_input_names.size() != 1)
     120          13 :         if (data.size() && (_input_names.size() != data.size()))
     121           6 :           paramError("positions_file",
     122             :                      "If more than one input mesh is provided then the number of positions must "
     123             :                      "exactly match the number of input meshes.");
     124             : 
     125          80 :       for (const auto & d : data)
     126          60 :         _positions.push_back(d);
     127          20 :     }
     128          20 :   }
     129         677 : }
     130             : 
     131             : std::unique_ptr<MeshBase>
     132         689 : CombinerGenerator::generate()
     133             : {
     134             :   // Two cases:
     135             :   // 1. Multiple input meshes and optional positions
     136             :   // 2. One input mesh and multiple positions
     137         689 :   fillPositions();
     138             : 
     139             :   // Case 1
     140         677 :   if (_meshes.size() != 1)
     141             :   {
     142             :     // merge all meshes into the first one
     143         562 :     auto mesh = dynamic_pointer_cast<UnstructuredMesh>(*_meshes[0]);
     144             : 
     145         562 :     if (!mesh)
     146           0 :       paramError("inputs", _input_names[0], " is not a valid unstructured mesh");
     147             : 
     148             :     // Move the first input mesh if applicable
     149         562 :     if (_positions.size())
     150             :     {
     151         214 :       MeshTools::Modification::translate(
     152         214 :           *mesh, _positions[0](0), _positions[0](1), _positions[0](2));
     153             :     }
     154             : 
     155             :     // Read in all of the other meshes
     156        2450 :     for (MooseIndex(_meshes) i = 1; i < _meshes.size(); ++i)
     157             :     {
     158        1888 :       auto other_mesh = dynamic_pointer_cast<UnstructuredMesh>(*_meshes[i]);
     159             : 
     160        1888 :       if (!other_mesh)
     161           0 :         paramError("inputs", _input_names[i], " is not a valid unstructured mesh");
     162             : 
     163             :       // We'll be using cached subdomain sets in copyIntoMesh, so our
     164             :       // const inputs need caches ready.
     165        1888 :       if (!other_mesh->preparation().has_cached_elem_data)
     166        1719 :         other_mesh->cache_elem_data();
     167             : 
     168             :       // Move It
     169        1888 :       if (_positions.size())
     170             :       {
     171         698 :         MeshTools::Modification::translate(
     172         698 :             *other_mesh, _positions[i](0), _positions[i](1), _positions[i](2));
     173             :       }
     174             : 
     175        1888 :       MooseMeshUtils::copyIntoMesh(*this,
     176        1888 :                                    *mesh,
     177        1888 :                                    *other_mesh,
     178        1888 :                                    _avoid_merging_subdomains,
     179        1888 :                                    _avoid_merging_boundaries,
     180             :                                    _communicator);
     181        1888 :     }
     182             : 
     183         562 :     mesh->unset_is_prepared();
     184         562 :     return dynamic_pointer_cast<MeshBase>(mesh);
     185         562 :   }
     186             :   else // Case 2
     187             :   {
     188         115 :     auto input_mesh = dynamic_pointer_cast<UnstructuredMesh>(*_meshes[0]);
     189             : 
     190             :     // We'll be using cached subdomain sets in copyIntoMesh
     191         115 :     if (!input_mesh->preparation().has_cached_elem_data)
     192         115 :       input_mesh->cache_elem_data();
     193             : 
     194         115 :     if (!input_mesh)
     195           0 :       paramError("inputs", _input_names[0], " is not a valid unstructured mesh");
     196             : 
     197             :     // Make a copy and displace it in order to get the final mesh started
     198             :     auto copy =
     199         115 :         input_mesh->clone(); // This is required because dynamic_pointer_cast() requires an l-value
     200         115 :     auto final_mesh = dynamic_pointer_cast<UnstructuredMesh>(copy);
     201             : 
     202         115 :     if (!final_mesh)
     203           0 :       mooseError("Unable to copy mesh!");
     204             : 
     205         115 :     MeshTools::Modification::translate(
     206         115 :         *final_mesh, _positions[0](0), _positions[0](1), _positions[0](2));
     207             : 
     208             :     // Here's the way this is going to work:
     209             :     // I'm going to make one more copy of the input_mesh so that I can move it and copy it in
     210             :     // Then, after it's copied in I'm going to reset its coordinates by looping over the input_mesh
     211             :     // and resetting the nodal positions.
     212             :     // This could be done without the copy - you would translate the mesh then translate it back...
     213             :     // However, I'm worried about floating point roundoff.  If you were doing this 100,000 times or
     214             :     // more then the mesh could "drift" away from its original position.  I really want the
     215             :     // translations to be exact each time.
     216             :     // I suppose that it is technically possible to just save off a datastructure (map, etc.) that
     217             :     // could hold the nodal positions only (instead of a copy of the mesh) but I'm not sure that
     218             :     // would really save much... we'll see if it shows up in profiling somewhere
     219         115 :     copy = input_mesh->clone();
     220         115 :     auto translated_mesh = dynamic_pointer_cast<UnstructuredMesh>(copy);
     221             : 
     222         115 :     if (!translated_mesh)
     223           0 :       mooseError("Unable to copy mesh!");
     224             : 
     225         163 :     for (MooseIndex(_meshes) i = 1; i < _positions.size(); ++i)
     226             :     {
     227             :       // Move
     228          48 :       MeshTools::Modification::translate(
     229          48 :           *translated_mesh, _positions[i](0), _positions[i](1), _positions[i](2));
     230             : 
     231             :       // Copy into final mesh
     232          48 :       MooseMeshUtils::copyIntoMesh(*this,
     233          48 :                                    *final_mesh,
     234          48 :                                    *translated_mesh,
     235          48 :                                    _avoid_merging_subdomains,
     236          48 :                                    _avoid_merging_boundaries,
     237             :                                    _communicator);
     238             : 
     239             :       // Reset nodal coordinates
     240        4744 :       for (auto translated_node_ptr : translated_mesh->node_ptr_range())
     241             :       {
     242        4696 :         auto & translated_node = *translated_node_ptr;
     243        4696 :         auto & input_node = input_mesh->node_ref(translated_node_ptr->id());
     244             : 
     245       18784 :         for (MooseIndex(LIBMESH_DIM) i = 0; i < LIBMESH_DIM; i++)
     246       14088 :           translated_node(i) = input_node(i);
     247          48 :       }
     248             :     }
     249             : 
     250         115 :     final_mesh->unset_is_prepared();
     251         115 :     return dynamic_pointer_cast<MeshBase>(final_mesh);
     252         115 :   }
     253             : }

Generated by: LCOV version 1.14