https://mooseframework.inl.gov
MeshGeneratorPD.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 
10 #include "MeshGeneratorPD.h"
11 #include "PeridynamicsMesh.h"
12 #include "CastUniquePointer.h"
13 
14 #include "libmesh/edge_edge2.h"
15 #include "libmesh/face_tri3.h"
16 #include "libmesh/cell_tet4.h"
17 
18 registerMooseObject("PeridynamicsApp", MeshGeneratorPD);
19 
22 {
24  params.addClassDescription("Mesh generator class to convert FE mesh to PD mesh");
25 
26  params.addRequiredParam<MeshGeneratorName>("input",
27  "The mesh based on which PD mesh will be created");
28  params.addParam<std::vector<SubdomainID>>("blocks_to_pd",
29  "IDs of the FE mesh blocks to be converted to PD mesh");
30  params.addParam<std::vector<SubdomainID>>(
31  "blocks_as_fe",
32  "IDs of the FE mesh blocks to not be converted to PD mesh. This should only be used when the "
33  "number of to-be-converted FE blocks is considerably large.");
34  params.addRequiredParam<bool>(
35  "retain_fe_mesh", "Whether to retain the FE mesh or not after conversion into PD mesh");
36  params.addParam<bool>(
37  "construct_pd_sidesets",
38  false,
39  "Whether to construct PD sidesets based on the sidesets in original FE mesh");
40  params.addParam<std::vector<boundary_id_type>>(
41  "sidesets_to_pd", "IDs of the FE sidesets to be reconstructed based on converted PD mesh");
42  params.addParam<std::vector<std::vector<SubdomainID>>>(
43  "bonding_block_pairs",
44  "List of FE block pairs between which inter-block bonds will be created after being "
45  "converted into PD mesh");
46  params.addParam<std::vector<std::vector<SubdomainID>>>(
47  "non_bonding_block_pairs",
48  "List of FE block pairs between which inter-block bonds will NOT be created after being "
49  "converted into PD mesh");
50  params.addParam<bool>(
51  "merge_pd_blocks",
52  false,
53  "Whether to merge all converted PD mesh blocks into a single block. This is "
54  "used when all PD blocks have the same properties");
55  params.addParam<bool>(
56  "merge_pd_interfacial_blocks",
57  false,
58  "Whether to merge all PD interfacial mesh blocks into a single block. This is used "
59  "when all PD interfacial blocks have the same properties");
60 
61  return params;
62 }
63 
65  : MeshGenerator(parameters),
66  _input(getMesh("input")),
67  _has_blks_to_pd(isParamValid("blocks_to_pd")),
68  _has_blks_as_fe(isParamValid("blocks_as_fe")),
69  _retain_fe_mesh(getParam<bool>("retain_fe_mesh")),
70  _merge_pd_blks(getParam<bool>("merge_pd_blocks")),
71  _construct_pd_sideset(getParam<bool>("construct_pd_sidesets")),
72  _has_sidesets_to_pd(isParamValid("sidesets_to_pd")),
73  _has_bonding_blk_pairs(isParamValid("bonding_block_pairs")),
74  _has_non_bonding_blk_pairs(isParamValid("non_bonding_block_pairs")),
75  _merge_pd_interfacial_blks(getParam<bool>("merge_pd_interfacial_blocks"))
76 {
78  mooseError("Please specifiy either 'blocks_to_pd' or 'blocks_as_fe'!");
79 
80  if (_has_blks_as_fe)
81  {
82  std::vector<SubdomainID> ids = getParam<std::vector<SubdomainID>>("blocks_as_fe");
83  for (unsigned int i = 0; i < ids.size(); ++i)
84  _blks_as_fe.insert(ids[i]);
85  }
86 
87  if (_has_blks_to_pd)
88  {
89  std::vector<SubdomainID> ids = getParam<std::vector<SubdomainID>>("blocks_to_pd");
90  for (unsigned int i = 0; i < ids.size(); ++i)
91  _blks_to_pd.insert(ids[i]);
92  }
93 
95  mooseError("'sidesets_to_pd' is provided without setting "
96  "'construct_pd_sidesets' to 'true'!");
97 
99  {
100  std::vector<boundary_id_type> ids = getParam<std::vector<boundary_id_type>>("sidesets_to_pd");
101  for (unsigned int i = 0; i < ids.size(); ++i)
102  _fe_sidesets_for_pd_construction.insert(ids[i]);
103  }
104 
106  mooseError("Please specifiy either 'bonding_block_pairs' or "
107  "'non_bonding_block_pairs'!");
108 
109  _pd_bonding_blk_pairs.clear();
111  {
112  std::vector<std::vector<SubdomainID>> id_pairs =
113  getParam<std::vector<std::vector<SubdomainID>>>("bonding_block_pairs");
114 
115  for (unsigned int i = 0; i < id_pairs.size(); ++i)
116  _pd_bonding_blk_pairs.insert(std::make_pair(id_pairs[i][0] + _pd_blk_offset_number,
117  id_pairs[i][1] + _pd_blk_offset_number));
118  } // considered the renumbering of IDs of converted blocks
119 
122  {
123  std::vector<std::vector<SubdomainID>> id_pairs =
124  getParam<std::vector<std::vector<SubdomainID>>>("non_bonding_block_pairs");
125 
126  for (unsigned int i = 0; i < id_pairs.size(); ++i)
127  _pd_non_bonding_blk_pairs.insert(std::make_pair(id_pairs[i][0] + _pd_blk_offset_number,
128  id_pairs[i][1] + _pd_blk_offset_number));
129  } // considered the renumbering of IDs of converted blocks
130 }
131 
132 std::unique_ptr<MeshBase>
134 {
135  // get the MeshBase object this generator will be working on
136  std::unique_ptr<MeshBase> old_mesh = std::move(_input);
137 
138  // STEP 1: obtain FE block(s) and elements to be converted to PD mesh
139 
140  // get the IDs of all available blocks in the input FE mesh
141  std::set<SubdomainID> all_fe_blks;
142  for (const auto & old_elem : old_mesh->element_ptr_range())
143  all_fe_blks.insert(old_elem->subdomain_id());
144 
145  // double check the existence of the block ids provided by user
146  if (_has_blks_to_pd)
147  for (const auto & blkit : _blks_to_pd)
148  if (!all_fe_blks.count(blkit))
149  mooseError("Block ID ", blkit, " in the 'blocks_to_pd' does not exist in the FE mesh!");
150 
151  if (_has_blks_as_fe)
152  for (const auto & blkit : _blks_as_fe)
153  if (!all_fe_blks.count(blkit))
154  mooseError("Block ID ", blkit, " in the 'blocks_as_fe' does not exist in the FE mesh!");
155 
156  // the maximum FE block ID, which will be used in determine the block ID for interfacial bond in
157  // the case of single interface block
158  const unsigned int max_fe_blk_id = *all_fe_blks.rbegin();
159 
160  // categorize mesh blocks into converted and non-converted blocks
161  if (_has_blks_to_pd)
162  std::set_difference(all_fe_blks.begin(),
163  all_fe_blks.end(),
164  _blks_to_pd.begin(),
165  _blks_to_pd.end(),
166  std::inserter(_blks_as_fe, _blks_as_fe.begin()));
167  else if (_has_blks_as_fe)
168  std::set_difference(all_fe_blks.begin(),
169  all_fe_blks.end(),
170  _blks_as_fe.begin(),
171  _blks_as_fe.end(),
172  std::inserter(_blks_to_pd, _blks_to_pd.begin()));
173  else // if no block ids provided by user, by default, convert all FE mesh to PD mesh
174  _blks_to_pd = all_fe_blks;
175 
177  {
178  for (const auto & blk_pair : _pd_bonding_blk_pairs)
179  {
180  if (!all_fe_blks.count(blk_pair.first - _pd_blk_offset_number))
181  mooseError("Block ID ",
182  blk_pair.first - _pd_blk_offset_number,
183  " in the 'bonding_block_pairs' does not exist in the FE mesh!");
184  if (!all_fe_blks.count(blk_pair.second - _pd_blk_offset_number))
185  mooseError("Block ID ",
186  blk_pair.second - _pd_blk_offset_number,
187  " in the 'bonding_block_pairs' does not exist in the FE mesh!");
188  if (!_blks_to_pd.count(blk_pair.first - _pd_blk_offset_number))
189  mooseError("Block ID ",
190  blk_pair.first - _pd_blk_offset_number,
191  " in the 'bonding_block_pairs' is a FE mesh block!");
192  if (!_blks_to_pd.count(blk_pair.second - _pd_blk_offset_number))
193  mooseError("Block ID ",
194  blk_pair.second - _pd_blk_offset_number,
195  " in the 'bonding_block_pairs' is a FE mesh block!");
196  }
197  }
198 
200  for (const auto & blk_pair : _pd_non_bonding_blk_pairs)
201  {
202  if (!all_fe_blks.count(blk_pair.first - _pd_blk_offset_number))
203  mooseError("Block ID ",
204  blk_pair.first - _pd_blk_offset_number,
205  " in the 'non_bonding_block_pairs' does not exist in the FE mesh!");
206  if (!all_fe_blks.count(blk_pair.second - _pd_blk_offset_number))
207  mooseError("Block ID ",
208  blk_pair.second - _pd_blk_offset_number,
209  " in the 'non_bonding_block_pairs' does not exist in the FE mesh!");
210  if (!_blks_as_fe.count(blk_pair.first - _pd_blk_offset_number))
211  mooseError("Block ID ",
212  blk_pair.first - _pd_blk_offset_number,
213  " in the 'non_bonding_block_pairs' is a FE mesh block!");
214  if (!_blks_as_fe.count(blk_pair.second - _pd_blk_offset_number))
215  mooseError("Block ID ",
216  blk_pair.second - _pd_blk_offset_number,
217  " in the 'non_bonding_block_pairs' is a FE mesh block!");
218  }
219 
220  // the minimum converted FE block ID, which will be used to assign block ID for non-interfacial
221  // bond in the case of combine converted blocks
222  const unsigned int min_converted_fe_blk_id = *_blks_to_pd.begin();
223 
224  // IDs of to-be-converted FE elems
225  std::set<dof_id_type> elems_to_pd;
226  // retained FE mesh and non-converted FE mesh, if any
227  std::set<dof_id_type> fe_nodes;
228  std::set<dof_id_type> fe_elems;
229  for (const auto & old_elem : old_mesh->element_ptr_range())
230  if (_blks_to_pd.count(old_elem->subdomain_id())) // record to-be-converted FE elem IDs
231  {
232  elems_to_pd.insert(old_elem->id());
233  if (_retain_fe_mesh) // save converted elems and their nodes if need to be retained
234  {
235  fe_elems.insert(old_elem->id());
236  for (unsigned int i = 0; i < old_elem->n_nodes(); ++i)
237  fe_nodes.insert(old_elem->node_id(i));
238  }
239  }
240  else // save non-converted elements and their nodes
241  {
242  fe_elems.insert(old_elem->id());
243  for (unsigned int i = 0; i < old_elem->n_nodes(); ++i)
244  fe_nodes.insert(old_elem->node_id(i));
245  }
246 
247  // number of FE elements and nodes in the old mesh to be saved in the new mesh
248  dof_id_type n_fe_nodes = fe_nodes.size();
249  dof_id_type n_fe_elems = fe_elems.size();
250  dof_id_type n_phantom_elems = 0;
251 
252  // determine the number of phantom elements to be generated in the new mesh based on sideset in
253  // old mesh
254  BoundaryInfo & old_boundary_info = old_mesh->get_boundary_info();
255  const std::set<boundary_id_type> & all_fe_sidesets = old_boundary_info.get_side_boundary_ids();
256 
257  // double check the existence of the sideset ids provided by user
259  {
260  for (const auto & sideset : _fe_sidesets_for_pd_construction)
261  if (!all_fe_sidesets.count(sideset))
262  mooseError("Sideset ID ",
263  sideset,
264  " in the 'sidesets_to_pd' does not exist in the finite "
265  "element mesh!");
266  }
267  else // save the IDs of all FE sidesets, this will be updated to the converted FE block later
268  _fe_sidesets_for_pd_construction = all_fe_sidesets;
269 
270  // determine number of FE side elements, the number of actual phantom elements is less than or
271  // equal to the number of FE side elements, this number is used to reserve number of elements
272  // in the new mesh only
273  std::map<boundary_id_type, std::set<dof_id_type>> fe_bid_eid;
274  auto fe_sbc_tuples = old_boundary_info.build_side_list();
275  // 0: element ID, 1: side ID, 2: boundary ID
276  for (const auto & sbct : fe_sbc_tuples)
278  std::get<2>(sbct))) // save elements of to-be-constructed sidesets only
279  {
280  fe_bid_eid[std::get<2>(sbct)].insert(std::get<0>(sbct));
281  ++n_phantom_elems;
282  }
283 
284  // STEP 2: generate PD data based on to-be-converted FE mesh and prepare for new mesh
285 
286  PeridynamicsMesh & pd_mesh = dynamic_cast<PeridynamicsMesh &>(*_mesh);
287  // generate PD node data, including neighbors
289  *old_mesh, elems_to_pd, _pd_bonding_blk_pairs, _pd_non_bonding_blk_pairs);
290 
291  // number of PD elements and nodes to be created
292  dof_id_type n_pd_nodes = pd_mesh.nPDNodes();
293  dof_id_type n_pd_bonds = pd_mesh.nPDBonds();
294 
295  // initialize an empty new mesh object
296  std::unique_ptr<MeshBase> new_mesh = buildMeshBaseObject();
297  new_mesh->clear();
298  // set new mesh dimension
299  new_mesh->set_mesh_dimension(old_mesh->mesh_dimension());
300  new_mesh->set_spatial_dimension(old_mesh->spatial_dimension());
301  // reserve elements and nodes for the new mesh
302  new_mesh->reserve_nodes(n_pd_nodes + n_fe_nodes);
303  new_mesh->reserve_elem(n_pd_bonds + n_fe_elems + n_phantom_elems);
304 
305  BoundaryInfo & new_boundary_info = new_mesh->get_boundary_info();
306 
307  // STEP 3: add points of PD and FE (retained and/or non-converted) nodes, if any, to new mesh
308 
309  // save PD nodes to new mesh first
310  unsigned int new_node_id = 0;
311  // map of IDs of converted FE elements and PD nodes
312  std::map<dof_id_type, dof_id_type> fe_elem_pd_node_map;
313  for (const auto & eid : elems_to_pd)
314  {
315  new_mesh->add_point(old_mesh->elem_ptr(eid)->vertex_average(), new_node_id);
316  fe_elem_pd_node_map.insert(std::make_pair(eid, new_node_id));
317 
318  ++new_node_id;
319  }
320  // then save both retained and non-converted FE nodes, if any, to the new mesh
321  // map of IDs of the same point in old and new meshes
322  std::map<dof_id_type, dof_id_type> fe_nodes_map;
323  for (const auto & nid : fe_nodes)
324  {
325  new_mesh->add_point(*old_mesh->node_ptr(nid), new_node_id);
326  fe_nodes_map.insert(std::make_pair(old_mesh->node_ptr(nid)->id(), new_node_id));
327 
328  ++new_node_id;
329  }
330 
331  // STEP 4: generate PD, phantom, and FE elems using retained and/or non-converted meshes if any
332 
333  // first, generate PD elements for new mesh
334  unsigned int new_elem_id = 0;
335  for (unsigned int i = 0; i < n_pd_nodes; ++i)
336  {
337  std::vector<dof_id_type> pd_node_neighbors = pd_mesh.getNeighbors(i); // neighbors of a PD node
338  for (unsigned int j = 0; j < pd_node_neighbors.size(); ++j)
339  if (pd_node_neighbors[j] > i)
340  {
341  SubdomainID bid_i = pd_mesh.getNodeBlockID(i);
342  SubdomainID bid_j = pd_mesh.getNodeBlockID(pd_node_neighbors[j]);
343  Elem * new_elem = new Edge2;
344  new_elem->set_id(new_elem_id);
345  if (bid_i == bid_j) // assign block ID to PD non-inter-block bonds
346  if (_merge_pd_blks)
347  new_elem->subdomain_id() = min_converted_fe_blk_id + _pd_blk_offset_number;
348  else
349  new_elem->subdomain_id() = bid_i;
350  else if (_merge_pd_interfacial_blks) // assign block ID (max_fe_blk_id + 1 +
351  // _pd_blk_offset_number) to all PD inter-block bonds
352  new_elem->subdomain_id() = max_fe_blk_id + 1 + _pd_blk_offset_number;
353  else // assign a new block ID (node i blk ID + node j blk ID) to this PD inter-block bonds
354  new_elem->subdomain_id() = bid_i + bid_j;
355 
356  new_elem = new_mesh->add_elem(new_elem);
357  new_elem->set_node(0, new_mesh->node_ptr(i));
358  new_elem->set_node(1, new_mesh->node_ptr(pd_node_neighbors[j]));
359 
360  ++new_elem_id;
361  }
362  }
363 
364  if (_merge_pd_blks) // update PD node block ID if use single block ID for all PD blocks
365  pd_mesh.setNodeBlockID(min_converted_fe_blk_id + _pd_blk_offset_number);
366 
367  // then generate phantom elements for sidesets in PD mesh, this is optional
368  std::map<std::pair<dof_id_type, dof_id_type>, std::set<dof_id_type>> elem_edge_nodes;
370  {
371  for (const auto & bidit : _fe_sidesets_for_pd_construction)
372  {
373  for (const auto & eidit : fe_bid_eid[bidit])
374  {
375  bool should_add = false;
376  Elem * old_elem = old_mesh->elem_ptr(eidit);
377  if (_blks_to_pd.count(
378  old_elem->subdomain_id())) // limit the sidesets to the converted FE blocks only
379  {
380  std::vector<dof_id_type> pd_nodes;
381  Point p0, p1, p2, p3;
382 
383  if (old_elem->dim() == 2) // 2D: construct 3-node triangular phanton elems
384  {
385  pd_nodes.resize(3);
386  // the current elem on the boundary side is the first node of a phantom element
387  pd_nodes[0] = fe_elem_pd_node_map.at(eidit);
388  p0 = *new_mesh->node_ptr(pd_nodes[0]);
389 
390  // search for two more nodes to construct a phantom 3-node triangular element
391  if (old_elem->n_nodes() == 3 ||
392  old_elem->n_nodes() == 6) // original triangular FE elements: 3-node or 6-node
393  {
394  // one special case of triangular mesh: two elems share a boundary node
395  bool has_neighbor_on_boundary = false;
396  for (unsigned int i = 0; i < old_elem->n_neighbors(); ++i)
397  {
398  Elem * nb = old_elem->neighbor_ptr(i);
399  if (nb != nullptr)
400  {
401  if (fe_bid_eid[bidit].count(nb->id()))
402  {
403  has_neighbor_on_boundary = true;
404  pd_nodes[1] = fe_elem_pd_node_map.at(nb->id());
405  p1 = *new_mesh->node_ptr(pd_nodes[1]);
406  }
407  else
408  {
409  pd_nodes[2] = fe_elem_pd_node_map.at(nb->id());
410  p2 = *new_mesh->node_ptr(pd_nodes[2]);
411  }
412  }
413  }
414 
415  if (has_neighbor_on_boundary &&
416  (p1(1) - p0(1)) * (p2(0) - p1(0)) - (p1(0) - p0(0)) * (p2(1) - p1(1)) < 0)
417  {
418  should_add = true;
419  }
420  else // common case of triangular mesh: three elems share a boundary node
421  {
422  for (unsigned int i = 0; i < old_elem->n_neighbors(); ++i)
423  {
424  Elem * nb = old_elem->neighbor_ptr(i);
425 
426  if (nb != nullptr)
427  {
428  p2 = *new_mesh->node_ptr(fe_elem_pd_node_map.at(nb->id()));
429 
430  for (unsigned int j = 0; j < nb->n_neighbors(); ++j)
431  {
432  Elem * nb_nb = nb->neighbor_ptr(j);
433 
434  if (nb_nb != nullptr && fe_bid_eid[bidit].count(nb_nb->id()) &&
435  nb_nb->id() != eidit)
436  {
437  p1 = *new_mesh->node_ptr(fe_elem_pd_node_map.at(nb_nb->id()));
438 
439  // add new phantom elem only if (p0, p1, p2) is counterclockwisely ordered
440  if ((p1(1) - p0(1)) * (p2(0) - p1(0)) - (p1(0) - p0(0)) * (p2(1) - p1(1)) <
441  0)
442  {
443  should_add = true;
444  pd_nodes[1] = fe_elem_pd_node_map.at(nb_nb->id());
445  pd_nodes[2] = fe_elem_pd_node_map.at(nb->id());
446  }
447  }
448  }
449 
450  if (!should_add) // if using the neighbor's neighbor is not a success
451  {
452  // one special case of triangular mesh: four elems share a boundary node
453  // other cases of more than four elems share a boundary node is not considered
454  for (unsigned int i = 0; i < old_elem->n_neighbors(); ++i)
455  {
456  Elem * nb = old_elem->neighbor_ptr(i);
457 
458  if (nb != nullptr)
459  {
460  p2 = *new_mesh->node_ptr(fe_elem_pd_node_map.at(nb->id()));
461 
462  for (unsigned int j = 0; j < nb->n_neighbors(); ++j)
463  {
464  Elem * nb_nb = nb->neighbor_ptr(j);
465  if (nb_nb != nullptr && nb_nb->id() != eidit)
466  {
467  for (unsigned int k = 0; k < nb_nb->n_neighbors(); ++k)
468  {
469  Elem * nb_nb_nb = nb_nb->neighbor_ptr(k);
470 
471  if (nb_nb_nb != nullptr &&
472  fe_bid_eid[bidit].count(nb_nb_nb->id()) &&
473  nb_nb_nb->id() != eidit)
474  {
475  p1 = *new_mesh->node_ptr(fe_elem_pd_node_map.at(nb_nb_nb->id()));
476 
477  // add new phantom elem only if (p0, p1, p2) is counterclockwisely
478  // ordered
479  if ((p1(1) - p0(1)) * (p2(0) - p1(0)) -
480  (p1(0) - p0(0)) * (p2(1) - p1(1)) <
481  0)
482  {
483  should_add = true;
484  pd_nodes[1] = fe_elem_pd_node_map.at(nb_nb_nb->id());
485  pd_nodes[2] = fe_elem_pd_node_map.at(nb->id());
486  }
487  }
488  }
489  }
490  }
491  }
492  }
493  }
494  }
495  }
496  }
497  }
498  else if (old_elem->n_nodes() == 4 || old_elem->n_nodes() == 8 ||
499  old_elem->n_nodes() ==
500  9) // original quadrilateral FE elements: 4-node, 8-node and 9-node
501  {
502  std::vector<dof_id_type> pd_boundary_node_ids;
503  for (unsigned int i = 0; i < old_elem->n_neighbors(); ++i)
504  {
505  Elem * nb = old_elem->neighbor_ptr(i);
506  if (nb != nullptr && fe_bid_eid[bidit].count(nb->id()))
507  pd_boundary_node_ids.push_back(fe_elem_pd_node_map.at(nb->id()));
508  }
509  // the neighbor opposite to the boundary side is the third node for the phantom elem
510  pd_nodes[2] = fe_elem_pd_node_map.at(
511  old_elem
512  ->neighbor_ptr(old_elem->opposite_side(
513  old_boundary_info.side_with_boundary_id(old_elem, bidit)))
514  ->id());
515  p2 = *new_mesh->node_ptr(pd_nodes[2]);
516 
517  // if two boundary neighbors, one of them is the second node for the phantom elem
518  // if one boundary neighbors, it is the second node for the phantom elem
519  for (unsigned int i = 0; i < pd_boundary_node_ids.size(); ++i)
520  {
521  p1 = *new_mesh->node_ptr(pd_boundary_node_ids[i]);
522 
523  // new phantom elem only if (p0, p1, p2) is counterclockwisely orderd
524  if ((p1(1) - p0(1)) * (p2(0) - p1(0)) - (p1(0) - p0(0)) * (p2(1) - p1(1)) < 0)
525  {
526  should_add = true;
527  pd_nodes[1] = pd_boundary_node_ids[i];
528  }
529  }
530  }
531  else
532  mooseError("Element type ",
533  old_elem->type(),
534  " is not supported for PD sideset construction!");
535 
536  if (should_add)
537  {
538  Elem * new_elem = new Tri3;
539  new_elem->set_id(new_elem_id);
540  if (_merge_pd_blks)
541  new_elem->subdomain_id() = min_converted_fe_blk_id + _phantom_blk_offset_number;
542  else
543  new_elem->subdomain_id() = old_elem->subdomain_id() + _phantom_blk_offset_number;
544  new_elem = new_mesh->add_elem(new_elem);
545  new_elem->set_node(0, new_mesh->node_ptr(pd_nodes[0]));
546  new_elem->set_node(1, new_mesh->node_ptr(pd_nodes[1]));
547  new_elem->set_node(2, new_mesh->node_ptr(pd_nodes[2]));
548 
549  ++new_elem_id;
550 
551  new_boundary_info.add_side(new_elem, 0, _pd_blk_offset_number + bidit);
552  if (old_boundary_info.get_sideset_name(bidit) != "")
553  new_boundary_info.sideset_name(_pd_blk_offset_number + bidit) =
554  "pd_side_" + old_boundary_info.get_sideset_name(bidit);
555  }
556  }
557  else if (old_elem->dim() == 3) // 3D
558  {
559  pd_nodes.resize(4); // construct 4-node tetrahedral phanton elems
560  pd_nodes[0] = fe_elem_pd_node_map.at(eidit);
561  p0 = *new_mesh->node_ptr(pd_nodes[0]);
562  // search for three more nodes to construct a phantom 4-node tetrahedral element
563  if (old_elem->n_nodes() == 4 ||
564  old_elem->n_nodes() == 10) // original tetrahedral FE elements: 4-node or 10-node
565  {
566  // construct phantom element based on original tet mesh
567  mooseError("Peridynamics sideset generation does not accept tetrahedral elements!");
568  }
569  else if (old_elem->n_nodes() == 8 || old_elem->n_nodes() == 20 ||
570  old_elem->n_nodes() ==
571  27) // original hexahedral FE elements: 8-node, 20-node or 27-node
572  {
573  std::vector<dof_id_type> boundary_elem_ids;
574  for (unsigned int i = 0; i < old_elem->n_neighbors(); ++i)
575  {
576  Elem * nb = old_elem->neighbor_ptr(i);
577  if (nb != nullptr && fe_bid_eid[bidit].count(nb->id()))
578  boundary_elem_ids.push_back(nb->id());
579  }
580 
581  // find the fourth pd node by the boundary side of the element
582  pd_nodes[3] = fe_elem_pd_node_map.at(
583  old_elem
584  ->neighbor_ptr(old_elem->opposite_side(
585  old_boundary_info.side_with_boundary_id(old_elem, bidit)))
586  ->id());
587  p3 = *new_mesh->node_ptr(pd_nodes[3]);
588 
589  // choose two neighbors of current node (total of three pd nodes) ordered in a way
590  // such that the normal points to the fourth pd node
591  for (unsigned int i = 0; i < boundary_elem_ids.size(); ++i)
592  {
593  Elem * nb_i = old_mesh->elem_ptr(boundary_elem_ids[i]);
594  p1 = *new_mesh->node_ptr(fe_elem_pd_node_map.at(boundary_elem_ids[i]));
595 
596  for (unsigned int j = i + 1; j < boundary_elem_ids.size(); ++j)
597  {
598  Elem * nb_j = old_mesh->elem_ptr(boundary_elem_ids[j]);
599  p2 = *new_mesh->node_ptr(fe_elem_pd_node_map.at(boundary_elem_ids[j]));
600 
601  if (old_elem->which_neighbor_am_i(nb_i) !=
602  old_elem->opposite_side(old_elem->which_neighbor_am_i(nb_j)))
603  {
604  should_add = true;
605  // check whether this new elem overlaps with already created elems by the
606  // saved edge and nodes of previously created phantom elems
607  std::pair<dof_id_type, dof_id_type> nodes_pair_i;
608  nodes_pair_i.first = std::min(eidit, boundary_elem_ids[i]);
609  nodes_pair_i.second = std::max(eidit, boundary_elem_ids[i]);
610  if (elem_edge_nodes.count(nodes_pair_i))
611  {
612  for (const auto & nbid : elem_edge_nodes[nodes_pair_i])
613  if (nbid != old_elem->id() && old_mesh->elem_ptr(nbid)->has_neighbor(nb_j))
614  should_add = false;
615  }
616 
617  std::pair<dof_id_type, dof_id_type> nodes_pair_j;
618  nodes_pair_j.first = std::min(eidit, boundary_elem_ids[j]);
619  nodes_pair_j.second = std::max(eidit, boundary_elem_ids[j]);
620  if (elem_edge_nodes.count(nodes_pair_j))
621  {
622  for (const auto & nbid : elem_edge_nodes[nodes_pair_j])
623  if (nbid != old_elem->id() && old_mesh->elem_ptr(nbid)->has_neighbor(nb_i))
624  should_add = false;
625  }
626 
627  if (should_add)
628  {
629  // check whether the normal of face formed by p0, p1 and p2 points to p3
630  Real val =
631  ((p1(1) - p0(1)) * (p2(2) - p0(2)) - (p1(2) - p0(2)) * (p2(1) - p0(1))) *
632  (p3(0) - p0(0)) +
633  ((p1(2) - p0(2)) * (p2(0) - p0(0)) - (p1(0) - p0(0)) * (p2(2) - p0(2))) *
634  (p3(1) - p0(1)) +
635  ((p1(0) - p0(0)) * (p2(1) - p0(1)) - (p1(1) - p0(1)) * (p2(0) - p0(0))) *
636  (p3(2) - p0(2));
637  if (val > 0) // normal point to p3
638  {
639  pd_nodes[1] = fe_elem_pd_node_map.at(boundary_elem_ids[i]);
640  pd_nodes[2] = fe_elem_pd_node_map.at(boundary_elem_ids[j]);
641  }
642  else
643  {
644  pd_nodes[1] = fe_elem_pd_node_map.at(boundary_elem_ids[j]);
645  pd_nodes[2] = fe_elem_pd_node_map.at(boundary_elem_ids[i]);
646  }
647 
648  // construct the new phantom element
649  Elem * new_elem = new Tet4;
650  new_elem->set_id(new_elem_id);
651 
652  if (_merge_pd_blks)
653  new_elem->subdomain_id() =
654  min_converted_fe_blk_id + _phantom_blk_offset_number;
655  else
656  new_elem->subdomain_id() =
657  old_elem->subdomain_id() + _phantom_blk_offset_number;
658 
659  new_elem = new_mesh->add_elem(new_elem);
660  new_elem->set_node(0, new_mesh->node_ptr(pd_nodes[0]));
661  new_elem->set_node(1, new_mesh->node_ptr(pd_nodes[1]));
662  new_elem->set_node(2, new_mesh->node_ptr(pd_nodes[2]));
663  new_elem->set_node(3, new_mesh->node_ptr(pd_nodes[3]));
664 
665  // save the edges and nodes used in the new phantom elem, which will be used
666  // for creating new phantom elements
667  elem_edge_nodes[nodes_pair_i].insert(boundary_elem_ids[j]);
668  elem_edge_nodes[nodes_pair_j].insert(boundary_elem_ids[i]);
669 
670  ++new_elem_id;
671 
672  new_boundary_info.add_side(new_elem, 0, _pd_blk_offset_number + bidit);
673  if (old_boundary_info.get_sideset_name(bidit) != "")
674  {
675  new_boundary_info.sideset_name(_pd_blk_offset_number + bidit) =
676  "pd_side_" + old_boundary_info.get_sideset_name(bidit);
677  }
678  }
679  }
680  }
681  }
682  }
683  else
684  mooseError("Element type ",
685  old_elem->type(),
686  " is not supported for PD sidesets construction!");
687  }
688  }
689  }
690  }
691  }
692 
693  // next, save non-converted or retained FE elements, if any, to new mesh
694  std::map<dof_id_type, dof_id_type> fe_elems_map; // IDs of the same elem in the old and new meshes
695  for (const auto & eid : fe_elems)
696  {
697  Elem * old_elem = old_mesh->elem_ptr(eid);
698  Elem * new_elem = Elem::build(old_elem->type()).release();
699  new_elem->set_id(new_elem_id);
700  new_elem->subdomain_id() = old_elem->subdomain_id();
701  new_elem = new_mesh->add_elem(new_elem);
702  for (unsigned int j = 0; j < old_elem->n_nodes(); ++j)
703  new_elem->set_node(j, new_mesh->node_ptr(fe_nodes_map.at(old_elem->node_ptr(j)->id())));
704 
705  fe_elems_map.insert(std::make_pair(old_elem->id(), new_elem_id));
706 
707  ++new_elem_id;
708  }
709 
710  // STEP 5: convert old boundary_info to new boundary_info for retained FE mesh
711 
712  // peridynamics ONLY accept nodesets and sidesets
713  // nodeset consisting single node in the converted FE block will no longer be available
714  if (old_boundary_info.n_edge_conds())
715  mooseError("PeridynamicsMesh does not support edgesets!");
716 
717  // check the existence of nodesets, if exist, build sidesets in case this hasn't been done yet
718  if (old_boundary_info.n_nodeset_conds())
719  old_boundary_info.build_side_list_from_node_list();
720 
721  // first, create a tuple to collect all sidesets (including those converted from nodesets) in the
722  // old mesh
723  auto old_fe_sbc_tuples = old_boundary_info.build_side_list();
724  // 0: element ID, 1: side ID, 2: boundary ID
725  // map of set of elem IDs connected to each boundary in the old mesh
726  std::map<boundary_id_type, std::set<dof_id_type>> old_fe_bnd_elem_ids;
727  // map of set of side ID for each elem in the old mesh
728  std::map<dof_id_type, std::map<boundary_id_type, dof_id_type>> old_fe_elem_bnd_side_ids;
729  for (const auto & sbct : old_fe_sbc_tuples)
730  {
731  old_fe_bnd_elem_ids[std::get<2>(sbct)].insert(std::get<0>(sbct));
732  old_fe_elem_bnd_side_ids[std::get<0>(sbct)].insert(
733  std::make_pair(std::get<2>(sbct), std::get<1>(sbct)));
734  }
735 
736  // next, convert element lists in old mesh to PD nodesets in new mesh
737  std::set<boundary_id_type> old_side_bid(old_boundary_info.get_side_boundary_ids());
738 
739  // loop through all old FE _sideset_ boundaries
740  for (const auto & sbid : old_side_bid)
741  for (const auto & beid : old_fe_bnd_elem_ids[sbid])
742  if (elems_to_pd.count(beid)) // for converted FE mesh
743  {
744  // save corresponding boundaries on converted FE mesh to PD nodes
745  new_boundary_info.add_node(new_mesh->node_ptr(fe_elem_pd_node_map.at(beid)),
746  sbid + _pd_blk_offset_number);
747  if (old_boundary_info.get_sideset_name(sbid) != "")
748  new_boundary_info.nodeset_name(sbid + _pd_blk_offset_number) =
749  "pd_nodes_" + old_boundary_info.get_sideset_name(sbid);
750 
751  if (_retain_fe_mesh) // if retained, copy the corresponding boundaries, if any, to new mesh
752  // from old mesh
753  {
754  new_boundary_info.add_side(
755  new_mesh->elem_ptr(fe_elems_map[beid]), old_fe_elem_bnd_side_ids[beid][sbid], sbid);
756  new_boundary_info.sideset_name(sbid) = old_boundary_info.get_sideset_name(sbid);
757  }
758  }
759  else // for non-converted FE mesh, if any, copy the corresponding boundaries to new mesh
760  // from old mesh
761  {
762  new_boundary_info.add_side(
763  new_mesh->elem_ptr(fe_elems_map[beid]), old_fe_elem_bnd_side_ids[beid][sbid], sbid);
764  new_boundary_info.sideset_name(sbid) = old_boundary_info.get_sideset_name(sbid);
765  }
766 
767  // similar for sideset above, save _nodesets_ of non-converted and/or retained FE mesh, if any, to
768  // new mesh
769  auto old_node_bc_tuples = old_boundary_info.build_node_list();
770  // 0: node ID, 1: boundary ID
771  std::map<boundary_id_type, std::set<dof_id_type>> old_bnd_node_ids;
772  for (const auto & nbct : old_node_bc_tuples)
773  old_bnd_node_ids[std::get<1>(nbct)].insert(std::get<0>(nbct));
774 
775  std::set<boundary_id_type> old_node_bid(old_boundary_info.get_node_boundary_ids());
776 
777  for (const auto & nbid : old_node_bid)
778  for (const auto & bnid : old_bnd_node_ids[nbid])
779  if (fe_nodes.count(bnid))
780  {
781  new_boundary_info.add_node(new_mesh->node_ptr(fe_nodes_map.at(bnid)), nbid);
782  new_boundary_info.nodeset_name(nbid) = old_boundary_info.get_sideset_name(nbid);
783  }
784 
785  // create nodesets to include all PD nodes for PD blocks in the new mesh
786  for (unsigned int i = 0; i < n_pd_nodes; ++i)
787  {
788  if (_blks_to_pd.size() > 1 && !_merge_pd_blks)
789  {
790  unsigned int j = 0;
791  for (const auto & blk_id : _blks_to_pd)
792  {
793  ++j;
794  SubdomainID real_blk_id =
795  blk_id + _pd_blk_offset_number; // account for the _pd_blk_offset_number increment after
796  // converting to PD mesh
797  if (pd_mesh.getNodeBlockID(i) == real_blk_id)
798  {
799  new_boundary_info.add_node(new_mesh->node_ptr(i), _pd_nodeset_offset_number - j);
800  new_boundary_info.nodeset_name(_pd_nodeset_offset_number - j) =
801  "pd_nodes_block_" + Moose::stringify(blk_id + _pd_blk_offset_number);
802  }
803  }
804  }
805 
806  new_boundary_info.add_node(new_mesh->node_ptr(i), _pd_nodeset_offset_number);
807  new_boundary_info.nodeset_name(_pd_nodeset_offset_number) = "pd_nodes_all";
808  }
809 
810  old_mesh.reset(); // destroy the old_mesh unique_ptr
811 
812  return dynamic_pointer_cast<MeshBase>(new_mesh);
813 }
unsigned int _pd_nodeset_offset_number
a number used to offset the boundary nodeset ID after being converted into PD nodeset ...
std::set< boundary_id_type > _fe_sidesets_for_pd_construction
std::multimap< SubdomainID, SubdomainID > _pd_non_bonding_blk_pairs
dof_id_type nPDBonds() const
Function to return number of PD Edge elements.
void createPeridynamicsMeshData(MeshBase &fe_mesh, std::set< dof_id_type > converted_elem_id, std::multimap< SubdomainID, SubdomainID > bonding_block_pairs, std::multimap< SubdomainID, SubdomainID > non_bonding_block_pairs)
Function to assign values to member variables (PD mesh data) of this class this function will be call...
T & getMesh(MooseMesh &mesh)
function to cast mesh
Definition: SCM.h:35
std::set< SubdomainID > _blks_to_pd
MeshGeneratorPD(const InputParameters &parameters)
void addParam(const std::string &name, const std::initializer_list< typename T::value_type > &value, const std::string &doc_string)
std::set< std::string > sideset
std::multimap< SubdomainID, SubdomainID > _pd_bonding_blk_pairs
std::vector< dof_id_type > getNeighbors(dof_id_type node_id)
Function to return neighbor nodes indices for node node_id.
Peridynamics mesh class.
bool _merge_pd_interfacial_blks
flag to specify whether a single block should be used for all PD interfacial blocks this is used when...
bool _merge_pd_blks
flag to specify whether to combine converted PD mesh blocks into a single mesh block or not this is u...
std::unique_ptr< T_DEST, T_DELETER > dynamic_pointer_cast(std::unique_ptr< T_SRC, T_DELETER > &src)
bool _retain_fe_mesh
flag to specify whether the FE mesh should be retained or not in addition to newly created PD mesh ...
bool _has_non_bonding_blk_pairs
pairs of converted FE block IDs when only certain blocks need NOT to be connected this is usually use...
void addRequiredParam(const std::string &name, const std::string &doc_string)
bool _has_blks_to_pd
block ID(s) of input FE mesh when only certain block(s) needs to be converted to PD mesh this is used...
bool _has_sidesets_to_pd
list of sideset ID(s) to be constructed based on converted PD mesh if the _construct_pd_sideset is tr...
SubdomainID getNodeBlockID(dof_id_type node_id)
Function to return block ID for node node_id.
dof_id_type nPDNodes() const
Function to return number of PD nodes.
std::unique_ptr< MeshBase > & _input
Reference to the input finite element mesh.
bool _has_blks_as_fe
block ID(s) of input FE mesh when only certain block(s) needs NOT to be converted to PD mesh this is ...
static InputParameters validParams()
static InputParameters validParams()
std::string stringify(const T &t)
unsigned int _phantom_blk_offset_number
a number used to offset the block ID for phantom elements
std::set< SubdomainID > _blks_as_fe
bool _has_bonding_blk_pairs
pairs of converted FE block IDs when only certain blocks need to be connected using interfacial bonds...
DIE A HORRIBLE DEATH HERE typedef LIBMESH_DEFAULT_SCALAR_TYPE Real
unsigned int _pd_blk_offset_number
a number used to offset the block ID after being converted into PD mesh
void mooseError(Args &&... args) const
void addClassDescription(const std::string &doc_string)
static const std::complex< double > j(0, 1)
Complex number "j" (also known as "i")
std::unique_ptr< MeshBase > buildMeshBaseObject(unsigned int dim=libMesh::invalid_uint)
Generate peridynamics mesh based on finite element mesh.
static const std::string k
Definition: NS.h:130
bool _construct_pd_sideset
flag to specify whether PD sideset should be constructed or not
std::unique_ptr< MeshBase > generate()
Function to convert the finite element mesh to peridynamics mesh.
void setNodeBlockID(SubdomainID id)
Function to set block ID for all PD nodes.
uint8_t dof_id_type
registerMooseObject("PeridynamicsApp", MeshGeneratorPD)