diff --git a/framework/doc/content/source/meshgenerators/SidesetsEncloseBlocks.md b/framework/doc/content/source/meshgenerators/SidesetsEncloseBlocks.md new file mode 100644 index 000000000000..e4ea1288b15e --- /dev/null +++ b/framework/doc/content/source/meshgenerators/SidesetsEncloseBlocks.md @@ -0,0 +1,17 @@ +# SidesetsEncloseBlocks + +!syntax description /Mesh/SidesetsEncloseBlocks + +This mesh generator ensures that the blocks provided by the +[!param](/Mesh/SidesetsEncloseBlocks/block) +parameter are enclosed by the sidesets provided by [!param](/Mesh/SidesetsEncloseBlocks/boundary). +If that is not the case, the behavior of the mesh generator depends on whether +[!param](/Mesh/SidesetsEncloseBlocks/new_boundary) is provided. +If it is not provided, then an error is thrown. If it is provided, then the side +that is not covered is added to the new boundary. + +!syntax parameters /Mesh/SidesetsEncloseBlocks + +!syntax inputs /Mesh/SidesetsEncloseBlocks + +!syntax children /Mesh/SidesetsEncloseBlocks diff --git a/framework/include/meshgenerators/SidesetsEncloseBlocks.h b/framework/include/meshgenerators/SidesetsEncloseBlocks.h new file mode 100644 index 000000000000..da44c4ce3304 --- /dev/null +++ b/framework/include/meshgenerators/SidesetsEncloseBlocks.h @@ -0,0 +1,31 @@ +//* This file is part of the MOOSE framework +//* https://www.mooseframework.org +//* +//* All rights reserved, see COPYRIGHT for full restrictions +//* https://github.com/idaholab/moose/blob/master/COPYRIGHT +//* +//* Licensed under LGPL 2.1, please see LICENSE for details +//* https://www.gnu.org/licenses/lgpl-2.1.html + +#pragma once + +#include "MeshGenerator.h" + +/** + * MeshGenerator that checks if a set of blocks is enclosed by a set of sidesets. + * It can either add sides that are not covered by a sideset by a new sidesets or + * error out. + */ +class SidesetsEncloseBlocks : public MeshGenerator +{ +public: + static InputParameters validParams(); + + SidesetsEncloseBlocks(const InputParameters & parameters); + + std::unique_ptr generate() override; + +protected: + /// the mesh that is passed from the meshgen executed before this meshgen + std::unique_ptr & _input; +}; diff --git a/framework/src/meshgenerators/SidesetsEncloseBlocks.C b/framework/src/meshgenerators/SidesetsEncloseBlocks.C new file mode 100644 index 000000000000..76d41e2f1224 --- /dev/null +++ b/framework/src/meshgenerators/SidesetsEncloseBlocks.C @@ -0,0 +1,140 @@ +//* This file is part of the MOOSE framework +//* https://www.mooseframework.org +//* +//* All rights reserved, see COPYRIGHT for full restrictions +//* https://github.com/idaholab/moose/blob/master/COPYRIGHT +//* +//* Licensed under LGPL 2.1, please see LICENSE for details +//* https://www.gnu.org/licenses/lgpl-2.1.html + +#include "SidesetsEncloseBlocks.h" +#include "InputParameters.h" +#include "MooseTypes.h" +#include "MooseMeshUtils.h" +#include "CastUniquePointer.h" + +#include "libmesh/remote_elem.h" + +registerMooseObject("MooseApp", SidesetsEncloseBlocks); + +InputParameters +SidesetsEncloseBlocks::validParams() +{ + InputParameters params = MeshGenerator::validParams(); + + params.addRequiredParam("input", "The mesh we want to modify"); + params.addParam>( + "block", "The set of blocks that are checked if they are enclosed by boundary"); + params.addParam>("boundary", + "The name of the boundaries enclosing the "); + params.addParam("new_boundary", "The name of the boundary to create"); + params.addClassDescription( + "MeshGenerator that checks if a set of blocks is enclosed by a set of sidesets." + "It can either add sides that are not covered by a sideset by a new sidesets or" + "error out."); + + return params; +} + +SidesetsEncloseBlocks::SidesetsEncloseBlocks(const InputParameters & parameters) + : MeshGenerator(parameters), _input(getMesh("input")) +{ +} + +std::unique_ptr +SidesetsEncloseBlocks::generate() +{ + std::unique_ptr mesh = std::move(_input); + + BoundaryInfo & boundary_info = mesh->get_boundary_info(); + + // error on finding a side that is not covered + bool error_out = !isParamValid("new_boundary"); + boundary_id_type new_sideset_id; + if (!error_out) + { + BoundaryName new_boundary = getParam("new_boundary"); + std::stringstream ss; + ss << new_boundary; + ss >> new_sideset_id; + if (ss.fail()) + new_sideset_id = boundary_info.get_id_by_name(new_boundary); + + if (new_sideset_id == BoundaryInfo::invalid_id) + paramError("new_boundary", "Not a valid boundary"); + } + + std::vector vec_block_ids = + MooseMeshUtils::getSubdomainIDs(*mesh, getParam>("block")); + std::set blk_ids(vec_block_ids.begin(), vec_block_ids.end()); + + // get boundaries + // check if the provided sideset name is actually a sideset id + // if it can be converted to integer it's interpreted + // as a sideset id + auto boundary_name_vec = getParam>("boundary"); + std::vector bnd_ids_vec; + bnd_ids_vec.reserve(boundary_name_vec.size()); + std::set bnd_ids; + for (auto & bnd_name : boundary_name_vec) + { + std::stringstream ss; + boundary_id_type sideset_id; + ss << bnd_name; + ss >> sideset_id; + if (ss.fail()) + sideset_id = boundary_info.get_id_by_name(bnd_name); + + if (sideset_id == BoundaryInfo::invalid_id) + paramError("boundary", "Not a valid boundary"); + bnd_ids_vec.push_back(sideset_id); + bnd_ids.insert(sideset_id); + } + + // loop over all elements in blocks and for each elem over each side + // and check the neighbors + for (auto & block_id : blk_ids) + { + for (const Elem * elem : as_range(mesh->active_local_subdomain_elements_begin(block_id), + mesh->active_local_subdomain_elements_end(block_id))) + { + for (unsigned int j = 0; j < elem->n_sides(); ++j) + { + const Elem * neigh = elem->neighbor_ptr(j); + + // is this an outside boundary to blocks? + // NOTE: the next line is NOT sufficient for AMR!! + bool is_outer_bnd = !neigh || blk_ids.find(neigh->subdomain_id()) == blk_ids.end(); + if (is_outer_bnd) + { + // get all boundary ids of this side, then compare the set of these boundary_ids + // to the set of boundary_ids provided to the mesh generator; if the intersection + // is empty then this side is NOT convered + std::vector side_boundary_ids_vec; + boundary_info.raw_boundary_ids(elem, j, side_boundary_ids_vec); + + std::set intersection; + std::set_intersection(side_boundary_ids_vec.begin(), + side_boundary_ids_vec.end(), + bnd_ids_vec.begin(), + bnd_ids_vec.end(), + std::inserter(intersection, intersection.end())); + + if (intersection.size() == 0) + { + if (error_out) + mooseError("Element id ", + elem->id(), + " side ", + j, + " is external and not covered by specified boundaries."); + else + boundary_info.add_side(elem, j, new_sideset_id); + } + } + } + } + } + + return dynamic_pointer_cast(mesh); +} diff --git a/test/tests/meshgenerators/sideset_enclose_block/gold/add_new_boundary.e b/test/tests/meshgenerators/sideset_enclose_block/gold/add_new_boundary.e new file mode 100644 index 000000000000..ceadc8ae97d9 Binary files /dev/null and b/test/tests/meshgenerators/sideset_enclose_block/gold/add_new_boundary.e differ diff --git a/test/tests/meshgenerators/sideset_enclose_block/gold/check_no_error.e b/test/tests/meshgenerators/sideset_enclose_block/gold/check_no_error.e new file mode 100644 index 000000000000..00ab14cea7b1 Binary files /dev/null and b/test/tests/meshgenerators/sideset_enclose_block/gold/check_no_error.e differ diff --git a/test/tests/meshgenerators/sideset_enclose_block/sideset_enclose_blocks.i b/test/tests/meshgenerators/sideset_enclose_block/sideset_enclose_blocks.i new file mode 100644 index 000000000000..dfb7945e4f55 --- /dev/null +++ b/test/tests/meshgenerators/sideset_enclose_block/sideset_enclose_blocks.i @@ -0,0 +1,27 @@ +[Mesh] + [cmg] + type = CartesianMeshGenerator + dim = 2 + dx = '1 1 1 1' + dy = '1 1 1 1' + subdomain_id = '1 2 1 2 + 1 3 4 4 + 2 5 10 1 + 1 2 2 2' + [] + + [add_sides] + type = SideSetsBetweenSubdomainsGenerator + primary_block = '3 4 5 10' + paired_block = '1 2' + new_boundary = 'interior' + input = cmg + [] + + [enclosed] + type = SidesetsEncloseBlocks + block = '3 4 5 10' + boundary = '1 4' + input = add_sides + [] +[] diff --git a/test/tests/meshgenerators/sideset_enclose_block/tests b/test/tests/meshgenerators/sideset_enclose_block/tests new file mode 100644 index 000000000000..e4b318173743 --- /dev/null +++ b/test/tests/meshgenerators/sideset_enclose_block/tests @@ -0,0 +1,35 @@ +[Tests] + design = 'meshgenerators/SidesetsEncloseBlocks.md' + issues = '#20621' + + [sidesets_enclosed_blocks] + requirement = 'The system shall be able to check of a set of blocks is enclosed by a set of sidesets' + [check_no_error] + type = 'Exodiff' + input = 'sideset_enclose_blocks.i' + exodiff = 'check_no_error.e' + detail = '.' + cli_args = '--mesh-only check_no_error.e' + recover = false + [] + + [check_error] + type = RunException + input = 'sideset_enclose_blocks.i' + expect_err = "Element id 5 side 0 is external and not covered by specified boundaries." + cli_args = 'Mesh/add_sides/paired_block="1" --mesh-only' + detail = ' and error out if this condition is not met.' + recover = false + [] + + [add_new_boundary] + type = 'Exodiff' + input = 'sideset_enclose_blocks.i' + exodiff = 'add_new_boundary.e' + max_parallel = 1 + cli_args = 'Mesh/add_sides/paired_block="1" Mesh/enclosed/new_boundary=12 --mesh-only add_new_boundary.e' + detail = ' and upon user request add missing sides into a specified input.' + recover = false + [] + [] +[]