https://mooseframework.inl.gov
Loading...
Searching...
No Matches
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!
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
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
78std::unique_ptr<MeshBase>
80{
81 std::unique_ptr<MeshBase> mesh = std::move(_input);
82 if (!mesh->is_serial())
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() &&
112 std::find(_included_subdomain_ids.begin(),
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 {
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;
272 subdomain_id_type common_sub = std::numeric_limits<subdomain_id_type>::max();
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 {
279 if (common_sub == std::numeric_limits<subdomain_id_type>::max())
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
301void
303 const Point &,
304 const subdomain_id_type & sub_id,
305 MeshBase &)
306{
307 elem->subdomain_id() = sub_id;
308}
void mooseWarning(Args &&... args)
Emit a warning message with the given stringified, concatenated args.
Definition MooseError.h:345
void mooseError(Args &&... args)
Emit an error message with the given stringified, concatenated args and terminate the application.
Definition MooseError.h:311
registerMooseObject("MooseApp", SurfaceSubdomainsFromAllNormalsGenerator)
The main MOOSE class responsible for handling user-defined parameters in almost every MOOSE system.
void suppressParameter(const std::string &name)
This method suppresses an inherited parameter so that it isn't required or valid in the derived class...
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...
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.
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.
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...
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.
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:205
Point get2DElemNormal(const Elem *const elem) const
Get the normal of the 2D element.
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...
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...
static InputParameters validParams()
void setup(MeshBase &mesh)
Sets up various data structures.
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 'in...
bool _flood_only_once
Only act on each element once.
std::unique_ptr< MeshBase > & _input
the mesh to add the subdomains to
const bool _check_painted_neighor_normals
Additional heuristic: check the element neighbors and if they have already been painted with the subd...
Point _normal
if specified, then surface elements are only considered if their normal is close to this
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.
This class will add subdomains to the entire mesh based on unique normals.
std::unique_ptr< MeshBase > generate() override
Generate / modify the mesh.
std::map< SubdomainID, RealVectorValue > _subdomain_to_normal_map
Map from subdomain IDs to the normals of the corresponding boundaries.
const bool _contiguous_assignments_only
Whether to only use the flood algorithm to group elements, without looking for the previously created...
std::map< SubdomainID, Elem * > _subdomain_to_starting_elem
Map from subdomain IDs to a pointer to the element where the subdomain-paiting started.
SurfaceSubdomainsFromAllNormalsGenerator(const InputParameters &parameters)
void actOnElem(Elem *const elem, const Point &normal, const subdomain_id_type &sub_id, MeshBase &mesh) override
Action to perform when flooding.
MeshBase & mesh
SubdomainID getNextFreeSubdomainID(MeshBase &input_mesh)
Checks input mesh and returns max(block ID) + 1, which represents a block ID that is not currently in...