MFEM v4.9.0
Finite element discretization library
Loading...
Searching...
No Matches
ncmesh.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 "mesh_headers.hpp"
14#include "../general/text.hpp"
15
16#include <string>
17#include <cmath>
18#include <map>
19
20#include "ncmesh_tables.hpp"
21
22
23namespace
24{
25/**
26 * @brief Base case of convenience variadic max function.
27 *
28 * @tparam T Base type
29 * @param arg Recursion base value
30 * @return T value to max over
31 */
32template<typename T>
33T max(T&& arg)
34{
35 return arg;
36}
37/**
38 * @brief Convenience variadic max function.
39 *
40 * @tparam T Base Type
41 * @tparam Ts Parameter pack of other types
42 * @param arg Singular argument
43 * @param args Pack of arguments
44 * @return T maximum value
45 */
46template<typename T, typename... Ts>
47T max(T arg, Ts... args)
48{
49 return std::max(std::forward<T>(arg), max(args...));
50}
51} // namespace
52namespace mfem
53{
54
55NCMesh::GeomInfo NCMesh::GI[Geometry::NumGeom];
56
58{
59 if (initialized) { return; }
60
61 auto elem = [&]()
62 {
63 switch (geom)
64 {
65 case Geometry::CUBE: return std::unique_ptr<mfem::Element>(new Hexahedron);
66 case Geometry::PRISM: return std::unique_ptr<mfem::Element>(new Wedge);
67 case Geometry::TETRAHEDRON: return std::unique_ptr<mfem::Element>
68 (new Tetrahedron);
69 case Geometry::PYRAMID: return std::unique_ptr<mfem::Element>(new Pyramid);
70 case Geometry::SQUARE: return std::unique_ptr<mfem::Element>(new Quadrilateral);
71 case Geometry::TRIANGLE: return std::unique_ptr<mfem::Element>(new Triangle);
72 case Geometry::SEGMENT: return std::unique_ptr<mfem::Element>(new Segment);
73 default: MFEM_ABORT("unsupported geometry " << geom);
74 }
75 }();
76
77 nv = elem->GetNVertices();
78 ne = elem->GetNEdges();
79 nf = elem->GetNFaces();
80 for (int i = 0; i < ne; i++)
81 {
82 for (int j = 0; j < 2; j++)
83 {
84 edges[i][j] = elem->GetEdgeVertices(i)[j];
85 }
86 }
87 for (int i = 0; i < nf; i++)
88 {
89 nfv[i] = elem->GetNFaceVertices(i);
90
91 faces[i][3] = 7; // invalid node index for 3-node faces
92 for (int j = 0; j < nfv[i]; j++)
93 {
94 faces[i][j] = elem->GetFaceVertices(i)[j];
95 }
96 }
97
98 // in 1D/2D we pretend to have faces too, so we can use NCMesh::Face::elem[2]
99 if (!nf)
100 {
101 if (ne)
102 {
103 for (int i = 0; i < ne; i++)
104 {
105 // make a degenerate face
106 faces[i][0] = faces[i][1] = edges[i][0];
107 faces[i][2] = faces[i][3] = edges[i][1];
108 nfv[i] = 2;
109 }
110 nf = ne;
111 }
112 else
113 {
114 for (int i = 0; i < nv; i++)
115 {
116 // 1D degenerate face
117 faces[i][0] = faces[i][1] = faces[i][2] = faces[i][3] = i;
118 nfv[i] = 1;
119 }
120 nf = nv;
121 }
122 }
123
124 initialized = true;
125}
126
128 : shadow(1024, 2048)
129{
130 Dim = mesh->Dimension();
131 spaceDim = mesh->SpaceDimension();
132 MyRank = 0;
133 Iso = true;
134 Legacy = false;
135
136 // create the NCMesh::Element struct for each Mesh element
137 for (int i = 0; i < mesh->GetNE(); i++)
138 {
139 const mfem::Element *elem = mesh->GetElement(i);
140
141 Geometry::Type geom = elem->GetGeometryType();
142 CheckSupportedGeom(geom);
143 GI[geom].InitGeom(geom);
144
145 // if we have pyramids we will need tets after refinement
146 if (geom == Geometry::PYRAMID)
147 {
149 }
150
151 // create NCMesh::Element for this mfem::Element
152 int root_id = AddElement(geom, elem->GetAttribute());
153 MFEM_ASSERT(root_id == i, "");
154 Element &root_elem = elements[root_id];
155
156 const int *v = elem->GetVertices();
157 for (int j = 0; j < GI[geom].nv; j++)
158 {
159 int id = v[j];
160 root_elem.node[j] = id;
161 nodes.Alloc(id, id, id);
162 // NOTE: top-level nodes are special: id == p1 == p2 == orig. vertex id
163 }
164 }
165
166 // if the user initialized any hanging nodes with Mesh::AddVertexParents,
167 // copy the hierarchy now
168 if (mesh->tmp_vertex_parents.Size())
169 {
170 for (const auto &triple : mesh->tmp_vertex_parents)
171 {
172 nodes.Reparent(triple.one, triple.two, triple.three);
173 }
174 }
175
176 // create edge nodes and faces
177 nodes.UpdateUnused();
178 for (int i = 0; i < elements.Size(); i++)
179 {
180 // increase reference count of all nodes the element is using (NOTE: this
181 // will also create and reference all edge nodes and faces)
183
184 // make links from faces back to the element
185 RegisterFaces(i);
186 }
187
188 // store boundary element attributes
189 for (int i = 0; i < mesh->GetNBE(); i++)
190 {
191 const mfem::Element *be = mesh->GetBdrElement(i);
192 const int *v = be->GetVertices();
193
194 Face* face = NULL;
195 switch (be->GetType())
196 {
198 face = faces.Find(v[0], v[1], v[2], v[3]);
199 break;
201 face = faces.Find(v[0], v[1], v[2]);
202 break;
204 face = faces.Find(v[0], v[0], v[1], v[1]);
205 break;
207 face = faces.Find(v[0], v[0], v[0], v[0]);
208 break;
209 default:
210 MFEM_ABORT("Unsupported boundary element geometry.");
211 }
212
213 MFEM_VERIFY(face, "Boundary face not found.");
214 face->attribute = be->GetAttribute();
215 }
216
217 // copy top-level vertex coordinates (leave empty if the mesh is curved)
218 if (!mesh->Nodes)
219 {
220 coordinates.SetSize(3*mesh->GetNV());
221 for (int i = 0; i < mesh->GetNV(); i++)
222 {
223 std::memcpy(&coordinates[3*i], mesh->GetVertex(i), 3*sizeof(real_t));
224 }
225 }
226
227 InitRootState(mesh->GetNE());
229
230 Update();
231}
232
234 : Dim(other.Dim)
235 , spaceDim(other.spaceDim)
236 , MyRank(other.MyRank)
237 , Iso(other.Iso)
238 , Geoms(other.Geoms)
239 , Legacy(other.Legacy)
240 , nodes(other.nodes)
241 , faces(other.faces)
242 , using_scaling(other.using_scaling)
243 , elements(other.elements)
244 , free_element_ids(other.free_element_ids)
245 , root_state(other.root_state)
246 , coordinates(other.coordinates)
247 , NEdges(other.NEdges)
248 , NFaces(other.NFaces)
249 , NGhostEdges(other.NGhostEdges)
250 , NGhostFaces(other.NGhostFaces)
251 , boundary_faces(other.boundary_faces)
252 , face_geom(other.face_geom)
253 , element_vertex(other.element_vertex)
254 , shadow(1024, 2048)
255{
256 Update();
257}
258
260{
261 Geoms = 0;
262 for (int i = 0; i < root_state.Size(); i++)
263 {
264 Geoms |= (1 << elements[i].Geom());
265 }
266}
267
269{
272
276
278}
279
281{
282#ifdef MFEM_DEBUG
283 // sign off of all faces and nodes
284 Array<int> elemFaces;
285 for (int i = 0; i < elements.Size(); i++)
286 {
287 if (elements[i].IsLeaf())
288 {
289 elemFaces.SetSize(0);
290 UnreferenceElement(i, elemFaces);
291 DeleteUnusedFaces(elemFaces);
292 }
293 }
294 // NOTE: in release mode, we just throw away all faces and nodes at once
295#endif
296}
297
298void NCMesh::Node::SetScale(real_t s, bool overwrite)
299{
300 MFEM_ASSERT(0.0 < s && s < 1.0, "Invalid scale");
301 if (!overwrite && scaleSet && std::abs((s - scale) / scale) > scaleTol)
302 {
303 MFEM_ABORT("Node scale is already set (inconsistent refinement)");
304 }
305 else if (overwrite || !scaleSet)
306 {
307 scale = s;
308 scaleSet = true;
309 }
310}
311
313{
314 MFEM_ASSERT(!vert_refc && !edge_refc, "node was not unreferenced properly, "
315 "vert_refc: " << (int) vert_refc << ", edge_refc: "
316 << (int) edge_refc);
317}
318
319void NCMesh::ReparentNode(int node, int new_p1, int new_p2, real_t scale)
320{
321 Node &nd = nodes[node];
322 nd.SetScale(GetScale(scale, new_p1 > new_p2), true);
323 int old_p1 = nd.p1, old_p2 = nd.p2;
324
325 // assign new parents
326 nodes.Reparent(node, new_p1, new_p2);
327
328 MFEM_ASSERT(shadow.FindId(old_p1, old_p2) < 0,
329 "shadow node already exists");
330
331 // store old parent pair temporarily in 'shadow'
332 int sh = shadow.GetId(old_p1, old_p2);
333 shadow[sh].vert_index = node;
334}
335
336int NCMesh::FindMidEdgeNode(int node1, int node2) const
337{
338 int mid = nodes.FindId(node1, node2);
339 if (mid < 0 && shadow.Size())
340 {
341 // if (anisotropic) refinement is underway, some nodes may temporarily be
342 // available under alternate parents (see ReparentNode)
343 mid = shadow.FindId(node1, node2);
344 if (mid >= 0)
345 {
346 mid = shadow[mid].vert_index; // index of the original node
347 }
348 }
349 return mid;
350}
351
352int NCMesh::GetMidEdgeNode(int node1, int node2)
353{
354 int mid = FindMidEdgeNode(node1, node2);
355 if (mid < 0) { mid = nodes.GetId(node1, node2); } // create if not found
356 return mid;
357}
358
359int NCMesh::GetMidFaceNode(int en1, int en2, int en3, int en4)
360{
361 // mid-face node can be created either from (en1, en3) or from (en2, en4)
362 int midf = FindMidEdgeNode(en1, en3);
363 if (midf >= 0) { return midf; }
364 return nodes.GetId(en2, en4);
365}
366
368{
369 const Element &el = elements[elem];
370 const int* node = el.node;
371 GeomInfo& gi = GI[el.Geom()];
372
373 // reference all vertices
374 for (int i = 0; i < gi.nv; i++)
375 {
376 nodes[node[i]].vert_refc++;
377 }
378
379 // reference all edges (possibly creating their nodes)
380 for (int i = 0; i < gi.ne; i++)
381 {
382 const int* ev = gi.edges[i];
383 nodes.Get(node[ev[0]], node[ev[1]])->edge_refc++;
384 }
385
386 // get all faces (possibly creating them)
387 for (int i = 0; i < gi.nf; i++)
388 {
389 const int* fv = gi.faces[i];
390 faces.GetId(node[fv[0]], node[fv[1]], node[fv[2]], node[fv[3]]);
391
392 // NOTE: face->RegisterElement called separately to avoid having to store
393 // 3 element indices temporarily in the face when refining. See also
394 // NCMesh::RegisterFaces.
395 }
396}
397
398void NCMesh::UnreferenceElement(int elem, Array<int> &elemFaces)
399{
400 Element &el = elements[elem];
401 int* node = el.node;
402 GeomInfo& gi = GI[el.Geom()];
403
404 // unreference all faces
405 for (int i = 0; i < gi.nf; i++)
406 {
407 const int* fv = gi.faces[i];
408 int face = faces.FindId(node[fv[0]], node[fv[1]],
409 node[fv[2]], node[fv[3]]);
410 MFEM_ASSERT(face >= 0, "face not found.");
411 faces[face].ForgetElement(elem);
412
413 // NOTE: faces.Delete() called later to avoid destroying and recreating
414 // faces during refinement, see NCMesh::DeleteUnusedFaces.
415 elemFaces.Append(face);
416 }
417
418 // unreference all edges (possibly destroying them)
419 for (int i = 0; i < gi.ne; i++)
420 {
421 const int* ev = gi.edges[i];
422 int enode = FindMidEdgeNode(node[ev[0]], node[ev[1]]);
423 MFEM_ASSERT(enode >= 0, "edge not found.");
424 MFEM_ASSERT(nodes.IdExists(enode), "edge does not exist.");
425 if (!nodes[enode].UnrefEdge())
426 {
427 nodes.Delete(enode);
428 }
429 }
430
431 // unreference all vertices (possibly destroying them)
432 for (int i = 0; i < gi.nv; i++)
433 {
434 if (!nodes[node[i]].UnrefVertex())
435 {
436 nodes.Delete(node[i]);
437 }
438 }
439}
440
441void NCMesh::RegisterFaces(int elem, int* fattr)
442{
443 Element &el = elements[elem];
444 GeomInfo &gi = GI[el.Geom()];
445
446 for (int i = 0; i < gi.nf; i++)
447 {
448 Face* face = GetFace(el, i);
449 MFEM_ASSERT(face, "face not found.");
450 face->RegisterElement(elem);
451 if (fattr) { face->attribute = fattr[i]; }
452 }
453}
454
456{
457 for (int i = 0; i < elemFaces.Size(); i++)
458 {
459 if (faces[elemFaces[i]].Unused())
460 {
461 faces.Delete(elemFaces[i]);
462 }
463 }
464}
465
467{
468 if (elem[0] < 0) { elem[0] = e; }
469 else if (elem[1] < 0) { elem[1] = e; }
470 else { MFEM_ABORT("can't have 3 elements in Face::elem[]."); }
471}
472
474{
475 if (elem[0] == e) { elem[0] = -1; }
476 else if (elem[1] == e) { elem[1] = -1; }
477 else { MFEM_ABORT("element " << e << " not found in Face::elem[]."); }
478}
479
481{
482 GeomInfo& gi = GI[(int) elem.geom];
483 const int* fv = gi.faces[face_no];
484 int* node = elem.node;
485 return faces.Find(node[fv[0]], node[fv[1]], node[fv[2]], node[fv[3]]);
486}
487
489{
490 if (elem[0] >= 0)
491 {
492 MFEM_ASSERT(elem[1] < 0, "not a single element face.");
493 return elem[0];
494 }
495 else
496 {
497 MFEM_ASSERT(elem[1] >= 0, "no elements in face.");
498 return elem[1];
499 }
500}
501
502
503//// Refinement ////////////////////////////////////////////////////////////////
504
505void Refinement::SetScale(const ScaledType &r)
506{
507 switch (r.first)
508 {
509 case X:
510 s[0] = r.second;
511 break;
512 case Y:
513 s[1] = r.second;
514 break;
515 case Z:
516 s[2] = r.second;
517 break;
518 case XY:
519 s[0] = r.second;
520 s[1] = r.second;
521 break;
522 case YZ:
523 s[1] = r.second;
524 s[2] = r.second;
525 break;
526 case XZ:
527 s[0] = r.second;
528 s[2] = r.second;
529 break;
530 case XYZ:
531 s[0] = r.second;
532 s[1] = r.second;
533 s[2] = r.second;
534 break;
535 default:
536 MFEM_ABORT("Unsupported refinement type.");
537 }
538}
539
541{
542 for (int i=0; i<3; ++i)
543 if (s[i] > real_t{0})
544 s[i] = scale[i];
545}
546
548 : index(index)
549{
550 for (int i=0; i<3; ++i) { s[i] = 0.0; }
551 // Default case is XYZ type with scale 0.5.
552 SetScale(ScaledType(XYZ, 0.5));
553}
554
555Refinement::Refinement(int index, const std::initializer_list<ScaledType> &refs)
556 : index(index)
557{
558 for (int i=0; i<3; ++i) { s[i] = 0.0; }
559 if (refs.size() == 0)
560 {
561 // Default case is XYZ type with scale 0.5.
562 SetScale(ScaledType(XYZ, 0.5));
563 }
564 else
565 {
566 for (const auto & ref : refs)
567 {
568 SetScale(ref);
569 }
570 }
571}
572
573Refinement::Refinement(int index, char type, real_t scale)
574 : index(index)
575{
576 for (int i=0; i<3; ++i) { s[i] = 0.0; }
577 SetScale(ScaledType(type, scale));
578}
579
581{
582 char t{0}; // Set the X, Y or Z bit
583 for (int i = 0; i < 3; ++i)
584 if (s[i] > real_t{0})
585 t |= (1 << i);
586 return t;
587}
588
589void Refinement::Set(int element, char type, real_t scale)
590{
591 index = element;
592 for (int i=0; i<3; ++i) { s[i] = 0.0; }
593 SetScale(ScaledType(type, scale));
594}
595
596void Refinement::SetType(char type, real_t scale)
597{
598 for (int i=0; i<3; ++i) { s[i] = 0.0; }
599 SetScale(ScaledType(type, scale));
600}
601
603 : geom(geom), ref_type(0), tet_type(0), flag(0), index(-1)
604 , rank(0), attribute(attr), parent(-1)
605{
606 for (int i = 0; i < MaxElemNodes; i++) { node[i] = -1; }
607 for (int i = 0; i < MaxElemChildren; i++) { child[i] = -1; }
608
609 // NOTE: in 2D the 8/10-element node/child arrays are not optimal, however,
610 // testing shows we would only save 17% of the total NCMesh memory if
611 // 4-element arrays were used (e.g. through templates); we thus prefer to
612 // keep the code as simple as possible.
613}
614
615int NCMesh::NewHexahedron(int n0, int n1, int n2, int n3,
616 int n4, int n5, int n6, int n7,
617 int attr,
618 int fattr0, int fattr1, int fattr2,
619 int fattr3, int fattr4, int fattr5)
620{
621 // create new element, initialize nodes
622 int new_id = AddElement(Geometry::CUBE, attr);
623 Element &el = elements[new_id];
624
625 el.node[0] = n0, el.node[1] = n1, el.node[2] = n2, el.node[3] = n3;
626 el.node[4] = n4, el.node[5] = n5, el.node[6] = n6, el.node[7] = n7;
627
628 // get faces and assign face attributes
630 const GeomInfo &gi_hex = GI[Geometry::CUBE];
631 for (int i = 0; i < gi_hex.nf; i++)
632 {
633 const int* fv = gi_hex.faces[i];
634 f[i] = faces.Get(el.node[fv[0]], el.node[fv[1]],
635 el.node[fv[2]], el.node[fv[3]]);
636 }
637
638 f[0]->attribute = fattr0, f[1]->attribute = fattr1;
639 f[2]->attribute = fattr2, f[3]->attribute = fattr3;
640 f[4]->attribute = fattr4, f[5]->attribute = fattr5;
641
642 return new_id;
643}
644
645int NCMesh::NewWedge(int n0, int n1, int n2,
646 int n3, int n4, int n5,
647 int attr,
648 int fattr0, int fattr1,
649 int fattr2, int fattr3, int fattr4)
650{
651 // create new element, initialize nodes
652 int new_id = AddElement(Geometry::PRISM, attr);
653 Element &el = elements[new_id];
654
655 el.node[0] = n0, el.node[1] = n1, el.node[2] = n2;
656 el.node[3] = n3, el.node[4] = n4, el.node[5] = n5;
657
658 // get faces and assign face attributes
659 Face* f[5];
660 const GeomInfo &gi_wedge = GI[Geometry::PRISM];
661 for (int i = 0; i < gi_wedge.nf; i++)
662 {
663 const int* fv = gi_wedge.faces[i];
664 f[i] = faces.Get(el.node[fv[0]], el.node[fv[1]],
665 el.node[fv[2]], el.node[fv[3]]);
666 }
667
668 f[0]->attribute = fattr0;
669 f[1]->attribute = fattr1;
670 f[2]->attribute = fattr2;
671 f[3]->attribute = fattr3;
672 f[4]->attribute = fattr4;
673
674 return new_id;
675}
676
677int NCMesh::NewTetrahedron(int n0, int n1, int n2, int n3, int attr,
678 int fattr0, int fattr1, int fattr2, int fattr3)
679{
680 // create new element, initialize nodes
681 int new_id = AddElement(Geometry::TETRAHEDRON, attr);
682 Element &el = elements[new_id];
683
684 el.node[0] = n0, el.node[1] = n1, el.node[2] = n2, el.node[3] = n3;
685
686 // get faces and assign face attributes
687 Face* f[4];
688 const GeomInfo &gi_tet = GI[Geometry::TETRAHEDRON];
689 for (int i = 0; i < gi_tet.nf; i++)
690 {
691 const int* fv = gi_tet.faces[i];
692 f[i] = faces.Get(el.node[fv[0]], el.node[fv[1]], el.node[fv[2]]);
693 }
694
695 f[0]->attribute = fattr0;
696 f[1]->attribute = fattr1;
697 f[2]->attribute = fattr2;
698 f[3]->attribute = fattr3;
699
700 return new_id;
701}
702int NCMesh::NewPyramid(int n0, int n1, int n2, int n3, int n4, int attr,
703 int fattr0, int fattr1, int fattr2, int fattr3,
704 int fattr4)
705{
706 // create new element, initialize nodes
707 int new_id = AddElement(Geometry::PYRAMID, attr);
708 Element &el = elements[new_id];
709
710 el.node[0] = n0, el.node[1] = n1, el.node[2] = n2, el.node[3] = n3;
711 el.node[4] = n4;
712
713 // get faces and assign face attributes
714 Face* f[5];
715 const GeomInfo &gi_pyr = GI[Geometry::PYRAMID];
716 for (int i = 0; i < gi_pyr.nf; i++)
717 {
718 const int* fv = gi_pyr.faces[i];
719 f[i] = faces.Get(el.node[fv[0]], el.node[fv[1]],
720 el.node[fv[2]], el.node[fv[3]]);
721 }
722
723 f[0]->attribute = fattr0;
724 f[1]->attribute = fattr1;
725 f[2]->attribute = fattr2;
726 f[3]->attribute = fattr3;
727 f[4]->attribute = fattr4;
728
729 return new_id;
730}
731
732int NCMesh::NewQuadrilateral(int n0, int n1, int n2, int n3,
733 int attr,
734 int eattr0, int eattr1, int eattr2, int eattr3)
735{
736 // create new element, initialize nodes
737 int new_id = AddElement(Geometry::SQUARE, attr);
738 Element &el = elements[new_id];
739
740 el.node[0] = n0, el.node[1] = n1, el.node[2] = n2, el.node[3] = n3;
741
742 // get (degenerate) faces and assign face attributes
743 Face* f[4];
744 const GeomInfo &gi_quad = GI[Geometry::SQUARE];
745 for (int i = 0; i < gi_quad.nf; i++)
746 {
747 const int* fv = gi_quad.faces[i];
748 f[i] = faces.Get(el.node[fv[0]], el.node[fv[1]],
749 el.node[fv[2]], el.node[fv[3]]);
750 }
751
752 f[0]->attribute = eattr0, f[1]->attribute = eattr1;
753 f[2]->attribute = eattr2, f[3]->attribute = eattr3;
754
755 return new_id;
756}
757
758int NCMesh::NewTriangle(int n0, int n1, int n2,
759 int attr, int eattr0, int eattr1, int eattr2)
760{
761 // create new element, initialize nodes
762 int new_id = AddElement(Geometry::TRIANGLE, attr);
763 Element &el = elements[new_id];
764
765 el.node[0] = n0, el.node[1] = n1, el.node[2] = n2;
766
767 // get (degenerate) faces and assign face attributes
768 Face* f[3];
769 const GeomInfo &gi_tri = GI[Geometry::TRIANGLE];
770 for (int i = 0; i < gi_tri.nf; i++)
771 {
772 const int* fv = gi_tri.faces[i];
773 f[i] = faces.Get(el.node[fv[0]], el.node[fv[1]],
774 el.node[fv[2]], el.node[fv[3]]);
775 }
776
777 f[0]->attribute = eattr0;
778 f[1]->attribute = eattr1;
779 f[2]->attribute = eattr2;
780
781 return new_id;
782}
783
784int NCMesh::NewSegment(int n0, int n1, int attr, int vattr1, int vattr2)
785{
786 // create new element, initialize nodes
787 int new_id = AddElement(Geometry::SEGMENT, attr);
788 Element &el = elements[new_id];
789 el.node[0] = n0, el.node[1] = n1;
790
791 // get (degenerate) faces and assign face attributes
792 int v0 = el.node[0], v1 = el.node[1];
793 faces.Get(v0, v0, v0, v0)->attribute = vattr1;
794 faces.Get(v1, v1, v1, v1)->attribute = vattr2;
795
796 return new_id;
797}
798
799inline bool CubeFaceLeft(int node, int* n)
800{ return node == n[0] || node == n[3] || node == n[4] || node == n[7]; }
801
802inline bool CubeFaceRight(int node, int* n)
803{ return node == n[1] || node == n[2] || node == n[5] || node == n[6]; }
804
805inline bool CubeFaceFront(int node, int* n)
806{ return node == n[0] || node == n[1] || node == n[4] || node == n[5]; }
807
808inline bool CubeFaceBack(int node, int* n)
809{ return node == n[2] || node == n[3] || node == n[6] || node == n[7]; }
810
811inline bool CubeFaceBottom(int node, int* n)
812{ return node == n[0] || node == n[1] || node == n[2] || node == n[3]; }
813
814inline bool CubeFaceTop(int node, int* n)
815{ return node == n[4] || node == n[5] || node == n[6] || node == n[7]; }
816
817inline bool PrismFaceBottom(int node, int* n)
818{ return node == n[0] || node == n[1] || node == n[2]; }
819
820inline bool PrismFaceTop(int node, int* n)
821{ return node == n[3] || node == n[4] || node == n[5]; }
822
823
824void NCMesh::ForceRefinement(int vn1, int vn2, int vn3, int vn4)
825{
826 // get the element this face belongs to
827 Face* face = faces.Find(vn1, vn2, vn3, vn4);
828 if (!face) { return; }
829
830 MFEM_VERIFY(!IsParallel(), "ForceRefinement is supported only in serial");
831
832 const int elem = face->GetSingleElement();
833 Element &el = elements[elem];
834 MFEM_ASSERT(!el.ref_type, "element already refined.");
835
836 int* el_nodes = el.node;
837 if (el.Geom() == Geometry::CUBE)
838 {
839 Node* node12 = nodes.Find(vn1, vn2);
840 real_t scale = 0.5;
841
842 if (node12)
843 {
844 scale = GetScale(node12->GetScale(), vn1 > vn2);
845 }
846 else
847 {
848 Node* node34 = nodes.Find(vn3, vn4);
849 MFEM_ASSERT(node34, "Scale not set in NCMesh::ForceRefinement");
850 scale = GetScale(node34->GetScale(), vn4 > vn3);
851 }
852
853 // schedule the right split depending on face orientation
854 if ((CubeFaceLeft(vn1, el_nodes) && CubeFaceRight(vn2, el_nodes)) ||
855 (CubeFaceLeft(vn2, el_nodes) && CubeFaceRight(vn1, el_nodes)))
856 {
857 // X split
858 const bool rev = CubeFaceLeft(vn2, el_nodes) &&
859 CubeFaceRight(vn1, el_nodes);
860 ref_stack.Append(Refinement(elem, 1, GetScale(scale, rev)));
861 }
862 else if ((CubeFaceFront(vn1, el_nodes) && CubeFaceBack(vn2, el_nodes)) ||
863 (CubeFaceFront(vn2, el_nodes) && CubeFaceBack(vn1, el_nodes)))
864 {
865 // Y split
866 const bool rev = CubeFaceFront(vn2, el_nodes) &&
867 CubeFaceBack(vn1, el_nodes);
868 ref_stack.Append(Refinement(elem, 2, GetScale(scale, rev)));
869 }
870 else if ((CubeFaceBottom(vn1, el_nodes) && CubeFaceTop(vn2, el_nodes)) ||
871 (CubeFaceBottom(vn2, el_nodes) && CubeFaceTop(vn1, el_nodes)))
872 {
873 // Z split
874 const bool rev = CubeFaceBottom(vn2, el_nodes) &&
875 CubeFaceTop(vn1, el_nodes);
876 ref_stack.Append(Refinement(elem, 4, GetScale(scale, rev)));
877 }
878 else
879 {
880 MFEM_ABORT("Inconsistent element/face structure.");
881 }
882 }
883 else if (el.Geom() == Geometry::PRISM)
884 {
885 if ((PrismFaceTop(vn1, el_nodes) && PrismFaceBottom(vn4, el_nodes)) ||
886 (PrismFaceTop(vn4, el_nodes) && PrismFaceBottom(vn1, el_nodes)))
887 {
888 ref_stack.Append(Refinement(elem, 3)); // XY split
889 }
890 else if ((PrismFaceTop(vn1, el_nodes) && PrismFaceBottom(vn2, el_nodes)) ||
891 (PrismFaceTop(vn2, el_nodes) && PrismFaceBottom(vn1, el_nodes)))
892 {
893 ref_stack.Append(Refinement(elem, 4)); // Z split
894 }
895 else
896 {
897 MFEM_ABORT("Inconsistent element/face structure.");
898 }
899 }
900 else
901 {
902 MFEM_ABORT("Unsupported geometry.")
903 }
904}
905
906
907void NCMesh::FindEdgeElements(int vn1, int vn2, int vn3, int vn4,
908 Array<MeshId> &elem_edge) const
909{
910 // Assuming that f = (vn1, vn2, vn3, vn4) is a quad face and e = (vn1, vn4)
911 // is its edge, this function finds the N elements sharing e, and returns the
912 // N different MeshIds of the edge (i.e., different element-local pairs
913 // describing the edge).
914
915 int ev1 = vn1, ev2 = vn4;
916
917 // follow face refinement towards 'vn1', get an existing face
918 int split, mid[5];
919 real_t scale;
920 while ((split = QuadFaceSplitType(vn1, vn2, vn3, vn4, scale, mid)) > 0)
921 {
922 if (split == 1) // vertical
923 {
924 vn2 = mid[0]; vn3 = mid[2];
925 }
926 else // horizontal
927 {
928 vn3 = mid[1]; vn4 = mid[3];
929 }
930 }
931
932 const Face *face = faces.Find(vn1, vn2, vn3, vn4);
933 MFEM_ASSERT(face != NULL, "Face not found: "
934 << vn1 << ", " << vn2 << ", " << vn3 << ", " << vn4
935 << " (edge " << ev1 << "-" << ev2 << ").");
936
937 int elem = face->GetSingleElement();
938 int local = find_node(elements[elem], vn1);
939
940 Array<int> cousins;
941 FindVertexCousins(elem, local, cousins);
942
943 elem_edge.SetSize(0);
944 for (int i = 0; i < cousins.Size(); i++)
945 {
946 local = find_element_edge(elements[cousins[i]], ev1, ev2, false);
947 if (local > 0)
948 {
949 elem_edge.Append(MeshId(-1, cousins[i], local, Geometry::SEGMENT));
950 }
951 }
952}
953
954
955void NCMesh::CheckAnisoPrism(int vn1, int vn2, int vn3, int vn4,
956 const Refinement *refs, int nref)
957{
958 MeshId buf[4];
959 Array<MeshId> eid(buf, 4);
960 FindEdgeElements(vn1, vn2, vn3, vn4, eid);
961
962 // see if there is an element that has not been force-refined yet
963 for (int i = 0, j; i < eid.Size(); i++)
964 {
965 int elem = eid[i].element;
966 for (j = 0; j < nref; j++)
967 {
968 if (refs[j].index == elem) { break; }
969 }
970 if (j == nref) // elem not found in refs[]
971 {
972 // schedule prism refinement along Z axis
973 MFEM_ASSERT(elements[elem].Geom() == Geometry::PRISM, "");
974 ref_stack.Append(Refinement(elem, 4));
975 }
976 }
977}
978
979
980void NCMesh::CheckAnisoFace(int vn1, int vn2, int vn3, int vn4,
981 int mid12, int mid34, int level)
982{
983 // When a face is getting split anisotropically (without loss of generality
984 // we assume a "vertical" split here, see picture), it is important to make
985 // sure that the mid-face vertex node (midf) has mid34 and mid12 as parents.
986 // This is necessary for the face traversal algorithm and at places like
987 // Refine() that assume the mid-edge nodes to be accessible through the right
988 // parents. However, midf may already exist under the parents mid41 and
989 // mid23. In that case we need to "reparent" midf, i.e., reinsert it to the
990 // hash-table under the correct parents. This doesn't affect other nodes as
991 // all IDs stay the same, only the face refinement "tree" is affected.
992 //
993 // vn4 mid34 vn3
994 // *------*------*
995 // | | |
996 // | |midf |
997 // mid41 *- - - *- - - * mid23
998 // | | |
999 // | | |
1000 // *------*------*
1001 // vn1 mid12 vn2
1002 //
1003 // This function is recursive, because the above applies to any node along
1004 // the middle vertical edge. The function calls itself again for the bottom
1005 // and upper half of the above picture.
1006
1007 const int mid23 = FindMidEdgeNode(vn2, vn3);
1008 const int mid41 = FindMidEdgeNode(vn4, vn1);
1009
1010 if (mid23 >= 0 && mid41 >= 0)
1011 {
1012 int midf = nodes.FindId(mid23, mid41);
1013 if (midf >= 0)
1014 {
1015 Node* midfNode = nodes.Find(mid23, mid41);
1016
1017 if (midfNode)
1018 {
1019 Node* node12 = nodes.Find(vn1, vn2);
1020 if (node12)
1021 {
1022 const bool rev = (vn1 < vn2) != (mid41 < mid23);
1023 midfNode->SetScale(GetScale(node12->GetScale(), rev));
1024 }
1025 else
1026 {
1027 Node* node34 = nodes.Find(vn3, vn4);
1028 if (node34)
1029 {
1030 const bool rev = (vn4 < vn3) != (mid41 < mid23);
1031 midfNode->SetScale(GetScale(node34->GetScale(), rev));
1032 }
1033 }
1034 }
1035
1036 reparents.Append(Triple<int, int, int>(midf, mid12, mid34));
1037
1038 Node* node23 = nodes.Find(vn2, vn3);
1039 reparent_scale.Append(GetScale(node23->GetScale(), vn2 > vn3));
1040
1041 int rs = ref_stack.Size();
1042
1043 CheckAnisoFace(vn1, vn2, mid23, mid41, mid12, midf, level+1);
1044 CheckAnisoFace(mid41, mid23, vn3, vn4, midf, mid34, level+1);
1045
1046 if (HavePrisms() && nodes[midf].HasEdge())
1047 {
1048 // Check if there is a prism with edge (mid23, mid41) that we may
1049 // have missed in 'CheckAnisoFace', and force-refine it if present.
1050
1051 if (ref_stack.Size() > rs)
1052 {
1053 CheckAnisoPrism(mid23, vn3, vn4, mid41,
1054 &ref_stack[rs], ref_stack.Size() - rs);
1055 }
1056 else
1057 {
1058 CheckAnisoPrism(mid23, vn3, vn4, mid41, NULL, 0);
1059 }
1060 }
1061
1062 // perform the reparents all at once at the end
1063 if (level == 0)
1064 {
1065 for (int i = 0; i < reparents.Size(); i++)
1066 {
1067 const Triple<int, int, int> &tr = reparents[i];
1068 ReparentNode(tr.one, tr.two, tr.three, reparent_scale[i]);
1069 }
1070 reparents.DeleteAll();
1072 }
1073 return;
1074 }
1075 }
1076
1077 // Also, this is the place where forced refinements begin. In the picture
1078 // above, edges mid12-midf and midf-mid34 should actually exist in the
1079 // neighboring elements, otherwise the mesh is inconsistent and needs to be
1080 // fixed. Example: suppose an element is being refined isotropically (!)
1081 // whose neighbors across some face look like this:
1082 //
1083 // *--------*--------*
1084 // | d | e |
1085 // *--------*--------*
1086 // | c |
1087 // *--------*--------*
1088 // | | |
1089 // | a | b |
1090 // | | |
1091 // *--------*--------*
1092 //
1093 // Element 'c' needs to be refined vertically for the mesh to remain valid.
1094
1095 if (level > 0)
1096 {
1097 ForceRefinement(vn1, vn2, vn3, vn4);
1098 }
1099}
1100
1101void NCMesh::CheckIsoFace(int vn1, int vn2, int vn3, int vn4,
1102 int en1, int en2, int en3, int en4, int midf)
1103{
1104 if (!Iso)
1105 {
1106 /* If anisotropic refinements are present in the mesh, we need to check
1107 isotropically split faces as well, see second comment in CheckAnisoFace
1108 above. */
1109
1110 CheckAnisoFace(vn1, vn2, en2, en4, en1, midf);
1111 CheckAnisoFace(en4, en2, vn3, vn4, midf, en3);
1112 CheckAnisoFace(vn4, vn1, en1, en3, en4, midf);
1113 CheckAnisoFace(en3, en1, vn2, vn3, midf, en2);
1114 }
1115}
1116
1117void NCMesh::SetNodeScale(int p0, int p1, real_t scale)
1118{
1119 Node* node = nodes.Find(p0, p1);
1120 if (node) { node->SetScale(GetScale(scale, p0 > p1)); }
1121}
1122
1123void NCMesh::RefineElement(int elem, char ref_type)
1124{
1125 RefineElement(Refinement(elem, ref_type));
1126}
1127
1129{
1130 const int elem = ref.index;
1131 char ref_type = ref.GetType();
1132 const real_t scale_x = ref.s[0];
1133 const real_t scale_y = ref.s[1];
1134 const real_t scale_z = ref.s[2];
1135
1136 if (!ref_type) { return; }
1137
1138 // handle elements that may have been (force-) refined already
1139 Element &el = elements[elem];
1140 if (el.ref_type)
1141 {
1142 const char remaining = ref_type & ~el.ref_type;
1143 if (remaining)
1144 {
1145 // do the remaining splits on the children
1146 for (int i = 0; i < MaxElemChildren; i++)
1147 {
1148 if (el.child[i] >= 0)
1149 {
1150 Refinement child_ref(el.child[i], remaining);
1151 child_ref.SetScaleForType(ref.s);
1152 RefineElement(child_ref);
1153 }
1154 }
1155 }
1156 return;
1157 }
1158
1159 /*mfem::out << "Refining element " << elem << " (" << el.node[0] << ", " <<
1160 el.node[1] << ", " << el.node[2] << ", " << el.node[3] << ", " <<
1161 el.node[4] << ", " << el.node[5] << ", " << el.node[6] << ", " <<
1162 el.node[7] << "), " << "ref_type " << int(ref_type) << std::endl;*/
1163
1164 int* no = el.node;
1165 int attr = el.attribute;
1166
1167 int child[MaxElemChildren];
1168 for (int i = 0; i < MaxElemChildren; i++) { child[i] = -1; }
1169
1170 // get parent's face attributes
1171 int fa[MaxElemFaces];
1172 GeomInfo& gi = GI[el.Geom()];
1173 for (int i = 0; i < gi.nf; i++)
1174 {
1175 const int* fv = gi.faces[i];
1176 Face* face = faces.Find(no[fv[0]], no[fv[1]], no[fv[2]], no[fv[3]]);
1177 fa[i] = face->attribute;
1178 }
1179
1180 // create child elements
1181 if (el.Geom() == Geometry::CUBE)
1182 {
1183 // Cube vertex numbering is assumed to be as follows:
1184 //
1185 // 7 6
1186 // +-----------+ Faces: 0 bottom
1187 // /| /| 1 front
1188 // 4 / | 5 / | 2 right
1189 // +-----------+ | 3 back
1190 // | | | | 4 left
1191 // | +--------|--+ 5 top
1192 // | / 3 | / 2 Z Y
1193 // |/ |/ |/
1194 // +-----------+ *--X
1195 // 0 1
1196
1197 if (ref_type == Refinement::X) // split along X axis
1198 {
1199 const int mid01 = GetMidEdgeNode(no[0], no[1]);
1200 const int mid23 = GetMidEdgeNode(no[2], no[3]);
1201 const int mid45 = GetMidEdgeNode(no[4], no[5]);
1202 const int mid67 = GetMidEdgeNode(no[6], no[7]);
1203
1204 SetNodeScale(no[0], no[1], scale_x);
1205 SetNodeScale(no[3], no[2], scale_x);
1206 SetNodeScale(no[4], no[5], scale_x);
1207 SetNodeScale(no[7], no[6], scale_x);
1208
1209 child[0] = NewHexahedron(no[0], mid01, mid23, no[3],
1210 no[4], mid45, mid67, no[7], attr,
1211 fa[0], fa[1], -1, fa[3], fa[4], fa[5]);
1212
1213 child[1] = NewHexahedron(mid01, no[1], no[2], mid23,
1214 mid45, no[5], no[6], mid67, attr,
1215 fa[0], fa[1], fa[2], fa[3], -1, fa[5]);
1216
1217 CheckAnisoFace(no[0], no[1], no[5], no[4], mid01, mid45);
1218 CheckAnisoFace(no[2], no[3], no[7], no[6], mid23, mid67);
1219 CheckAnisoFace(no[4], no[5], no[6], no[7], mid45, mid67);
1220 CheckAnisoFace(no[3], no[2], no[1], no[0], mid23, mid01);
1221 }
1222 else if (ref_type == Refinement::Y) // split along Y axis
1223 {
1224 const int mid12 = GetMidEdgeNode(no[1], no[2]);
1225 const int mid30 = GetMidEdgeNode(no[3], no[0]);
1226 const int mid56 = GetMidEdgeNode(no[5], no[6]);
1227 const int mid74 = GetMidEdgeNode(no[7], no[4]);
1228
1229 SetNodeScale(no[1], no[2], scale_y);
1230 SetNodeScale(no[0], no[3], scale_y);
1231 SetNodeScale(no[5], no[6], scale_y);
1232 SetNodeScale(no[4], no[7], scale_y);
1233
1234 child[0] = NewHexahedron(no[0], no[1], mid12, mid30,
1235 no[4], no[5], mid56, mid74, attr,
1236 fa[0], fa[1], fa[2], -1, fa[4], fa[5]);
1237
1238 child[1] = NewHexahedron(mid30, mid12, no[2], no[3],
1239 mid74, mid56, no[6], no[7], attr,
1240 fa[0], -1, fa[2], fa[3], fa[4], fa[5]);
1241
1242 CheckAnisoFace(no[1], no[2], no[6], no[5], mid12, mid56);
1243 CheckAnisoFace(no[3], no[0], no[4], no[7], mid30, mid74);
1244 CheckAnisoFace(no[5], no[6], no[7], no[4], mid56, mid74);
1245 CheckAnisoFace(no[0], no[3], no[2], no[1], mid30, mid12);
1246 }
1247 else if (ref_type == Refinement::Z) // split along Z axis
1248 {
1249 const int mid04 = GetMidEdgeNode(no[0], no[4]);
1250 const int mid15 = GetMidEdgeNode(no[1], no[5]);
1251 const int mid26 = GetMidEdgeNode(no[2], no[6]);
1252 const int mid37 = GetMidEdgeNode(no[3], no[7]);
1253
1254 SetNodeScale(no[0], no[4], scale_z);
1255 SetNodeScale(no[1], no[5], scale_z);
1256 SetNodeScale(no[2], no[6], scale_z);
1257 SetNodeScale(no[3], no[7], scale_z);
1258
1259 child[0] = NewHexahedron(no[0], no[1], no[2], no[3],
1260 mid04, mid15, mid26, mid37, attr,
1261 fa[0], fa[1], fa[2], fa[3], fa[4], -1);
1262
1263 child[1] = NewHexahedron(mid04, mid15, mid26, mid37,
1264 no[4], no[5], no[6], no[7], attr,
1265 -1, fa[1], fa[2], fa[3], fa[4], fa[5]);
1266
1267 CheckAnisoFace(no[4], no[0], no[1], no[5], mid04, mid15);
1268 CheckAnisoFace(no[5], no[1], no[2], no[6], mid15, mid26);
1269 CheckAnisoFace(no[6], no[2], no[3], no[7], mid26, mid37);
1270 CheckAnisoFace(no[7], no[3], no[0], no[4], mid37, mid04);
1271 }
1272 else if (ref_type == Refinement::XY) // XY split
1273 {
1274 const int mid01 = GetMidEdgeNode(no[0], no[1]);
1275 const int mid12 = GetMidEdgeNode(no[1], no[2]);
1276 const int mid23 = GetMidEdgeNode(no[2], no[3]);
1277 const int mid30 = GetMidEdgeNode(no[3], no[0]);
1278
1279 const int mid45 = GetMidEdgeNode(no[4], no[5]);
1280 const int mid56 = GetMidEdgeNode(no[5], no[6]);
1281 const int mid67 = GetMidEdgeNode(no[6], no[7]);
1282 const int mid74 = GetMidEdgeNode(no[7], no[4]);
1283
1284 const int midf0 = GetMidFaceNode(mid23, mid12, mid01, mid30);
1285 const int midf5 = GetMidFaceNode(mid45, mid56, mid67, mid74);
1286
1287 SetNodeScale(no[0], no[1], scale_x);
1288 SetNodeScale(no[3], no[2], scale_x);
1289 SetNodeScale(no[4], no[5], scale_x);
1290 SetNodeScale(no[7], no[6], scale_x);
1291
1292 SetNodeScale(no[1], no[2], scale_y);
1293 SetNodeScale(no[0], no[3], scale_y);
1294 SetNodeScale(no[5], no[6], scale_y);
1295 SetNodeScale(no[4], no[7], scale_y);
1296
1297 SetNodeScale(mid30, mid12, scale_x);
1298 SetNodeScale(mid74, mid56, scale_x);
1299
1300 SetNodeScale(mid01, mid23, scale_y);
1301 SetNodeScale(mid45, mid67, scale_y);
1302
1303 child[0] = NewHexahedron(no[0], mid01, midf0, mid30,
1304 no[4], mid45, midf5, mid74, attr,
1305 fa[0], fa[1], -1, -1, fa[4], fa[5]);
1306
1307 child[1] = NewHexahedron(mid01, no[1], mid12, midf0,
1308 mid45, no[5], mid56, midf5, attr,
1309 fa[0], fa[1], fa[2], -1, -1, fa[5]);
1310
1311 child[2] = NewHexahedron(midf0, mid12, no[2], mid23,
1312 midf5, mid56, no[6], mid67, attr,
1313 fa[0], -1, fa[2], fa[3], -1, fa[5]);
1314
1315 child[3] = NewHexahedron(mid30, midf0, mid23, no[3],
1316 mid74, midf5, mid67, no[7], attr,
1317 fa[0], -1, -1, fa[3], fa[4], fa[5]);
1318
1319 CheckAnisoFace(no[0], no[1], no[5], no[4], mid01, mid45);
1320 CheckAnisoFace(no[1], no[2], no[6], no[5], mid12, mid56);
1321 CheckAnisoFace(no[2], no[3], no[7], no[6], mid23, mid67);
1322 CheckAnisoFace(no[3], no[0], no[4], no[7], mid30, mid74);
1323
1324 CheckIsoFace(no[3], no[2], no[1], no[0], mid23, mid12, mid01, mid30, midf0);
1325 CheckIsoFace(no[4], no[5], no[6], no[7], mid45, mid56, mid67, mid74, midf5);
1326 }
1327 else if (ref_type == Refinement::XZ) // XZ split
1328 {
1329 const int mid01 = GetMidEdgeNode(no[0], no[1]);
1330 const int mid23 = GetMidEdgeNode(no[2], no[3]);
1331 const int mid45 = GetMidEdgeNode(no[4], no[5]);
1332 const int mid67 = GetMidEdgeNode(no[6], no[7]);
1333
1334 const int mid04 = GetMidEdgeNode(no[0], no[4]);
1335 const int mid15 = GetMidEdgeNode(no[1], no[5]);
1336 const int mid26 = GetMidEdgeNode(no[2], no[6]);
1337 const int mid37 = GetMidEdgeNode(no[3], no[7]);
1338
1339 const int midf1 = GetMidFaceNode(mid01, mid15, mid45, mid04);
1340 const int midf3 = GetMidFaceNode(mid23, mid37, mid67, mid26);
1341
1342 SetNodeScale(no[0], no[1], scale_x);
1343 SetNodeScale(no[3], no[2], scale_x);
1344 SetNodeScale(no[4], no[5], scale_x);
1345 SetNodeScale(no[7], no[6], scale_x);
1346
1347 SetNodeScale(no[0], no[4], scale_z);
1348 SetNodeScale(no[1], no[5], scale_z);
1349 SetNodeScale(no[2], no[6], scale_z);
1350 SetNodeScale(no[3], no[7], scale_z);
1351
1352 SetNodeScale(mid04, mid15, scale_x);
1353 SetNodeScale(mid37, mid26, scale_x);
1354
1355 SetNodeScale(mid01, mid45, scale_z);
1356 SetNodeScale(mid23, mid67, scale_z);
1357
1358 child[0] = NewHexahedron(no[0], mid01, mid23, no[3],
1359 mid04, midf1, midf3, mid37, attr,
1360 fa[0], fa[1], -1, fa[3], fa[4], -1);
1361
1362 child[1] = NewHexahedron(mid01, no[1], no[2], mid23,
1363 midf1, mid15, mid26, midf3, attr,
1364 fa[0], fa[1], fa[2], fa[3], -1, -1);
1365
1366 child[2] = NewHexahedron(midf1, mid15, mid26, midf3,
1367 mid45, no[5], no[6], mid67, attr,
1368 -1, fa[1], fa[2], fa[3], -1, fa[5]);
1369
1370 child[3] = NewHexahedron(mid04, midf1, midf3, mid37,
1371 no[4], mid45, mid67, no[7], attr,
1372 -1, fa[1], -1, fa[3], fa[4], fa[5]);
1373
1374 CheckAnisoFace(no[3], no[2], no[1], no[0], mid23, mid01);
1375 CheckAnisoFace(no[2], no[6], no[5], no[1], mid26, mid15);
1376 CheckAnisoFace(no[6], no[7], no[4], no[5], mid67, mid45);
1377 CheckAnisoFace(no[7], no[3], no[0], no[4], mid37, mid04);
1378
1379 CheckIsoFace(no[0], no[1], no[5], no[4], mid01, mid15, mid45, mid04, midf1);
1380 CheckIsoFace(no[2], no[3], no[7], no[6], mid23, mid37, mid67, mid26, midf3);
1381 }
1382 else if (ref_type == Refinement::YZ) // YZ split
1383 {
1384 const int mid12 = GetMidEdgeNode(no[1], no[2]);
1385 const int mid30 = GetMidEdgeNode(no[3], no[0]);
1386 const int mid56 = GetMidEdgeNode(no[5], no[6]);
1387 const int mid74 = GetMidEdgeNode(no[7], no[4]);
1388
1389 const int mid04 = GetMidEdgeNode(no[0], no[4]);
1390 const int mid15 = GetMidEdgeNode(no[1], no[5]);
1391 const int mid26 = GetMidEdgeNode(no[2], no[6]);
1392 const int mid37 = GetMidEdgeNode(no[3], no[7]);
1393
1394 const int midf2 = GetMidFaceNode(mid12, mid26, mid56, mid15);
1395 const int midf4 = GetMidFaceNode(mid30, mid04, mid74, mid37);
1396
1397 SetNodeScale(no[1], no[2], scale_y);
1398 SetNodeScale(no[0], no[3], scale_y);
1399 SetNodeScale(no[5], no[6], scale_y);
1400 SetNodeScale(no[4], no[7], scale_y);
1401
1402 SetNodeScale(no[0], no[4], scale_z);
1403 SetNodeScale(no[1], no[5], scale_z);
1404 SetNodeScale(no[2], no[6], scale_z);
1405 SetNodeScale(no[3], no[7], scale_z);
1406
1407 SetNodeScale(mid15, mid26, scale_y);
1408 SetNodeScale(mid04, mid37, scale_y);
1409
1410 SetNodeScale(mid12, mid56, scale_z);
1411 SetNodeScale(mid30, mid74, scale_z);
1412
1413 child[0] = NewHexahedron(no[0], no[1], mid12, mid30,
1414 mid04, mid15, midf2, midf4, attr,
1415 fa[0], fa[1], fa[2], -1, fa[4], -1);
1416
1417 child[1] = NewHexahedron(mid30, mid12, no[2], no[3],
1418 midf4, midf2, mid26, mid37, attr,
1419 fa[0], -1, fa[2], fa[3], fa[4], -1);
1420
1421 child[2] = NewHexahedron(mid04, mid15, midf2, midf4,
1422 no[4], no[5], mid56, mid74, attr,
1423 -1, fa[1], fa[2], -1, fa[4], fa[5]);
1424
1425 child[3] = NewHexahedron(midf4, midf2, mid26, mid37,
1426 mid74, mid56, no[6], no[7], attr,
1427 -1, -1, fa[2], fa[3], fa[4], fa[5]);
1428
1429 CheckAnisoFace(no[4], no[0], no[1], no[5], mid04, mid15);
1430 CheckAnisoFace(no[0], no[3], no[2], no[1], mid30, mid12);
1431 CheckAnisoFace(no[3], no[7], no[6], no[2], mid37, mid26);
1432 CheckAnisoFace(no[7], no[4], no[5], no[6], mid74, mid56);
1433
1434 CheckIsoFace(no[1], no[2], no[6], no[5], mid12, mid26, mid56, mid15, midf2);
1435 CheckIsoFace(no[3], no[0], no[4], no[7], mid30, mid04, mid74, mid37, midf4);
1436 }
1437 else if (ref_type == Refinement::XYZ) // full isotropic refinement
1438 {
1439 const int mid01 = GetMidEdgeNode(no[0], no[1]);
1440 const int mid12 = GetMidEdgeNode(no[1], no[2]);
1441 const int mid23 = GetMidEdgeNode(no[2], no[3]);
1442 const int mid30 = GetMidEdgeNode(no[3], no[0]);
1443
1444 const int mid45 = GetMidEdgeNode(no[4], no[5]);
1445 const int mid56 = GetMidEdgeNode(no[5], no[6]);
1446 const int mid67 = GetMidEdgeNode(no[6], no[7]);
1447 const int mid74 = GetMidEdgeNode(no[7], no[4]);
1448
1449 const int mid04 = GetMidEdgeNode(no[0], no[4]);
1450 const int mid15 = GetMidEdgeNode(no[1], no[5]);
1451 const int mid26 = GetMidEdgeNode(no[2], no[6]);
1452 const int mid37 = GetMidEdgeNode(no[3], no[7]);
1453
1454 const int midf0 = GetMidFaceNode(mid23, mid12, mid01, mid30);
1455 const int midf1 = GetMidFaceNode(mid01, mid15, mid45, mid04);
1456 const int midf2 = GetMidFaceNode(mid12, mid26, mid56, mid15);
1457 const int midf3 = GetMidFaceNode(mid23, mid37, mid67, mid26);
1458 const int midf4 = GetMidFaceNode(mid30, mid04, mid74, mid37);
1459 const int midf5 = GetMidFaceNode(mid45, mid56, mid67, mid74);
1460
1461 const int midel = GetMidEdgeNode(midf1, midf3);
1462
1463 SetNodeScale(midf1, midf3, scale_y);
1464
1465 SetNodeScale(no[0], no[1], scale_x);
1466 SetNodeScale(no[3], no[2], scale_x);
1467 SetNodeScale(no[1], no[2], scale_y);
1468 SetNodeScale(no[0], no[3], scale_y);
1469
1470 SetNodeScale(no[4], no[5], scale_x);
1471 SetNodeScale(no[7], no[6], scale_x);
1472 SetNodeScale(no[5], no[6], scale_y);
1473 SetNodeScale(no[4], no[7], scale_y);
1474
1475 SetNodeScale(no[0], no[4], scale_z);
1476 SetNodeScale(no[1], no[5], scale_z);
1477 SetNodeScale(no[2], no[6], scale_z);
1478 SetNodeScale(no[3], no[7], scale_z);
1479
1480 SetNodeScale(mid01, mid23, scale_y);
1481 SetNodeScale(mid30, mid12, scale_x);
1482
1483 SetNodeScale(mid04, mid15, scale_x);
1484 SetNodeScale(mid01, mid45, scale_z);
1485
1486 SetNodeScale(mid15, mid26, scale_y);
1487 SetNodeScale(mid12, mid56, scale_z);
1488
1489 SetNodeScale(mid23, mid67, scale_z);
1490 SetNodeScale(mid37, mid26, scale_x);
1491
1492 SetNodeScale(mid30, mid74, scale_z);
1493 SetNodeScale(mid04, mid37, scale_y);
1494
1495 SetNodeScale(mid74, mid56, scale_x);
1496 SetNodeScale(mid45, mid67, scale_y);
1497
1498 child[0] = NewHexahedron(no[0], mid01, midf0, mid30,
1499 mid04, midf1, midel, midf4, attr,
1500 fa[0], fa[1], -1, -1, fa[4], -1);
1501
1502 child[1] = NewHexahedron(mid01, no[1], mid12, midf0,
1503 midf1, mid15, midf2, midel, attr,
1504 fa[0], fa[1], fa[2], -1, -1, -1);
1505
1506 child[2] = NewHexahedron(midf0, mid12, no[2], mid23,
1507 midel, midf2, mid26, midf3, attr,
1508 fa[0], -1, fa[2], fa[3], -1, -1);
1509
1510 child[3] = NewHexahedron(mid30, midf0, mid23, no[3],
1511 midf4, midel, midf3, mid37, attr,
1512 fa[0], -1, -1, fa[3], fa[4], -1);
1513
1514 child[4] = NewHexahedron(mid04, midf1, midel, midf4,
1515 no[4], mid45, midf5, mid74, attr,
1516 -1, fa[1], -1, -1, fa[4], fa[5]);
1517
1518 child[5] = NewHexahedron(midf1, mid15, midf2, midel,
1519 mid45, no[5], mid56, midf5, attr,
1520 -1, fa[1], fa[2], -1, -1, fa[5]);
1521
1522 child[6] = NewHexahedron(midel, midf2, mid26, midf3,
1523 midf5, mid56, no[6], mid67, attr,
1524 -1, -1, fa[2], fa[3], -1, fa[5]);
1525
1526 child[7] = NewHexahedron(midf4, midel, midf3, mid37,
1527 mid74, midf5, mid67, no[7], attr,
1528 -1, -1, -1, fa[3], fa[4], fa[5]);
1529
1530 CheckIsoFace(no[3], no[2], no[1], no[0], mid23, mid12, mid01, mid30, midf0);
1531 CheckIsoFace(no[0], no[1], no[5], no[4], mid01, mid15, mid45, mid04, midf1);
1532 CheckIsoFace(no[1], no[2], no[6], no[5], mid12, mid26, mid56, mid15, midf2);
1533 CheckIsoFace(no[2], no[3], no[7], no[6], mid23, mid37, mid67, mid26, midf3);
1534 CheckIsoFace(no[3], no[0], no[4], no[7], mid30, mid04, mid74, mid37, midf4);
1535 CheckIsoFace(no[4], no[5], no[6], no[7], mid45, mid56, mid67, mid74, midf5);
1536 }
1537 else
1538 {
1539 MFEM_ABORT("invalid refinement type.");
1540 }
1541
1542 if (ref_type != Refinement::XYZ) { Iso = false; }
1543 }
1544 else if (el.Geom() == Geometry::PRISM)
1545 {
1546 // Wedge vertex numbering:
1547 //
1548 // 5
1549 // _+_
1550 // _/ | \_ Faces: 0 bottom
1551 // 3 / | \ 4 1 top
1552 // +---------+ 2 front
1553 // | | | 3 right (1 2 5 4)
1554 // | _+_ | 4 left (2 0 3 5)
1555 // | _/ 2 \_ | Z Y
1556 // |/ \| | /
1557 // +---------+ *--X
1558 // 0 1
1559
1560 if (ref_type < 4) // XY refinement (split in 4 wedges)
1561 {
1562 ref_type = Refinement::XY; // for consistency
1563
1564 int mid01 = GetMidEdgeNode(no[0], no[1]);
1565 int mid12 = GetMidEdgeNode(no[1], no[2]);
1566 int mid20 = GetMidEdgeNode(no[2], no[0]);
1567
1568 int mid34 = GetMidEdgeNode(no[3], no[4]);
1569 int mid45 = GetMidEdgeNode(no[4], no[5]);
1570 int mid53 = GetMidEdgeNode(no[5], no[3]);
1571
1572 child[0] = NewWedge(no[0], mid01, mid20,
1573 no[3], mid34, mid53, attr,
1574 fa[0], fa[1], fa[2], -1, fa[4]);
1575
1576 child[1] = NewWedge(mid01, no[1], mid12,
1577 mid34, no[4], mid45, attr,
1578 fa[0], fa[1], fa[2], fa[3], -1);
1579
1580 child[2] = NewWedge(mid20, mid12, no[2],
1581 mid53, mid45, no[5], attr,
1582 fa[0], fa[1], -1, fa[3], fa[4]);
1583
1584 child[3] = NewWedge(mid12, mid20, mid01,
1585 mid45, mid53, mid34, attr,
1586 fa[0], fa[1], -1, -1, -1);
1587
1588 CheckAnisoFace(no[0], no[1], no[4], no[3], mid01, mid34);
1589 CheckAnisoFace(no[1], no[2], no[5], no[4], mid12, mid45);
1590 CheckAnisoFace(no[2], no[0], no[3], no[5], mid20, mid53);
1591 }
1592 else if (ref_type == Refinement::Z) // Z refinement only (split in 2 wedges)
1593 {
1594 int mid03 = GetMidEdgeNode(no[0], no[3]);
1595 int mid14 = GetMidEdgeNode(no[1], no[4]);
1596 int mid25 = GetMidEdgeNode(no[2], no[5]);
1597
1598 child[0] = NewWedge(no[0], no[1], no[2],
1599 mid03, mid14, mid25, attr,
1600 fa[0], -1, fa[2], fa[3], fa[4]);
1601
1602 child[1] = NewWedge(mid03, mid14, mid25,
1603 no[3], no[4], no[5], attr,
1604 -1, fa[1], fa[2], fa[3], fa[4]);
1605
1606 CheckAnisoFace(no[3], no[0], no[1], no[4], mid03, mid14);
1607 CheckAnisoFace(no[4], no[1], no[2], no[5], mid14, mid25);
1608 CheckAnisoFace(no[5], no[2], no[0], no[3], mid25, mid03);
1609 }
1610 else // ref_type > 4, full isotropic refinement (split in 8 wedges)
1611 {
1612 ref_type = Refinement::XYZ; // for consistency
1613
1614 int mid01 = GetMidEdgeNode(no[0], no[1]);
1615 int mid12 = GetMidEdgeNode(no[1], no[2]);
1616 int mid20 = GetMidEdgeNode(no[2], no[0]);
1617
1618 int mid34 = GetMidEdgeNode(no[3], no[4]);
1619 int mid45 = GetMidEdgeNode(no[4], no[5]);
1620 int mid53 = GetMidEdgeNode(no[5], no[3]);
1621
1622 int mid03 = GetMidEdgeNode(no[0], no[3]);
1623 int mid14 = GetMidEdgeNode(no[1], no[4]);
1624 int mid25 = GetMidEdgeNode(no[2], no[5]);
1625
1626 int midf2 = GetMidFaceNode(mid01, mid14, mid34, mid03);
1627 int midf3 = GetMidFaceNode(mid12, mid25, mid45, mid14);
1628 int midf4 = GetMidFaceNode(mid20, mid03, mid53, mid25);
1629
1630 child[0] = NewWedge(no[0], mid01, mid20,
1631 mid03, midf2, midf4, attr,
1632 fa[0], -1, fa[2], -1, fa[4]);
1633
1634 child[1] = NewWedge(mid01, no[1], mid12,
1635 midf2, mid14, midf3, attr,
1636 fa[0], -1, fa[2], fa[3], -1);
1637
1638 child[2] = NewWedge(mid20, mid12, no[2],
1639 midf4, midf3, mid25, attr,
1640 fa[0], -1, -1, fa[3], fa[4]);
1641
1642 child[3] = NewWedge(mid12, mid20, mid01,
1643 midf3, midf4, midf2, attr,
1644 fa[0], -1, -1, -1, -1);
1645
1646 child[4] = NewWedge(mid03, midf2, midf4,
1647 no[3], mid34, mid53, attr,
1648 -1, fa[1], fa[2], -1, fa[4]);
1649
1650 child[5] = NewWedge(midf2, mid14, midf3,
1651 mid34, no[4], mid45, attr,
1652 -1, fa[1], fa[2], fa[3], -1);
1653
1654 child[6] = NewWedge(midf4, midf3, mid25,
1655 mid53, mid45, no[5], attr,
1656 -1, fa[1], -1, fa[3], fa[4]);
1657
1658 child[7] = NewWedge(midf3, midf4, midf2,
1659 mid45, mid53, mid34, attr,
1660 -1, fa[1], -1, -1, -1);
1661
1662 CheckIsoFace(no[0], no[1], no[4], no[3], mid01, mid14, mid34, mid03, midf2);
1663 CheckIsoFace(no[1], no[2], no[5], no[4], mid12, mid25, mid45, mid14, midf3);
1664 CheckIsoFace(no[2], no[0], no[3], no[5], mid20, mid03, mid53, mid25, midf4);
1665 }
1666
1667 if (ref_type != Refinement::XYZ) { Iso = false; }
1668 }
1669 else if (el.Geom() == Geometry::TETRAHEDRON)
1670 {
1671 // Tetrahedron vertex numbering:
1672 //
1673 // 3
1674 // + Faces: 0 back (1, 2, 3)
1675 // |\\_ 1 left (0, 3, 2)
1676 // || \_ 2 front (0, 1, 3)
1677 // | \ \_ 3 bottom (0, 1, 2)
1678 // | +__ \_
1679 // | /2 \__ \_ Z Y
1680 // |/ \__\ | /
1681 // +------------+ *--X
1682 // 0 1
1683
1684 ref_type = Refinement::XYZ; // for consistency
1685
1686 int mid01 = GetMidEdgeNode(no[0], no[1]);
1687 int mid12 = GetMidEdgeNode(no[1], no[2]);
1688 int mid02 = GetMidEdgeNode(no[2], no[0]);
1689
1690 int mid03 = GetMidEdgeNode(no[0], no[3]);
1691 int mid13 = GetMidEdgeNode(no[1], no[3]);
1692 int mid23 = GetMidEdgeNode(no[2], no[3]);
1693
1694 child[0] = NewTetrahedron(no[0], mid01, mid02, mid03, attr,
1695 -1, fa[1], fa[2], fa[3]);
1696
1697 child[1] = NewTetrahedron(mid01, no[1], mid12, mid13, attr,
1698 fa[0], -1, fa[2], fa[3]);
1699
1700 child[2] = NewTetrahedron(mid02, mid12, no[2], mid23, attr,
1701 fa[0], fa[1], -1, fa[3]);
1702
1703 child[3] = NewTetrahedron(mid03, mid13, mid23, no[3], attr,
1704 fa[0], fa[1], fa[2], -1);
1705
1706 // There are three ways to split the inner octahedron. A good strategy is
1707 // to use the shortest diagonal. At the moment we don't have the geometric
1708 // information in this class to determine which diagonal is the shortest,
1709 // but it seems that with reasonable shapes of the coarse tets and MFEM's
1710 // default tet orientation, always using tet_type == 0 produces stable
1711 // refinements. Types 1 and 2 are unused for now.
1712 el.tet_type = 0;
1713
1714 if (el.tet_type == 0) // shortest diagonal mid01--mid23
1715 {
1716 child[4] = NewTetrahedron(mid01, mid23, mid02, mid03, attr,
1717 fa[1], -1, -1, -1);
1718
1719 child[5] = NewTetrahedron(mid01, mid23, mid03, mid13, attr,
1720 -1, fa[2], -1, -1);
1721
1722 child[6] = NewTetrahedron(mid01, mid23, mid13, mid12, attr,
1723 fa[0], -1, -1, -1);
1724
1725 child[7] = NewTetrahedron(mid01, mid23, mid12, mid02, attr,
1726 -1, fa[3], -1, -1);
1727 }
1728 else if (el.tet_type == 1) // shortest diagonal mid12--mid03
1729 {
1730 child[4] = NewTetrahedron(mid03, mid01, mid02, mid12, attr,
1731 fa[3], -1, -1, -1);
1732
1733 child[5] = NewTetrahedron(mid03, mid02, mid23, mid12, attr,
1734 -1, -1, -1, fa[1]);
1735
1736 child[6] = NewTetrahedron(mid03, mid23, mid13, mid12, attr,
1737 fa[0], -1, -1, -1);
1738
1739 child[7] = NewTetrahedron(mid03, mid13, mid01, mid12, attr,
1740 -1, -1, -1, fa[2]);
1741 }
1742 else // el.tet_type == 2, shortest diagonal mid02--mid13
1743 {
1744 child[4] = NewTetrahedron(mid02, mid01, mid13, mid03, attr,
1745 fa[2], -1, -1, -1);
1746
1747 child[5] = NewTetrahedron(mid02, mid03, mid13, mid23, attr,
1748 -1, -1, fa[1], -1);
1749
1750 child[6] = NewTetrahedron(mid02, mid23, mid13, mid12, attr,
1751 fa[0], -1, -1, -1);
1752
1753 child[7] = NewTetrahedron(mid02, mid12, mid13, mid01, attr,
1754 -1, -1, fa[3], -1);
1755 }
1756 }
1757 else if (el.Geom() == Geometry::PYRAMID)
1758 {
1759 // Pyramid vertex numbering:
1760 //
1761 // 4
1762 // + \_ Faces: 0 bottom (3,2,1,0)
1763 // |\\_ \_ 1 front (0, 1, 4)
1764 // || \_ \__ 2 right (1, 2, 4)
1765 // | \ \_ \__ 3 back (2, 3, 4)
1766 // | +____\_ ____\ 4 left (3, 0, 4)
1767 // | /3 \_ 2 Z Y
1768 // |/ \ / | /
1769 // +------------+ *--X
1770 // 0 1
1771
1772 ref_type = Refinement::XYZ; // for consistency
1773
1774 int mid01 = GetMidEdgeNode(no[0], no[1]);
1775 int mid12 = GetMidEdgeNode(no[1], no[2]);
1776 int mid23 = GetMidEdgeNode(no[2], no[3]);
1777 int mid03 = GetMidEdgeNode(no[0], no[3]);
1778 int mid04 = GetMidEdgeNode(no[0], no[4]);
1779 int mid14 = GetMidEdgeNode(no[1], no[4]);
1780 int mid24 = GetMidEdgeNode(no[2], no[4]);
1781 int mid34 = GetMidEdgeNode(no[3], no[4]);
1782 int midf0 = GetMidFaceNode(mid23, mid12, mid01, mid03);
1783
1784 child[0] = NewPyramid(no[0], mid01, midf0, mid03, mid04,
1785 attr, fa[0], fa[1], -1, -1, fa[4]);
1786
1787 child[1] = NewPyramid(mid01, no[1], mid12, midf0, mid14,
1788 attr, fa[0], fa[1], fa[2], -1, -1);
1789
1790 child[2] = NewPyramid(midf0, mid12, no[2], mid23, mid24,
1791 attr, fa[0], -1, fa[2], fa[3], -1);
1792
1793 child[3] = NewPyramid(mid03, midf0, mid23, no[3], mid34,
1794 attr, fa[0], -1, -1, fa[3], fa[4]);
1795
1796 child[4] = NewPyramid(mid24, mid14, mid04, mid34, midf0,
1797 attr, -1, -1, -1, -1, -1);
1798
1799 child[5] = NewPyramid(mid04, mid14, mid24, mid34, no[4],
1800 attr, -1, fa[1], fa[2], fa[3], fa[4]);
1801
1802 child[6] = NewTetrahedron(mid01, midf0, mid04, mid14,
1803 attr, -1, -1, -1, fa[1]);
1804
1805 child[7] = NewTetrahedron(midf0, mid14, mid12, mid24,
1806 attr, -1, -1, fa[2], -1);
1807
1808 child[8] = NewTetrahedron(midf0, mid23, mid34, mid24,
1809 attr, -1, -1, fa[3], -1);
1810
1811 child[9] = NewTetrahedron(mid03, mid04, midf0, mid34,
1812 attr, -1, fa[4], -1, -1);
1813
1814 CheckIsoFace(no[3], no[2], no[1], no[0], mid23, mid12, mid01, mid03, midf0);
1815 }
1816 else if (el.Geom() == Geometry::SQUARE)
1817 {
1818 ref_type &= 0x3; // ignore Z bit
1819
1820 if (ref_type == Refinement::X) // X split
1821 {
1822 const int mid01 = nodes.GetId(no[0], no[1]);
1823 const int mid23 = nodes.GetId(no[2], no[3]);
1824
1825 SetNodeScale(no[0], no[1], scale_x);
1826 SetNodeScale(no[3], no[2], scale_x);
1827
1828 child[0] = NewQuadrilateral(no[0], mid01, mid23, no[3],
1829 attr, fa[0], -1, fa[2], fa[3]);
1830
1831 child[1] = NewQuadrilateral(mid01, no[1], no[2], mid23,
1832 attr, fa[0], fa[1], fa[2], -1);
1833 }
1834 else if (ref_type == Refinement::Y) // Y split
1835 {
1836 const int mid12 = nodes.GetId(no[1], no[2]);
1837 const int mid30 = nodes.GetId(no[3], no[0]);
1838
1839 SetNodeScale(no[1], no[2], scale_y);
1840 SetNodeScale(no[0], no[3], scale_y);
1841
1842 child[0] = NewQuadrilateral(no[0], no[1], mid12, mid30,
1843 attr, fa[0], fa[1], -1, fa[3]);
1844
1845 child[1] = NewQuadrilateral(mid30, mid12, no[2], no[3],
1846 attr, -1, fa[1], fa[2], fa[3]);
1847 }
1848 else if (ref_type == Refinement::XY) // iso split
1849 {
1850 const int mid01 = nodes.GetId(no[0], no[1]);
1851 const int mid12 = nodes.GetId(no[1], no[2]);
1852 const int mid23 = nodes.GetId(no[2], no[3]);
1853 const int mid30 = nodes.GetId(no[3], no[0]);
1854
1855 const int midel = nodes.GetId(mid01, mid23);
1856
1857 SetNodeScale(no[0], no[1], scale_x);
1858 SetNodeScale(no[3], no[2], scale_x);
1859
1860 SetNodeScale(no[1], no[2], scale_y);
1861 SetNodeScale(no[0], no[3], scale_y);
1862
1863 SetNodeScale(mid01, mid23, scale_y);
1864
1865 child[0] = NewQuadrilateral(no[0], mid01, midel, mid30,
1866 attr, fa[0], -1, -1, fa[3]);
1867
1868 child[1] = NewQuadrilateral(mid01, no[1], mid12, midel,
1869 attr, fa[0], fa[1], -1, -1);
1870
1871 child[2] = NewQuadrilateral(midel, mid12, no[2], mid23,
1872 attr, -1, fa[1], fa[2], -1);
1873
1874 child[3] = NewQuadrilateral(mid30, midel, mid23, no[3],
1875 attr, -1, -1, fa[2], fa[3]);
1876 }
1877 else
1878 {
1879 MFEM_ABORT("Invalid refinement type.");
1880 }
1881
1882 if (ref_type != Refinement::XY) { Iso = false; }
1883 }
1884 else if (el.Geom() == Geometry::TRIANGLE)
1885 {
1886 ref_type = Refinement::XY; // for consistency
1887
1888 // isotropic split - the only ref_type available for triangles
1889 int mid01 = nodes.GetId(no[0], no[1]);
1890 int mid12 = nodes.GetId(no[1], no[2]);
1891 int mid20 = nodes.GetId(no[2], no[0]);
1892
1893 child[0] = NewTriangle(no[0], mid01, mid20, attr, fa[0], -1, fa[2]);
1894 child[1] = NewTriangle(mid01, no[1], mid12, attr, fa[0], fa[1], -1);
1895 child[2] = NewTriangle(mid20, mid12, no[2], attr, -1, fa[1], fa[2]);
1896 child[3] = NewTriangle(mid12, mid20, mid01, attr, -1, -1, -1);
1897 }
1898 else if (el.Geom() == Geometry::SEGMENT)
1899 {
1900 ref_type = Refinement::X; // for consistency
1901
1902 int mid = nodes.GetId(no[0], no[1]);
1903 child[0] = NewSegment(no[0], mid, attr, fa[0], -1);
1904 child[1] = NewSegment(mid, no[1], attr, -1, fa[1]);
1905 }
1906 else
1907 {
1908 MFEM_ABORT("Unsupported element geometry.");
1909 }
1910
1911 // start using the nodes of the children, create edges & faces
1912 for (int i = 0; i < MaxElemChildren && child[i] >= 0; i++)
1913 {
1914 ReferenceElement(child[i]);
1915 }
1916
1917 int buf[MaxElemFaces];
1918 Array<int> parentFaces(buf, MaxElemFaces);
1919 parentFaces.SetSize(0);
1920
1921 // sign off of all nodes of the parent, clean up unused nodes, but keep faces
1922 UnreferenceElement(elem, parentFaces);
1923
1924 // register the children in their faces
1925 for (int i = 0; i < MaxElemChildren && child[i] >= 0; i++)
1926 {
1927 RegisterFaces(child[i]);
1928 }
1929
1930 // clean up parent faces, if unused
1931 DeleteUnusedFaces(parentFaces);
1932
1933 // make the children inherit our rank; set the parent element
1934 for (int i = 0; i < MaxElemChildren && child[i] >= 0; i++)
1935 {
1936 Element &ch = elements[child[i]];
1937 ch.rank = el.rank;
1938 ch.parent = elem;
1939 }
1940
1941 // finish the refinement
1942 el.ref_type = ref_type;
1943 std::memcpy(el.child, child, sizeof(el.child));
1944}
1945
1946
1947void NCMesh::Refine(const Array<Refinement>& refinements)
1948{
1949 // push all refinements on the stack in reverse order
1950 ref_stack.Reserve(refinements.Size());
1951 for (int i = refinements.Size()-1; i >= 0; i--)
1952 {
1953 Refinement ref = refinements[i]; // Copy
1954 ref.index = leaf_elements[ref.index];
1955 ref_stack.Append(ref);
1956 }
1957
1958 // keep refining as long as the stack contains something
1959 int nforced = 0;
1960 while (ref_stack.Size())
1961 {
1962 Refinement ref = ref_stack.Last();
1963 ref_stack.DeleteLast();
1964
1965 int size = ref_stack.Size();
1966 RefineElement(ref);
1967 nforced += ref_stack.Size() - size;
1968 }
1969
1970 /* TODO: the current algorithm of forced refinements is not optimal. As
1971 forced refinements spread through the mesh, some may not be necessary in
1972 the end, since the affected elements may still be scheduled for refinement
1973 that could stop the propagation. We should introduce the member
1974 Element::ref_pending that would show the intended refinement in the batch.
1975 A forced refinement would be combined with ref_pending to (possibly) stop
1976 the propagation earlier.
1977
1978 Update: what about a FIFO instead of ref_stack? */
1979
1980#if defined(MFEM_DEBUG) && !defined(MFEM_USE_MPI)
1981 mfem::out << "Refined " << refinements.Size() << " + " << nforced
1982 << " elements" << std::endl;
1983#else
1984 MFEM_CONTRACT_VAR(nforced);
1985#endif
1986
1987 ref_stack.DeleteAll();
1988 shadow.DeleteAll();
1989
1990 Update();
1991}
1992
1993
1994//// Derefinement //////////////////////////////////////////////////////////////
1995
1997{
1998 if (!el.ref_type) { return el.node[index]; }
1999
2000 // need to retrieve node from a child element (there is always a child that
2001 // inherited the parent's corner under the same index)
2002 int ch;
2003 switch (el.Geom())
2004 {
2005 case Geometry::CUBE:
2006 ch = el.child[hex_deref_table[el.ref_type - 1][index]];
2007 break;
2008
2009 case Geometry::PRISM:
2010 ch = prism_deref_table[el.ref_type - 1][index];
2011 MFEM_ASSERT(ch != -1, "");
2012 ch = el.child[ch];
2013 break;
2014
2015 case Geometry::PYRAMID:
2016 ch = pyramid_deref_table[el.ref_type - 1][index];
2017 MFEM_ASSERT(ch != -1, "");
2018 ch = el.child[ch];
2019 break;
2020
2021 case Geometry::SQUARE:
2022 ch = el.child[quad_deref_table[el.ref_type - 1][index]];
2023 break;
2024
2026 case Geometry::TRIANGLE:
2027 ch = el.child[index];
2028 break;
2029
2030 default:
2031 ch = 0; // suppress compiler warning
2032 MFEM_ABORT("Unsupported element geometry.");
2033 }
2034 return RetrieveNode(elements[ch], index);
2035}
2036
2037
2039{
2040 Element &el = elements[elem];
2041 if (!el.ref_type) { return; }
2042
2043 int child[MaxElemChildren];
2044 std::memcpy(child, el.child, sizeof(child));
2045
2046 // first make sure that all children are leaves, derefine them if not
2047 for (int i = 0; i < MaxElemChildren && child[i] >= 0; i++)
2048 {
2049 if (elements[child[i]].ref_type)
2050 {
2051 DerefineElement(child[i]);
2052 }
2053 }
2054
2055 int faces_attribute[MaxElemFaces];
2056 int ref_type_key = el.ref_type - 1;
2057
2058 for (int i = 0; i < MaxElemNodes; i++) { el.node[i] = -1; }
2059
2060 // retrieve original corner nodes and face attributes from the children
2061 if (el.Geom() == Geometry::CUBE)
2062 {
2063 // Sets corner nodes from childs
2064 constexpr int nb_cube_childs = 8;
2065 for (int i = 0; i < nb_cube_childs; i++)
2066 {
2067 const int child_local_index = hex_deref_table[ref_type_key][i];
2068 const int child_global_index = child[child_local_index];
2069 Element &ch = elements[child_global_index];
2070 el.node[i] = ch.node[i];
2071 }
2072 // Sets faces attributes from childs' faces
2073 constexpr int nb_cube_faces = 6;
2074 for (int i = 0; i < nb_cube_faces; i++)
2075 {
2076 const int child_local_index = hex_deref_table[ref_type_key]
2077 [i + nb_cube_childs];
2078 const int child_global_index = child[child_local_index];
2079 Element &ch = elements[child_global_index];
2080 const int* fv = GI[el.Geom()].faces[i];
2081 faces_attribute[i] = faces.Find(ch.node[fv[0]], ch.node[fv[1]],
2082 ch.node[fv[2]], ch.node[fv[3]])
2083 ->attribute;
2084 }
2085 }
2086 else if (el.Geom() == Geometry::PRISM)
2087 {
2088 MFEM_ASSERT(prism_deref_table[ref_type_key][0] != -1,
2089 "invalid prism refinement");
2090 constexpr int nb_prism_childs = 6;
2091 for (int i = 0; i < nb_prism_childs; i++)
2092 {
2093 const int child_local_index = prism_deref_table[ref_type_key][i];
2094 const int child_global_index = child[child_local_index];
2095 Element &ch = elements[child_global_index];
2096 el.node[i] = ch.node[i];
2097 }
2098 el.node[6] = el.node[7] = -1;
2099
2100 constexpr int nb_prism_faces = 5;
2101 for (int i = 0; i < nb_prism_faces; i++)
2102 {
2103 const int child_local_index = prism_deref_table[ref_type_key]
2104 [i + nb_prism_childs];
2105 const int child_global_index = child[child_local_index];
2106 Element &ch = elements[child_global_index];
2107 const int* fv = GI[el.Geom()].faces[i];
2108 faces_attribute[i] = faces.Find(ch.node[fv[0]], ch.node[fv[1]],
2109 ch.node[fv[2]], ch.node[fv[3]])
2110 ->attribute;
2111 }
2112 }
2113 else if (el.Geom() == Geometry::PYRAMID)
2114 {
2115 MFEM_ASSERT(pyramid_deref_table[ref_type_key][0] != -1,
2116 "invalid pyramid refinement");
2117 constexpr int nb_pyramid_childs = 5;
2118 for (int i = 0; i < nb_pyramid_childs; i++)
2119 {
2120 const int child_local_index = pyramid_deref_table[ref_type_key][i];
2121 const int child_global_index = child[child_local_index];
2122 Element &ch = elements[child_global_index];
2123 el.node[i] = ch.node[i];
2124 }
2125 el.node[5] = el.node[6] = el.node[7] = -1;
2126
2127
2128 constexpr int nb_pyramid_faces = 5;
2129 for (int i = 0; i < nb_pyramid_faces; i++)
2130 {
2131 const int child_local_index = pyramid_deref_table[ref_type_key]
2132 [i + nb_pyramid_childs];
2133 const int child_global_index = child[child_local_index];
2134 Element &ch = elements[child_global_index];
2135 const int* fv = GI[el.Geom()].faces[i];
2136 faces_attribute[i] = faces.Find(ch.node[fv[0]], ch.node[fv[1]],
2137 ch.node[fv[2]], ch.node[fv[3]])
2138 ->attribute;
2139 }
2140 }
2141 else if (el.Geom() == Geometry::TETRAHEDRON)
2142 {
2143 for (int i = 0; i < 4; i++)
2144 {
2145 Element& ch1 = elements[child[i]];
2146 Element& ch2 = elements[child[(i+1) & 0x3]];
2147 el.node[i] = ch1.node[i];
2148 const int* fv = GI[el.Geom()].faces[i];
2149 faces_attribute[i] = faces.Find(ch2.node[fv[0]], ch2.node[fv[1]],
2150 ch2.node[fv[2]], ch2.node[fv[3]])
2151 ->attribute;
2152 }
2153 }
2154 else if (el.Geom() == Geometry::SQUARE)
2155 {
2156 constexpr int nb_square_childs = 4;
2157 for (int i = 0; i < nb_square_childs; i++)
2158 {
2159 const int child_local_index = quad_deref_table[ref_type_key][i];
2160 const int child_global_index = child[child_local_index];
2161 Element &ch = elements[child_global_index];
2162 el.node[i] = ch.node[i];
2163 }
2164 constexpr int nb_square_faces = 4;
2165 for (int i = 0; i < nb_square_faces; i++)
2166 {
2167 const int child_local_index = quad_deref_table[ref_type_key]
2168 [i + nb_square_childs];
2169 const int child_global_index = child[child_local_index];
2170 Element &ch = elements[child_global_index];
2171 const int* fv = GI[el.Geom()].faces[i];
2172 faces_attribute[i] = faces.Find(ch.node[fv[0]], ch.node[fv[1]],
2173 ch.node[fv[2]], ch.node[fv[3]])
2174 ->attribute;
2175 }
2176 }
2177 else if (el.Geom() == Geometry::TRIANGLE)
2178 {
2179 constexpr int nb_triangle_childs = 3;
2180 for (int i = 0; i < nb_triangle_childs; i++)
2181 {
2182 Element& ch = elements[child[i]];
2183 el.node[i] = ch.node[i];
2184 const int* fv = GI[el.Geom()].faces[i];
2185 faces_attribute[i] = faces.Find(ch.node[fv[0]], ch.node[fv[1]],
2186 ch.node[fv[2]], ch.node[fv[3]])
2187 ->attribute;
2188 }
2189 }
2190 else if (el.Geom() == Geometry::SEGMENT)
2191 {
2192 constexpr int nb_segment_childs = 2;
2193 for (int i = 0; i < nb_segment_childs; i++)
2194 {
2195 int ni = elements[child[i]].node[i];
2196 el.node[i] = ni;
2197 faces_attribute[i] = faces.Find(ni, ni, ni, ni)->attribute;
2198 }
2199 }
2200 else
2201 {
2202 MFEM_ABORT("Unsupported element geometry.");
2203 }
2204
2205 // sign in to all nodes
2206 ReferenceElement(elem);
2207
2209 Array<int> childFaces(buf, MaxElemChildren*MaxElemFaces);
2210 childFaces.SetSize(0);
2211
2212 // delete children, determine rank
2213 el.rank = std::numeric_limits<int>::max();
2214 for (int i = 0; i < MaxElemChildren && child[i] >= 0; i++)
2215 {
2216 el.rank = std::min(el.rank, elements[child[i]].rank);
2217 UnreferenceElement(child[i], childFaces);
2218 FreeElement(child[i]);
2219 }
2220
2221 RegisterFaces(elem, faces_attribute);
2222
2223 // delete unused faces
2224 childFaces.Sort();
2225 childFaces.Unique();
2226 DeleteUnusedFaces(childFaces);
2227
2228 el.ref_type = 0;
2229}
2230
2231
2233{
2234 Element &el = elements[elem];
2235 if (!el.ref_type) { return; }
2236
2237 int total = 0, ref = 0, ghost = 0;
2238 for (int i = 0; i < MaxElemChildren && el.child[i] >= 0; i++)
2239 {
2240 total++;
2241 Element &ch = elements[el.child[i]];
2242 if (ch.ref_type) { ref++; break; }
2243 if (IsGhost(ch)) { ghost++; }
2244 }
2245
2246 if (!ref && ghost < total)
2247 {
2248 // can be derefined, add to list
2249 int next_row = list.Size() ? (list.Last().from + 1) : 0;
2250 for (int i = 0; i < MaxElemChildren && el.child[i] >= 0; i++)
2251 {
2252 Element &ch = elements[el.child[i]];
2253 list.Append(Connection(next_row, ch.index));
2254 }
2255 }
2256 else
2257 {
2258 for (int i = 0; i < MaxElemChildren && el.child[i] >= 0; i++)
2259 {
2260 CollectDerefinements(el.child[i], list);
2261 }
2262 }
2263}
2264
2266{
2267 Array<Connection> list;
2268 list.Reserve(leaf_elements.Size());
2269
2270 for (int i = 0; i < root_state.Size(); i++)
2271 {
2272 CollectDerefinements(i, list);
2273 }
2274
2275 int size = list.Size() ? (list.Last().from + 1) : 0;
2276 derefinements.MakeFromList(size, list);
2277 return derefinements;
2278}
2279
2281 Array<int> &level_ok, int max_nc_level)
2282{
2283 level_ok.SetSize(deref_table.Size());
2284 for (int i = 0; i < deref_table.Size(); i++)
2285 {
2286 const int* fine = deref_table.GetRow(i), size = deref_table.RowSize(i);
2287 Element &parent = elements[elements[leaf_elements[fine[0]]].parent];
2288
2289 int ok = 1;
2290 for (int j = 0; j < size; j++)
2291 {
2292 int splits[3];
2293 CountSplits(leaf_elements[fine[j]], splits);
2294
2295 for (int k = 0; k < Dim; k++)
2296 {
2297 if ((parent.ref_type & (1 << k)) &&
2298 splits[k] >= max_nc_level)
2299 {
2300 ok = 0; break;
2301 }
2302 }
2303 if (!ok) { break; }
2304 }
2305 level_ok[i] = ok;
2306 }
2307}
2308
2309void NCMesh::Derefine(const Array<int> &derefs)
2310{
2311 MFEM_VERIFY(Dim < 3 || Iso,
2312 "derefinement of 3D anisotropic meshes not implemented yet.");
2313
2315
2316 Array<int> fine_coarse;
2317 leaf_elements.Copy(fine_coarse);
2318
2319 // perform the derefinements
2320 for (int i = 0; i < derefs.Size(); i++)
2321 {
2322 int row = derefs[i];
2323 MFEM_VERIFY(row >= 0 && row < derefinements.Size(),
2324 "invalid derefinement number.");
2325
2326 const int* fine = derefinements.GetRow(row);
2327 int parent = elements[leaf_elements[fine[0]]].parent;
2328
2329 // record the relation of the fine elements to their parent
2330 SetDerefMatrixCodes(parent, fine_coarse);
2331
2332 DerefineElement(parent);
2333 }
2334
2335 // update leaf_elements, Element::index etc.
2336 Update();
2337
2338 // link old fine elements to the new coarse elements
2339 for (int i = 0; i < fine_coarse.Size(); i++)
2340 {
2341 transforms.embeddings[i].parent = elements[fine_coarse[i]].index;
2342 }
2343}
2344
2346{
2347 int nfine = leaf_elements.Size();
2348
2349 // this will tell GetDerefinementTransforms that transforms are not finished
2350 transforms.Clear();
2351
2352 transforms.embeddings.SetSize(nfine);
2353 for (int i = 0; i < nfine; i++)
2354 {
2356 emb.parent = -1;
2357 emb.matrix = 0;
2358 Element &el = elements[leaf_elements[i]];
2359 emb.geom = el.Geom();
2360 emb.ghost = IsGhost(el);
2361 }
2362}
2363
2364void NCMesh::SetDerefMatrixCodes(int parent, Array<int> &fine_coarse)
2365{
2366 // encode the ref_type and child number for GetDerefinementTransforms()
2367 Element &prn = elements[parent];
2368 for (int i = 0; i < MaxElemChildren && prn.child[i] >= 0; i++)
2369 {
2370 Element &ch = elements[prn.child[i]];
2371 if (ch.index >= 0)
2372 {
2373 int code = (prn.ref_type << 4) | i;
2374 transforms.embeddings[ch.index].matrix = code;
2375 fine_coarse[ch.index] = parent;
2376 }
2377 }
2378}
2379
2380
2381//// Mesh Interface ////////////////////////////////////////////////////////////
2382
2383void NCMesh::CollectLeafElements(int elem, int state, Array<int> &ghosts,
2384 int &counter)
2385{
2386 Element &el = elements[elem];
2387 if (!el.ref_type)
2388 {
2389 if (el.rank >= 0) // skip elements beyond the ghost layer in parallel
2390 {
2391 if (!IsGhost(el))
2392 {
2393 leaf_elements.Append(elem);
2394 }
2395 else
2396 {
2397 // in parallel (or in serial loading a parallel file), collect
2398 // elements of neighboring ranks in a separate array
2399 ghosts.Append(elem);
2400 }
2401
2402 // assign the SFC index (temporarily, will be replaced by Mesh index)
2403 el.index = counter++;
2404 }
2405 else
2406 {
2407 // elements beyond the ghost layer are invalid and don't appear in
2408 // 'leaf_elements' (also for performance reasons)
2409 el.index = -1;
2410 }
2411 }
2412 else // Refined element
2413 {
2414 // in non-leaf elements, the 'rank' and 'index' members have no meaning
2415 el.rank = -1;
2416 el.index = -1;
2417
2418 // recurse to subtrees; try to order leaf elements along a space-filling
2419 // curve by changing the order the children are visited at each level
2420 if (el.Geom() == Geometry::SQUARE && el.ref_type == Refinement::XY)
2421 {
2422 for (int i = 0; i < 4; i++)
2423 {
2424 int ch = quad_hilbert_child_order[state][i];
2425 int st = quad_hilbert_child_state[state][i];
2426 CollectLeafElements(el.child[ch], st, ghosts, counter);
2427 }
2428 }
2429 else if (el.Geom() == Geometry::CUBE && el.ref_type == Refinement::XYZ)
2430 {
2431 for (int i = 0; i < 8; i++)
2432 {
2433 int ch = hex_hilbert_child_order[state][i];
2434 int st = hex_hilbert_child_state[state][i];
2435 CollectLeafElements(el.child[ch], st, ghosts, counter);
2436 }
2437 }
2438 else // no space filling curve tables yet for remaining cases
2439 {
2440 for (int i = 0; i < MaxElemChildren; i++)
2441 {
2442 if (el.child[i] >= 0)
2443 {
2444 CollectLeafElements(el.child[i], state, ghosts, counter);
2445 }
2446 }
2447 }
2448 }
2449}
2450
2452{
2453 Array<int> ghosts;
2454
2455 // collect leaf elements in leaf_elements and ghosts elements in ghosts from
2456 // all roots
2458 for (int i = 0, counter = 0; i < root_state.Size(); i++)
2459 {
2460 CollectLeafElements(i, root_state[i], ghosts, counter);
2461 }
2462
2464 NGhostElements = ghosts.Size();
2465
2466 // append ghost elements at the end of 'leaf_element' (if any) and assign the
2467 // final (Mesh) indices of leaves
2468 leaf_elements.Append(ghosts);
2470 for (int i = 0; i < leaf_elements.Size(); i++)
2471 {
2472 Element &el = elements[leaf_elements[i]];
2473 leaf_sfc_index[i] = el.index;
2474 el.index = i;
2475 }
2476}
2477
2479{
2480#ifndef MFEM_NCMESH_OLD_VERTEX_ORDERING
2481 // This method assigns indices to vertices (Node::vert_index) that will be
2482 // seen by the Mesh class and the rest of MFEM. We must be careful to:
2483 //
2484 // 1. Stay compatible with the conforming code, which expects top-level
2485 // (original) vertices to be indexed first, otherwise GridFunctions
2486 // defined on a conforming mesh would no longer be valid when the mesh
2487 // is converted to an NC mesh.
2488 //
2489 // 2. Make sure serial NCMesh is compatible with the parallel ParNCMesh, so
2490 // it is possible to read parallel partial solutions in serial code
2491 // (e.g., serial GLVis). This means handling ghost elements, if present.
2492 //
2493 // 3. Assign vertices in a globally consistent order for parallel meshes:
2494 // if two vertices i,j are shared by two ranks r1,r2, and i<j on r1,
2495 // then i<j on r2 as well. This is true for top-level vertices but also
2496 // for the remaining shared vertices thanks to the globally consistent
2497 // SFC ordering of the leaf elements. This property reduces
2498 // communication and simplifies ParNCMesh.
2499
2500 // STEP 1: begin by splitting vertices into 4 classes:
2501 // - local top-level vertices (code -1)
2502 // - local non-top level vertices (code -2)
2503 // - ghost (non-local) vertices (code -3)
2504 // - vertices beyond the ghost layer (code -4)
2505
2506 for (auto & node : nodes)
2507 {
2508 node.vert_index = -4; // assume beyond ghost layer
2509 }
2510
2511 for (int i = 0; i < leaf_elements.Size(); i++)
2512 {
2513 Element &el = elements[leaf_elements[i]];
2514 for (int j = 0; j < GI[el.Geom()].nv; j++)
2515 {
2516 Node &nd = nodes[el.node[j]];
2517 if (el.rank == MyRank)
2518 {
2519 if (nd.p1 == nd.p2) // local top-level vertex
2520 {
2521 if (nd.vert_index < -1) { nd.vert_index = -1; }
2522 }
2523 else // local non-top-level vertex
2524 {
2525 if (nd.vert_index < -2) { nd.vert_index = -2; }
2526 }
2527 }
2528 else // ghost vertex
2529 {
2530 if (nd.vert_index < -3) { nd.vert_index = -3; }
2531 }
2532 }
2533 }
2534
2535 // STEP 2: assign indices of top-level local vertices, in original order
2536 NVertices = 0;
2537 for (auto &node : nodes)
2538 {
2539 if (node.vert_index == -1)
2540 {
2541 node.vert_index = NVertices++;
2542 }
2543 }
2544
2545 // STEP 3: go over all elements (local and ghost) in SFC order and assign
2546 // remaining local vertices in that order.
2547 Array<int> sfc_order(leaf_elements.Size());
2548 for (int i = 0; i < sfc_order.Size(); i++)
2549 {
2550 sfc_order[leaf_sfc_index[i]] = leaf_elements[i];
2551 }
2552
2553 for (int i = 0; i < sfc_order.Size(); i++)
2554 {
2555 const Element &el = elements[sfc_order[i]];
2556 for (int j = 0; j < GI[el.Geom()].nv; j++)
2557 {
2558 Node &nd = nodes[el.node[j]];
2559 if (nd.vert_index == -2) { nd.vert_index = NVertices++; }
2560 }
2561 }
2562
2563 // STEP 4: create the mapping from Mesh vertex index to NCMesh node index
2565 for (auto node = nodes.begin(); node != nodes.end(); ++node)
2566 {
2567 if (node->HasVertex() && node->vert_index >= 0)
2568 {
2569 MFEM_ASSERT(node->vert_index < vertex_nodeId.Size(), "");
2570 vertex_nodeId[node->vert_index] = node.index();
2571 }
2572 }
2573
2574 // STEP 5: assign remaining ghost vertices, ignore vertices beyond the ghost
2575 // layer
2576 NGhostVertices = 0;
2577 for (int i = 0; i < sfc_order.Size(); i++)
2578 {
2579 const Element &el = elements[sfc_order[i]];
2580 for (int j = 0; j < GI[el.Geom()].nv; j++)
2581 {
2582 Node &nd = nodes[el.node[j]];
2583 if (nd.vert_index == -3)
2584 {
2586 }
2587 }
2588 }
2589
2590#else // old ordering for debugging/testing only
2591 bool parallel = false;
2592#ifdef MFEM_USE_MPI
2593 if (dynamic_cast<ParNCMesh*>(this)) { parallel = true; }
2594#endif
2595
2596 if (!parallel)
2597 {
2598 NVertices = 0;
2599 for (auto node = nodes.begin(); node != nodes.end(); ++node)
2600 {
2601 if (node->HasVertex()) { node->vert_index = NVertices++; }
2602 }
2603
2605
2606 NVertices = 0;
2607 for (auto node = nodes.begin(); node != nodes.end(); ++node)
2608 {
2609 if (node->HasVertex()) { vertex_nodeId[NVertices++] = node.index(); }
2610 }
2611 }
2612 else
2613 {
2614 for (auto node = nodes.begin(); node != nodes.end(); ++node)
2615 {
2616 if (node->HasVertex()) { node->vert_index = -1; }
2617 }
2618
2619 NVertices = 0;
2620 for (int i = 0; i < leaf_elements.Size(); i++)
2621 {
2622 Element &el = elements[leaf_elements[i]];
2623 if (el.rank == MyRank)
2624 {
2625 for (int j = 0; j < GI[el.Geom()].nv; j++)
2626 {
2627 int &vindex = nodes[el.node[j]].vert_index;
2628 if (vindex < 0) { vindex = NVertices++; }
2629 }
2630 }
2631 }
2632
2634 for (auto &node : nodes)
2635 {
2636 if (node.HasVertex() && node.vert_index >= 0)
2637 {
2638 vertex_nodeId[node.vert_index] = node.index();
2639 }
2640 }
2641
2642 NGhostVertices = 0;
2643 for (auto &node : nodes)
2644 {
2645 if (node.HasVertex() && node.vert_index < 0)
2646 {
2647 node.vert_index = NVertices + (NGhostVertices++);
2648 }
2649 }
2650 }
2651#endif
2652}
2653
2654void NCMesh::InitRootState(int root_count)
2655{
2656 root_state.SetSize(root_count);
2657 root_state = 0;
2658
2659 if (elements.Size() == 0) { return; }
2660
2661 char* node_order;
2662 int nch;
2663
2664 switch (elements[0].Geom()) // TODO: mixed meshes
2665 {
2666 case Geometry::SQUARE:
2667 nch = 4;
2668 node_order = (char*) quad_hilbert_child_order;
2669 break;
2670
2671 case Geometry::CUBE:
2672 nch = 8;
2673 node_order = (char*) hex_hilbert_child_order;
2674 break;
2675
2676 default:
2677 return; // do nothing, all states stay zero
2678 }
2679
2680 int entry_node = -2;
2681
2682 // process the root element sequence
2683 for (int i = 0; i < root_count; i++)
2684 {
2685 Element &el = elements[i];
2686
2687 int v_in = FindNodeExt(el, entry_node, false);
2688 if (v_in < 0) { v_in = 0; }
2689
2690 // determine which nodes are shared with the next element
2691 bool shared[MaxElemNodes];
2692 for (int ni = 0; ni < MaxElemNodes; ++ni) { shared[ni] = 0; }
2693 if (i+1 < root_count)
2694 {
2695 Element &next = elements[i+1];
2696 for (int j = 0; j < nch; j++)
2697 {
2698 int node = FindNodeExt(el, RetrieveNode(next, j), false);
2699 if (node >= 0) { shared[node] = true; }
2700 }
2701 }
2702
2703 // select orientation that starts in v_in and exits in shared node
2704 int state = Dim*v_in;
2705 for (int j = 0; j < Dim; j++)
2706 {
2707 if (shared[(int) node_order[nch*(state + j) + nch-1]])
2708 {
2709 state += j;
2710 break;
2711 }
2712 }
2713
2714 root_state[i] = state;
2715
2716 entry_node = RetrieveNode(el, node_order[nch*state + nch-1]);
2717 }
2718}
2719
2721{
2722 switch (geom)
2723 {
2724 case Geometry::CUBE: return new mfem::Hexahedron;
2725 case Geometry::PRISM: return new mfem::Wedge;
2726 case Geometry::PYRAMID: return new mfem::Pyramid;
2728 case Geometry::SQUARE: return new mfem::Quadrilateral;
2729 case Geometry::TRIANGLE: return new mfem::Triangle;
2730 }
2731 MFEM_ABORT("invalid geometry");
2732 return NULL;
2733}
2734
2735const real_t* NCMesh::CalcVertexPos(int node) const
2736{
2737 const Node &nd = nodes[node];
2738 if (nd.p1 == nd.p2) // top-level vertex
2739 {
2740 return &coordinates[3*nd.p1];
2741 }
2742
2743 TmpVertex &tv = tmp_vertex[node];
2744 if (tv.valid) { return tv.pos; }
2745
2746 MFEM_VERIFY(tv.visited == false, "cyclic vertex dependencies.");
2747 tv.visited = true;
2748
2749 const real_t* pos1 = CalcVertexPos(nd.p1);
2750 const real_t* pos2 = CalcVertexPos(nd.p2);
2751
2752 for (int i = 0; i < 3; i++)
2753 {
2754 tv.pos[i] = ((1.0 - nd.GetScale()) * pos1[i]) + (nd.GetScale() * pos2[i]);
2755 }
2756 tv.valid = true;
2757 return tv.pos;
2758}
2759
2761{
2762 mesh.vertices.SetSize(vertex_nodeId.Size());
2763 if (coordinates.Size())
2764 {
2765 // calculate vertex positions from stored top-level vertex coordinates
2766 tmp_vertex = new TmpVertex[nodes.NumIds()];
2767 for (int i = 0; i < mesh.vertices.Size(); i++)
2768 {
2769 mesh.vertices[i].SetCoords(spaceDim, CalcVertexPos(vertex_nodeId[i]));
2770 }
2771 delete [] tmp_vertex;
2772 }
2773 // NOTE: if the mesh is curved ('coordinates' is empty), mesh.vertices are
2774 // left uninitialized here; they will be initialized later by the Mesh from
2775 // Nodes -- here we just make sure mesh.vertices has the correct size.
2776
2777 for (auto &elem : mesh.elements)
2778 {
2779 mesh.FreeElement(elem);
2780 }
2781 mesh.elements.SetSize(0);
2782
2783 for (auto &elem : mesh.boundary)
2784 {
2785 mesh.FreeElement(elem);
2786 }
2787 mesh.boundary.SetSize(0);
2788
2789 // Save off boundary face vertices to make boundary elements later.
2790 std::map<int, mfem::Array<int>> unique_boundary_faces;
2791
2792 // create an mfem::Element for each leaf Element
2793 for (int i = 0; i < NElements; i++)
2794 {
2795 const Element &nc_elem = elements[leaf_elements[i]];
2796
2797 const int* node = nc_elem.node;
2798 GeomInfo& gi = GI[(int) nc_elem.geom];
2799
2800 mfem::Element* elem = mesh.NewElement(nc_elem.geom);
2801 mesh.elements.Append(elem);
2802
2803 elem->SetAttribute(nc_elem.attribute);
2804 for (int j = 0; j < gi.nv; j++)
2805 {
2806 elem->GetVertices()[j] = nodes[node[j]].vert_index;
2807 }
2808
2809 // Loop over faces and collect those marked as boundaries
2810 for (int k = 0; k < gi.nf; ++k)
2811 {
2812 const int nfv = gi.nfv[k];
2813 const int * const fv = gi.faces[k];
2814 const auto id = faces.FindId(node[fv[0]], node[fv[1]], node[fv[2]],
2815 node[fv[3]]);
2816 if (id >= 0 && faces[id].Boundary())
2817 {
2818 const auto &face = faces[id];
2819 if (face.elem[0] >= 0 && face.elem[1] >= 0 &&
2820 nc_elem.rank != std::min(elements[face.elem[0]].rank,
2821 elements[face.elem[1]].rank))
2822 {
2823 // This is a conformal internal face, but this element is not the
2824 // lowest ranking attached processor, thus not the owner of the
2825 // face. Consequently, we do not add this face to avoid double
2826 // counting.
2827 continue;
2828 }
2829
2830 // Add in all boundary faces that are actual boundaries or not
2831 // masters of another face. The fv[2] in the edge split is on
2832 // purpose. A point cannot have a split level, thus do not check for
2833 // master/slave relation.
2834 if ((nfv == 4 &&
2835 !QuadFaceIsMaster(node[fv[0]], node[fv[1]], node[fv[2]], node[fv[3]]))
2836 || (nfv == 3 && !TriFaceIsMaster(node[fv[0]], node[fv[1]], node[fv[2]]))
2837 || (nfv == 2 && EdgeSplitLevel(node[fv[0]], node[fv[2]]) == 0) || (nfv == 1))
2838 {
2839 // This face has no split faces below, it is conformal or a
2840 // slave.
2841 unique_boundary_faces[id].SetSize(nfv);
2842 for (int v = 0; v < nfv; ++v)
2843 {
2844 // Using a map overwrites if a face is visited twice. The
2845 // nfv==2 is necessary because faces of 2D are storing the
2846 // second index in the 2 slot, not the 1 slot.
2847 unique_boundary_faces[id][v] = nodes[node[fv[(nfv==2) ? 2*v : v]]].vert_index;
2848 }
2849 }
2850 }
2851 }
2852 }
2853
2854 auto geom_from_nfv = [](int nfv)
2855 {
2856 switch (nfv)
2857 {
2858 case 1: return Geometry::POINT;
2859 case 2: return Geometry::SEGMENT;
2860 case 3: return Geometry::TRIANGLE;
2861 case 4: return Geometry::SQUARE;
2862 }
2863 return Geometry::INVALID;
2864 };
2865
2866 for (const auto &fv : unique_boundary_faces)
2867 {
2868 const auto f = fv.first;
2869 const auto &v = fv.second;
2870 const auto &face = faces.At(f);
2871
2872 auto geom = geom_from_nfv(v.Size());
2873
2874 MFEM_ASSERT(geom != Geometry::INVALID,
2875 "nfv: " << v.Size() <<
2876 " does not match a valid face geometry: Quad, Tri, Segment, Point");
2877
2878 // Add a new boundary element, with matching attribute and vertices
2879 mesh.boundary.Append(mesh.NewElement(geom));
2880 auto * const be = mesh.boundary.Last();
2881 be->SetAttribute(face.attribute);
2882 be->SetVertices(v);
2883 }
2884}
2885
2886
2888{
2889 //// PART 1: pull indices of regular edges/faces from the Mesh
2890
2891 NEdges = mesh->GetNEdges();
2892 NFaces = mesh->GetNumFaces();
2893 if (Dim < 2) { NFaces = 0; }
2894 // clear Node::edge_index and Face::index
2895 for (auto &node : nodes)
2896 {
2897 if (node.HasEdge()) { node.edge_index = -1; }
2898 }
2899 for (auto &face : faces)
2900 {
2901 face.index = -1;
2902 }
2903
2904 // get edge enumeration from the Mesh
2905 Table *edge_vertex = mesh->GetEdgeVertexTable();
2906 for (int i = 0; i < edge_vertex->Size(); i++)
2907 {
2908 const int *ev = edge_vertex->GetRow(i);
2909 Node* node = nodes.Find(vertex_nodeId[ev[0]], vertex_nodeId[ev[1]]);
2910 MFEM_ASSERT(node && node->HasEdge(),
2911 "edge (" << ev[0] << "," << ev[1] << ") not found, "
2912 "node = " << node << " node->HasEdge() "
2913 << (node != nullptr ? node->HasEdge() : false));
2914 node->edge_index = i;
2915 }
2916
2917 // get face enumeration from the Mesh, initialize 'face_geom'
2919 for (int i = 0; i < NFaces; i++)
2920 {
2921 const int* fv = mesh->GetFace(i)->GetVertices();
2922 const int nfv = mesh->GetFace(i)->GetNVertices();
2923
2924 Face* face;
2925 if (Dim == 3)
2926 {
2927 if (nfv == 4)
2928 {
2930 face = faces.Find(vertex_nodeId[fv[0]], vertex_nodeId[fv[1]],
2931 vertex_nodeId[fv[2]], vertex_nodeId[fv[3]]);
2932 }
2933 else
2934 {
2935 MFEM_ASSERT(nfv == 3, "");
2937 face = faces.Find(vertex_nodeId[fv[0]], vertex_nodeId[fv[1]],
2938 vertex_nodeId[fv[2]]);
2939 }
2940 }
2941 else
2942 {
2943 MFEM_ASSERT(nfv == 2, "");
2945 int n0 = vertex_nodeId[fv[0]], n1 = vertex_nodeId[fv[1]];
2946 face = faces.Find(n0, n0, n1, n1); // look up degenerate face
2947
2948#ifdef MFEM_DEBUG
2949 // (non-ghost) edge and face numbers must match in 2D
2950 const int *ev = edge_vertex->GetRow(i);
2951 MFEM_ASSERT((ev[0] == fv[0] && ev[1] == fv[1]) ||
2952 (ev[1] == fv[0] && ev[0] == fv[1]), "");
2953#endif
2954 }
2955
2956 MFEM_VERIFY(face, "face not found.");
2957 face->index = i;
2958 }
2959
2960 //// PART 2: assign indices of ghost edges/faces, if any
2961
2962 // count ghost edges and assign their indices
2963 NGhostEdges = 0;
2964 for (auto &node : nodes)
2965 {
2966 if (node.HasEdge() && node.edge_index < 0)
2967 {
2968 node.edge_index = NEdges + (NGhostEdges++);
2969 }
2970 }
2971
2972 // count ghost faces
2973 NGhostFaces = 0;
2974 for (auto &face : faces)
2975 {
2976 if (face.index < 0) { NGhostFaces++; }
2977 }
2978
2979 if (Dim == 2)
2980 {
2981 // in 2D we have fake faces because of DG
2982 MFEM_ASSERT(NFaces == NEdges, "");
2983 MFEM_ASSERT(NGhostFaces == NGhostEdges, "");
2984 }
2985
2986 // resize face_geom (default_geom is for slave faces beyond the ghost layer)
2987 Geometry::Type default_geom = Geometry::SQUARE;
2988 face_geom.SetSize(NFaces + NGhostFaces, default_geom);
2989
2990 // update 'face_geom' for ghost faces, assign ghost face indices
2991 int nghosts = 0;
2992 for (int i = 0; i < NGhostElements; i++)
2993 {
2994 Element &el = elements[leaf_elements[NElements + i]]; // ghost element
2995 GeomInfo &gi = GI[el.Geom()];
2996
2997 for (int j = 0; j < gi.nf; j++)
2998 {
2999 const int *fv = gi.faces[j];
3000 const int fid = faces.FindId(el.node[fv[0]], el.node[fv[1]],
3001 el.node[fv[2]], el.node[fv[3]]);
3002 MFEM_ASSERT(fid >= 0, "face not found!");
3003 auto &face = faces[fid];
3004
3005 if (face.index < 0)
3006 {
3007 face.index = NFaces + (nghosts++);
3008 // store the face geometry
3009 static const Geometry::Type types[5] =
3010 {
3013 };
3014 face_geom[face.index] = types[gi.nfv[j]];
3015 }
3016 }
3017 }
3018
3019 // assign valid indices also to faces beyond the ghost layer
3020 for (auto &face : faces)
3021 {
3022 if (face.index < 0) { face.index = NFaces + (nghosts++); }
3023 }
3024 MFEM_ASSERT(nghosts == NGhostFaces, "");
3025}
3026
3027
3028//// Face/edge lists ///////////////////////////////////////////////////////////
3029
3030int NCMesh::QuadFaceSplitType(int v1, int v2, int v3, int v4, real_t & s,
3031 int mid[5]) const
3032{
3033 MFEM_ASSERT(Dim >= 3, "");
3034
3035 // find edge nodes
3036 const int e1 = FindMidEdgeNode(v1, v2);
3037 const int e2 = FindMidEdgeNode(v2, v3);
3038 const int e3 = (e1 >= 0 &&
3039 nodes[e1].HasVertex()) ? FindMidEdgeNode(v3, v4) : -1;
3040 const int e4 = (e2 >= 0 &&
3041 nodes[e2].HasVertex()) ? FindMidEdgeNode(v4, v1) : -1;
3042
3043 // optional: return the mid-edge nodes if requested
3044 if (mid) { mid[0] = e1, mid[1] = e2, mid[2] = e3, mid[3] = e4; }
3045
3046 // try to get a mid-face node, either by (e1, e3) or by (e2, e4)
3047 int midf1 = -1, midf2 = -1;
3048 if (e1 >= 0 && e3 >= 0) { midf1 = FindMidEdgeNode(e1, e3); }
3049 if (e2 >= 0 && e4 >= 0) { midf2 = FindMidEdgeNode(e2, e4); }
3050
3051 // get proper node if shadow node exists
3052 if (midf1 >= 0 && midf1 == midf2)
3053 {
3054 const Node &nd = nodes[midf1];
3055 if (nd.p1 != e1 && nd.p2 != e1) { midf1 = -1; }
3056 if (nd.p1 != e2 && nd.p2 != e2) { midf2 = -1; }
3057 }
3058
3059 // only one way to access the mid-face node must always exist
3060 MFEM_ASSERT(!(midf1 >= 0 && midf2 >= 0), "incorrectly split face!");
3061
3062 if (midf1 < 0 && midf2 < 0) // face not split
3063 {
3064 if (mid) { mid[4] = -1; }
3065 s = 0.0;
3066 return 0;
3067 }
3068 else if (midf1 >= 0) // face split "vertically"
3069 {
3070 if (mid) { mid[4] = midf1; }
3071 s = nodes[e1].GetScale();
3072 if (v1 > v2) { s = 1.0 - s; }
3073 return 1;
3074 }
3075 else // face split "horizontally"
3076 {
3077 if (mid) { mid[4] = midf2; }
3078 s = nodes[e2].GetScale();
3079 if (v2 > v3) { s = 1.0 - s; }
3080 return 2;
3081 }
3082}
3083
3084bool NCMesh::TriFaceSplit(int v1, int v2, int v3, int mid[3]) const
3085{
3086 int e1 = nodes.FindId(v1, v2);
3087 if (e1 < 0 || !nodes[e1].HasVertex()) { return false; }
3088
3089 int e2 = nodes.FindId(v2, v3);
3090 if (e2 < 0 || !nodes[e2].HasVertex()) { return false; }
3091
3092 int e3 = nodes.FindId(v3, v1);
3093 if (e3 < 0 || !nodes[e3].HasVertex()) { return false; }
3094
3095 if (mid) { mid[0] = e1, mid[1] = e2, mid[2] = e3; }
3096
3097 // This is necessary but not sufficient to determine if a face has been
3098 // split. All edges might have been split due to edge attached faces being
3099 // refined. Need to check for existence of face made up of midpoints.
3100 return true;
3101}
3102
3103bool contains_node(const std::array<int, 4> &nodes, int n)
3104{
3105 return std::find(nodes.begin(), nodes.end(), n) != nodes.end();
3106};
3107
3108int NCMesh::ParentFaceNodes(std::array<int, 4> &face_nodes) const
3109{
3110 const bool is_tri = face_nodes[3] == -1;
3111 const bool is_segment = (face_nodes[0] == face_nodes[1] &&
3112 face_nodes[2] == face_nodes[3]);
3113 const bool is_quad = *std::min_element(face_nodes.begin(),
3114 face_nodes.end()) >= 0;
3115
3116 MFEM_ASSERT((is_tri && !is_segment && !is_quad)
3117 || (!is_tri && is_segment && !is_quad) || (!is_tri && !is_segment &&
3118 is_quad), "Inconsistent node geometry");
3119
3120 bool all_nodes_root = true;
3121 for (auto x : face_nodes)
3122 {
3123 all_nodes_root = all_nodes_root && (x < 0 || (nodes[x].p1 == nodes[x].p2));
3124 }
3125 // This face is a root face -> nothing to do.
3126 if (all_nodes_root) { return -1; }
3127
3128 int child = -1; // The index into parent.child that this face corresponds to.
3129 auto parent_nodes = face_nodes;
3130 if (is_quad)
3131 {
3132 // Logic for coarsening anisotropic faces is more complex, needs
3133 // identification and handling of multiple "crux" points. Will require
3134 // inspection of edge nodes.
3135 MFEM_VERIFY(Iso,
3136 "ParentFaceNodes does not support anisotropic refinement yet!");
3137
3138 // Finds the first node whose parents aren't in the face_nodes. This is
3139 // also the index of the child location in the parent face. Treated
3140 // separately as ultimately multiple crux will need to be handled for
3141 // anisotropic faces.
3142 const auto crux = [&]()
3143 {
3144 for (int i = 0; i < static_cast<int>(face_nodes.size()); i++)
3145 {
3146 if ((!contains_node(face_nodes, nodes[face_nodes[i]].p1)
3147 && !contains_node(face_nodes, nodes[face_nodes[i]].p2))
3148 || (nodes[face_nodes[i]].p1 == nodes[face_nodes[i]].p2) /* top level node */)
3149 {
3150 return i;
3151 }
3152 }
3153 return -1;
3154 }();
3155 MFEM_ASSERT(crux != -1, "A root face should have been returned early");
3156
3157 // Loop over nodes, starting from diagonal to child, wrapping and skipping
3158 // child. This will visit the node opposite child twice, thereby
3159 // coarsening to the diagonally opposite. NOTE: This assumes that the
3160 // nodes for a square are numbered (0 -> 1 -> 2 -> 3 -> 0).
3161 for (int i = 0; i < static_cast<int>(face_nodes.size()) + 1; i++)
3162 {
3163 int ind = (crux + i + 2) %
3164 4; // Start and end with coarsening of the diagonally opposite
3165 if (ind == crux) { continue; }
3166 auto &x = parent_nodes[ind];
3167
3168 // Check against parent_nodes rather than face_nodes so on second lap
3169 // the node opposite crux will coarsen again to the diagonally across
3170 // in the parent face. A top level node has p1 == p2, thus these
3171 // modifications do nothing.
3172 if (contains_node(parent_nodes, nodes[x].p1))
3173 {
3174 MFEM_ASSERT(nodes[x].p2 == nodes[x].p1 ||
3175 !contains_node(parent_nodes, nodes[x].p2), "!");
3176 x = nodes[x].p2;
3177 }
3178 else if (contains_node(parent_nodes, nodes[x].p2))
3179 {
3180 MFEM_ASSERT(nodes[x].p2 == nodes[x].p1 ||
3181 !contains_node(parent_nodes, nodes[x].p1), "!");
3182 x = nodes[x].p1;
3183 }
3184 else { /* do nothing */ }
3185 }
3186 }
3187 else if (is_tri)
3188 {
3189 for (int i = 0; i < 3; i++)
3190 {
3191 auto x = face_nodes[i];
3192 if (x == -1) { continue; }
3193 if (contains_node(face_nodes, nodes[x].p1))
3194 {
3195 MFEM_ASSERT(nodes[x].p2 == nodes[x].p1 ||
3196 !contains_node(face_nodes, nodes[x].p2), "!");
3197 parent_nodes[i] = nodes[x].p2;
3198 }
3199 else if (contains_node(face_nodes, nodes[x].p2))
3200 {
3201 MFEM_ASSERT(nodes[x].p2 == nodes[x].p1 ||
3202 !contains_node(face_nodes, nodes[x].p1), "!");
3203 parent_nodes[i] = nodes[x].p1;
3204 }
3205 else { /* do nothing */ }
3206 }
3207
3208 if (std::equal(face_nodes.begin(), face_nodes.end(), parent_nodes.begin()))
3209 {
3210 // Having excluded root faces, this must be an interior face. We need
3211 // to handle the special case of the interior face of the parent face.
3212 std::array<std::array<int, 2>, 6> parent_pairs;
3213 for (std::size_t i = 0; i < face_nodes.size() - 1; i++)
3214 {
3215 parent_pairs[i][0] = nodes[face_nodes[i]].p1;
3216 parent_pairs[i][1] = nodes[face_nodes[i]].p2;
3217 }
3218 // Each node gets mapped to the common node from its parents and the
3219 // predecessor node's parents.
3220 for (int i = 0; i < 3; i++)
3221 {
3222 // Parenting convention here assumes parent face has the SAME
3223 // orientation as the original. This is true on exterior boundaries,
3224 // but for an interior boundary the master face will have an
3225 // opposing orientation. TODO: Possibly fix for interior boundaries.
3226 const auto &prev = parent_pairs[(i - 1 + 3) % 3]; // (0 -> 2, 1 -> 0, 2 -> 1)
3227 const auto &next = parent_pairs[(i + 1 + 3) % 3]; // (0 -> 1, 1 -> 2, 2 -> 0)
3228 for (auto x : next)
3229 {
3230 if (std::find(prev.begin(), prev.end(), x) != prev.end()) { parent_nodes[i] = x; }
3231 }
3232 }
3233 child = 3; // The interior face is the final child.
3234 }
3235 }
3236 else if (is_segment)
3237 {
3238 // Given this isn't a root face, one node must be the parent of the other.
3239 if (face_nodes[0] == nodes[face_nodes[1]].p1)
3240 {
3241 face_nodes[1] = nodes[face_nodes[1]].p2;
3242 }
3243 else if (face_nodes[0] == nodes[face_nodes[1]].p2)
3244 {
3245 face_nodes[1] = nodes[face_nodes[1]].p1;
3246 }
3247 else if (face_nodes[1] == nodes[face_nodes[0]].p1)
3248 {
3249 face_nodes[0] = nodes[face_nodes[0]].p2;
3250 }
3251 else if (face_nodes[1] == nodes[face_nodes[0]].p2)
3252 {
3253 face_nodes[0] = nodes[face_nodes[0]].p1;
3254 }
3255 else
3256 {
3257 MFEM_ABORT("Internal logic error!");
3258 }
3259 }
3260 else
3261 {
3262 MFEM_ABORT("Unrecognized face geometry!");
3263 }
3264 for (int i = 0; i < 4 && face_nodes[i] >= 0; i++)
3265 {
3266 if (face_nodes[i] == parent_nodes[i])
3267 {
3268 MFEM_ASSERT(child == -1,
3269 "This face cannot be more than one child of the parent face!");
3270 child = i;
3271 }
3272 }
3273 MFEM_ASSERT(child != -1, "Root elements must have exited early!");
3274 std::swap(face_nodes, parent_nodes);
3275 return child;
3276}
3277
3278int NCMesh::find_node(const Element &el, int node)
3279{
3280 for (int i = 0; i < MaxElemNodes; i++)
3281 {
3282 if (el.node[i] == node) { return i; }
3283 }
3284 MFEM_ABORT("Node not found.");
3285 return -1;
3286}
3287
3288int NCMesh::FindNodeExt(const Element &el, int node, bool abort)
3289{
3290 for (int i = 0; i < GI[el.Geom()].nv; i++)
3291 {
3292 if (RetrieveNode(el, i) == node) { return i; }
3293 }
3294 if (abort) { MFEM_ABORT("Node not found."); }
3295 return -1;
3296}
3297
3298int NCMesh::find_element_edge(const Element &el, int vn0, int vn1, bool abort)
3299{
3300 MFEM_ASSERT(!el.ref_type, "");
3301
3302 GeomInfo &gi = GI[el.Geom()];
3303 for (int i = 0; i < gi.ne; i++)
3304 {
3305 const int* ev = gi.edges[i];
3306 int n0 = el.node[ev[0]];
3307 int n1 = el.node[ev[1]];
3308 if ((n0 == vn0 && n1 == vn1) ||
3309 (n0 == vn1 && n1 == vn0)) { return i; }
3310 }
3311
3312 if (abort) { MFEM_ABORT("Edge (" << vn0 << ", " << vn1 << ") not found"); }
3313 return -1;
3314}
3315
3316int NCMesh::find_local_face(int geom, int a, int b, int c)
3317{
3318 GeomInfo &gi = GI[geom];
3319 for (int i = 0; i < gi.nf; i++)
3320 {
3321 const int* fv = gi.faces[i];
3322 if ((a == fv[0] || a == fv[1] || a == fv[2] || a == fv[3]) &&
3323 (b == fv[0] || b == fv[1] || b == fv[2] || b == fv[3]) &&
3324 (c == fv[0] || c == fv[1] || c == fv[2] || c == fv[3]))
3325 {
3326 return i;
3327 }
3328 }
3329 MFEM_ABORT("Face not found.");
3330 return -1;
3331}
3332
3333namespace
3334{
3335template <typename T> struct IntHash;
3336template <> struct IntHash<float>
3337{
3338 using int_type = uint32_t;
3339 static constexpr int_type initial_value = 0xc4a016dd; // random value;
3340};
3341template <> struct IntHash<double>
3342{
3343 using int_type = uint64_t;
3344 static constexpr int_type initial_value = 0xf9ca9ba106acbba9; // random value
3345};
3346}
3347
3348/// Hash function for a PointMatrix, used in MatrixMap::map.
3349struct PointMatrixHash
3350{
3351 std::size_t operator()(const NCMesh::PointMatrix &pm) const
3352 {
3353 // This is a variation on "Hashing an array of floats" from here:
3354 // https://cs.stackexchange.com/questions/37952
3355
3356 // Make sure (at compile time) that the types have compatible sizes
3357 static_assert(sizeof(IntHash<real_t>::int_type) == sizeof(real_t), "");
3358 // Suppress maybe unused warnings
3359 MFEM_CONTRACT_VAR(IntHash<float>::initial_value);
3360 MFEM_CONTRACT_VAR(IntHash<double>::initial_value);
3361
3362 auto int_bit_cast = [](real_t val)
3363 {
3364 // std::memcpy is the proper way of doing type punning, see e.g.
3365 // https://gist.github.com/shafik/848ae25ee209f698763cffee272a58f8
3366 IntHash<real_t>::int_type int_val;
3367 std::memcpy(&int_val, &val, sizeof(real_t));
3368 return int_val;
3369 };
3370
3371 IntHash<real_t>::int_type hash = IntHash<real_t>::initial_value;
3372
3373 for (int i = 0; i < pm.np; i++)
3374 {
3375 for (int j = 0; j < pm.points[i].dim; j++)
3376 {
3377 // mix the doubles by adding their binary representations many times
3378 // over (note: 31 is 11111 in binary)
3379 real_t coord = pm.points[i].coord[j];
3380 hash = 31*hash + int_bit_cast(coord);
3381 }
3382 }
3383 return hash; // return the lowest bits of the huge sum
3384 }
3385};
3386
3387/** Helper container to keep track of point matrices encountered during
3388 * face/edge traversal and to assign unique indices to them.
3389 */
3390struct MatrixMap
3391{
3392 int GetIndex(const NCMesh::PointMatrix &pm)
3393 {
3394 int &index = map[pm];
3395 if (!index) { index = static_cast<int>(map.size()); }
3396 return index - 1;
3397 }
3398
3399 void ExportMatrices(Array<DenseMatrix*> &point_matrices) const
3400 {
3401 point_matrices.SetSize(static_cast<int>(map.size()));
3402 for (const auto &pair : map)
3403 {
3404 DenseMatrix* mat = new DenseMatrix();
3405 pair.first.GetMatrix(*mat);
3406 point_matrices[pair.second - 1] = mat;
3407 }
3408 }
3409
3410 void DumpBucketSizes() const
3411 {
3412 for (unsigned i = 0; i < map.bucket_count(); i++)
3413 {
3414 mfem::out << map.bucket_size(i) << " ";
3415 }
3416 }
3417
3418private:
3419 std::unordered_map<NCMesh::PointMatrix, int, PointMatrixHash> map;
3420};
3421
3422
3423int NCMesh::ReorderFacePointMat(int v0, int v1, int v2, int v3,
3424 int elem, const PointMatrix &pm,
3425 PointMatrix &reordered) const
3426{
3427 const Element &el = elements[elem];
3428 int master[4] =
3429 {
3430 find_node(el, v0), find_node(el, v1), find_node(el, v2),
3431 (v3 >= 0) ? find_node(el, v3) : -1
3432 };
3433 int nfv = (v3 >= 0) ? 4 : 3;
3434
3435 int local = find_local_face(el.Geom(), master[0], master[1], master[2]);
3436 const int* fv = GI[el.Geom()].faces[local];
3437
3438 reordered.np = pm.np;
3439 for (int i = 0, j; i < nfv; i++)
3440 {
3441 for (j = 0; j < nfv; j++)
3442 {
3443 if (fv[i] == master[j])
3444 {
3445 reordered.points[i] = pm.points[j];
3446 break;
3447 }
3448 }
3449 MFEM_ASSERT(j != nfv, "node not found.");
3450 }
3451 return local;
3452}
3453
3454void NCMesh::TraverseQuadFace(int vn0, int vn1, int vn2, int vn3,
3455 const PointMatrix& pm, int level,
3456 Face* eface[4], MatrixMap &matrix_map)
3457{
3458 if (level > 0)
3459 {
3460 // check if we made it to a face that is not split further
3461 Face* fa = faces.Find(vn0, vn1, vn2, vn3);
3462 if (fa)
3463 {
3464 // we have a slave face, add it to the list
3465 int elem = fa->GetSingleElement();
3466 face_list.slaves.Append(
3467 Slave(fa->index, elem, -1, Geometry::SQUARE));
3468 Slave &sl = face_list.slaves.Last();
3469
3470 // reorder the point matrix according to slave face orientation
3471 PointMatrix pm_r;
3472 sl.local = ReorderFacePointMat(vn0, vn1, vn2, vn3, elem, pm, pm_r);
3473 sl.matrix = matrix_map.GetIndex(pm_r);
3474
3475 eface[0] = eface[2] = fa;
3476 eface[1] = eface[3] = fa;
3477
3478 return;
3479 }
3480 }
3481
3482 // we need to recurse deeper
3483 int mid[5];
3484 real_t scale;
3485 const int split = QuadFaceSplitType(vn0, vn1, vn2, vn3, scale, mid);
3486
3487 Face *ef[2][4];
3488 if (split == 1) // "X" split face
3489 {
3490 Point pmid0(pm(0), pm(1), scale), pmid2(pm(2), pm(3), 1.0 - scale);
3491
3492 TraverseQuadFace(vn0, mid[0], mid[2], vn3,
3493 PointMatrix(pm(0), pmid0, pmid2, pm(3)),
3494 level+1, ef[0], matrix_map);
3495
3496 TraverseQuadFace(mid[0], vn1, vn2, mid[2],
3497 PointMatrix(pmid0, pm(1), pm(2), pmid2),
3498 level+1, ef[1], matrix_map);
3499
3500 eface[1] = ef[1][1];
3501 eface[3] = ef[0][3];
3502 eface[0] = eface[2] = NULL;
3503 }
3504 else if (split == 2) // "Y" split face
3505 {
3506 Point pmid1(pm(1), pm(2), scale), pmid3(pm(3), pm(0), 1.0 - scale);
3507
3508 TraverseQuadFace(vn0, vn1, mid[1], mid[3],
3509 PointMatrix(pm(0), pm(1), pmid1, pmid3),
3510 level+1, ef[0], matrix_map);
3511
3512 TraverseQuadFace(mid[3], mid[1], vn2, vn3,
3513 PointMatrix(pmid3, pmid1, pm(2), pm(3)),
3514 level+1, ef[1], matrix_map);
3515
3516 eface[0] = ef[0][0];
3517 eface[2] = ef[1][2];
3518 eface[1] = eface[3] = NULL;
3519 }
3520
3521 // check for a prism edge constrained by the master face
3522 if (HavePrisms() && mid[4] >= 0)
3523 {
3524 Node& enode = nodes[mid[4]];
3525 if (enode.HasEdge())
3526 {
3527 // process the edge only if it's not shared by slave faces within this
3528 // master face (i.e. the edge is "hidden")
3529 const int fi[3][2] = {{0, 0}, {1, 3}, {2, 0}};
3530 if (!ef[0][fi[split][0]] && !ef[1][fi[split][1]])
3531 {
3532 MFEM_ASSERT(enode.edge_refc == 1, "");
3533
3534 MeshId buf[4];
3535 Array<MeshId> eid(buf, 4);
3536
3537 (split == 1) ? FindEdgeElements(mid[0], vn1, vn2, mid[2], eid)
3538 /* */ : FindEdgeElements(mid[3], vn0, vn1, mid[1], eid);
3539
3540 MFEM_ASSERT(eid.Size() > 0, "edge prism not found");
3541 MFEM_ASSERT(eid.Size() < 2, "non-unique edge prism");
3542
3543 // create a slave face record with a degenerate point matrix
3544 face_list.slaves.Append(
3545 Slave(-1 - enode.edge_index,
3546 eid[0].element, eid[0].local, Geometry::SQUARE));
3547 Slave &sl = face_list.slaves.Last();
3548
3549 if (split == 1)
3550 {
3551 Point mid0(pm(0), pm(1)), mid2(pm(2), pm(3));
3552 int v1 = nodes[mid[0]].vert_index;
3553 int v2 = nodes[mid[2]].vert_index;
3554 sl.matrix =
3555 matrix_map.GetIndex(
3556 (v1 < v2) ? PointMatrix(mid0, mid2, mid2, mid0) :
3557 /* */ PointMatrix(mid2, mid0, mid0, mid2));
3558 }
3559 else
3560 {
3561 Point mid1(pm(1), pm(2)), mid3(pm(3), pm(0));
3562 int v1 = nodes[mid[1]].vert_index;
3563 int v2 = nodes[mid[3]].vert_index;
3564 sl.matrix =
3565 matrix_map.GetIndex(
3566 (v1 < v2) ? PointMatrix(mid1, mid3, mid3, mid1) :
3567 /* */ PointMatrix(mid3, mid1, mid1, mid3));
3568 }
3569 }
3570 }
3571 }
3572}
3573
3574void NCMesh::TraverseTetEdge(int vn0, int vn1, const Point &p0, const Point &p1,
3575 MatrixMap &matrix_map)
3576{
3577 int mid = nodes.FindId(vn0, vn1);
3578 if (mid < 0) { return; }
3579
3580 const Node &nd = nodes[mid];
3581 if (nd.HasEdge())
3582 {
3583 // check if the edge is already a master in 'edge_list'
3584 const auto eid_and_type = edge_list.GetMeshIdAndType(nd.edge_index);
3585 if (eid_and_type.type == NCList::MeshIdType::MASTER
3586 || eid_and_type.type == NCList::MeshIdType::CONFORMING)
3587 {
3588 // in this case we need to add an edge-face constraint, because the
3589 // non-slave edge is really a (face-)slave itself.
3590 const MeshId &eid = *eid_and_type.id;
3591 face_list.slaves.Append(
3592 Slave(-1 - eid.index, eid.element, eid.local, Geometry::TRIANGLE));
3593
3594 int v0index = nodes[vn0].vert_index;
3595 int v1index = nodes[vn1].vert_index;
3596
3597 face_list.slaves.Last().matrix =
3598 matrix_map.GetIndex((v0index < v1index) ? PointMatrix(p0, p1, p0)
3599 /* */ : PointMatrix(p1, p0, p1));
3600
3601 return; // no need to continue deeper
3602 }
3603 }
3604
3605 // recurse deeper
3606 Point pmid(p0, p1);
3607 TraverseTetEdge(vn0, mid, p0, pmid, matrix_map);
3608 TraverseTetEdge(mid, vn1, pmid, p1, matrix_map);
3609}
3610
3612 int vn2,
3613 const PointMatrix& pm, int level,
3614 MatrixMap &matrix_map)
3615{
3616 if (level > 0)
3617 {
3618 // check if we made it to a face that is not split further
3619 Face* fa = faces.Find(vn0, vn1, vn2);
3620 if (fa)
3621 {
3622 // we have a slave face, add it to the list
3623 int elem = fa->GetSingleElement();
3624 face_list.slaves.Append(
3625 Slave(fa->index, elem, -1, Geometry::TRIANGLE));
3626 Slave &sl = face_list.slaves.Last();
3627
3628 // reorder the point matrix according to slave face orientation
3629 PointMatrix pm_r;
3630 sl.local = ReorderFacePointMat(vn0, vn1, vn2, -1, elem, pm, pm_r);
3631 sl.matrix = matrix_map.GetIndex(pm_r);
3632
3633 return {true, elements[elem].rank != MyRank};
3634 }
3635 }
3636
3637 int mid[3];
3638 if (TriFaceSplit(vn0, vn1, vn2, mid))
3639 {
3640 Point pmid0(pm(0), pm(1)), pmid1(pm(1), pm(2)), pmid2(pm(2), pm(0));
3642
3643 b[0] = TraverseTriFace(vn0, mid[0], mid[2],
3644 PointMatrix(pm(0), pmid0, pmid2),
3645 level+1, matrix_map);
3646
3647 b[1] = TraverseTriFace(mid[0], vn1, mid[1],
3648 PointMatrix(pmid0, pm(1), pmid1),
3649 level+1, matrix_map);
3650
3651 b[2] = TraverseTriFace(mid[2], mid[1], vn2,
3652 PointMatrix(pmid2, pmid1, pm(2)),
3653 level+1, matrix_map);
3654
3655 b[3] = TraverseTriFace(mid[1], mid[2], mid[0],
3656 PointMatrix(pmid1, pmid2, pmid0),
3657 level+1, matrix_map);
3658
3659 // Traverse possible tet edges constrained by the master face. This needs
3660 // to occur if none of these first NC level faces are split further, OR if
3661 // they are on different processors. The different processor constraint is
3662 // needed in the case of local elements constrained by this face via the
3663 // edge alone. Cannot know this a priori, so just constrain any edge
3664 // attached to two neighbors.
3665 if (HaveTets() && (!b[3].unsplit || b[3].ghost_neighbor))
3666 {
3667 // If the faces have no further splits, so would not be captured by
3668 // normal face relations, add possible edge constraints.
3669 if (!b[1].unsplit || b[1].ghost_neighbor) { TraverseTetEdge(mid[0],mid[1], pmid0,pmid1, matrix_map); }
3670 if (!b[2].unsplit || b[2].ghost_neighbor) { TraverseTetEdge(mid[1],mid[2], pmid1,pmid2, matrix_map); }
3671 if (!b[0].unsplit || b[0].ghost_neighbor) { TraverseTetEdge(mid[2],mid[0], pmid2,pmid0, matrix_map); }
3672 }
3673 }
3674 return {false, false};
3675}
3676
3678{
3679 face_list.Clear();
3680 if (Dim < 3) { return; }
3681
3682 if (HaveTets()) { GetEdgeList(); } // needed by TraverseTetEdge()
3683
3685
3686 Array<char> processed_faces(faces.NumIds());
3687 processed_faces = 0;
3688
3689 MatrixMap matrix_maps[Geometry::NumGeom];
3690
3691 // visit faces of leaf elements
3692 for (int i = 0; i < leaf_elements.Size(); i++)
3693 {
3694 int elem = leaf_elements[i];
3695 Element &el = elements[elem];
3696 MFEM_ASSERT(!el.ref_type, "not a leaf element.");
3697
3698 GeomInfo& gi = GI[el.Geom()];
3699 for (int j = 0; j < gi.nf; j++)
3700 {
3701 // get nodes for this face
3702 int node[4];
3703 for (int k = 0; k < 4; k++)
3704 {
3705 node[k] = el.node[gi.faces[j][k]];
3706 }
3707
3708 int face = faces.FindId(node[0], node[1], node[2], node[3]);
3709 MFEM_ASSERT(face >= 0, "face not found!");
3710
3711 // tell ParNCMesh about the face
3712 ElementSharesFace(elem, j, face);
3713
3714 // have we already processed this face? skip if yes
3715 if (processed_faces[face]) { continue; }
3716 processed_faces[face] = 1;
3717
3718 int fgeom = (node[3] >= 0) ? Geometry::SQUARE : Geometry::TRIANGLE;
3719
3720 Face &fa = faces[face];
3721 bool is_master = false;
3722 if (fa.elem[0] >= 0 && fa.elem[1] >= 0)
3723 {
3724 // this is a conforming face, add it to the list
3725 face_list.conforming.Append(MeshId(fa.index, elem, j, fgeom));
3726 }
3727 else
3728 {
3729 // this is either a master face or a slave face, but we can't tell
3730 // until we traverse the face refinement 'tree'...
3731 int sb = face_list.slaves.Size();
3732 if (fgeom == Geometry::SQUARE)
3733 {
3734 Face* dummy[4];
3735 TraverseQuadFace(node[0], node[1], node[2], node[3],
3736 pm_quad_identity, 0, dummy, matrix_maps[fgeom]);
3737 }
3738 else
3739 {
3740 TraverseTriFace(node[0], node[1], node[2],
3741 pm_tri_identity, 0, matrix_maps[fgeom]);
3742 }
3743
3744 int se = face_list.slaves.Size();
3745 if (sb < se)
3746 {
3747 // found slaves, so this is a master face; add it to the list
3748 is_master = true;
3749 face_list.masters.Append(
3750 Master(fa.index, elem, j, fgeom, sb, se));
3751
3752 // also, set the master index for the slaves
3753 for (int ii = sb; ii < se; ii++)
3754 {
3755 face_list.slaves[ii].master = fa.index;
3756 }
3757 }
3758 }
3759
3760 // To support internal boundaries can only insert non-master faces.
3761 if (fa.Boundary() && !is_master) { boundary_faces.Append(face); }
3762 }
3763 }
3764
3765 // export unique point matrices
3766 for (int i = 0; i < Geometry::NumGeom; i++)
3767 {
3768 matrix_maps[i].ExportMatrices(face_list.point_matrices[i]);
3769 }
3770}
3771
3772void NCMesh::TraverseEdge(int vn0, int vn1, real_t t0, real_t t1, int flags,
3773 int level, MatrixMap &matrix_map)
3774{
3775 int mid = nodes.FindId(vn0, vn1);
3776 if (mid < 0) { return; }
3777
3778 Node &nd = nodes[mid];
3779 if (nd.HasEdge() && level > 0)
3780 {
3781 // we have a slave edge, add it to the list
3782 edge_list.slaves.Append(Slave(nd.edge_index, -1, -1, Geometry::SEGMENT));
3783
3784 Slave &sl = edge_list.slaves.Last();
3785 sl.matrix = matrix_map.GetIndex(PointMatrix(Point(t0), Point(t1)));
3786
3787 // handle slave edge orientation
3788 sl.edge_flags = flags;
3789 int v0index = nodes[vn0].vert_index;
3790 int v1index = nodes[vn1].vert_index;
3791 if (v0index > v1index) { sl.edge_flags |= 2; }
3792 }
3793
3794 // recurse deeper
3795 const real_t scale = GetScale(nd.GetScale(), vn0 > vn1);
3796
3797 const real_t tmid = ((1.0 - scale) * t0) + (scale * t1);
3798 TraverseEdge(vn0, mid, t0, tmid, flags, level+1, matrix_map);
3799 TraverseEdge(mid, vn1, tmid, t1, flags, level+1, matrix_map);
3800}
3801
3803{
3804 edge_list.Clear();
3805 if (Dim < 3) { boundary_faces.SetSize(0); }
3806
3807 Array<char> processed_edges(nodes.NumIds());
3808 processed_edges = 0;
3809
3810 Array<int> edge_element(nodes.NumIds());
3811 Array<signed char> edge_local(nodes.NumIds());
3812 edge_local = -1;
3813
3814 MatrixMap matrix_map;
3815
3816 // visit edges of leaf elements
3817 for (int i = 0; i < leaf_elements.Size(); i++)
3818 {
3819 int elem = leaf_elements[i];
3820 Element &el = elements[elem];
3821 MFEM_ASSERT(!el.ref_type, "not a leaf element.");
3822
3823 GeomInfo& gi = GI[el.Geom()];
3824 for (int j = 0; j < gi.ne; j++)
3825 {
3826 // get nodes for this edge
3827 const int* ev = gi.edges[j];
3828 int node[2] = { el.node[ev[0]], el.node[ev[1]] };
3829
3830 int enode = nodes.FindId(node[0], node[1]);
3831 MFEM_ASSERT(enode >= 0, "edge node not found!");
3832
3833 Node &nd = nodes[enode];
3834 MFEM_ASSERT(nd.HasEdge(), "edge not found!");
3835
3836 // tell ParNCMesh about the edge
3837 ElementSharesEdge(elem, j, enode);
3838
3839 // store element/local for later
3840 edge_element[nd.edge_index] = elem;
3841 edge_local[nd.edge_index] = j;
3842
3843 // skip slave edges here, they will be reached from their masters
3844 if (GetEdgeMaster(enode) >= 0)
3845 {
3846 // (2D only, store internal boundary faces)
3847 if (Dim <= 2)
3848 {
3849 int face = faces.FindId(node[0], node[0], node[1], node[1]);
3850 MFEM_ASSERT(face >= 0, "face not found!");
3851 if (faces[face].Boundary()) { boundary_faces.Append(face); }
3852 }
3853 continue;
3854 }
3855
3856 // have we already processed this edge? skip if yes
3857 if (processed_edges[enode]) { continue; }
3858 processed_edges[enode] = 1;
3859
3860 // prepare edge interval for slave traversal, handle orientation
3861 real_t t0 = 0.0, t1 = 1.0;
3862 int v0index = nodes[node[0]].vert_index;
3863 int v1index = nodes[node[1]].vert_index;
3864 int flags = (v0index > v1index) ? 1 : 0;
3865
3866 // try traversing the edge to find slave edges
3867 int sb = edge_list.slaves.Size();
3868 TraverseEdge(node[0], node[1], t0, t1, flags, 0, matrix_map);
3869
3870 int se = edge_list.slaves.Size();
3871 if (sb < se)
3872 {
3873 // found slaves, this is a master face; add it to the list
3874 edge_list.masters.Append(
3875 Master(nd.edge_index, elem, j, Geometry::SEGMENT, sb, se));
3876
3877 // also, set the master index for the slaves
3878 for (int ii = sb; ii < se; ii++)
3879 {
3880 edge_list.slaves[ii].master = nd.edge_index;
3881 }
3882 }
3883 else
3884 {
3885 // no slaves, this is a conforming edge
3886 edge_list.conforming.Append(MeshId(nd.edge_index, elem, j));
3887 // (2D only, store boundary faces)
3888 if (Dim <= 2)
3889 {
3890 int face = faces.FindId(node[0], node[0], node[1], node[1]);
3891 MFEM_ASSERT(face >= 0, "face not found!");
3892 if (faces[face].Boundary()) { boundary_faces.Append(face); }
3893 }
3894 }
3895 }
3896 }
3897
3898 // fix up slave edge element/local
3899 for (int i = 0; i < edge_list.slaves.Size(); i++)
3900 {
3901 Slave &sl = edge_list.slaves[i];
3902 int local = edge_local[sl.index];
3903 if (local >= 0)
3904 {
3905 sl.local = local;
3906 sl.element = edge_element[sl.index];
3907 }
3908 }
3909
3910 // export unique point matrices
3911 matrix_map.ExportMatrices(edge_list.point_matrices[Geometry::SEGMENT]);
3912}
3913
3915{
3916 int total = NVertices + NGhostVertices;
3917
3919 vertex_list.conforming.Reserve(total);
3920
3921 Array<char> processed_vertices(total);
3922 processed_vertices = 0;
3923
3924 // analogously to above, visit vertices of leaf elements
3925 for (int i = 0; i < leaf_elements.Size(); i++)
3926 {
3927 int elem = leaf_elements[i];
3928 Element &el = elements[elem];
3929
3930 for (int j = 0; j < GI[el.Geom()].nv; j++)
3931 {
3932 int node = el.node[j];
3933 Node &nd = nodes[node];
3934
3935 int index = nd.vert_index;
3936 if (index >= 0)
3937 {
3938 ElementSharesVertex(elem, j, node);
3939
3940 if (processed_vertices[index]) { continue; }
3941 processed_vertices[index] = 1;
3942
3943 vertex_list.conforming.Append(MeshId(index, elem, j));
3944 }
3945 }
3946 }
3947}
3948
3950 DenseMatrix &oriented_matrix) const
3951{
3952 oriented_matrix = *(point_matrices[slave.Geom()][slave.matrix]);
3953
3954 if (slave.edge_flags)
3955 {
3956 MFEM_ASSERT(oriented_matrix.Height() == 1 &&
3957 oriented_matrix.Width() == 2, "not an edge point matrix");
3958
3959 if (slave.edge_flags & 1) // master inverted
3960 {
3961 oriented_matrix(0,0) = 1.0 - oriented_matrix(0,0);
3962 oriented_matrix(0,1) = 1.0 - oriented_matrix(0,1);
3963 }
3964 if (slave.edge_flags & 2) // slave inverted
3965 {
3966 std::swap(oriented_matrix(0,0), oriented_matrix(0,1));
3967 }
3968 }
3969}
3970
3972{
3973 conforming.DeleteAll();
3974 masters.DeleteAll();
3975 slaves.DeleteAll();
3976
3977 for (int i = 0; i < Geometry::NumGeom; i++)
3978 {
3979 for (int j = 0; j < point_matrices[i].Size(); j++)
3980 {
3981 delete point_matrices[i][j];
3982 }
3983 point_matrices[i].DeleteAll();
3984 }
3985
3986 inv_index.clear();
3987}
3988
3991{
3992 BuildIndex();
3993 const auto it = inv_index.find(index);
3994 auto ft = it != inv_index.end() ? it->second.first : MeshIdType::UNRECOGNIZED;
3995 switch (ft)
3996 {
3997 case MeshIdType::CONFORMING:
3998 return {&conforming[it->second.second], it->second.first};
3999 case MeshIdType::MASTER:
4000 return {&masters[it->second.second], it->second.first};
4001 case MeshIdType::SLAVE:
4002 return {&slaves[it->second.second], it->second.first};
4003 case MeshIdType::UNRECOGNIZED:
4004 default:
4005 return {nullptr, MeshIdType::UNRECOGNIZED};
4006 }
4007}
4008
4011{
4012 BuildIndex();
4013 auto it = inv_index.find(index);
4014 return (it != inv_index.end()) ? it->second.first : MeshIdType::UNRECOGNIZED;
4015}
4016
4017bool
4019{
4020 return GetMeshIdType(index) == ft;
4021}
4022
4023void
4024NCMesh::NCList::BuildIndex() const
4025{
4026 if (inv_index.size() == 0)
4027 {
4028 auto index_compare = [](const MeshId &a, const MeshId &b) { return a.index < b.index; };
4029 auto max_conforming = std::max_element(conforming.begin(), conforming.end(),
4030 index_compare);
4031 auto max_master = std::max_element(masters.begin(), masters.end(),
4032 index_compare);
4033 auto max_slave = std::max_element(slaves.begin(), slaves.end(), index_compare);
4034
4035 int max_conforming_index = max_conforming != nullptr ? max_conforming->index :
4036 -1;
4037 int max_master_index = max_master != nullptr ? max_master->index : -1;
4038 int max_slave_index = max_slave != nullptr ? max_slave->index : -1;
4039
4040 inv_index.reserve(max(max_conforming_index, max_master_index, max_slave_index,
4041 0));
4042 for (int i = 0; i < conforming.Size(); i++)
4043 {
4044 inv_index.emplace(conforming[i].index, std::make_pair(MeshIdType::CONFORMING,
4045 i));
4046 }
4047 for (int i = 0; i < masters.Size(); i++)
4048 {
4049 inv_index.emplace(masters[i].index, std::make_pair(MeshIdType::MASTER, i));
4050 }
4051 for (int i = 0; i < slaves.Size(); i++)
4052 {
4053 inv_index.emplace(slaves[i].index, std::make_pair(MeshIdType::SLAVE, i));
4054 }
4055 }
4056}
4057
4058//// Neighbors /////////////////////////////////////////////////////////////////
4059
4060void NCMesh::CollectEdgeVertices(int v0, int v1, Array<int> &indices)
4061{
4062 int mid = nodes.FindId(v0, v1);
4063 if (mid >= 0 && nodes[mid].HasVertex())
4064 {
4065 indices.Append(mid);
4066
4067 CollectEdgeVertices(v0, mid, indices);
4068 CollectEdgeVertices(mid, v1, indices);
4069 }
4070}
4071
4072void NCMesh::CollectTriFaceVertices(int v0, int v1, int v2, Array<int> &indices)
4073{
4074 int mid[3];
4075 if (TriFaceSplit(v0, v1, v2, mid))
4076 {
4077 for (int i = 0; i < 3; i++)
4078 {
4079 indices.Append(mid[i]);
4080 }
4081
4082 CollectTriFaceVertices(v0, mid[0], mid[2], indices);
4083 CollectTriFaceVertices(mid[0], v1, mid[1], indices);
4084 CollectTriFaceVertices(mid[2], mid[1], v2, indices);
4085 CollectTriFaceVertices(mid[0], mid[1], mid[2], indices);
4086
4087 if (HaveTets()) // possible edge-face contact
4088 {
4089 CollectEdgeVertices(mid[0], mid[1], indices);
4090 CollectEdgeVertices(mid[1], mid[2], indices);
4091 CollectEdgeVertices(mid[2], mid[0], indices);
4092 }
4093 }
4094}
4095
4096void NCMesh::CollectQuadFaceVertices(int v0, int v1, int v2, int v3,
4097 Array<int> &indices)
4098{
4099 int mid[5];
4100 real_t scale;
4101 switch (QuadFaceSplitType(v0, v1, v2, v3, scale, mid))
4102 {
4103 case 1:
4104 indices.Append(mid[0]);
4105 indices.Append(mid[2]);
4106
4107 CollectQuadFaceVertices(v0, mid[0], mid[2], v3, indices);
4108 CollectQuadFaceVertices(mid[0], v1, v2, mid[2], indices);
4109
4110 if (HavePrisms()) // possible edge-face contact
4111 {
4112 CollectEdgeVertices(mid[0], mid[2], indices);
4113 }
4114 break;
4115
4116 case 2:
4117 indices.Append(mid[1]);
4118 indices.Append(mid[3]);
4119
4120 CollectQuadFaceVertices(v0, v1, mid[1], mid[3], indices);
4121 CollectQuadFaceVertices(mid[3], mid[1], v2, v3, indices);
4122
4123 if (HavePrisms()) // possible edge-face contact
4124 {
4125 CollectEdgeVertices(mid[1], mid[3], indices);
4126 }
4127 break;
4128 }
4129}
4130
4132{
4133 int nrows = leaf_elements.Size();
4134 int* I = Memory<int>(nrows + 1);
4135 int** JJ = new int*[nrows];
4136
4137 Array<int> indices;
4138 indices.Reserve(128);
4139
4140 // collect vertices coinciding with each element, including hanging vertices
4141 for (int i = 0; i < leaf_elements.Size(); i++)
4142 {
4143 int elem = leaf_elements[i];
4144 Element &el = elements[elem];
4145 MFEM_ASSERT(!el.ref_type, "not a leaf element.");
4146
4147 GeomInfo& gi = GI[el.Geom()];
4148 int* node = el.node;
4149
4150 indices.SetSize(0);
4151 for (int j = 0; j < gi.ne; j++)
4152 {
4153 const int* ev = gi.edges[j];
4154 CollectEdgeVertices(node[ev[0]], node[ev[1]], indices);
4155 }
4156
4157 if (Dim >= 3)
4158 {
4159 for (int j = 0; j < gi.nf; j++)
4160 {
4161 const int* fv = gi.faces[j];
4162 if (gi.nfv[j] == 4)
4163 {
4164 CollectQuadFaceVertices(node[fv[0]], node[fv[1]],
4165 node[fv[2]], node[fv[3]], indices);
4166 }
4167 else
4168 {
4169 CollectTriFaceVertices(node[fv[0]], node[fv[1]], node[fv[2]],
4170 indices);
4171 }
4172 }
4173 }
4174
4175 // temporarily store one row of the table
4176 indices.Sort();
4177 indices.Unique();
4178 int size = indices.Size();
4179 I[i] = size;
4180 JJ[i] = new int[size];
4181 std::memcpy(JJ[i], indices.GetData(), size * sizeof(int));
4182 }
4183
4184 // finalize the I array of the table
4185 int nnz = 0;
4186 for (int i = 0; i < nrows; i++)
4187 {
4188 int cnt = I[i];
4189 I[i] = nnz;
4190 nnz += cnt;
4191 }
4192 I[nrows] = nnz;
4193
4194 // copy the temporarily stored rows into one J array
4195 int *J = Memory<int>(nnz);
4196 nnz = 0;
4197 for (int i = 0; i < nrows; i++)
4198 {
4199 int cnt = I[i+1] - I[i];
4200 std::memcpy(J+nnz, JJ[i], cnt * sizeof(int));
4201 delete [] JJ[i];
4202 nnz += cnt;
4203 }
4204
4205 element_vertex.SetIJ(I, J, nrows);
4206
4207 delete [] JJ;
4208}
4209
4210
4212 Array<int> *neighbors,
4213 Array<char> *neighbor_set)
4214{
4215 // If A is the element-to-vertex table (see 'element_vertex') listing all
4216 // vertices touching each element, including hanging vertices, then A*A^T is
4217 // the element-to-neighbor table. Multiplying the element set with A*A^T
4218 // gives the neighbor set. To save memory, this function only computes the
4219 // action of A*A^T, the product itself is not stored anywhere.
4220
4221 // Optimization: the 'element_vertex' table does not store the obvious corner
4222 // nodes in it. The table is therefore empty for conforming meshes.
4223
4225
4226 int nleaves = leaf_elements.Size();
4227 MFEM_VERIFY(elem_set.Size() == nleaves, "");
4228 MFEM_ASSERT(element_vertex.Size() == nleaves, "");
4229
4230 // step 1: vertices = A^T * elem_set, i.e, find all vertices touching the
4231 // element set
4232
4233 Array<char> vmark(nodes.NumIds());
4234 vmark = 0;
4235
4236 for (int i = 0; i < nleaves; i++)
4237 {
4238 if (elem_set[i])
4239 {
4240 int *v = element_vertex.GetRow(i);
4241 int nv = element_vertex.RowSize(i);
4242 for (int j = 0; j < nv; j++)
4243 {
4244 vmark[v[j]] = 1;
4245 }
4246
4247 Element &el = elements[leaf_elements[i]];
4248 nv = GI[el.Geom()].nv;
4249 for (int j = 0; j < nv; j++)
4250 {
4251 vmark[el.node[j]] = 1;
4252 }
4253 }
4254 }
4255
4256 // step 2: neighbors = A * vertices, i.e., find all elements coinciding with
4257 // vertices from step 1; NOTE: in the result we don't include elements from
4258 // the original set
4259
4260 if (neighbor_set)
4261 {
4262 neighbor_set->SetSize(nleaves);
4263 *neighbor_set = 0;
4264 }
4265
4266 for (int i = 0; i < nleaves; i++)
4267 {
4268 if (!elem_set[i])
4269 {
4270 bool hit = false;
4271
4272 int *v = element_vertex.GetRow(i);
4273 int nv = element_vertex.RowSize(i);
4274 for (int j = 0; j < nv; j++)
4275 {
4276 if (vmark[v[j]]) { hit = true; break; }
4277 }
4278
4279 if (!hit)
4280 {
4281 Element &el = elements[leaf_elements[i]];
4282 nv = GI[el.Geom()].nv;
4283 for (int j = 0; j < nv; j++)
4284 {
4285 if (vmark[el.node[j]]) { hit = true; break; }
4286 }
4287 }
4288
4289 if (hit)
4290 {
4291 if (neighbors) { neighbors->Append(leaf_elements[i]); }
4292 if (neighbor_set) { (*neighbor_set)[i] = 1; }
4293 }
4294 }
4295 }
4296}
4297
4298static bool sorted_lists_intersect(const int* a, const int* b, int na, int nb)
4299{
4300 // pointers to "end" sentinel, not last entry. Not for dereferencing.
4301 const int * const a_end = a + na;
4302 const int * const b_end = b + nb;
4303 while (a != a_end && b != b_end)
4304 {
4305 if (*a < *b)
4306 {
4307 ++a;
4308 }
4309 else if (*b < *a)
4310 {
4311 ++b;
4312 }
4313 else
4314 {
4315 return true; // neither *a < *b nor *b < *a thus a == b
4316 }
4317 }
4318 return false; // no common element found
4319}
4320
4321
4322void NCMesh::FindNeighbors(int elem, Array<int> &neighbors,
4323 const Array<int> *search_set)
4324{
4325 // TODO future: this function is inefficient. For a single element, an octree
4326 // neighbor search algorithm would be better. However, the octree neighbor
4327 // algorithm is hard to get right in the multi-octree case due to the
4328 // different orientations of the octrees (i.e., the root elements).
4329
4331
4332 // sorted list of all vertex nodes touching 'elem'
4333 Array<int> vert;
4334 vert.Reserve(128);
4335
4336 // support for non-leaf 'elem', collect vertices of all children
4337 Array<int> stack;
4338 stack.Reserve(64);
4339 stack.Append(elem);
4340
4341 while (stack.Size())
4342 {
4343 Element &el = elements[stack.Last()];
4344 stack.DeleteLast();
4345
4346 if (!el.ref_type)
4347 {
4348 int *v = element_vertex.GetRow(el.index);
4349 int nv = element_vertex.RowSize(el.index);
4350 for (int i = 0; i < nv; i++)
4351 {
4352 vert.Append(v[i]);
4353 }
4354
4355 nv = GI[el.Geom()].nv;
4356 for (int i = 0; i < nv; i++)
4357 {
4358 vert.Append(el.node[i]);
4359 }
4360 }
4361 else
4362 {
4363 for (int i = 0; i < MaxElemChildren && el.child[i] >= 0; i++)
4364 {
4365 stack.Append(el.child[i]);
4366 }
4367 }
4368 }
4369
4370 vert.Sort();
4371 vert.Unique();
4372
4373 int *v1 = vert.GetData();
4374 int nv1 = vert.Size();
4375
4376 if (!search_set) { search_set = &leaf_elements; }
4377
4378 // test *all* potential neighbors from the search set
4379 for (int i = 0; i < search_set->Size(); i++)
4380 {
4381 int testme = (*search_set)[i];
4382 if (testme != elem)
4383 {
4384 Element &el = elements[testme];
4385 int *v2 = element_vertex.GetRow(el.index);
4386 int nv2 = element_vertex.RowSize(el.index);
4387
4388 bool hit = sorted_lists_intersect(v1, v2, nv1, nv2);
4389
4390 if (!hit)
4391 {
4392 int nv = GI[el.Geom()].nv;
4393 for (int j = 0; j < nv; j++)
4394 {
4395 hit = sorted_lists_intersect(&el.node[j], v1, 1, nv1);
4396 if (hit) { break; }
4397 }
4398 }
4399
4400 if (hit) { neighbors.Append(testme); }
4401 }
4402 }
4403}
4404
4406 Array<int> &expanded,
4407 const Array<int> *search_set)
4408{
4410
4411 Array<char> vmark(nodes.NumIds());
4412 vmark = 0;
4413
4414 for (int i = 0; i < elems.Size(); i++)
4415 {
4416 Element &el = elements[elems[i]];
4417
4418 int *v = element_vertex.GetRow(el.index);
4419 int nv = element_vertex.RowSize(el.index);
4420 for (int j = 0; j < nv; j++)
4421 {
4422 vmark[v[j]] = 1;
4423 }
4424
4425 nv = GI[el.Geom()].nv;
4426 for (int j = 0; j < nv; j++)
4427 {
4428 vmark[el.node[j]] = 1;
4429 }
4430 }
4431
4432 if (!search_set)
4433 {
4434 search_set = &leaf_elements;
4435 }
4436
4437 expanded.SetSize(0);
4438 for (int i = 0; i < search_set->Size(); i++)
4439 {
4440 int testme = (*search_set)[i];
4441 Element &el = elements[testme];
4442 bool hit = false;
4443
4444 int *v = element_vertex.GetRow(el.index);
4445 int nv = element_vertex.RowSize(el.index);
4446 for (int j = 0; j < nv; j++)
4447 {
4448 if (vmark[v[j]]) { hit = true; break; }
4449 }
4450
4451 if (!hit)
4452 {
4453 nv = GI[el.Geom()].nv;
4454 for (int j = 0; j < nv; j++)
4455 {
4456 if (vmark[el.node[j]]) { hit = true; break; }
4457 }
4458 }
4459
4460 if (hit) { expanded.Append(testme); }
4461 }
4462}
4463
4464int NCMesh::GetVertexRootCoord(int elem, RefCoord coord[3]) const
4465{
4466 while (1)
4467 {
4468 const Element &el = elements[elem];
4469 if (el.parent < 0) { return elem; }
4470
4471 const Element &pa = elements[el.parent];
4472 MFEM_ASSERT(pa.ref_type, "internal error");
4473
4474 int ch = 0;
4475 while (ch < MaxElemChildren && pa.child[ch] != elem) { ch++; }
4476 MFEM_ASSERT(ch < MaxElemChildren, "internal error");
4477
4478 MFEM_ASSERT(geom_parent[el.Geom()], "unsupported geometry");
4479 const RefTrf &tr = geom_parent[el.Geom()][(int) pa.ref_type][ch];
4480 tr.Apply(coord, coord);
4481
4482 elem = el.parent;
4483 }
4484}
4485
4486static bool RefPointInside(Geometry::Type geom, const RefCoord pt[3])
4487{
4488 switch (geom)
4489 {
4490 case Geometry::SQUARE:
4491 return (pt[0] >= 0) && (pt[0] <= T_ONE) &&
4492 (pt[1] >= 0) && (pt[1] <= T_ONE);
4493
4494 case Geometry::CUBE:
4495 return (pt[0] >= 0) && (pt[0] <= T_ONE) &&
4496 (pt[1] >= 0) && (pt[1] <= T_ONE) &&
4497 (pt[2] >= 0) && (pt[2] <= T_ONE);
4498
4499 case Geometry::TRIANGLE:
4500 return (pt[0] >= 0) && (pt[1] >= 0) && (pt[0] + pt[1] <= T_ONE);
4501
4502 case Geometry::PRISM:
4503 return (pt[0] >= 0) && (pt[1] >= 0) && (pt[0] + pt[1] <= T_ONE) &&
4504 (pt[2] >= 0) && (pt[2] <= T_ONE);
4505
4506 case Geometry::PYRAMID:
4507 return (pt[0] >= 0) && (pt[1] >= 0) && (pt[2] >= 0.0) &&
4508 (pt[0] + pt[2] <= T_ONE) && (pt[1] + pt[2] <= T_ONE) &&
4509 (pt[2] <= T_ONE);
4510
4511 default:
4512 MFEM_ABORT("unsupported geometry");
4513 return false;
4514 }
4515}
4516
4517void NCMesh::CollectIncidentElements(int elem, const RefCoord coord[3],
4518 Array<int> &list) const
4519{
4520 const Element &el = elements[elem];
4521 if (!el.ref_type)
4522 {
4523 list.Append(elem);
4524 return;
4525 }
4526
4527 RefCoord tcoord[3];
4528 for (int ch = 0; ch < MaxElemChildren && el.child[ch] >= 0; ch++)
4529 {
4530 const RefTrf &tr = geom_child[el.Geom()][(int) el.ref_type][ch];
4531 tr.Apply(coord, tcoord);
4532
4533 if (RefPointInside(el.Geom(), tcoord))
4534 {
4535 CollectIncidentElements(el.child[ch], tcoord, list);
4536 }
4537 }
4538}
4539
4540void NCMesh::FindVertexCousins(int elem, int local, Array<int> &cousins) const
4541{
4542 const Element &el = elements[elem];
4543
4544 RefCoord coord[3];
4545 MFEM_ASSERT(geom_corners[el.Geom()], "unsupported geometry");
4546 std::memcpy(coord, geom_corners[el.Geom()][local], sizeof(coord));
4547
4548 int root = GetVertexRootCoord(elem, coord);
4549
4550 cousins.SetSize(0);
4551 CollectIncidentElements(root, coord, cousins);
4552}
4553
4554
4555//// Coarse/fine transformations ///////////////////////////////////////////////
4556
4558{
4559 MFEM_ASSERT(np == pm.np, "");
4560 for (int i = 0; i < np; i++)
4561 {
4562 MFEM_ASSERT(points[i].dim == pm.points[i].dim, "");
4563 for (int j = 0; j < points[i].dim; j++)
4564 {
4565 if (points[i].coord[j] != pm.points[i].coord[j]) { return false; }
4566 }
4567 }
4568 return true;
4569}
4570
4572{
4573 point_matrix.SetSize(points[0].dim, np);
4574 for (int i = 0; i < np; i++)
4575 {
4576 for (int j = 0; j < points[0].dim; j++)
4577 {
4578 point_matrix(j, i) = points[i].coord[j];
4579 }
4580 }
4581}
4582
4584 Point(0), Point(1)
4585);
4587 Point(0, 0), Point(1, 0), Point(0, 1)
4588);
4590 Point(0, 0), Point(1, 0), Point(1, 1), Point(0, 1)
4591);
4593 Point(0, 0, 0), Point(1, 0, 0), Point(0, 1, 0), Point(0, 0, 1)
4594);
4596 Point(0, 0, 0), Point(1, 0, 0), Point(0, 1, 0),
4597 Point(0, 0, 1), Point(1, 0, 1), Point(0, 1, 1)
4598);
4600 Point(0, 0, 0), Point(1, 0, 0), Point(1, 1, 0),
4601 Point(0, 1, 0), Point(0, 0, 1)
4602);
4604 Point(0, 0, 0), Point(1, 0, 0), Point(1, 1, 0), Point(0, 1, 0),
4605 Point(0, 0, 1), Point(1, 0, 1), Point(1, 1, 1), Point(0, 1, 1)
4606);
4607
4609{
4610 switch (geom)
4611 {
4618 case Geometry::CUBE: return pm_hex_identity;
4619 default:
4620 MFEM_ABORT("unsupported geometry " << geom);
4621 return pm_tri_identity;
4622 }
4623}
4624
4625void NCMesh::GetPointMatrix(Geometry::Type geom, const char* ref_path,
4626 DenseMatrix& matrix) const
4627{
4628 PointMatrix pm = GetGeomIdentity(geom);
4629
4630 while (*ref_path)
4631 {
4632 int ref_type = *ref_path++;
4633 int child = *ref_path++;
4634
4635 // TODO: do this with the new child transform tables
4636
4637 if (geom == Geometry::CUBE)
4638 {
4639 if (ref_type == 1) // split along X axis
4640 {
4641 Point mid01(pm(0), pm(1)), mid23(pm(2), pm(3));
4642 Point mid67(pm(6), pm(7)), mid45(pm(4), pm(5));
4643
4644 if (child == 0)
4645 {
4646 pm = PointMatrix(pm(0), mid01, mid23, pm(3),
4647 pm(4), mid45, mid67, pm(7));
4648 }
4649 else if (child == 1)
4650 {
4651 pm = PointMatrix(mid01, pm(1), pm(2), mid23,
4652 mid45, pm(5), pm(6), mid67);
4653 }
4654 }
4655 else if (ref_type == 2) // split along Y axis
4656 {
4657 Point mid12(pm(1), pm(2)), mid30(pm(3), pm(0));
4658 Point mid56(pm(5), pm(6)), mid74(pm(7), pm(4));
4659
4660 if (child == 0)
4661 {
4662 pm = PointMatrix(pm(0), pm(1), mid12, mid30,
4663 pm(4), pm(5), mid56, mid74);
4664 }
4665 else if (child == 1)
4666 {
4667 pm = PointMatrix(mid30, mid12, pm(2), pm(3),
4668 mid74, mid56, pm(6), pm(7));
4669 }
4670 }
4671 else if (ref_type == 4) // split along Z axis
4672 {
4673 Point mid04(pm(0), pm(4)), mid15(pm(1), pm(5));
4674 Point mid26(pm(2), pm(6)), mid37(pm(3), pm(7));
4675
4676 if (child == 0)
4677 {
4678 pm = PointMatrix(pm(0), pm(1), pm(2), pm(3),
4679 mid04, mid15, mid26, mid37);
4680 }
4681 else if (child == 1)
4682 {
4683 pm = PointMatrix(mid04, mid15, mid26, mid37,
4684 pm(4), pm(5), pm(6), pm(7));
4685 }
4686 }
4687 else if (ref_type == 3) // XY split
4688 {
4689 Point mid01(pm(0), pm(1)), mid12(pm(1), pm(2));
4690 Point mid23(pm(2), pm(3)), mid30(pm(3), pm(0));
4691 Point mid45(pm(4), pm(5)), mid56(pm(5), pm(6));
4692 Point mid67(pm(6), pm(7)), mid74(pm(7), pm(4));
4693
4694 Point midf0(mid23, mid12, mid01, mid30);
4695 Point midf5(mid45, mid56, mid67, mid74);
4696
4697 if (child == 0)
4698 {
4699 pm = PointMatrix(pm(0), mid01, midf0, mid30,
4700 pm(4), mid45, midf5, mid74);
4701 }
4702 else if (child == 1)
4703 {
4704 pm = PointMatrix(mid01, pm(1), mid12, midf0,
4705 mid45, pm(5), mid56, midf5);
4706 }
4707 else if (child == 2)
4708 {
4709 pm = PointMatrix(midf0, mid12, pm(2), mid23,
4710 midf5, mid56, pm(6), mid67);
4711 }
4712 else if (child == 3)
4713 {
4714 pm = PointMatrix(mid30, midf0, mid23, pm(3),
4715 mid74, midf5, mid67, pm(7));
4716 }
4717 }
4718 else if (ref_type == 5) // XZ split
4719 {
4720 Point mid01(pm(0), pm(1)), mid23(pm(2), pm(3));
4721 Point mid45(pm(4), pm(5)), mid67(pm(6), pm(7));
4722 Point mid04(pm(0), pm(4)), mid15(pm(1), pm(5));
4723 Point mid26(pm(2), pm(6)), mid37(pm(3), pm(7));
4724
4725 Point midf1(mid01, mid15, mid45, mid04);
4726 Point midf3(mid23, mid37, mid67, mid26);
4727
4728 if (child == 0)
4729 {
4730 pm = PointMatrix(pm(0), mid01, mid23, pm(3),
4731 mid04, midf1, midf3, mid37);
4732 }
4733 else if (child == 1)
4734 {
4735 pm = PointMatrix(mid01, pm(1), pm(2), mid23,
4736 midf1, mid15, mid26, midf3);
4737 }
4738 else if (child == 2)
4739 {
4740 pm = PointMatrix(midf1, mid15, mid26, midf3,
4741 mid45, pm(5), pm(6), mid67);
4742 }
4743 else if (child == 3)
4744 {
4745 pm = PointMatrix(mid04, midf1, midf3, mid37,
4746 pm(4), mid45, mid67, pm(7));
4747 }
4748 }
4749 else if (ref_type == 6) // YZ split
4750 {
4751 Point mid12(pm(1), pm(2)), mid30(pm(3), pm(0));
4752 Point mid56(pm(5), pm(6)), mid74(pm(7), pm(4));
4753 Point mid04(pm(0), pm(4)), mid15(pm(1), pm(5));
4754 Point mid26(pm(2), pm(6)), mid37(pm(3), pm(7));
4755
4756 Point midf2(mid12, mid26, mid56, mid15);
4757 Point midf4(mid30, mid04, mid74, mid37);
4758
4759 if (child == 0)
4760 {
4761 pm = PointMatrix(pm(0), pm(1), mid12, mid30,
4762 mid04, mid15, midf2, midf4);
4763 }
4764 else if (child == 1)
4765 {
4766 pm = PointMatrix(mid30, mid12, pm(2), pm(3),
4767 midf4, midf2, mid26, mid37);
4768 }
4769 else if (child == 2)
4770 {
4771 pm = PointMatrix(mid04, mid15, midf2, midf4,
4772 pm(4), pm(5), mid56, mid74);
4773 }
4774 else if (child == 3)
4775 {
4776 pm = PointMatrix(midf4, midf2, mid26, mid37,
4777 mid74, mid56, pm(6), pm(7));
4778 }
4779 }
4780 else if (ref_type == 7) // full isotropic refinement
4781 {
4782 Point mid01(pm(0), pm(1)), mid12(pm(1), pm(2));
4783 Point mid23(pm(2), pm(3)), mid30(pm(3), pm(0));
4784 Point mid45(pm(4), pm(5)), mid56(pm(5), pm(6));
4785 Point mid67(pm(6), pm(7)), mid74(pm(7), pm(4));
4786 Point mid04(pm(0), pm(4)), mid15(pm(1), pm(5));
4787 Point mid26(pm(2), pm(6)), mid37(pm(3), pm(7));
4788
4789 Point midf0(mid23, mid12, mid01, mid30);
4790 Point midf1(mid01, mid15, mid45, mid04);
4791 Point midf2(mid12, mid26, mid56, mid15);
4792 Point midf3(mid23, mid37, mid67, mid26);
4793 Point midf4(mid30, mid04, mid74, mid37);
4794 Point midf5(mid45, mid56, mid67, mid74);
4795
4796 Point midel(midf1, midf3);
4797
4798 if (child == 0)
4799 {
4800 pm = PointMatrix(pm(0), mid01, midf0, mid30,
4801 mid04, midf1, midel, midf4);
4802 }
4803 else if (child == 1)
4804 {
4805 pm = PointMatrix(mid01, pm(1), mid12, midf0,
4806 midf1, mid15, midf2, midel);
4807 }
4808 else if (child == 2)
4809 {
4810 pm = PointMatrix(midf0, mid12, pm(2), mid23,
4811 midel, midf2, mid26, midf3);
4812 }
4813 else if (child == 3)
4814 {
4815 pm = PointMatrix(mid30, midf0, mid23, pm(3),
4816 midf4, midel, midf3, mid37);
4817 }
4818 else if (child == 4)
4819 {
4820 pm = PointMatrix(mid04, midf1, midel, midf4,
4821 pm(4), mid45, midf5, mid74);
4822 }
4823 else if (child == 5)
4824 {
4825 pm = PointMatrix(midf1, mid15, midf2, midel,
4826 mid45, pm(5), mid56, midf5);
4827 }
4828 else if (child == 6)
4829 {
4830 pm = PointMatrix(midel, midf2, mid26, midf3,
4831 midf5, mid56, pm(6), mid67);
4832 }
4833 else if (child == 7)
4834 {
4835 pm = PointMatrix(midf4, midel, midf3, mid37,
4836 mid74, midf5, mid67, pm(7));
4837 }
4838 }
4839 }
4840 else if (geom == Geometry::PRISM)
4841 {
4842 if (ref_type < 4) // XY split
4843 {
4844 Point mid01(pm(0), pm(1)), mid12(pm(1), pm(2));
4845 Point mid20(pm(2), pm(0)), mid34(pm(3), pm(4));
4846 Point mid45(pm(4), pm(5)), mid53(pm(5), pm(3));
4847
4848 if (child == 0)
4849 {
4850 pm = PointMatrix(pm(0), mid01, mid20, pm(3), mid34, mid53);
4851 }
4852 else if (child == 1)
4853 {
4854 pm = PointMatrix(mid01, pm(1), mid12, mid34, pm(4), mid45);
4855 }
4856 else if (child == 2)
4857 {
4858 pm = PointMatrix(mid20, mid12, pm(2), mid53, mid45, pm(5));
4859 }
4860 else if (child == 3)
4861 {
4862 pm = PointMatrix(mid12, mid20, mid01, mid45, mid53, mid34);
4863 }
4864 }
4865 else if (ref_type == 4) // Z split
4866 {
4867 Point mid03(pm(0), pm(3)), mid14(pm(1), pm(4)), mid25(pm(2), pm(5));
4868
4869 if (child == 0)
4870 {
4871 pm = PointMatrix(pm(0), pm(1), pm(2), mid03, mid14, mid25);
4872 }
4873 else if (child == 1)
4874 {
4875 pm = PointMatrix(mid03, mid14, mid25, pm(3), pm(4), pm(5));
4876 }
4877 }
4878 else // ref_type > 4, iso split
4879 {
4880 Point mid01(pm(0), pm(1)), mid12(pm(1), pm(2)), mid20(pm(2), pm(0));
4881 Point mid34(pm(3), pm(4)), mid45(pm(4), pm(5)), mid53(pm(5), pm(3));
4882 Point mid03(pm(0), pm(3)), mid14(pm(1), pm(4)), mid25(pm(2), pm(5));
4883
4884 Point midf2(mid01, mid14, mid34, mid03);
4885 Point midf3(mid12, mid25, mid45, mid14);
4886 Point midf4(mid20, mid03, mid53, mid25);
4887
4888 if (child == 0)
4889 {
4890 pm = PointMatrix(pm(0), mid01, mid20, mid03, midf2, midf4);
4891 }
4892 else if (child == 1)
4893 {
4894 pm = PointMatrix(mid01, pm(1), mid12, midf2, mid14, midf3);
4895 }
4896 else if (child == 2)
4897 {
4898 pm = PointMatrix(mid20, mid12, pm(2), midf4, midf3, mid25);
4899 }
4900 else if (child == 3)
4901 {
4902 pm = PointMatrix(mid12, mid20, mid01, midf3, midf4, midf2);
4903 }
4904 else if (child == 4)
4905 {
4906 pm = PointMatrix(mid03, midf2, midf4, pm(3), mid34, mid53);
4907 }
4908 else if (child == 5)
4909 {
4910 pm = PointMatrix(midf2, mid14, midf3, mid34, pm(4), mid45);
4911 }
4912 else if (child == 6)
4913 {
4914 pm = PointMatrix(midf4, midf3, mid25, mid53, mid45, pm(5));
4915 }
4916 else if (child == 7)
4917 {
4918 pm = PointMatrix(midf3, midf4, midf2, mid45, mid53, mid34);
4919 }
4920 }
4921 }
4922 else if (geom == Geometry::PYRAMID)
4923 {
4924 Point mid01(pm(0), pm(1)), mid23(pm(2), pm(3));
4925 Point mid03(pm(0), pm(3)), mid12(pm(1), pm(2));
4926 Point mid04(pm(0), pm(4)), mid14(pm(1), pm(4));
4927 Point mid24(pm(2), pm(4)), mid34(pm(3), pm(4));
4928 Point midf0(mid23, mid12, mid01, mid03);
4929
4930 if (child == 0) // Pyramid
4931 {
4932 pm = PointMatrix(pm(0), mid01, midf0, mid03, mid04);
4933 }
4934 else if (child == 1) // Pyramid
4935 {
4936 pm = PointMatrix(mid01, pm(1), mid12, midf0, mid14);
4937 }
4938 else if (child == 2) // Pyramid
4939 {
4940 pm = PointMatrix(midf0, mid12, pm(2), mid23, mid24);
4941 }
4942 else if (child == 3) // Pyramid
4943 {
4944 pm = PointMatrix(mid03, midf0, mid23, pm(3), mid34);
4945 }
4946 else if (child == 4) // Pyramid
4947 {
4948 pm = PointMatrix(mid24, mid14, mid04, mid34, midf0);
4949 }
4950 else if (child == 5) // Pyramid
4951 {
4952 pm = PointMatrix(mid04, mid14, mid24, mid34, pm(4));
4953 }
4954 else if (child == 6) // Tet
4955 {
4956 pm = PointMatrix(mid01, midf0, mid04, mid14);
4957 }
4958 else if (child == 7) // Tet
4959 {
4960 pm = PointMatrix(midf0, mid14, mid12, mid24);
4961 }
4962 else if (child == 8) // Tet
4963 {
4964 pm = PointMatrix(midf0, mid23, mid34, mid24);
4965 }
4966 else if (child == 9) // Tet
4967 {
4968 pm = PointMatrix(mid03, mid04, midf0, mid34);
4969 }
4970 }
4971 else if (geom == Geometry::TETRAHEDRON)
4972 {
4973 Point mid01(pm(0), pm(1)), mid12(pm(1), pm(2)), mid02(pm(2), pm(0));
4974 Point mid03(pm(0), pm(3)), mid13(pm(1), pm(3)), mid23(pm(2), pm(3));
4975
4976 if (child == 0)
4977 {
4978 pm = PointMatrix(pm(0), mid01, mid02, mid03);
4979 }
4980 else if (child == 1)
4981 {
4982 pm = PointMatrix(mid01, pm(1), mid12, mid13);
4983 }
4984 else if (child == 2)
4985 {
4986 pm = PointMatrix(mid02, mid12, pm(2), mid23);
4987 }
4988 else if (child == 3)
4989 {
4990 pm = PointMatrix(mid03, mid13, mid23, pm(3));
4991 }
4992 else if (child == 4)
4993 {
4994 pm = PointMatrix(mid01, mid23, mid02, mid03);
4995 }
4996 else if (child == 5)
4997 {
4998 pm = PointMatrix(mid01, mid23, mid03, mid13);
4999 }
5000 else if (child == 6)
5001 {
5002 pm = PointMatrix(mid01, mid23, mid13, mid12);
5003 }
5004 else if (child == 7)
5005 {
5006 pm = PointMatrix(mid01, mid23, mid12, mid02);
5007 }
5008 }
5009 else if (geom == Geometry::SQUARE)
5010 {
5011 if (ref_type == 1) // X split
5012 {
5013 Point mid01(pm(0), pm(1)), mid23(pm(2), pm(3));
5014
5015 if (child == 0)
5016 {
5017 pm = PointMatrix(pm(0), mid01, mid23, pm(3));
5018 }
5019 else if (child == 1)
5020 {
5021 pm = PointMatrix(mid01, pm(1), pm(2), mid23);
5022 }
5023 }
5024 else if (ref_type == 2) // Y split
5025 {
5026 Point mid12(pm(1), pm(2)), mid30(pm(3), pm(0));
5027
5028 if (child == 0)
5029 {
5030 pm = PointMatrix(pm(0), pm(1), mid12, mid30);
5031 }
5032 else if (child == 1)
5033 {
5034 pm = PointMatrix(mid30, mid12, pm(2), pm(3));
5035 }
5036 }
5037 else if (ref_type == 3) // iso split
5038 {
5039 Point mid01(pm(0), pm(1)), mid12(pm(1), pm(2));
5040 Point mid23(pm(2), pm(3)), mid30(pm(3), pm(0));
5041 Point midel(mid01, mid23);
5042
5043 if (child == 0)
5044 {
5045 pm = PointMatrix(pm(0), mid01, midel, mid30);
5046 }
5047 else if (child == 1)
5048 {
5049 pm = PointMatrix(mid01, pm(1), mid12, midel);
5050 }
5051 else if (child == 2)
5052 {
5053 pm = PointMatrix(midel, mid12, pm(2), mid23);
5054 }
5055 else if (child == 3)
5056 {
5057 pm = PointMatrix(mid30, midel, mid23, pm(3));
5058 }
5059 }
5060 }
5061 else if (geom == Geometry::TRIANGLE)
5062 {
5063 Point mid01(pm(0), pm(1)), mid12(pm(1), pm(2)), mid20(pm(2), pm(0));
5064
5065 if (child == 0)
5066 {
5067 pm = PointMatrix(pm(0), mid01, mid20);
5068 }
5069 else if (child == 1)
5070 {
5071 pm = PointMatrix(mid01, pm(1), mid12);
5072 }
5073 else if (child == 2)
5074 {
5075 pm = PointMatrix(mid20, mid12, pm(2));
5076 }
5077 else if (child == 3)
5078 {
5079 pm = PointMatrix(mid12, mid20, mid01);
5080 }
5081 }
5082 else if (geom == Geometry::SEGMENT)
5083 {
5084 Point mid01(pm(0), pm(1));
5085
5086 if (child == 0)
5087 {
5088 pm = PointMatrix(pm(0), mid01);
5089 }
5090 else if (child == 1)
5091 {
5092 pm = PointMatrix(mid01, pm(1));
5093 }
5094 }
5095 }
5096
5097 // write the points to the matrix
5098 for (int i = 0; i < pm.np; i++)
5099 {
5100 for (int j = 0; j < pm(i).dim; j++)
5101 {
5102 matrix(j, i) = pm(i).coord[j];
5103 }
5104 }
5105}
5106
5107void RemapKnotIndex(bool rev, const Array<int> &rf, int &k);
5108std::pair<int, int> QuadrupleToPair(const std::array<int, 4> &q);
5109
5110void NCMesh::RefineVertexToKnotSpan(const std::vector<Array<int>> &kvf,
5111 const Array<KnotVector*> &kvext,
5112 std::map<std::pair<int, int>,
5113 std::array<int, 2>> &parentToKV)
5114{
5115 // Note that entries 1 and 2 of vertex_to_knotspan are (k1, k2), which are knot
5116 // span (element) indices in the two dimensions of a patch face.
5117
5118 for (int i=0; i<vertex_to_knotspan.Size(); ++i)
5119 {
5120 if (Dim == 3)
5121 {
5122 int tv;
5123 std::array<int, 2> ks;
5124 std::array<int, 4> pv;
5125 vertex_to_knotspan.GetVertex3D(i, tv, ks, pv);
5126
5127 bool edgeReverse[2];
5128 for (int j=0; j<2; ++j)
5129 {
5130 const bool ascending = pv[j+1] > pv[j];
5131 edgeReverse[j] = !ascending;
5132 }
5133
5134 // The parent face is defined with vertices (pv0, pv1, pv2, pv3).
5135 const std::pair<int, int> parentPair = QuadrupleToPair(pv);
5136 const std::array<int, 2> kv = parentToKV.at(parentPair);
5137 RemapKnotIndex(edgeReverse[0], kvf[kv[0]], ks[0]);
5138 RemapKnotIndex(edgeReverse[1], kvf[kv[1]], ks[1]);
5140 }
5141 else // 2D
5142 {
5143 int tv, ks;
5144 std::array<int, 2> pv;
5145 vertex_to_knotspan.GetVertex2D(i, tv, ks, pv);
5146 const bool rev = pv[1] < pv[0];
5147 const std::pair<int, int> parentPair(rev ? pv[1] : pv[0], rev ? pv[0] : pv[1]);
5148 const std::array<int, 2> kv = parentToKV.at(parentPair);
5149 const int kvId = kv[0];
5150 RemapKnotIndex(rev, kvf[kvId], ks);
5152 }
5153 }
5154}
5155
5157{
5160
5161 for (int i = 0; i < leaf_elements.Size(); i++)
5162 {
5163 int elem = leaf_elements[i];
5164 if (!IsGhost(elements[elem])) { coarse_elements.Append(elem); }
5165 }
5166
5167 transforms.embeddings.DeleteAll();
5168}
5169
5170void NCMesh::TraverseRefinements(int elem, int coarse_index,
5171 std::string &ref_path, RefPathMap &map) const
5172{
5173 const Element &el = elements[elem];
5174 if (!el.ref_type)
5175 {
5176 int &matrix = map[ref_path];
5177 if (!matrix) { matrix = static_cast<int>(map.size()); }
5178
5180 emb.parent = coarse_index;
5181 emb.matrix = matrix - 1;
5182 emb.geom = el.Geom();
5183 emb.ghost = IsGhost(el);
5184 }
5185 else
5186 {
5187 MFEM_ASSERT(el.tet_type == 0, "not implemented");
5188
5189 ref_path.push_back(el.ref_type);
5190 ref_path.push_back(0);
5191
5192 for (int i = 0; i < MaxElemChildren; i++)
5193 {
5194 if (el.child[i] >= 0)
5195 {
5196 ref_path[ref_path.length()-1] = i;
5197 TraverseRefinements(el.child[i], coarse_index, ref_path, map);
5198 }
5199 }
5200 ref_path.resize(ref_path.length()-2);
5201 }
5202}
5203
5205{
5206 MFEM_VERIFY(coarse_elements.Size() || !leaf_elements.Size(),
5207 "GetRefinementTransforms() must be preceded by MarkCoarseLevel()"
5208 " and Refine().");
5209
5210 if (!transforms.embeddings.Size())
5211 {
5212 transforms.Clear();
5214
5215 std::string ref_path;
5216 ref_path.reserve(100);
5217
5218 RefPathMap path_map[Geometry::NumGeom];
5219 for (int g = 0; g < Geometry::NumGeom; g++)
5220 {
5221 path_map[g][ref_path] = 1; // empty path == identity
5222 }
5223
5224 int used_geoms = 0;
5225 for (int i = 0; i < coarse_elements.Size(); i++)
5226 {
5227 int geom = elements[coarse_elements[i]].geom;
5228 TraverseRefinements(coarse_elements[i], i, ref_path, path_map[geom]);
5229 used_geoms |= (1 << geom);
5230 }
5231
5232 for (int g = 0; g < Geometry::NumGeom; g++)
5233 {
5234 if (used_geoms & (1 << g))
5235 {
5237 const PointMatrix &identity = GetGeomIdentity(geom);
5238
5240 .SetSize(Dim, identity.np, static_cast<int>(path_map[g].size()));
5241
5242 // calculate the point matrices
5243 RefPathMap::iterator it;
5244 for (it = path_map[g].begin(); it != path_map[g].end(); ++it)
5245 {
5246 GetPointMatrix(geom, it->first.c_str(),
5247 transforms.point_matrices[g](it->second-1));
5248 }
5249 }
5250 }
5251 }
5252 return transforms;
5253}
5254
5256{
5257 MFEM_VERIFY(transforms.embeddings.Size() || !leaf_elements.Size(),
5258 "GetDerefinementTransforms() must be preceded by Derefine().");
5259
5261 {
5262 std::map<int, int> mat_no[Geometry::NumGeom];
5263 for (int g = 0; g < Geometry::NumGeom; g++)
5264 {
5265 mat_no[g][0] = 1; // 0 == identity
5266 }
5267
5268 // assign numbers to the different matrices used
5269 for (int i = 0; i < transforms.embeddings.Size(); i++)
5270 {
5272 int code = emb.matrix; // see SetDerefMatrixCodes()
5273 if (code)
5274 {
5275 int &matrix = mat_no[emb.geom][code];
5276 if (!matrix) { matrix = static_cast<int>(mat_no[emb.geom].size()); }
5277
5278 emb.matrix = matrix - 1;
5279 }
5280 }
5281
5282 for (int g = 0; g < Geometry::NumGeom; g++)
5283 {
5284 if (Geoms & (1 << g))
5285 {
5287 const PointMatrix &identity = GetGeomIdentity(geom);
5288
5290 .SetSize(Dim, identity.np, static_cast<int>(mat_no[geom].size()));
5291
5292 // calculate point matrices
5293 for (auto it = mat_no[geom].begin(); it != mat_no[geom].end(); ++it)
5294 {
5295 char path[3] = { 0, 0, 0 };
5296
5297 int code = it->first;
5298 if (code)
5299 {
5300 path[0] = code >> 4; // ref_type (see SetDerefMatrixCodes())
5301 path[1] = code & 0xf; // child
5302 }
5303
5304 GetPointMatrix(geom, path,
5305 transforms.point_matrices[geom](it->second-1));
5306 }
5307 }
5308 }
5309 }
5310 return transforms;
5311}
5312
5314 bool want_ghosts) const
5315{
5316 Array<Connection> conn;
5317 conn.Reserve(embeddings.Size());
5318
5319 int max_parent = -1;
5320 for (int i = 0; i < embeddings.Size(); i++)
5321 {
5322 const Embedding &emb = embeddings[i];
5323 if ((emb.parent >= 0) &&
5324 (!emb.ghost || want_ghosts))
5325 {
5326 conn.Append(Connection(emb.parent, i));
5327 max_parent = std::max(emb.parent, max_parent);
5328 }
5329 }
5330
5331 conn.Sort(); // NOTE: unique is not necessary
5332 coarse_to_fine.MakeFromList(max_parent+1, conn);
5333}
5334
5340
5342{
5343 for (int i = 0; i < Geometry::NumGeom; i++)
5344 {
5345 point_matrices[i].SetSize(0, 0, 0);
5346 }
5347 embeddings.DeleteAll();
5348}
5349
5351{
5352 // return true if point matrices are present for any geometry
5353 for (int i = 0; i < Geometry::NumGeom; i++)
5354 {
5355 if (point_matrices[i].SizeK()) { return true; }
5356 }
5357 return false;
5358}
5359
5361{
5362 for (int g = 0; g < Geometry::NumGeom; ++g)
5363 {
5364 a.point_matrices[g].Swap(b.point_matrices[g]);
5365 }
5366 Swap(a.embeddings, b.embeddings);
5367}
5368
5369
5370//// SFC Ordering //////////////////////////////////////////////////////////////
5371
5372static int sgn(int x)
5373{
5374 return (x < 0) ? -1 : (x > 0) ? 1 : 0;
5375}
5376
5377static void HilbertSfc2D(int x, int y, int ax, int ay, int bx, int by,
5378 Array<int> &coords)
5379{
5380 int w = std::abs(ax + ay);
5381 int h = std::abs(bx + by);
5382
5383 int dax = sgn(ax), day = sgn(ay); // unit major direction ("right")
5384 int dbx = sgn(bx), dby = sgn(by); // unit orthogonal direction ("up")
5385
5386 if (h == 1) // trivial row fill
5387 {
5388 for (int i = 0; i < w; i++, x += dax, y += day)
5389 {
5390 coords.Append(x);
5391 coords.Append(y);
5392 }
5393 return;
5394 }
5395 if (w == 1) // trivial column fill
5396 {
5397 for (int i = 0; i < h; i++, x += dbx, y += dby)
5398 {
5399 coords.Append(x);
5400 coords.Append(y);
5401 }
5402 return;
5403 }
5404
5405 int ax2 = ax/2, ay2 = ay/2;
5406 int bx2 = bx/2, by2 = by/2;
5407
5408 int w2 = std::abs(ax2 + ay2);
5409 int h2 = std::abs(bx2 + by2);
5410
5411 if (2*w > 3*h) // long case: split in two parts only
5412 {
5413 if ((w2 & 0x1) && (w > 2))
5414 {
5415 ax2 += dax, ay2 += day; // prefer even steps
5416 }
5417
5418 HilbertSfc2D(x, y, ax2, ay2, bx, by, coords);
5419 HilbertSfc2D(x+ax2, y+ay2, ax-ax2, ay-ay2, bx, by, coords);
5420 }
5421 else // standard case: one step up, one long horizontal step, one step down
5422 {
5423 if ((h2 & 0x1) && (h > 2))
5424 {
5425 bx2 += dbx, by2 += dby; // prefer even steps
5426 }
5427
5428 HilbertSfc2D(x, y, bx2, by2, ax2, ay2, coords);
5429 HilbertSfc2D(x+bx2, y+by2, ax, ay, bx-bx2, by-by2, coords);
5430 HilbertSfc2D(x+(ax-dax)+(bx2-dbx), y+(ay-day)+(by2-dby),
5431 -bx2, -by2, -(ax-ax2), -(ay-ay2), coords);
5432 }
5433}
5434
5435static void HilbertSfc3D(int x, int y, int z,
5436 int ax, int ay, int az,
5437 int bx, int by, int bz,
5438 int cx, int cy, int cz,
5439 Array<int> &coords)
5440{
5441 int w = std::abs(ax + ay + az);
5442 int h = std::abs(bx + by + bz);
5443 int d = std::abs(cx + cy + cz);
5444
5445 int dax = sgn(ax), day = sgn(ay), daz = sgn(az); // unit major dir ("right")
5446 int dbx = sgn(bx), dby = sgn(by), dbz = sgn(bz); // unit ortho dir ("forward")
5447 int dcx = sgn(cx), dcy = sgn(cy), dcz = sgn(cz); // unit ortho dir ("up")
5448
5449 // trivial row/column fills
5450 if (h == 1 && d == 1)
5451 {
5452 for (int i = 0; i < w; i++, x += dax, y += day, z += daz)
5453 {
5454 coords.Append(x);
5455 coords.Append(y);
5456 coords.Append(z);
5457 }
5458 return;
5459 }
5460 if (w == 1 && d == 1)
5461 {
5462 for (int i = 0; i < h; i++, x += dbx, y += dby, z += dbz)
5463 {
5464 coords.Append(x);
5465 coords.Append(y);
5466 coords.Append(z);
5467 }
5468 return;
5469 }
5470 if (w == 1 && h == 1)
5471 {
5472 for (int i = 0; i < d; i++, x += dcx, y += dcy, z += dcz)
5473 {
5474 coords.Append(x);
5475 coords.Append(y);
5476 coords.Append(z);
5477 }
5478 return;
5479 }
5480
5481 int ax2 = ax/2, ay2 = ay/2, az2 = az/2;
5482 int bx2 = bx/2, by2 = by/2, bz2 = bz/2;
5483 int cx2 = cx/2, cy2 = cy/2, cz2 = cz/2;
5484
5485 int w2 = std::abs(ax2 + ay2 + az2);
5486 int h2 = std::abs(bx2 + by2 + bz2);
5487 int d2 = std::abs(cx2 + cy2 + cz2);
5488
5489 // prefer even steps
5490 if ((w2 & 0x1) && (w > 2))
5491 {
5492 ax2 += dax, ay2 += day, az2 += daz;
5493 }
5494 if ((h2 & 0x1) && (h > 2))
5495 {
5496 bx2 += dbx, by2 += dby, bz2 += dbz;
5497 }
5498 if ((d2 & 0x1) && (d > 2))
5499 {
5500 cx2 += dcx, cy2 += dcy, cz2 += dcz;
5501 }
5502
5503 // wide case, split in w only
5504 if ((2*w > 3*h) && (2*w > 3*d))
5505 {
5506 HilbertSfc3D(x, y, z,
5507 ax2, ay2, az2,
5508 bx, by, bz,
5509 cx, cy, cz, coords);
5510
5511 HilbertSfc3D(x+ax2, y+ay2, z+az2,
5512 ax-ax2, ay-ay2, az-az2,
5513 bx, by, bz,
5514 cx, cy, cz, coords);
5515 }
5516 // do not split in d
5517 else if (3*h > 4*d)
5518 {
5519 HilbertSfc3D(x, y, z,
5520 bx2, by2, bz2,
5521 cx, cy, cz,
5522 ax2, ay2, az2, coords);
5523
5524 HilbertSfc3D(x+bx2, y+by2, z+bz2,
5525 ax, ay, az,
5526 bx-bx2, by-by2, bz-bz2,
5527 cx, cy, cz, coords);
5528
5529 HilbertSfc3D(x+(ax-dax)+(bx2-dbx),
5530 y+(ay-day)+(by2-dby),
5531 z+(az-daz)+(bz2-dbz),
5532 -bx2, -by2, -bz2,
5533 cx, cy, cz,
5534 -(ax-ax2), -(ay-ay2), -(az-az2), coords);
5535 }
5536 // do not split in h
5537 else if (3*d > 4*h)
5538 {
5539 HilbertSfc3D(x, y, z,
5540 cx2, cy2, cz2,
5541 ax2, ay2, az2,
5542 bx, by, bz, coords);
5543
5544 HilbertSfc3D(x+cx2, y+cy2, z+cz2,
5545 ax, ay, az,
5546 bx, by, bz,
5547 cx-cx2, cy-cy2, cz-cz2, coords);
5548
5549 HilbertSfc3D(x+(ax-dax)+(cx2-dcx),
5550 y+(ay-day)+(cy2-dcy),
5551 z+(az-daz)+(cz2-dcz),
5552 -cx2, -cy2, -cz2,
5553 -(ax-ax2), -(ay-ay2), -(az-az2),
5554 bx, by, bz, coords);
5555 }
5556 // regular case, split in all w/h/d
5557 else
5558 {
5559 HilbertSfc3D(x, y, z,
5560 bx2, by2, bz2,
5561 cx2, cy2, cz2,
5562 ax2, ay2, az2, coords);
5563
5564 HilbertSfc3D(x+bx2, y+by2, z+bz2,
5565 cx, cy, cz,
5566 ax2, ay2, az2,
5567 bx-bx2, by-by2, bz-bz2, coords);
5568
5569 HilbertSfc3D(x+(bx2-dbx)+(cx-dcx),
5570 y+(by2-dby)+(cy-dcy),
5571 z+(bz2-dbz)+(cz-dcz),
5572 ax, ay, az,
5573 -bx2, -by2, -bz2,
5574 -(cx-cx2), -(cy-cy2), -(cz-cz2), coords);
5575
5576 HilbertSfc3D(x+(ax-dax)+bx2+(cx-dcx),
5577 y+(ay-day)+by2+(cy-dcy),
5578 z+(az-daz)+bz2+(cz-dcz),
5579 -cx, -cy, -cz,
5580 -(ax-ax2), -(ay-ay2), -(az-az2),
5581 bx-bx2, by-by2, bz-bz2, coords);
5582
5583 HilbertSfc3D(x+(ax-dax)+(bx2-dbx),
5584 y+(ay-day)+(by2-dby),
5585 z+(az-daz)+(bz2-dbz),
5586 -bx2, -by2, -bz2,
5587 cx2, cy2, cz2,
5588 -(ax-ax2), -(ay-ay2), -(az-az2), coords);
5589 }
5590}
5591
5592void NCMesh::GridSfcOrdering2D(int width, int height, Array<int> &coords)
5593{
5594 coords.SetSize(0);
5595 coords.Reserve(2*width*height);
5596
5597 if (width >= height)
5598 {
5599 HilbertSfc2D(0, 0, width, 0, 0, height, coords);
5600 }
5601 else
5602 {
5603 HilbertSfc2D(0, 0, 0, height, width, 0, coords);
5604 }
5605}
5606
5607void NCMesh::GridSfcOrdering3D(int width, int height, int depth,
5608 Array<int> &coords)
5609{
5610 coords.SetSize(0);
5611 coords.Reserve(3*width*height*depth);
5612
5613 if (width >= height && width >= depth)
5614 {
5615 HilbertSfc3D(0, 0, 0,
5616 width, 0, 0,
5617 0, height, 0,
5618 0, 0, depth, coords);
5619 }
5620 else if (height >= width && height >= depth)
5621 {
5622 HilbertSfc3D(0, 0, 0,
5623 0, height, 0,
5624 width, 0, 0,
5625 0, 0, depth, coords);
5626 }
5627 else // depth >= width && depth >= height
5628 {
5629 HilbertSfc3D(0, 0, 0,
5630 0, 0, depth,
5631 width, 0, 0,
5632 0, height, 0, coords);
5633 }
5634}
5635
5636
5637//// Utility ///////////////////////////////////////////////////////////////////
5638
5639void NCMesh::GetEdgeVertices(const MeshId &edge_id, int vert_index[2],
5640 bool oriented) const
5641{
5642 const Element &el = elements[edge_id.element];
5643 const GeomInfo& gi = GI[el.Geom()];
5644 const int* ev = gi.edges[(int) edge_id.local];
5645
5646 int n0 = el.node[ev[0]], n1 = el.node[ev[1]];
5647 if (n0 > n1) { std::swap(n0, n1); }
5648
5649 vert_index[0] = nodes[n0].vert_index;
5650 vert_index[1] = nodes[n1].vert_index;
5651
5652 if (oriented && vert_index[0] > vert_index[1])
5653 {
5654 std::swap(vert_index[0], vert_index[1]);
5655 }
5656}
5657
5659{
5660 const Element &el = elements[edge_id.element];
5661 const GeomInfo& gi = GI[el.Geom()];
5662 const int* ev = gi.edges[(int) edge_id.local];
5663
5664 int v0 = nodes[el.node[ev[0]]].vert_index;
5665 int v1 = nodes[el.node[ev[1]]].vert_index;
5666
5667 return ((v0 < v1 && ev[0] > ev[1]) || (v0 > v1 && ev[0] < ev[1])) ? -1 : 1;
5668}
5669
5671 int vert_index[4], int edge_index[4],
5672 int edge_orientation[4]) const
5673{
5674 MFEM_ASSERT(Dim >= 3, "");
5675
5676 const Element &el = elements[face_id.element];
5677 const GeomInfo& gi = GI[el.Geom()];
5678
5679 const int *fv = gi.faces[(int) face_id.local];
5680 const int nfv = gi.nfv[(int) face_id.local];
5681
5682 vert_index[3] = edge_index[3] = -1;
5683 edge_orientation[3] = 0;
5684
5685 for (int i = 0; i < nfv; i++)
5686 {
5687 vert_index[i] = nodes[el.node[fv[i]]].vert_index;
5688 }
5689
5690 for (int i = 0; i < nfv; i++)
5691 {
5692 int j = i+1;
5693 if (j >= nfv) { j = 0; }
5694
5695 int n1 = el.node[fv[i]];
5696 int n2 = el.node[fv[j]];
5697
5698 const Node* en = nodes.Find(n1, n2);
5699 MFEM_ASSERT(en != NULL, "edge not found.");
5700
5701 edge_index[i] = en->edge_index;
5702 edge_orientation[i] = (vert_index[i] < vert_index[j]) ? 1 : -1;
5703 }
5704
5705 return nfv;
5706}
5707
5708int NCMesh::GetEdgeMaster(int node) const
5709{
5710 MFEM_ASSERT(node >= 0, "edge node not found.");
5711 const Node &nd = nodes[node];
5712
5713 int p1 = nd.p1, p2 = nd.p2;
5714 MFEM_ASSERT(p1 != p2, "invalid edge node.");
5715
5716 const Node &n1 = nodes[p1], &n2 = nodes[p2];
5717
5718 int n1p1 = n1.p1, n1p2 = n1.p2;
5719 int n2p1 = n2.p1, n2p2 = n2.p2;
5720
5721 if ((n2p1 != n2p2) && (p1 == n2p1 || p1 == n2p2))
5722 {
5723 // n1 is parent of n2: (n1)--(nd)--(n2)------(*)
5724 if (n2.HasEdge()) { return p2; }
5725 else { return GetEdgeMaster(p2); }
5726 }
5727
5728 if ((n1p1 != n1p2) && (p2 == n1p1 || p2 == n1p2))
5729 {
5730 // n2 is parent of n1: (n2)--(nd)--(n1)------(*)
5731 if (n1.HasEdge()) { return p1; }
5732 else { return GetEdgeMaster(p1); }
5733 }
5734
5735 return -1;
5736}
5737
5738int NCMesh::GetEdgeMaster(int v1, int v2) const
5739{
5740 int node = nodes.FindId(vertex_nodeId[v1], vertex_nodeId[v2]);
5741 MFEM_ASSERT(node >= 0 && nodes[node].HasEdge(), "(v1, v2) is not an edge.");
5742
5743 int master = GetEdgeMaster(node);
5744 return (master >= 0) ? nodes[master].edge_index : -1;
5745}
5746
5748{
5749 int elem = leaf_elements[i];
5750 int depth = 0, parent;
5751 while ((parent = elements[elem].parent) != -1)
5752 {
5753 elem = parent;
5754 depth++;
5755 }
5756 return depth;
5757}
5758
5760{
5761 int elem = leaf_elements[i];
5762 int parent, reduction = 1;
5763 while ((parent = elements[elem].parent) != -1)
5764 {
5765 if (elements[parent].ref_type & 1) { reduction *= 2; }
5766 if (elements[parent].ref_type & 2) { reduction *= 2; }
5767 if (elements[parent].ref_type & 4) { reduction *= 2; }
5768 elem = parent;
5769 }
5770 return reduction;
5771}
5772
5774 Array<int> &face_indices,
5775 Array<int> &face_attribs) const
5776{
5777 const Element &el = elements[leaf_elements[leaf_elem]];
5778 const GeomInfo& gi = GI[el.Geom()];
5779
5780 face_indices.SetSize(gi.nf);
5781 face_attribs.SetSize(gi.nf);
5782
5783 for (int i = 0; i < gi.nf; i++)
5784 {
5785 const int* fv = gi.faces[i];
5786 const Face *face = faces.Find(el.node[fv[0]], el.node[fv[1]],
5787 el.node[fv[2]], el.node[fv[3]]);
5788 MFEM_ASSERT(face, "face not found");
5789 face_indices[i] = face->index;
5790 face_attribs[i] = face->attribute;
5791 }
5792}
5793void NCMesh::FindFaceNodes(int face, int node[4]) const
5794{
5795 auto tmp = FindFaceNodes(face);
5796 std::copy(tmp.begin(), tmp.end(), node);
5797}
5798
5799std::array<int, 4> NCMesh::FindFaceNodes(int face) const
5800{
5801 return FindFaceNodes(faces[face]);
5802}
5803
5804std::array<int, 4> NCMesh::FindFaceNodes(const Face &fa) const
5805{
5806 // Obtain face nodes from one of its elements (note that face->p1, p2, p3
5807 // cannot be used directly since they are not in order and p4 is missing).
5808 int elem = fa.elem[0];
5809 if (elem < 0) { elem = fa.elem[1]; }
5810 MFEM_ASSERT(elem >= 0, "Face has no elements?");
5811
5812 const Element &el = elements[elem];
5813 int f = find_local_face(el.Geom(),
5814 find_node(el, fa.p1),
5815 find_node(el, fa.p2),
5816 find_node(el, fa.p3));
5817
5818 const int* fv = GI[el.Geom()].faces[f];
5819 std::array<int, 4> node;
5820 for (int i = 0; i < 4; i++)
5821 {
5822 node[i] = el.node[fv[i]];
5823 }
5824 return node;
5825}
5826
5827void NCMesh::GetBoundaryClosure(const Array<int> &bdr_attr_is_ess,
5828 Array<int> &bdr_vertices, Array<int> &bdr_edges,
5829 Array<int> &bdr_faces)
5830{
5831 bdr_vertices.SetSize(0);
5832 bdr_edges.SetSize(0);
5833 bdr_faces.SetSize(0);
5834
5835 if (Dim == 3)
5836 {
5837 GetFaceList(); // make sure 'boundary_faces' is up to date
5838 for (int f : boundary_faces)
5839 {
5840 if (bdr_attr_is_ess[faces[f].attribute - 1])
5841 {
5842 auto node = FindFaceNodes(f);
5843 int nfv = (node[3] < 0) ? 3 : 4;
5844
5845 for (int j = 0; j < nfv; j++)
5846 {
5847 bdr_vertices.Append(nodes[node[j]].vert_index);
5848
5849 int enode = nodes.FindId(node[j], node[(j+1) % nfv]);
5850 MFEM_ASSERT(enode >= 0 && nodes[enode].HasEdge(), "Edge not found.");
5851 bdr_edges.Append(nodes[enode].edge_index);
5852
5853 while ((enode = GetEdgeMaster(enode)) >= 0)
5854 {
5855 // append master edges that may not be accessible from any
5856 // boundary element, this happens in 3D in re-entrant corners
5857 bdr_edges.Append(nodes[enode].edge_index);
5858 }
5859 }
5860
5861 // If the face is a slave face, collect its non-ghost master face
5862 const Face &face = faces[f];
5863
5864 const auto id_and_type = GetFaceList().GetMeshIdAndType(face.index);
5865 if (id_and_type.type == NCList::MeshIdType::SLAVE)
5866 {
5867 // A slave face must mark its masters
5868 const auto &slave_face_id = static_cast<const Slave&>(*id_and_type.id);
5869 bdr_faces.Append(slave_face_id.master);
5870 }
5871 }
5872 }
5873 }
5874 else if (Dim == 2)
5875 {
5876 GetFaceList();
5877 GetEdgeList(); // make sure 'boundary_faces' is up to date
5878
5879 for (int f : boundary_faces)
5880 {
5881 Face &face = faces[f];
5882 if (bdr_attr_is_ess[face.attribute - 1])
5883 {
5884 bdr_vertices.Append(nodes[face.p1].vert_index);
5885 bdr_vertices.Append(nodes[face.p3].vert_index);
5886 }
5887
5888 const auto id_and_type = GetEdgeList().GetMeshIdAndType(face.index);
5889 if (id_and_type.type == NCList::MeshIdType::SLAVE)
5890 {
5891 // A slave face must mark its masters
5892 const auto &slave_edge_id = static_cast<const Slave&>(*id_and_type.id);
5893 bdr_edges.Append(slave_edge_id.master);
5894 }
5895 }
5896 }
5897
5898 // Filter, sort and unique an array, so it contains only local unique values.
5899 auto FilterSortUnique = [](Array<int> &v, int N)
5900 {
5901 // Perform the O(N) filter before the O(NlogN) sort. begin -> it is only
5902 // entries < N.
5903 auto it = std::remove_if(v.begin(), v.end(), [N](int i) { return i >= N; });
5904 std::sort(v.begin(), it);
5905 v.SetSize(std::distance(v.begin(), std::unique(v.begin(), it)));
5906 };
5907
5908 FilterSortUnique(bdr_vertices, NVertices);
5909 FilterSortUnique(bdr_edges, NEdges);
5910 FilterSortUnique(bdr_faces, NFaces);
5911}
5912
5913int NCMesh::EdgeSplitLevel(int vn1, int vn2) const
5914{
5915 int mid = nodes.FindId(vn1, vn2);
5916 if (mid < 0 || !nodes[mid].HasVertex()) { return 0; }
5917 return 1 + std::max(EdgeSplitLevel(vn1, mid), EdgeSplitLevel(mid, vn2));
5918}
5919
5920int NCMesh::TriFaceSplitLevel(int vn1, int vn2, int vn3) const
5921{
5922 int mid[3];
5923 if (TriFaceSplit(vn1, vn2, vn3, mid) &&
5924 faces.FindId(vn1, vn2, vn3) < 0)
5925 {
5926 return 1 + max(TriFaceSplitLevel(vn1, mid[0], mid[2]),
5927 TriFaceSplitLevel(mid[0], vn2, mid[1]),
5928 TriFaceSplitLevel(mid[2], mid[1], vn3),
5929 TriFaceSplitLevel(mid[0], mid[1], mid[2]));
5930 }
5931
5932 return 0; // not split
5933}
5934
5935void NCMesh::QuadFaceSplitLevel(int vn1, int vn2, int vn3, int vn4,
5936 int& h_level, int& v_level) const
5937{
5938 int hl1, hl2, vl1, vl2;
5939 int mid[5];
5940 real_t scale;
5941
5942 switch (QuadFaceSplitType(vn1, vn2, vn3, vn4, scale, mid))
5943 {
5944 case 0: // not split
5945 h_level = v_level = 0;
5946 break;
5947
5948 case 1: // vertical
5949 QuadFaceSplitLevel(vn1, mid[0], mid[2], vn4, hl1, vl1);
5950 QuadFaceSplitLevel(mid[0], vn2, vn3, mid[2], hl2, vl2);
5951 h_level = std::max(hl1, hl2);
5952 v_level = std::max(vl1, vl2) + 1;
5953 break;
5954
5955 default: // horizontal
5956 QuadFaceSplitLevel(vn1, vn2, mid[1], mid[3], hl1, vl1);
5957 QuadFaceSplitLevel(mid[3], mid[1], vn3, vn4, hl2, vl2);
5958 h_level = std::max(hl1, hl2) + 1;
5959 v_level = std::max(vl1, vl2);
5960 }
5961}
5962
5963int NCMesh::QuadFaceSplitLevel(int vn1, int vn2, int vn3, int vn4) const
5964{
5965 int h_level, v_level;
5966 QuadFaceSplitLevel(vn1, vn2, vn3, vn4, h_level, v_level);
5967 return h_level + v_level;
5968}
5969
5970void NCMesh::CountSplits(int elem, int splits[3]) const
5971{
5972 const Element &el = elements[elem];
5973 const int* node = el.node;
5974 GeomInfo& gi = GI[el.Geom()];
5975
5976 int elevel[MaxElemEdges];
5977 for (int i = 0; i < gi.ne; i++)
5978 {
5979 const int* ev = gi.edges[i];
5980 elevel[i] = EdgeSplitLevel(node[ev[0]], node[ev[1]]);
5981 }
5982
5983 int flevel[MaxElemFaces][2];
5984 if (Dim >= 3)
5985 {
5986 for (int i = 0; i < gi.nf; i++)
5987 {
5988 const int* fv = gi.faces[i];
5989 if (gi.nfv[i] == 4)
5990 {
5991 QuadFaceSplitLevel(node[fv[0]], node[fv[1]],
5992 node[fv[2]], node[fv[3]],
5993 flevel[i][1], flevel[i][0]);
5994 }
5995 else
5996 {
5997 flevel[i][1] = 0;
5998 flevel[i][0] =
5999 TriFaceSplitLevel(node[fv[0]], node[fv[1]], node[fv[2]]);
6000 }
6001 }
6002 }
6003
6004 if (el.Geom() == Geometry::CUBE)
6005 {
6006 splits[0] = max(flevel[0][0], flevel[1][0], flevel[3][0], flevel[5][0],
6007 elevel[0], elevel[2], elevel[4], elevel[6]);
6008
6009 splits[1] = max(flevel[0][1], flevel[2][0], flevel[4][0], flevel[5][1],
6010 elevel[1], elevel[3], elevel[5], elevel[7]);
6011
6012 splits[2] = max(flevel[1][1], flevel[2][1], flevel[3][1], flevel[4][1],
6013 elevel[8], elevel[9], elevel[10], elevel[11]);
6014 }
6015 else if (el.Geom() == Geometry::PRISM)
6016 {
6017 splits[0] = splits[1] = max(flevel[0][0], flevel[1][0], 0,
6018 flevel[2][0], flevel[3][0], flevel[4][0],
6019 elevel[0], elevel[1], elevel[2],
6020 elevel[3], elevel[4], elevel[5]);
6021
6022 splits[2] = max(flevel[2][1], flevel[3][1], flevel[4][1],
6023 elevel[6], elevel[7], elevel[8]);
6024 }
6025 else if (el.Geom() == Geometry::PYRAMID)
6026 {
6027 splits[0] = max(flevel[0][0], flevel[1][0], 0,
6028 flevel[2][0], flevel[3][0], flevel[4][0],
6029 elevel[0], elevel[1], elevel[2],
6030 elevel[3], elevel[4], elevel[5],
6031 elevel[6], elevel[7]);
6032
6033 splits[1] = splits[0];
6034 splits[2] = splits[0];
6035 }
6036 else if (el.Geom() == Geometry::TETRAHEDRON)
6037 {
6038 splits[0] = max(flevel[0][0], flevel[1][0], flevel[2][0], flevel[3][0],
6039 elevel[0], elevel[1], elevel[2], elevel[3], elevel[4], elevel[5]);
6040
6041 splits[1] = splits[0];
6042 splits[2] = splits[0];
6043 }
6044 else if (el.Geom() == Geometry::SQUARE)
6045 {
6046 splits[0] = max(elevel[0], elevel[2]);
6047 splits[1] = max(elevel[1], elevel[3]);
6048 }
6049 else if (el.Geom() == Geometry::TRIANGLE)
6050 {
6051 splits[0] = max(elevel[0], elevel[1], elevel[2]);
6052 splits[1] = splits[0];
6053 }
6054 else
6055 {
6056 MFEM_ABORT("Unsupported element geometry.");
6057 }
6058}
6059
6060void NCMesh::GetLimitRefinements(Array<Refinement> &refinements, int max_level)
6061{
6062 for (int i = 0; i < leaf_elements.Size(); i++)
6063 {
6064 if (IsGhost(elements[leaf_elements[i]])) { break; } // TODO: NElements
6065
6066 int splits[3];
6067 CountSplits(leaf_elements[i], splits);
6068
6069 char ref_type = 0;
6070 for (int k = 0; k < Dim; k++)
6071 {
6072 if (splits[k] > max_level)
6073 {
6074 ref_type |= (1 << k);
6075 }
6076 }
6077
6078 if (ref_type)
6079 {
6080 if (Iso)
6081 {
6082 // iso meshes should only be modified by iso refinements
6083 ref_type = 7;
6084 }
6085 refinements.Append(Refinement(i, ref_type));
6086 }
6087 }
6088}
6089
6090void NCMesh::LimitNCLevel(int max_nc_level)
6091{
6092 MFEM_VERIFY(max_nc_level >= 1, "'max_nc_level' must be 1 or greater.");
6093
6094 while (1)
6095 {
6096 Array<Refinement> refinements;
6097 GetLimitRefinements(refinements, max_nc_level);
6098 if (!refinements.Size()) { break; }
6099 Refine(refinements);
6100 }
6101}
6102
6103
6104//// I/O ///////////////////////////////////////////////////////////////////////
6105
6106int NCMesh::PrintVertexParents(std::ostream *os) const
6107{
6108 if (!os)
6109 {
6110 // count vertex nodes with parents
6111 int nv = 0;
6112 for (auto node = nodes.cbegin(); node != nodes.cend(); ++node)
6113 {
6114 if (node->HasVertex() && node->p1 != node->p2) { nv++; }
6115 }
6116 return nv;
6117 }
6118 else
6119 {
6120 // print the relations
6121 bool uniform_scaling = true; // Check whether all nodes have scale 0.5
6122 for (auto node = nodes.cbegin(); node != nodes.cend(); ++node)
6123 {
6124 if (node->HasVertex() && node->p1 != node->p2)
6125 {
6126 MFEM_ASSERT(nodes[node->p1].HasVertex(), "");
6127 MFEM_ASSERT(nodes[node->p2].HasVertex(), "");
6128
6129 (*os) << node.index() << " " << node->p1 << " " << node->p2;
6130 if (node->GetScale() != 0.5) { uniform_scaling = false; }
6131 if (using_scaling) { (*os) << " " << node->GetScale(); }
6132 (*os) << "\n";
6133 }
6134 }
6135 MFEM_VERIFY(using_scaling || uniform_scaling, "NCMesh has nonuniform "
6136 "scaling. Call Mesh::SetScaledNCMesh first.");
6137 return 0;
6138 }
6139}
6140
6141void NCMesh::LoadVertexParents(std::istream &input)
6142{
6143 int nv;
6144 input >> nv;
6145 while (nv--)
6146 {
6147 int id, p1, p2;
6148 real_t s{0.5};
6149 input >> id >> p1 >> p2;
6150 if (using_scaling) { input >> s; }
6151 MFEM_VERIFY(input, "problem reading vertex parents.");
6152
6153 MFEM_VERIFY(nodes.IdExists(id), "vertex " << id << " not found.");
6154 MFEM_VERIFY(nodes.IdExists(p1), "parent " << p1 << " not found.");
6155 MFEM_VERIFY(nodes.IdExists(p2), "parent " << p2 << " not found.");
6156
6157 int check = nodes.FindId(p1, p2);
6158 MFEM_VERIFY(check < 0, "parents (" << p1 << ", " << p2 << ") already "
6159 "assigned to node " << check);
6160
6161 // assign new parents for the node
6162 nodes.Reparent(id, p1, p2);
6163
6164 nodes[id].SetScale(s);
6165 }
6166}
6167
6168void NCMesh::LoadVertexToKnotSpan(std::istream &input)
6169{
6170 if (Dim == 2) { LoadVertexToKnotSpan2D(input); }
6171 else { LoadVertexToKnotSpan3D(input); }
6172}
6173
6174void NCMesh::LoadVertexToKnotSpan2D(std::istream &input)
6175{
6176 int nv;
6177 input >> nv;
6178 MFEM_VERIFY(0 <= nv, "Invalid vertex-to-knot data");
6180 for (int i=0; i<nv; ++i)
6181 {
6182 int id, ks;
6183 std::array<int, 2> pv;
6184 input >> id >> ks >> pv[0] >> pv[1];
6185
6186 const bool idsExist = nodes.IdExists(id) && nodes.IdExists(pv[0])
6187 && nodes.IdExists(pv[1]);
6188
6189 MFEM_VERIFY(idsExist && 0 < ks, "Invalid index");
6190 vertex_to_knotspan.SetVertex2D(i, id, ks, pv);
6191 }
6192}
6193
6194void NCMesh::LoadVertexToKnotSpan3D(std::istream &input)
6195{
6196 int nv;
6197 input >> nv;
6198 MFEM_VERIFY(0 <= nv, "Invalid vertex-to-knot data");
6200 for (int i=0; i<nv; ++i)
6201 {
6202 int id;
6203 std::array<int, 2> ks;
6204 std::array<int, 4> pv; // Parent vertex indices
6205 input >> id >> ks[0] >> ks[1] >> pv[0] >> pv[1] >> pv[2] >> pv[3];
6206
6207#ifdef MFEM_DEBUG
6208 bool idsExist = nodes.IdExists(id);
6209 for (int j=0; j<4; ++j)
6210 {
6211 idsExist = idsExist && nodes.IdExists(pv[j]);
6212 }
6213
6214 const bool validKnotIds = (0 <= ks[0] || 0 <= ks[1]) &&
6215 (0 < ks[0] || 0 < ks[1]);
6216 MFEM_ASSERT(idsExist && validKnotIds, "Invalid index");
6217#endif
6218 vertex_to_knotspan.SetVertex3D(i, id, ks, pv);
6219 }
6220}
6221
6222int NCMesh::PrintBoundary(std::ostream *os) const
6223{
6224 static const int nfv2geom[5] =
6225 {
6228 };
6229 int deg = (Dim == 2) ? 2 : 1; // for degenerate faces in 2D
6230
6231 int count = 0;
6232 for (int i = 0; i < elements.Size(); i++)
6233 {
6234 const Element &el = elements[i];
6235 if (!el.IsLeaf()) { continue; }
6236
6237 GeomInfo& gi = GI[el.Geom()];
6238 for (int k = 0; k < gi.nf; k++)
6239 {
6240 const int* fv = gi.faces[k];
6241 const int nfv = gi.nfv[k];
6242 const Face* face = faces.Find(el.node[fv[0]], el.node[fv[1]],
6243 el.node[fv[2]], el.node[fv[3]]);
6244 MFEM_ASSERT(face != NULL, "face not found");
6245 if (face->Boundary())
6246 {
6247 if (!os) { count++; continue; }
6248
6249 (*os) << face->attribute << " " << nfv2geom[nfv];
6250 for (int j = 0; j < nfv; j++)
6251 {
6252 (*os) << " " << el.node[fv[j*deg]];
6253 }
6254 (*os) << "\n";
6255 }
6256 }
6257 }
6258 return count;
6259}
6260
6261void NCMesh::LoadBoundary(std::istream &input)
6262{
6263 int nb, attr, geom;
6264 input >> nb;
6265 for (int i = 0; i < nb; i++)
6266 {
6267 input >> attr >> geom;
6268
6269 int v1, v2, v3, v4;
6270 if (geom == Geometry::SQUARE)
6271 {
6272 input >> v1 >> v2 >> v3 >> v4;
6273 Face* face = faces.Get(v1, v2, v3, v4);
6274 face->attribute = attr;
6275 }
6276 else if (geom == Geometry::TRIANGLE)
6277 {
6278 input >> v1 >> v2 >> v3;
6279 Face* face = faces.Get(v1, v2, v3);
6280 face->attribute = attr;
6281 }
6282 else if (geom == Geometry::SEGMENT)
6283 {
6284 input >> v1 >> v2;
6285 Face* face = faces.Get(v1, v1, v2, v2);
6286 face->attribute = attr;
6287 }
6288 else if (geom == Geometry::POINT)
6289 {
6290 input >> v1;
6291 Face* face = faces.Get(v1, v1, v1, v1);
6292 face->attribute = attr;
6293 }
6294 else
6295 {
6296 MFEM_ABORT("unsupported boundary element geometry: " << geom);
6297 }
6298 }
6299}
6300
6301void NCMesh::PrintCoordinates(std::ostream &os) const
6302{
6303 int nv = coordinates.Size()/3;
6304 os << nv << "\n";
6305 if (!nv) { return; }
6306
6307 os << spaceDim << "\n";
6308 for (int i = 0; i < nv; i++)
6309 {
6310 os << coordinates[3*i];
6311 for (int j = 1; j < spaceDim; j++)
6312 {
6313 os << " " << coordinates[3*i + j];
6314 }
6315 os << "\n";
6316 }
6317}
6318
6319void NCMesh::LoadCoordinates(std::istream &input)
6320{
6321 int nv;
6322 input >> nv;
6323 if (!nv) { return; }
6324
6325 input >> spaceDim;
6326
6327 coordinates.SetSize(nv * 3);
6328 coordinates = 0.0;
6329
6330 for (int i = 0; i < nv; i++)
6331 {
6332 for (int j = 0; j < spaceDim; j++)
6333 {
6334 input >> coordinates[3*i + j];
6335 MFEM_VERIFY(input.good(), "unexpected EOF");
6336 }
6337 }
6338}
6339
6341{
6342 for (int i = 0; i < root_state.Size(); i++)
6343 {
6344 if (root_state[i]) { return false; }
6345 }
6346 return true;
6347}
6348
6349void NCMesh::Print(std::ostream &os, const std::string &comments,
6350 bool nurbs) const
6351{
6352 if (nurbs)
6353 {
6354 os << "MFEM NURBS NC-patch mesh v1.0\n\n";
6355 }
6356 else if (using_scaling)
6357 {
6358 os << "MFEM NC mesh v1.1\n\n";
6359 }
6360 else
6361 {
6362 os << "MFEM NC mesh v1.0\n\n";
6363 }
6364
6365 if (!comments.empty()) { os << comments << "\n\n"; }
6366
6367 os <<
6368 "# NCMesh supported geometry types:\n"
6369 "# SEGMENT = 1\n"
6370 "# TRIANGLE = 2\n"
6371 "# SQUARE = 3\n"
6372 "# TETRAHEDRON = 4\n"
6373 "# CUBE = 5\n"
6374 "# PRISM = 6\n"
6375 "# PYRAMID = 7\n";
6376
6377 os << "\ndimension\n" << Dim << "\n";
6378
6379#ifndef MFEM_USE_MPI
6380 if (MyRank != 0) // don't print this section in serial: default rank is 0
6381#endif
6382 {
6383 os << "\nrank\n" << MyRank << "\n";
6384 }
6385
6386 os << "\n# rank attr geom ref_type nodes/children";
6387 os << "\nelements\n" << elements.Size() << "\n";
6388
6389 for (int i = 0; i < elements.Size(); i++)
6390 {
6391 const Element &el = elements[i];
6392 os << el.rank << " " << el.attribute << " ";
6393 if (el.parent == -2) { os << "-1\n"; continue; } // unused element
6394
6395 os << int(el.geom) << " " << int(el.ref_type);
6396 for (int j = 0; j < MaxElemNodes && el.node[j] >= 0; j++)
6397 {
6398 os << " " << el.node[j];
6399 }
6400 os << "\n";
6401 }
6402
6403 int nb = PrintBoundary(NULL);
6404 if (nb)
6405 {
6406 os << "\n# attr geom nodes";
6407 os << "\nboundary\n" << nb << "\n";
6408
6409 PrintBoundary(&os);
6410 }
6411
6412 int nvp = PrintVertexParents(NULL);
6413 if (nvp)
6414 {
6415 os << "\n# vert_id p1 p2";
6416 os << "\nvertex_parents\n" << nvp << "\n";
6417
6418 PrintVertexParents(&os);
6419 }
6420
6421 if (!ZeroRootStates()) // root_state section is optional
6422 {
6423 os << "\n# root element orientation";
6424 os << "\nroot_state\n" << root_state.Size() << "\n";
6425
6426 for (int i = 0; i < root_state.Size(); i++)
6427 {
6428 os << root_state[i] << "\n";
6429 }
6430 }
6431
6432 if (nurbs && vertex_to_knotspan.Size() > 0)
6433 {
6434 os << "\nvertex_to_knotspan\n";
6436 }
6437
6438 if (coordinates.Size())
6439 {
6440 os << "\n# top-level node coordinates";
6441 os << "\ncoordinates\n";
6442
6443 PrintCoordinates(os);
6444 }
6445 else
6446 {
6447 // 'nodes' will be printed one level up by Mesh::Printer()
6448 }
6449}
6450
6452{
6453 // initialize Element::parent
6454 for (int i = 0; i < elements.Size(); i++)
6455 {
6456 Element &el = elements[i];
6457 if (el.ref_type)
6458 {
6459 for (int j = 0; j < MaxElemChildren && el.child[j] >= 0; j++)
6460 {
6461 int child = el.child[j];
6462 MFEM_VERIFY(child < elements.Size(), "invalid mesh file: "
6463 "element " << i << " references invalid child " << child);
6464 elements[child].parent = i;
6465 }
6466 }
6467 }
6468
6469 // count the root elements
6470 int nroots = 0;
6471 for (const auto &e : elements)
6472 if (e.parent == -1)
6473 {
6474 ++nroots;
6475 }
6476 MFEM_VERIFY(nroots > 0 ||
6477 elements.Size() == 0,
6478 "invalid mesh file: no root elements in non-empty mesh found.");
6479
6480
6481 // check that only the first 'nroot' elements are roots (have no parent)
6482 for (int i = nroots; i < elements.Size(); i++)
6483 {
6484 MFEM_VERIFY(elements[i].parent != -1,
6485 "invalid mesh file: only the first M elements can be roots. "
6486 "Found element " << i << " with no parent.");
6487 }
6488
6489 // default root state is zero
6490 root_state.SetSize(nroots);
6491 root_state = 0;
6492}
6493
6495{
6496 int ntop = 0;
6497 for (auto node = nodes.cbegin(); node != nodes.cend(); ++node)
6498 {
6499 if (node->p1 == node->p2) { ntop = node.index() + 1; }
6500 }
6501 return ntop;
6502}
6503
6504NCMesh::NCMesh(std::istream &input, int version, int &curved, int &is_nc)
6505 : spaceDim(0), MyRank(0), Iso(true), Legacy(false),
6506 using_scaling(version == 11)
6507{
6508 is_nc = 1;
6509 if (version == 1) // old MFEM mesh v1.1 format
6510 {
6511 LoadLegacyFormat(input, curved, is_nc);
6512 Legacy = true;
6513 return;
6514 }
6515
6516 MFEM_ASSERT(version == 10 || version == 11, "");
6517 std::string ident;
6518 int count;
6519
6520 // Skip the version string
6521 skip_comment_lines(input, 'M');
6522
6523 // load dimension
6524 skip_comment_lines(input, '#');
6525 input >> ident;
6526 MFEM_VERIFY(ident == "dimension", "Invalid mesh file: " << ident);
6527 input >> Dim;
6528
6529 // load rank, if present
6530 skip_comment_lines(input, '#');
6531 input >> ident;
6532 if (ident == "rank")
6533 {
6534 input >> MyRank;
6535 MFEM_VERIFY(MyRank >= 0, "Invalid rank");
6536
6537 skip_comment_lines(input, '#');
6538 input >> ident;
6539 }
6540
6541 // load file SFC version, if present (for future changes to SFC ordering)
6542 if (ident == "sfc_version")
6543 {
6544 int sfc_version; // TODO future: store as class member
6545 input >> sfc_version;
6546 MFEM_VERIFY(sfc_version == 0,
6547 "Unsupported mesh file SFC version (" << sfc_version << "). "
6548 "Please update MFEM.");
6549
6550 skip_comment_lines(input, '#');
6551 input >> ident;
6552 }
6553
6554 // load elements
6555 MFEM_VERIFY(ident == "elements", "Invalid mesh file: " << ident);
6556 input >> count;
6557 for (int i = 0; i < count; i++)
6558 {
6559 int rank, attr, geom, ref_type;
6560 input >> rank >> attr >> geom;
6561
6562 Geometry::Type type = Geometry::Type(geom);
6563 elements.Append(Element(type, attr));
6564
6565 MFEM_ASSERT(elements.Size() == i+1, "");
6566 Element &el = elements[i];
6567 el.rank = rank;
6568
6569 if (geom >= 0)
6570 {
6571 CheckSupportedGeom(type);
6572 GI[geom].InitGeom(type);
6573
6574 input >> ref_type;
6575 MFEM_VERIFY(ref_type >= 0 && ref_type < 8, "");
6576 el.ref_type = ref_type;
6577
6578 if (ref_type) // refined element
6579 {
6580 for (int j = 0; j < ref_type_num_children[ref_type]; j++)
6581 {
6582 input >> el.child[j];
6583 }
6584 if (Dim == 3 && ref_type != 7) { Iso = false; }
6585 }
6586 else // leaf element
6587 {
6588 for (int j = 0; j < GI[geom].nv; j++)
6589 {
6590 int id;
6591 input >> id;
6592 el.node[j] = id;
6593 nodes.Alloc(id, id, id);
6594 // NOTE: nodes that won't get parents assigned will stay hashed
6595 // with p1 == p2 == id (top-level nodes)
6596 }
6597 }
6598 }
6599 else
6600 {
6601 el.parent = -2; // mark as unused
6603 }
6604 }
6605
6607 InitGeomFlags();
6608
6609 skip_comment_lines(input, '#');
6610 input >> ident;
6611
6612 // load boundary
6613 if (ident == "boundary")
6614 {
6615 LoadBoundary(input);
6616
6617 skip_comment_lines(input, '#');
6618 input >> ident;
6619 }
6620
6621 // load vertex hierarchy
6622 if (ident == "vertex_parents")
6623 {
6624 LoadVertexParents(input);
6625
6626 skip_comment_lines(input, '#');
6627 input >> ident;
6628 }
6629
6630 // load map from hanging patch vertices to patch edge knots
6631 if (ident == "vertex_to_knotspan")
6632 {
6633 LoadVertexToKnotSpan(input);
6634
6635 skip_comment_lines(input, '#');
6636 input >> ident;
6637 }
6638
6639 // load root states
6640 if (ident == "root_state")
6641 {
6642 input >> count;
6643 MFEM_VERIFY(count <= root_state.Size(), "Too many root states");
6644 for (int i = 0; i < count; i++)
6645 {
6646 input >> root_state[i];
6647 }
6648
6649 skip_comment_lines(input, '#');
6650 input >> ident;
6651 }
6652
6653 // load coordinates or nodes
6654 if (ident == "coordinates")
6655 {
6656 LoadCoordinates(input);
6657
6658 MFEM_VERIFY(coordinates.Size() >= 3*CountTopLevelNodes(),
6659 "Invalid mesh file: not all top-level nodes are covered by "
6660 "the 'coordinates' section of the mesh file: " << coordinates.Size() << ' ' <<
6661 3*CountTopLevelNodes());
6662 curved = 0;
6663 }
6664 else if (ident == "nodes")
6665 {
6666 coordinates.SetSize(0); // this means the mesh is curved
6667
6668 // prepare to read the nodes
6669 input >> std::ws;
6670 curved = 1;
6671 }
6672 else
6673 {
6674 MFEM_ABORT("Invalid mesh file: either 'coordinates' or "
6675 "'nodes' must be present");
6676 }
6677
6678 // create edge nodes and faces
6679 nodes.UpdateUnused();
6680 for (int i = 0; i < elements.Size(); i++)
6681 {
6682 if (elements[i].IsLeaf())
6683 {
6685 RegisterFaces(i);
6686 }
6687 }
6688
6689 Update();
6690}
6691
6693 const BlockArray<Element> &tmp_elements)
6694{
6695 Element &el = elements[elem];
6696 if (el.ref_type)
6697 {
6698 for (int i = 0; i < MaxElemChildren && el.child[i] >= 0; i++)
6699 {
6700 int old_id = el.child[i];
6701 // here we know 'free_element_ids' is empty
6702 int new_id = elements.Append(tmp_elements[old_id]);
6703 el.child[i] = new_id;
6704 elements[new_id].parent = elem;
6705 CopyElements(new_id, tmp_elements);
6706 }
6707 }
6708}
6709
6710void NCMesh::LoadCoarseElements(std::istream &input)
6711{
6712 int ne;
6713 input >> ne;
6714
6715 bool iso = true;
6716
6717 // load the coarse elements
6718 while (ne--)
6719 {
6720 int ref_type;
6721 input >> ref_type;
6722
6723 int elem = AddElement(Geometry::INVALID, 0);
6724 Element &el = elements[elem];
6725 el.ref_type = ref_type;
6726
6727 if (Dim == 3 && ref_type != 7) { iso = false; }
6728
6729 // load child IDs and make parent-child links
6730 int nch = ref_type_num_children[ref_type];
6731 for (int i = 0, id; i < nch; i++)
6732 {
6733 input >> id;
6734 MFEM_VERIFY(id >= 0, "");
6735 MFEM_VERIFY(id < elements.Size(),
6736 "coarse element cannot be referenced before it is "
6737 "defined (id=" << id << ").");
6738
6739 Element &child = elements[id];
6740 MFEM_VERIFY(child.parent == -1,
6741 "element " << id << " cannot have two parents.");
6742
6743 el.child[i] = id;
6744 child.parent = elem;
6745
6746 if (!i) // copy geom and attribute from first child
6747 {
6748 el.geom = child.geom;
6749 el.attribute = child.attribute;
6750 }
6751 }
6752 }
6753
6754 // prepare for reordering the elements
6755 BlockArray<Element> tmp_elements;
6756 elements.Swap(tmp_elements);
6757
6758 // copy roots, they need to be at the beginning of 'elements'
6759 int root_count = 0;
6760 for (auto el = tmp_elements.begin(); el != tmp_elements.end(); ++el)
6761 {
6762 if (el->parent == -1)
6763 {
6764 elements.Append(*el); // same as AddElement()
6765 root_count++;
6766 }
6767 }
6768
6769 // copy the rest of the hierarchy
6770 for (int i = 0; i < root_count; i++)
6771 {
6772 CopyElements(i, tmp_elements);
6773 }
6774
6775 // set the Iso flag (must be false if there are 3D aniso refinements)
6776 Iso = iso;
6777
6778 InitRootState(root_count);
6779}
6780
6781void NCMesh::LoadLegacyFormat(std::istream &input, int &curved, int &is_nc)
6782{
6783 MFEM_ASSERT(elements.Size() == 0, "");
6784 MFEM_ASSERT(nodes.Size() == 0, "");
6785 MFEM_ASSERT(free_element_ids.Size() == 0, "");
6786
6787 std::string ident;
6788 int count, attr, geom;
6789
6790 // load dimension
6791 skip_comment_lines(input, '#');
6792 input >> ident;
6793 MFEM_VERIFY(ident == "dimension", "invalid mesh file");
6794 input >> Dim;
6795
6796 // load elements
6797 skip_comment_lines(input, '#');
6798 input >> ident;
6799 MFEM_VERIFY(ident == "elements", "invalid mesh file");
6800
6801 input >> count;
6802 for (int i = 0; i < count; i++)
6803 {
6804 input >> attr >> geom;
6805
6806 Geometry::Type type = Geometry::Type(geom);
6807 CheckSupportedGeom(type);
6808 GI[geom].InitGeom(type);
6809
6810 int eid = AddElement(type, attr);
6811 MFEM_ASSERT(eid == i, "");
6812
6813 Element &el = elements[eid];
6814 for (int j = 0; j < GI[geom].nv; j++)
6815 {
6816 int id;
6817 input >> id;
6818 el.node[j] = id;
6819 nodes.Alloc(id, id, id); // see comment in NCMesh::NCMesh
6820 }
6821 el.index = i; // needed for file leaf order below
6822 }
6823
6824 // load boundary
6825 skip_comment_lines(input, '#');
6826 input >> ident;
6827 MFEM_VERIFY(ident == "boundary", "invalid mesh file");
6828
6829 LoadBoundary(input);
6830
6831 // load vertex hierarchy
6832 skip_comment_lines(input, '#');
6833 input >> ident;
6834 if (ident == "vertex_parents")
6835 {
6836 LoadVertexParents(input);
6837 is_nc = 1;
6838
6839 skip_comment_lines(input, '#');
6840 input >> ident;
6841 }
6842 else
6843 {
6844 // no "vertex_parents" section: this file needs to be treated as a
6845 // conforming mesh for complete backward compatibility with MFEM 4.2
6846 is_nc = 0;
6847 }
6848
6849 // load element hierarchy
6850 if (ident == "coarse_elements")
6851 {
6852 LoadCoarseElements(input);
6853
6854 skip_comment_lines(input, '#');
6855 input >> ident;
6856 }
6857 else
6858 {
6859 // no element hierarchy -> all elements are roots
6860 InitRootState(elements.Size());
6861 }
6862 InitGeomFlags();
6863
6864 // load vertices
6865 MFEM_VERIFY(ident == "vertices", "invalid mesh file");
6866 int nvert;
6867 input >> nvert;
6868 input >> std::ws >> ident;
6869 if (ident != "nodes")
6870 {
6871 spaceDim = atoi(ident.c_str());
6872
6873 coordinates.SetSize(3*nvert);
6874 coordinates = 0.0;
6875
6876 for (int i = 0; i < nvert; i++)
6877 {
6878 for (int j = 0; j < spaceDim; j++)
6879 {
6880 input >> coordinates[3*i + j];
6881 MFEM_VERIFY(input.good(), "unexpected EOF");
6882 }
6883 }
6884
6885 // truncate extra coordinates (legacy vertices section is longer)
6886 int ntop = CountTopLevelNodes();
6887 if (3*ntop < coordinates.Size())
6888 {
6889 coordinates.SetSize(3*ntop);
6890 }
6891 }
6892 else
6893 {
6895
6896 // prepare to read the nodes
6897 input >> std::ws;
6898 curved = 1;
6899 }
6900
6901 // create edge nodes and faces
6902 nodes.UpdateUnused();
6903 int leaf_count = 0;
6904 for (int i = 0; i < elements.Size(); i++)
6905 {
6906 if (elements[i].IsLeaf())
6907 {
6909 RegisterFaces(i);
6910 leaf_count++;
6911 }
6912 }
6913
6914 // v1.1 honors file leaf order on load, prepare legacy 'leaf_elements'
6915 Array<int> file_leaf_elements(leaf_count);
6916 file_leaf_elements = -1;
6917 for (int i = 0; i < elements.Size(); i++)
6918 {
6919 if (elements[i].IsLeaf())
6920 {
6921 file_leaf_elements[elements[i].index] = i;
6922 }
6923 }
6924 MFEM_ASSERT(file_leaf_elements.Min() >= 0, "");
6925
6926 Update();
6927
6928 // force file leaf order
6929 Swap(leaf_elements, file_leaf_elements);
6930
6931 // make sure Mesh::NVertices is equal to "nvert" from the file (in case of
6932 // unused vertices), see also GetMeshComponents
6933 if (nvert > vertex_nodeId.Size())
6934 {
6935 vertex_nodeId.SetSize(nvert, -1);
6936 }
6937}
6938
6940{
6941 order.SetSize(NVertices);
6942 order = -1;
6943
6944 int count = 0;
6945 for (auto node = nodes.cbegin(); node != nodes.cend(); ++node)
6946 {
6947 if (node->HasVertex())
6948 {
6949 MFEM_ASSERT(node.index() >= 0, "");
6950 MFEM_ASSERT(node.index() < order.Size(), "");
6951 MFEM_ASSERT(order[node.index()] == -1, "");
6952
6953 order[node.index()] = node->vert_index;
6954 count++;
6955 }
6956 }
6957 MFEM_ASSERT(count == order.Size(), "");
6958 MFEM_CONTRACT_VAR(count);
6959}
6960
6961
6962////////////////////////////////////////////////////////////////////////////////
6963
6965{
6967 face_list.Clear();
6968 edge_list.Clear();
6969
6972
6974
6975 // TODO future: consider trimming unused blocks at the end of 'elements' and
6976 // maybe also of 'nodes' and 'faces'.
6977}
6978
6980{
6981 int pm_size = 0;
6982 for (int i = 0; i < Geometry::NumGeom; i++)
6983 {
6984 for (int j = 0; j < point_matrices[i].Size(); i++)
6985 {
6986 pm_size += static_cast<int>(point_matrices[i][j]->MemoryUsage());
6987 }
6988 pm_size += static_cast<int>(point_matrices[i].MemoryUsage());
6989 }
6990
6991 return conforming.MemoryUsage() +
6992 masters.MemoryUsage() +
6993 slaves.MemoryUsage() +
6994 pm_size;
6995}
6996
6998{
6999 long mem = embeddings.MemoryUsage();
7000 for (int i = 0; i < Geometry::NumGeom; i++)
7001 {
7002 mem += point_matrices[i].MemoryUsage();
7003 }
7004 return mem;
7005}
7006
7008{
7009 return nodes.MemoryUsage() +
7010 faces.MemoryUsage() +
7011 elements.MemoryUsage() +
7023 ref_stack.MemoryUsage() +
7027 sizeof(*this);
7028}
7029
7031{
7032 nodes.PrintMemoryDetail(); mfem::out << " nodes\n";
7033 faces.PrintMemoryDetail(); mfem::out << " faces\n";
7034
7035 mfem::out << elements.MemoryUsage() << " elements\n"
7036 << free_element_ids.MemoryUsage() << " free_element_ids\n"
7037 << root_state.MemoryUsage() << " root_state\n"
7038 << coordinates.MemoryUsage() << " top_vertex_pos\n"
7039 << leaf_elements.MemoryUsage() << " leaf_elements\n"
7040 << leaf_sfc_index.MemoryUsage() << " leaf_sfc_index\n"
7041 << vertex_nodeId.MemoryUsage() << " vertex_nodeId\n"
7042 << face_list.MemoryUsage() << " face_list\n"
7043 << edge_list.MemoryUsage() << " edge_list\n"
7044 << vertex_list.MemoryUsage() << " vertex_list\n"
7045 << boundary_faces.MemoryUsage() << " boundary_faces\n"
7046 << element_vertex.MemoryUsage() << " element_vertex\n"
7047 << ref_stack.MemoryUsage() << " ref_stack\n"
7048 << derefinements.MemoryUsage() << " derefinements\n"
7049 << transforms.MemoryUsage() << " transforms\n"
7050 << coarse_elements.MemoryUsage() << " coarse_elements\n"
7051 << sizeof(*this) << " NCMesh"
7052 << std::endl;
7053
7054 return elements.Size() - free_element_ids.Size();
7055}
7056
7057#ifdef MFEM_DEBUG
7058void NCMesh::DebugLeafOrder(std::ostream &os) const
7059{
7060 tmp_vertex = new TmpVertex[nodes.NumIds()];
7061 for (int i = 0; i < leaf_elements.Size(); i++)
7062 {
7063 const Element* elem = &elements[leaf_elements[i]];
7064 for (int j = 0; j < Dim; j++)
7065 {
7066 real_t sum = 0.0;
7067 int count = 0;
7068 for (int k = 0; k < MaxElemNodes; k++)
7069 {
7070 if (elem->node[k] >= 0)
7071 {
7072 sum += CalcVertexPos(elem->node[k])[j];
7073 count++;
7074 }
7075 }
7076 os << sum / count << " ";
7077 }
7078 os << "\n";
7079 }
7080 delete [] tmp_vertex;
7081}
7082
7083void NCMesh::DebugDump(std::ostream &os) const
7084{
7085 // dump nodes
7086 tmp_vertex = new TmpVertex[nodes.NumIds()];
7087 os << nodes.Size() << "\n";
7088 for (auto node = nodes.cbegin(); node != nodes.cend(); ++node)
7089 {
7090 const real_t *pos = CalcVertexPos(node.index());
7091 os << node.index() << " "
7092 << pos[0] << " " << pos[1] << " " << pos[2] << " "
7093 << node->p1 << " " << node->p2 << " "
7094 << node->vert_index << " " << node->edge_index << " "
7095 << 0 << "\n";
7096 }
7097 delete [] tmp_vertex;
7098 os << "\n";
7099
7100 // dump elements
7101 int nleaves = 0;
7102 for (int i = 0; i < elements.Size(); i++)
7103 {
7104 if (elements[i].IsLeaf()) { nleaves++; }
7105 }
7106 os << nleaves << "\n";
7107 for (int i = 0; i < elements.Size(); i++)
7108 {
7109 const Element &el = elements[i];
7110 if (el.IsLeaf())
7111 {
7112 const GeomInfo& gi = GI[el.Geom()];
7113 os << gi.nv << " ";
7114 for (int j = 0; j < gi.nv; j++)
7115 {
7116 os << el.node[j] << " ";
7117 }
7118 os << el.attribute << " " << el.rank << " " << i << "\n";
7119 }
7120 }
7121 os << "\n";
7122
7123 // dump faces
7124 os << faces.Size() << "\n";
7125 for (const auto &face : faces)
7126 {
7127 int elem = face.elem[0];
7128 if (elem < 0) { elem = face.elem[1]; }
7129 MFEM_ASSERT(elem >= 0, "");
7130 const Element &el = elements[elem];
7131
7132 int lf = find_local_face(el.Geom(),
7133 find_node(el, face.p1),
7134 find_node(el, face.p2),
7135 find_node(el, face.p3));
7136
7137 const int* fv = GI[el.Geom()].faces[lf];
7138 const int nfv = GI[el.Geom()].nfv[lf];
7139
7140 os << nfv;
7141 for (int i = 0; i < nfv; i++)
7142 {
7143 os << " " << el.node[fv[i]];
7144 }
7145 //os << " # face " << face.index() << ", index " << face.index << "\n";
7146 os << "\n";
7147 }
7148}
7149#endif
7150
7151} // namespace mfem
void Sort()
Sorts the array in ascending order. This requires operator< to be defined for T.
Definition array.hpp:312
void Reserve(int capacity)
Ensures that the allocated size is at least the given size.
Definition array.hpp:184
void SetSize(int nsize)
Change the logical size of the array, keep existing entries.
Definition array.hpp:840
T Min() const
Find the minimal element in the array, using the comparison operator < for class T.
Definition array.cpp:86
int Size() const
Return the logical size of the array.
Definition array.hpp:166
void DeleteAll()
Delete the whole array.
Definition array.hpp:1033
int Append(const T &el)
Append element 'el' to array, resize if necessary.
Definition array.hpp:912
T * GetData()
Returns the data.
Definition array.hpp:140
void Copy(Array &copy) const
Create a copy of the internal array to the provided copy.
Definition array.hpp:1042
void Unique()
Removes duplicities from a sorted array. This requires operator== to be defined for T.
Definition array.hpp:320
T * end()
STL-like end. Returns pointer after the last element of the array.
Definition array.hpp:369
T * begin()
STL-like begin. Returns pointer to the first element of the array.
Definition array.hpp:366
std::size_t MemoryUsage() const
Returns the number of bytes allocated for the array including any reserve.
Definition array.hpp:378
iterator begin()
Definition array.hpp:710
void Swap(BlockArray< T > &other)
Definition array.hpp:1257
iterator end()
Definition array.hpp:711
Data type dense matrix using column-major storage.
Definition densemat.hpp:24
void SetSize(int s)
Change the size of the DenseMatrix to s x s.
Definition densemat.hpp:116
void SetSize(int i, int j, int k, MemoryType mt_=MemoryType::PRESERVE)
Abstract data type element.
Definition element.hpp:29
Geometry::Type GetGeometryType() const
Definition element.hpp:55
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
virtual Type GetType() const =0
Returns element's type.
int GetAttribute() const
Return element's attribute.
Definition element.hpp:58
static const int NumGeom
Definition geom.hpp:46
Data type hexahedron element.
Class used by MFEM to store pointers to host and/or device memory.
Mesh data type.
Definition mesh.hpp:65
Array< Vertex > vertices
Definition mesh.hpp:107
Element * NewElement(int geom)
Definition mesh.cpp:4818
int GetNEdges() const
Return the number of edges.
Definition mesh.hpp:1383
int GetNumFaces() const
Return the number of faces (3D), edges (2D) or vertices (1D).
Definition mesh.cpp:6846
const Element * GetElement(int i) const
Return pointer to the i'th element object.
Definition mesh.hpp:1434
int GetNE() const
Returns number of elements.
Definition mesh.hpp:1377
const Element * GetFace(int i) const
Return pointer to the i'th face element object.
Definition mesh.hpp:1461
int Dimension() const
Dimension of the reference space used within the elements.
Definition mesh.hpp:1306
const Element * GetBdrElement(int i) const
Return pointer to the i'th boundary element object.
Definition mesh.hpp:1449
int SpaceDimension() const
Dimension of the physical space containing the mesh.
Definition mesh.hpp:1309
int GetNV() const
Returns number of vertices. Vertices are only at the corners of elements, where you would expect them...
Definition mesh.hpp:1374
GridFunction * Nodes
Definition mesh.hpp:267
Array< Triple< int, int, int > > tmp_vertex_parents
Definition mesh.hpp:281
int GetNBE() const
Returns number of boundary elements.
Definition mesh.hpp:1380
void FreeElement(Element *E)
Definition mesh.cpp:13775
Array< Element * > boundary
Definition mesh.hpp:108
Table * GetEdgeVertexTable() const
Definition mesh.cpp:7709
Array< Element * > elements
Definition mesh.hpp:102
const real_t * GetVertex(int i) const
Return pointer to vertex i's coordinates.
Definition mesh.hpp:1416
A class for non-conforming AMR. The class is not used directly by the user, rather it is an extension...
Definition ncmesh.hpp:190
int GetEdgeMaster(int v1, int v2) const
Definition ncmesh.cpp:5738
static constexpr int MaxElemEdges
Number of edges an element can have.
Definition ncmesh.hpp:554
static PointMatrix pm_tet_identity
Definition ncmesh.hpp:1243
void OnMeshUpdated(Mesh *mesh)
Definition ncmesh.cpp:2887
void GetElementFacesAttributes(int i, Array< int > &faces, Array< int > &fattr) const
Return the faces and face attributes of leaf element i.
Definition ncmesh.cpp:5773
static GeomInfo GI[Geometry::NumGeom]
Definition ncmesh.hpp:1410
virtual void Update()
Definition ncmesh.cpp:268
void FindNeighbors(int elem, Array< int > &neighbors, const Array< int > *search_set=NULL)
Definition ncmesh.cpp:4322
void FreeElement(int id)
Definition ncmesh.hpp:890
virtual void Trim()
Save memory by releasing all non-essential and cached data.
Definition ncmesh.cpp:6964
virtual void ElementSharesVertex(int elem, int local, int vnode)
Definition ncmesh.hpp:1078
HashTable< Node > shadow
temporary storage for reparented nodes
Definition ncmesh.hpp:856
void SetDerefMatrixCodes(int parent, Array< int > &fine_coarse)
Definition ncmesh.cpp:2364
void CopyElements(int elem, const BlockArray< Element > &tmp_elements)
Definition ncmesh.cpp:6692
Array< Refinement > ref_stack
stack of scheduled refinements (temporary)
Definition ncmesh.hpp:855
std::int64_t RefCoord
Definition ncmesh.hpp:550
const Face & GetFace(int i) const
Access a Face.
Definition ncmesh.hpp:716
mfem::Element * NewMeshElement(int geom) const
Definition ncmesh.cpp:2720
void SetNodeScale(int p0, int p1, real_t scale)
Helper function to set scale for a node with parents p0, p1.
Definition ncmesh.cpp:1117
int NewTriangle(int n0, int n1, int n2, int attr, int eattr0, int eattr1, int eattr2)
Definition ncmesh.cpp:758
void GetMeshComponents(Mesh &mesh) const
Fill Mesh::{vertices,elements,boundary} for the current finest level.
Definition ncmesh.cpp:2760
void LoadVertexToKnotSpan3D(std::istream &input)
Definition ncmesh.cpp:6194
NCMesh()=default
int NGhostElements
Definition ncmesh.hpp:785
void LoadCoarseElements(std::istream &input)
Load the element refinement hierarchy from a legacy mesh file.
Definition ncmesh.cpp:6710
void NeighborExpand(const Array< int > &elems, Array< int > &expanded, const Array< int > *search_set=NULL)
Definition ncmesh.cpp:4405
HashTable< Node > nodes
Definition ncmesh.hpp:683
int PrintVertexParents(std::ostream *out) const
Print the "vertex_parents" section of the mesh file.
Definition ncmesh.cpp:6106
static PointMatrix pm_prism_identity
Definition ncmesh.hpp:1244
int NewSegment(int n0, int n1, int attr, int vattr1, int vattr2)
Definition ncmesh.cpp:784
virtual bool IsParallel() const
Return true for ParNCMesh with more than one MPI process.
Definition ncmesh.hpp:548
static int find_node(const Element &el, int node)
Definition ncmesh.cpp:3278
void DeleteUnusedFaces(const Array< int > &elemFaces)
Definition ncmesh.cpp:455
const CoarseFineTransformations & GetRefinementTransforms() const
Definition ncmesh.cpp:5204
void BuildElementToVertexTable()
Definition ncmesh.cpp:4131
void TraverseEdge(int vn0, int vn1, real_t t0, real_t t1, int flags, int level, MatrixMap &matrix_map)
Definition ncmesh.cpp:3772
bool using_scaling
Definition ncmesh.hpp:686
static int find_element_edge(const Element &el, int vn0, int vn1, bool abort=true)
Definition ncmesh.cpp:3298
Array< int > free_element_ids
Definition ncmesh.hpp:689
void LoadBoundary(std::istream &input)
Load the "boundary" section of the mesh file.
Definition ncmesh.cpp:6261
virtual void ElementSharesFace(int elem, int local, int face)
Definition ncmesh.hpp:1076
int PrintMemoryDetail() const
Definition ncmesh.cpp:7030
Array< real_t > reparent_scale
scale associated with reparents (tmp)
Definition ncmesh.hpp:858
static constexpr int MaxElemNodes
Number of nodes an element can have.
Definition ncmesh.hpp:552
int NewHexahedron(int n0, int n1, int n2, int n3, int n4, int n5, int n6, int n7, int attr, int fattr0, int fattr1, int fattr2, int fattr3, int fattr4, int fattr5)
Definition ncmesh.cpp:615
HashTable< Face > faces
Definition ncmesh.hpp:684
bool HaveTets() const
Return true if the mesh contains tetrahedral elements.
Definition ncmesh.hpp:848
void GetEdgeVertices(const MeshId &edge_id, int vert_index[2], bool oriented=true) const
Return Mesh vertex indices of an edge identified by 'edge_id'.
Definition ncmesh.cpp:5639
static PointMatrix pm_seg_identity
Definition ncmesh.hpp:1240
bool QuadFaceIsMaster(int n1, int n2, int n3, int n4) const
Determine if a Quad face is a master face.
Definition ncmesh.hpp:994
Array< int > boundary_faces
subset of all faces, set by BuildFaceList
Definition ncmesh.hpp:795
void Print(std::ostream &out, const std::string &comments="", bool nurbs=false) const
Definition ncmesh.cpp:6349
friend struct MatrixMap
Definition ncmesh.hpp:1422
std::array< int, 4 > FindFaceNodes(int face) const
Method for finding the nodes associated to a face.
Definition ncmesh.cpp:5799
static PointMatrix pm_quad_identity
Definition ncmesh.hpp:1242
void ReferenceElement(int elem)
Add references to all nodes, edges and faces of the element.
Definition ncmesh.cpp:367
void UpdateElementToVertexTable()
Definition ncmesh.hpp:1115
void LoadVertexToKnotSpan2D(std::istream &input)
Definition ncmesh.cpp:6174
BlockArray< Element > elements
Definition ncmesh.hpp:688
Array< int > coarse_elements
state of leaf_elements before Refine(), set by MarkCoarseLevel()
Definition ncmesh.hpp:1262
const CoarseFineTransformations & GetDerefinementTransforms() const
Definition ncmesh.cpp:5255
Table derefinements
possible derefinements, see GetDerefinementTable
Definition ncmesh.hpp:860
int FindNodeExt(const Element &el, int node, bool abort=true)
Extended version of find_node: works if 'el' is refined.
Definition ncmesh.cpp:3288
int ReorderFacePointMat(int v0, int v1, int v2, int v3, int elem, const PointMatrix &pm, PointMatrix &reordered) const
Definition ncmesh.cpp:3423
int FindMidEdgeNode(int node1, int node2) const
Definition ncmesh.cpp:336
friend struct PointMatrixHash
Definition ncmesh.hpp:1423
Array< char > face_geom
face geometry by face index, set by OnMeshUpdated
Definition ncmesh.hpp:796
int GetEdgeNCOrientation(const MeshId &edge_id) const
Definition ncmesh.cpp:5658
Table element_vertex
leaf-element to vertex table, see FindSetNeighbors
Definition ncmesh.hpp:798
Array< Triple< int, int, int > > reparents
scheduled node reparents (tmp)
Definition ncmesh.hpp:857
virtual void BuildFaceList()
Definition ncmesh.cpp:3677
void CheckAnisoPrism(int vn1, int vn2, int vn3, int vn4, const Refinement *refs, int nref)
Definition ncmesh.cpp:955
void LoadVertexParents(std::istream &input)
Load the vertex parent hierarchy from a mesh file.
Definition ncmesh.cpp:6141
TmpVertex * tmp_vertex
Definition ncmesh.hpp:1275
static void GridSfcOrdering3D(int width, int height, int depth, Array< int > &coords)
Definition ncmesh.cpp:5607
Array< int > leaf_elements
finest elements, in Mesh ordering (+ ghosts)
Definition ncmesh.hpp:787
const real_t * CalcVertexPos(int node) const
Definition ncmesh.cpp:2735
void CollectLeafElements(int elem, int state, Array< int > &ghosts, int &counter)
Definition ncmesh.cpp:2383
int CountTopLevelNodes() const
Return the index of the last top-level node plus one.
Definition ncmesh.cpp:6494
void QuadFaceSplitLevel(int vn1, int vn2, int vn3, int vn4, int &h_level, int &v_level) const
Computes the number of horizontal and vertical splits of this quad that have occurred in the NCMesh....
Definition ncmesh.cpp:5935
int Geoms
bit mask of element geometries present, see InitGeomFlags()
Definition ncmesh.hpp:591
void InitDerefTransforms()
Definition ncmesh.cpp:2345
int GetElementSizeReduction(int i) const
Definition ncmesh.cpp:5759
virtual void LimitNCLevel(int max_nc_level)
Definition ncmesh.cpp:6090
int NGhostVertices
Definition ncmesh.hpp:785
bool ZeroRootStates() const
Return true if all root_states are zero.
Definition ncmesh.cpp:6340
Array< int > vertex_nodeId
vertex-index to node-id map, see UpdateVertices
Definition ncmesh.hpp:789
int ParentFaceNodes(std::array< int, 4 > &nodes) const
Given a set of nodes defining a face, traverse the nodes structure to find the nodes that make up the...
Definition ncmesh.cpp:3108
int RetrieveNode(const Element &el, int index)
Return el.node[index] correctly, even if the element is refined.
Definition ncmesh.cpp:1996
void FindEdgeElements(int vn1, int vn2, int vn3, int vn4, Array< MeshId > &prisms) const
Definition ncmesh.cpp:907
virtual ~NCMesh()
Definition ncmesh.cpp:280
void CollectDerefinements(int elem, Array< Connection > &list)
Definition ncmesh.cpp:2232
virtual void ElementSharesEdge(int elem, int local, int enode)
Definition ncmesh.hpp:1077
VertexToKnotSpan vertex_to_knotspan
This is used for a NURBS mesh with this NCMesh as its patch topology.
Definition ncmesh.hpp:1413
int GetFaceVerticesEdges(const MeshId &face_id, int vert_index[4], int edge_index[4], int edge_orientation[4]) const
Definition ncmesh.cpp:5670
void RefineElement(int elem, char ref_type)
Refine the element elem with the refinement type ref_type.
Definition ncmesh.cpp:1123
void InitRootState(int root_count)
Definition ncmesh.cpp:2654
int NewQuadrilateral(int n0, int n1, int n2, int n3, int attr, int eattr0, int eattr1, int eattr2, int eattr3)
Definition ncmesh.cpp:732
Array< int > leaf_sfc_index
natural tree ordering of leaf elements
Definition ncmesh.hpp:788
void ForceRefinement(int vn1, int vn2, int vn3, int vn4)
Definition ncmesh.cpp:824
void CollectEdgeVertices(int v0, int v1, Array< int > &indices)
Definition ncmesh.cpp:4060
void RefineVertexToKnotSpan(const std::vector< Array< int > > &kvf, const Array< KnotVector * > &kvext, std::map< std::pair< int, int >, std::array< int, 2 > > &parentToKV)
Remap knot-span indices vertex_to_knotspan after refinement.
Definition ncmesh.cpp:5110
NCList edge_list
lazy-initialized list of edges, see GetEdgeList
Definition ncmesh.hpp:792
static PointMatrix pm_pyramid_identity
Definition ncmesh.hpp:1245
Array< int > root_state
Definition ncmesh.hpp:765
const NCList & GetFaceList()
Return the current list of conforming and nonconforming faces.
Definition ncmesh.hpp:369
real_t GetScale(real_t s, bool reverse) const
Return directed scale in (0,1).
Definition ncmesh.hpp:1285
void CollectTriFaceVertices(int v0, int v1, int v2, Array< int > &indices)
Definition ncmesh.cpp:4072
void InitRootElements()
Count root elements and initialize root_state.
Definition ncmesh.cpp:6451
int GetMidEdgeNode(int node1, int node2)
Definition ncmesh.cpp:352
virtual void BuildEdgeList()
Definition ncmesh.cpp:3802
void DebugDump(std::ostream &out) const
Definition ncmesh.cpp:7083
void DebugLeafOrder(std::ostream &out) const
Definition ncmesh.cpp:7058
std::map< std::string, int > RefPathMap
Definition ncmesh.hpp:1253
int GetElementDepth(int i) const
Return the distance of leaf i from the root.
Definition ncmesh.cpp:5747
void LoadVertexToKnotSpan(std::istream &input)
Definition ncmesh.cpp:6168
void TraverseRefinements(int elem, int coarse_index, std::string &ref_path, RefPathMap &map) const
Definition ncmesh.cpp:5170
void CheckIsoFace(int vn1, int vn2, int vn3, int vn4, int en1, int en2, int en3, int en4, int midf)
Definition ncmesh.cpp:1101
void GetLimitRefinements(Array< Refinement > &refinements, int max_level)
Definition ncmesh.cpp:6060
int NewPyramid(int n0, int n1, int n2, int n3, int n4, int attr, int fattr0, int fattr1, int fattr2, int fattr3, int fattr4)
Definition ncmesh.cpp:702
virtual void Derefine(const Array< int > &derefs)
Definition ncmesh.cpp:2309
bool Iso
true if the mesh only contains isotropic refinements
Definition ncmesh.hpp:590
virtual void GetBoundaryClosure(const Array< int > &bdr_attr_is_ess, Array< int > &bdr_vertices, Array< int > &bdr_edges, Array< int > &bdr_faces)
Get a list of vertices (2D/3D), edges (3D) and faces (3D) that coincide with boundary elements with t...
Definition ncmesh.cpp:5827
bool TriFaceSplit(int n1, int n2, int n3, int mid[3]=NULL) const
Given a tri face defined by three vertices, establish whether the edges that make up this face have b...
Definition ncmesh.cpp:3084
bool Legacy
true if the mesh was loaded from the legacy v1.1 format
Definition ncmesh.hpp:592
virtual void BuildVertexList()
Definition ncmesh.cpp:3914
static const PointMatrix & GetGeomIdentity(Geometry::Type geom)
Definition ncmesh.cpp:4608
static constexpr int MaxElemFaces
Number of faces an element can have.
Definition ncmesh.hpp:556
void CheckAnisoFace(int vn1, int vn2, int vn3, int vn4, int mid12, int mid34, int level=0)
Definition ncmesh.cpp:980
int NewWedge(int n0, int n1, int n2, int n3, int n4, int n5, int attr, int fattr0, int fattr1, int fattr2, int fattr3, int fattr4)
Definition ncmesh.cpp:645
int GetVertexRootCoord(int elem, RefCoord coord[3]) const
Definition ncmesh.cpp:4464
static void CheckSupportedGeom(Geometry::Type geom)
Definition ncmesh.hpp:1347
Array< real_t > coordinates
Definition ncmesh.hpp:769
void FindVertexCousins(int elem, int local, Array< int > &cousins) const
Definition ncmesh.cpp:4540
bool IsGhost(const Element &el) const
Return true if the Element el is a ghost element.
Definition ncmesh.hpp:851
static constexpr int MaxElemChildren
Number of children an element can have.
Definition ncmesh.hpp:558
const NCList & GetEdgeList()
Return the current list of conforming and nonconforming edges.
Definition ncmesh.hpp:376
bool TriFaceIsMaster(int n1, int n2, int n3) const
Determine if a Triangle face is a master face.
Definition ncmesh.hpp:974
int MyRank
used in parallel, or when loading a parallel file in serial
Definition ncmesh.hpp:589
int AddElement(const Element &el)
Add an Element el to the NCMesh, optimized to reuse freed elements.
Definition ncmesh.hpp:876
void CountSplits(int elem, int splits[3]) const
Definition ncmesh.cpp:5970
int spaceDim
dimensions of the elements and the vertex coordinates
Definition ncmesh.hpp:588
void UnreferenceElement(int elem, Array< int > &elemFaces)
Definition ncmesh.cpp:398
void LegacyToNewVertexOrdering(Array< int > &order) const
I/O: Return a map from old (v1.1) vertex indices to new vertex indices.
Definition ncmesh.cpp:6939
int NGhostEdges
Definition ncmesh.hpp:785
void CollectIncidentElements(int elem, const RefCoord coord[3], Array< int > &list) const
Definition ncmesh.cpp:4517
void TraverseTetEdge(int vn0, int vn1, const Point &p0, const Point &p1, MatrixMap &matrix_map)
Definition ncmesh.cpp:3574
int QuadFaceSplitType(int n1, int n2, int n3, int n4, real_t &s, int mid[5]=NULL) const
Given a quad face defined by four vertices, establish which edges of this face have been split,...
Definition ncmesh.cpp:3030
void RegisterFaces(int elem, int *fattr=NULL)
Definition ncmesh.cpp:441
int PrintBoundary(std::ostream *out) const
Definition ncmesh.cpp:6222
CoarseFineTransformations transforms
storage for data returned by Get[De]RefinementTransforms()
Definition ncmesh.hpp:1259
void MarkCoarseLevel()
Definition ncmesh.cpp:5156
int TriFaceSplitLevel(int vn1, int vn2, int vn3) const
Return the number of splits of this triangle that have occurred in the NCMesh. If zero,...
Definition ncmesh.cpp:5920
virtual void CheckDerefinementNCLevel(const Table &deref_table, Array< int > &level_ok, int max_nc_level)
Definition ncmesh.cpp:2280
void UpdateVertices()
This method assigns indices to vertices (Node::vert_index) that will be seen by the Mesh class and th...
Definition ncmesh.cpp:2478
NCList vertex_list
lazy-initialized list of vertices, see GetVertexList
Definition ncmesh.hpp:793
void InitGeomFlags()
Definition ncmesh.cpp:259
int NewTetrahedron(int n0, int n1, int n2, int n3, int attr, int fattr0, int fattr1, int fattr2, int fattr3)
Definition ncmesh.cpp:677
void LoadCoordinates(std::istream &input)
Load the "coordinates" section of the mesh file.
Definition ncmesh.cpp:6319
void GetPointMatrix(Geometry::Type geom, const char *ref_path, DenseMatrix &matrix) const
Definition ncmesh.cpp:4625
void TraverseQuadFace(int vn0, int vn1, int vn2, int vn3, const PointMatrix &pm, int level, Face *eface[4], MatrixMap &matrix_map)
Definition ncmesh.cpp:3454
const Table & GetDerefinementTable()
Definition ncmesh.cpp:2265
void ClearTransforms()
Free all internal data created by the above three functions.
Definition ncmesh.cpp:5335
void FindSetNeighbors(const Array< char > &elem_set, Array< int > *neighbors, Array< char > *neighbor_set=NULL)
Definition ncmesh.cpp:4211
void CollectQuadFaceVertices(int v0, int v1, int v2, int v3, Array< int > &indices)
Definition ncmesh.cpp:4096
void PrintCoordinates(std::ostream &out) const
Print the "coordinates" section of the mesh file.
Definition ncmesh.cpp:6301
TriFaceTraverseResults TraverseTriFace(int vn0, int vn1, int vn2, const PointMatrix &pm, int level, MatrixMap &matrix_map)
Definition ncmesh.cpp:3611
int GetMidFaceNode(int en1, int en2, int en3, int en4)
Definition ncmesh.cpp:359
bool HavePrisms() const
Return true if the mesh contains prism elements.
Definition ncmesh.hpp:842
long MemoryUsage() const
Return total number of bytes allocated.
Definition ncmesh.cpp:7007
void DerefineElement(int elem)
Derefine the element elem, does nothing on leaf elements.
Definition ncmesh.cpp:2038
NCList face_list
lazy-initialized list of faces, see GetFaceList
Definition ncmesh.hpp:791
virtual void Refine(const Array< Refinement > &refinements)
Definition ncmesh.cpp:1947
void ReparentNode(int node, int new_p1, int new_p2, real_t scale)
Definition ncmesh.cpp:319
static PointMatrix pm_tri_identity
Definition ncmesh.hpp:1241
int NGhostFaces
Definition ncmesh.hpp:785
static PointMatrix pm_hex_identity
Definition ncmesh.hpp:1246
void UpdateLeafElements()
Update the leaf elements indices in leaf_elements.
Definition ncmesh.cpp:2451
void LoadLegacyFormat(std::istream &input, int &curved, int &is_nc)
Load the deprecated MFEM mesh v1.1 format for backward compatibility.
Definition ncmesh.cpp:6781
int EdgeSplitLevel(int vn1, int vn2) const
Return the number of splits of this edge that have occurred in the NCMesh. If zero,...
Definition ncmesh.cpp:5913
static void GridSfcOrdering2D(int width, int height, Array< int > &coords)
Definition ncmesh.cpp:5592
static int find_local_face(int geom, int a, int b, int c)
Definition ncmesh.cpp:3316
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
A parallel extension of the NCMesh class.
Definition pncmesh.hpp:63
Data type Pyramid element.
Definition pyramid.hpp:23
Data type quadrilateral element.
Data type line segment element.
Definition segment.hpp:23
Table stores the connectivity of elements of TYPE I to elements of TYPE II. For example,...
Definition table.hpp:43
int RowSize(int i) const
Definition table.hpp:122
void Clear()
Definition table.cpp:420
void GetRow(int i, Array< int > &row) const
Return row i in array row (the Table must be finalized)
Definition table.cpp:233
void SetIJ(int *newI, int *newJ, int newsize=-1)
Replace the I and J arrays with the given newI and newJ arrays.
Definition table.cpp:253
int Size() const
Returns the number of TYPE I elements.
Definition table.hpp:103
std::size_t MemoryUsage() const
Definition table.cpp:437
void MakeFromList(int nrows, const Array< Connection > &list)
Create the table from a list of connections {(from, to)}, where 'from' is a TYPE I index and 'to' is ...
Definition table.cpp:322
Data type tetrahedron element.
Data type triangle element.
Definition triangle.hpp:24
A triple of objects.
void GetVertex3D(int index, int &v, std::array< int, 2 > &ks, std::array< int, 4 > &pv) const
Get the data for a vertex in 3D.
Definition ncnurbs.cpp:3293
void SetVertex2D(int index, int v, int ks, const std::array< int, 2 > &pv)
Set the data for a vertex in 2D.
Definition ncnurbs.cpp:3251
void SetVertex3D(int index, int v, const std::array< int, 2 > &ks, const std::array< int, 4 > &pv)
Set the data for a vertex in 3D.
Definition ncnurbs.cpp:3260
void SetSize(int dimension, int numVertices)
Set the spatial dimension and number of vertices.
Definition ncnurbs.cpp:3244
void GetVertex2D(int index, int &v, int &ks, std::array< int, 2 > &pv) const
Get the data for a vertex in 2D.
Definition ncnurbs.cpp:3284
void Print(std::ostream &os) const
Print all the data.
Definition ncnurbs.cpp:3305
void SetKnotSpans3D(int index, const std::array< int, 2 > &ks)
Set the knot-span indices for a vertex in 3D.
Definition ncnurbs.cpp:3278
int Size() const
Return the number of vertices.
Definition ncmesh.hpp:157
void SetKnotSpan2D(int index, int ks)
Set the knot-span index for a vertex in 2D.
Definition ncnurbs.cpp:3273
Data type Wedge element.
Definition wedge.hpp:23
int dim
Definition ex24.cpp:53
int index(int i, int j, int nx, int ny)
Definition life.cpp:236
real_t b
Definition lissajous.cpp:42
real_t a
Definition lissajous.cpp:41
mfem::real_t real_t
bool PrismFaceBottom(int node, int *n)
Definition ncmesh.cpp:817
bool PrismFaceTop(int node, int *n)
Definition ncmesh.cpp:820
OutStream out(std::cout)
Global stream used by the library for standard output. Initially it uses the same std::streambuf as s...
Definition globals.hpp:66
bool CubeFaceBottom(int node, int *n)
Definition ncmesh.cpp:811
bool contains_node(const std::array< int, 4 > &nodes, int n)
Definition ncmesh.cpp:3103
bool CubeFaceBack(int node, int *n)
Definition ncmesh.cpp:808
bool CubeFaceLeft(int node, int *n)
Definition ncmesh.cpp:799
void Swap(T &a, T &b)
Swap objects of type T. The operation is performed using the most specialized swap function from the ...
Definition array.hpp:738
bool CubeFaceRight(int node, int *n)
Definition ncmesh.cpp:802
std::pair< int, int > QuadrupleToPair(const std::array< int, 4 > &q)
Definition ncnurbs.cpp:3237
bool CubeFaceFront(int node, int *n)
Definition ncmesh.cpp:805
NCMesh::RefCoord RefCoord
float real_t
Definition config.hpp:46
void RemapKnotIndex(bool rev, const Array< int > &rf, int &k)
Definition ncnurbs.cpp:2302
std::function< real_t(const Vector &)> f(real_t mass_coeff)
Definition lor_mms.hpp:30
void skip_comment_lines(std::istream &is, const char comment_char)
Check if the stream starts with comment_char. If so skip it.
Definition text.hpp:31
bool CubeFaceTop(int node, int *n)
Definition ncmesh.cpp:814
Defines the coarse-fine transformations of all fine elements.
Definition ncmesh.hpp:90
Array< Embedding > embeddings
Fine element positions in their parents.
Definition ncmesh.hpp:92
void MakeCoarseToFineTable(Table &coarse_to_fine, bool want_ghosts=false) const
Definition ncmesh.cpp:5313
DenseTensor point_matrices[Geometry::NumGeom]
Definition ncmesh.hpp:96
Helper struct for defining a connectivity table, see Table::MakeFromList.
Definition table.hpp:28
Defines the position of a fine element within a coarse element.
Definition ncmesh.hpp:69
unsigned geom
Definition ncmesh.hpp:77
unsigned ghost
For internal use: 0 if regular fine element, 1 if parallel ghost element.
Definition ncmesh.hpp:81
int parent
Coarse Element index in the coarse mesh.
Definition ncmesh.hpp:71
unsigned matrix
Definition ncmesh.hpp:78
int rank
processor number (ParNCMesh), -1 if undefined/unknown
Definition ncmesh.hpp:666
bool IsLeaf() const
Definition ncmesh.hpp:677
int child[MaxElemChildren]
2-10 children (if ref_type != 0)
Definition ncmesh.hpp:671
char tet_type
tetrahedron split type, currently always 0
Definition ncmesh.hpp:663
Element(Geometry::Type geom, int attr)
Definition ncmesh.cpp:602
int node[MaxElemNodes]
element corners (if ref_type == 0)
Definition ncmesh.hpp:670
char ref_type
bit mask of X,Y,Z refinements (bits 0,1,2 respectively)
Definition ncmesh.hpp:662
char geom
Geometry::Type of the element (char for storage only)
Definition ncmesh.hpp:661
int index
element number in the Mesh, -1 if refined
Definition ncmesh.hpp:665
int parent
parent element, -1 if this is a root element, -2 if free'd
Definition ncmesh.hpp:673
Geometry::Type Geom() const
Definition ncmesh.hpp:676
void ForgetElement(int e)
Definition ncmesh.cpp:473
int elem[2]
up to 2 elements sharing the face
Definition ncmesh.hpp:640
void RegisterElement(int e)
Definition ncmesh.cpp:466
bool Boundary() const
Definition ncmesh.hpp:644
int index
face number in the Mesh
Definition ncmesh.hpp:639
int GetSingleElement() const
Return one of elem[0] or elem[1] and make sure the other is -1.
Definition ncmesh.cpp:488
int attribute
boundary element attribute, -1 if internal face
Definition ncmesh.hpp:638
This holds in one place the constants about the geometries we support.
Definition ncmesh.hpp:1398
int nfv[MaxElemFaces]
Definition ncmesh.hpp:1402
int faces[MaxElemFaces][4]
Definition ncmesh.hpp:1401
int edges[MaxElemEdges][2]
Definition ncmesh.hpp:1400
void InitGeom(Geometry::Type geom)
Definition ncmesh.cpp:57
Identifies a vertex/edge/face in both Mesh and NCMesh.
Definition ncmesh.hpp:260
int element
NCMesh::Element containing this vertex/edge/face.
Definition ncmesh.hpp:262
int index
Mesh number.
Definition ncmesh.hpp:261
Geometry::Type Geom() const
Definition ncmesh.hpp:266
signed char local
local number within 'element'
Definition ncmesh.hpp:263
MeshIdType GetMeshIdType(int index) const
Return a face type for a given nc index.
Definition ncmesh.cpp:4010
Array< MeshId > conforming
All MeshIds corresponding to conformal faces.
Definition ncmesh.hpp:302
long MemoryUsage() const
Definition ncmesh.cpp:6979
Array< Slave > slaves
All MeshIds corresponding to slave faces.
Definition ncmesh.hpp:304
bool CheckMeshIdType(int index, MeshIdType type) const
Given an index, check if this is a certain face type.
Definition ncmesh.cpp:4018
void Clear()
Erase the contents of the conforming, master and slave arrays.
Definition ncmesh.cpp:3971
void OrientedPointMatrix(const Slave &slave, DenseMatrix &oriented_matrix) const
Definition ncmesh.cpp:3949
Array< Master > masters
All MeshIds corresponding to master faces.
Definition ncmesh.hpp:303
Array< DenseMatrix * > point_matrices[Geometry::NumGeom]
List of unique point matrices for each slave geometry.
Definition ncmesh.hpp:307
MeshIdAndType GetMeshIdAndType(int index) const
Return a mesh id and type for a given nc index.
Definition ncmesh.cpp:3990
bool HasEdge() const
Definition ncmesh.hpp:613
real_t GetScale() const
Definition ncmesh.hpp:619
void SetScale(real_t s, bool overwrite=false)
Definition ncmesh.cpp:298
The PointMatrix stores the coordinates of the slave face using the master face coordinate as referenc...
Definition ncmesh.hpp:1194
void GetMatrix(DenseMatrix &point_matrix) const
Definition ncmesh.cpp:4571
Point points[MaxElemNodes]
Definition ncmesh.hpp:1196
bool operator==(const PointMatrix &pm) const
Definition ncmesh.cpp:4557
Nonconforming edge/face within a bigger edge/face.
Definition ncmesh.hpp:287
unsigned edge_flags
orientation flags, see OrientedPointMatrix
Definition ncmesh.hpp:290
unsigned matrix
index into NCList::point_matrices[geom]
Definition ncmesh.hpp:289
Refinement()=default
Refinement scale in each dimension.
real_t s[3]
Definition ncmesh.hpp:45
void SetScaleForType(const real_t *scale)
Set the scale in the directions for the currently set type.
Definition ncmesh.cpp:540
std::pair< char, real_t > ScaledType
Definition ncmesh.hpp:44
int index
Mesh element number.
Definition ncmesh.hpp:42
void Set(int element, char type, real_t scale=0.5)
Set the element, type, and scale.
Definition ncmesh.cpp:589
char GetType() const
Return the type as char.
Definition ncmesh.cpp:580
void SetType(char type, real_t scale=0.5)
Set the type and scale, assuming the element is already set.
Definition ncmesh.cpp:596
std::array< int, NCMesh::MaxFaceNodes > nodes