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