https://mooseframework.inl.gov
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.");
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 
77 std::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]))
111  paramError(
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,
211  ANGLE_DEGREE);
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,
281  _num_layers,
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->subdomain_name(_transition_layer_id) = _transition_layer_name;
326  return input_mesh;
327 }
328 
329 void
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 }
const MeshGeneratorName _input_name
Name of the input mesh that needs the modification.
T & setMeshProperty(const std::string &data_name, Args &&... args)
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...
bool absoluteFuzzyEqual(const T &var1, const T2 &var2, const T3 &tol=libMesh::TOLERANCE *libMesh::TOLERANCE)
QUAD8
std::unique_ptr< ReplicatedMesh > buildReplicatedMesh(unsigned int dim=libMesh::invalid_uint)
void addParam(const std::string &name, const std::initializer_list< typename T::value_type > &value, const std::string &doc_string)
const subdomain_id_type _transition_layer_id
Block ID of the transition layer to be generated.
const BoundaryID INVALID_BOUNDARY_ID
boundary_id_type _input_mesh_external_bid
Boundary ID of the external boundary of the input mesh.
QuadratureType quad_type
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...
MeshBase & mesh
std::unique_ptr< T_DEST, T_DELETER > dynamic_pointer_cast(std::unique_ptr< T_SRC, T_DELETER > &src)
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.
PatternedPolygonPeripheralModifierBase(const InputParameters &parameters)
unsigned int _num_sides
Number of sides of the mesh to be generated.
void addRequiredParam(const std::string &name, const std::string &doc_string)
QUAD4
BoundaryID getBoundaryID(const BoundaryName &boundary_name, const MeshBase &mesh)
static InputParameters validParams()
const std::string _type
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)
SimpleRange< IndexType > as_range(const std::pair< IndexType, IndexType > &p)
void neighborSearch(const Point &query_point, unsigned int patch_size, std::vector< std::size_t > &return_index)
void paramError(const std::string &param, Args... args) const
auto norm(const T &a) -> decltype(std::abs(a))
const std::string _name
const unsigned int _new_num_sector
Target number of mesh sectors on each side of the square.
std::unique_ptr< MeshBase > & _mesh
The main mesh used in this class.
DIE A HORRIBLE DEATH HERE typedef LIBMESH_DEFAULT_SCALAR_TYPE Real
const SubdomainName _transition_layer_name
Block name of the transition layer to be generated.
A base class that contains common members for Reactor module mesh generators.
void addClassDescription(const std::string &doc_string)
QUAD9
static const std::complex< double > j(0, 1)
Complex number "j" (also known as "i")
void addRangeCheckedParam(const std::string &name, const T &value, const std::string &parsed_function, const std::string &doc_string)
const unsigned int _num_layers
Number of element layers of the transition layer to be generated.
void ErrorVector unsigned int
auto index_range(const T &sizable)
const BoundaryName _input_mesh_external_boundary
External boundary name of the input mesh.
uint8_t dof_id_type
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...