1 // The libMesh Finite Element Library.
2 // Copyright (C) 2002-2020 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 #include "libmesh/libmesh_config.h"
19
20 #ifdef LIBMESH_ENABLE_INFINITE_ELEMENTS
21
22 // C++ includes
23
24 // Local includes
25 #include "libmesh/inf_elem_builder.h"
26 #include "libmesh/libmesh_logging.h"
27 #include "libmesh/mesh_tools.h"
28 #include "libmesh/face_inf_quad4.h"
29 #include "libmesh/face_inf_quad6.h"
30 #include "libmesh/cell_inf_prism6.h"
31 #include "libmesh/cell_inf_prism12.h"
32 #include "libmesh/cell_inf_hex8.h"
33 #include "libmesh/cell_inf_hex16.h"
34 #include "libmesh/cell_inf_hex18.h"
35 #include "libmesh/mesh_base.h"
36 #include "libmesh/remote_elem.h"
37
38 namespace libMesh
39 {
40
build_inf_elem(bool be_verbose)41 const Point InfElemBuilder::build_inf_elem(bool be_verbose)
42 {
43 // determine origin automatically,
44 // works only if the mesh has no symmetry planes.
45 const BoundingBox b_box = MeshTools::create_bounding_box(_mesh);
46 Point origin = (b_box.first + b_box.second) / 2;
47
48 if (be_verbose && _mesh.processor_id() == 0)
49 {
50 #ifdef DEBUG
51 libMesh::out << " Determined origin for Infinite Elements:"
52 << std::endl
53 << " ";
54 origin.write_unformatted(libMesh::out);
55 libMesh::out << std::endl;
56 #endif
57 }
58
59 // Call the protected implementation function with the
60 // automatically determined origin.
61 this->build_inf_elem(origin, false, false, false, be_verbose);
62
63 // when finished with building the Ifems,
64 // it remains to prepare the mesh for use:
65 // find neighbors (again), partition (if needed)...
66 this->_mesh.prepare_for_use ();
67
68 return origin;
69 }
70
71
72
73
74
75
76
77
78
79
80
81
build_inf_elem(const InfElemOriginValue & origin_x,const InfElemOriginValue & origin_y,const InfElemOriginValue & origin_z,const bool x_sym,const bool y_sym,const bool z_sym,const bool be_verbose,std::vector<const Node * > * inner_boundary_nodes)82 const Point InfElemBuilder::build_inf_elem (const InfElemOriginValue & origin_x,
83 const InfElemOriginValue & origin_y,
84 const InfElemOriginValue & origin_z,
85 const bool x_sym,
86 const bool y_sym,
87 const bool z_sym,
88 const bool be_verbose,
89 std::vector<const Node *> * inner_boundary_nodes)
90 {
91 START_LOG("build_inf_elem()", "InfElemBuilder");
92
93 // first determine the origin of the
94 // infinite elements. For this, the
95 // origin defaults to the given values,
96 // and may be overridden when the user
97 // provided values
98 Point origin(origin_x.second, origin_y.second, origin_z.second);
99
100 // when only _one_ of the origin coordinates is _not_
101 // given, we have to determine it on our own
102 if ( !origin_x.first || !origin_y.first || !origin_z.first)
103 {
104 // determine origin
105 const BoundingBox b_box = MeshTools::create_bounding_box(_mesh);
106 const Point auto_origin = (b_box.first+b_box.second)/2;
107
108 // override default values, if necessary
109 if (!origin_x.first)
110 origin(0) = auto_origin(0);
111 #if LIBMESH_DIM > 1
112 if (!origin_y.first)
113 origin(1) = auto_origin(1);
114 #endif
115 #if LIBMESH_DIM > 2
116 if (!origin_z.first)
117 origin(2) = auto_origin(2);
118 #endif
119
120 if (be_verbose)
121 {
122 libMesh::out << " Origin for Infinite Elements:" << std::endl;
123
124 if (!origin_x.first)
125 libMesh::out << " determined x-coordinate" << std::endl;
126 if (!origin_y.first)
127 libMesh::out << " determined y-coordinate" << std::endl;
128 if (!origin_z.first)
129 libMesh::out << " determined z-coordinate" << std::endl;
130
131 libMesh::out << " coordinates: ";
132 origin.write_unformatted(libMesh::out);
133 libMesh::out << std::endl;
134 }
135 }
136
137 else if (be_verbose)
138
139 {
140 libMesh::out << " Origin for Infinite Elements:" << std::endl;
141 libMesh::out << " coordinates: ";
142 origin.write_unformatted(libMesh::out);
143 libMesh::out << std::endl;
144 }
145
146
147
148 // Now that we have the origin, check if the user provided an \p
149 // inner_boundary_nodes. If so, we pass a std::set to the actual
150 // implementation of the build_inf_elem(), so that we can convert
151 // this to the Node * vector
152 if (inner_boundary_nodes != nullptr)
153 {
154 // note that the std::set that we will get
155 // from build_inf_elem() uses the index of
156 // the element in this->_elements vector,
157 // and the second entry is the side index
158 // for this element. Therefore, we do _not_
159 // need to renumber nodes and elements
160 // prior to building the infinite elements.
161 //
162 // However, note that this method here uses
163 // node id's... Do we need to renumber?
164
165
166 // Form the list of faces of elements which finally
167 // will tell us which nodes should receive boundary
168 // conditions (to form the std::vector<const Node *>)
169 std::set<std::pair<dof_id_type,
170 unsigned int>> inner_faces;
171
172
173 // build infinite elements
174 this->build_inf_elem(origin,
175 x_sym, y_sym, z_sym,
176 be_verbose,
177 &inner_faces);
178
179 if (be_verbose)
180 {
181 this->_mesh.print_info();
182 libMesh::out << "Data pre-processing:" << std::endl
183 << " convert the <int,int> list to a Node * list..."
184 << std::endl;
185 }
186
187 // First use a std::vector<dof_id_type> that holds
188 // the global node numbers. Then sort this vector,
189 // so that it can be made unique (no multiple occurrence
190 // of a node), and then finally insert the Node * in
191 // the vector inner_boundary_nodes.
192 //
193 // Reserve memory for the vector<> with
194 // 4 times the size of the number of elements in the
195 // std::set. This is a good bet for Quad4 face elements.
196 // For higher-order elements, this probably _has_ to lead
197 // to additional allocations...
198 // Practice has to show how this affects performance.
199 std::vector<dof_id_type> inner_boundary_node_numbers;
200 inner_boundary_node_numbers.reserve(4*inner_faces.size());
201
202 // Now transform the set of pairs to a list of (possibly
203 // duplicate) global node numbers.
204 for (const auto & p : inner_faces)
205 {
206 // build a full-ordered side element to get _all_ the base nodes
207 std::unique_ptr<Elem> side(this->_mesh.elem_ref(p.first).build_side_ptr(p.second));
208
209 // insert all the node numbers in inner_boundary_node_numbers
210 for (const Node & node : side->node_ref_range())
211 inner_boundary_node_numbers.push_back(node.id());
212 }
213
214
215 // inner_boundary_node_numbers now still holds multiple entries of
216 // node numbers. So first sort, then unique the vector.
217 // Note that \p std::unique only puts the new ones in
218 // front, while to leftovers are not deleted. Instead,
219 // it returns a pointer to the end of the unique range.
220 //TODO:[BSK] int_ibn_size_before is not the same type as unique_size!
221 #ifndef NDEBUG
222 const std::size_t ibn_size_before = inner_boundary_node_numbers.size();
223 #endif
224 std::sort (inner_boundary_node_numbers.begin(), inner_boundary_node_numbers.end());
225 auto unique_end =
226 std::unique (inner_boundary_node_numbers.begin(), inner_boundary_node_numbers.end());
227
228 std::size_t unique_size = std::distance(inner_boundary_node_numbers.begin(), unique_end);
229 libmesh_assert_less_equal (unique_size, ibn_size_before);
230
231 // Finally, create const Node * in the inner_boundary_nodes
232 // vector. Reserve, not resize (otherwise, the push_back
233 // would append the interesting nodes, while nullptr-nodes
234 // live in the resize'd area...
235 inner_boundary_nodes->reserve (unique_size);
236 inner_boundary_nodes->clear();
237
238 for (const auto & dof : as_range(inner_boundary_node_numbers.begin(), unique_end))
239 {
240 const Node * node = this->_mesh.node_ptr(dof);
241 inner_boundary_nodes->push_back(node);
242 }
243
244 if (be_verbose)
245 libMesh::out << " finished identifying " << unique_size
246 << " target nodes." << std::endl;
247 }
248
249 else
250
251 {
252 // There are no inner boundary nodes, so simply build the infinite elements
253 this->build_inf_elem(origin, x_sym, y_sym, z_sym, be_verbose);
254 }
255
256
257 STOP_LOG("build_inf_elem()", "InfElemBuilder");
258
259 // when finished with building the Ifems,
260 // it remains to prepare the mesh for use:
261 // find neighbors again, partition (if needed)...
262 this->_mesh.prepare_for_use ();
263
264 return origin;
265 }
266
267
268
269
270
271
272
273
274
275 // The actual implementation of building elements.
build_inf_elem(const Point & origin,const bool x_sym,const bool y_sym,const bool z_sym,const bool be_verbose,std::set<std::pair<dof_id_type,unsigned int>> * inner_faces)276 void InfElemBuilder::build_inf_elem(const Point & origin,
277 const bool x_sym,
278 const bool y_sym,
279 const bool z_sym,
280 const bool be_verbose,
281 std::set<std::pair<dof_id_type,
282 unsigned int>> * inner_faces)
283 {
284 if (be_verbose)
285 {
286 #ifdef DEBUG
287 libMesh::out << " Building Infinite Elements:" << std::endl;
288 libMesh::out << " updating element neighbor tables..." << std::endl;
289 #else
290 libMesh::out << " Verbose mode disabled in non-debug mode." << std::endl;
291 #endif
292 }
293
294
295 // update element neighbors
296 this->_mesh.find_neighbors();
297
298 LOG_SCOPE("build_inf_elem()", "InfElemBuilder");
299
300 // A set for storing element number, side number pairs.
301 // pair.first == element number, pair.second == side number
302 std::set<std::pair<dof_id_type,unsigned int>> faces;
303 std::set<std::pair<dof_id_type,unsigned int>> ofaces;
304
305 // A set for storing node numbers on the outer faces.
306 std::set<dof_id_type> onodes;
307
308 // The distance to the farthest point in the mesh from the origin
309 Real max_r=0.;
310
311 // The index of the farthest point in the mesh from the origin
312 int max_r_node = -1;
313
314 #ifdef DEBUG
315 if (be_verbose)
316 {
317 libMesh::out << " collecting boundary sides";
318 if (x_sym || y_sym || z_sym)
319 libMesh::out << ", skipping sides in symmetry planes..." << std::endl;
320 else
321 libMesh::out << "..." << std::endl;
322 }
323 #endif
324
325 // Iterate through all elements and sides, collect indices of all active
326 // boundary sides in the faces set. Skip sides which lie in symmetry planes.
327 // Later, sides of the inner boundary will be sorted out.
328 for (const auto & elem : _mesh.active_element_ptr_range())
329 for (auto s : elem->side_index_range())
330 if (elem->neighbor_ptr(s) == nullptr)
331 {
332 // note that it is safe to use the Elem::side() method,
333 // which gives a non-full-ordered element
334 std::unique_ptr<Elem> side(elem->build_side_ptr(s));
335
336 // bool flags for symmetry detection
337 bool sym_side=false;
338 bool on_x_sym=true;
339 bool on_y_sym=true;
340 bool on_z_sym=true;
341
342
343 // Loop over the nodes to check whether they are on the symmetry planes,
344 // and therefore sufficient to use a non-full-ordered side element
345 for (const Node & node : side->node_ref_range())
346 {
347 const dof_id_type node_id = node.id();
348 const Point dist_from_origin =
349 this->_mesh.point(node_id) - origin;
350
351 if (x_sym)
352 if (std::abs(dist_from_origin(0)) > 1.e-3)
353 on_x_sym=false;
354
355 if (y_sym)
356 if (std::abs(dist_from_origin(1)) > 1.e-3)
357 on_y_sym=false;
358
359 if (z_sym)
360 if (std::abs(dist_from_origin(2)) > 1.e-3)
361 on_z_sym=false;
362
363 // if (x_sym)
364 // if (std::abs(dist_from_origin(0)) > 1.e-6)
365 // on_x_sym=false;
366
367 // if (y_sym)
368 // if (std::abs(dist_from_origin(1)) > 1.e-6)
369 // on_y_sym=false;
370
371 // if (z_sym)
372 // if (std::abs(dist_from_origin(2)) > 1.e-6)
373 // on_z_sym=false;
374
375 //find the node most distant from origin
376
377 Real r = dist_from_origin.norm();
378 if (r > max_r)
379 {
380 max_r = r;
381 max_r_node=node_id;
382 }
383
384 }
385
386 sym_side = (x_sym && on_x_sym) || (y_sym && on_y_sym) || (z_sym && on_z_sym);
387
388 if (!sym_side)
389 faces.emplace(elem->id(), s);
390
391 } // neighbor(s) == nullptr
392
393
394
395
396
397
398 // If a boundary side has one node on the outer boundary,
399 // all points of this side are on the outer boundary.
400 // Start with the node most distant from origin, which has
401 // to be on the outer boundary, then recursively find all
402 // sides and nodes connected to it. Found sides are moved
403 // from faces to ofaces, nodes are collected in onodes.
404 // Here, the search is done iteratively, because, depending on
405 // the mesh, a very high level of recursion might be necessary.
406 if (max_r_node >= 0)
407 // include the possibility of the 1st element being most far away.
408 // Only the case of no outer boundary is to be excluded.
409 onodes.insert(max_r_node);
410
411
412 {
413 auto face_it = faces.begin();
414 auto face_end = faces.end();
415 unsigned int facesfound=0;
416 while (face_it != face_end) {
417 std::pair<dof_id_type, unsigned int> p = *face_it;
418
419 // This has to be a full-ordered side element,
420 // since we need the correct n_nodes,
421 std::unique_ptr<Elem> side(this->_mesh.elem_ref(p.first).build_side_ptr(p.second));
422
423 bool found=false;
424 for (const Node & node : side->node_ref_range())
425 if (onodes.count(node.id()))
426 {
427 found=true;
428 break;
429 }
430
431 // If a new oface is found, include its nodes in onodes
432 if (found)
433 {
434 for (const Node & node : side->node_ref_range())
435 onodes.insert(node.id());
436
437 ofaces.insert(p);
438 face_it = faces.erase(face_it); // increment is done here
439
440 facesfound++;
441 }
442
443 else
444 ++face_it; // increment is done here
445
446 // If at least one new oface was found in this cycle,
447 // do another search cycle.
448 if (facesfound>0 && face_it == faces.end())
449 {
450 facesfound = 0;
451 face_it = faces.begin();
452 }
453 }
454 }
455
456
457 #ifdef DEBUG
458 if (be_verbose)
459 libMesh::out << " found "
460 << faces.size()
461 << " inner and "
462 << ofaces.size()
463 << " outer boundary faces"
464 << std::endl;
465 #endif
466
467 // When the user provided a non-null pointer to
468 // inner_faces, that implies he wants to have
469 // this std::set. For now, simply copy the data.
470 if (inner_faces != nullptr)
471 *inner_faces = faces;
472
473 // free memory, clear our local variable, no need
474 // for it any more.
475 faces.clear();
476
477
478 // outer_nodes maps onodes to their duplicates
479 std::map<dof_id_type, Node *> outer_nodes;
480
481 // We may need to pick our own object ids in parallel
482 dof_id_type old_max_node_id = _mesh.max_node_id();
483 dof_id_type old_max_elem_id = _mesh.max_elem_id();
484
485 // Likewise with our unique_ids
486 #ifdef LIBMESH_ENABLE_UNIQUE_ID
487 unique_id_type old_max_unique_id = _mesh.parallel_max_unique_id();
488 #endif
489
490 // for each boundary node, add an outer_node with
491 // double distance from origin.
492 for (const auto & dof : onodes)
493 {
494 Point p = (Point(this->_mesh.point(dof)) * 2) - origin;
495 if (_mesh.is_serial())
496 {
497 // Add with a default id in serial
498 outer_nodes[dof]=this->_mesh.add_point(p);
499 }
500 else
501 {
502 // Pick a unique id in parallel
503 Node & bnode = _mesh.node_ref(dof);
504 dof_id_type new_id = bnode.id() + old_max_node_id;
505 std::unique_ptr<Node> new_node = Node::build(p, new_id);
506 new_node->processor_id() = bnode.processor_id();
507 #ifdef LIBMESH_ENABLE_UNIQUE_ID
508 new_node->set_unique_id(old_max_unique_id + bnode.id());
509 #endif
510
511 outer_nodes[dof] =
512 this->_mesh.add_node(std::move(new_node));
513 }
514 }
515
516
517 #ifdef DEBUG
518 // for verbose, remember n_elem
519 dof_id_type n_conventional_elem = this->_mesh.n_elem();
520 #endif
521
522
523 // build Elems based on boundary side type
524 for (auto & p : ofaces)
525 {
526 Elem & belem = this->_mesh.elem_ref(p.first);
527
528 // build a full-ordered side element to get the base nodes
529 std::unique_ptr<Elem> side(belem.build_side_ptr(p.second));
530
531 // create cell depending on side type, assign nodes,
532 // use braces to force scope.
533 bool is_higher_order_elem = false;
534
535 std::unique_ptr<Elem> el;
536 switch(side->type())
537 {
538 // 3D infinite elements
539 // TRIs
540 case TRI3:
541 el = Elem::build(INFPRISM6);
542 break;
543
544 case TRI6:
545 el = Elem::build(INFPRISM12);
546 is_higher_order_elem = true;
547 break;
548
549 // QUADs
550 case QUAD4:
551 el = Elem::build(INFHEX8);
552 break;
553
554 case QUAD8:
555 el = Elem::build(INFHEX16);
556 is_higher_order_elem = true;
557 break;
558
559 case QUAD9:
560 el = Elem::build(INFHEX18);
561
562 // the method of assigning nodes (which follows below)
563 // omits in the case of QUAD9 the bubble node; therefore
564 // we assign these first by hand here.
565 el->set_node(16) = side->node_ptr(8);
566 el->set_node(17) = outer_nodes[side->node_id(8)];
567 is_higher_order_elem=true;
568 break;
569
570 // 2D infinite elements
571 case EDGE2:
572 el = Elem::build(INFQUAD4);
573 break;
574
575 case EDGE3:
576 el = Elem::build(INFQUAD6);
577 el->set_node(4) = side->node_ptr(2);
578 break;
579
580 // 1D infinite elements not supported
581 default:
582 libMesh::out << "InfElemBuilder::build_inf_elem(Point, bool, bool, bool, bool): "
583 << "invalid face element "
584 << std::endl;
585 continue;
586 }
587
588 const unsigned int n_base_vertices = side->n_vertices();
589
590 // On a distributed mesh, manually assign unique ids to the new
591 // element, and make sure any RemoteElem neighbor links are set.
592 if (!_mesh.is_serial())
593 {
594 el->processor_id() = belem.processor_id();
595
596 // We'd better not have elements with more than 6 sides
597 const unsigned int max_sides = 6;
598 libmesh_assert_less_equal(el->n_sides(), max_sides);
599 el->set_id (belem.id() * max_sides + p.second + old_max_elem_id);
600
601 #ifdef LIBMESH_ENABLE_UNIQUE_ID
602 el->set_unique_id(old_max_unique_id + old_max_node_id +
603 belem.id() * max_sides + p.second);
604 #endif
605
606 // If we have a remote neighbor on a boundary element side
607 if (belem.dim() > 1)
608 for (auto s : belem.side_index_range())
609 if (belem.neighbor_ptr(s) == remote_elem)
610 {
611 // Find any corresponding infinite element side
612 std::unique_ptr<const Elem> remote_side(belem.build_side_ptr(s));
613
614 for (auto inf_s : el->side_index_range())
615 {
616 // The base side 0 shares all vertices but isn't
617 // remote
618 if (!inf_s)
619 continue;
620
621 // But another side, one which shares enough
622 // vertices to show it's the same side, is.
623 unsigned int n_shared_vertices = 0;
624 for (unsigned int i = 0; i != n_base_vertices; ++i)
625 for (auto & node : remote_side->node_ref_range())
626 if (side->node_ptr(i) == &node &&
627 el->is_node_on_side(i,inf_s))
628 ++n_shared_vertices;
629
630 if (n_shared_vertices + 1 >= belem.dim())
631 {
632 el->set_neighbor
633 (inf_s, const_cast<RemoteElem *>(remote_elem));
634 break;
635 }
636 }
637 }
638 }
639
640 // assign vertices to the new infinite element
641 for (unsigned int i=0; i<n_base_vertices; i++)
642 {
643 el->set_node(i ) = side->node_ptr(i);
644 el->set_node(i+n_base_vertices) = outer_nodes[side->node_id(i)];
645 }
646
647
648 // when this is a higher order element,
649 // assign also the nodes in between
650 if (is_higher_order_elem)
651 {
652 // n_safe_base_nodes is the number of nodes in \p side
653 // that may be safely assigned using below for loop.
654 // Actually, n_safe_base_nodes is _identical_ with el->n_vertices(),
655 // since for QUAD9, the 9th node was already assigned above
656 const unsigned int n_safe_base_nodes = el->n_vertices();
657
658 for (unsigned int i=n_base_vertices; i<n_safe_base_nodes; i++)
659 {
660 el->set_node(i+n_base_vertices) = side->node_ptr(i);
661 el->set_node(i+n_safe_base_nodes) =
662 outer_nodes[side->node_id(i)];
663 }
664 }
665
666
667 // add infinite element to mesh
668 this->_mesh.add_elem(std::move(el));
669 } // for
670
671
672 #ifdef DEBUG
673 _mesh.libmesh_assert_valid_parallel_ids();
674
675 if (be_verbose)
676 libMesh::out << " added "
677 << this->_mesh.n_elem() - n_conventional_elem
678 << " infinite elements and "
679 << onodes.size()
680 << " nodes to the mesh"
681 << std::endl
682 << std::endl;
683 #endif
684 }
685
686 } // namespace libMesh
687
688
689
690
691
692 #endif // LIBMESH_ENABLE_INFINITE_ELEMENTS
693