libMesh
mesh_subdivision_support.C
Go to the documentation of this file.
1 // The libMesh Finite Element Library.
2 // Copyright (C) 2002-2025 Benjamin S. Kirk, John W. Peterson, Roy H. Stogner
3 
4 // This library is free software; you can redistribute it and/or
5 // modify it under the terms of the GNU Lesser General Public
6 // License as published by the Free Software Foundation; either
7 // version 2.1 of the License, or (at your option) any later version.
8 
9 // This library is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 // Lesser General Public License for more details.
13 
14 // You should have received a copy of the GNU Lesser General Public
15 // License along with this library; if not, write to the Free Software
16 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17 
18 
19 
20 // C++ includes
21 
22 // Local includes
23 #include "libmesh/mesh_tools.h"
24 #include "libmesh/mesh_subdivision_support.h"
25 #include "libmesh/boundary_info.h"
26 
27 namespace libMesh
28 {
29 
30 
32  std::vector<const Node *> & nodes)
33 {
36 
37  unsigned int valence = elem->get_ordered_valence(0);
38  nodes.resize(valence + 6);
39 
40  // The first three vertices in the patch are the ones from the element triangle
41  nodes[0] = elem->get_ordered_node(0);
42  nodes[1] = elem->get_ordered_node(1);
43  nodes[valence] = elem->get_ordered_node(2);
44 
45  const unsigned int nn0 = elem->local_node_number(nodes[0]->id());
46 
47  const Tri3Subdivision * nb = dynamic_cast<const Tri3Subdivision *>(elem->neighbor_ptr(nn0));
48  libmesh_assert(nb);
49 
50  unsigned int j, i = 1;
51 
52  do
53  {
54  ++i;
55  j = nb->local_node_number(nodes[0]->id());
56  nodes[i] = nb->node_ptr(next[j]);
57  nb = static_cast<const Tri3Subdivision *>(nb->neighbor_ptr(j));
58  } while (nb != elem);
59 
60  /* for nodes connected with N (= valence[0]) */
61  nb = static_cast<const Tri3Subdivision *>(elem->neighbor_ptr(next[nn0]));
62  j = nb->local_node_number(nodes[1]->id());
63  nodes[valence+1] = nb->node_ptr(next[j]);
64 
65  nb = static_cast<const Tri3Subdivision *>(nb->neighbor_ptr(next[j]));
66  j = nb->local_node_number(nodes[valence+1]->id());
67  nodes[valence+4] = nb->node_ptr(next[j]);
68 
69  nb = static_cast<const Tri3Subdivision *>(nb->neighbor_ptr(next[j]));
70  j = nb->local_node_number(nodes[valence+4]->id());
71  nodes[valence+5] = nb->node_ptr(next[j]);
72 
73  /* for nodes connected with 1 */
74  nb = static_cast<const Tri3Subdivision *>(elem->neighbor_ptr(next[nn0]));
75  j = nb->local_node_number(nodes[1]->id());
76  // nodes[valence+1] has been determined already
77 
78  nb = static_cast<const Tri3Subdivision *>(nb->neighbor_ptr(j));
79  j = nb->local_node_number(nodes[1]->id());
80  nodes[valence+2] = nb->node_ptr(next[j]);
81 
82  nb = static_cast<const Tri3Subdivision *>(nb->neighbor_ptr(j));
83  j = nb->local_node_number(nodes[1]->id());
84  nodes[valence+3] = nb->node_ptr(next[j]);
85 
86  return;
87 }
88 
89 
91 {
92  const bool mesh_has_boundary_data =
94 
95  std::vector<Elem *> new_boundary_elements;
96  std::vector<short int> new_boundary_sides;
97  std::vector<boundary_id_type> new_boundary_ids;
98 
99  // Container to catch ids handed back from BoundaryInfo
100  std::vector<boundary_id_type> ids;
101 
102  for (const auto & elem : mesh.element_ptr_range())
103  {
104  libmesh_assert_equal_to(elem->type(), TRI3);
105 
106  auto tri = Elem::build_with_id(TRI3SUBDIVISION, elem->id());
107  tri->subdomain_id() = elem->subdomain_id();
108  tri->set_node(0, elem->node_ptr(0));
109  tri->set_node(1, elem->node_ptr(1));
110  tri->set_node(2, elem->node_ptr(2));
111 
112  if (mesh_has_boundary_data)
113  {
114  for (auto side : elem->side_index_range())
115  {
116  mesh.get_boundary_info().boundary_ids(elem, side, ids);
117 
118  for (const auto & id : ids)
119  {
120  // add the boundary id to the list of new boundary ids
121  new_boundary_ids.push_back(id);
122  new_boundary_elements.push_back(tri.get());
123  new_boundary_sides.push_back(side);
124  }
125  }
126 
127  // remove the original element from the BoundaryInfo structure
128  mesh.get_boundary_info().remove(elem);
129  }
130 
131  mesh.insert_elem(std::move(tri));
132  }
134 
135  if (mesh_has_boundary_data)
136  {
137  // If the old mesh had boundary data, the new mesh better have some too.
138  libmesh_assert_greater(new_boundary_elements.size(), 0);
139 
140  // We should also be sure that the lengths of the new boundary data vectors
141  // are all the same.
142  libmesh_assert_equal_to(new_boundary_sides.size(), new_boundary_elements.size());
143  libmesh_assert_equal_to(new_boundary_sides.size(), new_boundary_ids.size());
144 
145  // Add the new boundary info to the mesh.
146  for (auto s : index_range(new_boundary_elements))
147  mesh.get_boundary_info().add_side(new_boundary_elements[s],
148  new_boundary_sides[s],
149  new_boundary_ids[s]);
150  }
151 
153 }
154 
155 
157 {
159 
160  // convert all mesh elements to subdivision elements
162 
163  if (!ghosted)
164  {
165  // add the ghost elements for the boundaries
167  }
168  else
169  {
170  // This assumes that the mesh already has the ghosts. Only tagging them is required here.
172  }
173 
175 
176  std::unordered_map<dof_id_type, std::vector<const Elem *>> nodes_to_elem_map;
177  MeshTools::build_nodes_to_elem_map(mesh, nodes_to_elem_map);
178 
179  // compute the node valences
180  for (auto & node : mesh.node_ptr_range())
181  {
182  std::vector<const Node *> neighbors;
183  MeshTools::find_nodal_neighbors(mesh, *node, nodes_to_elem_map, neighbors);
184  const unsigned int valence =
185  cast_int<unsigned int>(neighbors.size());
186  libmesh_assert_greater(valence, 1);
187  node->set_valence(valence);
188  }
189 
190  for (auto & elem : mesh.element_ptr_range())
191  {
192  Tri3Subdivision * tri3s = dynamic_cast<Tri3Subdivision *>(elem);
193  libmesh_assert(tri3s);
194  if (!tri3s->is_ghost())
196  }
197 }
198 
199 
201 {
202  for (auto & elem : mesh.element_ptr_range())
203  {
204  libmesh_assert_equal_to(elem->type(), TRI3SUBDIVISION);
205 
206  Tri3Subdivision * sd_elem = static_cast<Tri3Subdivision *>(elem);
207  for (auto i : elem->side_index_range())
208  {
209  if (elem->neighbor_ptr(i) == nullptr)
210  {
211  sd_elem->set_ghost(true);
212  // set all other neighbors to ghosts as well
213  if (elem->neighbor_ptr(next[i]))
214  {
215  Tri3Subdivision * nb = static_cast<Tri3Subdivision *>(elem->neighbor_ptr(next[i]));
216  nb->set_ghost(true);
217  }
218  if (elem->neighbor_ptr(prev[i]))
219  {
220  Tri3Subdivision * nb = static_cast<Tri3Subdivision *>(elem->neighbor_ptr(prev[i]));
221  nb->set_ghost(true);
222  }
223  }
224  }
225  }
226 }
227 
228 
230 {
231  static const Real tol = 1e-5;
232 
233  // add the mirrored ghost elements (without using iterators, because the mesh is modified in the course)
234  std::vector<Tri3Subdivision *> ghost_elems;
235  std::vector<Node *> ghost_nodes;
236  const unsigned int n_elem = mesh.n_elem();
237  for (unsigned int eid = 0; eid < n_elem; ++eid)
238  {
239  Elem * elem = mesh.elem_ptr(eid);
240  libmesh_assert_equal_to(elem->type(), TRI3SUBDIVISION);
241 
242  // If the triangle happens to be in a corner (two boundary
243  // edges), we perform a counter-clockwise loop by mirroring the
244  // previous triangle until we come back to the original
245  // triangle. This prevents degenerated triangles in the mesh
246  // corners and guarantees that the node in the middle of the
247  // loop is of valence=6.
248  for (auto i : elem->side_index_range())
249  {
250  libmesh_assert_not_equal_to(elem->neighbor_ptr(i), elem);
251 
252  if (elem->neighbor_ptr(i) == nullptr &&
253  elem->neighbor_ptr(next[i]) == nullptr)
254  {
255  Elem * nelem = elem;
256  unsigned int k = i;
257  for (unsigned int l=0;l<4;l++)
258  {
259  // this is the vertex to be mirrored
260  Point point = nelem->point(k) + nelem->point(next[k]) - nelem->point(prev[k]);
261 
262  // Check if the proposed vertex doesn't coincide
263  // with one of the existing vertices. This is
264  // necessary because for some triangulations, it can
265  // happen that two mirrored ghost vertices coincide,
266  // which would then lead to a zero size ghost
267  // element below.
268  Node * node = nullptr;
269  for (auto & ghost_node : ghost_nodes)
270  if ((*ghost_node - point).norm() < tol * (elem->point(k) - point).norm())
271  {
272  node = ghost_node;
273  break;
274  }
275 
276  // add the new vertex only if no other is nearby
277  if (node == nullptr)
278  {
279  node = mesh.add_point(point);
280  ghost_nodes.push_back(node);
281  }
282 
283  auto uelem = Elem::build(TRI3SUBDIVISION);
284  auto newelem = cast_ptr<Tri3Subdivision *>(uelem.get());
285 
286  // add the first new ghost element to the list just as in the non-corner case
287  if (l == 0)
288  ghost_elems.push_back(newelem);
289 
290  newelem->set_node(0, nelem->node_ptr(next[k]));
291  newelem->set_node(1, nelem->node_ptr(k));
292  newelem->set_node(2, node);
293  newelem->set_neighbor(0, nelem);
294  newelem->set_ghost(true);
295  if (l>0)
296  newelem->set_neighbor(2, nullptr);
297  nelem->set_neighbor(k, newelem);
298 
299  Elem * added_elem = mesh.add_elem(std::move(uelem));
300  mesh.get_boundary_info().add_node(nelem->node_ptr(k), 1);
301  mesh.get_boundary_info().add_node(nelem->node_ptr(next[k]), 1);
302  mesh.get_boundary_info().add_node(nelem->node_ptr(prev[k]), 1);
303  mesh.get_boundary_info().add_node(node, 1);
304 
305  nelem = added_elem;
306  k = 2 ;
307  }
308 
309  auto uelem = Elem::build(TRI3SUBDIVISION);
310  auto newelem = cast_ptr<Tri3Subdivision *>(uelem.get());
311 
312  newelem->set_node(0, elem->node_ptr(next[i]));
313  newelem->set_node(1, nelem->node_ptr(2));
314  newelem->set_node(2, elem->node_ptr(prev[i]));
315  newelem->set_neighbor(0, nelem);
316  nelem->set_neighbor(2, newelem);
317  newelem->set_ghost(true);
318  newelem->set_neighbor(2, elem);
319  elem->set_neighbor(next[i],newelem);
320 
321  mesh.add_elem(std::move(uelem));
322 
323  break;
324  }
325  }
326 
327  for (auto i : elem->side_index_range())
328  {
329  libmesh_assert_not_equal_to(elem->neighbor_ptr(i), elem);
330  if (elem->neighbor_ptr(i) == nullptr)
331  {
332  // this is the vertex to be mirrored
333  Point point = elem->point(i) + elem->point(next[i]) - elem->point(prev[i]);
334 
335  // Check if the proposed vertex doesn't coincide with
336  // one of the existing vertices. This is necessary
337  // because for some triangulations, it can happen that
338  // two mirrored ghost vertices coincide, which would
339  // then lead to a zero size ghost element below.
340  Node * node = nullptr;
341  for (auto & ghost_node : ghost_nodes)
342  if ((*ghost_node - point).norm() < tol * (elem->point(i) - point).norm())
343  {
344  node = ghost_node;
345  break;
346  }
347 
348  // add the new vertex only if no other is nearby
349  if (node == nullptr)
350  {
351  node = mesh.add_point(point);
352  ghost_nodes.push_back(node);
353  }
354 
355  auto uelem = Elem::build(TRI3SUBDIVISION);
356  auto newelem = cast_ptr<Tri3Subdivision *>(uelem.get());
357 
358  ghost_elems.push_back(newelem);
359 
360  newelem->set_node(0, elem->node_ptr(next[i]));
361  newelem->set_node(1, elem->node_ptr(i));
362  newelem->set_node(2, node);
363  newelem->set_neighbor(0, elem);
364  newelem->set_ghost(true);
365  elem->set_neighbor(i, newelem);
366 
367  mesh.add_elem(std::move(uelem));
368  mesh.get_boundary_info().add_node(elem->node_ptr(i), 1);
369  mesh.get_boundary_info().add_node(elem->node_ptr(next[i]), 1);
370  mesh.get_boundary_info().add_node(elem->node_ptr(prev[i]), 1);
371  mesh.get_boundary_info().add_node(node, 1);
372  }
373  }
374  }
375 
376  // add the missing ghost elements (connecting new ghost nodes)
377  std::vector<std::unique_ptr<Elem>> missing_ghost_elems;
378  for (auto & elem : ghost_elems)
379  {
380  libmesh_assert(elem->is_ghost());
381 
382  for (auto i : elem->side_index_range())
383  {
384  if (elem->neighbor_ptr(i) == nullptr &&
385  elem->neighbor_ptr(prev[i]) != nullptr)
386  {
387  // go around counter-clockwise
388  Tri3Subdivision * nb1 = static_cast<Tri3Subdivision *>(elem->neighbor_ptr(prev[i]));
389  Tri3Subdivision * nb2 = nb1;
390  unsigned int j = i;
391  unsigned int n_nb = 0;
392  while (nb1 != nullptr && nb1->id() != elem->id())
393  {
394  j = nb1->local_node_number(elem->node_id(i));
395  nb2 = nb1;
396  nb1 = static_cast<Tri3Subdivision *>(nb1->neighbor_ptr(prev[j]));
397  libmesh_assert(nb1 == nullptr || nb1->id() != nb2->id());
398  n_nb++;
399  }
400 
401  libmesh_assert_not_equal_to(nb2->id(), elem->id());
402 
403  // Above, we merged coinciding ghost vertices. Therefore, we need
404  // to exclude the case where there is no ghost element to add between
405  // these two (identical) ghost nodes.
406  if (elem->node_ptr(next[i])->id() == nb2->node_ptr(prev[j])->id())
407  break;
408 
409  // If the number of already present neighbors is less than 4, we add another extra element
410  // so that the node in the middle of the loop ends up being of valence=6.
411  // This case usually happens when the middle node corresponds to a corner of the original mesh,
412  // and the extra element below prevents degenerated triangles in the mesh corners.
413  if (n_nb < 4)
414  {
415  // this is the vertex to be mirrored
416  Point point = nb2->point(j) + nb2->point(prev[j]) - nb2->point(next[j]);
417 
418  // Check if the proposed vertex doesn't coincide with one of the existing vertices.
419  // This is necessary because for some triangulations, it can happen that two mirrored
420  // ghost vertices coincide, which would then lead to a zero size ghost element below.
421  Node * node = nullptr;
422  for (auto & ghost_node : ghost_nodes)
423  if ((*ghost_node - point).norm() < tol * (nb2->point(j) - point).norm())
424  {
425  node = ghost_node;
426  break;
427  }
428 
429  // add the new vertex only if no other is nearby
430  if (node == nullptr)
431  {
432  node = mesh.add_point(point);
433  ghost_nodes.push_back(node);
434  }
435 
436  auto uelem = Elem::build(TRI3SUBDIVISION);
437  auto newelem = cast_ptr<Tri3Subdivision *>(uelem.get());
438 
439  newelem->set_node(0, nb2->node_ptr(j));
440  newelem->set_node(1, nb2->node_ptr(prev[j]));
441  newelem->set_node(2, node);
442  newelem->set_neighbor(0, nb2);
443  newelem->set_neighbor(1, nullptr);
444  newelem->set_ghost(true);
445  nb2->set_neighbor(prev[j], newelem);
446 
447  Elem * added_elem = mesh.add_elem(std::move(uelem));
448  mesh.get_boundary_info().add_node(nb2->node_ptr(j), 1);
450  mesh.get_boundary_info().add_node(node, 1);
451 
452  nb2 = cast_ptr<Tri3Subdivision *>(added_elem);
453  j = nb2->local_node_number(elem->node_id(i));
454  }
455 
456  auto uelem = Elem::build(TRI3SUBDIVISION);
457  auto newelem = cast_ptr<Tri3Subdivision *>(uelem.get());
458 
459  newelem->set_node(0, elem->node_ptr(next[i]));
460  newelem->set_node(1, elem->node_ptr(i));
461  newelem->set_node(2, nb2->node_ptr(prev[j]));
462  newelem->set_neighbor(0, elem);
463  newelem->set_neighbor(1, nb2);
464  newelem->set_neighbor(2, nullptr);
465  newelem->set_ghost(true);
466 
467  elem->set_neighbor(i, newelem);
468  nb2->set_neighbor(prev[j], newelem);
469 
470  missing_ghost_elems.push_back(std::move(uelem));
471  break;
472  }
473  } // end side loop
474  } // end ghost element loop
475 
476  // add the missing ghost elements to the mesh
477  for (auto & elem : missing_ghost_elems)
478  mesh.add_elem(std::move(elem));
479 }
480 
481 } // namespace libMesh
A Node is like a Point, but with more information.
Definition: node.h:52
void add_boundary_ghosts(MeshBase &mesh)
Adds a new layer of "ghost" elements along the domain boundaries.
IntRange< unsigned short > side_index_range() const
Definition: elem.h:2710
dof_id_type n_elem(const MeshBase::const_element_iterator &begin, const MeshBase::const_element_iterator &end)
Count up the number of elements of a specific type (as defined by an iterator range).
Definition: mesh_tools.C:969
void tag_boundary_ghosts(MeshBase &mesh)
Flags the outermost element layer along the domain boundaries as "ghost" elements.
void prepare_for_use(const bool skip_renumber_nodes_and_elements, const bool skip_find_neighbors)
Prepare a newly ecreated (or read) mesh for use.
Definition: mesh_base.C:759
void remove(const Node *node)
Removes the boundary conditions associated with node node, if any exist.
static const unsigned int prev[3]
A lookup table for the decrement modulo 3 operation, for iterating through the three nodes per elemen...
This is the base class from which all geometric element types are derived.
Definition: elem.h:94
MeshBase & mesh
void find_nodal_neighbors(const MeshBase &mesh, const Node &n, const std::vector< std::vector< const Elem *>> &nodes_to_elem_map, std::vector< const Node *> &neighbors)
Given a mesh and a node in the mesh, the vector will be filled with every node directly attached to t...
Definition: mesh_tools.C:1036
void all_subdivision(MeshBase &mesh)
Turns a triangulated mesh into a subdivision mesh.
void build_nodes_to_elem_map(const MeshBase &mesh, std::vector< std::vector< dof_id_type >> &nodes_to_elem_map)
After calling this function the input vector nodes_to_elem_map will contain the node to element conne...
Definition: mesh_tools.C:449
void boundary_ids(const Node *node, std::vector< boundary_id_type > &vec_to_fill) const
Fills a user-provided std::vector with the boundary ids associated with Node node.
The libMesh namespace provides an interface to certain functionality in the library.
const BoundaryInfo & get_boundary_info() const
The information about boundary ids on the mesh.
Definition: mesh_base.h:165
void prepare_subdivision_properties()
Prepares the element for use by reordering the nodes such that the irregular node (valence != 6)...
virtual Node * add_point(const Point &p, const dof_id_type id=DofObject::invalid_id, const processor_id_type proc_id=DofObject::invalid_processor_id)=0
Add a new Node at Point p to the end of the vertex array, with processor_id procid.
This is the MeshBase class.
Definition: mesh_base.h:75
std::size_t n_boundary_ids() const
Node * get_ordered_node(unsigned int node_id) const
void find_one_ring(const Tri3Subdivision *elem, std::vector< const Node *> &nodes)
Determines the 1-ring of element elem, and writes it to the nodes vector.
void add_node(const Node *node, const boundary_id_type id)
Add Node node with boundary id id to the boundary information data structures.
dof_id_type id() const
Definition: dof_object.h:828
virtual Elem * add_elem(Elem *e)=0
Add elem e to the end of the element array.
static std::unique_ptr< Elem > build(const ElemType type, Elem *p=nullptr)
Definition: elem.C:444
libmesh_assert(ctx)
The Tri3Subdivision element is a three-noded subdivision surface shell element used in mechanics calc...
unsigned int local_node_number(unsigned int node_id) const
auto norm(const T &a) -> decltype(std::abs(a))
Definition: tensor_tools.h:74
void set_neighbor(const unsigned int i, Elem *n)
Assigns n as the neighbor.
Definition: elem.h:2618
unsigned int get_ordered_valence(unsigned int node_id) const
virtual const Elem * elem_ptr(const dof_id_type i) const =0
virtual Elem * insert_elem(Elem *e)=0
Insert elem e to the element array, preserving its id and replacing/deleting any existing element wit...
const Elem * neighbor_ptr(unsigned int i) const
Definition: elem.h:2598
static const unsigned int next[3]
A lookup table for the increment modulo 3 operation, for iterating through the three nodes per elemen...
DIE A HORRIBLE DEATH HERE typedef LIBMESH_DEFAULT_SCALAR_TYPE Real
void set_ghost(bool ghosted)
Sets the boolean flag identifying ghost elements.
const Node * node_ptr(const unsigned int i) const
Definition: elem.h:2507
static std::unique_ptr< Elem > build_with_id(const ElemType type, dof_id_type id)
Calls the build() method above with a nullptr parent, and additionally sets the newly-created Elem&#39;s ...
Definition: elem.C:558
void add_side(const dof_id_type elem, const unsigned short int side, const boundary_id_type id)
Add side side of element number elem with boundary id id to the boundary information data structure...
void prepare_subdivision_mesh(MeshBase &mesh, bool ghosted=false)
Prepares the mesh for use with subdivision elements.
virtual dof_id_type n_elem() const =0
virtual ElemType type() const =0
A Point defines a location in LIBMESH_DIM dimensional Real space.
Definition: point.h:39
const Point & point(const unsigned int i) const
Definition: elem.h:2453
auto index_range(const T &sizable)
Helper function that returns an IntRange<std::size_t> representing all the indices of the passed-in v...
Definition: int_range.h:117