diff --git a/framework/doc/content/source/meshgenerators/FlipSidesetGenerator.md b/framework/doc/content/source/meshgenerators/FlipSidesetGenerator.md new file mode 100644 index 000000000000..8a84c1a77614 --- /dev/null +++ b/framework/doc/content/source/meshgenerators/FlipSidesetGenerator.md @@ -0,0 +1,14 @@ +# FlipSidesetGenerator + +!syntax description /Mesh/FlipSidesetGenerator + +## Overview + +The `FlipSidesetGenerator` object flips the normal of a sideset by changing the element the sideset is attached to. +The sideset cannot be flipped if the original element it is attached to does not have a neighbor through the sideset. + +!syntax parameters /Mesh/FlipSidesetGenerator + +!syntax inputs /Mesh/FlipSidesetGenerator + +!syntax children /Mesh/FlipSidesetGenerator \ No newline at end of file diff --git a/framework/include/meshgenerators/FlipSidesetGenerator.h b/framework/include/meshgenerators/FlipSidesetGenerator.h new file mode 100644 index 000000000000..8eacb1d70293 --- /dev/null +++ b/framework/include/meshgenerators/FlipSidesetGenerator.h @@ -0,0 +1,33 @@ +//* 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 for flipping a sideset + */ +class FlipSidesetGenerator : public MeshGenerator +{ +public: + static InputParameters validParams(); + + FlipSidesetGenerator(const InputParameters & parameters); + +protected: + std::unique_ptr generate() override; + +private: + /// Input mesh the operation will be applied to + std::unique_ptr & _input; + + /// Name of the sideset to flip + const BoundaryName _sideset_name; +}; diff --git a/framework/src/meshgenerators/FlipSidesetGenerator.C b/framework/src/meshgenerators/FlipSidesetGenerator.C new file mode 100644 index 000000000000..a97f212412f4 --- /dev/null +++ b/framework/src/meshgenerators/FlipSidesetGenerator.C @@ -0,0 +1,72 @@ +//* 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 "FlipSidesetGenerator.h" + +#include "CastUniquePointer.h" + +registerMooseObject("MooseApp", FlipSidesetGenerator); + +InputParameters +FlipSidesetGenerator::validParams() +{ + InputParameters params = MeshGenerator::validParams(); + params.addClassDescription("A Mesh Generator which flips a given sideset"); + params.addRequiredParam("input", "The mesh we want to modify"); + params.addRequiredParam("boundary", "The sideset (boundary) that will be flipped"); + return params; +} + +FlipSidesetGenerator::FlipSidesetGenerator(const InputParameters & parameters) + : MeshGenerator(parameters), + _input(getMesh("input")), + _sideset_name(getParam("boundary")) +{ +} + +std::unique_ptr +FlipSidesetGenerator::generate() +{ + // get boundary info + BoundaryInfo & boundary_info = _input->get_boundary_info(); + // get id of the input sideset + const auto sideset_id = boundary_info.get_id_by_name(_sideset_name); + + // Throw an error if the sideset doesn't exist + if (sideset_id == libMesh::BoundaryInfo::invalid_id) + paramError("boundary", "The boundary '", _sideset_name, "' was not found"); + + // get a copy of sideset map to avoid changing the sideset map while looping on it + std::multimap> sideset_map = + boundary_info.get_sideset_map(); + + // old_elem is the original element attached to the sideset before flipping + // new_elem is the element attached to the sideset after flipping + for (const auto & [old_elem, id_pair] : sideset_map) + { + boundary_id_type boundary_id = std::get<1>(id_pair); + if (boundary_id == sideset_id) + { + const auto old_side_id = std::get<0>(id_pair); + const auto old_elem_id = old_elem->id(); + const auto new_elem = old_elem->neighbor_ptr(old_side_id); + + // Throw an error if the old element doesn't have a neighbor on the old side + if (!new_elem) + mooseError("elem " + std::to_string(old_elem_id) + + " does not have a neighbor through side " + std::to_string(old_side_id) + + " therefore it cannot be flipped"); + + const auto new_side_id = new_elem->which_neighbor_am_i(old_elem); + boundary_info.remove_side(old_elem, old_side_id, sideset_id); + boundary_info.add_side(new_elem, new_side_id, sideset_id); + } + } + return dynamic_pointer_cast(_input); +} diff --git a/test/tests/meshgenerators/flip_sideset_generator/flux_flip_2D.i b/test/tests/meshgenerators/flip_sideset_generator/flux_flip_2D.i new file mode 100644 index 000000000000..ffca2baf617b --- /dev/null +++ b/test/tests/meshgenerators/flip_sideset_generator/flux_flip_2D.i @@ -0,0 +1,97 @@ +[Mesh] + [gmg] + type = GeneratedMeshGenerator + dim = 2 + nx = 3 + ny = 3 + xmax = 3 + ymax = 3 + [] + [s1] + type = ParsedGenerateSideset + input = gmg + combinatorial_geometry = 'x > 0.9 & x < 1.1 & y > -0.1 & y < 1.1' + normal = '1 0 0' + new_sideset_name = s1 + [] + [s2] + type = ParsedGenerateSideset + input = s1 + combinatorial_geometry = 'x > 0.9 & x < 2.1 & y > 0.9 & y < 1.1' + normal = '0 1 0' + new_sideset_name = s2 + [] + [s3] + type = ParsedGenerateSideset + input = s2 + combinatorial_geometry = 'x > 1.9 & x < 2.1 & y > 0.9 & y < 2.1' + normal = '1 0 0' + new_sideset_name = s3 + [] + [s4] + type = ParsedGenerateSideset + input = s3 + combinatorial_geometry = 'x > 1.9 & x < 3.1 & y > 1.9 & y < 2.1' + normal = '0 1 0' + new_sideset_name = s4 + [] + [sideset] + type = SideSetsFromBoundingBoxGenerator + input = s4 + bottom_left = '0 0 0' + top_right = '3 3 3' + boundaries_old = 's1 s2 s3 s4' + boundary_new = 's_combined' + [] + [flip] + type = FlipSidesetGenerator + input = sideset + boundary = s_combined + [] +[] + +[AuxVariables] + [u] + [] +[] + +[AuxKernels] + [diffusion] + type = FunctionAux + variable = u + function = func + [] +[] + +[Functions] + [func] + type = ParsedFunction + expression = x+y + [] +[] + +[Problem] + type = FEProblem + solve = false +[] + +[Postprocessors] + [flux] + type = SideDiffusiveFluxIntegral + variable = u + boundary = s_combined + diffusivity = 1 + [] + [area] + type = AreaPostprocessor + boundary = s_combined + [] +[] + +[Executioner] + type = Steady +[] + +[Outputs] + csv = true +[] diff --git a/test/tests/meshgenerators/flip_sideset_generator/flux_flip_3D.i b/test/tests/meshgenerators/flip_sideset_generator/flux_flip_3D.i new file mode 100644 index 000000000000..281f241ca8ee --- /dev/null +++ b/test/tests/meshgenerators/flip_sideset_generator/flux_flip_3D.i @@ -0,0 +1,76 @@ +[Mesh] + [gmg] + type = GeneratedMeshGenerator + dim = 3 + nx = 3 + ny = 3 + nz = 3 + xmax = 3 + ymax = 3 + zmax = 3 + [] + [subdomains] + type = ParsedSubdomainMeshGenerator + input = gmg + combinatorial_geometry = 'x < 1 & y > 1 & y < 2' + block_id = 1 + [] + [sideset] + type = ParsedGenerateSideset + input = subdomains + combinatorial_geometry = 'z < 1' + included_subdomains = '1' + normal = '1 0 0' + new_sideset_name = interior + [] + [flip] + type = FlipSidesetGenerator + input = sideset + boundary = interior + [] +[] +[AuxVariables] + [u] + [] +[] + +[AuxKernels] + [diffusion] + type = FunctionAux + variable = u + function = func + [] +[] + +[Functions] + [func] + type = ParsedFunction + expression = x+y+z + [] +[] + +[Problem] + type = FEProblem + solve = false +[] + +[Postprocessors] + [flux] + type = SideDiffusiveFluxIntegral + variable = u + boundary = interior + diffusivity = 1 + [] + [area] + type = AreaPostprocessor + boundary = interior + [] +[] + +[Executioner] + type = Steady +[] + +[Outputs] + csv = true +[] diff --git a/test/tests/meshgenerators/flip_sideset_generator/gold/flux_flip_2D_out.csv b/test/tests/meshgenerators/flip_sideset_generator/gold/flux_flip_2D_out.csv new file mode 100644 index 000000000000..27ae8a591af6 --- /dev/null +++ b/test/tests/meshgenerators/flip_sideset_generator/gold/flux_flip_2D_out.csv @@ -0,0 +1,3 @@ +time,area,flux +0,0,0 +1,4,4 diff --git a/test/tests/meshgenerators/flip_sideset_generator/gold/flux_flip_3D_out.csv b/test/tests/meshgenerators/flip_sideset_generator/gold/flux_flip_3D_out.csv new file mode 100644 index 000000000000..a24fa4fd19eb --- /dev/null +++ b/test/tests/meshgenerators/flip_sideset_generator/gold/flux_flip_3D_out.csv @@ -0,0 +1,3 @@ +time,area,flux +0,0,0 +1,1,1 diff --git a/test/tests/meshgenerators/flip_sideset_generator/no_neighbor.i b/test/tests/meshgenerators/flip_sideset_generator/no_neighbor.i new file mode 100644 index 000000000000..e543990f06d9 --- /dev/null +++ b/test/tests/meshgenerators/flip_sideset_generator/no_neighbor.i @@ -0,0 +1,16 @@ +[Mesh] + [gmg] + type = GeneratedMeshGenerator + dim = 2 + nx = 1 + ny = 1 + [] + [flip] + type = FlipSidesetGenerator + input = gmg + boundary = 'right' + [] +[] +[Outputs] + exodus = true +[] diff --git a/test/tests/meshgenerators/flip_sideset_generator/no_sideset.i b/test/tests/meshgenerators/flip_sideset_generator/no_sideset.i new file mode 100644 index 000000000000..0d6da7e82af0 --- /dev/null +++ b/test/tests/meshgenerators/flip_sideset_generator/no_sideset.i @@ -0,0 +1,16 @@ +[Mesh] + [gmg] + type = GeneratedMeshGenerator + dim = 2 + nx = 1 + ny = 1 + [] + [flip] + type = FlipSidesetGenerator + input = gmg + boundary = 'bad_side' + [] +[] +[Outputs] + exodus = true +[] diff --git a/test/tests/meshgenerators/flip_sideset_generator/tests b/test/tests/meshgenerators/flip_sideset_generator/tests new file mode 100644 index 000000000000..1319ef08dc32 --- /dev/null +++ b/test/tests/meshgenerators/flip_sideset_generator/tests @@ -0,0 +1,39 @@ +[Tests] + issues = '#25528' + design = 'FlipSidesetGenerator.md' + [flux_2D] + type = 'CSVDiff' + input = 'flux_flip_2D.i' + csvdiff = 'flux_flip_2D_out.csv' + recover = false + requirement = 'The system shall support switching the normal orientation of a sideset in a two-dimensional mesh' + mesh_mode = 'replicated' + [] + + [flux_3D] + type = 'CSVDiff' + input = 'flux_flip_3D.i' + csvdiff = 'flux_flip_3D_out.csv' + recover = false + requirement = 'The system shall support switching the normal orientation of a sideset in a three-dimensional mesh' + mesh_mode = 'replicated' + [] + + [no_sideset_exception] + type = 'RunException' + input = 'no_sideset.i' + cli_args = '--mesh-only' + expect_err = "The boundary 'bad_side' was not found" + requirement = "The system shall produce a reasonable error when switching the normal orientation of a sideset if the sideset does not exist" + mesh_mode = 'replicated' + [] + + [no_neighbor_exception] + type = 'RunException' + input = 'no_neighbor.i' + cli_args = '--mesh-only' + expect_err = "elem 0 does not have a neighbor through side 1 therefore it cannot be flipped" + requirement = "The system shall produce a reasonable error when switching the normal orientation of a sideset if the sideset cannot be flipped" + mesh_mode = 'replicated' + [] +[]