LCOV - code coverage report
Current view: top level - src/meshgenerators - CombinerGenerator.C (source / functions) Hit Total Coverage
Test: idaholab/moose framework: #31761 (28487c) with base 701993 Lines: 96 106 90.6 %
Date: 2025-11-11 13:51:07 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       15671 : CombinerGenerator::validParams()
      27             : {
      28       15671 :   InputParameters params = MeshGenerator::validParams();
      29             : 
      30       31342 :   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       62684 :   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       62684 :   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       62684 :   params.addParam<std::vector<FileName>>(
      46             :       "positions_file", "Alternative way to provide the position of each given mesh.");
      47             : 
      48       47013 :   params.addParam<bool>("avoid_merging_subdomains",
      49       31342 :                         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       31342 :   params.addParam<bool>("avoid_merging_boundaries",
      54       31342 :                         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       15671 :   return params;
      60           0 : }
      61             : 
      62         702 : CombinerGenerator::CombinerGenerator(const InputParameters & parameters)
      63             :   : MeshGenerator(parameters),
      64         702 :     _meshes(getMeshes("inputs")),
      65        1404 :     _input_names(getParam<std::vector<MeshGeneratorName>>("inputs")),
      66        1404 :     _avoid_merging_subdomains(getParam<bool>("avoid_merging_subdomains")),
      67        2808 :     _avoid_merging_boundaries(getParam<bool>("avoid_merging_boundaries"))
      68             : {
      69         702 :   if (_input_names.empty())
      70           0 :     paramError("input_names", "You need to specify at least one MeshGenerator as an input.");
      71             : 
      72        2636 :   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         702 :   if (_input_names.size() == 1)
      78         183 :     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         702 : }
      83             : 
      84             : void
      85         683 : CombinerGenerator::fillPositions()
      86             : {
      87        2049 :   if (isParamValid("positions"))
      88             :   {
      89         528 :     _positions = getParam<std::vector<Point>>("positions");
      90             : 
      91             :     // the check in the constructor wont catch error where the user sets positions = ''
      92         264 :     if ((_input_names.size() == 1) && _positions.empty())
      93           8 :       paramError("positions", "If only one input mesh is given, then 'positions' cannot be empty.");
      94             : 
      95         260 :     if (_input_names.size() != 1)
      96         229 :       if (_positions.size() && (_input_names.size() != _positions.size()))
      97           8 :         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        1257 :   else if (isParamValid("positions_file"))
     103             :   {
     104          60 :     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          30 :     if ((_input_names.size() == 1) && positions_file.empty())
     108           8 :       paramError("positions_file",
     109             :                  "If only one input mesh is given, then 'positions_file' cannot be empty.");
     110             : 
     111          48 :     for (const auto & f : positions_file)
     112             :     {
     113          26 :       MooseUtils::DelimitedFileReader file(f, &_communicator);
     114          26 :       file.setFormatFlag(MooseUtils::DelimitedFileReader::FormatFlag::ROWS);
     115          26 :       file.read();
     116             : 
     117          26 :       const std::vector<Point> & data = file.getDataAsPoints();
     118             : 
     119          26 :       if (_input_names.size() != 1)
     120          15 :         if (data.size() && (_input_names.size() != data.size()))
     121           8 :           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          88 :       for (const auto & d : data)
     126          66 :         _positions.push_back(d);
     127          22 :     }
     128          22 :   }
     129         667 : }
     130             : 
     131             : std::unique_ptr<MeshBase>
     132         683 : CombinerGenerator::generate()
     133             : {
     134             :   // Two cases:
     135             :   // 1. Multiple input meshes and optional positions
     136             :   // 2. One input mesh and multiple positions
     137         683 :   fillPositions();
     138             : 
     139             :   // Case 1
     140         667 :   if (_meshes.size() != 1)
     141             :   {
     142             :     // merge all meshes into the first one
     143         625 :     auto mesh = dynamic_pointer_cast<UnstructuredMesh>(*_meshes[0]);
     144             : 
     145         625 :     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         625 :     if (_positions.size())
     150             :     {
     151         236 :       MeshTools::Modification::translate(
     152         236 :           *mesh, _positions[0](0), _positions[0](1), _positions[0](2));
     153             :     }
     154             : 
     155             :     // Read in all of the other meshes
     156        2715 :     for (MooseIndex(_meshes) i = 1; i < _meshes.size(); ++i)
     157             :     {
     158        2090 :       auto other_mesh = dynamic_pointer_cast<UnstructuredMesh>(*_meshes[i]);
     159             : 
     160        2090 :       if (!other_mesh)
     161           0 :         paramError("inputs", _input_names[i], " is not a valid unstructured mesh");
     162             : 
     163             :       // Move It
     164        2090 :       if (_positions.size())
     165             :       {
     166         774 :         MeshTools::Modification::translate(
     167         774 :             *other_mesh, _positions[i](0), _positions[i](1), _positions[i](2));
     168             :       }
     169             : 
     170        2090 :       MooseMeshUtils::copyIntoMesh(*this,
     171        2090 :                                    *mesh,
     172        2090 :                                    *other_mesh,
     173        2090 :                                    _avoid_merging_subdomains,
     174        2090 :                                    _avoid_merging_boundaries,
     175             :                                    _communicator);
     176        2090 :     }
     177             : 
     178         625 :     mesh->set_isnt_prepared();
     179         625 :     return dynamic_pointer_cast<MeshBase>(mesh);
     180         625 :   }
     181             :   else // Case 2
     182             :   {
     183          42 :     auto input_mesh = dynamic_pointer_cast<UnstructuredMesh>(*_meshes[0]);
     184             : 
     185          42 :     if (!input_mesh)
     186           0 :       paramError("inputs", _input_names[0], " is not a valid unstructured mesh");
     187             : 
     188             :     // Make a copy and displace it in order to get the final mesh started
     189             :     auto copy =
     190          42 :         input_mesh->clone(); // This is required because dynamic_pointer_cast() requires an l-value
     191          42 :     auto final_mesh = dynamic_pointer_cast<UnstructuredMesh>(copy);
     192             : 
     193          42 :     if (!final_mesh)
     194           0 :       mooseError("Unable to copy mesh!");
     195             : 
     196          42 :     MeshTools::Modification::translate(
     197          42 :         *final_mesh, _positions[0](0), _positions[0](1), _positions[0](2));
     198             : 
     199             :     // Here's the way this is going to work:
     200             :     // I'm going to make one more copy of the input_mesh so that I can move it and copy it in
     201             :     // Then, after it's copied in I'm going to reset its coordinates by looping over the input_mesh
     202             :     // and resetting the nodal positions.
     203             :     // This could be done without the copy - you would translate the mesh then translate it back...
     204             :     // However, I'm worried about floating point roundoff.  If you were doing this 100,000 times or
     205             :     // more then the mesh could "drift" away from its original position.  I really want the
     206             :     // translations to be exact each time.
     207             :     // I suppose that it is technically possible to just save off a datastructure (map, etc.) that
     208             :     // could hold the nodal positions only (instead of a copy of the mesh) but I'm not sure that
     209             :     // would really save much... we'll see if it shows up in profiling somewhere
     210          42 :     copy = input_mesh->clone();
     211          42 :     auto translated_mesh = dynamic_pointer_cast<UnstructuredMesh>(copy);
     212             : 
     213          42 :     if (!translated_mesh)
     214           0 :       mooseError("Unable to copy mesh!");
     215             : 
     216          86 :     for (MooseIndex(_meshes) i = 1; i < _positions.size(); ++i)
     217             :     {
     218             :       // Move
     219          44 :       MeshTools::Modification::translate(
     220          44 :           *translated_mesh, _positions[i](0), _positions[i](1), _positions[i](2));
     221             : 
     222             :       // Copy into final mesh
     223          44 :       MooseMeshUtils::copyIntoMesh(*this,
     224          44 :                                    *final_mesh,
     225          44 :                                    *translated_mesh,
     226          44 :                                    _avoid_merging_subdomains,
     227          44 :                                    _avoid_merging_boundaries,
     228             :                                    _communicator);
     229             : 
     230             :       // Reset nodal coordinates
     231        5016 :       for (auto translated_node_ptr : translated_mesh->node_ptr_range())
     232             :       {
     233        4972 :         auto & translated_node = *translated_node_ptr;
     234        4972 :         auto & input_node = input_mesh->node_ref(translated_node_ptr->id());
     235             : 
     236       19888 :         for (MooseIndex(LIBMESH_DIM) i = 0; i < LIBMESH_DIM; i++)
     237       14916 :           translated_node(i) = input_node(i);
     238          44 :       }
     239             :     }
     240             : 
     241          42 :     final_mesh->set_isnt_prepared();
     242          42 :     return dynamic_pointer_cast<MeshBase>(final_mesh);
     243          42 :   }
     244             : }

Generated by: LCOV version 1.14