Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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 can't be flipped if the original element it is attached to doesn't have a neighbor through the sideset.

!syntax parameters /Mesh/FlipSidesetGenerator

!syntax inputs /Mesh/FlipSidesetGenerator

!syntax children /Mesh/FlipSidesetGenerator
24 changes: 24 additions & 0 deletions framework/include/meshgenerators/FlipSidesetGenerator.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
#pragma once

#include "MeshGenerator.h"

/**
* MeshGenerator for flipping sideset
*/
class FlipSidesetGenerator : public MeshGenerator
{
public:
static InputParameters validParams();

FlipSidesetGenerator(const InputParameters & parameters);

protected:
std::unique_ptr<MeshBase> generate() override;

private:
///Input mesh the operation will be applied to
std::unique_ptr<MeshBase> & _input;

///Name of the sideset to flip
const BoundaryName _sideset_name;
Copy link
Owner

@aprilnovak aprilnovak Dec 6, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be a const reference (const BoundaryName &)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a specific reason for this?

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, one reason is related to MOOSE's Controls system. That system allows an input file to essentially modify input file parameters on-the-fly -- so using a reference will make sure that everything in an input file is deeply connected to the input parameter.

It's also convention in MOOSE to use whenever possible, so when you cannot do const references there's some extra context that the software developer can have.

};
87 changes: 87 additions & 0 deletions framework/src/meshgenerators/FlipSidesetGenerator.C
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
//* 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<MeshGeneratorName>("input", "The mesh we want to modify");
params.addRequiredParam<BoundaryName>("boundary", "The sideset (boundary) that will be flipped");
return params;
}

FlipSidesetGenerator::FlipSidesetGenerator(const InputParameters & parameters)
: MeshGenerator(parameters),
_input(getMesh("input")),
_sideset_name(getParam<BoundaryName>("boundary"))
{
}

std::unique_ptr<MeshBase>
FlipSidesetGenerator::generate()
{
//get boundary info
BoundaryInfo & boundary_info = _input->get_boundary_info();
//get id of the input sideset
boundary_id_type 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)
mooseError("sideset doesn't exist in mesh");

//get sideset map
std::multimap<const Elem *, std::pair<unsigned short int, boundary_id_type>> 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)
{
unsigned short int old_side_id = std::get<0>(id_pair);
dof_id_type old_elem_id = old_elem->id();
const Elem * 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) + " doesn't have a neighbor through side " +
std::to_string(old_side_id) + " therefore it can't be flipped");

unsigned int new_elem_n_sides = new_elem->n_sides();
unsigned int new_side_id;

//To find the new_side_id:
//loop over new_elem sides until you find the neighbor which have the same id as the old elem
for (unsigned int i = 0; i < new_elem_n_sides; ++i)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did you ever try that "neighbor through face" idea? Which would get this information from the mesh topology instead of looping through and checking each face as a candidate?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you mean using a built in function that takes a neighbor and returns the side?

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I thought you had found a possible candidate - I'd prefer that if possible since it'd be a bit more readable code and would hopefully be faster (not a constraint from the test cases you have, but could be important for larger meshes).

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The only function I found that seemed to do something similar was: which_side_am_i (const Elem * e) which we discussed before and found that is not doing what I thought, other than this I didn't find a suitable candidate function

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, I am not sure - ok let's leave the code as-is for now, and we can ask on the MOOSE repo if they have any advice on where to look for something like that (if it exists).

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you check which_neighbor_am_i()
I think this may be the one, I didn't understand its documentation the first time but may be it the correct one
https://mooseframework.inl.gov/docs/doxygen/libmesh/classlibMesh_1_1Elem.html#ac4fd20bbdc7f49f25ecc67d67f2a2295

Copy link
Owner

@aprilnovak aprilnovak Dec 6, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that might be it (not 100% sure until we test it). It looks like these lines in which_neighbor_am_i look like they're doing the same looping-over-candidates you are doing (but it would be preferred if we can use the which_neighbor_am_i to reduce code duplication).

2326   for (unsigned int s=0, n_s = this->n_sides(); s != n_s; ++s)
 2327     if (this->[neighbor_ptr](s) == eparent)
 2328       return s;

Can you try it out?

{
const Elem * neighbor = new_elem->neighbor_ptr(i);
if(neighbor)
{
dof_id_type neighbor_id = neighbor->id();
if(neighbor_id == old_elem_id)
{
new_side_id = i;
boundary_info.remove_side(old_elem, old_side_id, sideset_id);
boundary_info.add_side(new_elem, new_side_id, sideset_id);
break;
}
}
}
}
}
return dynamic_pointer_cast<MeshBase>(_input);
}
97 changes: 97 additions & 0 deletions test/tests/meshgenerators/flip_sideset_generator/flux_flip_2D.i
Original file line number Diff line number Diff line change
@@ -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
[]
76 changes: 76 additions & 0 deletions test/tests/meshgenerators/flip_sideset_generator/flux_flip_3D.i
Original file line number Diff line number Diff line change
@@ -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
[]
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
time,area,flux
0,0,0
1,4,4
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
time,area,flux
0,0,0
1,1,1
16 changes: 16 additions & 0 deletions test/tests/meshgenerators/flip_sideset_generator/no_neighbor.i
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
[Mesh]
[gmg]
type = GeneratedMeshGenerator
dim = 2
nx = 1
ny = 1
[]
[flip]
type = FlipSidesetGenerator
input = gmg
boundary = 'right'
[]
[]
[Outputs]
exodus = true
[]
16 changes: 16 additions & 0 deletions test/tests/meshgenerators/flip_sideset_generator/no_sideset.i
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
[Mesh]
[gmg]
type = GeneratedMeshGenerator
dim = 2
nx = 1
ny = 1
[]
[flip]
type = FlipSidesetGenerator
input = gmg
boundary = 'side'
[]
[]
[Outputs]
exodus = true
[]
40 changes: 40 additions & 0 deletions test/tests/meshgenerators/flip_sideset_generator/tests
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
[Tests]
[flux_2D]
type = 'CSVDiff'
input = 'flux_flip_2D.i'
csvdiff = 'flux_flip_2D_out.csv'
recover = false
issues = '#25528'
design = 'FlipSidesetGenerator.md'
requirement = 'This system allows the sideset to be flipped'
[]

[flux_3D]
type = 'CSVDiff'
input = 'flux_flip_3D.i'
csvdiff = 'flux_flip_3D_out.csv'
recover = false
requirement = 'This system allows the sideset to be flipped'
design = 'FlipSidesetGenerator.md'
issues = '#25528'
[]
[no_sideset_exception]
type = 'RunException'
input = 'no_sideset.i'
cli_args = '--mesh-only'
expect_err = "sideset doesn't exist in mesh"
requirement = 'The system shall error'
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be expanded to get more specific - generally, you want the requirement to be unique for every test in the tests file (otherwise, strictly speaking, a test would be redundant with another test if it was entirely duplicative in what it is checking)

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same comment applies to the other three tests as well

design = 'FlipSidesetGenerator.md'
issues = '#25528'
[]
[no_neighbor_exception]
type = 'RunException'
input = 'no_neighbor.i'
cli_args = '--mesh-only'
expect_err = "neighbor"
requirement = 'The system shall error'
design = 'FlipSidesetGenerator.md'
issues = '#25528'
[]
[]