parallel_ghost_sync.h
Go to the documentation of this file.
1 // The libMesh Finite Element Library.
2 // Copyright (C) 2002-2018 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 #ifndef LIBMESH_PARALLEL_GHOST_SYNC_H
21 #define LIBMESH_PARALLEL_GHOST_SYNC_H
22 
23 // Local Includes
24 #include "libmesh/auto_ptr.h" // deprecated
25 #include "libmesh/elem.h"
26 #include "libmesh/location_maps.h"
27 #include "libmesh/mesh_base.h"
28 #include "libmesh/parallel.h"
30 #include "libmesh/parallel_sync.h"
31 
32 
33 namespace libMesh
34 {
35 
36 
37 
38 //--------------------------------------------------------------------------
39 namespace Parallel {
40 
41 //------------------------------------------------------------------------
58 template <typename Iterator,
59  typename DofObjType,
60  typename SyncFunctor>
61 void sync_dofobject_data_by_xyz(const Communicator & comm,
62  const Iterator & range_begin,
63  const Iterator & range_end,
64  LocationMap<DofObjType> * location_map,
65  SyncFunctor & sync);
66 
67 //------------------------------------------------------------------------
80 template <typename Iterator,
81  typename SyncFunctor>
82 void sync_dofobject_data_by_id(const Communicator & comm,
83  const Iterator & range_begin,
84  const Iterator & range_end,
85  SyncFunctor & sync);
86 
94 template <typename Iterator,
95  typename DofObjectCheckFunctor,
96  typename SyncFunctor>
97 void sync_dofobject_data_by_id(const Communicator & comm,
98  const Iterator & range_begin,
99  const Iterator & range_end,
100  const DofObjectCheckFunctor & dofobj_check,
101  SyncFunctor & sync);
102 
103 //------------------------------------------------------------------------
117 template <typename Iterator,
118  typename SyncFunctor>
119 void sync_element_data_by_parent_id(MeshBase & mesh,
120  const Iterator & range_begin,
121  const Iterator & range_end,
122  SyncFunctor & sync);
123 
124 //------------------------------------------------------------------------
154 template <typename ElemCheckFunctor,
155  typename NodeCheckFunctor,
156  typename SyncFunctor>
158  const MeshBase::const_element_iterator & range_begin,
159  const MeshBase::const_element_iterator & range_end,
160  const ElemCheckFunctor & elem_check,
161  const NodeCheckFunctor & node_check,
162  SyncFunctor & sync);
163 
164 
165 
166 //------------------------------------------------------------------------
201 template <typename ElemCheckFunctor,
202  typename NodeCheckFunctor,
203  typename SyncFunctor>
204 void sync_node_data_by_element_id(MeshBase & mesh,
205  const MeshBase::const_element_iterator & range_begin,
206  const MeshBase::const_element_iterator & range_end,
207  const ElemCheckFunctor & elem_check,
208  const NodeCheckFunctor & node_check,
209  SyncFunctor & sync);
210 
211 
212 //------------------------------------------------------------------------
213 // Parallel members
214 
215 
216 // "Check" Functor to perform sync operations with no exclusions
218 {
220 
221  bool operator() (const DofObject *) const { return true; }
222 
223  bool operator() (const Elem *, unsigned int) const
224  { return true; }
225 };
226 
227 
228 
229 template <typename Iterator,
230  typename DofObjType,
231  typename SyncFunctor>
233  const Iterator & range_begin,
234  const Iterator & range_end,
235  LocationMap<DofObjType> & location_map,
236  SyncFunctor & sync)
237 {
238  // This function must be run on all processors at once
239  libmesh_parallel_only(comm);
240 
241  // We need a valid location_map
242 #ifdef DEBUG
243  bool need_map_update = (range_begin != range_end && location_map.empty());
244  comm.max(need_map_update);
245  libmesh_assert(!need_map_update);
246 #endif
247 
248  // Count the objects to ask each processor about
249  std::vector<dof_id_type>
250  ghost_objects_from_proc(comm.size(), 0);
251 
252  for (Iterator it = range_begin; it != range_end; ++it)
253  {
254  DofObjType * obj = *it;
255  libmesh_assert (obj);
256  processor_id_type obj_procid = obj->processor_id();
257  if (obj_procid != DofObject::invalid_processor_id)
258  ghost_objects_from_proc[obj_procid]++;
259  }
260 
261  // Request sets to send to each processor
262  std::map<processor_id_type, std::vector<Point>>
263  requested_objs_pt;
264  // Corresponding ids to keep track of
265  std::map<processor_id_type, std::vector<dof_id_type>>
266  requested_objs_id;
267 
268  // We know how many objects live on each processor, so reserve()
269  // space for each.
270  for (processor_id_type p=0; p != comm.size(); ++p)
271  if (p != comm.rank() && ghost_objects_from_proc[p])
272  {
273  requested_objs_pt[p].reserve(ghost_objects_from_proc[p]);
274  requested_objs_id[p].reserve(ghost_objects_from_proc[p]);
275  }
276 
277  for (Iterator it = range_begin; it != range_end; ++it)
278  {
279  DofObjType * obj = *it;
280  processor_id_type obj_procid = obj->processor_id();
281  if (obj_procid == comm.rank() ||
282  obj_procid == DofObject::invalid_processor_id)
283  continue;
284 
285  Point p = location_map.point_of(*obj);
286  requested_objs_pt[obj_procid].push_back(p);
287  requested_objs_id[obj_procid].push_back(obj->id());
288  }
289 
290  auto gather_functor =
291  [&location_map, &sync]
292  (processor_id_type /*pid*/, const std::vector<Point> & pts,
293  std::vector<typename SyncFunctor::datum> & data)
294  {
295  // Find the local id of each requested object
296  std::size_t query_size = pts.size();
297  std::vector<dof_id_type> query_id(query_size);
298  for (std::size_t i=0; i != query_size; ++i)
299  {
300  Point pt = pts[i];
301 
302  // Look for this object in the multimap
303  DofObjType * obj = location_map.find(pt);
304 
305  // We'd better find every object we're asked for
306  libmesh_assert (obj);
307 
308  // Return the object's correct processor id,
309  // and our (correct if it's local) id for it.
310  query_id[i] = obj->id();
311  }
312 
313  // Gather whatever data the user wants
314  sync.gather_data(query_id, data);
315  };
316 
317  auto action_functor =
318  [&sync, &requested_objs_id]
319  (processor_id_type pid, const std::vector<Point> &,
320  const std::vector<typename SyncFunctor::datum> & data)
321  {
322  // Let the user process the results
323  sync.act_on_data(requested_objs_id[pid], data);
324  };
325 
326  // Trade requests with other processors
327  typename SyncFunctor::datum * ex = nullptr;
329  (comm, requested_objs_pt, gather_functor, action_functor, ex);
330 }
331 
332 
333 
334 template <typename Iterator,
335  typename SyncFunctor>
337  const Iterator & range_begin,
338  const Iterator & range_end,
339  SyncFunctor & sync)
340 {
341  sync_dofobject_data_by_id(comm, range_begin, range_end, SyncEverything(), sync);
342 }
343 
344 template <typename Iterator,
345  typename DofObjectCheckFunctor,
346  typename SyncFunctor>
348  const Iterator & range_begin,
349  const Iterator & range_end,
350  const DofObjectCheckFunctor & dofobj_check,
351  SyncFunctor & sync)
352 {
353  // This function must be run on all processors at once
354  libmesh_parallel_only(comm);
355 
356  // Count the objects to ask each processor about
357  std::vector<dof_id_type>
358  ghost_objects_from_proc(comm.size(), 0);
359 
360  for (Iterator it = range_begin; it != range_end; ++it)
361  {
362  DofObject * obj = *it;
363  libmesh_assert (obj);
364 
365  // We may want to pass Elem* or Node* to the check function, not
366  // just DofObject*
367  if (!dofobj_check(*it))
368  continue;
369 
370  processor_id_type obj_procid = obj->processor_id();
371  if (obj_procid != DofObject::invalid_processor_id)
372  ghost_objects_from_proc[obj_procid]++;
373  }
374 
375  // Request sets to send to each processor
376  std::map<processor_id_type, std::vector<dof_id_type>>
377  requested_objs_id;
378 
379  // We know how many objects live on each processor, so reserve()
380  // space for each.
381  for (processor_id_type p=0; p != comm.size(); ++p)
382  if (p != comm.rank() && ghost_objects_from_proc[p])
383  requested_objs_id[p].reserve(ghost_objects_from_proc[p]);
384 
385  for (Iterator it = range_begin; it != range_end; ++it)
386  {
387  DofObject * obj = *it;
388 
389  if (!dofobj_check(*it))
390  continue;
391 
392  processor_id_type obj_procid = obj->processor_id();
393  if (obj_procid == comm.rank() ||
394  obj_procid == DofObject::invalid_processor_id)
395  continue;
396 
397  requested_objs_id[obj_procid].push_back(obj->id());
398  }
399 
400  auto gather_functor =
401  [&sync]
402  (processor_id_type, const std::vector<dof_id_type> & ids,
403  std::vector<typename SyncFunctor::datum> & data)
404  {
405  sync.gather_data(ids, data);
406  };
407 
408  auto action_functor =
409  [&sync]
410  (processor_id_type, const std::vector<dof_id_type> & ids,
411  const std::vector<typename SyncFunctor::datum> & data)
412  {
413  // Let the user process the results
414  sync.act_on_data(ids, data);
415  };
416 
417  // Trade requests with other processors
418  typename SyncFunctor::datum * ex = nullptr;
420  (comm, requested_objs_id, gather_functor, action_functor, ex);
421 }
422 
423 
424 
425 // If there's no refined elements, there's nothing to sync
426 #ifdef LIBMESH_ENABLE_AMR
427 template <typename Iterator,
428  typename SyncFunctor>
430  const Iterator & range_begin,
431  const Iterator & range_end,
432  SyncFunctor & sync)
433 {
434  const Communicator & comm (mesh.comm());
435 
436  // This function must be run on all processors at once
437  libmesh_parallel_only(comm);
438 
439  // Count the objects to ask each processor about
440  std::vector<dof_id_type>
441  ghost_objects_from_proc(comm.size(), 0);
442 
443  for (Iterator it = range_begin; it != range_end; ++it)
444  {
445  Elem * elem = *it;
446  processor_id_type obj_procid = elem->processor_id();
447  if (obj_procid == comm.rank() ||
448  obj_procid == DofObject::invalid_processor_id)
449  continue;
450  const Elem * parent = elem->parent();
451  if (!parent || !elem->active())
452  continue;
453 
454  ghost_objects_from_proc[obj_procid]++;
455  }
456 
457  // Request sets to send to each processor
458  std::map<processor_id_type, std::vector<dof_id_type>>
459  requested_objs_id;
460  std::map<processor_id_type, std::vector<std::pair<dof_id_type,unsigned char>>>
461  requested_objs_parent_id_child_num;
462 
463  // We know how many objects live on each processor, so reserve()
464  // space for each.
465  for (processor_id_type p=0; p != comm.size(); ++p)
466  if (p != comm.rank() && ghost_objects_from_proc[p])
467  {
468  requested_objs_id[p].reserve(ghost_objects_from_proc[p]);
469  requested_objs_parent_id_child_num[p].reserve(ghost_objects_from_proc[p]);
470  }
471 
472  for (Iterator it = range_begin; it != range_end; ++it)
473  {
474  Elem * elem = *it;
475  processor_id_type obj_procid = elem->processor_id();
476  if (obj_procid == comm.rank() ||
477  obj_procid == DofObject::invalid_processor_id)
478  continue;
479  const Elem * parent = elem->parent();
480  if (!parent || !elem->active())
481  continue;
482 
483  requested_objs_id[obj_procid].push_back(elem->id());
484  requested_objs_parent_id_child_num[obj_procid].push_back
485  (std::make_pair
486  (parent->id(),
487  cast_int<unsigned char>
488  (parent->which_child_am_i(elem))));
489  }
490 
491  auto gather_functor =
492  [&mesh, &sync]
494  const std::vector<std::pair<dof_id_type, unsigned char>> & parent_id_child_num,
495  std::vector<typename SyncFunctor::datum> & data)
496  {
497  // Find the id of each requested element
498  std::size_t query_size = parent_id_child_num.size();
499  std::vector<dof_id_type> query_id(query_size);
500  for (std::size_t i=0; i != query_size; ++i)
501  {
502  Elem & parent = mesh.elem_ref(parent_id_child_num[i].first);
503  libmesh_assert(parent.has_children());
504  Elem * child = parent.child_ptr(parent_id_child_num[i].second);
505  libmesh_assert(child);
506  libmesh_assert(child->active());
507  query_id[i] = child->id();
508  }
509 
510  // Gather whatever data the user wants
511  sync.gather_data(query_id, data);
512  };
513 
514  auto action_functor =
515  [&sync, &requested_objs_id]
516  (processor_id_type pid,
517  const std::vector<std::pair<dof_id_type, unsigned char>> &,
518  const std::vector<typename SyncFunctor::datum> & data)
519  {
520  // Let the user process the results
521  sync.act_on_data(requested_objs_id[pid], data);
522  };
523 
524  // Trade requests with other processors
525  typename SyncFunctor::datum * ex = nullptr;
527  (comm, requested_objs_parent_id_child_num, gather_functor,
528  action_functor, ex);
529 }
530 #else
531 template <typename Iterator,
532  typename SyncFunctor>
534  const Iterator &,
535  const Iterator &,
536  SyncFunctor &)
537 {
538 }
539 #endif // LIBMESH_ENABLE_AMR
540 
541 
542 
543 template <typename ElemCheckFunctor,
544  typename NodeCheckFunctor,
545  typename SyncFunctor>
547  const MeshBase::const_element_iterator & range_begin,
548  const MeshBase::const_element_iterator & range_end,
549  const ElemCheckFunctor & elem_check,
550  const NodeCheckFunctor & node_check,
551  SyncFunctor & sync)
552 {
553  const Communicator & comm (mesh.comm());
554 
555  // Count the objects to ask each processor about
556  std::vector<dof_id_type>
557  ghost_objects_from_proc(comm.size(), 0);
558 
559  for (const auto & elem : as_range(range_begin, range_end))
560  {
561  libmesh_assert (elem);
562 
563  if (!elem_check(elem))
564  continue;
565 
566  const processor_id_type proc_id = elem->processor_id();
567 
568  bool i_have_elem =
569  (proc_id == comm.rank() ||
571 
572  if (elem->active() && i_have_elem)
573  continue;
574 
575  for (auto n : elem->node_index_range())
576  {
577  if (!node_check(elem, n))
578  continue;
579 
580  const processor_id_type node_pid =
581  elem->node_ref(n).processor_id();
582 
583  if (i_have_elem && (node_pid == comm.rank()))
584  continue;
585 
586  if (i_have_elem)
587  {
588  libmesh_assert_not_equal_to
590  ghost_objects_from_proc[node_pid]++;
591  }
592  else
593  {
594  const processor_id_type request_pid =
595  (node_pid == DofObject::invalid_processor_id) ?
596  proc_id : node_pid;
597  ghost_objects_from_proc[request_pid]++;
598  }
599  }
600  }
601 
602  // Now repeat that iteration, filling request sets this time.
603 
604  // Request sets to send to each processor
605  std::map<processor_id_type, std::vector<std::pair<dof_id_type, unsigned char>>>
606  requested_objs_elem_id_node_num;
607 
608  // We know how many objects live on each processor, so reserve()
609  // space for each.
610  for (processor_id_type p=0; p != comm.size(); ++p)
611  if (p != comm.rank() && ghost_objects_from_proc[p])
612  {
613  requested_objs_elem_id_node_num[p].reserve(ghost_objects_from_proc[p]);
614  }
615 
616  for (const auto & elem : as_range(range_begin, range_end))
617  {
618  libmesh_assert (elem);
619 
620  if (!elem_check(elem))
621  continue;
622 
623  const processor_id_type proc_id = elem->processor_id();
624 
625  bool i_have_elem =
626  (proc_id == comm.rank() ||
628 
629  if (elem->active() && i_have_elem)
630  continue;
631 
632  const dof_id_type elem_id = elem->id();
633 
634  for (auto n : elem->node_index_range())
635  {
636  if (!node_check(elem, n))
637  continue;
638 
639  const Node & node = elem->node_ref(n);
640  const processor_id_type node_pid = node.processor_id();
641 
642  if (i_have_elem && (node_pid == comm.rank()))
643  continue;
644 
645  if (i_have_elem)
646  {
647  libmesh_assert_not_equal_to
649  requested_objs_elem_id_node_num[node_pid].push_back
650  (std::make_pair
651  (elem_id,
652  cast_int<unsigned char>(n)));
653  }
654  else
655  {
656  const processor_id_type request_pid =
657  (node_pid == DofObject::invalid_processor_id) ?
658  proc_id : node_pid;
659  requested_objs_elem_id_node_num[request_pid].push_back
660  (std::make_pair
661  (elem_id,
662  cast_int<unsigned char>(n)));
663  }
664  }
665  }
666 
667  auto gather_functor =
668  [&mesh, &sync]
670  const std::vector<std::pair<dof_id_type, unsigned char>> & elem_id_node_num,
671  std::vector<typename SyncFunctor::datum> & data)
672  {
673  // Find the id of each requested element
674  std::size_t request_size = elem_id_node_num.size();
675  std::vector<dof_id_type> query_id(request_size);
676  for (std::size_t i=0; i != request_size; ++i)
677  {
678  // We might now get queries about remote elements, in which
679  // case we'll have to ignore them and wait for the query
680  // answer to filter to the querier via another source.
681  const Elem * elem = mesh.query_elem_ptr(elem_id_node_num[i].first);
682 
683  if (elem)
684  {
685  const unsigned int n = elem_id_node_num[i].second;
686  libmesh_assert_less (n, elem->n_nodes());
687 
688  const Node & node = elem->node_ref(n);
689 
690  // This isn't a safe assertion in the case where we're
691  // syncing processor ids
692  // libmesh_assert_equal_to (node->processor_id(), comm.rank());
693 
694  query_id[i] = node.id();
695  }
696  else
697  query_id[i] = DofObject::invalid_id;
698  }
699 
700  // Gather whatever data the user wants
701  sync.gather_data(query_id, data);
702  };
703 
704  bool data_changed = false;
705 
706  auto action_functor =
707  [&sync, &mesh, &requested_objs_elem_id_node_num, &data_changed]
708  (processor_id_type pid,
709  const std::vector<std::pair<dof_id_type, unsigned char>> &,
710  const std::vector<typename SyncFunctor::datum> & data)
711  {
712  const auto & elem_id_node_num =
713  requested_objs_elem_id_node_num[pid];
714 
715  const std::size_t data_size = data.size();
716 
717  libmesh_assert_equal_to(elem_id_node_num.size(), data_size);
718 
719  std::vector<dof_id_type> requested_objs_id(data.size());
720 
721  for (auto i : IntRange<std::size_t>(0,data_size))
722  {
723  const Elem & elem = mesh.elem_ref(elem_id_node_num[i].first);
724  const Node & node = elem.node_ref(elem_id_node_num[i].second);
725  requested_objs_id[i] = node.id();
726  }
727 
728  // Let the user process the results. If any of the results
729  // were different than what the user expected, then we may
730  // need to sync again just in case this processor has to
731  // pass on the changes to yet another processor.
732  if (sync.act_on_data(requested_objs_id, data))
733  data_changed = true;
734  };
735 
736  // Trade requests with other processors
737  typename SyncFunctor::datum * ex = nullptr;
739  (comm, requested_objs_elem_id_node_num, gather_functor,
740  action_functor, ex);
741 
742  comm.max(data_changed);
743 
744  return data_changed;
745 }
746 
747 
748 
749 template <typename ElemCheckFunctor,
750  typename NodeCheckFunctor,
751  typename SyncFunctor>
753  const MeshBase::const_element_iterator & range_begin,
754  const MeshBase::const_element_iterator & range_end,
755  const ElemCheckFunctor & elem_check,
756  const NodeCheckFunctor & node_check,
757  SyncFunctor & sync)
758 {
759  // This function must be run on all processors at once
760  libmesh_parallel_only(mesh.comm());
761 
762  bool need_sync = false;
763 
764  do
765  {
766  need_sync =
768  (mesh, range_begin, range_end, elem_check, node_check,
769  sync);
770  } while (need_sync);
771 }
772 
773 
774 }
775 
776 
777 
778 // This struct can be created and passed to the
779 // Parallel::sync_dofobject_data_by_id() function.
781 {
782  // The constructor. You need a reference to the mesh where you will
783  // be setting/getting nodal positions.
784  explicit
786 
787  // The datum typedef is required of this functor, so that the
788  // Parallel::sync_dofobject_data_by_id() function can create e.g.
789  // std::vector<datum>.
790  typedef Point datum;
791 
792  // First required interface. This function must fill up the data vector for the
793  // ids specified in the ids vector.
794  void gather_data (const std::vector<dof_id_type> & ids, std::vector<datum> & data) const;
795 
796  // Second required interface. This function must do something with the data in
797  // the data vector for the ids in the ids vector.
798  void act_on_data (const std::vector<dof_id_type> & ids, const std::vector<datum> & data) const;
799 
801 };
802 
803 
804 } // namespace libMesh
805 
806 #endif // LIBMESH_PARALLEL_GHOST_SYNC_H
T * find(const Point &, const Real tol=TOLERANCE)
const Elem * parent() const
Definition: elem.h:2479
A geometric point in (x,y,z) space associated with a DOF.
Definition: node.h:52
processor_id_type size() const
Definition: communicator.h:175
The base class for all geometric element types.
Definition: elem.h:100
MeshBase & mesh
uint8_t processor_id_type
Definition: id_types.h:99
void sync_dofobject_data_by_xyz(const Communicator &comm, const Iterator &range_begin, const Iterator &range_end, LocationMap< DofObjType > *location_map, SyncFunctor &sync)
Point point_of(const T &) const
bool operator()(const DofObject *) const
Base class for Mesh.
Definition: mesh_base.h:77
void act_on_data(const std::vector< dof_id_type > &ids, const std::vector< datum > &data) const
const Node & node_ref(const unsigned int i) const
Definition: elem.h:1979
dof_id_type id() const
Definition: dof_object.h:655
processor_id_type rank() const
Definition: communicator.h:173
void pull_parallel_vector_data(const Communicator &comm, const MapToVectors &queries, RequestContainer &reqs, GatherFunctor &gather_data, ActionFunctor &act_on_data, const datum *example)
virtual unsigned int n_nodes() const =0
static const processor_id_type invalid_processor_id
Definition: dof_object.h:358
void sync_node_data_by_element_id(MeshBase &mesh, const MeshBase::const_element_iterator &range_begin, const MeshBase::const_element_iterator &range_end, const ElemCheckFunctor &elem_check, const NodeCheckFunctor &node_check, SyncFunctor &sync)
SimpleRange< I > as_range(const std::pair< I, I > &p)
Definition: simple_range.h:57
static const dof_id_type invalid_id
Definition: dof_object.h:347
void sync_dofobject_data_by_id(const Communicator &comm, const Iterator &range_begin, const Iterator &range_end, SyncFunctor &sync)
std::map-like data structure using hashed Points for keys.
Definition: location_maps.h:53
unsigned int which_child_am_i(const Elem *e) const
Definition: elem.h:2620
bool sync_node_data_by_element_id_once(MeshBase &mesh, const MeshBase::const_element_iterator &range_begin, const MeshBase::const_element_iterator &range_end, const ElemCheckFunctor &elem_check, const NodeCheckFunctor &node_check, SyncFunctor &sync)
void gather_data(const std::vector< dof_id_type > &ids, std::vector< datum > &data) const
IterBase * data
bool active() const
Definition: elem.h:2390
processor_id_type processor_id() const
Definition: dof_object.h:717
A geometric point in (x,y,z) space.
Definition: point.h:38
bool has_children() const
Definition: elem.h:2428
void sync_element_data_by_parent_id(MeshBase &mesh, const Iterator &range_begin, const Iterator &range_end, SyncFunctor &sync)
bool empty() const
Definition: location_maps.h:63
const Elem * child_ptr(unsigned int i) const
Definition: elem.h:2578
uint8_t dof_id_type
Definition: id_types.h:64