https://mooseframework.inl.gov
SurfaceSubdomainsFromAllNormalsGenerator.C
Go to the documentation of this file.
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 
11 #include "InputParameters.h"
12 #include "MooseMeshUtils.h"
13 #include "CastUniquePointer.h"
14 
15 #include "libmesh/mesh_generation.h"
16 #include "libmesh/mesh.h"
17 #include "libmesh/string_to_enum.h"
18 #include "libmesh/elem.h"
19 
20 #include <typeinfo>
21 
23 
26 {
28 
29  params.renameParam("max_paint_size_centroids", "max_subdomain_size_centroids", "");
30  params.addParam<bool>(
31  "contiguous_assignments_only",
32  false,
33  "Whether to only group elements in a subdomain using the 'flooding' algorithm. "
34  "Note: this also sets 'flood_elements_once' to true if the user did not explicitly pass "
35  "false.");
36 
37  // Post flooding operations/cleanup
38  params.addParam<bool>("select_max_neighbor_element_subdomains",
39  false,
40  "Whether to perform a final subdomain assignment phase where each element "
41  "is assigned to the subdomain "
42  "that holds the most neighbors of the element with a similar normal.");
43  params.addParam<bool>(
44  "separate_elements_connected_by_a_single_node",
45  false,
46  "Whether to perform a final subdomain assignment phase where any element that is not "
47  "connected to other elements from the same subdomain is re-assigned to either the subdomain "
48  "that all side neighbor elements belong to, or a new subdomain if there is no such subdomain "
49  "including all its side neighbors. Note that normals are not checked in this phase, and the "
50  "subdomain size criterion is assumed to be already met.");
51 
52  params.addParamNamesToGroup("contiguous_assignments_only", "Other flooding");
53  // NOTE: these post-flooding / post-treatment operations could be moved to a base class.
54  // They could also become their own mesh generator!
55  params.addParamNamesToGroup(
56  "separate_elements_connected_by_a_single_node select_max_neighbor_element_subdomains",
57  "Post flooding subdomain re-assignments");
58 
59  // There can be many of them, and we don't control the number in this generator
60  params.suppressParameter<std::vector<SubdomainName>>("new_subdomain");
61  // Using normals is the base principle of this mesh generator
62  params.addPrivateParam<bool>("_using_normal", true);
63 
64  params.addClassDescription(
65  "Adds subdomains to surface (2D) elements in the (3D) mesh based on unique normals.");
66  return params;
67 }
68 
70  const InputParameters & parameters)
71  : SurfaceMeshGeneratorBase(parameters),
72  _contiguous_assignments_only(getParam<bool>("contiguous_assignments_only"))
73 {
74  if (_contiguous_assignments_only && !isParamSetByUser("flood_elements_once"))
75  _flood_only_once = true;
76 }
77 
78 std::unique_ptr<MeshBase>
80 {
81  std::unique_ptr<MeshBase> mesh = std::move(_input);
82  if (!mesh->is_serial())
83  mooseError(
84  "SurfaceSubdomainsFromAllNormalsGenerator is not implemented for non serialized meshes");
85  setup(*mesh);
86 
87  unsigned int num_neighborless = 0;
88  const bool user_specified_normal = isParamSetByUser("normal");
89 
90  // Make sure neighbors are known
91  if (!mesh->preparation().has_neighbor_ptrs)
92  mesh->find_neighbors();
93 
94  // We'll need to loop over all of the elements to find ones that match this normal.
95  // We can't rely on flood catching them all in one go. We have to flood from multiple elements
96  // in case there are disconnected groups of elements.
97  // Depending on user parameters, we may be "flooding" multiple times the same elements
98  for (auto & elem : mesh->element_ptr_range())
99  {
100  // Nothing to do with edges or 3D elements
101  if (elem->dim() != 2)
102  continue;
103  // Likely an issue with the mesh
104  if (_contiguous_assignments_only && elem->n_neighbors() == 0)
105  {
106  num_neighborless++;
107  mooseWarning("Element '" + std::to_string(elem->id()) + "' has no neighbors");
108  }
109 
110  // Check if element should be used to paint from
111  if (_included_subdomain_ids.size() &&
114  elem->subdomain_id()) == _included_subdomain_ids.end())
115  continue;
116 
117  // Compute the normal
118  const auto elem_normal = get2DElemNormal(elem);
119 
120  // Four options for which subdomain to paint with:
121  // 1) See if we've seen this normal before (linear search), within some tolerance
122  // NOTE: these elements might not be neighbors of the current element,
123  // so the patch may not be contiguous
124  const std::map<SubdomainID, RealVectorValue>::value_type * item = nullptr;
125  bool sub_id_found = false;
127  for (const auto & id_pair : _subdomain_to_normal_map)
129  elem, id_pair.second, *_subdomain_to_starting_elem[id_pair.first], elem_normal))
130  {
131  sub_id_found = true;
132  item = &id_pair;
133  break;
134  }
135  // 2) Look at the neighbors, if a majority of them have the same subdomain and a similar normal,
136  // use that subdomain to paint elements with
137  // NOTE: compatible with 'contiguous_assignments_only' option
138  // NOTE: this overrides the result of the previous search
139  SubdomainID neighbor_majority_sub_id;
141  {
142  std::map<SubdomainID, unsigned int> sub_id_neighbors;
143  // Try to flood from each side with the same subdomain
144  // Look for the neighbor subdomain id with the most neighbors
145  for (const auto neighbor : make_range(elem->n_sides()))
146  if (elem->neighbor_ptr(neighbor) &&
147  _acted_upon_once.find(elem->neighbor_ptr(neighbor)) != _acted_upon_once.end() &&
149  get2DElemNormal(elem->neighbor_ptr(neighbor)),
150  *elem->neighbor_ptr(neighbor),
151  elem_normal))
152  {
153  sub_id_found = true;
154  sub_id_neighbors[elem->neighbor_ptr(neighbor)->subdomain_id()]++;
155  }
156 
157  unsigned int max_of_subid = 0;
158  for (const auto & [key, item] : sub_id_neighbors)
159  if (item >= max_of_subid)
160  {
161  max_of_subid = item;
162  neighbor_majority_sub_id = key;
163  }
164 
165  // Note: the max distance from starting element of the subdomain flooding to this
166  // element should be checked in the call to flood()
167  }
168  // 3) Flood only with the selected fixed normal
169  // Note: this steers away from an "FromAllNormals" generator, it's more like a "FromNormal"
170  // generator capability.
171  if (user_specified_normal && !elementSatisfiesRequirements(elem, _normal, *elem, elem_normal))
172  continue;
173  // 4) Flood with a new subdomain every time ('contiguous_assignments_only' option)
174 
175  // Finalize flooding parameter selection
176  Elem * starting_element = elem;
177  subdomain_id_type flooding_sub_id;
178  Point flooding_normal = elem_normal;
179  if (item)
180  {
181  starting_element = _subdomain_to_starting_elem[item->first];
182  flooding_sub_id = item->first;
183  flooding_normal = item->second;
184  }
185  else if (sub_id_found)
186  {
187  starting_element = _subdomain_to_starting_elem[neighbor_majority_sub_id];
188  flooding_sub_id = neighbor_majority_sub_id;
189  }
190  else
191  {
192  flooding_sub_id = MooseMeshUtils::getNextFreeSubdomainID(*mesh);
193  _subdomain_to_normal_map[flooding_sub_id] = elem_normal;
194  _subdomain_to_starting_elem[flooding_sub_id] = elem;
195  }
196  // User input of a normal takes precedence
197  if (user_specified_normal)
198  {
199  flooding_normal = _normal;
200  _subdomain_to_normal_map[flooding_sub_id] = flooding_normal;
201  }
202 
203  // Flood with the previously created subdomains and normals
204  flood(elem, flooding_normal, *starting_element, flooding_sub_id, *mesh);
205  }
206 
207  // Check all elements once with only neighbor averaging
208  if (getParam<bool>("select_max_neighbor_element_subdomains"))
209  for (auto & elem : mesh->element_ptr_range())
210  {
211  // Nothing to do with edges or 3D elements
212  if (elem->dim() != 2)
213  continue;
214 
215  // Compute the normal
216  const auto normal = get2DElemNormal(elem);
217 
218  bool sub_id_found = false;
219  SubdomainID sub_id = 0;
220  std::map<SubdomainID, unsigned int> sub_id_neighbors;
221 
222  // NOTE: we use side neighbors instead of point neighbors as side neighbors
223  // are always connected by 2 nodes to the rest of the subdomains, and elements
224  // connected by a single node to a subdomain cause issues when triangulating
225  // that subdomain.
226 
227  // Try to flood from each side with the same subdomain
228  // Look for the neighbor subdomain id with the most neighbors
229  for (const auto neighbor_i : make_range(elem->n_sides()))
230  {
231  const auto neighbor = elem->neighbor_ptr(neighbor_i);
232  // NOTE: to avoid exceeding the patch size, we pass the element that was used to start
233  // painting the patch to check the 'distance'/'patch size' criterion
234  if (neighbor &&
236  get2DElemNormal(neighbor),
237  *_subdomain_to_starting_elem[neighbor->subdomain_id()],
238  normal))
239  {
240  sub_id_found = true;
241  sub_id_neighbors[neighbor->subdomain_id()]++;
242  }
243  }
244 
245  unsigned int max_of_subid = 0;
246  for (const auto & [key, item] : sub_id_neighbors)
247  if (item >= max_of_subid)
248  {
249  max_of_subid = item;
250  sub_id = key;
251  }
252  if (sub_id_found)
253  elem->subdomain_id() = sub_id;
254  }
255 
256  if (getParam<bool>("separate_elements_connected_by_a_single_node"))
257  for (auto & elem : mesh->element_ptr_range())
258  {
259  // Nothing to do with edges or 3D elements
260  if (elem->dim() != 2)
261  continue;
262 
263  bool connected_to_a_neighbor = false;
264  for (const auto neighbor : make_range(elem->n_sides()))
265  if (elem->neighbor_ptr(neighbor) &&
266  elem->subdomain_id() == elem->neighbor_ptr(neighbor)->subdomain_id())
267  connected_to_a_neighbor = true;
268 
269  if (!connected_to_a_neighbor)
270  {
271  bool same_subdomain = true;
273 
274  // If all side-neighbors have the same subdomain, use that subdomain instead
275  // NOTE: we don't check the normal criteria here
276  for (const auto neighbor : make_range(elem->n_sides()))
277  if (elem->neighbor_ptr(neighbor))
278  {
280  common_sub = elem->neighbor_ptr(neighbor)->subdomain_id();
281  else if (common_sub != elem->neighbor_ptr(neighbor)->subdomain_id())
282  same_subdomain = false;
283  }
284 
285  if (same_subdomain && (common_sub != std::numeric_limits<subdomain_id_type>::max()))
286  elem->subdomain_id() = common_sub;
287  else
288  elem->subdomain_id() = MooseMeshUtils::getNextFreeSubdomainID(*mesh);
289  }
290  }
291 
292  if (_contiguous_assignments_only && num_neighborless)
293  mooseWarning("Several subdomains were created for neighborless elements: " +
294  std::to_string(num_neighborless));
295 
296  // subdomain assignments have changed
297  mesh->unset_is_prepared();
298  return dynamic_pointer_cast<MeshBase>(mesh);
299 }
300 
301 void
303  const Point &,
304  const subdomain_id_type & sub_id,
305  MeshBase &)
306 {
307  elem->subdomain_id() = sub_id;
308 }
void renameParam(const std::string &old_name, const std::string &new_name, const std::string &new_docstring)
Rename a parameter and provide a new documentation string.
registerMooseObject("MooseApp", SurfaceSubdomainsFromAllNormalsGenerator)
KOKKOS_INLINE_FUNCTION const T * find(const T &target, const T *const begin, const T *const end)
Find a value in an array.
Definition: KokkosUtils.h:40
void setup(MeshBase &mesh)
Sets up various data structures.
void flood(Elem *const elem, const Point &normal, const Elem &starting_elem, const subdomain_id_type &sub_id, MeshBase &mesh)
This method implements a recursive flood routine to paint (applying an operation) to elements on mesh...
void addPrivateParam(const std::string &name, const T &value)
These method add a parameter to the InputParameters object which can be retrieved like any other para...
MeshBase & mesh
std::unique_ptr< MeshBase > generate() override
Generate / modify the mesh.
The main MOOSE class responsible for handling user-defined parameters in almost every MOOSE system...
std::unique_ptr< T_DEST, T_DELETER > dynamic_pointer_cast(std::unique_ptr< T_SRC, T_DELETER > &src)
These are reworked from https://stackoverflow.com/a/11003103.
Point get2DElemNormal(const Elem *const elem) const
Get the normal of the 2D element.
auto max(const L &left, const R &right)
void suppressParameter(const std::string &name)
This method suppresses an inherited parameter so that it isn&#39;t required or valid in the derived class...
void mooseWarning(Args &&... args) const
const bool _check_painted_neighor_normals
Additional heuristic: check the element neighbors and if they have already been painted with the subd...
SurfaceSubdomainsFromAllNormalsGenerator(const InputParameters &parameters)
std::map< SubdomainID, Elem * > _subdomain_to_starting_elem
Map from subdomain IDs to a pointer to the element where the subdomain-paiting started.
void actOnElem(Elem *const elem, const Point &normal, const subdomain_id_type &sub_id, MeshBase &mesh) override
Action to perform when flooding.
Point _normal
if specified, then surface elements are only considered if their normal is close to this ...
static InputParameters validParams()
IntRange< T > make_range(T beg, T end)
void mooseError(Args &&... args) const
Emits an error prefixed with object name and type and optionally a file path to the top-level block p...
Definition: MooseBase.h:281
void addClassDescription(const std::string &doc_string)
This method adds a description of the class that will be displayed in the input file syntax dump...
void addParam(const std::string &name, const S &value, const std::string &doc_string)
These methods add an optional parameter and a documentation string to the InputParameters object...
bool _flood_only_once
Only act on each element once.
std::unordered_set< Elem * > _acted_upon_once
Set used when flooding each element once. If the element pointer is in the set, it has been visited a...
SubdomainID getNextFreeSubdomainID(MeshBase &input_mesh)
Checks input mesh and returns max(block ID) + 1, which represents a block ID that is not currently in...
std::map< SubdomainID, RealVectorValue > _subdomain_to_normal_map
Map from subdomain IDs to the normals of the corresponding boundaries.
bool isParamSetByUser(const std::string &name) const
Test if the supplied parameter is set by a user, as opposed to not set or set to default.
Definition: MooseBase.h:215
This class will add subdomains to the entire mesh based on unique normals.
const bool _contiguous_assignments_only
Whether to only use the flood algorithm to group elements, without looking for the previously created...
std::unique_ptr< MeshBase > & _input
the mesh to add the subdomains to
void addParamNamesToGroup(const std::string &space_delim_names, const std::string group_name)
This method takes a space delimited list of parameter names and adds them to the specified group name...
std::vector< subdomain_id_type > _included_subdomain_ids
A list of included subdomain ids that the element has to be priorly a part of, extracted from the &#39;in...
bool elementSatisfiesRequirements(const Elem *const elem, const Point &desired_normal, const Elem &base_elem, const Point &face_normal) const
Determines whether the given element satisfies a set of criteria that are defined in this base class...