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