https://mooseframework.inl.gov
SurfaceMeshGeneratorBase.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 "MeshTraversingUtils.h"
14 
15 #include "libmesh/mesh_generation.h"
16 #include "libmesh/mesh.h"
17 #include "libmesh/elem.h"
18 #include "libmesh/remote_elem.h"
19 #include "libmesh/string_to_enum.h"
20 
23 {
25  params.addRequiredParam<MeshGeneratorName>("input", "The mesh we want to modify");
26  // NOTE: don't forget to suppress this if not using this base class to create subdomains
27  params.addRequiredParam<std::vector<SubdomainName>>(
28  "new_subdomain", "The list of subdomain names to create on the supplied subdomain");
29  params.addParam<std::vector<SubdomainName>>(
30  "included_subdomains",
31  "A set of subdomain names or ids an element has to be previously a part of to be considered "
32  "for modification.");
33 
34  // Painting/flooding parameters
35  // Using normals of the surface element
36  params.addParam<Point>("normal",
37  Point(),
38  "If supplied, only surface (2D) elements with normal equal to this, up to "
39  "normal_tol, will be considered for modification");
40  params.addRangeCheckedParam<Real>("normal_tol",
41  0.1,
42  "normal_tol>=0 & normal_tol<=2",
43  "Surface elements are "
44  "only added if face_normal.normal_hat >= "
45  "1 - normal_tol, where normal_hat = "
46  "normal/|normal|. The normal can the normal specified by the "
47  "parameter or by a specific mesh generator.");
48  params.addParam<bool>(
49  "fixed_normal",
50  false,
51  "Whether to move the normal vector as we paint/flood the geometry, or keep it "
52  "fixed from the first element we started painting with");
53  params.addParam<bool>("check_painted_neighbor_normals",
54  false,
55  "When examining the normal of a 2D element and comparing to the 'painting' "
56  "normal, also check if the element neighbors in the 'painted' subdomain "
57  "have a closer normal and accept the element into the new subdomain if so");
58 
59  // Parameters for handling surface elemets with flipped normals. These are unfortunately quite
60  // common in STL files, and if we do not account for them the flooding algorithm often floods
61  // elements all around the flipped normal element, creating a single-element patch 'hole' inside
62  // the larger patch!
63  params.addRangeCheckedParam<Real>("flipped_normal_tol",
64  0.1,
65  "flipped_normal_tol>=0 & flipped_normal_tol<=2",
66  "If 'consider_flipped_normals' is true, surface elements are "
67  "also added if -1 * face_normal.normal_hat >= "
68  "1 - normal_tol, where normal_hat = "
69  "normal/|normal|");
70  params.addParam<bool>(
71  "consider_flipped_normals",
72  false,
73  "Whether to allow for surface elements to be considered "
74  "when their normal is flipped with regard to the neighbor element we are painting from. A "
75  "specific tolerance may be specified for these flipped normals using the "
76  "'flipped_normal_tol'.");
77  params.addParam<bool>(
78  "flip_inverted_normals", false, "Whether to flip the elements with inverted normals");
79 
80  // Other painting / flooding parameters
81  params.addParam<std::vector<Real>>(
82  "max_paint_size_centroids",
83  "Maximum distance between element centroids (using a vertex average approximation) "
84  "when painting/flooding the geometry. 0 means not applying a distance constraint, a single "
85  "value in the vector is applied to all elements, multiple values can be specified for each "
86  "'included_subdomains'");
87  params.addParam<bool>(
88  "flood_elements_once",
89  false,
90  "Whether to consider elements only once when painting/flooding the geometry");
91  params.addParam<unsigned int>(
92  "flood_max_recursion",
93  1e5,
94  "Max number of recursions when flooding. Once this number is reached, the propagation is "
95  "stopped until enough calls have returned");
96 
97  params.addParamNamesToGroup("normal normal_tol fixed_normal check_painted_neighbor_normals",
98  "Flooding using surface element normals");
99  params.addParamNamesToGroup("consider_flipped_normals flipped_normal_tol",
100  "Flooding using surface element inverted normals");
101  params.addParamNamesToGroup("max_paint_size_centroids flood_elements_once", "Other flooding");
102  return params;
103 }
104 
106  : MeshGenerator(parameters),
107  _input(getMesh("input")),
108  _subdomain_names(isParamValid("new_subdomain")
109  ? getParam<std::vector<SubdomainName>>("new_subdomain")
110  : std::vector<SubdomainName>()),
111  _check_subdomains(isParamValid("included_subdomains")),
112  _included_subdomain_ids(std::vector<subdomain_id_type>()),
113  _using_normal(isParamSetByUser("normal") || isParamValid("_using_normal")),
114  _normal(isParamSetByUser("normal")
115  ? Point(getParam<Point>("normal") / getParam<Point>("normal").norm())
116  : getParam<Point>("normal")),
117  _normal_tol(getParam<Real>("normal_tol")),
118  _flipped_normal_tol(getParam<Real>("flipped_normal_tol")),
119  _fixed_flooding_normal(getParam<bool>("fixed_normal")),
120  _consider_flipped_normals(getParam<bool>("consider_flipped_normals")),
121  _flip_inverted_normals(getParam<bool>("flip_inverted_normals")),
122  _has_max_distance_criterion(isParamSetByUser("max_paint_size_centroids")),
123  _flood_only_once(getParam<bool>("flood_elements_once")),
124  _flood_max_recursion(getParam<unsigned int>("flood_max_recursion")),
125  _check_painted_neighor_normals(getParam<bool>("check_painted_neighbor_normals"))
126 {
127 }
128 
129 void
131 {
132  // To know the dimension of the mesh
133  if (!mesh.preparation().has_cached_elem_data)
134  mesh.cache_elem_data();
135 
136  // Get the subdomain ids from the names
137  if (isParamValid("included_subdomains"))
138  {
139  // check that the subdomains exist in the mesh
140  const auto & subdomains = getParam<std::vector<SubdomainName>>("included_subdomains");
141  for (const auto & name : subdomains)
143  paramError("included_subdomains", "The block '", name, "' was not found in the mesh");
144 
146  }
147 
148  // Set up the max elem-to-elem distance map
150  {
151  const auto & max_dists = getParam<std::vector<Real>>("max_paint_size_centroids");
152  if (max_dists.size() != _included_subdomain_ids.size() && max_dists.size() != 1)
153  paramError("max_paint_size_centroids",
154  "Maximum distance should be specified uniformly for all subdomains (1 value) or a "
155  "value for each 'included_subdomains'");
156 
157  if (_included_subdomain_ids.size())
158  for (const auto i : index_range(_included_subdomain_ids))
159  // Single value is applied for all subdomains
160  // 0 is translated to a very big number which therefore won't impose the criterion
162  (max_dists.size() == 1)
163  ? max_dists[0]
164  : (max_dists[i] > 0 ? max_dists[i]
166  else
167  _max_elem_distance[static_cast<subdomain_id_type>(-1)] = max_dists[0];
168  }
169 
170  // Clear the maps used to count visits
171  _visited.clear();
172  _acted_upon_once.clear();
173 }
174 
175 void
177  const Point & base_normal,
178  const Elem & starting_elem,
179  const subdomain_id_type & sub_id,
180  MeshBase & mesh)
181 {
182  // Avoid re-considering the same elements
183  if (elem == nullptr || elem == remote_elem ||
184  (_visited[sub_id].find(elem) != _visited[sub_id].end()))
185  return;
186  if (_flood_only_once && _acted_upon_once.count(elem))
187  return;
188 
189  // Skip if element is not in specified subdomains
190  if (_check_subdomains &&
192  return;
193 
194  _visited[sub_id].insert(elem);
195  auto elem_normal = get2DElemNormal(elem);
196 
197  bool criterion_met = false;
198  if (elementSatisfiesRequirements(elem, base_normal, starting_elem, elem_normal))
199  criterion_met = true;
200  // Additional heuristic based on neighbor normal vs element normal
202  {
203  // Try to flood from each side with the same subdomain
204  for (const auto neighbor : make_range(elem->n_sides()))
205  if (elem->neighbor_ptr(neighbor) &&
206  (elem->neighbor_ptr(neighbor)->subdomain_id() == sub_id) &&
208  elem, get2DElemNormal(elem->neighbor_ptr(neighbor)), starting_elem, elem_normal))
209  criterion_met = true;
210  }
211 
212  if (!criterion_met)
213  return;
214 
215  // Modify the subdomain
216  // TODO: consider working with base class attributes rather than arguments
217  actOnElem(elem, base_normal, sub_id, mesh);
218 
219  // We don't want to remove the element from consideration too early
220  _acted_upon_once.insert(elem);
221 
222  // Flip the element if needed
223  // NOTE: we have already passed "elementSatisfiesRequirements" here
224  if (_consider_flipped_normals && base_normal * elem_normal < 0)
225  {
226  elem_normal *= -1;
227  BoundaryInfo & boundary_info = mesh.get_boundary_info();
228 
230  elem->flip(&boundary_info);
231  }
232 
233  for (const auto neighbor : make_range(elem->n_sides()))
234  {
236  // Flood to the neighboring elements
238  flood(elem->neighbor_ptr(neighbor),
239  _fixed_flooding_normal ? base_normal : elem_normal,
240  starting_elem,
241  sub_id,
242  mesh);
244  }
245 }
246 
247 bool
249  const Point & desired_normal,
250  const Elem & base_elem,
251  const Point & face_normal) const
252 {
253  // False if element is not in specified subdomains
254  if (_check_subdomains &&
256  return false;
257 
258  // False if normal does not meet criteria
259  if (_using_normal &&
260  (!MeshTraversingUtils::normalsWithinTol(desired_normal, face_normal, _normal_tol) &&
262  !MeshTraversingUtils::normalsWithinTol(desired_normal, -face_normal, _flipped_normal_tol))))
263  return false;
264 
265  // False if exceeding the patch size
267  {
268  // The subdomain from which the element to paint over comes from is used to find the limitation
269  // on the radius, which will effectively be applied onto the new subdomains (to which base_elem
270  // already belongs)
271  const auto max_dsq =
273  ? MathUtils::pow(libmesh_map_find(_max_elem_distance, elem->subdomain_id()), 2)
274  : MathUtils::pow(
275  libmesh_map_find(_max_elem_distance, static_cast<subdomain_id_type>(-1)), 2);
276  if ((elem->vertex_average() - base_elem.vertex_average()).norm_sq() > max_dsq)
277  return false;
278  }
279 
280  return true;
281 }
282 
283 Point
284 SurfaceMeshGeneratorBase::get2DElemNormal(const Elem * const elem) const
285 {
286  mooseAssert(elem->dim() == 2, "Should be a 2D element");
287  mooseAssert(elem->default_order() == FIRST, "Should be a first order element");
288  // TODO: add support for non-planar element
289  // TODO: optimize to avoid the allocation
290  if (elem->n_nodes() > 3)
291  {
292  std::vector<Point> vec_pts(elem->n_nodes());
293  for (const auto & node : elem->node_ref_range())
294  vec_pts.push_back(node);
295  if (!MooseMeshUtils::isCoPlanar(vec_pts))
296  mooseDoOnce(mooseWarning("A non planar element was detected. Normal is approximated with the "
297  "first 3 nodes at time."););
298  }
299 
300  const auto & p1 = elem->point(0);
301  const auto & p2 = elem->point(1);
302  const auto & p3 = elem->point(2);
303  auto elem_normal = (p1 - p2).cross(p1 - p3);
304  if (elem_normal.norm_sq() == 0)
305  {
306  mooseWarning("Colinear nodes on element " + std::to_string(elem->id()) + ", using 0 normal");
307  return elem_normal;
308  }
309  return elem_normal.unit();
310 }
bool elementSubdomainIdInList(const Elem *const elem, const std::vector< subdomain_id_type > &subdomain_id_list)
Determines whether the given element&#39;s subdomain id is in the given subdomain_id_list.
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.
std::unordered_map< subdomain_id_type, std::unordered_set< Elem * > > _visited
Map used for the flooding algorithm to keep track of which elements have been visited for which subdo...
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 paramError(const std::string &param, Args... args) const
Emits an error prefixed with the file and line number of the given param (from the input file) along ...
Definition: MooseBase.h:467
const Real _normal_tol
Tolerance to group elements with normals such that face_normal.normal_hat <= 1 - normal_tol where nor...
bool _using_normal
true if only elements are only considered when their normal is close to either the "_normal" or a mov...
auto norm_sq(const T &a)
bool isCoPlanar(const std::vector< Point > &vec_pts, const Point plane_nvec, const Point fixed_pt)
Decides whether all the Points of a vector of Points are in a plane that is defined by a normal vecto...
FIRST
SurfaceMeshGeneratorBase(const InputParameters &parameters)
MeshBase & mesh
const bool _flip_inverted_normals
Whether to flip the normal of a surface element when they meet the criterion.
The main MOOSE class responsible for handling user-defined parameters in almost every MOOSE system...
std::vector< subdomain_id_type > getSubdomainIDs(const libMesh::MeshBase &mesh, const std::vector< SubdomainName > &subdomain_name)
Get the associated subdomainIDs for the subdomain names that are passed in.
Point get2DElemNormal(const Elem *const elem) const
Get the normal of the 2D element.
void addRequiredParam(const std::string &name, const std::string &doc_string)
This method adds a parameter and documentation string to the InputParameters object that will be extr...
auto max(const L &left, const R &right)
virtual void actOnElem(Elem *const elem, const Point &normal, const subdomain_id_type &sub_id, MeshBase &mesh)=0
Action to perform when flooding.
unsigned int _flood_recursion_count
Current tally for the number of flood routine calls active.
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...
const std::string & name() const
Get the name of the class.
Definition: MooseBase.h:103
const Real _flipped_normal_tol
Tolerance but when using the flipped normal.
std::unordered_map< subdomain_id_type, Real > _max_elem_distance
Distance to use for max painting radius. This distance can be specified per subdomain.
static InputParameters validParams()
Definition: MeshGenerator.C:23
static InputParameters validParams()
bool hasSubdomainName(const MeshBase &input_mesh, const SubdomainName &name)
Whether a particular subdomain name exists in the mesh.
DIE A HORRIBLE DEATH HERE typedef LIBMESH_DEFAULT_SCALAR_TYPE Real
const bool _consider_flipped_normals
Whether to also consider surface elements that have a flipped normal.
auto norm(const T &a)
const bool _has_max_distance_criterion
Whether to painting beyond a certain radius.
const unsigned int _flood_max_recursion
Maximum amount of calls to the flood routine at once.
const bool _check_subdomains
whether to check the prior subdomain id of the element when choosing whether to change its subdomain ...
IntRange< T > make_range(T beg, T end)
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...
void addRangeCheckedParam(const std::string &name, const T &value, const std::string &parsed_function, const std::string &doc_string)
bool isParamValid(const std::string &name) const
Test if the supplied parameter is valid.
Definition: MooseBase.h:209
const bool _fixed_flooding_normal
Whether to paint/flood using a fixed normal or a moving normal.
T pow(T x, int e)
Definition: MathUtils.h:91
MooseUnits pow(const MooseUnits &, int)
Definition: Units.C:537
MeshGenerators are objects that can modify or add to an existing mesh.
Definition: MeshGenerator.h:33
void ErrorVector unsigned int
auto index_range(const T &sizable)
bool normalsWithinTol(const Point &normal_1, const Point &normal_2, const Real tol)
Determines whether two normal vectors are within normal_tol of each other.
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...
const RemoteElem * remote_elem