14#include "libmesh/elem.h"
24 "Coarsens a 2D-element (TRI3/QUAD4) surface mesh along a sideset by collapsing alternate "
25 "boundary nodes. The sideset may be internal: elements on both sides of it are coarsened "
26 "and the sideset itself is preserved. Apply the generator multiple times for additional "
28 params.
addRequiredParam<MeshGeneratorName>(
"input",
"Input mesh to coarsen");
29 params.
addParam<std::vector<BoundaryName>>(
"boundaries",
30 "The sideset(s) to coarsen the mesh along");
31 params.
addParam<std::vector<BoundaryName>>(
33 "Coarsen the mesh along all of its sidesets except these. Mutually exclusive with "
36 "max_normal_deviation",
37 "max_normal_deviation >= 0 & max_normal_deviation <= 180",
38 "Maximum angle, in degrees, between the normals of the two elements merged together. "
39 "Merges exceeding it are skipped, which preserves features/corners");
41 "max_merged_side_length",
42 "max_merged_side_length > 0",
43 "Maximum length of the side created along the sideset by merging two elements. "
44 "Merges exceeding it are skipped");
46 "max_merged_element_area",
47 "max_merged_element_area > 0",
48 "Maximum area of an element created by merging two elements. Merges exceeding it are "
51 "coarsen_more_than_two_elements",
53 "Whether to coarsen iteratively in a single invocation so that more than two elements can "
54 "be merged together. The amount of coarsening is then bounded by the merge criteria");
58 "Whether to make the mesh generator output details of its actions on the console");
65 _input(getMesh(
"input")),
66 _boundaries(isParamValid(
"boundaries") ? getParam<
std::vector<BoundaryName>>(
"boundaries")
67 :
std::vector<BoundaryName>{}),
68 _exclude_boundaries(isParamValid(
"exclude_boundaries")
69 ? getParam<std::vector<BoundaryName>>(
"exclude_boundaries")
70 :
std::vector<BoundaryName>{}),
71 _has_max_normal_deviation(isParamValid(
"max_normal_deviation")),
72 _max_normal_deviation(_has_max_normal_deviation ? getParam<Real>(
"max_normal_deviation") : 0),
73 _has_max_side_length(isParamValid(
"max_merged_side_length")),
74 _max_merged_side_length(_has_max_side_length ? getParam<
Real>(
"max_merged_side_length") : 0),
75 _has_max_element_area(isParamValid(
"max_merged_element_area")),
76 _max_merged_element_area(_has_max_element_area ? getParam<
Real>(
"max_merged_element_area") : 0),
77 _coarsen_more_than_two_elements(getParam<bool>(
"coarsen_more_than_two_elements")),
78 _verbose(getParam<bool>(
"verbose"))
82 "Exactly one of 'boundaries' and 'exclude_boundaries' must be provided");
91newellNormal(
const std::vector<Point> & pts)
96 const Point & current = pts[i];
97 const Point & next = pts[(i + 1) % pts.size()];
98 n(0) += (current(1) - next(1)) * (current(2) + next(2));
99 n(1) += (current(2) - next(2)) * (current(0) + next(0));
100 n(2) += (current(0) - next(0)) * (current(1) + next(1));
106std::unique_ptr<MeshBase>
109 std::unique_ptr<MeshBase>
mesh = std::move(
_input);
111 if (!
mesh->is_serial())
112 paramError(
"input",
"Input mesh must not be distributed");
113 if (
mesh->mesh_dimension() != 2)
115 "Only meshes of 2D elements (TRI3/QUAD4) are supported, but the input mesh "
117 std::to_string(
mesh->mesh_dimension()));
119 if (!
mesh->is_prepared())
120 mesh->complete_preparation();
122 const auto & boundary_info =
mesh->get_boundary_info();
126 std::set<boundary_id_type> boundary_id_set;
130 boundary_id_set.insert(boundary_ids.begin(), boundary_ids.end());
134 boundary_id_set = boundary_info.get_side_boundary_ids();
136 for (
const auto id : exclude_ids)
137 boundary_id_set.erase(
id);
141 bool found_side =
false;
142 for (
const auto & t : boundary_info.build_side_list())
143 if (boundary_id_set.count(std::get<2>(t)))
149 paramError(
"boundaries",
"No sides were found for the requested sideset(s)");
153 const auto n_elem_before =
mesh->n_elem();
154 unsigned int collapsed = 0;
160 <<
" element(s) together along the sideset(s)." << std::endl;
162 return dynamic_pointer_cast<MeshBase>(
mesh);
167 std::unique_ptr<MeshBase> & mesh,
const std::set<boundary_id_type> & boundary_id_set)
169 const auto & boundary_info =
mesh->get_boundary_info();
173 std::map<dof_id_type, std::set<dof_id_type>> boundary_node_neighbors;
174 for (
const auto & [elem_id, side, bid] : boundary_info.build_side_list())
176 if (!boundary_id_set.count(bid))
178 const Elem * elem =
mesh->elem_ptr(elem_id);
179 const auto edge = elem->build_side_ptr(side);
180 const auto n0 = edge->node_id(0);
181 const auto n1 = edge->node_id(1);
182 boundary_node_neighbors[n0].insert(n1);
183 boundary_node_neighbors[n1].insert(n0);
188 std::map<dof_id_type, std::vector<dof_id_type>> node_to_elems;
189 for (
const auto & elem :
mesh->active_element_ptr_range())
190 for (
const auto & node : elem->node_ref_range())
191 node_to_elems[node.id()].push_back(elem->id());
196 std::set<dof_id_type> locked;
197 std::vector<Elem *> elems_to_delete;
198 unsigned int num_collapsed = 0;
200 for (
const auto & [node_id, neighbors] : boundary_node_neighbors)
203 if (neighbors.size() != 2 || locked.count(node_id))
206 const Node * b_node =
mesh->node_ptr(node_id);
209 for (
const auto target_id : neighbors)
211 if (locked.count(target_id))
214 const Point a_point = *
mesh->node_ptr(target_id);
218 dof_id_type other_id = DofObject::invalid_id;
219 for (
const auto n : neighbors)
235 std::vector<dof_id_type> ids;
236 std::vector<Point> orig_pts;
237 std::vector<Point> new_pts;
240 std::vector<ElemInfo> infos;
241 for (
const auto incident_id : node_to_elems[node_id])
243 Elem * elem =
mesh->elem_ptr(incident_id);
246 if (elem->type() != TRI3 && elem->type() != QUAD4)
253 ElemInfo info{elem,
false, {}, {}, {}};
254 for (
const auto & node : elem->node_ref_range())
256 info.ids.push_back(node.id());
257 info.orig_pts.push_back(node);
258 if (node.id() == node_id)
259 info.new_pts.push_back(a_point);
262 if (node.id() == target_id)
263 info.degenerate =
true;
264 info.new_pts.push_back(node);
272 if (elem->type() != TRI3)
281 const Point orig_n = newellNormal(info.orig_pts);
282 const Point new_n = newellNormal(info.new_pts);
295 infos.push_back(info);
303 for (
const auto & degen_only : infos)
305 if (!degen_only.degenerate)
307 dof_id_type apex_id = DofObject::invalid_id;
308 for (
const auto id : degen_only.ids)
309 if (
id != target_id &&
id != node_id)
311 for (
const auto & remaining : infos)
313 if (remaining.degenerate ||
314 std::find(remaining.ids.begin(), remaining.ids.end(), apex_id) ==
317 const Point normal_degenerate = newellNormal(degen_only.orig_pts);
318 const Point normal_remaining = newellNormal(remaining.orig_pts);
319 const Real denom = normal_degenerate.norm() * normal_remaining.norm();
320 if (denom > 0 && (normal_degenerate * normal_remaining) / denom < cos_threshold)
327 std::set<Elem *> degenerate_set;
328 for (
const auto & info : infos)
330 degenerate_set.insert(info.elem);
331 if (!valid || degenerate_set.empty())
336 for (
const auto incident_id : node_to_elems[node_id])
338 Elem * elem =
mesh->elem_ptr(incident_id);
339 if (!elem || degenerate_set.count(elem))
341 elem->set_node(elem->get_node_index(b_node),
mesh->node_ptr(target_id));
343 for (
auto elem : degenerate_set)
344 elems_to_delete.push_back(elem);
346 locked.insert(node_id);
347 for (
const auto neighbor : neighbors)
348 locked.insert(neighbor);
354 for (
auto elem : elems_to_delete)
355 mesh->delete_elem(elem);
358 _console <<
name() <<
": collapsed " << num_collapsed <<
" boundary node(s), deleted "
359 << elems_to_delete.size() <<
" element(s)." << std::endl;
362 mesh->unset_has_neighbor_ptrs();
363 mesh->unset_has_cached_elem_data();
364 mesh->unset_has_boundary_id_sets();
369 return num_collapsed;
registerMooseObject("MooseApp", CoarsenSurfaceMeshAlongSidesetGenerator)
MeshGenerator that coarsens a 2D-element (TRI3/QUAD4) surface mesh along a sideset by collapsing alte...
const bool _has_max_element_area
Whether a maximum merged element area is enforced.
static InputParameters validParams()
const bool _verbose
Whether the mesh generator should be verbose to the console.
std::unique_ptr< MeshBase > generate() override
Generate / modify the mesh.
const bool _coarsen_more_than_two_elements
Whether to repeat the coarsening pass so that more than two elements can be merged together.
const std::vector< BoundaryName > _exclude_boundaries
Sideset(s) to exclude when coarsening along all the sidesets of the mesh.
const std::vector< BoundaryName > _boundaries
Sideset(s) to coarsen the mesh along.
CoarsenSurfaceMeshAlongSidesetGenerator(const InputParameters ¶meters)
const bool _has_max_normal_deviation
Whether a maximum normal deviation between merged elements is enforced.
const Real _max_merged_element_area
Maximum area of an element created by merging two elements.
const bool _has_max_side_length
Whether a maximum merged side length is enforced.
const Real _max_normal_deviation
Maximum angle (degrees) between the normals of the two elements merged together.
unsigned int coarsenAlongSidesets(std::unique_ptr< MeshBase > &mesh, const std::set< boundary_id_type > &boundary_id_set)
Performs a single coarsening pass: collapse non-adjacent sideset nodes, merging pairs of elements.
std::unique_ptr< MeshBase > & _input
Input mesh to coarsen.
const Real _max_merged_side_length
Maximum length of the side created by merging two elements.
const ConsoleStream _console
An instance of helper class to write streams to the Console objects.
Class used for caching additional information for elements such as the volume and centroid.
MeshGenerators are objects that can modify or add to an existing mesh.
static InputParameters validParams()
const std::string & name() const
Get the name of the class.
void paramError(const std::string ¶m, Args... args) const
Emits an error prefixed with the file and line number of the given param (from the input file) along ...
std::vector< BoundaryID > getBoundaryIDs(const libMesh::MeshBase &mesh, const std::vector< BoundaryName > &boundary_name, bool generate_unknown, const std::set< BoundaryID > &mesh_boundary_ids)
Gets the boundary IDs with their names.
auto index_range(const T &sizable)
static constexpr Real TOLERANCE
DIE A HORRIBLE DEATH HERE typedef LIBMESH_DEFAULT_SCALAR_TYPE Real