MFEM v4.8.0
Finite element discretization library
Loading...
Searching...
No Matches
submesh_utils.cpp
Go to the documentation of this file.
1// Copyright (c) 2010-2025, Lawrence Livermore National Security, LLC. Produced
2// at the Lawrence Livermore National Laboratory. All Rights reserved. See files
3// LICENSE and NOTICE for details. LLNL-CODE-806117.
4//
5// This file is part of the MFEM library. For more information and source code
6// availability visit https://mfem.org.
7//
8// MFEM is free software; you can redistribute it and/or modify it under the
9// terms of the BSD-3 license. We welcome feedback and contributions, see file
10// CONTRIBUTING.md for details.
11
12#include "submesh_utils.hpp"
13#include "ncsubmesh.hpp"
14#include "submesh.hpp"
15#include "pncsubmesh.hpp"
16#include "psubmesh.hpp"
17
18#include <numeric>
19
20namespace mfem
21{
22namespace SubMeshUtils
23{
24int UniqueIndexGenerator::Get(int i, bool &new_index)
25{
26 auto f = idx.find(i);
27 if (f == idx.end())
28 {
29 idx[i] = counter;
30 new_index = true;
31 return counter++;
32 }
33 else
34 {
35 new_index = false;
36 return (*f).second;
37 }
38}
39
40template <typename ElementT>
41bool ElementHasAttribute(const ElementT &el, const Array<int> &attributes)
42{
43 for (int a = 0; a < attributes.Size(); a++)
44 {
45 if (el.GetAttribute() == attributes[a])
46 {
47 return true;
48 }
49 }
50 return false;
51}
52
53std::tuple< Array<int>, Array<int> >
54AddElementsToMesh(const Mesh& parent,
55 Mesh& mesh,
56 const Array<int> &attributes,
57 bool from_boundary)
58{
59 UniqueIndexGenerator vertex_ids;
60 Array<int> parent_vertex_ids, parent_element_ids;
61 Array<int> vert, submesh_vert;
62
63 const int ne = from_boundary ? parent.GetNBE() : parent.GetNE();
64 for (int i = 0; i < ne; i++)
65 {
66 const Element *pel = from_boundary ?
67 parent.GetBdrElement(i) : parent.GetElement(i);
68 if (!HasAttribute(*pel, attributes)) { continue; }
69 pel->GetVertices(vert);
70 submesh_vert.SetSize(vert.Size());
71 for (int iv = 0; iv < vert.Size(); iv++)
72 {
73 bool new_vertex;
74 int mesh_vertex_id = vert[iv];
75 int submesh_vertex_id = vertex_ids.Get(mesh_vertex_id, new_vertex);
76 if (new_vertex)
77 {
78 mesh.AddVertex(parent.GetVertex(mesh_vertex_id));
79 parent_vertex_ids.Append(mesh_vertex_id);
80 }
81 submesh_vert[iv] = submesh_vertex_id;
82 }
83 Element *el = mesh.NewElement(from_boundary ?
84 parent.GetBdrElementType(i) : parent.GetElementType(i));
85 el->SetVertices(submesh_vert);
86 el->SetAttribute(pel->GetAttribute());
87 mesh.AddElement(el);
88 parent_element_ids.Append(i);
89 }
90 return {parent_vertex_ids, parent_element_ids};
91}
92
94 const FiniteElementSpace& parentfes,
95 const SubMesh::From& from,
96 const Array<int>& parent_element_ids,
97 Array<int>& vdof_to_vdof_map)
98{
99 auto *m = subfes.GetMesh();
100 vdof_to_vdof_map.SetSize(subfes.GetVSize());
101 const int vdim = parentfes.GetVDim();
102
104 DenseMatrix T;
105 Array<int> z1;
106 for (int i = 0; i < m->GetNE(); i++)
107 {
108 Array<int> parent_vdofs;
109 if (from == SubMesh::From::Domain)
110 {
111 parentfes.GetElementVDofs(parent_element_ids[i], parent_vdofs);
112 }
113 else if (from == SubMesh::From::Boundary)
114 {
115 if (parentfes.IsDGSpace())
116 {
117 MFEM_ASSERT(static_cast<const L2_FECollection*>
118 (parentfes.FEColl())->GetBasisType() == BasisType::GaussLobatto,
119 "Only BasisType::GaussLobatto is supported for L2 spaces");
120
121 auto pm = parentfes.GetMesh();
122
123 const Geometry::Type face_geom =
124 pm->GetBdrElementGeometry(parent_element_ids[i]);
125 int face_info, parent_volel_id;
126 pm->GetBdrElementAdjacentElement(
127 parent_element_ids[i], parent_volel_id, face_info);
128 face_info = Mesh::EncodeFaceInfo(
131 face_geom, Mesh::DecodeFaceInfoOrientation(face_info)));
132 pm->GetLocalFaceTransformation(
133 pm->GetBdrElementType(parent_element_ids[i]),
134 pm->GetElementType(parent_volel_id),
135 Tr.Transf,
136 face_info);
137
138 const FiniteElement *face_el =
139 parentfes.GetTraceElement(parent_volel_id, face_geom);
140 MFEM_VERIFY(dynamic_cast<const NodalFiniteElement*>(face_el),
141 "Nodal Finite Element is required");
142
143 face_el->GetTransferMatrix(*parentfes.GetFE(parent_volel_id),
144 Tr.Transf,
145 T);
146
147 parentfes.GetElementVDofs(parent_volel_id, z1);
148
149 parent_vdofs.SetSize(vdim * T.Height());
150 for (int j = 0; j < T.Height(); j++)
151 {
152 for (int k = 0; k < T.Width(); k++)
153 {
154 if (T(j, k) != 0.0)
155 {
156 for (int vd=0; vd<vdim; vd++)
157 {
158 int sub_vdof = j + T.Height() * vd;
159 int parent_vdof = k + T.Width() * vd;
160 parent_vdofs[sub_vdof] =
161 z1[static_cast<int>(parent_vdof)];
162 }
163 }
164 }
165 }
166 }
167 else
168 {
169 parentfes.GetBdrElementVDofs(parent_element_ids[i], parent_vdofs);
170 }
171 }
172 else
173 {
174 MFEM_ABORT("SubMesh::From type unknown");
175 }
176
177 Array<int> sub_vdofs;
178 subfes.GetElementVDofs(i, sub_vdofs);
179
180 MFEM_ASSERT(parent_vdofs.Size() == sub_vdofs.Size(),
181 "elem " << i << ' ' << parent_vdofs.Size() << ' ' << sub_vdofs.Size());
182 for (int j = 0; j < parent_vdofs.Size(); j++)
183 {
184 real_t sub_sign = 1.0;
185 int sub_vdof = subfes.DecodeDof(sub_vdofs[j], sub_sign);
186
187 real_t parent_sign = 1.0;
188 int parent_vdof = parentfes.DecodeDof(parent_vdofs[j], parent_sign);
189
190 vdof_to_vdof_map[sub_vdof] =
191 (sub_sign * parent_sign > 0.0) ? parent_vdof : (-1-parent_vdof);
192 }
193 }
194
195#ifdef MFEM_DEBUG
196 auto tmp = vdof_to_vdof_map;
197 tmp.Sort();
198 tmp.Unique();
199
200 if (tmp.Size() != vdof_to_vdof_map.Size())
201 {
202 std::stringstream msg;
203 for (int i = 0; i < vdof_to_vdof_map.Size(); i++)
204 for (int j = i + 1; j < vdof_to_vdof_map.Size(); j++)
205 {
206 auto x = vdof_to_vdof_map[i];
207 auto y = vdof_to_vdof_map[j];
208 if (x == y)
209 {
210 msg << "i " << i << " (" << x << ") j " << j << " (" << y << ")\n";
211 }
212 }
213 MFEM_ABORT("vdof_to_vdof_map should be 1 to 1:\n" << msg.str());
214 }
215#endif
216
217}
218
219Array<int> BuildFaceMap(const Mesh& pm, const Mesh& sm,
220 const Array<int> &parent_element_ids)
221{
222 // TODO: Check if parent is really a parent of mesh
223
224 Array<int> pfids(sm.GetNumFaces());
225 pfids = -1;
226 for (int i = 0; i < sm.GetNE(); i++)
227 {
228 int peid = parent_element_ids[i];
229
230 Array<int> sel_faces, pel_faces, o;
231 if (pm.Dimension() == 2)
232 {
233 sm.GetElementEdges(i, sel_faces, o);
234 pm.GetElementEdges(peid, pel_faces, o);
235 }
236 else
237 {
238 sm.GetElementFaces(i, sel_faces, o);
239 pm.GetElementFaces(peid, pel_faces, o);
240 }
241
242 MFEM_ASSERT(sel_faces.Size() == pel_faces.Size(), "internal error");
243
244 for (int j = 0; j < sel_faces.Size(); j++)
245 {
246 if (pfids[sel_faces[j]] != -1)
247 {
248 MFEM_ASSERT(pfids[sel_faces[j]] == pel_faces[j], "internal error");
249 }
250 pfids[sel_faces[j]] = pel_faces[j];
251 }
252 }
253 return pfids;
254}
255
256template <typename SubMeshT>
257void AddBoundaryElements(SubMeshT &mesh,
258 const std::unordered_map<int,int> &lface_to_boundary_attribute)
259{
260 mesh.Dimension();
261 const int num_codim_1 = [&mesh]()
262 {
263 auto Dim = mesh.Dimension();
264 if (Dim == 1) { return mesh.GetNV(); }
265 else if (Dim == 2) { return mesh.GetNEdges(); }
266 else if (Dim == 3) { return mesh.GetNFaces(); }
267 else { MFEM_ABORT("Invalid dimension."); return -1; }
268 }();
269
270 if (mesh.Dimension() == 3)
271 {
272 // In 3D we check for `bel_to_edge`. It shouldn't have been set
273 // previously.
274 mesh.DeleteBoundaryElementToEdge();
275 }
276 int NumOfBdrElements = 0;
277 for (int i = 0; i < num_codim_1; i++)
278 {
279 if (mesh.GetFaceInformation(i).IsBoundary())
280 {
281 NumOfBdrElements++;
282 }
283 }
284
285 Array<Element *> boundary;
286 Array<int> be_to_face;
287 boundary.Reserve(NumOfBdrElements);
288 be_to_face.Reserve(NumOfBdrElements);
289
290 const auto &parent = *mesh.GetParent();
291 const auto &parent_face_ids = mesh.GetParentFaceIDMap();
292 const auto &parent_edge_ids = mesh.GetParentEdgeIDMap();
293 const auto &parent_vertex_ids = mesh.GetParentVertexIDMap();
294 const auto &parent_face_to_be = parent.GetFaceToBdrElMap();
295 const auto &face_to_be = mesh.GetFaceToBdrElMap();
296 int max_bdr_attr = parent.bdr_attributes.Max();
297 for (int i = 0; i < num_codim_1; i++)
298 {
299 auto pfid = [&](int i)
300 {
301 switch (mesh.Dimension())
302 {
303 case 3: return parent_face_ids[i];
304 case 2: return parent_edge_ids[i];
305 case 1: return parent_vertex_ids[i];
306 }
307 MFEM_ABORT("!");
308 return -1;
309 };
310 if (mesh.GetFaceInformation(i).IsBoundary()
311 && (face_to_be.IsEmpty() || face_to_be[i] == -1))
312 {
313 auto * be = mesh.GetFace(i)->Duplicate(&mesh);
314
315 if (mesh.GetFrom() == SubMesh::From::Domain && mesh.Dimension() >= 2)
316 {
317 int pbeid = parent_face_to_be[pfid(i)];
318 if (pbeid != -1)
319 {
320 be->SetAttribute(parent.GetBdrAttribute(pbeid));
321 }
322 else
323 {
324 auto ghost_attr = lface_to_boundary_attribute.find(pfid(i));
325 int battr = ghost_attr != lface_to_boundary_attribute.end() ?
326 ghost_attr->second : max_bdr_attr + 1;
327 be->SetAttribute(battr);
328 }
329 }
330 else
331 {
332 auto ghost_attr = lface_to_boundary_attribute.find(pfid(i));
333 int battr = ghost_attr != lface_to_boundary_attribute.end() ?
334 ghost_attr->second : max_bdr_attr + 1;
335 be->SetAttribute(battr);
336 }
337 be_to_face.Append(i);
338 boundary.Append(be);
339 }
340 }
341
342 if (mesh.GetFrom() == SubMesh::From::Domain && mesh.Dimension() >= 2)
343 {
344 // Search for and count interior boundary elements
345 int InteriorBdrElems = 0;
346 for (int i=0; i<parent.GetNBE(); i++)
347 {
348 const int parentFaceIdx = parent.GetBdrElementFaceIndex(i);
349 const int submeshFaceIdx =
350 mesh.Dimension() == 3 ?
351 mesh.GetSubMeshFaceFromParent(parentFaceIdx) :
352 mesh.GetSubMeshEdgeFromParent(parentFaceIdx);
353
354 if (submeshFaceIdx == -1) { continue; }
355 if (mesh.GetFaceInformation(submeshFaceIdx).IsBoundary()) { continue; }
356 InteriorBdrElems++;
357 }
358
359 if (InteriorBdrElems > 0)
360 {
361 NumOfBdrElements += InteriorBdrElems;
362 boundary.Reserve(NumOfBdrElements);
363 be_to_face.Reserve(NumOfBdrElements);
364
365 // Search for and transfer interior boundary elements
366 for (int i = 0; i < parent.GetNBE(); i++)
367 {
368 const int parentFaceIdx = parent.GetBdrElementFaceIndex(i);
369 const int submeshFaceIdx =
370 mesh.Dimension() == 3 ?
371 mesh.GetSubMeshFaceFromParent(parentFaceIdx) :
372 mesh.GetSubMeshEdgeFromParent(parentFaceIdx);
373
374 if (submeshFaceIdx == -1) { continue; }
375 if (mesh.GetFaceInformation(submeshFaceIdx).IsBoundary())
376 { continue; }
377
378 auto * be = mesh.GetFace(submeshFaceIdx)->Duplicate(&mesh);
379 be->SetAttribute(parent.GetBdrAttribute(i));
380 boundary.Append(be);
381 be_to_face.Append(submeshFaceIdx);
382 }
383 }
384 }
385 mesh.AddBdrElements(boundary, be_to_face);
386}
387
388// Explicit instantiations
389template void AddBoundaryElements(SubMesh &mesh,
390 const std::unordered_map<int,int> &);
391
392#ifdef MFEM_USE_MPI
393template void AddBoundaryElements(ParSubMesh &mesh,
394 const std::unordered_map<int,int> &);
395#endif
396
397namespace
398{
399/**
400 * @brief Helper class for storing and comparing arrays of face nodes.
401 * @details The comparison operator uses the sorted nodes and a lexicographic
402 * compare so that two different orientations of the same set of nodes will be
403 * identical. The actual nodes are stored unsorted as the ordering is important
404 * for constructing the leaf-root relations.
405 */
406struct FaceNodes
407{
408 std::array<int, NCMesh::MaxFaceNodes> nodes;
409 bool operator<(FaceNodes t2) const
410 {
411 std::array<int, NCMesh::MaxFaceNodes> t1 = nodes;
412 std::sort(t1.begin(), t1.end());
413 std::sort(t2.nodes.begin(), t2.nodes.end());
414 return std::lexicographical_compare(t1.begin(), t1.end(),
415 t2.nodes.begin(), t2.nodes.end());
416 };
417};
418
419/**
420 * @brief Establish the Geometry::Type from an array of nodes
421 *
422 * @param nodes
423 * @return Geometry::Type
424 */
425Geometry::Type FaceGeomFromNodes(const std::array<int, NCMesh::MaxFaceNodes>
426 &nodes)
427{
428 if (nodes[3] == -1) { return Geometry::Type::TRIANGLE; }
429 if (nodes[0] == nodes[1] && nodes[2] == nodes[3]) { return Geometry::Type::SEGMENT; }
431};
432
433} // namespace
434
435template<typename NCSubMeshT>
436void ConstructFaceTree(NCSubMeshT &submesh, const Array<int> &attributes)
437{
438 // Convenience references to avoid `submesh.` repeatedly.
439 auto &parent_node_ids = submesh.parent_node_ids_;
440 auto &parent_element_ids = submesh.parent_element_ids_;
441 auto &parent_to_submesh_node_ids = submesh.parent_to_submesh_node_ids_;
442 auto &parent_to_submesh_element_ids = submesh.parent_to_submesh_element_ids_;
443 const auto &parent = *submesh.GetParent();
444
445 // Collect parent vertex nodes to add in sequence. Map from parent nodes to
446 // the new element in the ncsubmesh.
447 UniqueIndexGenerator node_ids;
448 std::map<FaceNodes, int> pnodes_new_elem;
449 std::set<int> new_nodes;
450 parent_to_submesh_element_ids.reserve(parent.GetNumFaces());
451 parent_element_ids.Reserve(parent.GetNumFaces());
452 // Base class cast then const cast because GetFaceList uses just in time
453 // construction.
454 const auto &face_list = const_cast<NCMesh&>(static_cast<const NCMesh&>
455 (parent)).GetFaceList();
456 // Double indexing loop because begin() and end() do not align with index 0
457 // and size-1.
458 for (int i = 0, ipe = 0; ipe < parent.GetNumFaces(); i++)
459 {
460 const auto &face = parent.GetFace(i);
461 if (face.Unused()) { continue; }
462 ipe++; // actual possible parent element.
463 if (!HasAttribute(face, attributes)
464 || face_list.GetMeshIdType(face.index) == NCMesh::NCList::MeshIdType::MASTER
465 ) { continue; }
466
467 FaceNodes fn{submesh.parent_->FindFaceNodes(face)};
468 if (pnodes_new_elem.find(fn) != pnodes_new_elem.end()) { continue; }
469
470 // TODO: Internal nc submesh can be constructed and solved on, but the
471 // transfer to the parent mesh can be erroneous, this is likely due to not
472 // treating the changing orientation of internal faces for ncmesh within
473 // the ptransfermap.
474 MFEM_ASSERT(face.elem[0] < 0 || face.elem[1] < 0,
475 "Internal nonconforming boundaries are not reliably supported yet.");
476 auto face_geom = FaceGeomFromNodes(fn.nodes);
477 int new_elem_id = submesh.AddElement(face_geom, face.attribute);
478
479 // Rank needs to be established by presence (or lack of) in the submesh.
480 submesh.elements[new_elem_id].rank = [&parent, &face]()
481 {
482 auto rank0 = face.elem[0] >= 0 ? parent.GetElement(face.elem[0]).rank : -1;
483 auto rank1 = face.elem[1] >= 0 ? parent.GetElement(face.elem[1]).rank : -1;
484 if (rank0 < 0) { return rank1; }
485 if (rank1 < 0) { return rank0; }
486 return rank0 < rank1 ? rank0 : rank1;
487 }();
488 pnodes_new_elem[fn] = new_elem_id;
489 parent_element_ids.Append(i);
490 parent_to_submesh_element_ids[i] = new_elem_id;
491
492 // Copy in the parent nodes. These will be relabeled once the tree is
493 // built.
494 std::copy(fn.nodes.begin(), fn.nodes.end(), submesh.elements[new_elem_id].node);
495 for (auto x : fn.nodes)
496 if (x != -1)
497 {
498 new_nodes.insert(x);
499 }
500 auto &gi = submesh.GI[face_geom];
501 gi.InitGeom(face_geom);
502 for (int e = 0; e < gi.ne; e++)
503 {
504 new_nodes.insert(submesh.ParentNodes().FindId(fn.nodes[gi.edges[e][0]],
505 fn.nodes[gi.edges[e][1]]));
506 }
507
508 /*
509 - Check not top level face
510 - Check for parent of the newly entered element
511 - if not present, add in
512 - if present but different order and this path is non-ambiguous,
513 reorder so consistent with child elements.
514 - Set .parent in the newly entered element
515 Break if top level face or joined existing branch (without reordering).
516
517 child element indices will be set afterwards because the orientation can change
518 during traversal.
519 */
520 bool root_path_is_ambiguous=false;
521 bool fix_parent = false, tri_face = (face_geom == Geometry::TRIANGLE);
522 while (true)
523 {
524 int child = submesh.parent_->ParentFaceNodes(fn.nodes);
525 if (tri_face && child == 3)
526 {
527 // Traversing a central triangle face involves flipping the face orientation.
528 // Do not use this pathway for reordering any parent face's nodes.
529 root_path_is_ambiguous = true;
530 }
531
532 if (child == -1) // A root face
533 {
534 submesh.elements[new_elem_id].parent = -1;
535 break;
536 }
537 auto pelem = pnodes_new_elem.find(fn);
538 bool new_parent = pelem == pnodes_new_elem.end();
539 if (new_parent)
540 {
541 // Add in this parent
542 int pelem_id = submesh.AddElement(FaceGeomFromNodes(fn.nodes), face.attribute);
543 pelem = pnodes_new_elem.emplace(fn, pelem_id).first;
544 auto parent_face_id = submesh.ParentFaces().FindId(fn.nodes[0], fn.nodes[1],
545 fn.nodes[2],
546 fn.nodes[3]);
547 parent_element_ids.Append(parent_face_id);
548 }
549 else
550 {
551 // There are two scenarios where the parent nodes should be
552 // rearranged:
553 // 1. The found face is a slave, then the master might have been
554 // added in reverse orientation
555 // 2. The parent face was added from the central face of a triangle,
556 // the orientation of the parent face is only fixed relative to
557 // the outer child faces not the interior. If either of these
558 // scenarios, and there's a mismatch, then reorder the parent and
559 // all ancestors if necessary.
560 if (!root_path_is_ambiguous &&
561 !std::equal(fn.nodes.begin(), fn.nodes.end(), pelem->first.nodes.begin()))
562 {
563 fix_parent = true;
564 auto pelem_id = pelem->second;
565 MFEM_ASSERT(!submesh.elements[pelem_id].IsLeaf(), pelem_id);
566
567 // Re-key the map, the existing entry is inconsistent with the tree.
568 pnodes_new_elem.erase(pelem->first);
569 pelem = pnodes_new_elem.emplace(fn, pelem_id).first;
570 }
571 }
572 // Ensure parent element is marked as non-leaf, and attach to the child.
573 submesh.elements[pelem->second].ref_type = submesh.Dim == 2 ? Refinement::XY :
575 submesh.elements[new_elem_id].parent = pelem->second;
576
577 // If this was neither new nor a fixed parent, the higher levels of the
578 // tree have been built, otherwise we recurse up the tree to add more parents, or
579 // to potentially fix any ambiguously added FaceNodes.
580 if (!new_parent && !fix_parent) { break; }
581
582 new_elem_id = pelem->second;
583 }
584 }
585 parent_element_ids.ShrinkToFit();
586 MFEM_ASSERT(parent_element_ids.Size() == submesh.elements.Size(),
587 parent_element_ids.Size() << ' ' << submesh.elements.Size());
588
589 // All elements have been added, with their parents, and the nodal orientation of parents is
590 // consistent with children, but the children indices have not been marked. Traverse the
591 // tree from root to leaf to fill the child arrays.
592 for (const auto & fn_elem : pnodes_new_elem)
593 {
594 auto fn = fn_elem.first;
595 const auto &child_elem = submesh.elements[fn_elem.second];
596 if (child_elem.parent == -1) { continue; }
597 int child = submesh.parent_->ParentFaceNodes(fn.nodes);
598 MFEM_ASSERT(pnodes_new_elem[fn] == child_elem.parent,
599 pnodes_new_elem[fn] << ' ' << child_elem.parent);
600 MFEM_ASSERT(submesh.elements[child_elem.parent].ref_type != char(0),
601 int(submesh.elements[child_elem.parent].ref_type));
602 submesh.elements[child_elem.parent].child[child] = fn_elem.second;
603 }
604
605 /*
606 All elements have been added into the tree but a) The nodes are all from
607 the parent ncmesh b) The nodes do not know their parents c) The element
608 ordering is wrong, root elements are not first d) The parent and child
609 element numbers reflect the incorrect ordering
610
611 1. Add in nodes in the same order from the parent ncmesh
612 2. Compute reordering of elements with parent elements first, that is
613 stable across processors.
614 */
615 // Build an inverse (and consecutive) map.
616 Array<FaceNodes> new_elem_to_parent_face_nodes(static_cast<int>
617 (pnodes_new_elem.size()));
618 for (const auto &kv : pnodes_new_elem)
619 {
620 new_elem_to_parent_face_nodes[kv.second] = kv.first;
621 }
622 pnodes_new_elem.clear(); // no longer needed
623
624 // Add new nodes preserving parent mesh ordering
625 parent_node_ids.Reserve(static_cast<int>(new_nodes.size()));
626 parent_to_submesh_node_ids.reserve(new_nodes.size());
627 for (auto n : new_nodes)
628 {
629 bool new_node;
630 auto new_node_id = node_ids.Get(n, new_node);
631 MFEM_ASSERT(new_node, "!");
632 submesh.nodes.Alloc(new_node_id, new_node_id, new_node_id);
633 parent_node_ids.Append(n);
634 parent_to_submesh_node_ids[n] = new_node_id;
635 }
636 parent_node_ids.ShrinkToFit();
637 new_nodes.clear(); // not needed any more.
638
639 // Comparator for deciding order of elements. Building the ordering from the
640 // parent ncmesh ensures the root ordering is common across ranks.
641 auto comp_elements = [&](int l, int r)
642 {
643 const auto &elem_l = submesh.elements[l];
644 const auto &elem_r = submesh.elements[r];
645 if (elem_l.parent == elem_r.parent)
646 {
647 const auto &fnl = new_elem_to_parent_face_nodes[l].nodes;
648 const auto &fnr = new_elem_to_parent_face_nodes[r].nodes;
649 return std::lexicographical_compare(fnl.begin(), fnl.end(), fnr.begin(),
650 fnr.end());
651 }
652 else
653 {
654 return elem_l.parent < elem_r.parent;
655 }
656 };
657 Array<int> indices(submesh.elements.Size());
658 auto parental_sorted = [&]()
659 {
660 std::iota(indices.begin(), indices.end(), 0);
661 return std::is_sorted(indices.begin(), indices.end(), comp_elements);
662 };
663
664 Array<int> new_to_old(submesh.elements.Size()),
665 old_to_new(submesh.elements.Size());
666 while (!parental_sorted())
667 {
668 // Stably reorder elements in order of refinement, and by parental nodes
669 // within a nuclear family.
670 new_to_old.SetSize(submesh.elements.Size()),
671 old_to_new.SetSize(submesh.elements.Size());
672 std::iota(new_to_old.begin(), new_to_old.end(), 0);
673 std::stable_sort(new_to_old.begin(), new_to_old.end(), comp_elements);
674 // Build the inverse relation for converting the old elements to new
675 for (int i = 0; i < submesh.elements.Size(); i++)
676 {
677 old_to_new[new_to_old[i]] = i;
678 }
679
680 // Permute whilst reordering new_to_old. Avoids unnecessary copies.
681 Permute(std::move(new_to_old), submesh.elements, parent_element_ids,
682 new_elem_to_parent_face_nodes);
683 parent_to_submesh_element_ids.clear();
684 for (int i = 0; i < parent_element_ids.Size(); i++)
685 {
686 if (parent_element_ids[i] == -1) {continue;}
687 parent_to_submesh_element_ids[parent_element_ids[i]] = i;
688 }
689
690 // Apply the new ordering to child and parent elements
691 for (auto &elem : submesh.elements)
692 {
693 if (!elem.IsLeaf())
694 {
695 // Parent rank is minimum of child ranks.
696 elem.rank = std::numeric_limits<int>::max();
697 for (int c = 0; c < NCMesh::MaxElemChildren && elem.child[c] >= 0; c++)
698 {
699 elem.child[c] = old_to_new[elem.child[c]];
700 elem.rank = std::min(elem.rank, submesh.elements[elem.child[c]].rank);
701 }
702 }
703 elem.parent = elem.parent == -1 ? -1 : old_to_new[elem.parent];
704 }
705 }
706
707 // Apply new node ordering to relations, and sign in on edges/vertices
708 for (auto &elem : submesh.elements)
709 {
710 if (elem.IsLeaf())
711 {
712 bool new_id;
713 auto &gi = submesh.GI[elem.Geom()];
714 gi.InitGeom(elem.Geom());
715 for (int e = 0; e < gi.ne; e++)
716 {
717 const int pid = submesh.ParentNodes().FindId(
718 elem.node[gi.edges[e][0]], elem.node[gi.edges[e][1]]);
719 MFEM_ASSERT(pid >= 0,
720 elem.node[gi.edges[e][0]] << ' ' << elem.node[gi.edges[e][1]]);
721 auto submesh_node_id = node_ids.Get(pid, new_id);
722 MFEM_ASSERT(!new_id, "!");
723 submesh.nodes[submesh_node_id].edge_refc++;
724 }
725 for (int n = 0; n < gi.nv; n++)
726 {
727 MFEM_ASSERT(parent_to_submesh_node_ids.find(elem.node[n]) !=
728 parent_to_submesh_node_ids.end(), "!");
729 elem.node[n] = parent_to_submesh_node_ids[elem.node[n]];
730 submesh.nodes[elem.node[n]].vert_refc++;
731 }
732 // Register faces
733 for (int f = 0; f < gi.nf; f++)
734 {
735 auto *face = submesh.faces.Get(
736 elem.node[gi.faces[f][0]],
737 elem.node[gi.faces[f][1]],
738 elem.node[gi.faces[f][2]],
739 elem.node[gi.faces[f][3]]);
740 face->attribute = -1;
741 face->index = -1;
742 }
743 }
744 }
745}
746
747// Explicit instantiations
748template void ConstructFaceTree(NCSubMesh &submesh,
749 const Array<int> &attributes);
750#ifdef MFEM_USE_MPI
751template void ConstructFaceTree(ParNCSubMesh &submesh,
752 const Array<int> &attributes);
753#endif
754
755template <typename NCSubMeshT>
756void ConstructVolumeTree(NCSubMeshT &submesh, const Array<int> &attributes)
757{
758 // Convenience references to avoid `submesh.` repeatedly.
759 auto &parent_node_ids = submesh.parent_node_ids_;
760 auto &parent_element_ids = submesh.parent_element_ids_;
761 auto &parent_to_submesh_node_ids = submesh.parent_to_submesh_node_ids_;
762 auto &parent_to_submesh_element_ids = submesh.parent_to_submesh_element_ids_;
763 const auto &parent = *submesh.GetParent();
764
765 UniqueIndexGenerator node_ids;
766 parent_to_submesh_element_ids.reserve(parent.GetNumElements());
767 std::set<int> new_nodes;
768 for (int ipe = 0; ipe < parent.GetNumElements(); ipe++)
769 {
770 const auto& pe = parent.GetElement(ipe);
771 if (!HasAttribute(pe, attributes)) { continue; }
772 const int elem_id = submesh.AddElement(pe);
773 auto &el = submesh.elements[elem_id];
774 parent_element_ids.Append(ipe); // submesh -> parent
775 parent_to_submesh_element_ids[ipe] = elem_id; // parent -> submesh
776 if (!pe.IsLeaf()) { continue; }
777 const auto gi = submesh.GI[pe.Geom()];
778 for (int n = 0; n < gi.nv; n++)
779 {
780 new_nodes.insert(el.node[n]);
781 }
782 for (int e = 0; e < gi.ne; e++)
783 {
784 new_nodes.insert(submesh.ParentNodes().FindId(el.node[gi.edges[e][0]],
785 el.node[gi.edges[e][1]]));
786 }
787 }
788
789 parent_node_ids.Reserve(static_cast<int>(new_nodes.size()));
790 parent_to_submesh_node_ids.reserve(new_nodes.size());
791 for (const auto &n : new_nodes)
792 {
793 bool new_node;
794 auto new_node_id = node_ids.Get(n, new_node);
795 MFEM_ASSERT(new_node, "!");
796 submesh.nodes.Alloc(new_node_id, new_node_id, new_node_id);
797 parent_node_ids.Append(n);
798 parent_to_submesh_node_ids[n] = new_node_id;
799 }
800
801 // Loop over elements and reference edges and faces (creating any nodes on
802 // first encounter).
803 for (auto &el : submesh.elements)
804 {
805 if (el.IsLeaf())
806 {
807 const auto gi = submesh.GI[el.Geom()];
808 bool new_id = false;
809
810 for (int n = 0; n < gi.nv; n++)
811 {
812 // Relabel nodes from parent to submesh.
813 el.node[n] = node_ids.Get(el.node[n], new_id);
814 MFEM_ASSERT(new_id == false, "Should not be new.");
815 submesh.nodes[el.node[n]].vert_refc++;
816 }
817 for (int e = 0; e < gi.ne; e++)
818 {
819 const int pid = submesh.ParentNodes().FindId(
820 parent_node_ids[el.node[gi.edges[e][0]]],
821 parent_node_ids[el.node[gi.edges[e][1]]]);
822 MFEM_ASSERT(pid >= 0, "Edge not found");
823 auto submesh_node_id = node_ids.Get(pid, new_id);
824 MFEM_ASSERT(new_id == false, "Should not be new.");
825 submesh.nodes[submesh_node_id].edge_refc++; // Register the edge
826 }
827 for (int f = 0; f < gi.nf; f++)
828 {
829 const int *fv = gi.faces[f];
830 const int pid = submesh.ParentFaces().FindId(
831 parent_node_ids[el.node[fv[0]]],
832 parent_node_ids[el.node[fv[1]]],
833 parent_node_ids[el.node[fv[2]]],
834 el.node[fv[3]] >= 0 ? parent_node_ids[el.node[fv[3]]]: - 1);
835 MFEM_ASSERT(pid >= 0, "Face not found");
836 const int id = submesh.faces.GetId(
837 el.node[fv[0]], el.node[fv[1]], el.node[fv[2]], el.node[fv[3]]);
838 submesh.faces[id].attribute = submesh.ParentFaces()[pid].attribute;
839 }
840 }
841 else
842 {
843 // All elements have been collected, remap the child ids.
844 for (int i = 0; i < NCMesh::MaxElemChildren && el.child[i] >= 0; i++)
845 {
846 el.child[i] = parent_to_submesh_element_ids[el.child[i]];
847 }
848 }
849 el.parent = el.parent < 0 ? el.parent
850 : parent_to_submesh_element_ids.at(el.parent);
851 }
852}
853
854// Explicit instantiations
855template void ConstructVolumeTree(NCSubMesh &submesh,
856 const Array<int> &attributes);
857#ifdef MFEM_USE_MPI
858template void ConstructVolumeTree(ParNCSubMesh &submesh,
859 const Array<int> &attributes);
860#endif
861} // namespace SubMeshUtils
862} // namespace mfem
void Sort()
Sorts the array in ascending order. This requires operator< to be defined for T.
Definition array.hpp:278
void Reserve(int capacity)
Ensures that the allocated size is at least the given size.
Definition array.hpp:165
void SetSize(int nsize)
Change the logical size of the array, keep existing entries.
Definition array.hpp:758
int Size() const
Return the logical size of the array.
Definition array.hpp:147
int Append(const T &el)
Append element 'el' to array, resize if necessary.
Definition array.hpp:830
T * end()
STL-like end. Returns pointer after the last element of the array.
Definition array.hpp:325
T * begin()
STL-like begin. Returns pointer to the first element of the array.
Definition array.hpp:322
@ GaussLobatto
Closed type.
Definition fe_base.hpp:36
Data type dense matrix using column-major storage.
Definition densemat.hpp:24
Abstract data type element.
Definition element.hpp:29
virtual void GetVertices(Array< int > &v) const =0
Get the indices defining the vertices.
void SetAttribute(const int attr)
Set element's attribute.
Definition element.hpp:61
int GetAttribute() const
Return element's attribute.
Definition element.hpp:58
virtual void SetVertices(const Array< int > &v)=0
Set the indices defining the vertices.
Class FiniteElementSpace - responsible for providing FEM view of the mesh, mainly managing the set of...
Definition fespace.hpp:244
DofTransformation * GetElementVDofs(int i, Array< int > &vdofs) const
Returns indices of degrees of freedom for the i'th element. The returned indices are offsets into an ...
Definition fespace.cpp:332
virtual const FiniteElement * GetFE(int i) const
Returns pointer to the FiniteElement in the FiniteElementCollection associated with i'th element in t...
Definition fespace.cpp:3835
const FiniteElement * GetTraceElement(int i, Geometry::Type geom_type) const
Return the trace element from element 'i' to the given 'geom_type'.
Definition fespace.cpp:3953
const FiniteElementCollection * FEColl() const
Definition fespace.hpp:878
Mesh * GetMesh() const
Returns the mesh.
Definition fespace.hpp:679
int GetVSize() const
Return the number of vector dofs, i.e. GetNDofs() x GetVDim().
Definition fespace.hpp:848
int GetVDim() const
Returns the vector dimension of the finite element space.
Definition fespace.hpp:841
bool IsDGSpace() const
Return whether or not the space is discontinuous (L2)
Definition fespace.hpp:1510
static int DecodeDof(int dof)
Helper to return the DOF associated with a sign encoded DOF.
Definition fespace.hpp:1168
DofTransformation * GetBdrElementVDofs(int i, Array< int > &vdofs) const
Returns indices of degrees of freedom for i'th boundary element. The returned indices are offsets int...
Definition fespace.cpp:348
Abstract class for all finite elements.
Definition fe_base.hpp:244
virtual void GetTransferMatrix(const FiniteElement &fe, ElementTransformation &Trans, DenseMatrix &I) const
Return interpolation matrix, I, which maps dofs from a coarse element, fe, to the fine dofs on this f...
Definition fe_base.cpp:119
static int GetInverseOrientation(Type geom_type, int orientation)
Return the inverse of the given orientation for the specified geometry type.
Definition geom.cpp:264
IsoparametricTransformation Transf
Definition eltrans.hpp:733
Arbitrary order "L2-conforming" discontinuous finite elements.
Definition fe_coll.hpp:346
int GetBasisType() const
Definition fe_coll.hpp:390
Mesh data type.
Definition mesh.hpp:64
Element * NewElement(int geom)
Definition mesh.cpp:4672
Element::Type GetElementType(int i) const
Returns the type of element i.
Definition mesh.cpp:7635
Element::Type GetBdrElementType(int i) const
Returns the type of boundary element i.
Definition mesh.cpp:7640
int GetNumFaces() const
Return the number of faces (3D), edges (2D) or vertices (1D).
Definition mesh.cpp:6531
Geometry::Type GetBdrElementGeometry(int i) const
Definition mesh.hpp:1446
const Element * GetElement(int i) const
Return pointer to the i'th element object.
Definition mesh.hpp:1339
static int EncodeFaceInfo(int local_face_index, int orientation)
Given local_face_index and orientation, return the corresponding encoded "face info int".
Definition mesh.hpp:2076
int AddVertex(real_t x, real_t y=0.0, real_t z=0.0)
Definition mesh.cpp:1873
int GetNE() const
Returns number of elements.
Definition mesh.hpp:1282
int Dimension() const
Dimension of the reference space used within the elements.
Definition mesh.hpp:1216
const Element * GetBdrElement(int i) const
Return pointer to the i'th boundary element object.
Definition mesh.hpp:1354
int AddElement(Element *elem)
Definition mesh.cpp:2236
static int DecodeFaceInfoLocalIndex(int info)
Given a "face info int", return the local face index.
Definition mesh.hpp:2072
void GetElementFaces(int i, Array< int > &faces, Array< int > &ori) const
Return the indices and the orientations of all faces of element i.
Definition mesh.cpp:7514
static int DecodeFaceInfoOrientation(int info)
Given a "face info int", return the face orientation.
Definition mesh.hpp:2069
int GetNBE() const
Returns number of boundary elements.
Definition mesh.hpp:1285
void GetElementEdges(int i, Array< int > &edges, Array< int > &cor) const
Return the indices and the orientations of all edges of element i.
Definition mesh.cpp:7267
const real_t * GetVertex(int i) const
Return pointer to vertex i's coordinates.
Definition mesh.hpp:1321
A class for non-conforming AMR. The class is not used directly by the user, rather it is an extension...
Definition ncmesh.hpp:140
const NCList & GetFaceList()
Return the current list of conforming and nonconforming faces.
Definition ncmesh.hpp:319
static constexpr int MaxElemChildren
Number of children an element can have.
Definition ncmesh.hpp:494
Class representing a Nonconformal SubMesh. This is only used by SubMesh.
Definition ncsubmesh.hpp:28
Class for standard nodal finite elements.
Definition fe_base.hpp:721
int Height() const
Get the height (size of output) of the Operator. Synonym with NumRows().
Definition operator.hpp:66
int Width() const
Get the width (size of input) of the Operator. Synonym with NumCols().
Definition operator.hpp:72
Class representing a Parallel Nonconformal SubMesh. This is only used by ParSubMesh.
Subdomain representation of a topological parent in another ParMesh.
Definition psubmesh.hpp:54
Subdomain representation of a topological parent in another Mesh.
Definition submesh.hpp:44
From
Indicator from which part of the parent Mesh the SubMesh is created.
Definition submesh.hpp:49
real_t a
Definition lissajous.cpp:41
std::tuple< Array< int >, Array< int > > AddElementsToMesh(const Mesh &parent, Mesh &mesh, const Array< int > &attributes, bool from_boundary)
Given a Mesh parent and another Mesh mesh using the list of attributes in attributes,...
void ConstructFaceTree(NCSubMeshT &submesh, const Array< int > &attributes)
Construct a nonconformal mesh (serial or parallel) for a surface submesh, from an existing nonconform...
void BuildVdofToVdofMap(const FiniteElementSpace &subfes, const FiniteElementSpace &parentfes, const SubMesh::From &from, const Array< int > &parent_element_ids, Array< int > &vdof_to_vdof_map)
Build the vdof to vdof mapping between two FiniteElementSpace objects.
bool HasAttribute(const T &el, const Array< int > &attributes)
Helper for checking if an object's attributes match a list.
bool ElementHasAttribute(const ElementT &el, const Array< int > &attributes)
void Permute(const Array< int > &indices, T1 &t1, T2 &t2, T3 &t3)
Apply permutation to a container type.
void ConstructVolumeTree(NCSubMeshT &submesh, const Array< int > &attributes)
Construct a nonconformal mesh (serial or parallel) for a volume submesh, from an existing nonconforma...
Array< int > BuildFaceMap(const Mesh &pm, const Mesh &sm, const Array< int > &parent_element_ids)
Given two meshes that have a parent to SubMesh relationship create a face map, using a SubMesh to par...
void AddBoundaryElements(SubMeshT &mesh, const std::unordered_map< int, int > &lface_to_boundary_attribute)
Add boundary elements to the SubMesh.
float real_t
Definition config.hpp:43
std::function< real_t(const Vector &)> f(real_t mass_coeff)
Definition lor_mms.hpp:30
bool operator<(const Pair< A, B > &p, const Pair< A, B > &q)
Comparison operator for class Pair, based on the first element only.
Convenience object to create unique indices.
std::unordered_map< int, int > idx
int Get(int i, bool &new_index)
Returns the unique index from an index set.
std::array< int, NCMesh::MaxFaceNodes > nodes