https://mooseframework.inl.gov
Loading...
Searching...
No Matches
PatternedPolygonPeripheralModifierBase.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
12#include "MooseMeshUtils.h"
14#include "KDTree.h"
15
18{
20 params.addRequiredParam<MeshGeneratorName>(
21 "input",
22 "The input mesh to be modified. Note that this generator only works with "
23 "PatternedHex/CartesianMeshGenerator and its derived classes such as "
24 "HexIDPatternedMeshGenerator.");
25 params.addRequiredParam<BoundaryName>("input_mesh_external_boundary",
26 "The external boundary of the input mesh.");
27 params.addRequiredParam<unsigned int>("new_num_sector",
28 "Number of sectors of each side for the new mesh.");
29 params.addParam<subdomain_id_type>(
30 "transition_layer_id",
32 "Optional customized block id for the transition layer block.");
33 params.addParam<SubdomainName>("transition_layer_name",
34 "transition_layer",
35 "Optional customized block name for the transition layer block.");
36 params.addRangeCheckedParam<unsigned int>(
37 "num_layers", 1, "num_layers>0", "Layers of elements for transition.");
38 params.addParam<std::vector<std::string>>(
39 "extra_id_names_to_modify",
40 "Names of the element extra ids in the peripheral region that should be modified");
41 params.addParam<std::vector<dof_id_type>>(
42 "new_extra_id_values_to_assign",
43 std::vector<dof_id_type>(),
44 "Values of the modified extra ids in the peripheral region.");
45 params.addClassDescription("PatternedPolygonPeripheralModifierBase is the base class for "
46 "PatternedCartPeripheralModifier and PatternedHexPeripheralModifier.");
47
48 return params;
49}
50
52 const InputParameters & parameters)
53 : PolygonMeshGeneratorBase(parameters),
54 _input_name(getParam<MeshGeneratorName>("input")),
55 _input_mesh_external_boundary(getParam<BoundaryName>("input_mesh_external_boundary")),
56 _new_num_sector(getParam<unsigned int>("new_num_sector")),
57 _transition_layer_id(getParam<subdomain_id_type>("transition_layer_id")),
58 _transition_layer_name(getParam<SubdomainName>("transition_layer_name")),
59 _num_layers(getParam<unsigned int>("num_layers")),
60 _extra_id_names_to_modify(isParamValid("extra_id_names_to_modify")
61 ? getParam<std::vector<std::string>>("extra_id_names_to_modify")
62 : std::vector<std::string>()),
63 _new_extra_id_values_to_assign(
64 getParam<std::vector<dof_id_type>>("new_extra_id_values_to_assign")),
65 // Use CartesianConcentricCircleAdaptiveBoundaryMeshGenerator for cartesian control drum meshing
66 // Use HexagonConcentricCircleAdaptiveBoundaryMeshGenerator for hexagonal control drum meshing
67 _mesh(getMeshByName(_input_name))
68{
69 declareMeshProperty<Real>("pattern_pitch_meta", 0.0);
70 declareMeshProperty<bool>("is_control_drum_meta", false);
72 paramError("new_extra_id_values_to_assign",
73 "This parameter must have the same size as extra_id_names_to_modify.");
74 declareMeshProperty<bool>("peripheral_modifier_compatible", false);
75}
76
77std::unique_ptr<MeshBase>
79{
80 // Transmit the Mesh Metadata
81 auto pattern_pitch_meta = setMeshProperty(
82 "pattern_pitch_meta", getMeshProperty<Real>("pattern_pitch_meta", _input_name));
83 // Load the input mesh as ReplicatedMesh
84 auto input_mesh = dynamic_pointer_cast<ReplicatedMesh>(_mesh);
85 // Load boundary information
89 paramError("input_mesh_external_boundary",
90 "External boundary does not exist in the input mesh");
91 std::vector<std::tuple<dof_id_type, unsigned short int, boundary_id_type>> side_list =
92 input_mesh->get_boundary_info().build_side_list();
93 input_mesh->get_boundary_info().build_node_list_from_side_list();
94 std::vector<std::tuple<dof_id_type, boundary_id_type>> node_list =
95 input_mesh->get_boundary_info().build_node_list();
96 // Find neighbors
97 if (!input_mesh->is_prepared())
98 input_mesh->find_neighbors();
99 // Load all the extra integer indexing information from the input mesh
100 const unsigned int n_extra_integer_input = input_mesh->n_elem_integers();
101 std::vector<std::string> extra_integer_names;
102 for (unsigned int i = 0; i < n_extra_integer_input; i++)
103 extra_integer_names.push_back(input_mesh->get_elem_integer_name(i));
104
105 // Check elem extra integers names and convert them into indices
106 std::vector<bool> extra_id_modify_flags = std::vector<bool>(n_extra_integer_input, false);
107 std::vector<dof_id_type> extra_modify_values = std::vector<dof_id_type>(n_extra_integer_input, 0);
108 for (unsigned int i = 0; i < _extra_id_names_to_modify.size(); i++)
109 {
110 if (!input_mesh->has_elem_integer(_extra_id_names_to_modify[i]))
112 "extra_id_names_to_modify",
113 "The parameter contains an extra element integer that does not exist in the input mesh.");
114 else
115 {
116 extra_id_modify_flags[input_mesh->get_elem_integer_index(_extra_id_names_to_modify[i])] =
117 true;
118 extra_modify_values[input_mesh->get_elem_integer_index(_extra_id_names_to_modify[i])] =
120 }
121 }
122
123 std::vector<dof_id_type> bid_elem_list;
124 std::vector<dof_id_type> new_bid_elem_list;
125 std::vector<dof_id_type> ext_node_list;
126 // We check the type of the QUAD elements on the boundary
127 unsigned short quad_type = 0;
128 for (unsigned int i = 0; i < side_list.size(); i++)
129 {
130 if (std::get<2>(side_list[i]) == _input_mesh_external_bid)
131 {
132 // List of elements on the boundary
133 bid_elem_list.push_back(std::get<0>(side_list[i]));
134 // Check if the element is QUAD4 or QUAD8/9
135 const auto elem_type = input_mesh->elem_ptr(std::get<0>(side_list[i]))->type();
136 if (elem_type != QUAD4 && elem_type != QUAD8 && elem_type != QUAD9)
137 paramError("input",
138 "The input mesh has non-QUAD4/QUAD8/QUAD9 elements in its peripheral area, "
139 "which is not supported.");
140 // Generally, we need to check if the sideset involves mixed order elements
141 // But this is not possible for a mesh generated by PatternedHex/CartesianMeshGenerator
142 // So we only need to check the first element
143 else if (quad_type == 0)
144 quad_type = elem_type == QUAD4 ? 1 : (elem_type == QUAD8 ? 8 : 9);
145
146 // Note this object only works with quad elements
147 // (side_id + 2)%4 gives the opposite side's neighboring element
148 // Thus, list of elements on the new boundary is made
149 new_bid_elem_list.push_back(input_mesh->elem_ptr(std::get<0>(side_list[i]))
150 ->neighbor_ptr((std::get<1>(side_list[i]) + 2) % 4)
151 ->id());
152 }
153 }
154 // This eliminates the duplicate elements, which happen at the vertices.
155 auto bid_elem_list_it = std::unique(bid_elem_list.begin(), bid_elem_list.end());
156 auto new_bid_elem_list_it = std::unique(new_bid_elem_list.begin(), new_bid_elem_list.end());
157 bid_elem_list.resize(std::distance(bid_elem_list.begin(), bid_elem_list_it));
158 new_bid_elem_list.resize(std::distance(new_bid_elem_list.begin(), new_bid_elem_list_it));
159 // Create a data structure to record centroid positions and extra element IDS of the
160 // elements to be deleted
161 std::vector<std::pair<Point, std::vector<dof_id_type>>> del_elem_extra_ids;
162 // Delete the outer layer QUAD elements so that they can be replaced by TRI elements later.
163 for (unsigned int i = 0; i < bid_elem_list.size(); i++)
164 {
165 const auto & tmp_elem_ptr = input_mesh->elem_ptr(bid_elem_list[i]);
166 // Retain all the extra integer information first if any of them need to be retained
167 if (n_extra_integer_input > _extra_id_names_to_modify.size())
168 {
169 del_elem_extra_ids.push_back(
170 std::make_pair(tmp_elem_ptr->true_centroid(), std::vector<dof_id_type>()));
171 for (unsigned int j = 0; j < n_extra_integer_input; j++)
172 del_elem_extra_ids[i].second.push_back(
173 extra_id_modify_flags[j] ? extra_modify_values[j] : tmp_elem_ptr->get_extra_integer(j));
174 }
175 input_mesh->delete_elem(tmp_elem_ptr);
176 }
177 // if all the extra id will need to be re-assigned, just a dummy reference vector is needed.
178 if (n_extra_integer_input == _extra_id_names_to_modify.size())
179 del_elem_extra_ids.push_back(
180 std::make_pair(Point(0.0, 0.0, 0.0), _new_extra_id_values_to_assign));
181 // As some elements are deleted, update the neighbor list
182 input_mesh->find_neighbors();
183 // Identify the new external boundary
184 BoundaryInfo & boundary_info = input_mesh->get_boundary_info();
185 for (unsigned int i = 0; i < new_bid_elem_list.size(); i++)
186 {
187 // Assign default external Sideset ID to the new boundary
188 for (unsigned int j = 0; j < input_mesh->elem_ptr(new_bid_elem_list[i])->n_sides(); j++)
189 {
190 if (input_mesh->elem_ptr(new_bid_elem_list[i])->neighbor_ptr(j) == nullptr)
191 boundary_info.add_side(new_bid_elem_list[i], j, OUTER_SIDESET_ID);
192 }
193 }
194
195 // Refresh the mesh as it lost some elements
196 input_mesh->contract();
197 input_mesh->prepare_for_use();
198
199 // Clone the original mesh without outer layer for information extraction
200 auto input_mesh_origin = dynamic_pointer_cast<ReplicatedMesh>(input_mesh->clone());
201
202 // Make four (cartesian) or six (hexagonal) meshes of new outer layer for four or six sides.
203 for (unsigned i_side = 0; i_side < _num_sides; i_side++)
204 {
205 // Use azimuthalAnglesCollector to assign boundary Points to a reference vector
206 std::vector<Point> inner_pts;
207 std::vector<Real> new_bdry_azi = azimuthalAnglesCollector(*input_mesh_origin,
208 inner_pts,
209 -180.0 / (Real)_num_sides,
210 180.0 / (Real)_num_sides,
212 MeshTools::Modification::rotate(*input_mesh_origin, -360.0 / (Real)_num_sides, 0, 0);
213
214 // Setting up of the outer boundary is done by defining the two ends
215 // And then use libMesh interpolation tool to make the straight line
216 std::vector<Point> outer_pts;
217 const Point outer_end_1 = Point(pattern_pitch_meta / 2.0,
218 -pattern_pitch_meta / 2.0 * std::tan(M_PI / (Real)_num_sides),
219 0.0);
220 const Point outer_end_2 = Point(pattern_pitch_meta / 2.0,
221 pattern_pitch_meta / 2.0 * std::tan(M_PI / (Real)_num_sides),
222 0.0);
223
224 const std::vector<Real> input_arg{0.0, 1.0};
225 const std::vector<Real> input_x{outer_end_1(0), outer_end_2(0)};
226 const std::vector<Real> input_y{outer_end_1(1), outer_end_2(1)};
227 std::unique_ptr<LinearInterpolation> linear_outer_x =
228 std::make_unique<LinearInterpolation>(input_arg, input_x);
229 std::unique_ptr<LinearInterpolation> linear_outer_y =
230 std::make_unique<LinearInterpolation>(input_arg, input_y);
231
232 // Uniformly spaced nodes on the outer boundary to facilitate stitching
233 for (unsigned int i = 0; i < _new_num_sector + 1; i++)
234 {
235 outer_pts.push_back(Point(linear_outer_x->sample((Real)i / (Real)_new_num_sector),
236 linear_outer_y->sample((Real)i / (Real)_new_num_sector),
237 0.0));
238 }
239 // Now we have the inner and outer boundary points as input for the transition layer
240 // For linear elements, that's all we need;
241 // For quadratic elements, the inner point vector contains both vertices and midpoints.
242 // fillBetweenPointVectorsGenerator only supports linear elements
243 // So we need to
244 // (1) remove the midpoints from the inner point vector
245 // (2) generate linear mesh using fillBetweenPointVectorsGenerator
246 // (3) convert the linear mesh to quadratic mesh
247 // Generally, in the input mesh, the midpoint might not be located in the middle of the two
248 // vertices. (4) In that case, the midpoint needs to be moved to match the input mesh In the
249 // case of PatternedHex/CartesianMeshGenerator, the midpoints of the inner boundary shoule be
250 // the average of the vertices unless the non-circular regions are deformed. So this step still
251 // needs to be done.
252
253 // Use fillBetweenPointVectorsGenerator to generate the transition layer
254
255 // Remove the midpoints from the inner point vector
256 // But record the midpoints for later use
257 // In the meantime, calculate the average of the two vertices
258 std::vector<std::pair<Point, Point>> inner_midpts_pairs;
259 if (quad_type > 4)
260 {
261 auto inner_pts_tmp(inner_pts);
262 inner_pts.clear();
263 for (const auto i : index_range(inner_pts_tmp))
264 if (i % 2 == 0)
265 inner_pts.push_back(inner_pts_tmp[i]);
266 else
267 {
268 // We check if the midpoint is located in the middle of the two vertices
269 // If not, we record the them for later use
270 const Point avgpt = 0.5 * (inner_pts_tmp[i - 1] + inner_pts_tmp[i + 1]);
271 if (!MooseUtils::absoluteFuzzyEqual((avgpt - inner_pts_tmp[i]).norm(), 0.0))
272 inner_midpts_pairs.push_back(std::make_pair(avgpt, inner_pts_tmp[i]));
273 }
274 inner_pts_tmp.clear();
275 }
276
277 auto mesh = buildReplicatedMesh(2);
279 inner_pts,
280 outer_pts,
284 _type,
285 _name);
286
287 // Convert the linear mesh to quadratic mesh
288 if (quad_type == 8)
289 mesh->all_second_order();
290 else if (quad_type == 9)
291 mesh->all_complete_order();
292
293 // Move the midpoints to match the input mesh if applicable
294 if (inner_midpts_pairs.size())
295 {
296 mesh->get_boundary_info().build_node_list_from_side_list();
297 auto tl_node_list = mesh->get_boundary_info().build_node_list();
298 std::vector<dof_id_type> bdry_node_ids;
299 std::vector<Point> bdry_node_pts;
300 for (const auto & tl_node_tuple : tl_node_list)
301 {
302 if (std::get<1>(tl_node_tuple) == OUTER_SIDESET_ID)
303 {
304 bdry_node_ids.push_back(std::get<0>(tl_node_tuple));
305 bdry_node_pts.push_back(*(mesh->node_ptr(std::get<0>(tl_node_tuple))));
306 }
307 }
308 KDTree ref_kd_tree(bdry_node_pts, 4);
309 for (const auto & midpt_pair : inner_midpts_pairs)
310 {
311 std::vector<std::size_t> nn_id;
312 ref_kd_tree.neighborSearch(midpt_pair.first, 1, nn_id);
313 *(mesh->node_ptr(bdry_node_ids[nn_id.front()])) = midpt_pair.second;
314 }
315 }
316
317 MeshTools::Modification::rotate(*mesh, (Real)i_side * 360.0 / (Real)_num_sides, 0, 0);
318 // Assign extra element id based on the nearest deleted element
319 mesh->add_elem_integers(extra_integer_names, true);
320 transferExtraElemIntegers(*mesh, del_elem_extra_ids);
321 mesh->prepare_for_use();
322 input_mesh->stitch_meshes(*mesh, OUTER_SIDESET_ID, OUTER_SIDESET_ID, TOLERANCE, true);
323 mesh->clear();
324 }
325 input_mesh->set_subdomain_name(_transition_layer_id, _transition_layer_name);
326 return input_mesh;
327}
328
329void
331 ReplicatedMesh & mesh,
332 const std::vector<std::pair<Point, std::vector<dof_id_type>>> ref_extra_ids)
333{
334 // Build points vector for k-d tree constructor
335 std::vector<Point> ref_pts;
336 for (auto & pt_extra_id : ref_extra_ids)
337 ref_pts.push_back(pt_extra_id.first);
338 // K-d tree construction
339 KDTree ref_kd_tree(ref_pts, 4);
340 // Use the k-d tree for nearest neighbor searching
341 for (const auto & elem : as_range(mesh.active_elements_begin(), mesh.active_elements_end()))
342 {
343 std::vector<std::size_t> nn_id;
344 ref_kd_tree.neighborSearch(elem->true_centroid(), 1, nn_id);
345 for (unsigned int i = 0; i < ref_extra_ids[nn_id.front()].second.size(); i++)
346 elem->set_extra_integer(i, ref_extra_ids[nn_id.front()].second[i]);
347 }
348}
void ErrorVector unsigned int
void addRequiredParam(const std::string &name, const std::string &doc_string)
void addParam(const std::string &name, const std::initializer_list< typename T::value_type > &value, const std::string &doc_string)
void addClassDescription(const std::string &doc_string)
void addRangeCheckedParam(const std::string &name, const T &value, const std::string &parsed_function, const std::string &doc_string)
void neighborSearch(const Point &query_point, unsigned int patch_size, std::vector< std::size_t > &return_index)
T & setMeshProperty(const std::string &data_name, Args &&... args)
std::unique_ptr< ReplicatedMesh > buildReplicatedMesh(unsigned int dim=libMesh::invalid_uint)
void paramError(const std::string &param, Args... args) const
const std::string & _type
const std::string & _name
std::unique_ptr< MeshBase > & _mesh
The main mesh used in this class.
const SubdomainName _transition_layer_name
Block name of the transition layer to be generated.
const unsigned int _new_num_sector
Target number of mesh sectors on each side of the square.
void transferExtraElemIntegers(ReplicatedMesh &mesh, const std::vector< std::pair< Point, std::vector< dof_id_type > > > ref_extra_ids)
Assign extra element integers to the newly generated transition layer mesh based on the nearest eleme...
const std::vector< std::string > _extra_id_names_to_modify
Names of extra element integers in the input mesh that need to be retained or reassigned in the trans...
const BoundaryName _input_mesh_external_boundary
External boundary name of the input mesh.
const unsigned int _num_layers
Number of element layers of the transition layer to be generated.
const MeshGeneratorName _input_name
Name of the input mesh that needs the modification.
const subdomain_id_type _transition_layer_id
Block ID of the transition layer to be generated.
PatternedPolygonPeripheralModifierBase(const InputParameters &parameters)
const std::vector< dof_id_type > _new_extra_id_values_to_assign
Customized values to be used to reassign the extra element integers in the transition layer.
boundary_id_type _input_mesh_external_bid
Boundary ID of the external boundary of the input mesh.
unsigned int _num_sides
Number of sides of the mesh to be generated.
A base class that contains common members for Reactor module mesh generators.
std::vector< Real > azimuthalAnglesCollector(ReplicatedMesh &mesh, std::vector< Point > &boundary_points, const Real lower_azi=-30.0, const Real upper_azi=30.0, const unsigned int return_type=ANGLE_TANGENT, const unsigned int num_sides=6, const boundary_id_type bid=OUTER_SIDESET_ID, const bool calculate_origin=true, const Real input_origin_x=0.0, const Real input_origin_y=0.0, const Real tol=1.0E-10) const
Collects sorted azimuthal angles of the external boundary.
static InputParameters validParams()
QuadratureType quad_type
MeshBase & mesh
void fillBetweenPointVectorsGenerator(MeshBase &mesh, const std::vector< Point > &boundary_points_vec_1, const std::vector< Point > &boundary_points_vec_2, const unsigned int num_layers, const subdomain_id_type transition_layer_id, const boundary_id_type input_boundary_1_id, const boundary_id_type input_boundary_2_id, const boundary_id_type begin_side_boundary_id, const boundary_id_type end_side_boundary_id, const std::string type, const std::string name, const bool quad_elem=false, const Real bias_parameter=1.0, const Real sigma=3.0)
BoundaryID getBoundaryID(const BoundaryName &boundary_name, const MeshBase &mesh)
const BoundaryID INVALID_BOUNDARY_ID