Files
mfem/mesh/ncmesh.cpp
T

7152 lines
216 KiB
C++

// Copyright (c) 2010-2025, Lawrence Livermore National Security, LLC. Produced
// at the Lawrence Livermore National Laboratory. All Rights reserved. See files
// LICENSE and NOTICE for details. LLNL-CODE-806117.
//
// This file is part of the MFEM library. For more information and source code
// availability visit https://mfem.org.
//
// MFEM is free software; you can redistribute it and/or modify it under the
// terms of the BSD-3 license. We welcome feedback and contributions, see file
// CONTRIBUTING.md for details.
#include "mesh_headers.hpp"
#include "../general/sort_pairs.hpp"
#include "../general/text.hpp"
#include <string>
#include <cmath>
#include <map>
#include "ncmesh_tables.hpp"
namespace
{
/**
* @brief Base case of convenience variadic max function.
*
* @tparam T Base type
* @param arg Recursion base value
* @return T value to max over
*/
template<typename T>
T max(T&& arg)
{
return arg;
}
/**
* @brief Convenience variadic max function.
*
* @tparam T Base Type
* @tparam Ts Parameter pack of other types
* @param arg Singular argument
* @param args Pack of arguments
* @return T maximum value
*/
template<typename T, typename... Ts>
T max(T arg, Ts... args)
{
return std::max(std::forward<T>(arg), max(args...));
}
} // namespace
namespace mfem
{
NCMesh::GeomInfo NCMesh::GI[Geometry::NumGeom];
void NCMesh::GeomInfo::InitGeom(Geometry::Type geom)
{
if (initialized) { return; }
auto elem = [&]()
{
switch (geom)
{
case Geometry::CUBE: return std::unique_ptr<mfem::Element>(new Hexahedron);
case Geometry::PRISM: return std::unique_ptr<mfem::Element>(new Wedge);
case Geometry::TETRAHEDRON: return std::unique_ptr<mfem::Element>
(new Tetrahedron);
case Geometry::PYRAMID: return std::unique_ptr<mfem::Element>(new Pyramid);
case Geometry::SQUARE: return std::unique_ptr<mfem::Element>(new Quadrilateral);
case Geometry::TRIANGLE: return std::unique_ptr<mfem::Element>(new Triangle);
case Geometry::SEGMENT: return std::unique_ptr<mfem::Element>(new Segment);
default: MFEM_ABORT("unsupported geometry " << geom);
}
}();
nv = elem->GetNVertices();
ne = elem->GetNEdges();
nf = elem->GetNFaces();
for (int i = 0; i < ne; i++)
{
for (int j = 0; j < 2; j++)
{
edges[i][j] = elem->GetEdgeVertices(i)[j];
}
}
for (int i = 0; i < nf; i++)
{
nfv[i] = elem->GetNFaceVertices(i);
faces[i][3] = 7; // invalid node index for 3-node faces
for (int j = 0; j < nfv[i]; j++)
{
faces[i][j] = elem->GetFaceVertices(i)[j];
}
}
// in 1D/2D we pretend to have faces too, so we can use NCMesh::Face::elem[2]
if (!nf)
{
if (ne)
{
for (int i = 0; i < ne; i++)
{
// make a degenerate face
faces[i][0] = faces[i][1] = edges[i][0];
faces[i][2] = faces[i][3] = edges[i][1];
nfv[i] = 2;
}
nf = ne;
}
else
{
for (int i = 0; i < nv; i++)
{
// 1D degenerate face
faces[i][0] = faces[i][1] = faces[i][2] = faces[i][3] = i;
nfv[i] = 1;
}
nf = nv;
}
}
initialized = true;
}
NCMesh::NCMesh(const Mesh *mesh)
: shadow(1024, 2048)
{
Dim = mesh->Dimension();
spaceDim = mesh->SpaceDimension();
MyRank = 0;
Iso = true;
Legacy = false;
// create the NCMesh::Element struct for each Mesh element
for (int i = 0; i < mesh->GetNE(); i++)
{
const mfem::Element *elem = mesh->GetElement(i);
Geometry::Type geom = elem->GetGeometryType();
CheckSupportedGeom(geom);
GI[geom].InitGeom(geom);
// if we have pyramids we will need tets after refinement
if (geom == Geometry::PYRAMID)
{
GI[Geometry::TETRAHEDRON].InitGeom(Geometry::TETRAHEDRON);
}
// create NCMesh::Element for this mfem::Element
int root_id = AddElement(geom, elem->GetAttribute());
MFEM_ASSERT(root_id == i, "");
Element &root_elem = elements[root_id];
const int *v = elem->GetVertices();
for (int j = 0; j < GI[geom].nv; j++)
{
int id = v[j];
root_elem.node[j] = id;
nodes.Alloc(id, id, id);
// NOTE: top-level nodes are special: id == p1 == p2 == orig. vertex id
}
}
// if the user initialized any hanging nodes with Mesh::AddVertexParents,
// copy the hierarchy now
if (mesh->tmp_vertex_parents.Size())
{
for (const auto &triple : mesh->tmp_vertex_parents)
{
nodes.Reparent(triple.one, triple.two, triple.three);
}
}
// create edge nodes and faces
nodes.UpdateUnused();
for (int i = 0; i < elements.Size(); i++)
{
// increase reference count of all nodes the element is using (NOTE: this
// will also create and reference all edge nodes and faces)
ReferenceElement(i);
// make links from faces back to the element
RegisterFaces(i);
}
// store boundary element attributes
for (int i = 0; i < mesh->GetNBE(); i++)
{
const mfem::Element *be = mesh->GetBdrElement(i);
const int *v = be->GetVertices();
Face* face = NULL;
switch (be->GetType())
{
case mfem::Element::QUADRILATERAL:
face = faces.Find(v[0], v[1], v[2], v[3]);
break;
case mfem::Element::TRIANGLE:
face = faces.Find(v[0], v[1], v[2]);
break;
case mfem::Element::SEGMENT:
face = faces.Find(v[0], v[0], v[1], v[1]);
break;
case mfem::Element::POINT:
face = faces.Find(v[0], v[0], v[0], v[0]);
break;
default:
MFEM_ABORT("Unsupported boundary element geometry.");
}
MFEM_VERIFY(face, "Boundary face not found.");
face->attribute = be->GetAttribute();
}
// copy top-level vertex coordinates (leave empty if the mesh is curved)
if (!mesh->Nodes)
{
coordinates.SetSize(3*mesh->GetNV());
for (int i = 0; i < mesh->GetNV(); i++)
{
std::memcpy(&coordinates[3*i], mesh->GetVertex(i), 3*sizeof(real_t));
}
}
InitRootState(mesh->GetNE());
InitGeomFlags();
Update();
}
NCMesh::NCMesh(const NCMesh &other)
: Dim(other.Dim)
, spaceDim(other.spaceDim)
, MyRank(other.MyRank)
, Iso(other.Iso)
, Geoms(other.Geoms)
, Legacy(other.Legacy)
, nodes(other.nodes)
, faces(other.faces)
, using_scaling(other.using_scaling)
, elements(other.elements)
, free_element_ids(other.free_element_ids)
, root_state(other.root_state)
, coordinates(other.coordinates)
, NEdges(other.NEdges)
, NFaces(other.NFaces)
, NGhostEdges(other.NGhostEdges)
, NGhostFaces(other.NGhostFaces)
, boundary_faces(other.boundary_faces)
, face_geom(other.face_geom)
, element_vertex(other.element_vertex)
, shadow(1024, 2048)
{
Update();
}
void NCMesh::InitGeomFlags()
{
Geoms = 0;
for (int i = 0; i < root_state.Size(); i++)
{
Geoms |= (1 << elements[i].Geom());
}
}
void NCMesh::Update()
{
UpdateLeafElements();
UpdateVertices();
vertex_list.Clear();
face_list.Clear();
edge_list.Clear();
element_vertex.Clear();
}
NCMesh::~NCMesh()
{
#ifdef MFEM_DEBUG
// sign off of all faces and nodes
Array<int> elemFaces;
for (int i = 0; i < elements.Size(); i++)
{
if (elements[i].IsLeaf())
{
elemFaces.SetSize(0);
UnreferenceElement(i, elemFaces);
DeleteUnusedFaces(elemFaces);
}
}
// NOTE: in release mode, we just throw away all faces and nodes at once
#endif
}
void NCMesh::Node::SetScale(real_t s, bool overwrite)
{
MFEM_ASSERT(0.0 < s && s < 1.0, "Invalid scale");
if (!overwrite && scaleSet && std::abs((s - scale) / scale) > scaleTol)
{
MFEM_ABORT("Node scale is already set (inconsistent refinement)");
}
else if (overwrite || !scaleSet)
{
scale = s;
scaleSet = true;
}
}
NCMesh::Node::~Node()
{
MFEM_ASSERT(!vert_refc && !edge_refc, "node was not unreferenced properly, "
"vert_refc: " << (int) vert_refc << ", edge_refc: "
<< (int) edge_refc);
}
void NCMesh::ReparentNode(int node, int new_p1, int new_p2, real_t scale)
{
Node &nd = nodes[node];
nd.SetScale(GetScale(scale, new_p1 > new_p2), true);
int old_p1 = nd.p1, old_p2 = nd.p2;
// assign new parents
nodes.Reparent(node, new_p1, new_p2);
MFEM_ASSERT(shadow.FindId(old_p1, old_p2) < 0,
"shadow node already exists");
// store old parent pair temporarily in 'shadow'
int sh = shadow.GetId(old_p1, old_p2);
shadow[sh].vert_index = node;
}
int NCMesh::FindMidEdgeNode(int node1, int node2) const
{
int mid = nodes.FindId(node1, node2);
if (mid < 0 && shadow.Size())
{
// if (anisotropic) refinement is underway, some nodes may temporarily be
// available under alternate parents (see ReparentNode)
mid = shadow.FindId(node1, node2);
if (mid >= 0)
{
mid = shadow[mid].vert_index; // index of the original node
}
}
return mid;
}
int NCMesh::GetMidEdgeNode(int node1, int node2)
{
int mid = FindMidEdgeNode(node1, node2);
if (mid < 0) { mid = nodes.GetId(node1, node2); } // create if not found
return mid;
}
int NCMesh::GetMidFaceNode(int en1, int en2, int en3, int en4)
{
// mid-face node can be created either from (en1, en3) or from (en2, en4)
int midf = FindMidEdgeNode(en1, en3);
if (midf >= 0) { return midf; }
return nodes.GetId(en2, en4);
}
void NCMesh::ReferenceElement(int elem)
{
const Element &el = elements[elem];
const int* node = el.node;
GeomInfo& gi = GI[el.Geom()];
// reference all vertices
for (int i = 0; i < gi.nv; i++)
{
nodes[node[i]].vert_refc++;
}
// reference all edges (possibly creating their nodes)
for (int i = 0; i < gi.ne; i++)
{
const int* ev = gi.edges[i];
nodes.Get(node[ev[0]], node[ev[1]])->edge_refc++;
}
// get all faces (possibly creating them)
for (int i = 0; i < gi.nf; i++)
{
const int* fv = gi.faces[i];
faces.GetId(node[fv[0]], node[fv[1]], node[fv[2]], node[fv[3]]);
// NOTE: face->RegisterElement called separately to avoid having to store
// 3 element indices temporarily in the face when refining. See also
// NCMesh::RegisterFaces.
}
}
void NCMesh::UnreferenceElement(int elem, Array<int> &elemFaces)
{
Element &el = elements[elem];
int* node = el.node;
GeomInfo& gi = GI[el.Geom()];
// unreference all faces
for (int i = 0; i < gi.nf; i++)
{
const int* fv = gi.faces[i];
int face = faces.FindId(node[fv[0]], node[fv[1]],
node[fv[2]], node[fv[3]]);
MFEM_ASSERT(face >= 0, "face not found.");
faces[face].ForgetElement(elem);
// NOTE: faces.Delete() called later to avoid destroying and recreating
// faces during refinement, see NCMesh::DeleteUnusedFaces.
elemFaces.Append(face);
}
// unreference all edges (possibly destroying them)
for (int i = 0; i < gi.ne; i++)
{
const int* ev = gi.edges[i];
int enode = FindMidEdgeNode(node[ev[0]], node[ev[1]]);
MFEM_ASSERT(enode >= 0, "edge not found.");
MFEM_ASSERT(nodes.IdExists(enode), "edge does not exist.");
if (!nodes[enode].UnrefEdge())
{
nodes.Delete(enode);
}
}
// unreference all vertices (possibly destroying them)
for (int i = 0; i < gi.nv; i++)
{
if (!nodes[node[i]].UnrefVertex())
{
nodes.Delete(node[i]);
}
}
}
void NCMesh::RegisterFaces(int elem, int* fattr)
{
Element &el = elements[elem];
GeomInfo &gi = GI[el.Geom()];
for (int i = 0; i < gi.nf; i++)
{
Face* face = GetFace(el, i);
MFEM_ASSERT(face, "face not found.");
face->RegisterElement(elem);
if (fattr) { face->attribute = fattr[i]; }
}
}
void NCMesh::DeleteUnusedFaces(const Array<int> &elemFaces)
{
for (int i = 0; i < elemFaces.Size(); i++)
{
if (faces[elemFaces[i]].Unused())
{
faces.Delete(elemFaces[i]);
}
}
}
void NCMesh::Face::RegisterElement(int e)
{
if (elem[0] < 0) { elem[0] = e; }
else if (elem[1] < 0) { elem[1] = e; }
else { MFEM_ABORT("can't have 3 elements in Face::elem[]."); }
}
void NCMesh::Face::ForgetElement(int e)
{
if (elem[0] == e) { elem[0] = -1; }
else if (elem[1] == e) { elem[1] = -1; }
else { MFEM_ABORT("element " << e << " not found in Face::elem[]."); }
}
NCMesh::Face* NCMesh::GetFace(Element &elem, int face_no)
{
GeomInfo& gi = GI[(int) elem.geom];
const int* fv = gi.faces[face_no];
int* node = elem.node;
return faces.Find(node[fv[0]], node[fv[1]], node[fv[2]], node[fv[3]]);
}
int NCMesh::Face::GetSingleElement() const
{
if (elem[0] >= 0)
{
MFEM_ASSERT(elem[1] < 0, "not a single element face.");
return elem[0];
}
else
{
MFEM_ASSERT(elem[1] >= 0, "no elements in face.");
return elem[1];
}
}
//// Refinement ////////////////////////////////////////////////////////////////
void Refinement::SetScale(const ScaledType &r)
{
switch (r.first)
{
case X:
s[0] = r.second;
break;
case Y:
s[1] = r.second;
break;
case Z:
s[2] = r.second;
break;
case XY:
s[0] = r.second;
s[1] = r.second;
break;
case YZ:
s[1] = r.second;
s[2] = r.second;
break;
case XZ:
s[0] = r.second;
s[2] = r.second;
break;
case XYZ:
s[0] = r.second;
s[1] = r.second;
s[2] = r.second;
break;
default:
MFEM_ABORT("Unsupported refinement type.");
}
}
void Refinement::SetScaleForType(const real_t *scale)
{
for (int i=0; i<3; ++i)
if (s[i] > real_t{0})
s[i] = scale[i];
}
Refinement::Refinement(int index)
: index(index)
{
for (int i=0; i<3; ++i) { s[i] = 0.0; }
// Default case is XYZ type with scale 0.5.
SetScale(ScaledType(XYZ, 0.5));
}
Refinement::Refinement(int index, const std::initializer_list<ScaledType> &refs)
: index(index)
{
for (int i=0; i<3; ++i) { s[i] = 0.0; }
if (refs.size() == 0)
{
// Default case is XYZ type with scale 0.5.
SetScale(ScaledType(XYZ, 0.5));
}
else
{
for (const auto & ref : refs)
{
SetScale(ref);
}
}
}
Refinement::Refinement(int index, char type, real_t scale)
: index(index)
{
for (int i=0; i<3; ++i) { s[i] = 0.0; }
SetScale(ScaledType(type, scale));
}
char Refinement::GetType() const
{
char t{0}; // Set the X, Y or Z bit
for (int i = 0; i < 3; ++i)
if (s[i] > real_t{0})
t |= (1 << i);
return t;
}
void Refinement::Set(int element, char type, real_t scale)
{
index = element;
for (int i=0; i<3; ++i) { s[i] = 0.0; }
SetScale(ScaledType(type, scale));
}
void Refinement::SetType(char type, real_t scale)
{
for (int i=0; i<3; ++i) { s[i] = 0.0; }
SetScale(ScaledType(type, scale));
}
NCMesh::Element::Element(Geometry::Type geom, int attr)
: geom(geom), ref_type(0), tet_type(0), flag(0), index(-1)
, rank(0), attribute(attr), parent(-1)
{
for (int i = 0; i < MaxElemNodes; i++) { node[i] = -1; }
for (int i = 0; i < MaxElemChildren; i++) { child[i] = -1; }
// NOTE: in 2D the 8/10-element node/child arrays are not optimal, however,
// testing shows we would only save 17% of the total NCMesh memory if
// 4-element arrays were used (e.g. through templates); we thus prefer to
// keep the code as simple as possible.
}
int NCMesh::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)
{
// create new element, initialize nodes
int new_id = AddElement(Geometry::CUBE, attr);
Element &el = elements[new_id];
el.node[0] = n0, el.node[1] = n1, el.node[2] = n2, el.node[3] = n3;
el.node[4] = n4, el.node[5] = n5, el.node[6] = n6, el.node[7] = n7;
// get faces and assign face attributes
Face* f[MaxElemFaces];
const GeomInfo &gi_hex = GI[Geometry::CUBE];
for (int i = 0; i < gi_hex.nf; i++)
{
const int* fv = gi_hex.faces[i];
f[i] = faces.Get(el.node[fv[0]], el.node[fv[1]],
el.node[fv[2]], el.node[fv[3]]);
}
f[0]->attribute = fattr0, f[1]->attribute = fattr1;
f[2]->attribute = fattr2, f[3]->attribute = fattr3;
f[4]->attribute = fattr4, f[5]->attribute = fattr5;
return new_id;
}
int NCMesh::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)
{
// create new element, initialize nodes
int new_id = AddElement(Geometry::PRISM, attr);
Element &el = elements[new_id];
el.node[0] = n0, el.node[1] = n1, el.node[2] = n2;
el.node[3] = n3, el.node[4] = n4, el.node[5] = n5;
// get faces and assign face attributes
Face* f[5];
const GeomInfo &gi_wedge = GI[Geometry::PRISM];
for (int i = 0; i < gi_wedge.nf; i++)
{
const int* fv = gi_wedge.faces[i];
f[i] = faces.Get(el.node[fv[0]], el.node[fv[1]],
el.node[fv[2]], el.node[fv[3]]);
}
f[0]->attribute = fattr0;
f[1]->attribute = fattr1;
f[2]->attribute = fattr2;
f[3]->attribute = fattr3;
f[4]->attribute = fattr4;
return new_id;
}
int NCMesh::NewTetrahedron(int n0, int n1, int n2, int n3, int attr,
int fattr0, int fattr1, int fattr2, int fattr3)
{
// create new element, initialize nodes
int new_id = AddElement(Geometry::TETRAHEDRON, attr);
Element &el = elements[new_id];
el.node[0] = n0, el.node[1] = n1, el.node[2] = n2, el.node[3] = n3;
// get faces and assign face attributes
Face* f[4];
const GeomInfo &gi_tet = GI[Geometry::TETRAHEDRON];
for (int i = 0; i < gi_tet.nf; i++)
{
const int* fv = gi_tet.faces[i];
f[i] = faces.Get(el.node[fv[0]], el.node[fv[1]], el.node[fv[2]]);
}
f[0]->attribute = fattr0;
f[1]->attribute = fattr1;
f[2]->attribute = fattr2;
f[3]->attribute = fattr3;
return new_id;
}
int NCMesh::NewPyramid(int n0, int n1, int n2, int n3, int n4, int attr,
int fattr0, int fattr1, int fattr2, int fattr3,
int fattr4)
{
// create new element, initialize nodes
int new_id = AddElement(Geometry::PYRAMID, attr);
Element &el = elements[new_id];
el.node[0] = n0, el.node[1] = n1, el.node[2] = n2, el.node[3] = n3;
el.node[4] = n4;
// get faces and assign face attributes
Face* f[5];
const GeomInfo &gi_pyr = GI[Geometry::PYRAMID];
for (int i = 0; i < gi_pyr.nf; i++)
{
const int* fv = gi_pyr.faces[i];
f[i] = faces.Get(el.node[fv[0]], el.node[fv[1]],
el.node[fv[2]], el.node[fv[3]]);
}
f[0]->attribute = fattr0;
f[1]->attribute = fattr1;
f[2]->attribute = fattr2;
f[3]->attribute = fattr3;
f[4]->attribute = fattr4;
return new_id;
}
int NCMesh::NewQuadrilateral(int n0, int n1, int n2, int n3,
int attr,
int eattr0, int eattr1, int eattr2, int eattr3)
{
// create new element, initialize nodes
int new_id = AddElement(Geometry::SQUARE, attr);
Element &el = elements[new_id];
el.node[0] = n0, el.node[1] = n1, el.node[2] = n2, el.node[3] = n3;
// get (degenerate) faces and assign face attributes
Face* f[4];
const GeomInfo &gi_quad = GI[Geometry::SQUARE];
for (int i = 0; i < gi_quad.nf; i++)
{
const int* fv = gi_quad.faces[i];
f[i] = faces.Get(el.node[fv[0]], el.node[fv[1]],
el.node[fv[2]], el.node[fv[3]]);
}
f[0]->attribute = eattr0, f[1]->attribute = eattr1;
f[2]->attribute = eattr2, f[3]->attribute = eattr3;
return new_id;
}
int NCMesh::NewTriangle(int n0, int n1, int n2,
int attr, int eattr0, int eattr1, int eattr2)
{
// create new element, initialize nodes
int new_id = AddElement(Geometry::TRIANGLE, attr);
Element &el = elements[new_id];
el.node[0] = n0, el.node[1] = n1, el.node[2] = n2;
// get (degenerate) faces and assign face attributes
Face* f[3];
const GeomInfo &gi_tri = GI[Geometry::TRIANGLE];
for (int i = 0; i < gi_tri.nf; i++)
{
const int* fv = gi_tri.faces[i];
f[i] = faces.Get(el.node[fv[0]], el.node[fv[1]],
el.node[fv[2]], el.node[fv[3]]);
}
f[0]->attribute = eattr0;
f[1]->attribute = eattr1;
f[2]->attribute = eattr2;
return new_id;
}
int NCMesh::NewSegment(int n0, int n1, int attr, int vattr1, int vattr2)
{
// create new element, initialize nodes
int new_id = AddElement(Geometry::SEGMENT, attr);
Element &el = elements[new_id];
el.node[0] = n0, el.node[1] = n1;
// get (degenerate) faces and assign face attributes
int v0 = el.node[0], v1 = el.node[1];
faces.Get(v0, v0, v0, v0)->attribute = vattr1;
faces.Get(v1, v1, v1, v1)->attribute = vattr2;
return new_id;
}
inline bool CubeFaceLeft(int node, int* n)
{ return node == n[0] || node == n[3] || node == n[4] || node == n[7]; }
inline bool CubeFaceRight(int node, int* n)
{ return node == n[1] || node == n[2] || node == n[5] || node == n[6]; }
inline bool CubeFaceFront(int node, int* n)
{ return node == n[0] || node == n[1] || node == n[4] || node == n[5]; }
inline bool CubeFaceBack(int node, int* n)
{ return node == n[2] || node == n[3] || node == n[6] || node == n[7]; }
inline bool CubeFaceBottom(int node, int* n)
{ return node == n[0] || node == n[1] || node == n[2] || node == n[3]; }
inline bool CubeFaceTop(int node, int* n)
{ return node == n[4] || node == n[5] || node == n[6] || node == n[7]; }
inline bool PrismFaceBottom(int node, int* n)
{ return node == n[0] || node == n[1] || node == n[2]; }
inline bool PrismFaceTop(int node, int* n)
{ return node == n[3] || node == n[4] || node == n[5]; }
void NCMesh::ForceRefinement(int vn1, int vn2, int vn3, int vn4)
{
// get the element this face belongs to
Face* face = faces.Find(vn1, vn2, vn3, vn4);
if (!face) { return; }
MFEM_VERIFY(!IsParallel(), "ForceRefinement is supported only in serial");
const int elem = face->GetSingleElement();
Element &el = elements[elem];
MFEM_ASSERT(!el.ref_type, "element already refined.");
int* el_nodes = el.node;
if (el.Geom() == Geometry::CUBE)
{
Node* node12 = nodes.Find(vn1, vn2);
real_t scale = 0.5;
if (node12)
{
scale = GetScale(node12->GetScale(), vn1 > vn2);
}
else
{
Node* node34 = nodes.Find(vn3, vn4);
MFEM_ASSERT(node34, "Scale not set in NCMesh::ForceRefinement");
scale = GetScale(node34->GetScale(), vn4 > vn3);
}
// schedule the right split depending on face orientation
if ((CubeFaceLeft(vn1, el_nodes) && CubeFaceRight(vn2, el_nodes)) ||
(CubeFaceLeft(vn2, el_nodes) && CubeFaceRight(vn1, el_nodes)))
{
// X split
const bool rev = CubeFaceLeft(vn2, el_nodes) &&
CubeFaceRight(vn1, el_nodes);
ref_stack.Append(Refinement(elem, 1, GetScale(scale, rev)));
}
else if ((CubeFaceFront(vn1, el_nodes) && CubeFaceBack(vn2, el_nodes)) ||
(CubeFaceFront(vn2, el_nodes) && CubeFaceBack(vn1, el_nodes)))
{
// Y split
const bool rev = CubeFaceFront(vn2, el_nodes) &&
CubeFaceBack(vn1, el_nodes);
ref_stack.Append(Refinement(elem, 2, GetScale(scale, rev)));
}
else if ((CubeFaceBottom(vn1, el_nodes) && CubeFaceTop(vn2, el_nodes)) ||
(CubeFaceBottom(vn2, el_nodes) && CubeFaceTop(vn1, el_nodes)))
{
// Z split
const bool rev = CubeFaceBottom(vn2, el_nodes) &&
CubeFaceTop(vn1, el_nodes);
ref_stack.Append(Refinement(elem, 4, GetScale(scale, rev)));
}
else
{
MFEM_ABORT("Inconsistent element/face structure.");
}
}
else if (el.Geom() == Geometry::PRISM)
{
if ((PrismFaceTop(vn1, el_nodes) && PrismFaceBottom(vn4, el_nodes)) ||
(PrismFaceTop(vn4, el_nodes) && PrismFaceBottom(vn1, el_nodes)))
{
ref_stack.Append(Refinement(elem, 3)); // XY split
}
else if ((PrismFaceTop(vn1, el_nodes) && PrismFaceBottom(vn2, el_nodes)) ||
(PrismFaceTop(vn2, el_nodes) && PrismFaceBottom(vn1, el_nodes)))
{
ref_stack.Append(Refinement(elem, 4)); // Z split
}
else
{
MFEM_ABORT("Inconsistent element/face structure.");
}
}
else
{
MFEM_ABORT("Unsupported geometry.")
}
}
void NCMesh::FindEdgeElements(int vn1, int vn2, int vn3, int vn4,
Array<MeshId> &elem_edge) const
{
// Assuming that f = (vn1, vn2, vn3, vn4) is a quad face and e = (vn1, vn4)
// is its edge, this function finds the N elements sharing e, and returns the
// N different MeshIds of the edge (i.e., different element-local pairs
// describing the edge).
int ev1 = vn1, ev2 = vn4;
// follow face refinement towards 'vn1', get an existing face
int split, mid[5];
real_t scale;
while ((split = QuadFaceSplitType(vn1, vn2, vn3, vn4, scale, mid)) > 0)
{
if (split == 1) // vertical
{
vn2 = mid[0]; vn3 = mid[2];
}
else // horizontal
{
vn3 = mid[1]; vn4 = mid[3];
}
}
const Face *face = faces.Find(vn1, vn2, vn3, vn4);
MFEM_ASSERT(face != NULL, "Face not found: "
<< vn1 << ", " << vn2 << ", " << vn3 << ", " << vn4
<< " (edge " << ev1 << "-" << ev2 << ").");
int elem = face->GetSingleElement();
int local = find_node(elements[elem], vn1);
Array<int> cousins;
FindVertexCousins(elem, local, cousins);
elem_edge.SetSize(0);
for (int i = 0; i < cousins.Size(); i++)
{
local = find_element_edge(elements[cousins[i]], ev1, ev2, false);
if (local > 0)
{
elem_edge.Append(MeshId(-1, cousins[i], local, Geometry::SEGMENT));
}
}
}
void NCMesh::CheckAnisoPrism(int vn1, int vn2, int vn3, int vn4,
const Refinement *refs, int nref)
{
MeshId buf[4];
Array<MeshId> eid(buf, 4);
FindEdgeElements(vn1, vn2, vn3, vn4, eid);
// see if there is an element that has not been force-refined yet
for (int i = 0, j; i < eid.Size(); i++)
{
int elem = eid[i].element;
for (j = 0; j < nref; j++)
{
if (refs[j].index == elem) { break; }
}
if (j == nref) // elem not found in refs[]
{
// schedule prism refinement along Z axis
MFEM_ASSERT(elements[elem].Geom() == Geometry::PRISM, "");
ref_stack.Append(Refinement(elem, 4));
}
}
}
void NCMesh::CheckAnisoFace(int vn1, int vn2, int vn3, int vn4,
int mid12, int mid34, int level)
{
// When a face is getting split anisotropically (without loss of generality
// we assume a "vertical" split here, see picture), it is important to make
// sure that the mid-face vertex node (midf) has mid34 and mid12 as parents.
// This is necessary for the face traversal algorithm and at places like
// Refine() that assume the mid-edge nodes to be accessible through the right
// parents. However, midf may already exist under the parents mid41 and
// mid23. In that case we need to "reparent" midf, i.e., reinsert it to the
// hash-table under the correct parents. This doesn't affect other nodes as
// all IDs stay the same, only the face refinement "tree" is affected.
//
// vn4 mid34 vn3
// *------*------*
// | | |
// | |midf |
// mid41 *- - - *- - - * mid23
// | | |
// | | |
// *------*------*
// vn1 mid12 vn2
//
// This function is recursive, because the above applies to any node along
// the middle vertical edge. The function calls itself again for the bottom
// and upper half of the above picture.
const int mid23 = FindMidEdgeNode(vn2, vn3);
const int mid41 = FindMidEdgeNode(vn4, vn1);
if (mid23 >= 0 && mid41 >= 0)
{
int midf = nodes.FindId(mid23, mid41);
if (midf >= 0)
{
Node* midfNode = nodes.Find(mid23, mid41);
if (midfNode)
{
Node* node12 = nodes.Find(vn1, vn2);
if (node12)
{
const bool rev = (vn1 < vn2) != (mid41 < mid23);
midfNode->SetScale(GetScale(node12->GetScale(), rev));
}
else
{
Node* node34 = nodes.Find(vn3, vn4);
if (node34)
{
const bool rev = (vn4 < vn3) != (mid41 < mid23);
midfNode->SetScale(GetScale(node34->GetScale(), rev));
}
}
}
reparents.Append(Triple<int, int, int>(midf, mid12, mid34));
Node* node23 = nodes.Find(vn2, vn3);
reparent_scale.Append(GetScale(node23->GetScale(), vn2 > vn3));
int rs = ref_stack.Size();
CheckAnisoFace(vn1, vn2, mid23, mid41, mid12, midf, level+1);
CheckAnisoFace(mid41, mid23, vn3, vn4, midf, mid34, level+1);
if (HavePrisms() && nodes[midf].HasEdge())
{
// Check if there is a prism with edge (mid23, mid41) that we may
// have missed in 'CheckAnisoFace', and force-refine it if present.
if (ref_stack.Size() > rs)
{
CheckAnisoPrism(mid23, vn3, vn4, mid41,
&ref_stack[rs], ref_stack.Size() - rs);
}
else
{
CheckAnisoPrism(mid23, vn3, vn4, mid41, NULL, 0);
}
}
// perform the reparents all at once at the end
if (level == 0)
{
for (int i = 0; i < reparents.Size(); i++)
{
const Triple<int, int, int> &tr = reparents[i];
ReparentNode(tr.one, tr.two, tr.three, reparent_scale[i]);
}
reparents.DeleteAll();
reparent_scale.DeleteAll();
}
return;
}
}
// Also, this is the place where forced refinements begin. In the picture
// above, edges mid12-midf and midf-mid34 should actually exist in the
// neighboring elements, otherwise the mesh is inconsistent and needs to be
// fixed. Example: suppose an element is being refined isotropically (!)
// whose neighbors across some face look like this:
//
// *--------*--------*
// | d | e |
// *--------*--------*
// | c |
// *--------*--------*
// | | |
// | a | b |
// | | |
// *--------*--------*
//
// Element 'c' needs to be refined vertically for the mesh to remain valid.
if (level > 0)
{
ForceRefinement(vn1, vn2, vn3, vn4);
}
}
void NCMesh::CheckIsoFace(int vn1, int vn2, int vn3, int vn4,
int en1, int en2, int en3, int en4, int midf)
{
if (!Iso)
{
/* If anisotropic refinements are present in the mesh, we need to check
isotropically split faces as well, see second comment in CheckAnisoFace
above. */
CheckAnisoFace(vn1, vn2, en2, en4, en1, midf);
CheckAnisoFace(en4, en2, vn3, vn4, midf, en3);
CheckAnisoFace(vn4, vn1, en1, en3, en4, midf);
CheckAnisoFace(en3, en1, vn2, vn3, midf, en2);
}
}
void NCMesh::SetNodeScale(int p0, int p1, real_t scale)
{
Node* node = nodes.Find(p0, p1);
if (node) { node->SetScale(GetScale(scale, p0 > p1)); }
}
void NCMesh::RefineElement(int elem, char ref_type)
{
RefineElement(Refinement(elem, ref_type));
}
void NCMesh::RefineElement(const Refinement & ref)
{
const int elem = ref.index;
char ref_type = ref.GetType();
const real_t scale_x = ref.s[0];
const real_t scale_y = ref.s[1];
const real_t scale_z = ref.s[2];
if (!ref_type) { return; }
// handle elements that may have been (force-) refined already
Element &el = elements[elem];
if (el.ref_type)
{
const char remaining = ref_type & ~el.ref_type;
if (remaining)
{
// do the remaining splits on the children
for (int i = 0; i < MaxElemChildren; i++)
{
if (el.child[i] >= 0)
{
Refinement child_ref(el.child[i], remaining);
child_ref.SetScaleForType(ref.s);
RefineElement(child_ref);
}
}
}
return;
}
/*mfem::out << "Refining element " << elem << " (" << el.node[0] << ", " <<
el.node[1] << ", " << el.node[2] << ", " << el.node[3] << ", " <<
el.node[4] << ", " << el.node[5] << ", " << el.node[6] << ", " <<
el.node[7] << "), " << "ref_type " << int(ref_type) << std::endl;*/
int* no = el.node;
int attr = el.attribute;
int child[MaxElemChildren];
for (int i = 0; i < MaxElemChildren; i++) { child[i] = -1; }
// get parent's face attributes
int fa[MaxElemFaces];
GeomInfo& gi = GI[el.Geom()];
for (int i = 0; i < gi.nf; i++)
{
const int* fv = gi.faces[i];
Face* face = faces.Find(no[fv[0]], no[fv[1]], no[fv[2]], no[fv[3]]);
fa[i] = face->attribute;
}
// create child elements
if (el.Geom() == Geometry::CUBE)
{
// Cube vertex numbering is assumed to be as follows:
//
// 7 6
// +-----------+ Faces: 0 bottom
// /| /| 1 front
// 4 / | 5 / | 2 right
// +-----------+ | 3 back
// | | | | 4 left
// | +--------|--+ 5 top
// | / 3 | / 2 Z Y
// |/ |/ |/
// +-----------+ *--X
// 0 1
if (ref_type == Refinement::X) // split along X axis
{
const int mid01 = GetMidEdgeNode(no[0], no[1]);
const int mid23 = GetMidEdgeNode(no[2], no[3]);
const int mid45 = GetMidEdgeNode(no[4], no[5]);
const int mid67 = GetMidEdgeNode(no[6], no[7]);
SetNodeScale(no[0], no[1], scale_x);
SetNodeScale(no[3], no[2], scale_x);
SetNodeScale(no[4], no[5], scale_x);
SetNodeScale(no[7], no[6], scale_x);
child[0] = NewHexahedron(no[0], mid01, mid23, no[3],
no[4], mid45, mid67, no[7], attr,
fa[0], fa[1], -1, fa[3], fa[4], fa[5]);
child[1] = NewHexahedron(mid01, no[1], no[2], mid23,
mid45, no[5], no[6], mid67, attr,
fa[0], fa[1], fa[2], fa[3], -1, fa[5]);
CheckAnisoFace(no[0], no[1], no[5], no[4], mid01, mid45);
CheckAnisoFace(no[2], no[3], no[7], no[6], mid23, mid67);
CheckAnisoFace(no[4], no[5], no[6], no[7], mid45, mid67);
CheckAnisoFace(no[3], no[2], no[1], no[0], mid23, mid01);
}
else if (ref_type == Refinement::Y) // split along Y axis
{
const int mid12 = GetMidEdgeNode(no[1], no[2]);
const int mid30 = GetMidEdgeNode(no[3], no[0]);
const int mid56 = GetMidEdgeNode(no[5], no[6]);
const int mid74 = GetMidEdgeNode(no[7], no[4]);
SetNodeScale(no[1], no[2], scale_y);
SetNodeScale(no[0], no[3], scale_y);
SetNodeScale(no[5], no[6], scale_y);
SetNodeScale(no[4], no[7], scale_y);
child[0] = NewHexahedron(no[0], no[1], mid12, mid30,
no[4], no[5], mid56, mid74, attr,
fa[0], fa[1], fa[2], -1, fa[4], fa[5]);
child[1] = NewHexahedron(mid30, mid12, no[2], no[3],
mid74, mid56, no[6], no[7], attr,
fa[0], -1, fa[2], fa[3], fa[4], fa[5]);
CheckAnisoFace(no[1], no[2], no[6], no[5], mid12, mid56);
CheckAnisoFace(no[3], no[0], no[4], no[7], mid30, mid74);
CheckAnisoFace(no[5], no[6], no[7], no[4], mid56, mid74);
CheckAnisoFace(no[0], no[3], no[2], no[1], mid30, mid12);
}
else if (ref_type == Refinement::Z) // split along Z axis
{
const int mid04 = GetMidEdgeNode(no[0], no[4]);
const int mid15 = GetMidEdgeNode(no[1], no[5]);
const int mid26 = GetMidEdgeNode(no[2], no[6]);
const int mid37 = GetMidEdgeNode(no[3], no[7]);
SetNodeScale(no[0], no[4], scale_z);
SetNodeScale(no[1], no[5], scale_z);
SetNodeScale(no[2], no[6], scale_z);
SetNodeScale(no[3], no[7], scale_z);
child[0] = NewHexahedron(no[0], no[1], no[2], no[3],
mid04, mid15, mid26, mid37, attr,
fa[0], fa[1], fa[2], fa[3], fa[4], -1);
child[1] = NewHexahedron(mid04, mid15, mid26, mid37,
no[4], no[5], no[6], no[7], attr,
-1, fa[1], fa[2], fa[3], fa[4], fa[5]);
CheckAnisoFace(no[4], no[0], no[1], no[5], mid04, mid15);
CheckAnisoFace(no[5], no[1], no[2], no[6], mid15, mid26);
CheckAnisoFace(no[6], no[2], no[3], no[7], mid26, mid37);
CheckAnisoFace(no[7], no[3], no[0], no[4], mid37, mid04);
}
else if (ref_type == Refinement::XY) // XY split
{
const int mid01 = GetMidEdgeNode(no[0], no[1]);
const int mid12 = GetMidEdgeNode(no[1], no[2]);
const int mid23 = GetMidEdgeNode(no[2], no[3]);
const int mid30 = GetMidEdgeNode(no[3], no[0]);
const int mid45 = GetMidEdgeNode(no[4], no[5]);
const int mid56 = GetMidEdgeNode(no[5], no[6]);
const int mid67 = GetMidEdgeNode(no[6], no[7]);
const int mid74 = GetMidEdgeNode(no[7], no[4]);
const int midf0 = GetMidFaceNode(mid23, mid12, mid01, mid30);
const int midf5 = GetMidFaceNode(mid45, mid56, mid67, mid74);
SetNodeScale(no[0], no[1], scale_x);
SetNodeScale(no[3], no[2], scale_x);
SetNodeScale(no[4], no[5], scale_x);
SetNodeScale(no[7], no[6], scale_x);
SetNodeScale(no[1], no[2], scale_y);
SetNodeScale(no[0], no[3], scale_y);
SetNodeScale(no[5], no[6], scale_y);
SetNodeScale(no[4], no[7], scale_y);
SetNodeScale(mid30, mid12, scale_x);
SetNodeScale(mid74, mid56, scale_x);
SetNodeScale(mid01, mid23, scale_y);
SetNodeScale(mid45, mid67, scale_y);
child[0] = NewHexahedron(no[0], mid01, midf0, mid30,
no[4], mid45, midf5, mid74, attr,
fa[0], fa[1], -1, -1, fa[4], fa[5]);
child[1] = NewHexahedron(mid01, no[1], mid12, midf0,
mid45, no[5], mid56, midf5, attr,
fa[0], fa[1], fa[2], -1, -1, fa[5]);
child[2] = NewHexahedron(midf0, mid12, no[2], mid23,
midf5, mid56, no[6], mid67, attr,
fa[0], -1, fa[2], fa[3], -1, fa[5]);
child[3] = NewHexahedron(mid30, midf0, mid23, no[3],
mid74, midf5, mid67, no[7], attr,
fa[0], -1, -1, fa[3], fa[4], fa[5]);
CheckAnisoFace(no[0], no[1], no[5], no[4], mid01, mid45);
CheckAnisoFace(no[1], no[2], no[6], no[5], mid12, mid56);
CheckAnisoFace(no[2], no[3], no[7], no[6], mid23, mid67);
CheckAnisoFace(no[3], no[0], no[4], no[7], mid30, mid74);
CheckIsoFace(no[3], no[2], no[1], no[0], mid23, mid12, mid01, mid30, midf0);
CheckIsoFace(no[4], no[5], no[6], no[7], mid45, mid56, mid67, mid74, midf5);
}
else if (ref_type == Refinement::XZ) // XZ split
{
const int mid01 = GetMidEdgeNode(no[0], no[1]);
const int mid23 = GetMidEdgeNode(no[2], no[3]);
const int mid45 = GetMidEdgeNode(no[4], no[5]);
const int mid67 = GetMidEdgeNode(no[6], no[7]);
const int mid04 = GetMidEdgeNode(no[0], no[4]);
const int mid15 = GetMidEdgeNode(no[1], no[5]);
const int mid26 = GetMidEdgeNode(no[2], no[6]);
const int mid37 = GetMidEdgeNode(no[3], no[7]);
const int midf1 = GetMidFaceNode(mid01, mid15, mid45, mid04);
const int midf3 = GetMidFaceNode(mid23, mid37, mid67, mid26);
SetNodeScale(no[0], no[1], scale_x);
SetNodeScale(no[3], no[2], scale_x);
SetNodeScale(no[4], no[5], scale_x);
SetNodeScale(no[7], no[6], scale_x);
SetNodeScale(no[0], no[4], scale_z);
SetNodeScale(no[1], no[5], scale_z);
SetNodeScale(no[2], no[6], scale_z);
SetNodeScale(no[3], no[7], scale_z);
SetNodeScale(mid04, mid15, scale_x);
SetNodeScale(mid37, mid26, scale_x);
SetNodeScale(mid01, mid45, scale_z);
SetNodeScale(mid23, mid67, scale_z);
child[0] = NewHexahedron(no[0], mid01, mid23, no[3],
mid04, midf1, midf3, mid37, attr,
fa[0], fa[1], -1, fa[3], fa[4], -1);
child[1] = NewHexahedron(mid01, no[1], no[2], mid23,
midf1, mid15, mid26, midf3, attr,
fa[0], fa[1], fa[2], fa[3], -1, -1);
child[2] = NewHexahedron(midf1, mid15, mid26, midf3,
mid45, no[5], no[6], mid67, attr,
-1, fa[1], fa[2], fa[3], -1, fa[5]);
child[3] = NewHexahedron(mid04, midf1, midf3, mid37,
no[4], mid45, mid67, no[7], attr,
-1, fa[1], -1, fa[3], fa[4], fa[5]);
CheckAnisoFace(no[3], no[2], no[1], no[0], mid23, mid01);
CheckAnisoFace(no[2], no[6], no[5], no[1], mid26, mid15);
CheckAnisoFace(no[6], no[7], no[4], no[5], mid67, mid45);
CheckAnisoFace(no[7], no[3], no[0], no[4], mid37, mid04);
CheckIsoFace(no[0], no[1], no[5], no[4], mid01, mid15, mid45, mid04, midf1);
CheckIsoFace(no[2], no[3], no[7], no[6], mid23, mid37, mid67, mid26, midf3);
}
else if (ref_type == Refinement::YZ) // YZ split
{
const int mid12 = GetMidEdgeNode(no[1], no[2]);
const int mid30 = GetMidEdgeNode(no[3], no[0]);
const int mid56 = GetMidEdgeNode(no[5], no[6]);
const int mid74 = GetMidEdgeNode(no[7], no[4]);
const int mid04 = GetMidEdgeNode(no[0], no[4]);
const int mid15 = GetMidEdgeNode(no[1], no[5]);
const int mid26 = GetMidEdgeNode(no[2], no[6]);
const int mid37 = GetMidEdgeNode(no[3], no[7]);
const int midf2 = GetMidFaceNode(mid12, mid26, mid56, mid15);
const int midf4 = GetMidFaceNode(mid30, mid04, mid74, mid37);
SetNodeScale(no[1], no[2], scale_y);
SetNodeScale(no[0], no[3], scale_y);
SetNodeScale(no[5], no[6], scale_y);
SetNodeScale(no[4], no[7], scale_y);
SetNodeScale(no[0], no[4], scale_z);
SetNodeScale(no[1], no[5], scale_z);
SetNodeScale(no[2], no[6], scale_z);
SetNodeScale(no[3], no[7], scale_z);
SetNodeScale(mid15, mid26, scale_y);
SetNodeScale(mid04, mid37, scale_y);
SetNodeScale(mid12, mid56, scale_z);
SetNodeScale(mid30, mid74, scale_z);
child[0] = NewHexahedron(no[0], no[1], mid12, mid30,
mid04, mid15, midf2, midf4, attr,
fa[0], fa[1], fa[2], -1, fa[4], -1);
child[1] = NewHexahedron(mid30, mid12, no[2], no[3],
midf4, midf2, mid26, mid37, attr,
fa[0], -1, fa[2], fa[3], fa[4], -1);
child[2] = NewHexahedron(mid04, mid15, midf2, midf4,
no[4], no[5], mid56, mid74, attr,
-1, fa[1], fa[2], -1, fa[4], fa[5]);
child[3] = NewHexahedron(midf4, midf2, mid26, mid37,
mid74, mid56, no[6], no[7], attr,
-1, -1, fa[2], fa[3], fa[4], fa[5]);
CheckAnisoFace(no[4], no[0], no[1], no[5], mid04, mid15);
CheckAnisoFace(no[0], no[3], no[2], no[1], mid30, mid12);
CheckAnisoFace(no[3], no[7], no[6], no[2], mid37, mid26);
CheckAnisoFace(no[7], no[4], no[5], no[6], mid74, mid56);
CheckIsoFace(no[1], no[2], no[6], no[5], mid12, mid26, mid56, mid15, midf2);
CheckIsoFace(no[3], no[0], no[4], no[7], mid30, mid04, mid74, mid37, midf4);
}
else if (ref_type == Refinement::XYZ) // full isotropic refinement
{
const int mid01 = GetMidEdgeNode(no[0], no[1]);
const int mid12 = GetMidEdgeNode(no[1], no[2]);
const int mid23 = GetMidEdgeNode(no[2], no[3]);
const int mid30 = GetMidEdgeNode(no[3], no[0]);
const int mid45 = GetMidEdgeNode(no[4], no[5]);
const int mid56 = GetMidEdgeNode(no[5], no[6]);
const int mid67 = GetMidEdgeNode(no[6], no[7]);
const int mid74 = GetMidEdgeNode(no[7], no[4]);
const int mid04 = GetMidEdgeNode(no[0], no[4]);
const int mid15 = GetMidEdgeNode(no[1], no[5]);
const int mid26 = GetMidEdgeNode(no[2], no[6]);
const int mid37 = GetMidEdgeNode(no[3], no[7]);
const int midf0 = GetMidFaceNode(mid23, mid12, mid01, mid30);
const int midf1 = GetMidFaceNode(mid01, mid15, mid45, mid04);
const int midf2 = GetMidFaceNode(mid12, mid26, mid56, mid15);
const int midf3 = GetMidFaceNode(mid23, mid37, mid67, mid26);
const int midf4 = GetMidFaceNode(mid30, mid04, mid74, mid37);
const int midf5 = GetMidFaceNode(mid45, mid56, mid67, mid74);
const int midel = GetMidEdgeNode(midf1, midf3);
SetNodeScale(midf1, midf3, scale_y);
SetNodeScale(no[0], no[1], scale_x);
SetNodeScale(no[3], no[2], scale_x);
SetNodeScale(no[1], no[2], scale_y);
SetNodeScale(no[0], no[3], scale_y);
SetNodeScale(no[4], no[5], scale_x);
SetNodeScale(no[7], no[6], scale_x);
SetNodeScale(no[5], no[6], scale_y);
SetNodeScale(no[4], no[7], scale_y);
SetNodeScale(no[0], no[4], scale_z);
SetNodeScale(no[1], no[5], scale_z);
SetNodeScale(no[2], no[6], scale_z);
SetNodeScale(no[3], no[7], scale_z);
SetNodeScale(mid01, mid23, scale_y);
SetNodeScale(mid30, mid12, scale_x);
SetNodeScale(mid04, mid15, scale_x);
SetNodeScale(mid01, mid45, scale_z);
SetNodeScale(mid15, mid26, scale_y);
SetNodeScale(mid12, mid56, scale_z);
SetNodeScale(mid23, mid67, scale_z);
SetNodeScale(mid37, mid26, scale_x);
SetNodeScale(mid30, mid74, scale_z);
SetNodeScale(mid04, mid37, scale_y);
SetNodeScale(mid74, mid56, scale_x);
SetNodeScale(mid45, mid67, scale_y);
child[0] = NewHexahedron(no[0], mid01, midf0, mid30,
mid04, midf1, midel, midf4, attr,
fa[0], fa[1], -1, -1, fa[4], -1);
child[1] = NewHexahedron(mid01, no[1], mid12, midf0,
midf1, mid15, midf2, midel, attr,
fa[0], fa[1], fa[2], -1, -1, -1);
child[2] = NewHexahedron(midf0, mid12, no[2], mid23,
midel, midf2, mid26, midf3, attr,
fa[0], -1, fa[2], fa[3], -1, -1);
child[3] = NewHexahedron(mid30, midf0, mid23, no[3],
midf4, midel, midf3, mid37, attr,
fa[0], -1, -1, fa[3], fa[4], -1);
child[4] = NewHexahedron(mid04, midf1, midel, midf4,
no[4], mid45, midf5, mid74, attr,
-1, fa[1], -1, -1, fa[4], fa[5]);
child[5] = NewHexahedron(midf1, mid15, midf2, midel,
mid45, no[5], mid56, midf5, attr,
-1, fa[1], fa[2], -1, -1, fa[5]);
child[6] = NewHexahedron(midel, midf2, mid26, midf3,
midf5, mid56, no[6], mid67, attr,
-1, -1, fa[2], fa[3], -1, fa[5]);
child[7] = NewHexahedron(midf4, midel, midf3, mid37,
mid74, midf5, mid67, no[7], attr,
-1, -1, -1, fa[3], fa[4], fa[5]);
CheckIsoFace(no[3], no[2], no[1], no[0], mid23, mid12, mid01, mid30, midf0);
CheckIsoFace(no[0], no[1], no[5], no[4], mid01, mid15, mid45, mid04, midf1);
CheckIsoFace(no[1], no[2], no[6], no[5], mid12, mid26, mid56, mid15, midf2);
CheckIsoFace(no[2], no[3], no[7], no[6], mid23, mid37, mid67, mid26, midf3);
CheckIsoFace(no[3], no[0], no[4], no[7], mid30, mid04, mid74, mid37, midf4);
CheckIsoFace(no[4], no[5], no[6], no[7], mid45, mid56, mid67, mid74, midf5);
}
else
{
MFEM_ABORT("invalid refinement type.");
}
if (ref_type != Refinement::XYZ) { Iso = false; }
}
else if (el.Geom() == Geometry::PRISM)
{
// Wedge vertex numbering:
//
// 5
// _+_
// _/ | \_ Faces: 0 bottom
// 3 / | \ 4 1 top
// +---------+ 2 front
// | | | 3 right (1 2 5 4)
// | _+_ | 4 left (2 0 3 5)
// | _/ 2 \_ | Z Y
// |/ \| | /
// +---------+ *--X
// 0 1
if (ref_type < 4) // XY refinement (split in 4 wedges)
{
ref_type = Refinement::XY; // for consistency
int mid01 = GetMidEdgeNode(no[0], no[1]);
int mid12 = GetMidEdgeNode(no[1], no[2]);
int mid20 = GetMidEdgeNode(no[2], no[0]);
int mid34 = GetMidEdgeNode(no[3], no[4]);
int mid45 = GetMidEdgeNode(no[4], no[5]);
int mid53 = GetMidEdgeNode(no[5], no[3]);
child[0] = NewWedge(no[0], mid01, mid20,
no[3], mid34, mid53, attr,
fa[0], fa[1], fa[2], -1, fa[4]);
child[1] = NewWedge(mid01, no[1], mid12,
mid34, no[4], mid45, attr,
fa[0], fa[1], fa[2], fa[3], -1);
child[2] = NewWedge(mid20, mid12, no[2],
mid53, mid45, no[5], attr,
fa[0], fa[1], -1, fa[3], fa[4]);
child[3] = NewWedge(mid12, mid20, mid01,
mid45, mid53, mid34, attr,
fa[0], fa[1], -1, -1, -1);
CheckAnisoFace(no[0], no[1], no[4], no[3], mid01, mid34);
CheckAnisoFace(no[1], no[2], no[5], no[4], mid12, mid45);
CheckAnisoFace(no[2], no[0], no[3], no[5], mid20, mid53);
}
else if (ref_type == Refinement::Z) // Z refinement only (split in 2 wedges)
{
int mid03 = GetMidEdgeNode(no[0], no[3]);
int mid14 = GetMidEdgeNode(no[1], no[4]);
int mid25 = GetMidEdgeNode(no[2], no[5]);
child[0] = NewWedge(no[0], no[1], no[2],
mid03, mid14, mid25, attr,
fa[0], -1, fa[2], fa[3], fa[4]);
child[1] = NewWedge(mid03, mid14, mid25,
no[3], no[4], no[5], attr,
-1, fa[1], fa[2], fa[3], fa[4]);
CheckAnisoFace(no[3], no[0], no[1], no[4], mid03, mid14);
CheckAnisoFace(no[4], no[1], no[2], no[5], mid14, mid25);
CheckAnisoFace(no[5], no[2], no[0], no[3], mid25, mid03);
}
else // ref_type > 4, full isotropic refinement (split in 8 wedges)
{
ref_type = Refinement::XYZ; // for consistency
int mid01 = GetMidEdgeNode(no[0], no[1]);
int mid12 = GetMidEdgeNode(no[1], no[2]);
int mid20 = GetMidEdgeNode(no[2], no[0]);
int mid34 = GetMidEdgeNode(no[3], no[4]);
int mid45 = GetMidEdgeNode(no[4], no[5]);
int mid53 = GetMidEdgeNode(no[5], no[3]);
int mid03 = GetMidEdgeNode(no[0], no[3]);
int mid14 = GetMidEdgeNode(no[1], no[4]);
int mid25 = GetMidEdgeNode(no[2], no[5]);
int midf2 = GetMidFaceNode(mid01, mid14, mid34, mid03);
int midf3 = GetMidFaceNode(mid12, mid25, mid45, mid14);
int midf4 = GetMidFaceNode(mid20, mid03, mid53, mid25);
child[0] = NewWedge(no[0], mid01, mid20,
mid03, midf2, midf4, attr,
fa[0], -1, fa[2], -1, fa[4]);
child[1] = NewWedge(mid01, no[1], mid12,
midf2, mid14, midf3, attr,
fa[0], -1, fa[2], fa[3], -1);
child[2] = NewWedge(mid20, mid12, no[2],
midf4, midf3, mid25, attr,
fa[0], -1, -1, fa[3], fa[4]);
child[3] = NewWedge(mid12, mid20, mid01,
midf3, midf4, midf2, attr,
fa[0], -1, -1, -1, -1);
child[4] = NewWedge(mid03, midf2, midf4,
no[3], mid34, mid53, attr,
-1, fa[1], fa[2], -1, fa[4]);
child[5] = NewWedge(midf2, mid14, midf3,
mid34, no[4], mid45, attr,
-1, fa[1], fa[2], fa[3], -1);
child[6] = NewWedge(midf4, midf3, mid25,
mid53, mid45, no[5], attr,
-1, fa[1], -1, fa[3], fa[4]);
child[7] = NewWedge(midf3, midf4, midf2,
mid45, mid53, mid34, attr,
-1, fa[1], -1, -1, -1);
CheckIsoFace(no[0], no[1], no[4], no[3], mid01, mid14, mid34, mid03, midf2);
CheckIsoFace(no[1], no[2], no[5], no[4], mid12, mid25, mid45, mid14, midf3);
CheckIsoFace(no[2], no[0], no[3], no[5], mid20, mid03, mid53, mid25, midf4);
}
if (ref_type != Refinement::XYZ) { Iso = false; }
}
else if (el.Geom() == Geometry::TETRAHEDRON)
{
// Tetrahedron vertex numbering:
//
// 3
// + Faces: 0 back (1, 2, 3)
// |\\_ 1 left (0, 3, 2)
// || \_ 2 front (0, 1, 3)
// | \ \_ 3 bottom (0, 1, 2)
// | +__ \_
// | /2 \__ \_ Z Y
// |/ \__\ | /
// +------------+ *--X
// 0 1
ref_type = Refinement::XYZ; // for consistency
int mid01 = GetMidEdgeNode(no[0], no[1]);
int mid12 = GetMidEdgeNode(no[1], no[2]);
int mid02 = GetMidEdgeNode(no[2], no[0]);
int mid03 = GetMidEdgeNode(no[0], no[3]);
int mid13 = GetMidEdgeNode(no[1], no[3]);
int mid23 = GetMidEdgeNode(no[2], no[3]);
child[0] = NewTetrahedron(no[0], mid01, mid02, mid03, attr,
-1, fa[1], fa[2], fa[3]);
child[1] = NewTetrahedron(mid01, no[1], mid12, mid13, attr,
fa[0], -1, fa[2], fa[3]);
child[2] = NewTetrahedron(mid02, mid12, no[2], mid23, attr,
fa[0], fa[1], -1, fa[3]);
child[3] = NewTetrahedron(mid03, mid13, mid23, no[3], attr,
fa[0], fa[1], fa[2], -1);
// There are three ways to split the inner octahedron. A good strategy is
// to use the shortest diagonal. At the moment we don't have the geometric
// information in this class to determine which diagonal is the shortest,
// but it seems that with reasonable shapes of the coarse tets and MFEM's
// default tet orientation, always using tet_type == 0 produces stable
// refinements. Types 1 and 2 are unused for now.
el.tet_type = 0;
if (el.tet_type == 0) // shortest diagonal mid01--mid23
{
child[4] = NewTetrahedron(mid01, mid23, mid02, mid03, attr,
fa[1], -1, -1, -1);
child[5] = NewTetrahedron(mid01, mid23, mid03, mid13, attr,
-1, fa[2], -1, -1);
child[6] = NewTetrahedron(mid01, mid23, mid13, mid12, attr,
fa[0], -1, -1, -1);
child[7] = NewTetrahedron(mid01, mid23, mid12, mid02, attr,
-1, fa[3], -1, -1);
}
else if (el.tet_type == 1) // shortest diagonal mid12--mid03
{
child[4] = NewTetrahedron(mid03, mid01, mid02, mid12, attr,
fa[3], -1, -1, -1);
child[5] = NewTetrahedron(mid03, mid02, mid23, mid12, attr,
-1, -1, -1, fa[1]);
child[6] = NewTetrahedron(mid03, mid23, mid13, mid12, attr,
fa[0], -1, -1, -1);
child[7] = NewTetrahedron(mid03, mid13, mid01, mid12, attr,
-1, -1, -1, fa[2]);
}
else // el.tet_type == 2, shortest diagonal mid02--mid13
{
child[4] = NewTetrahedron(mid02, mid01, mid13, mid03, attr,
fa[2], -1, -1, -1);
child[5] = NewTetrahedron(mid02, mid03, mid13, mid23, attr,
-1, -1, fa[1], -1);
child[6] = NewTetrahedron(mid02, mid23, mid13, mid12, attr,
fa[0], -1, -1, -1);
child[7] = NewTetrahedron(mid02, mid12, mid13, mid01, attr,
-1, -1, fa[3], -1);
}
}
else if (el.Geom() == Geometry::PYRAMID)
{
// Pyramid vertex numbering:
//
// 4
// + \_ Faces: 0 bottom (3,2,1,0)
// |\\_ \_ 1 front (0, 1, 4)
// || \_ \__ 2 right (1, 2, 4)
// | \ \_ \__ 3 back (2, 3, 4)
// | +____\_ ____\ 4 left (3, 0, 4)
// | /3 \_ 2 Z Y
// |/ \ / | /
// +------------+ *--X
// 0 1
ref_type = Refinement::XYZ; // for consistency
int mid01 = GetMidEdgeNode(no[0], no[1]);
int mid12 = GetMidEdgeNode(no[1], no[2]);
int mid23 = GetMidEdgeNode(no[2], no[3]);
int mid03 = GetMidEdgeNode(no[0], no[3]);
int mid04 = GetMidEdgeNode(no[0], no[4]);
int mid14 = GetMidEdgeNode(no[1], no[4]);
int mid24 = GetMidEdgeNode(no[2], no[4]);
int mid34 = GetMidEdgeNode(no[3], no[4]);
int midf0 = GetMidFaceNode(mid23, mid12, mid01, mid03);
child[0] = NewPyramid(no[0], mid01, midf0, mid03, mid04,
attr, fa[0], fa[1], -1, -1, fa[4]);
child[1] = NewPyramid(mid01, no[1], mid12, midf0, mid14,
attr, fa[0], fa[1], fa[2], -1, -1);
child[2] = NewPyramid(midf0, mid12, no[2], mid23, mid24,
attr, fa[0], -1, fa[2], fa[3], -1);
child[3] = NewPyramid(mid03, midf0, mid23, no[3], mid34,
attr, fa[0], -1, -1, fa[3], fa[4]);
child[4] = NewPyramid(mid24, mid14, mid04, mid34, midf0,
attr, -1, -1, -1, -1, -1);
child[5] = NewPyramid(mid04, mid14, mid24, mid34, no[4],
attr, -1, fa[1], fa[2], fa[3], fa[4]);
child[6] = NewTetrahedron(mid01, midf0, mid04, mid14,
attr, -1, -1, -1, fa[1]);
child[7] = NewTetrahedron(midf0, mid14, mid12, mid24,
attr, -1, -1, fa[2], -1);
child[8] = NewTetrahedron(midf0, mid23, mid34, mid24,
attr, -1, -1, fa[3], -1);
child[9] = NewTetrahedron(mid03, mid04, midf0, mid34,
attr, -1, fa[4], -1, -1);
CheckIsoFace(no[3], no[2], no[1], no[0], mid23, mid12, mid01, mid03, midf0);
}
else if (el.Geom() == Geometry::SQUARE)
{
ref_type &= 0x3; // ignore Z bit
if (ref_type == Refinement::X) // X split
{
const int mid01 = nodes.GetId(no[0], no[1]);
const int mid23 = nodes.GetId(no[2], no[3]);
SetNodeScale(no[0], no[1], scale_x);
SetNodeScale(no[3], no[2], scale_x);
child[0] = NewQuadrilateral(no[0], mid01, mid23, no[3],
attr, fa[0], -1, fa[2], fa[3]);
child[1] = NewQuadrilateral(mid01, no[1], no[2], mid23,
attr, fa[0], fa[1], fa[2], -1);
}
else if (ref_type == Refinement::Y) // Y split
{
const int mid12 = nodes.GetId(no[1], no[2]);
const int mid30 = nodes.GetId(no[3], no[0]);
SetNodeScale(no[1], no[2], scale_y);
SetNodeScale(no[0], no[3], scale_y);
child[0] = NewQuadrilateral(no[0], no[1], mid12, mid30,
attr, fa[0], fa[1], -1, fa[3]);
child[1] = NewQuadrilateral(mid30, mid12, no[2], no[3],
attr, -1, fa[1], fa[2], fa[3]);
}
else if (ref_type == Refinement::XY) // iso split
{
const int mid01 = nodes.GetId(no[0], no[1]);
const int mid12 = nodes.GetId(no[1], no[2]);
const int mid23 = nodes.GetId(no[2], no[3]);
const int mid30 = nodes.GetId(no[3], no[0]);
const int midel = nodes.GetId(mid01, mid23);
SetNodeScale(no[0], no[1], scale_x);
SetNodeScale(no[3], no[2], scale_x);
SetNodeScale(no[1], no[2], scale_y);
SetNodeScale(no[0], no[3], scale_y);
SetNodeScale(mid01, mid23, scale_y);
child[0] = NewQuadrilateral(no[0], mid01, midel, mid30,
attr, fa[0], -1, -1, fa[3]);
child[1] = NewQuadrilateral(mid01, no[1], mid12, midel,
attr, fa[0], fa[1], -1, -1);
child[2] = NewQuadrilateral(midel, mid12, no[2], mid23,
attr, -1, fa[1], fa[2], -1);
child[3] = NewQuadrilateral(mid30, midel, mid23, no[3],
attr, -1, -1, fa[2], fa[3]);
}
else
{
MFEM_ABORT("Invalid refinement type.");
}
if (ref_type != Refinement::XY) { Iso = false; }
}
else if (el.Geom() == Geometry::TRIANGLE)
{
ref_type = Refinement::XY; // for consistency
// isotropic split - the only ref_type available for triangles
int mid01 = nodes.GetId(no[0], no[1]);
int mid12 = nodes.GetId(no[1], no[2]);
int mid20 = nodes.GetId(no[2], no[0]);
child[0] = NewTriangle(no[0], mid01, mid20, attr, fa[0], -1, fa[2]);
child[1] = NewTriangle(mid01, no[1], mid12, attr, fa[0], fa[1], -1);
child[2] = NewTriangle(mid20, mid12, no[2], attr, -1, fa[1], fa[2]);
child[3] = NewTriangle(mid12, mid20, mid01, attr, -1, -1, -1);
}
else if (el.Geom() == Geometry::SEGMENT)
{
ref_type = Refinement::X; // for consistency
int mid = nodes.GetId(no[0], no[1]);
child[0] = NewSegment(no[0], mid, attr, fa[0], -1);
child[1] = NewSegment(mid, no[1], attr, -1, fa[1]);
}
else
{
MFEM_ABORT("Unsupported element geometry.");
}
// start using the nodes of the children, create edges & faces
for (int i = 0; i < MaxElemChildren && child[i] >= 0; i++)
{
ReferenceElement(child[i]);
}
int buf[MaxElemFaces];
Array<int> parentFaces(buf, MaxElemFaces);
parentFaces.SetSize(0);
// sign off of all nodes of the parent, clean up unused nodes, but keep faces
UnreferenceElement(elem, parentFaces);
// register the children in their faces
for (int i = 0; i < MaxElemChildren && child[i] >= 0; i++)
{
RegisterFaces(child[i]);
}
// clean up parent faces, if unused
DeleteUnusedFaces(parentFaces);
// make the children inherit our rank; set the parent element
for (int i = 0; i < MaxElemChildren && child[i] >= 0; i++)
{
Element &ch = elements[child[i]];
ch.rank = el.rank;
ch.parent = elem;
}
// finish the refinement
el.ref_type = ref_type;
std::memcpy(el.child, child, sizeof(el.child));
}
void NCMesh::Refine(const Array<Refinement>& refinements)
{
// push all refinements on the stack in reverse order
ref_stack.Reserve(refinements.Size());
for (int i = refinements.Size()-1; i >= 0; i--)
{
Refinement ref = refinements[i]; // Copy
ref.index = leaf_elements[ref.index];
ref_stack.Append(ref);
}
// keep refining as long as the stack contains something
int nforced = 0;
while (ref_stack.Size())
{
Refinement ref = ref_stack.Last();
ref_stack.DeleteLast();
int size = ref_stack.Size();
RefineElement(ref);
nforced += ref_stack.Size() - size;
}
/* TODO: the current algorithm of forced refinements is not optimal. As
forced refinements spread through the mesh, some may not be necessary in
the end, since the affected elements may still be scheduled for refinement
that could stop the propagation. We should introduce the member
Element::ref_pending that would show the intended refinement in the batch.
A forced refinement would be combined with ref_pending to (possibly) stop
the propagation earlier.
Update: what about a FIFO instead of ref_stack? */
#if defined(MFEM_DEBUG) && !defined(MFEM_USE_MPI)
mfem::out << "Refined " << refinements.Size() << " + " << nforced
<< " elements" << std::endl;
#else
MFEM_CONTRACT_VAR(nforced);
#endif
ref_stack.DeleteAll();
shadow.DeleteAll();
Update();
}
//// Derefinement //////////////////////////////////////////////////////////////
int NCMesh::RetrieveNode(const Element &el, int index)
{
if (!el.ref_type) { return el.node[index]; }
// need to retrieve node from a child element (there is always a child that
// inherited the parent's corner under the same index)
int ch;
switch (el.Geom())
{
case Geometry::CUBE:
ch = el.child[hex_deref_table[el.ref_type - 1][index]];
break;
case Geometry::PRISM:
ch = prism_deref_table[el.ref_type - 1][index];
MFEM_ASSERT(ch != -1, "");
ch = el.child[ch];
break;
case Geometry::PYRAMID:
ch = pyramid_deref_table[el.ref_type - 1][index];
MFEM_ASSERT(ch != -1, "");
ch = el.child[ch];
break;
case Geometry::SQUARE:
ch = el.child[quad_deref_table[el.ref_type - 1][index]];
break;
case Geometry::TETRAHEDRON:
case Geometry::TRIANGLE:
ch = el.child[index];
break;
default:
ch = 0; // suppress compiler warning
MFEM_ABORT("Unsupported element geometry.");
}
return RetrieveNode(elements[ch], index);
}
void NCMesh::DerefineElement(int elem)
{
Element &el = elements[elem];
if (!el.ref_type) { return; }
int child[MaxElemChildren];
std::memcpy(child, el.child, sizeof(child));
// first make sure that all children are leaves, derefine them if not
for (int i = 0; i < MaxElemChildren && child[i] >= 0; i++)
{
if (elements[child[i]].ref_type)
{
DerefineElement(child[i]);
}
}
int faces_attribute[MaxElemFaces];
int ref_type_key = el.ref_type - 1;
for (int i = 0; i < MaxElemNodes; i++) { el.node[i] = -1; }
// retrieve original corner nodes and face attributes from the children
if (el.Geom() == Geometry::CUBE)
{
// Sets corner nodes from childs
constexpr int nb_cube_childs = 8;
for (int i = 0; i < nb_cube_childs; i++)
{
const int child_local_index = hex_deref_table[ref_type_key][i];
const int child_global_index = child[child_local_index];
Element &ch = elements[child_global_index];
el.node[i] = ch.node[i];
}
// Sets faces attributes from childs' faces
constexpr int nb_cube_faces = 6;
for (int i = 0; i < nb_cube_faces; i++)
{
const int child_local_index = hex_deref_table[ref_type_key]
[i + nb_cube_childs];
const int child_global_index = child[child_local_index];
Element &ch = elements[child_global_index];
const int* fv = GI[el.Geom()].faces[i];
faces_attribute[i] = faces.Find(ch.node[fv[0]], ch.node[fv[1]],
ch.node[fv[2]], ch.node[fv[3]])
->attribute;
}
}
else if (el.Geom() == Geometry::PRISM)
{
MFEM_ASSERT(prism_deref_table[ref_type_key][0] != -1,
"invalid prism refinement");
constexpr int nb_prism_childs = 6;
for (int i = 0; i < nb_prism_childs; i++)
{
const int child_local_index = prism_deref_table[ref_type_key][i];
const int child_global_index = child[child_local_index];
Element &ch = elements[child_global_index];
el.node[i] = ch.node[i];
}
el.node[6] = el.node[7] = -1;
constexpr int nb_prism_faces = 5;
for (int i = 0; i < nb_prism_faces; i++)
{
const int child_local_index = prism_deref_table[ref_type_key]
[i + nb_prism_childs];
const int child_global_index = child[child_local_index];
Element &ch = elements[child_global_index];
const int* fv = GI[el.Geom()].faces[i];
faces_attribute[i] = faces.Find(ch.node[fv[0]], ch.node[fv[1]],
ch.node[fv[2]], ch.node[fv[3]])
->attribute;
}
}
else if (el.Geom() == Geometry::PYRAMID)
{
MFEM_ASSERT(pyramid_deref_table[ref_type_key][0] != -1,
"invalid pyramid refinement");
constexpr int nb_pyramid_childs = 5;
for (int i = 0; i < nb_pyramid_childs; i++)
{
const int child_local_index = pyramid_deref_table[ref_type_key][i];
const int child_global_index = child[child_local_index];
Element &ch = elements[child_global_index];
el.node[i] = ch.node[i];
}
el.node[5] = el.node[6] = el.node[7] = -1;
constexpr int nb_pyramid_faces = 5;
for (int i = 0; i < nb_pyramid_faces; i++)
{
const int child_local_index = pyramid_deref_table[ref_type_key]
[i + nb_pyramid_childs];
const int child_global_index = child[child_local_index];
Element &ch = elements[child_global_index];
const int* fv = GI[el.Geom()].faces[i];
faces_attribute[i] = faces.Find(ch.node[fv[0]], ch.node[fv[1]],
ch.node[fv[2]], ch.node[fv[3]])
->attribute;
}
}
else if (el.Geom() == Geometry::TETRAHEDRON)
{
for (int i = 0; i < 4; i++)
{
Element& ch1 = elements[child[i]];
Element& ch2 = elements[child[(i+1) & 0x3]];
el.node[i] = ch1.node[i];
const int* fv = GI[el.Geom()].faces[i];
faces_attribute[i] = faces.Find(ch2.node[fv[0]], ch2.node[fv[1]],
ch2.node[fv[2]], ch2.node[fv[3]])
->attribute;
}
}
else if (el.Geom() == Geometry::SQUARE)
{
constexpr int nb_square_childs = 4;
for (int i = 0; i < nb_square_childs; i++)
{
const int child_local_index = quad_deref_table[ref_type_key][i];
const int child_global_index = child[child_local_index];
Element &ch = elements[child_global_index];
el.node[i] = ch.node[i];
}
constexpr int nb_square_faces = 4;
for (int i = 0; i < nb_square_faces; i++)
{
const int child_local_index = quad_deref_table[ref_type_key]
[i + nb_square_childs];
const int child_global_index = child[child_local_index];
Element &ch = elements[child_global_index];
const int* fv = GI[el.Geom()].faces[i];
faces_attribute[i] = faces.Find(ch.node[fv[0]], ch.node[fv[1]],
ch.node[fv[2]], ch.node[fv[3]])
->attribute;
}
}
else if (el.Geom() == Geometry::TRIANGLE)
{
constexpr int nb_triangle_childs = 3;
for (int i = 0; i < nb_triangle_childs; i++)
{
Element& ch = elements[child[i]];
el.node[i] = ch.node[i];
const int* fv = GI[el.Geom()].faces[i];
faces_attribute[i] = faces.Find(ch.node[fv[0]], ch.node[fv[1]],
ch.node[fv[2]], ch.node[fv[3]])
->attribute;
}
}
else if (el.Geom() == Geometry::SEGMENT)
{
constexpr int nb_segment_childs = 2;
for (int i = 0; i < nb_segment_childs; i++)
{
int ni = elements[child[i]].node[i];
el.node[i] = ni;
faces_attribute[i] = faces.Find(ni, ni, ni, ni)->attribute;
}
}
else
{
MFEM_ABORT("Unsupported element geometry.");
}
// sign in to all nodes
ReferenceElement(elem);
int buf[MaxElemChildren*MaxElemFaces];
Array<int> childFaces(buf, MaxElemChildren*MaxElemFaces);
childFaces.SetSize(0);
// delete children, determine rank
el.rank = std::numeric_limits<int>::max();
for (int i = 0; i < MaxElemChildren && child[i] >= 0; i++)
{
el.rank = std::min(el.rank, elements[child[i]].rank);
UnreferenceElement(child[i], childFaces);
FreeElement(child[i]);
}
RegisterFaces(elem, faces_attribute);
// delete unused faces
childFaces.Sort();
childFaces.Unique();
DeleteUnusedFaces(childFaces);
el.ref_type = 0;
}
void NCMesh::CollectDerefinements(int elem, Array<Connection> &list)
{
Element &el = elements[elem];
if (!el.ref_type) { return; }
int total = 0, ref = 0, ghost = 0;
for (int i = 0; i < MaxElemChildren && el.child[i] >= 0; i++)
{
total++;
Element &ch = elements[el.child[i]];
if (ch.ref_type) { ref++; break; }
if (IsGhost(ch)) { ghost++; }
}
if (!ref && ghost < total)
{
// can be derefined, add to list
int next_row = list.Size() ? (list.Last().from + 1) : 0;
for (int i = 0; i < MaxElemChildren && el.child[i] >= 0; i++)
{
Element &ch = elements[el.child[i]];
list.Append(Connection(next_row, ch.index));
}
}
else
{
for (int i = 0; i < MaxElemChildren && el.child[i] >= 0; i++)
{
CollectDerefinements(el.child[i], list);
}
}
}
const Table& NCMesh::GetDerefinementTable()
{
Array<Connection> list;
list.Reserve(leaf_elements.Size());
for (int i = 0; i < root_state.Size(); i++)
{
CollectDerefinements(i, list);
}
int size = list.Size() ? (list.Last().from + 1) : 0;
derefinements.MakeFromList(size, list);
return derefinements;
}
void NCMesh::CheckDerefinementNCLevel(const Table &deref_table,
Array<int> &level_ok, int max_nc_level)
{
level_ok.SetSize(deref_table.Size());
for (int i = 0; i < deref_table.Size(); i++)
{
const int* fine = deref_table.GetRow(i), size = deref_table.RowSize(i);
Element &parent = elements[elements[leaf_elements[fine[0]]].parent];
int ok = 1;
for (int j = 0; j < size; j++)
{
int splits[3];
CountSplits(leaf_elements[fine[j]], splits);
for (int k = 0; k < Dim; k++)
{
if ((parent.ref_type & (1 << k)) &&
splits[k] >= max_nc_level)
{
ok = 0; break;
}
}
if (!ok) { break; }
}
level_ok[i] = ok;
}
}
void NCMesh::Derefine(const Array<int> &derefs)
{
MFEM_VERIFY(Dim < 3 || Iso,
"derefinement of 3D anisotropic meshes not implemented yet.");
InitDerefTransforms();
Array<int> fine_coarse;
leaf_elements.Copy(fine_coarse);
// perform the derefinements
for (int i = 0; i < derefs.Size(); i++)
{
int row = derefs[i];
MFEM_VERIFY(row >= 0 && row < derefinements.Size(),
"invalid derefinement number.");
const int* fine = derefinements.GetRow(row);
int parent = elements[leaf_elements[fine[0]]].parent;
// record the relation of the fine elements to their parent
SetDerefMatrixCodes(parent, fine_coarse);
DerefineElement(parent);
}
// update leaf_elements, Element::index etc.
Update();
// link old fine elements to the new coarse elements
for (int i = 0; i < fine_coarse.Size(); i++)
{
transforms.embeddings[i].parent = elements[fine_coarse[i]].index;
}
}
void NCMesh::InitDerefTransforms()
{
int nfine = leaf_elements.Size();
// this will tell GetDerefinementTransforms that transforms are not finished
transforms.Clear();
transforms.embeddings.SetSize(nfine);
for (int i = 0; i < nfine; i++)
{
Embedding &emb = transforms.embeddings[i];
emb.parent = -1;
emb.matrix = 0;
Element &el = elements[leaf_elements[i]];
emb.geom = el.Geom();
emb.ghost = IsGhost(el);
}
}
void NCMesh::SetDerefMatrixCodes(int parent, Array<int> &fine_coarse)
{
// encode the ref_type and child number for GetDerefinementTransforms()
Element &prn = elements[parent];
for (int i = 0; i < MaxElemChildren && prn.child[i] >= 0; i++)
{
Element &ch = elements[prn.child[i]];
if (ch.index >= 0)
{
int code = (prn.ref_type << 4) | i;
transforms.embeddings[ch.index].matrix = code;
fine_coarse[ch.index] = parent;
}
}
}
//// Mesh Interface ////////////////////////////////////////////////////////////
void NCMesh::CollectLeafElements(int elem, int state, Array<int> &ghosts,
int &counter)
{
Element &el = elements[elem];
if (!el.ref_type)
{
if (el.rank >= 0) // skip elements beyond the ghost layer in parallel
{
if (!IsGhost(el))
{
leaf_elements.Append(elem);
}
else
{
// in parallel (or in serial loading a parallel file), collect
// elements of neighboring ranks in a separate array
ghosts.Append(elem);
}
// assign the SFC index (temporarily, will be replaced by Mesh index)
el.index = counter++;
}
else
{
// elements beyond the ghost layer are invalid and don't appear in
// 'leaf_elements' (also for performance reasons)
el.index = -1;
}
}
else // Refined element
{
// in non-leaf elements, the 'rank' and 'index' members have no meaning
el.rank = -1;
el.index = -1;
// recurse to subtrees; try to order leaf elements along a space-filling
// curve by changing the order the children are visited at each level
if (el.Geom() == Geometry::SQUARE && el.ref_type == Refinement::XY)
{
for (int i = 0; i < 4; i++)
{
int ch = quad_hilbert_child_order[state][i];
int st = quad_hilbert_child_state[state][i];
CollectLeafElements(el.child[ch], st, ghosts, counter);
}
}
else if (el.Geom() == Geometry::CUBE && el.ref_type == Refinement::XYZ)
{
for (int i = 0; i < 8; i++)
{
int ch = hex_hilbert_child_order[state][i];
int st = hex_hilbert_child_state[state][i];
CollectLeafElements(el.child[ch], st, ghosts, counter);
}
}
else // no space filling curve tables yet for remaining cases
{
for (int i = 0; i < MaxElemChildren; i++)
{
if (el.child[i] >= 0)
{
CollectLeafElements(el.child[i], state, ghosts, counter);
}
}
}
}
}
void NCMesh::UpdateLeafElements()
{
Array<int> ghosts;
// collect leaf elements in leaf_elements and ghosts elements in ghosts from
// all roots
leaf_elements.SetSize(0);
for (int i = 0, counter = 0; i < root_state.Size(); i++)
{
CollectLeafElements(i, root_state[i], ghosts, counter);
}
NElements = leaf_elements.Size();
NGhostElements = ghosts.Size();
// append ghost elements at the end of 'leaf_element' (if any) and assign the
// final (Mesh) indices of leaves
leaf_elements.Append(ghosts);
leaf_sfc_index.SetSize(leaf_elements.Size());
for (int i = 0; i < leaf_elements.Size(); i++)
{
Element &el = elements[leaf_elements[i]];
leaf_sfc_index[i] = el.index;
el.index = i;
}
}
void NCMesh::UpdateVertices()
{
#ifndef MFEM_NCMESH_OLD_VERTEX_ORDERING
// This method assigns indices to vertices (Node::vert_index) that will be
// seen by the Mesh class and the rest of MFEM. We must be careful to:
//
// 1. Stay compatible with the conforming code, which expects top-level
// (original) vertices to be indexed first, otherwise GridFunctions
// defined on a conforming mesh would no longer be valid when the mesh
// is converted to an NC mesh.
//
// 2. Make sure serial NCMesh is compatible with the parallel ParNCMesh, so
// it is possible to read parallel partial solutions in serial code
// (e.g., serial GLVis). This means handling ghost elements, if present.
//
// 3. Assign vertices in a globally consistent order for parallel meshes:
// if two vertices i,j are shared by two ranks r1,r2, and i<j on r1,
// then i<j on r2 as well. This is true for top-level vertices but also
// for the remaining shared vertices thanks to the globally consistent
// SFC ordering of the leaf elements. This property reduces
// communication and simplifies ParNCMesh.
// STEP 1: begin by splitting vertices into 4 classes:
// - local top-level vertices (code -1)
// - local non-top level vertices (code -2)
// - ghost (non-local) vertices (code -3)
// - vertices beyond the ghost layer (code -4)
for (auto & node : nodes)
{
node.vert_index = -4; // assume beyond ghost layer
}
for (int i = 0; i < leaf_elements.Size(); i++)
{
Element &el = elements[leaf_elements[i]];
for (int j = 0; j < GI[el.Geom()].nv; j++)
{
Node &nd = nodes[el.node[j]];
if (el.rank == MyRank)
{
if (nd.p1 == nd.p2) // local top-level vertex
{
if (nd.vert_index < -1) { nd.vert_index = -1; }
}
else // local non-top-level vertex
{
if (nd.vert_index < -2) { nd.vert_index = -2; }
}
}
else // ghost vertex
{
if (nd.vert_index < -3) { nd.vert_index = -3; }
}
}
}
// STEP 2: assign indices of top-level local vertices, in original order
NVertices = 0;
for (auto &node : nodes)
{
if (node.vert_index == -1)
{
node.vert_index = NVertices++;
}
}
// STEP 3: go over all elements (local and ghost) in SFC order and assign
// remaining local vertices in that order.
Array<int> sfc_order(leaf_elements.Size());
for (int i = 0; i < sfc_order.Size(); i++)
{
sfc_order[leaf_sfc_index[i]] = leaf_elements[i];
}
for (int i = 0; i < sfc_order.Size(); i++)
{
const Element &el = elements[sfc_order[i]];
for (int j = 0; j < GI[el.Geom()].nv; j++)
{
Node &nd = nodes[el.node[j]];
if (nd.vert_index == -2) { nd.vert_index = NVertices++; }
}
}
// STEP 4: create the mapping from Mesh vertex index to NCMesh node index
vertex_nodeId.SetSize(NVertices);
for (auto node = nodes.begin(); node != nodes.end(); ++node)
{
if (node->HasVertex() && node->vert_index >= 0)
{
MFEM_ASSERT(node->vert_index < vertex_nodeId.Size(), "");
vertex_nodeId[node->vert_index] = node.index();
}
}
// STEP 5: assign remaining ghost vertices, ignore vertices beyond the ghost
// layer
NGhostVertices = 0;
for (int i = 0; i < sfc_order.Size(); i++)
{
const Element &el = elements[sfc_order[i]];
for (int j = 0; j < GI[el.Geom()].nv; j++)
{
Node &nd = nodes[el.node[j]];
if (nd.vert_index == -3)
{
nd.vert_index = NVertices + NGhostVertices++;
}
}
}
#else // old ordering for debugging/testing only
bool parallel = false;
#ifdef MFEM_USE_MPI
if (dynamic_cast<ParNCMesh*>(this)) { parallel = true; }
#endif
if (!parallel)
{
NVertices = 0;
for (auto node = nodes.begin(); node != nodes.end(); ++node)
{
if (node->HasVertex()) { node->vert_index = NVertices++; }
}
vertex_nodeId.SetSize(NVertices);
NVertices = 0;
for (auto node = nodes.begin(); node != nodes.end(); ++node)
{
if (node->HasVertex()) { vertex_nodeId[NVertices++] = node.index(); }
}
}
else
{
for (auto node = nodes.begin(); node != nodes.end(); ++node)
{
if (node->HasVertex()) { node->vert_index = -1; }
}
NVertices = 0;
for (int i = 0; i < leaf_elements.Size(); i++)
{
Element &el = elements[leaf_elements[i]];
if (el.rank == MyRank)
{
for (int j = 0; j < GI[el.Geom()].nv; j++)
{
int &vindex = nodes[el.node[j]].vert_index;
if (vindex < 0) { vindex = NVertices++; }
}
}
}
vertex_nodeId.SetSize(NVertices);
for (auto &node : nodes)
{
if (node.HasVertex() && node.vert_index >= 0)
{
vertex_nodeId[node.vert_index] = node.index();
}
}
NGhostVertices = 0;
for (auto &node : nodes)
{
if (node.HasVertex() && node.vert_index < 0)
{
node.vert_index = NVertices + (NGhostVertices++);
}
}
}
#endif
}
void NCMesh::InitRootState(int root_count)
{
root_state.SetSize(root_count);
root_state = 0;
if (elements.Size() == 0) { return; }
char* node_order;
int nch;
switch (elements[0].Geom()) // TODO: mixed meshes
{
case Geometry::SQUARE:
nch = 4;
node_order = (char*) quad_hilbert_child_order;
break;
case Geometry::CUBE:
nch = 8;
node_order = (char*) hex_hilbert_child_order;
break;
default:
return; // do nothing, all states stay zero
}
int entry_node = -2;
// process the root element sequence
for (int i = 0; i < root_count; i++)
{
Element &el = elements[i];
int v_in = FindNodeExt(el, entry_node, false);
if (v_in < 0) { v_in = 0; }
// determine which nodes are shared with the next element
bool shared[MaxElemNodes];
for (int ni = 0; ni < MaxElemNodes; ++ni) { shared[ni] = 0; }
if (i+1 < root_count)
{
Element &next = elements[i+1];
for (int j = 0; j < nch; j++)
{
int node = FindNodeExt(el, RetrieveNode(next, j), false);
if (node >= 0) { shared[node] = true; }
}
}
// select orientation that starts in v_in and exits in shared node
int state = Dim*v_in;
for (int j = 0; j < Dim; j++)
{
if (shared[(int) node_order[nch*(state + j) + nch-1]])
{
state += j;
break;
}
}
root_state[i] = state;
entry_node = RetrieveNode(el, node_order[nch*state + nch-1]);
}
}
mfem::Element* NCMesh::NewMeshElement(int geom) const
{
switch (geom)
{
case Geometry::CUBE: return new mfem::Hexahedron;
case Geometry::PRISM: return new mfem::Wedge;
case Geometry::PYRAMID: return new mfem::Pyramid;
case Geometry::TETRAHEDRON: return new mfem::Tetrahedron;
case Geometry::SQUARE: return new mfem::Quadrilateral;
case Geometry::TRIANGLE: return new mfem::Triangle;
}
MFEM_ABORT("invalid geometry");
return NULL;
}
const real_t* NCMesh::CalcVertexPos(int node) const
{
const Node &nd = nodes[node];
if (nd.p1 == nd.p2) // top-level vertex
{
return &coordinates[3*nd.p1];
}
TmpVertex &tv = tmp_vertex[node];
if (tv.valid) { return tv.pos; }
MFEM_VERIFY(tv.visited == false, "cyclic vertex dependencies.");
tv.visited = true;
const real_t* pos1 = CalcVertexPos(nd.p1);
const real_t* pos2 = CalcVertexPos(nd.p2);
for (int i = 0; i < 3; i++)
{
tv.pos[i] = ((1.0 - nd.GetScale()) * pos1[i]) + (nd.GetScale() * pos2[i]);
}
tv.valid = true;
return tv.pos;
}
void NCMesh::GetMeshComponents(Mesh &mesh) const
{
mesh.vertices.SetSize(vertex_nodeId.Size());
if (coordinates.Size())
{
// calculate vertex positions from stored top-level vertex coordinates
tmp_vertex = new TmpVertex[nodes.NumIds()];
for (int i = 0; i < mesh.vertices.Size(); i++)
{
mesh.vertices[i].SetCoords(spaceDim, CalcVertexPos(vertex_nodeId[i]));
}
delete [] tmp_vertex;
}
// NOTE: if the mesh is curved ('coordinates' is empty), mesh.vertices are
// left uninitialized here; they will be initialized later by the Mesh from
// Nodes -- here we just make sure mesh.vertices has the correct size.
for (auto &elem : mesh.elements)
{
mesh.FreeElement(elem);
}
mesh.elements.SetSize(0);
for (auto &elem : mesh.boundary)
{
mesh.FreeElement(elem);
}
mesh.boundary.SetSize(0);
// Save off boundary face vertices to make boundary elements later.
std::map<int, mfem::Array<int>> unique_boundary_faces;
// create an mfem::Element for each leaf Element
for (int i = 0; i < NElements; i++)
{
const Element &nc_elem = elements[leaf_elements[i]];
const int* node = nc_elem.node;
GeomInfo& gi = GI[(int) nc_elem.geom];
mfem::Element* elem = mesh.NewElement(nc_elem.geom);
mesh.elements.Append(elem);
elem->SetAttribute(nc_elem.attribute);
for (int j = 0; j < gi.nv; j++)
{
elem->GetVertices()[j] = nodes[node[j]].vert_index;
}
// Loop over faces and collect those marked as boundaries
for (int k = 0; k < gi.nf; ++k)
{
const int nfv = gi.nfv[k];
const int * const fv = gi.faces[k];
const auto id = faces.FindId(node[fv[0]], node[fv[1]], node[fv[2]],
node[fv[3]]);
if (id >= 0 && faces[id].Boundary())
{
const auto &face = faces[id];
if (face.elem[0] >= 0 && face.elem[1] >= 0 &&
nc_elem.rank != std::min(elements[face.elem[0]].rank,
elements[face.elem[1]].rank))
{
// This is a conformal internal face, but this element is not the
// lowest ranking attached processor, thus not the owner of the
// face. Consequently, we do not add this face to avoid double
// counting.
continue;
}
// Add in all boundary faces that are actual boundaries or not
// masters of another face. The fv[2] in the edge split is on
// purpose. A point cannot have a split level, thus do not check for
// master/slave relation.
if ((nfv == 4 &&
!QuadFaceIsMaster(node[fv[0]], node[fv[1]], node[fv[2]], node[fv[3]]))
|| (nfv == 3 && !TriFaceIsMaster(node[fv[0]], node[fv[1]], node[fv[2]]))
|| (nfv == 2 && EdgeSplitLevel(node[fv[0]], node[fv[2]]) == 0) || (nfv == 1))
{
// This face has no split faces below, it is conformal or a
// slave.
unique_boundary_faces[id].SetSize(nfv);
for (int v = 0; v < nfv; ++v)
{
// Using a map overwrites if a face is visited twice. The
// nfv==2 is necessary because faces of 2D are storing the
// second index in the 2 slot, not the 1 slot.
unique_boundary_faces[id][v] = nodes[node[fv[(nfv==2) ? 2*v : v]]].vert_index;
}
}
}
}
}
auto geom_from_nfv = [](int nfv)
{
switch (nfv)
{
case 1: return Geometry::POINT;
case 2: return Geometry::SEGMENT;
case 3: return Geometry::TRIANGLE;
case 4: return Geometry::SQUARE;
}
return Geometry::INVALID;
};
for (const auto &fv : unique_boundary_faces)
{
const auto f = fv.first;
const auto &v = fv.second;
const auto &face = faces.At(f);
auto geom = geom_from_nfv(v.Size());
MFEM_ASSERT(geom != Geometry::INVALID,
"nfv: " << v.Size() <<
" does not match a valid face geometry: Quad, Tri, Segment, Point");
// Add a new boundary element, with matching attribute and vertices
mesh.boundary.Append(mesh.NewElement(geom));
auto * const be = mesh.boundary.Last();
be->SetAttribute(face.attribute);
be->SetVertices(v);
}
}
void NCMesh::OnMeshUpdated(Mesh *mesh)
{
//// PART 1: pull indices of regular edges/faces from the Mesh
NEdges = mesh->GetNEdges();
NFaces = mesh->GetNumFaces();
if (Dim < 2) { NFaces = 0; }
// clear Node::edge_index and Face::index
for (auto &node : nodes)
{
if (node.HasEdge()) { node.edge_index = -1; }
}
for (auto &face : faces)
{
face.index = -1;
}
// get edge enumeration from the Mesh
Table *edge_vertex = mesh->GetEdgeVertexTable();
for (int i = 0; i < edge_vertex->Size(); i++)
{
const int *ev = edge_vertex->GetRow(i);
Node* node = nodes.Find(vertex_nodeId[ev[0]], vertex_nodeId[ev[1]]);
MFEM_ASSERT(node && node->HasEdge(),
"edge (" << ev[0] << "," << ev[1] << ") not found, "
"node = " << node << " node->HasEdge() "
<< (node != nullptr ? node->HasEdge() : false));
node->edge_index = i;
}
// get face enumeration from the Mesh, initialize 'face_geom'
face_geom.SetSize(NFaces);
for (int i = 0; i < NFaces; i++)
{
const int* fv = mesh->GetFace(i)->GetVertices();
const int nfv = mesh->GetFace(i)->GetNVertices();
Face* face;
if (Dim == 3)
{
if (nfv == 4)
{
face_geom[i] = Geometry::SQUARE;
face = faces.Find(vertex_nodeId[fv[0]], vertex_nodeId[fv[1]],
vertex_nodeId[fv[2]], vertex_nodeId[fv[3]]);
}
else
{
MFEM_ASSERT(nfv == 3, "");
face_geom[i] = Geometry::TRIANGLE;
face = faces.Find(vertex_nodeId[fv[0]], vertex_nodeId[fv[1]],
vertex_nodeId[fv[2]]);
}
}
else
{
MFEM_ASSERT(nfv == 2, "");
face_geom[i] = Geometry::SEGMENT;
int n0 = vertex_nodeId[fv[0]], n1 = vertex_nodeId[fv[1]];
face = faces.Find(n0, n0, n1, n1); // look up degenerate face
#ifdef MFEM_DEBUG
// (non-ghost) edge and face numbers must match in 2D
const int *ev = edge_vertex->GetRow(i);
MFEM_ASSERT((ev[0] == fv[0] && ev[1] == fv[1]) ||
(ev[1] == fv[0] && ev[0] == fv[1]), "");
#endif
}
MFEM_VERIFY(face, "face not found.");
face->index = i;
}
//// PART 2: assign indices of ghost edges/faces, if any
// count ghost edges and assign their indices
NGhostEdges = 0;
for (auto &node : nodes)
{
if (node.HasEdge() && node.edge_index < 0)
{
node.edge_index = NEdges + (NGhostEdges++);
}
}
// count ghost faces
NGhostFaces = 0;
for (auto &face : faces)
{
if (face.index < 0) { NGhostFaces++; }
}
if (Dim == 2)
{
// in 2D we have fake faces because of DG
MFEM_ASSERT(NFaces == NEdges, "");
MFEM_ASSERT(NGhostFaces == NGhostEdges, "");
}
// resize face_geom (default_geom is for slave faces beyond the ghost layer)
Geometry::Type default_geom = Geometry::SQUARE;
face_geom.SetSize(NFaces + NGhostFaces, default_geom);
// update 'face_geom' for ghost faces, assign ghost face indices
int nghosts = 0;
for (int i = 0; i < NGhostElements; i++)
{
Element &el = elements[leaf_elements[NElements + i]]; // ghost element
GeomInfo &gi = GI[el.Geom()];
for (int j = 0; j < gi.nf; j++)
{
const int *fv = gi.faces[j];
const int fid = faces.FindId(el.node[fv[0]], el.node[fv[1]],
el.node[fv[2]], el.node[fv[3]]);
MFEM_ASSERT(fid >= 0, "face not found!");
auto &face = faces[fid];
if (face.index < 0)
{
face.index = NFaces + (nghosts++);
// store the face geometry
static const Geometry::Type types[5] =
{
Geometry::INVALID, Geometry::INVALID,
Geometry::SEGMENT, Geometry::TRIANGLE, Geometry::SQUARE
};
face_geom[face.index] = types[gi.nfv[j]];
}
}
}
// assign valid indices also to faces beyond the ghost layer
for (auto &face : faces)
{
if (face.index < 0) { face.index = NFaces + (nghosts++); }
}
MFEM_ASSERT(nghosts == NGhostFaces, "");
}
//// Face/edge lists ///////////////////////////////////////////////////////////
int NCMesh::QuadFaceSplitType(int v1, int v2, int v3, int v4, real_t & s,
int mid[5]) const
{
MFEM_ASSERT(Dim >= 3, "");
// find edge nodes
const int e1 = FindMidEdgeNode(v1, v2);
const int e2 = FindMidEdgeNode(v2, v3);
const int e3 = (e1 >= 0 &&
nodes[e1].HasVertex()) ? FindMidEdgeNode(v3, v4) : -1;
const int e4 = (e2 >= 0 &&
nodes[e2].HasVertex()) ? FindMidEdgeNode(v4, v1) : -1;
// optional: return the mid-edge nodes if requested
if (mid) { mid[0] = e1, mid[1] = e2, mid[2] = e3, mid[3] = e4; }
// try to get a mid-face node, either by (e1, e3) or by (e2, e4)
int midf1 = -1, midf2 = -1;
if (e1 >= 0 && e3 >= 0) { midf1 = FindMidEdgeNode(e1, e3); }
if (e2 >= 0 && e4 >= 0) { midf2 = FindMidEdgeNode(e2, e4); }
// get proper node if shadow node exists
if (midf1 >= 0 && midf1 == midf2)
{
const Node &nd = nodes[midf1];
if (nd.p1 != e1 && nd.p2 != e1) { midf1 = -1; }
if (nd.p1 != e2 && nd.p2 != e2) { midf2 = -1; }
}
// only one way to access the mid-face node must always exist
MFEM_ASSERT(!(midf1 >= 0 && midf2 >= 0), "incorrectly split face!");
if (midf1 < 0 && midf2 < 0) // face not split
{
if (mid) { mid[4] = -1; }
s = 0.0;
return 0;
}
else if (midf1 >= 0) // face split "vertically"
{
if (mid) { mid[4] = midf1; }
s = nodes[e1].GetScale();
if (v1 > v2) { s = 1.0 - s; }
return 1;
}
else // face split "horizontally"
{
if (mid) { mid[4] = midf2; }
s = nodes[e2].GetScale();
if (v2 > v3) { s = 1.0 - s; }
return 2;
}
}
bool NCMesh::TriFaceSplit(int v1, int v2, int v3, int mid[3]) const
{
int e1 = nodes.FindId(v1, v2);
if (e1 < 0 || !nodes[e1].HasVertex()) { return false; }
int e2 = nodes.FindId(v2, v3);
if (e2 < 0 || !nodes[e2].HasVertex()) { return false; }
int e3 = nodes.FindId(v3, v1);
if (e3 < 0 || !nodes[e3].HasVertex()) { return false; }
if (mid) { mid[0] = e1, mid[1] = e2, mid[2] = e3; }
// This is necessary but not sufficient to determine if a face has been
// split. All edges might have been split due to edge attached faces being
// refined. Need to check for existence of face made up of midpoints.
return true;
}
bool contains_node(const std::array<int, 4> &nodes, int n)
{
return std::find(nodes.begin(), nodes.end(), n) != nodes.end();
};
int NCMesh::ParentFaceNodes(std::array<int, 4> &face_nodes) const
{
const bool is_tri = face_nodes[3] == -1;
const bool is_segment = (face_nodes[0] == face_nodes[1] &&
face_nodes[2] == face_nodes[3]);
const bool is_quad = *std::min_element(face_nodes.begin(),
face_nodes.end()) >= 0;
MFEM_ASSERT((is_tri && !is_segment && !is_quad)
|| (!is_tri && is_segment && !is_quad) || (!is_tri && !is_segment &&
is_quad), "Inconsistent node geometry");
bool all_nodes_root = true;
for (auto x : face_nodes)
{
all_nodes_root = all_nodes_root && (x < 0 || (nodes[x].p1 == nodes[x].p2));
}
// This face is a root face -> nothing to do.
if (all_nodes_root) { return -1; }
int child = -1; // The index into parent.child that this face corresponds to.
auto parent_nodes = face_nodes;
if (is_quad)
{
// Logic for coarsening anisotropic faces is more complex, needs
// identification and handling of multiple "crux" points. Will require
// inspection of edge nodes.
MFEM_VERIFY(Iso,
"ParentFaceNodes does not support anisotropic refinement yet!");
// Finds the first node whose parents aren't in the face_nodes. This is
// also the index of the child location in the parent face. Treated
// separately as ultimately multiple crux will need to be handled for
// anisotropic faces.
const auto crux = [&]()
{
for (int i = 0; i < static_cast<int>(face_nodes.size()); i++)
{
if ((!contains_node(face_nodes, nodes[face_nodes[i]].p1)
&& !contains_node(face_nodes, nodes[face_nodes[i]].p2))
|| (nodes[face_nodes[i]].p1 == nodes[face_nodes[i]].p2) /* top level node */)
{
return i;
}
}
return -1;
}();
MFEM_ASSERT(crux != -1, "A root face should have been returned early");
// Loop over nodes, starting from diagonal to child, wrapping and skipping
// child. This will visit the node opposite child twice, thereby
// coarsening to the diagonally opposite. NOTE: This assumes that the
// nodes for a square are numbered (0 -> 1 -> 2 -> 3 -> 0).
for (int i = 0; i < static_cast<int>(face_nodes.size()) + 1; i++)
{
int ind = (crux + i + 2) %
4; // Start and end with coarsening of the diagonally opposite
if (ind == crux) { continue; }
auto &x = parent_nodes[ind];
// Check against parent_nodes rather than face_nodes so on second lap
// the node opposite crux will coarsen again to the diagonally across
// in the parent face. A top level node has p1 == p2, thus these
// modifications do nothing.
if (contains_node(parent_nodes, nodes[x].p1))
{
MFEM_ASSERT(nodes[x].p2 == nodes[x].p1 ||
!contains_node(parent_nodes, nodes[x].p2), "!");
x = nodes[x].p2;
}
else if (contains_node(parent_nodes, nodes[x].p2))
{
MFEM_ASSERT(nodes[x].p2 == nodes[x].p1 ||
!contains_node(parent_nodes, nodes[x].p1), "!");
x = nodes[x].p1;
}
else { /* do nothing */ }
}
}
else if (is_tri)
{
for (int i = 0; i < 3; i++)
{
auto x = face_nodes[i];
if (x == -1) { continue; }
if (contains_node(face_nodes, nodes[x].p1))
{
MFEM_ASSERT(nodes[x].p2 == nodes[x].p1 ||
!contains_node(face_nodes, nodes[x].p2), "!");
parent_nodes[i] = nodes[x].p2;
}
else if (contains_node(face_nodes, nodes[x].p2))
{
MFEM_ASSERT(nodes[x].p2 == nodes[x].p1 ||
!contains_node(face_nodes, nodes[x].p1), "!");
parent_nodes[i] = nodes[x].p1;
}
else { /* do nothing */ }
}
if (std::equal(face_nodes.begin(), face_nodes.end(), parent_nodes.begin()))
{
// Having excluded root faces, this must be an interior face. We need
// to handle the special case of the interior face of the parent face.
std::array<std::array<int, 2>, 6> parent_pairs;
for (std::size_t i = 0; i < face_nodes.size() - 1; i++)
{
parent_pairs[i][0] = nodes[face_nodes[i]].p1;
parent_pairs[i][1] = nodes[face_nodes[i]].p2;
}
// Each node gets mapped to the common node from its parents and the
// predecessor node's parents.
for (int i = 0; i < 3; i++)
{
// Parenting convention here assumes parent face has the SAME
// orientation as the original. This is true on exterior boundaries,
// but for an interior boundary the master face will have an
// opposing orientation. TODO: Possibly fix for interior boundaries.
const auto &prev = parent_pairs[(i - 1 + 3) % 3]; // (0 -> 2, 1 -> 0, 2 -> 1)
const auto &next = parent_pairs[(i + 1 + 3) % 3]; // (0 -> 1, 1 -> 2, 2 -> 0)
for (auto x : next)
{
if (std::find(prev.begin(), prev.end(), x) != prev.end()) { parent_nodes[i] = x; }
}
}
child = 3; // The interior face is the final child.
}
}
else if (is_segment)
{
// Given this isn't a root face, one node must be the parent of the other.
if (face_nodes[0] == nodes[face_nodes[1]].p1)
{
face_nodes[1] = nodes[face_nodes[1]].p2;
}
else if (face_nodes[0] == nodes[face_nodes[1]].p2)
{
face_nodes[1] = nodes[face_nodes[1]].p1;
}
else if (face_nodes[1] == nodes[face_nodes[0]].p1)
{
face_nodes[0] = nodes[face_nodes[0]].p2;
}
else if (face_nodes[1] == nodes[face_nodes[0]].p2)
{
face_nodes[0] = nodes[face_nodes[0]].p1;
}
else
{
MFEM_ABORT("Internal logic error!");
}
}
else
{
MFEM_ABORT("Unrecognized face geometry!");
}
for (int i = 0; i < 4 && face_nodes[i] >= 0; i++)
{
if (face_nodes[i] == parent_nodes[i])
{
MFEM_ASSERT(child == -1,
"This face cannot be more than one child of the parent face!");
child = i;
}
}
MFEM_ASSERT(child != -1, "Root elements must have exited early!");
std::swap(face_nodes, parent_nodes);
return child;
}
int NCMesh::find_node(const Element &el, int node)
{
for (int i = 0; i < MaxElemNodes; i++)
{
if (el.node[i] == node) { return i; }
}
MFEM_ABORT("Node not found.");
return -1;
}
int NCMesh::FindNodeExt(const Element &el, int node, bool abort)
{
for (int i = 0; i < GI[el.Geom()].nv; i++)
{
if (RetrieveNode(el, i) == node) { return i; }
}
if (abort) { MFEM_ABORT("Node not found."); }
return -1;
}
int NCMesh::find_element_edge(const Element &el, int vn0, int vn1, bool abort)
{
MFEM_ASSERT(!el.ref_type, "");
GeomInfo &gi = GI[el.Geom()];
for (int i = 0; i < gi.ne; i++)
{
const int* ev = gi.edges[i];
int n0 = el.node[ev[0]];
int n1 = el.node[ev[1]];
if ((n0 == vn0 && n1 == vn1) ||
(n0 == vn1 && n1 == vn0)) { return i; }
}
if (abort) { MFEM_ABORT("Edge (" << vn0 << ", " << vn1 << ") not found"); }
return -1;
}
int NCMesh::find_local_face(int geom, int a, int b, int c)
{
GeomInfo &gi = GI[geom];
for (int i = 0; i < gi.nf; i++)
{
const int* fv = gi.faces[i];
if ((a == fv[0] || a == fv[1] || a == fv[2] || a == fv[3]) &&
(b == fv[0] || b == fv[1] || b == fv[2] || b == fv[3]) &&
(c == fv[0] || c == fv[1] || c == fv[2] || c == fv[3]))
{
return i;
}
}
MFEM_ABORT("Face not found.");
return -1;
}
namespace
{
template <typename T> struct IntHash;
template <> struct IntHash<float>
{
using int_type = uint32_t;
static constexpr int_type initial_value = 0xc4a016dd; // random value;
};
template <> struct IntHash<double>
{
using int_type = uint64_t;
static constexpr int_type initial_value = 0xf9ca9ba106acbba9; // random value
};
}
/// Hash function for a PointMatrix, used in MatrixMap::map.
struct PointMatrixHash
{
std::size_t operator()(const NCMesh::PointMatrix &pm) const
{
// This is a variation on "Hashing an array of floats" from here:
// https://cs.stackexchange.com/questions/37952
// Make sure (at compile time) that the types have compatible sizes
static_assert(sizeof(IntHash<real_t>::int_type) == sizeof(real_t), "");
// Suppress maybe unused warnings
MFEM_CONTRACT_VAR(IntHash<float>::initial_value);
MFEM_CONTRACT_VAR(IntHash<double>::initial_value);
auto int_bit_cast = [](real_t val)
{
// std::memcpy is the proper way of doing type punning, see e.g.
// https://gist.github.com/shafik/848ae25ee209f698763cffee272a58f8
IntHash<real_t>::int_type int_val;
std::memcpy(&int_val, &val, sizeof(real_t));
return int_val;
};
IntHash<real_t>::int_type hash = IntHash<real_t>::initial_value;
for (int i = 0; i < pm.np; i++)
{
for (int j = 0; j < pm.points[i].dim; j++)
{
// mix the doubles by adding their binary representations many times
// over (note: 31 is 11111 in binary)
real_t coord = pm.points[i].coord[j];
hash = 31*hash + int_bit_cast(coord);
}
}
return hash; // return the lowest bits of the huge sum
}
};
/** Helper container to keep track of point matrices encountered during
* face/edge traversal and to assign unique indices to them.
*/
struct MatrixMap
{
int GetIndex(const NCMesh::PointMatrix &pm)
{
int &index = map[pm];
if (!index) { index = static_cast<int>(map.size()); }
return index - 1;
}
void ExportMatrices(Array<DenseMatrix*> &point_matrices) const
{
point_matrices.SetSize(static_cast<int>(map.size()));
for (const auto &pair : map)
{
DenseMatrix* mat = new DenseMatrix();
pair.first.GetMatrix(*mat);
point_matrices[pair.second - 1] = mat;
}
}
void DumpBucketSizes() const
{
for (unsigned i = 0; i < map.bucket_count(); i++)
{
mfem::out << map.bucket_size(i) << " ";
}
}
private:
std::unordered_map<NCMesh::PointMatrix, int, PointMatrixHash> map;
};
int NCMesh::ReorderFacePointMat(int v0, int v1, int v2, int v3,
int elem, const PointMatrix &pm,
PointMatrix &reordered) const
{
const Element &el = elements[elem];
int master[4] =
{
find_node(el, v0), find_node(el, v1), find_node(el, v2),
(v3 >= 0) ? find_node(el, v3) : -1
};
int nfv = (v3 >= 0) ? 4 : 3;
int local = find_local_face(el.Geom(), master[0], master[1], master[2]);
const int* fv = GI[el.Geom()].faces[local];
reordered.np = pm.np;
for (int i = 0, j; i < nfv; i++)
{
for (j = 0; j < nfv; j++)
{
if (fv[i] == master[j])
{
reordered.points[i] = pm.points[j];
break;
}
}
MFEM_ASSERT(j != nfv, "node not found.");
}
return local;
}
void NCMesh::TraverseQuadFace(int vn0, int vn1, int vn2, int vn3,
const PointMatrix& pm, int level,
Face* eface[4], MatrixMap &matrix_map)
{
if (level > 0)
{
// check if we made it to a face that is not split further
Face* fa = faces.Find(vn0, vn1, vn2, vn3);
if (fa)
{
// we have a slave face, add it to the list
int elem = fa->GetSingleElement();
face_list.slaves.Append(
Slave(fa->index, elem, -1, Geometry::SQUARE));
Slave &sl = face_list.slaves.Last();
// reorder the point matrix according to slave face orientation
PointMatrix pm_r;
sl.local = ReorderFacePointMat(vn0, vn1, vn2, vn3, elem, pm, pm_r);
sl.matrix = matrix_map.GetIndex(pm_r);
eface[0] = eface[2] = fa;
eface[1] = eface[3] = fa;
return;
}
}
// we need to recurse deeper
int mid[5];
real_t scale;
const int split = QuadFaceSplitType(vn0, vn1, vn2, vn3, scale, mid);
Face *ef[2][4];
if (split == 1) // "X" split face
{
Point pmid0(pm(0), pm(1), scale), pmid2(pm(2), pm(3), 1.0 - scale);
TraverseQuadFace(vn0, mid[0], mid[2], vn3,
PointMatrix(pm(0), pmid0, pmid2, pm(3)),
level+1, ef[0], matrix_map);
TraverseQuadFace(mid[0], vn1, vn2, mid[2],
PointMatrix(pmid0, pm(1), pm(2), pmid2),
level+1, ef[1], matrix_map);
eface[1] = ef[1][1];
eface[3] = ef[0][3];
eface[0] = eface[2] = NULL;
}
else if (split == 2) // "Y" split face
{
Point pmid1(pm(1), pm(2), scale), pmid3(pm(3), pm(0), 1.0 - scale);
TraverseQuadFace(vn0, vn1, mid[1], mid[3],
PointMatrix(pm(0), pm(1), pmid1, pmid3),
level+1, ef[0], matrix_map);
TraverseQuadFace(mid[3], mid[1], vn2, vn3,
PointMatrix(pmid3, pmid1, pm(2), pm(3)),
level+1, ef[1], matrix_map);
eface[0] = ef[0][0];
eface[2] = ef[1][2];
eface[1] = eface[3] = NULL;
}
// check for a prism edge constrained by the master face
if (HavePrisms() && mid[4] >= 0)
{
Node& enode = nodes[mid[4]];
if (enode.HasEdge())
{
// process the edge only if it's not shared by slave faces within this
// master face (i.e. the edge is "hidden")
const int fi[3][2] = {{0, 0}, {1, 3}, {2, 0}};
if (!ef[0][fi[split][0]] && !ef[1][fi[split][1]])
{
MFEM_ASSERT(enode.edge_refc == 1, "");
MeshId buf[4];
Array<MeshId> eid(buf, 4);
(split == 1) ? FindEdgeElements(mid[0], vn1, vn2, mid[2], eid)
/* */ : FindEdgeElements(mid[3], vn0, vn1, mid[1], eid);
MFEM_ASSERT(eid.Size() > 0, "edge prism not found");
MFEM_ASSERT(eid.Size() < 2, "non-unique edge prism");
// create a slave face record with a degenerate point matrix
face_list.slaves.Append(
Slave(FlipIndexSign(enode.edge_index),
eid[0].element, eid[0].local, Geometry::SQUARE));
Slave &sl = face_list.slaves.Last();
if (split == 1)
{
Point mid0(pm(0), pm(1)), mid2(pm(2), pm(3));
int v1 = nodes[mid[0]].vert_index;
int v2 = nodes[mid[2]].vert_index;
sl.matrix =
matrix_map.GetIndex(
(v1 < v2) ? PointMatrix(mid0, mid2, mid2, mid0) :
/* */ PointMatrix(mid2, mid0, mid0, mid2));
}
else
{
Point mid1(pm(1), pm(2)), mid3(pm(3), pm(0));
int v1 = nodes[mid[1]].vert_index;
int v2 = nodes[mid[3]].vert_index;
sl.matrix =
matrix_map.GetIndex(
(v1 < v2) ? PointMatrix(mid1, mid3, mid3, mid1) :
/* */ PointMatrix(mid3, mid1, mid1, mid3));
}
}
}
}
}
void NCMesh::TraverseTetEdge(int vn0, int vn1, const Point &p0, const Point &p1,
MatrixMap &matrix_map)
{
int mid = nodes.FindId(vn0, vn1);
if (mid < 0) { return; }
const Node &nd = nodes[mid];
if (nd.HasEdge())
{
// check if the edge is already a master in 'edge_list'
const auto eid_and_type = edge_list.GetMeshIdAndType(nd.edge_index);
if (eid_and_type.type == NCList::MeshIdType::MASTER
|| eid_and_type.type == NCList::MeshIdType::CONFORMING)
{
// in this case we need to add an edge-face constraint, because the
// non-slave edge is really a (face-)slave itself.
const MeshId &eid = *eid_and_type.id;
face_list.slaves.Append(
Slave(FlipIndexSign(eid.index), eid.element, eid.local, Geometry::TRIANGLE));
int v0index = nodes[vn0].vert_index;
int v1index = nodes[vn1].vert_index;
face_list.slaves.Last().matrix =
matrix_map.GetIndex((v0index < v1index) ? PointMatrix(p0, p1, p0)
/* */ : PointMatrix(p1, p0, p1));
return; // no need to continue deeper
}
}
// recurse deeper
Point pmid(p0, p1);
TraverseTetEdge(vn0, mid, p0, pmid, matrix_map);
TraverseTetEdge(mid, vn1, pmid, p1, matrix_map);
}
NCMesh::TriFaceTraverseResults NCMesh::TraverseTriFace(int vn0, int vn1,
int vn2,
const PointMatrix& pm, int level,
MatrixMap &matrix_map)
{
if (level > 0)
{
// check if we made it to a face that is not split further
Face* fa = faces.Find(vn0, vn1, vn2);
if (fa)
{
// we have a slave face, add it to the list
int elem = fa->GetSingleElement();
face_list.slaves.Append(
Slave(fa->index, elem, -1, Geometry::TRIANGLE));
Slave &sl = face_list.slaves.Last();
// reorder the point matrix according to slave face orientation
PointMatrix pm_r;
sl.local = ReorderFacePointMat(vn0, vn1, vn2, -1, elem, pm, pm_r);
sl.matrix = matrix_map.GetIndex(pm_r);
return {true, elements[elem].rank != MyRank};
}
}
int mid[3];
if (TriFaceSplit(vn0, vn1, vn2, mid))
{
Point pmid0(pm(0), pm(1)), pmid1(pm(1), pm(2)), pmid2(pm(2), pm(0));
TriFaceTraverseResults b[4];
b[0] = TraverseTriFace(vn0, mid[0], mid[2],
PointMatrix(pm(0), pmid0, pmid2),
level+1, matrix_map);
b[1] = TraverseTriFace(mid[0], vn1, mid[1],
PointMatrix(pmid0, pm(1), pmid1),
level+1, matrix_map);
b[2] = TraverseTriFace(mid[2], mid[1], vn2,
PointMatrix(pmid2, pmid1, pm(2)),
level+1, matrix_map);
b[3] = TraverseTriFace(mid[1], mid[2], mid[0],
PointMatrix(pmid1, pmid2, pmid0),
level+1, matrix_map);
// Traverse possible tet edges constrained by the master face. This needs
// to occur if none of these first NC level faces are split further, OR if
// they are on different processors. The different processor constraint is
// needed in the case of local elements constrained by this face via the
// edge alone. Cannot know this a priori, so just constrain any edge
// attached to two neighbors.
if (HaveTets() && (!b[3].unsplit || b[3].ghost_neighbor))
{
// If the faces have no further splits, so would not be captured by
// normal face relations, add possible edge constraints.
if (!b[1].unsplit || b[1].ghost_neighbor) { TraverseTetEdge(mid[0],mid[1], pmid0,pmid1, matrix_map); }
if (!b[2].unsplit || b[2].ghost_neighbor) { TraverseTetEdge(mid[1],mid[2], pmid1,pmid2, matrix_map); }
if (!b[0].unsplit || b[0].ghost_neighbor) { TraverseTetEdge(mid[2],mid[0], pmid2,pmid0, matrix_map); }
}
}
return {false, false};
}
void NCMesh::BuildFaceList()
{
face_list.Clear();
if (Dim < 3) { return; }
if (HaveTets()) { GetEdgeList(); } // needed by TraverseTetEdge()
boundary_faces.SetSize(0);
Array<char> processed_faces(faces.NumIds());
processed_faces = 0;
MatrixMap matrix_maps[Geometry::NumGeom];
// visit faces of leaf elements
for (int i = 0; i < leaf_elements.Size(); i++)
{
int elem = leaf_elements[i];
Element &el = elements[elem];
MFEM_ASSERT(!el.ref_type, "not a leaf element.");
GeomInfo& gi = GI[el.Geom()];
for (int j = 0; j < gi.nf; j++)
{
// get nodes for this face
int node[4];
for (int k = 0; k < 4; k++)
{
node[k] = el.node[gi.faces[j][k]];
}
int face = faces.FindId(node[0], node[1], node[2], node[3]);
MFEM_ASSERT(face >= 0, "face not found!");
// tell ParNCMesh about the face
ElementSharesFace(elem, j, face);
// have we already processed this face? skip if yes
if (processed_faces[face]) { continue; }
processed_faces[face] = 1;
int fgeom = (node[3] >= 0) ? Geometry::SQUARE : Geometry::TRIANGLE;
Face &fa = faces[face];
bool is_master = false;
if (fa.elem[0] >= 0 && fa.elem[1] >= 0)
{
// this is a conforming face, add it to the list
face_list.conforming.Append(MeshId(fa.index, elem, j, fgeom));
}
else
{
// this is either a master face or a slave face, but we can't tell
// until we traverse the face refinement 'tree'...
int sb = face_list.slaves.Size();
if (fgeom == Geometry::SQUARE)
{
Face* dummy[4];
TraverseQuadFace(node[0], node[1], node[2], node[3],
pm_quad_identity, 0, dummy, matrix_maps[fgeom]);
}
else
{
TraverseTriFace(node[0], node[1], node[2],
pm_tri_identity, 0, matrix_maps[fgeom]);
}
int se = face_list.slaves.Size();
if (sb < se)
{
// found slaves, so this is a master face; add it to the list
is_master = true;
face_list.masters.Append(
Master(fa.index, elem, j, fgeom, sb, se));
// also, set the master index for the slaves
for (int ii = sb; ii < se; ii++)
{
face_list.slaves[ii].master = fa.index;
}
}
}
// To support internal boundaries can only insert non-master faces.
if (fa.Boundary() && !is_master) { boundary_faces.Append(face); }
}
}
// export unique point matrices
for (int i = 0; i < Geometry::NumGeom; i++)
{
matrix_maps[i].ExportMatrices(face_list.point_matrices[i]);
}
}
void NCMesh::TraverseEdge(int vn0, int vn1, real_t t0, real_t t1, int flags,
int level, MatrixMap &matrix_map)
{
int mid = nodes.FindId(vn0, vn1);
if (mid < 0) { return; }
Node &nd = nodes[mid];
if (nd.HasEdge() && level > 0)
{
// we have a slave edge, add it to the list
edge_list.slaves.Append(Slave(nd.edge_index, -1, -1, Geometry::SEGMENT));
Slave &sl = edge_list.slaves.Last();
sl.matrix = matrix_map.GetIndex(PointMatrix(Point(t0), Point(t1)));
// handle slave edge orientation
sl.edge_flags = flags;
int v0index = nodes[vn0].vert_index;
int v1index = nodes[vn1].vert_index;
if (v0index > v1index) { sl.edge_flags |= 2; }
}
// recurse deeper
const real_t scale = GetScale(nd.GetScale(), vn0 > vn1);
const real_t tmid = ((1.0 - scale) * t0) + (scale * t1);
TraverseEdge(vn0, mid, t0, tmid, flags, level+1, matrix_map);
TraverseEdge(mid, vn1, tmid, t1, flags, level+1, matrix_map);
}
void NCMesh::BuildEdgeList()
{
edge_list.Clear();
if (Dim < 3) { boundary_faces.SetSize(0); }
Array<char> processed_edges(nodes.NumIds());
processed_edges = 0;
Array<int> edge_element(nodes.NumIds());
Array<signed char> edge_local(nodes.NumIds());
edge_local = -1;
MatrixMap matrix_map;
// visit edges of leaf elements
for (int i = 0; i < leaf_elements.Size(); i++)
{
int elem = leaf_elements[i];
Element &el = elements[elem];
MFEM_ASSERT(!el.ref_type, "not a leaf element.");
GeomInfo& gi = GI[el.Geom()];
for (int j = 0; j < gi.ne; j++)
{
// get nodes for this edge
const int* ev = gi.edges[j];
int node[2] = { el.node[ev[0]], el.node[ev[1]] };
int enode = nodes.FindId(node[0], node[1]);
MFEM_ASSERT(enode >= 0, "edge node not found!");
Node &nd = nodes[enode];
MFEM_ASSERT(nd.HasEdge(), "edge not found!");
// tell ParNCMesh about the edge
ElementSharesEdge(elem, j, enode);
// store element/local for later
edge_element[nd.edge_index] = elem;
edge_local[nd.edge_index] = j;
// skip slave edges here, they will be reached from their masters
if (GetEdgeMaster(enode) >= 0)
{
// (2D only, store internal boundary faces)
if (Dim <= 2)
{
int face = faces.FindId(node[0], node[0], node[1], node[1]);
MFEM_ASSERT(face >= 0, "face not found!");
if (faces[face].Boundary()) { boundary_faces.Append(face); }
}
continue;
}
// have we already processed this edge? skip if yes
if (processed_edges[enode]) { continue; }
processed_edges[enode] = 1;
// prepare edge interval for slave traversal, handle orientation
real_t t0 = 0.0, t1 = 1.0;
int v0index = nodes[node[0]].vert_index;
int v1index = nodes[node[1]].vert_index;
int flags = (v0index > v1index) ? 1 : 0;
// try traversing the edge to find slave edges
int sb = edge_list.slaves.Size();
TraverseEdge(node[0], node[1], t0, t1, flags, 0, matrix_map);
int se = edge_list.slaves.Size();
if (sb < se)
{
// found slaves, this is a master face; add it to the list
edge_list.masters.Append(
Master(nd.edge_index, elem, j, Geometry::SEGMENT, sb, se));
// also, set the master index for the slaves
for (int ii = sb; ii < se; ii++)
{
edge_list.slaves[ii].master = nd.edge_index;
}
}
else
{
// no slaves, this is a conforming edge
edge_list.conforming.Append(MeshId(nd.edge_index, elem, j));
// (2D only, store boundary faces)
if (Dim <= 2)
{
int face = faces.FindId(node[0], node[0], node[1], node[1]);
MFEM_ASSERT(face >= 0, "face not found!");
if (faces[face].Boundary()) { boundary_faces.Append(face); }
}
}
}
}
// fix up slave edge element/local
for (int i = 0; i < edge_list.slaves.Size(); i++)
{
Slave &sl = edge_list.slaves[i];
int local = edge_local[sl.index];
if (local >= 0)
{
sl.local = local;
sl.element = edge_element[sl.index];
}
}
// export unique point matrices
matrix_map.ExportMatrices(edge_list.point_matrices[Geometry::SEGMENT]);
}
void NCMesh::BuildVertexList()
{
int total = NVertices + NGhostVertices;
vertex_list.Clear();
vertex_list.conforming.Reserve(total);
Array<char> processed_vertices(total);
processed_vertices = 0;
// analogously to above, visit vertices of leaf elements
for (int i = 0; i < leaf_elements.Size(); i++)
{
int elem = leaf_elements[i];
Element &el = elements[elem];
for (int j = 0; j < GI[el.Geom()].nv; j++)
{
int node = el.node[j];
Node &nd = nodes[node];
int index = nd.vert_index;
if (index >= 0)
{
ElementSharesVertex(elem, j, node);
if (processed_vertices[index]) { continue; }
processed_vertices[index] = 1;
vertex_list.conforming.Append(MeshId(index, elem, j));
}
}
}
}
void NCMesh::NCList::OrientedPointMatrix(const Slave &slave,
DenseMatrix &oriented_matrix) const
{
oriented_matrix = *(point_matrices[slave.Geom()][slave.matrix]);
if (slave.edge_flags)
{
MFEM_ASSERT(oriented_matrix.Height() == 1 &&
oriented_matrix.Width() == 2, "not an edge point matrix");
if (slave.edge_flags & 1) // master inverted
{
oriented_matrix(0,0) = 1.0 - oriented_matrix(0,0);
oriented_matrix(0,1) = 1.0 - oriented_matrix(0,1);
}
if (slave.edge_flags & 2) // slave inverted
{
std::swap(oriented_matrix(0,0), oriented_matrix(0,1));
}
}
}
void NCMesh::NCList::Clear()
{
conforming.DeleteAll();
masters.DeleteAll();
slaves.DeleteAll();
for (int i = 0; i < Geometry::NumGeom; i++)
{
for (int j = 0; j < point_matrices[i].Size(); j++)
{
delete point_matrices[i][j];
}
point_matrices[i].DeleteAll();
}
inv_index.clear();
}
NCMesh::NCList::MeshIdAndType
NCMesh::NCList::GetMeshIdAndType(int index) const
{
BuildIndex();
const auto it = inv_index.find(index);
auto ft = it != inv_index.end() ? it->second.first : MeshIdType::UNRECOGNIZED;
switch (ft)
{
case MeshIdType::CONFORMING:
return {&conforming[it->second.second], it->second.first};
case MeshIdType::MASTER:
return {&masters[it->second.second], it->second.first};
case MeshIdType::SLAVE:
return {&slaves[it->second.second], it->second.first};
case MeshIdType::UNRECOGNIZED:
default:
return {nullptr, MeshIdType::UNRECOGNIZED};
}
}
NCMesh::NCList::MeshIdType
NCMesh::NCList::GetMeshIdType(int index) const
{
BuildIndex();
auto it = inv_index.find(index);
return (it != inv_index.end()) ? it->second.first : MeshIdType::UNRECOGNIZED;
}
bool
NCMesh::NCList::CheckMeshIdType(int index, MeshIdType ft) const
{
return GetMeshIdType(index) == ft;
}
void
NCMesh::NCList::BuildIndex() const
{
if (inv_index.size() == 0)
{
auto index_compare = [](const MeshId &a, const MeshId &b) { return a.index < b.index; };
auto max_conforming = std::max_element(conforming.begin(), conforming.end(),
index_compare);
auto max_master = std::max_element(masters.begin(), masters.end(),
index_compare);
auto max_slave = std::max_element(slaves.begin(), slaves.end(), index_compare);
int max_conforming_index = max_conforming != nullptr ? max_conforming->index :
-1;
int max_master_index = max_master != nullptr ? max_master->index : -1;
int max_slave_index = max_slave != nullptr ? max_slave->index : -1;
inv_index.reserve(max(max_conforming_index, max_master_index, max_slave_index,
0));
for (int i = 0; i < conforming.Size(); i++)
{
inv_index.emplace(conforming[i].index, std::make_pair(MeshIdType::CONFORMING,
i));
}
for (int i = 0; i < masters.Size(); i++)
{
inv_index.emplace(masters[i].index, std::make_pair(MeshIdType::MASTER, i));
}
for (int i = 0; i < slaves.Size(); i++)
{
inv_index.emplace(slaves[i].index, std::make_pair(MeshIdType::SLAVE, i));
}
}
}
//// Neighbors /////////////////////////////////////////////////////////////////
void NCMesh::CollectEdgeVertices(int v0, int v1, Array<int> &indices)
{
int mid = nodes.FindId(v0, v1);
if (mid >= 0 && nodes[mid].HasVertex())
{
indices.Append(mid);
CollectEdgeVertices(v0, mid, indices);
CollectEdgeVertices(mid, v1, indices);
}
}
void NCMesh::CollectTriFaceVertices(int v0, int v1, int v2, Array<int> &indices)
{
int mid[3];
if (TriFaceSplit(v0, v1, v2, mid))
{
for (int i = 0; i < 3; i++)
{
indices.Append(mid[i]);
}
CollectTriFaceVertices(v0, mid[0], mid[2], indices);
CollectTriFaceVertices(mid[0], v1, mid[1], indices);
CollectTriFaceVertices(mid[2], mid[1], v2, indices);
CollectTriFaceVertices(mid[0], mid[1], mid[2], indices);
if (HaveTets()) // possible edge-face contact
{
CollectEdgeVertices(mid[0], mid[1], indices);
CollectEdgeVertices(mid[1], mid[2], indices);
CollectEdgeVertices(mid[2], mid[0], indices);
}
}
}
void NCMesh::CollectQuadFaceVertices(int v0, int v1, int v2, int v3,
Array<int> &indices)
{
int mid[5];
real_t scale;
switch (QuadFaceSplitType(v0, v1, v2, v3, scale, mid))
{
case 1:
indices.Append(mid[0]);
indices.Append(mid[2]);
CollectQuadFaceVertices(v0, mid[0], mid[2], v3, indices);
CollectQuadFaceVertices(mid[0], v1, v2, mid[2], indices);
if (HavePrisms()) // possible edge-face contact
{
CollectEdgeVertices(mid[0], mid[2], indices);
}
break;
case 2:
indices.Append(mid[1]);
indices.Append(mid[3]);
CollectQuadFaceVertices(v0, v1, mid[1], mid[3], indices);
CollectQuadFaceVertices(mid[3], mid[1], v2, v3, indices);
if (HavePrisms()) // possible edge-face contact
{
CollectEdgeVertices(mid[1], mid[3], indices);
}
break;
}
}
void NCMesh::BuildElementToVertexTable()
{
int nrows = leaf_elements.Size();
int* I = Memory<int>(nrows + 1);
int** JJ = new int*[nrows];
Array<int> indices;
indices.Reserve(128);
// collect vertices coinciding with each element, including hanging vertices
for (int i = 0; i < leaf_elements.Size(); i++)
{
int elem = leaf_elements[i];
Element &el = elements[elem];
MFEM_ASSERT(!el.ref_type, "not a leaf element.");
GeomInfo& gi = GI[el.Geom()];
int* node = el.node;
indices.SetSize(0);
for (int j = 0; j < gi.ne; j++)
{
const int* ev = gi.edges[j];
CollectEdgeVertices(node[ev[0]], node[ev[1]], indices);
}
if (Dim >= 3)
{
for (int j = 0; j < gi.nf; j++)
{
const int* fv = gi.faces[j];
if (gi.nfv[j] == 4)
{
CollectQuadFaceVertices(node[fv[0]], node[fv[1]],
node[fv[2]], node[fv[3]], indices);
}
else
{
CollectTriFaceVertices(node[fv[0]], node[fv[1]], node[fv[2]],
indices);
}
}
}
// temporarily store one row of the table
indices.Sort();
indices.Unique();
int size = indices.Size();
I[i] = size;
JJ[i] = new int[size];
std::memcpy(JJ[i], indices.GetData(), size * sizeof(int));
}
// finalize the I array of the table
int nnz = 0;
for (int i = 0; i < nrows; i++)
{
int cnt = I[i];
I[i] = nnz;
nnz += cnt;
}
I[nrows] = nnz;
// copy the temporarily stored rows into one J array
int *J = Memory<int>(nnz);
nnz = 0;
for (int i = 0; i < nrows; i++)
{
int cnt = I[i+1] - I[i];
std::memcpy(J+nnz, JJ[i], cnt * sizeof(int));
delete [] JJ[i];
nnz += cnt;
}
element_vertex.SetIJ(I, J, nrows);
delete [] JJ;
}
void NCMesh::FindSetNeighbors(const Array<char> &elem_set,
Array<int> *neighbors,
Array<char> *neighbor_set)
{
// If A is the element-to-vertex table (see 'element_vertex') listing all
// vertices touching each element, including hanging vertices, then A*A^T is
// the element-to-neighbor table. Multiplying the element set with A*A^T
// gives the neighbor set. To save memory, this function only computes the
// action of A*A^T, the product itself is not stored anywhere.
// Optimization: the 'element_vertex' table does not store the obvious corner
// nodes in it. The table is therefore empty for conforming meshes.
UpdateElementToVertexTable();
int nleaves = leaf_elements.Size();
MFEM_VERIFY(elem_set.Size() == nleaves, "");
MFEM_ASSERT(element_vertex.Size() == nleaves, "");
// step 1: vertices = A^T * elem_set, i.e, find all vertices touching the
// element set
Array<char> vmark(nodes.NumIds());
vmark = 0;
for (int i = 0; i < nleaves; i++)
{
if (elem_set[i])
{
int *v = element_vertex.GetRow(i);
int nv = element_vertex.RowSize(i);
for (int j = 0; j < nv; j++)
{
vmark[v[j]] = 1;
}
Element &el = elements[leaf_elements[i]];
nv = GI[el.Geom()].nv;
for (int j = 0; j < nv; j++)
{
vmark[el.node[j]] = 1;
}
}
}
// step 2: neighbors = A * vertices, i.e., find all elements coinciding with
// vertices from step 1; NOTE: in the result we don't include elements from
// the original set
if (neighbor_set)
{
neighbor_set->SetSize(nleaves);
*neighbor_set = 0;
}
for (int i = 0; i < nleaves; i++)
{
if (!elem_set[i])
{
bool hit = false;
int *v = element_vertex.GetRow(i);
int nv = element_vertex.RowSize(i);
for (int j = 0; j < nv; j++)
{
if (vmark[v[j]]) { hit = true; break; }
}
if (!hit)
{
Element &el = elements[leaf_elements[i]];
nv = GI[el.Geom()].nv;
for (int j = 0; j < nv; j++)
{
if (vmark[el.node[j]]) { hit = true; break; }
}
}
if (hit)
{
if (neighbors) { neighbors->Append(leaf_elements[i]); }
if (neighbor_set) { (*neighbor_set)[i] = 1; }
}
}
}
}
static bool sorted_lists_intersect(const int* a, const int* b, int na, int nb)
{
// pointers to "end" sentinel, not last entry. Not for dereferencing.
const int * const a_end = a + na;
const int * const b_end = b + nb;
while (a != a_end && b != b_end)
{
if (*a < *b)
{
++a;
}
else if (*b < *a)
{
++b;
}
else
{
return true; // neither *a < *b nor *b < *a thus a == b
}
}
return false; // no common element found
}
void NCMesh::FindNeighbors(int elem, Array<int> &neighbors,
const Array<int> *search_set)
{
// TODO future: this function is inefficient. For a single element, an octree
// neighbor search algorithm would be better. However, the octree neighbor
// algorithm is hard to get right in the multi-octree case due to the
// different orientations of the octrees (i.e., the root elements).
UpdateElementToVertexTable();
// sorted list of all vertex nodes touching 'elem'
Array<int> vert;
vert.Reserve(128);
// support for non-leaf 'elem', collect vertices of all children
Array<int> stack;
stack.Reserve(64);
stack.Append(elem);
while (stack.Size())
{
Element &el = elements[stack.Last()];
stack.DeleteLast();
if (!el.ref_type)
{
int *v = element_vertex.GetRow(el.index);
int nv = element_vertex.RowSize(el.index);
for (int i = 0; i < nv; i++)
{
vert.Append(v[i]);
}
nv = GI[el.Geom()].nv;
for (int i = 0; i < nv; i++)
{
vert.Append(el.node[i]);
}
}
else
{
for (int i = 0; i < MaxElemChildren && el.child[i] >= 0; i++)
{
stack.Append(el.child[i]);
}
}
}
vert.Sort();
vert.Unique();
int *v1 = vert.GetData();
int nv1 = vert.Size();
if (!search_set) { search_set = &leaf_elements; }
// test *all* potential neighbors from the search set
for (int i = 0; i < search_set->Size(); i++)
{
int testme = (*search_set)[i];
if (testme != elem)
{
Element &el = elements[testme];
int *v2 = element_vertex.GetRow(el.index);
int nv2 = element_vertex.RowSize(el.index);
bool hit = sorted_lists_intersect(v1, v2, nv1, nv2);
if (!hit)
{
int nv = GI[el.Geom()].nv;
for (int j = 0; j < nv; j++)
{
hit = sorted_lists_intersect(&el.node[j], v1, 1, nv1);
if (hit) { break; }
}
}
if (hit) { neighbors.Append(testme); }
}
}
}
void NCMesh::NeighborExpand(const Array<int> &elems,
Array<int> &expanded,
const Array<int> *search_set)
{
UpdateElementToVertexTable();
Array<char> vmark(nodes.NumIds());
vmark = 0;
for (int i = 0; i < elems.Size(); i++)
{
Element &el = elements[elems[i]];
int *v = element_vertex.GetRow(el.index);
int nv = element_vertex.RowSize(el.index);
for (int j = 0; j < nv; j++)
{
vmark[v[j]] = 1;
}
nv = GI[el.Geom()].nv;
for (int j = 0; j < nv; j++)
{
vmark[el.node[j]] = 1;
}
}
if (!search_set)
{
search_set = &leaf_elements;
}
expanded.SetSize(0);
for (int i = 0; i < search_set->Size(); i++)
{
int testme = (*search_set)[i];
Element &el = elements[testme];
bool hit = false;
int *v = element_vertex.GetRow(el.index);
int nv = element_vertex.RowSize(el.index);
for (int j = 0; j < nv; j++)
{
if (vmark[v[j]]) { hit = true; break; }
}
if (!hit)
{
nv = GI[el.Geom()].nv;
for (int j = 0; j < nv; j++)
{
if (vmark[el.node[j]]) { hit = true; break; }
}
}
if (hit) { expanded.Append(testme); }
}
}
int NCMesh::GetVertexRootCoord(int elem, RefCoord coord[3]) const
{
while (1)
{
const Element &el = elements[elem];
if (el.parent < 0) { return elem; }
const Element &pa = elements[el.parent];
MFEM_ASSERT(pa.ref_type, "internal error");
int ch = 0;
while (ch < MaxElemChildren && pa.child[ch] != elem) { ch++; }
MFEM_ASSERT(ch < MaxElemChildren, "internal error");
MFEM_ASSERT(geom_parent[el.Geom()], "unsupported geometry");
const RefTrf &tr = geom_parent[el.Geom()][(int) pa.ref_type][ch];
tr.Apply(coord, coord);
elem = el.parent;
}
}
static bool RefPointInside(Geometry::Type geom, const RefCoord pt[3])
{
switch (geom)
{
case Geometry::SQUARE:
return (pt[0] >= 0) && (pt[0] <= T_ONE) &&
(pt[1] >= 0) && (pt[1] <= T_ONE);
case Geometry::CUBE:
return (pt[0] >= 0) && (pt[0] <= T_ONE) &&
(pt[1] >= 0) && (pt[1] <= T_ONE) &&
(pt[2] >= 0) && (pt[2] <= T_ONE);
case Geometry::TRIANGLE:
return (pt[0] >= 0) && (pt[1] >= 0) && (pt[0] + pt[1] <= T_ONE);
case Geometry::PRISM:
return (pt[0] >= 0) && (pt[1] >= 0) && (pt[0] + pt[1] <= T_ONE) &&
(pt[2] >= 0) && (pt[2] <= T_ONE);
case Geometry::PYRAMID:
return (pt[0] >= 0) && (pt[1] >= 0) && (pt[2] >= 0.0) &&
(pt[0] + pt[2] <= T_ONE) && (pt[1] + pt[2] <= T_ONE) &&
(pt[2] <= T_ONE);
default:
MFEM_ABORT("unsupported geometry");
return false;
}
}
void NCMesh::CollectIncidentElements(int elem, const RefCoord coord[3],
Array<int> &list) const
{
const Element &el = elements[elem];
if (!el.ref_type)
{
list.Append(elem);
return;
}
RefCoord tcoord[3];
for (int ch = 0; ch < MaxElemChildren && el.child[ch] >= 0; ch++)
{
const RefTrf &tr = geom_child[el.Geom()][(int) el.ref_type][ch];
tr.Apply(coord, tcoord);
if (RefPointInside(el.Geom(), tcoord))
{
CollectIncidentElements(el.child[ch], tcoord, list);
}
}
}
void NCMesh::FindVertexCousins(int elem, int local, Array<int> &cousins) const
{
const Element &el = elements[elem];
RefCoord coord[3];
MFEM_ASSERT(geom_corners[el.Geom()], "unsupported geometry");
std::memcpy(coord, geom_corners[el.Geom()][local], sizeof(coord));
int root = GetVertexRootCoord(elem, coord);
cousins.SetSize(0);
CollectIncidentElements(root, coord, cousins);
}
//// Coarse/fine transformations ///////////////////////////////////////////////
bool NCMesh::PointMatrix::operator==(const PointMatrix &pm) const
{
MFEM_ASSERT(np == pm.np, "");
for (int i = 0; i < np; i++)
{
MFEM_ASSERT(points[i].dim == pm.points[i].dim, "");
for (int j = 0; j < points[i].dim; j++)
{
if (points[i].coord[j] != pm.points[i].coord[j]) { return false; }
}
}
return true;
}
void NCMesh::PointMatrix::GetMatrix(DenseMatrix& point_matrix) const
{
point_matrix.SetSize(points[0].dim, np);
for (int i = 0; i < np; i++)
{
for (int j = 0; j < points[0].dim; j++)
{
point_matrix(j, i) = points[i].coord[j];
}
}
}
NCMesh::PointMatrix NCMesh::pm_seg_identity(
Point(0), Point(1)
);
NCMesh::PointMatrix NCMesh::pm_tri_identity(
Point(0, 0), Point(1, 0), Point(0, 1)
);
NCMesh::PointMatrix NCMesh::pm_quad_identity(
Point(0, 0), Point(1, 0), Point(1, 1), Point(0, 1)
);
NCMesh::PointMatrix NCMesh::pm_tet_identity(
Point(0, 0, 0), Point(1, 0, 0), Point(0, 1, 0), Point(0, 0, 1)
);
NCMesh::PointMatrix NCMesh::pm_prism_identity(
Point(0, 0, 0), Point(1, 0, 0), Point(0, 1, 0),
Point(0, 0, 1), Point(1, 0, 1), Point(0, 1, 1)
);
NCMesh::PointMatrix NCMesh::pm_pyramid_identity(
Point(0, 0, 0), Point(1, 0, 0), Point(1, 1, 0),
Point(0, 1, 0), Point(0, 0, 1)
);
NCMesh::PointMatrix NCMesh::pm_hex_identity(
Point(0, 0, 0), Point(1, 0, 0), Point(1, 1, 0), Point(0, 1, 0),
Point(0, 0, 1), Point(1, 0, 1), Point(1, 1, 1), Point(0, 1, 1)
);
const NCMesh::PointMatrix& NCMesh::GetGeomIdentity(Geometry::Type geom)
{
switch (geom)
{
case Geometry::SEGMENT: return pm_seg_identity;
case Geometry::TRIANGLE: return pm_tri_identity;
case Geometry::SQUARE: return pm_quad_identity;
case Geometry::TETRAHEDRON: return pm_tet_identity;
case Geometry::PRISM: return pm_prism_identity;
case Geometry::PYRAMID: return pm_pyramid_identity;
case Geometry::CUBE: return pm_hex_identity;
default:
MFEM_ABORT("unsupported geometry " << geom);
return pm_tri_identity;
}
}
void NCMesh::GetPointMatrix(Geometry::Type geom, const char* ref_path,
DenseMatrix& matrix) const
{
PointMatrix pm = GetGeomIdentity(geom);
while (*ref_path)
{
int ref_type = *ref_path++;
int child = *ref_path++;
// TODO: do this with the new child transform tables
if (geom == Geometry::CUBE)
{
if (ref_type == 1) // split along X axis
{
Point mid01(pm(0), pm(1)), mid23(pm(2), pm(3));
Point mid67(pm(6), pm(7)), mid45(pm(4), pm(5));
if (child == 0)
{
pm = PointMatrix(pm(0), mid01, mid23, pm(3),
pm(4), mid45, mid67, pm(7));
}
else if (child == 1)
{
pm = PointMatrix(mid01, pm(1), pm(2), mid23,
mid45, pm(5), pm(6), mid67);
}
}
else if (ref_type == 2) // split along Y axis
{
Point mid12(pm(1), pm(2)), mid30(pm(3), pm(0));
Point mid56(pm(5), pm(6)), mid74(pm(7), pm(4));
if (child == 0)
{
pm = PointMatrix(pm(0), pm(1), mid12, mid30,
pm(4), pm(5), mid56, mid74);
}
else if (child == 1)
{
pm = PointMatrix(mid30, mid12, pm(2), pm(3),
mid74, mid56, pm(6), pm(7));
}
}
else if (ref_type == 4) // split along Z axis
{
Point mid04(pm(0), pm(4)), mid15(pm(1), pm(5));
Point mid26(pm(2), pm(6)), mid37(pm(3), pm(7));
if (child == 0)
{
pm = PointMatrix(pm(0), pm(1), pm(2), pm(3),
mid04, mid15, mid26, mid37);
}
else if (child == 1)
{
pm = PointMatrix(mid04, mid15, mid26, mid37,
pm(4), pm(5), pm(6), pm(7));
}
}
else if (ref_type == 3) // XY split
{
Point mid01(pm(0), pm(1)), mid12(pm(1), pm(2));
Point mid23(pm(2), pm(3)), mid30(pm(3), pm(0));
Point mid45(pm(4), pm(5)), mid56(pm(5), pm(6));
Point mid67(pm(6), pm(7)), mid74(pm(7), pm(4));
Point midf0(mid23, mid12, mid01, mid30);
Point midf5(mid45, mid56, mid67, mid74);
if (child == 0)
{
pm = PointMatrix(pm(0), mid01, midf0, mid30,
pm(4), mid45, midf5, mid74);
}
else if (child == 1)
{
pm = PointMatrix(mid01, pm(1), mid12, midf0,
mid45, pm(5), mid56, midf5);
}
else if (child == 2)
{
pm = PointMatrix(midf0, mid12, pm(2), mid23,
midf5, mid56, pm(6), mid67);
}
else if (child == 3)
{
pm = PointMatrix(mid30, midf0, mid23, pm(3),
mid74, midf5, mid67, pm(7));
}
}
else if (ref_type == 5) // XZ split
{
Point mid01(pm(0), pm(1)), mid23(pm(2), pm(3));
Point mid45(pm(4), pm(5)), mid67(pm(6), pm(7));
Point mid04(pm(0), pm(4)), mid15(pm(1), pm(5));
Point mid26(pm(2), pm(6)), mid37(pm(3), pm(7));
Point midf1(mid01, mid15, mid45, mid04);
Point midf3(mid23, mid37, mid67, mid26);
if (child == 0)
{
pm = PointMatrix(pm(0), mid01, mid23, pm(3),
mid04, midf1, midf3, mid37);
}
else if (child == 1)
{
pm = PointMatrix(mid01, pm(1), pm(2), mid23,
midf1, mid15, mid26, midf3);
}
else if (child == 2)
{
pm = PointMatrix(midf1, mid15, mid26, midf3,
mid45, pm(5), pm(6), mid67);
}
else if (child == 3)
{
pm = PointMatrix(mid04, midf1, midf3, mid37,
pm(4), mid45, mid67, pm(7));
}
}
else if (ref_type == 6) // YZ split
{
Point mid12(pm(1), pm(2)), mid30(pm(3), pm(0));
Point mid56(pm(5), pm(6)), mid74(pm(7), pm(4));
Point mid04(pm(0), pm(4)), mid15(pm(1), pm(5));
Point mid26(pm(2), pm(6)), mid37(pm(3), pm(7));
Point midf2(mid12, mid26, mid56, mid15);
Point midf4(mid30, mid04, mid74, mid37);
if (child == 0)
{
pm = PointMatrix(pm(0), pm(1), mid12, mid30,
mid04, mid15, midf2, midf4);
}
else if (child == 1)
{
pm = PointMatrix(mid30, mid12, pm(2), pm(3),
midf4, midf2, mid26, mid37);
}
else if (child == 2)
{
pm = PointMatrix(mid04, mid15, midf2, midf4,
pm(4), pm(5), mid56, mid74);
}
else if (child == 3)
{
pm = PointMatrix(midf4, midf2, mid26, mid37,
mid74, mid56, pm(6), pm(7));
}
}
else if (ref_type == 7) // full isotropic refinement
{
Point mid01(pm(0), pm(1)), mid12(pm(1), pm(2));
Point mid23(pm(2), pm(3)), mid30(pm(3), pm(0));
Point mid45(pm(4), pm(5)), mid56(pm(5), pm(6));
Point mid67(pm(6), pm(7)), mid74(pm(7), pm(4));
Point mid04(pm(0), pm(4)), mid15(pm(1), pm(5));
Point mid26(pm(2), pm(6)), mid37(pm(3), pm(7));
Point midf0(mid23, mid12, mid01, mid30);
Point midf1(mid01, mid15, mid45, mid04);
Point midf2(mid12, mid26, mid56, mid15);
Point midf3(mid23, mid37, mid67, mid26);
Point midf4(mid30, mid04, mid74, mid37);
Point midf5(mid45, mid56, mid67, mid74);
Point midel(midf1, midf3);
if (child == 0)
{
pm = PointMatrix(pm(0), mid01, midf0, mid30,
mid04, midf1, midel, midf4);
}
else if (child == 1)
{
pm = PointMatrix(mid01, pm(1), mid12, midf0,
midf1, mid15, midf2, midel);
}
else if (child == 2)
{
pm = PointMatrix(midf0, mid12, pm(2), mid23,
midel, midf2, mid26, midf3);
}
else if (child == 3)
{
pm = PointMatrix(mid30, midf0, mid23, pm(3),
midf4, midel, midf3, mid37);
}
else if (child == 4)
{
pm = PointMatrix(mid04, midf1, midel, midf4,
pm(4), mid45, midf5, mid74);
}
else if (child == 5)
{
pm = PointMatrix(midf1, mid15, midf2, midel,
mid45, pm(5), mid56, midf5);
}
else if (child == 6)
{
pm = PointMatrix(midel, midf2, mid26, midf3,
midf5, mid56, pm(6), mid67);
}
else if (child == 7)
{
pm = PointMatrix(midf4, midel, midf3, mid37,
mid74, midf5, mid67, pm(7));
}
}
}
else if (geom == Geometry::PRISM)
{
if (ref_type < 4) // XY split
{
Point mid01(pm(0), pm(1)), mid12(pm(1), pm(2));
Point mid20(pm(2), pm(0)), mid34(pm(3), pm(4));
Point mid45(pm(4), pm(5)), mid53(pm(5), pm(3));
if (child == 0)
{
pm = PointMatrix(pm(0), mid01, mid20, pm(3), mid34, mid53);
}
else if (child == 1)
{
pm = PointMatrix(mid01, pm(1), mid12, mid34, pm(4), mid45);
}
else if (child == 2)
{
pm = PointMatrix(mid20, mid12, pm(2), mid53, mid45, pm(5));
}
else if (child == 3)
{
pm = PointMatrix(mid12, mid20, mid01, mid45, mid53, mid34);
}
}
else if (ref_type == 4) // Z split
{
Point mid03(pm(0), pm(3)), mid14(pm(1), pm(4)), mid25(pm(2), pm(5));
if (child == 0)
{
pm = PointMatrix(pm(0), pm(1), pm(2), mid03, mid14, mid25);
}
else if (child == 1)
{
pm = PointMatrix(mid03, mid14, mid25, pm(3), pm(4), pm(5));
}
}
else // ref_type > 4, iso split
{
Point mid01(pm(0), pm(1)), mid12(pm(1), pm(2)), mid20(pm(2), pm(0));
Point mid34(pm(3), pm(4)), mid45(pm(4), pm(5)), mid53(pm(5), pm(3));
Point mid03(pm(0), pm(3)), mid14(pm(1), pm(4)), mid25(pm(2), pm(5));
Point midf2(mid01, mid14, mid34, mid03);
Point midf3(mid12, mid25, mid45, mid14);
Point midf4(mid20, mid03, mid53, mid25);
if (child == 0)
{
pm = PointMatrix(pm(0), mid01, mid20, mid03, midf2, midf4);
}
else if (child == 1)
{
pm = PointMatrix(mid01, pm(1), mid12, midf2, mid14, midf3);
}
else if (child == 2)
{
pm = PointMatrix(mid20, mid12, pm(2), midf4, midf3, mid25);
}
else if (child == 3)
{
pm = PointMatrix(mid12, mid20, mid01, midf3, midf4, midf2);
}
else if (child == 4)
{
pm = PointMatrix(mid03, midf2, midf4, pm(3), mid34, mid53);
}
else if (child == 5)
{
pm = PointMatrix(midf2, mid14, midf3, mid34, pm(4), mid45);
}
else if (child == 6)
{
pm = PointMatrix(midf4, midf3, mid25, mid53, mid45, pm(5));
}
else if (child == 7)
{
pm = PointMatrix(midf3, midf4, midf2, mid45, mid53, mid34);
}
}
}
else if (geom == Geometry::PYRAMID)
{
Point mid01(pm(0), pm(1)), mid23(pm(2), pm(3));
Point mid03(pm(0), pm(3)), mid12(pm(1), pm(2));
Point mid04(pm(0), pm(4)), mid14(pm(1), pm(4));
Point mid24(pm(2), pm(4)), mid34(pm(3), pm(4));
Point midf0(mid23, mid12, mid01, mid03);
if (child == 0) // Pyramid
{
pm = PointMatrix(pm(0), mid01, midf0, mid03, mid04);
}
else if (child == 1) // Pyramid
{
pm = PointMatrix(mid01, pm(1), mid12, midf0, mid14);
}
else if (child == 2) // Pyramid
{
pm = PointMatrix(midf0, mid12, pm(2), mid23, mid24);
}
else if (child == 3) // Pyramid
{
pm = PointMatrix(mid03, midf0, mid23, pm(3), mid34);
}
else if (child == 4) // Pyramid
{
pm = PointMatrix(mid24, mid14, mid04, mid34, midf0);
}
else if (child == 5) // Pyramid
{
pm = PointMatrix(mid04, mid14, mid24, mid34, pm(4));
}
else if (child == 6) // Tet
{
pm = PointMatrix(mid01, midf0, mid04, mid14);
}
else if (child == 7) // Tet
{
pm = PointMatrix(midf0, mid14, mid12, mid24);
}
else if (child == 8) // Tet
{
pm = PointMatrix(midf0, mid23, mid34, mid24);
}
else if (child == 9) // Tet
{
pm = PointMatrix(mid03, mid04, midf0, mid34);
}
}
else if (geom == Geometry::TETRAHEDRON)
{
Point mid01(pm(0), pm(1)), mid12(pm(1), pm(2)), mid02(pm(2), pm(0));
Point mid03(pm(0), pm(3)), mid13(pm(1), pm(3)), mid23(pm(2), pm(3));
if (child == 0)
{
pm = PointMatrix(pm(0), mid01, mid02, mid03);
}
else if (child == 1)
{
pm = PointMatrix(mid01, pm(1), mid12, mid13);
}
else if (child == 2)
{
pm = PointMatrix(mid02, mid12, pm(2), mid23);
}
else if (child == 3)
{
pm = PointMatrix(mid03, mid13, mid23, pm(3));
}
else if (child == 4)
{
pm = PointMatrix(mid01, mid23, mid02, mid03);
}
else if (child == 5)
{
pm = PointMatrix(mid01, mid23, mid03, mid13);
}
else if (child == 6)
{
pm = PointMatrix(mid01, mid23, mid13, mid12);
}
else if (child == 7)
{
pm = PointMatrix(mid01, mid23, mid12, mid02);
}
}
else if (geom == Geometry::SQUARE)
{
if (ref_type == 1) // X split
{
Point mid01(pm(0), pm(1)), mid23(pm(2), pm(3));
if (child == 0)
{
pm = PointMatrix(pm(0), mid01, mid23, pm(3));
}
else if (child == 1)
{
pm = PointMatrix(mid01, pm(1), pm(2), mid23);
}
}
else if (ref_type == 2) // Y split
{
Point mid12(pm(1), pm(2)), mid30(pm(3), pm(0));
if (child == 0)
{
pm = PointMatrix(pm(0), pm(1), mid12, mid30);
}
else if (child == 1)
{
pm = PointMatrix(mid30, mid12, pm(2), pm(3));
}
}
else if (ref_type == 3) // iso split
{
Point mid01(pm(0), pm(1)), mid12(pm(1), pm(2));
Point mid23(pm(2), pm(3)), mid30(pm(3), pm(0));
Point midel(mid01, mid23);
if (child == 0)
{
pm = PointMatrix(pm(0), mid01, midel, mid30);
}
else if (child == 1)
{
pm = PointMatrix(mid01, pm(1), mid12, midel);
}
else if (child == 2)
{
pm = PointMatrix(midel, mid12, pm(2), mid23);
}
else if (child == 3)
{
pm = PointMatrix(mid30, midel, mid23, pm(3));
}
}
}
else if (geom == Geometry::TRIANGLE)
{
Point mid01(pm(0), pm(1)), mid12(pm(1), pm(2)), mid20(pm(2), pm(0));
if (child == 0)
{
pm = PointMatrix(pm(0), mid01, mid20);
}
else if (child == 1)
{
pm = PointMatrix(mid01, pm(1), mid12);
}
else if (child == 2)
{
pm = PointMatrix(mid20, mid12, pm(2));
}
else if (child == 3)
{
pm = PointMatrix(mid12, mid20, mid01);
}
}
else if (geom == Geometry::SEGMENT)
{
Point mid01(pm(0), pm(1));
if (child == 0)
{
pm = PointMatrix(pm(0), mid01);
}
else if (child == 1)
{
pm = PointMatrix(mid01, pm(1));
}
}
}
// write the points to the matrix
for (int i = 0; i < pm.np; i++)
{
for (int j = 0; j < pm(i).dim; j++)
{
matrix(j, i) = pm(i).coord[j];
}
}
}
void RemapKnotIndex(bool rev, const Array<int> &rf, int &k);
std::pair<int, int> QuadrupleToPair(const std::array<int, 4> &q);
void NCMesh::RefineVertexToKnotSpan(const std::vector<Array<int>> &kvf,
const Array<KnotVector*> &kvext,
std::map<std::pair<int, int>,
std::array<int, 2>> &parentToKV)
{
// Note that entries 1 and 2 of vertex_to_knotspan are (k1, k2), which are knot
// span (element) indices in the two dimensions of a patch face.
for (int i=0; i<vertex_to_knotspan.Size(); ++i)
{
if (Dim == 3)
{
int tv;
std::array<int, 2> ks;
std::array<int, 4> pv;
vertex_to_knotspan.GetVertex3D(i, tv, ks, pv);
bool edgeReverse[2];
for (int j=0; j<2; ++j)
{
const bool ascending = pv[j+1] > pv[j];
edgeReverse[j] = !ascending;
}
// The parent face is defined with vertices (pv0, pv1, pv2, pv3).
const std::pair<int, int> parentPair = QuadrupleToPair(pv);
const std::array<int, 2> kv = parentToKV.at(parentPair);
RemapKnotIndex(edgeReverse[0], kvf[kv[0]], ks[0]);
RemapKnotIndex(edgeReverse[1], kvf[kv[1]], ks[1]);
vertex_to_knotspan.SetKnotSpans3D(i, ks);
}
else // 2D
{
int tv, ks;
std::array<int, 2> pv;
vertex_to_knotspan.GetVertex2D(i, tv, ks, pv);
const bool rev = pv[1] < pv[0];
const std::pair<int, int> parentPair(rev ? pv[1] : pv[0], rev ? pv[0] : pv[1]);
const std::array<int, 2> kv = parentToKV.at(parentPair);
const int kvId = kv[0];
RemapKnotIndex(rev, kvf[kvId], ks);
vertex_to_knotspan.SetKnotSpan2D(i, ks);
}
}
}
void NCMesh::MarkCoarseLevel()
{
coarse_elements.SetSize(leaf_elements.Size());
coarse_elements.SetSize(0);
for (int i = 0; i < leaf_elements.Size(); i++)
{
int elem = leaf_elements[i];
if (!IsGhost(elements[elem])) { coarse_elements.Append(elem); }
}
transforms.embeddings.DeleteAll();
}
void NCMesh::TraverseRefinements(int elem, int coarse_index,
std::string &ref_path, RefPathMap &map) const
{
const Element &el = elements[elem];
if (!el.ref_type)
{
int &matrix = map[ref_path];
if (!matrix) { matrix = static_cast<int>(map.size()); }
Embedding &emb = transforms.embeddings[el.index];
emb.parent = coarse_index;
emb.matrix = matrix - 1;
emb.geom = el.Geom();
emb.ghost = IsGhost(el);
}
else
{
MFEM_ASSERT(el.tet_type == 0, "not implemented");
ref_path.push_back(el.ref_type);
ref_path.push_back(0);
for (int i = 0; i < MaxElemChildren; i++)
{
if (el.child[i] >= 0)
{
ref_path[ref_path.length()-1] = i;
TraverseRefinements(el.child[i], coarse_index, ref_path, map);
}
}
ref_path.resize(ref_path.length()-2);
}
}
const CoarseFineTransformations& NCMesh::GetRefinementTransforms() const
{
MFEM_VERIFY(coarse_elements.Size() || !leaf_elements.Size(),
"GetRefinementTransforms() must be preceded by MarkCoarseLevel()"
" and Refine().");
if (!transforms.embeddings.Size())
{
transforms.Clear();
transforms.embeddings.SetSize(NElements);
std::string ref_path;
ref_path.reserve(100);
RefPathMap path_map[Geometry::NumGeom];
for (int g = 0; g < Geometry::NumGeom; g++)
{
path_map[g][ref_path] = 1; // empty path == identity
}
int used_geoms = 0;
for (int i = 0; i < coarse_elements.Size(); i++)
{
int geom = elements[coarse_elements[i]].geom;
TraverseRefinements(coarse_elements[i], i, ref_path, path_map[geom]);
used_geoms |= (1 << geom);
}
for (int g = 0; g < Geometry::NumGeom; g++)
{
if (used_geoms & (1 << g))
{
Geometry::Type geom = Geometry::Type(g);
const PointMatrix &identity = GetGeomIdentity(geom);
transforms.point_matrices[g]
.SetSize(Dim, identity.np, static_cast<int>(path_map[g].size()));
// calculate the point matrices
RefPathMap::iterator it;
for (it = path_map[g].begin(); it != path_map[g].end(); ++it)
{
GetPointMatrix(geom, it->first.c_str(),
transforms.point_matrices[g](it->second-1));
}
}
}
}
return transforms;
}
const CoarseFineTransformations& NCMesh::GetDerefinementTransforms() const
{
MFEM_VERIFY(transforms.embeddings.Size() || !leaf_elements.Size(),
"GetDerefinementTransforms() must be preceded by Derefine().");
if (!transforms.IsInitialized())
{
std::map<int, int> mat_no[Geometry::NumGeom];
for (int g = 0; g < Geometry::NumGeom; g++)
{
mat_no[g][0] = 1; // 0 == identity
}
// assign numbers to the different matrices used
for (int i = 0; i < transforms.embeddings.Size(); i++)
{
Embedding &emb = transforms.embeddings[i];
int code = emb.matrix; // see SetDerefMatrixCodes()
if (code)
{
int &matrix = mat_no[emb.geom][code];
if (!matrix) { matrix = static_cast<int>(mat_no[emb.geom].size()); }
emb.matrix = matrix - 1;
}
}
for (int g = 0; g < Geometry::NumGeom; g++)
{
if (Geoms & (1 << g))
{
Geometry::Type geom = Geometry::Type(g);
const PointMatrix &identity = GetGeomIdentity(geom);
transforms.point_matrices[geom]
.SetSize(Dim, identity.np, static_cast<int>(mat_no[geom].size()));
// calculate point matrices
for (auto it = mat_no[geom].begin(); it != mat_no[geom].end(); ++it)
{
char path[3] = { 0, 0, 0 };
int code = it->first;
if (code)
{
path[0] = code >> 4; // ref_type (see SetDerefMatrixCodes())
path[1] = code & 0xf; // child
}
GetPointMatrix(geom, path,
transforms.point_matrices[geom](it->second-1));
}
}
}
}
return transforms;
}
void CoarseFineTransformations::MakeCoarseToFineTable(Table &coarse_to_fine,
bool want_ghosts) const
{
Array<Connection> conn;
conn.Reserve(embeddings.Size());
int max_parent = -1;
for (int i = 0; i < embeddings.Size(); i++)
{
const Embedding &emb = embeddings[i];
if ((emb.parent >= 0) &&
(!emb.ghost || want_ghosts))
{
conn.Append(Connection(emb.parent, i));
max_parent = std::max(emb.parent, max_parent);
}
}
conn.Sort(); // NOTE: unique is not necessary
coarse_to_fine.MakeFromList(max_parent+1, conn);
}
void NCMesh::ClearTransforms()
{
coarse_elements.DeleteAll();
transforms.Clear();
}
void CoarseFineTransformations::Clear()
{
for (int i = 0; i < Geometry::NumGeom; i++)
{
point_matrices[i].SetSize(0, 0, 0);
}
embeddings.DeleteAll();
}
bool CoarseFineTransformations::IsInitialized() const
{
// return true if point matrices are present for any geometry
for (int i = 0; i < Geometry::NumGeom; i++)
{
if (point_matrices[i].SizeK()) { return true; }
}
return false;
}
void Swap(CoarseFineTransformations &a, CoarseFineTransformations &b)
{
for (int g = 0; g < Geometry::NumGeom; ++g)
{
a.point_matrices[g].Swap(b.point_matrices[g]);
}
Swap(a.embeddings, b.embeddings);
}
//// SFC Ordering //////////////////////////////////////////////////////////////
static int sgn(int x)
{
return (x < 0) ? -1 : (x > 0) ? 1 : 0;
}
static void HilbertSfc2D(int x, int y, int ax, int ay, int bx, int by,
Array<int> &coords)
{
int w = std::abs(ax + ay);
int h = std::abs(bx + by);
int dax = sgn(ax), day = sgn(ay); // unit major direction ("right")
int dbx = sgn(bx), dby = sgn(by); // unit orthogonal direction ("up")
if (h == 1) // trivial row fill
{
for (int i = 0; i < w; i++, x += dax, y += day)
{
coords.Append(x);
coords.Append(y);
}
return;
}
if (w == 1) // trivial column fill
{
for (int i = 0; i < h; i++, x += dbx, y += dby)
{
coords.Append(x);
coords.Append(y);
}
return;
}
int ax2 = ax/2, ay2 = ay/2;
int bx2 = bx/2, by2 = by/2;
int w2 = std::abs(ax2 + ay2);
int h2 = std::abs(bx2 + by2);
if (2*w > 3*h) // long case: split in two parts only
{
if ((w2 & 0x1) && (w > 2))
{
ax2 += dax, ay2 += day; // prefer even steps
}
HilbertSfc2D(x, y, ax2, ay2, bx, by, coords);
HilbertSfc2D(x+ax2, y+ay2, ax-ax2, ay-ay2, bx, by, coords);
}
else // standard case: one step up, one long horizontal step, one step down
{
if ((h2 & 0x1) && (h > 2))
{
bx2 += dbx, by2 += dby; // prefer even steps
}
HilbertSfc2D(x, y, bx2, by2, ax2, ay2, coords);
HilbertSfc2D(x+bx2, y+by2, ax, ay, bx-bx2, by-by2, coords);
HilbertSfc2D(x+(ax-dax)+(bx2-dbx), y+(ay-day)+(by2-dby),
-bx2, -by2, -(ax-ax2), -(ay-ay2), coords);
}
}
static void HilbertSfc3D(int x, int y, int z,
int ax, int ay, int az,
int bx, int by, int bz,
int cx, int cy, int cz,
Array<int> &coords)
{
int w = std::abs(ax + ay + az);
int h = std::abs(bx + by + bz);
int d = std::abs(cx + cy + cz);
int dax = sgn(ax), day = sgn(ay), daz = sgn(az); // unit major dir ("right")
int dbx = sgn(bx), dby = sgn(by), dbz = sgn(bz); // unit ortho dir ("forward")
int dcx = sgn(cx), dcy = sgn(cy), dcz = sgn(cz); // unit ortho dir ("up")
// trivial row/column fills
if (h == 1 && d == 1)
{
for (int i = 0; i < w; i++, x += dax, y += day, z += daz)
{
coords.Append(x);
coords.Append(y);
coords.Append(z);
}
return;
}
if (w == 1 && d == 1)
{
for (int i = 0; i < h; i++, x += dbx, y += dby, z += dbz)
{
coords.Append(x);
coords.Append(y);
coords.Append(z);
}
return;
}
if (w == 1 && h == 1)
{
for (int i = 0; i < d; i++, x += dcx, y += dcy, z += dcz)
{
coords.Append(x);
coords.Append(y);
coords.Append(z);
}
return;
}
int ax2 = ax/2, ay2 = ay/2, az2 = az/2;
int bx2 = bx/2, by2 = by/2, bz2 = bz/2;
int cx2 = cx/2, cy2 = cy/2, cz2 = cz/2;
int w2 = std::abs(ax2 + ay2 + az2);
int h2 = std::abs(bx2 + by2 + bz2);
int d2 = std::abs(cx2 + cy2 + cz2);
// prefer even steps
if ((w2 & 0x1) && (w > 2))
{
ax2 += dax, ay2 += day, az2 += daz;
}
if ((h2 & 0x1) && (h > 2))
{
bx2 += dbx, by2 += dby, bz2 += dbz;
}
if ((d2 & 0x1) && (d > 2))
{
cx2 += dcx, cy2 += dcy, cz2 += dcz;
}
// wide case, split in w only
if ((2*w > 3*h) && (2*w > 3*d))
{
HilbertSfc3D(x, y, z,
ax2, ay2, az2,
bx, by, bz,
cx, cy, cz, coords);
HilbertSfc3D(x+ax2, y+ay2, z+az2,
ax-ax2, ay-ay2, az-az2,
bx, by, bz,
cx, cy, cz, coords);
}
// do not split in d
else if (3*h > 4*d)
{
HilbertSfc3D(x, y, z,
bx2, by2, bz2,
cx, cy, cz,
ax2, ay2, az2, coords);
HilbertSfc3D(x+bx2, y+by2, z+bz2,
ax, ay, az,
bx-bx2, by-by2, bz-bz2,
cx, cy, cz, coords);
HilbertSfc3D(x+(ax-dax)+(bx2-dbx),
y+(ay-day)+(by2-dby),
z+(az-daz)+(bz2-dbz),
-bx2, -by2, -bz2,
cx, cy, cz,
-(ax-ax2), -(ay-ay2), -(az-az2), coords);
}
// do not split in h
else if (3*d > 4*h)
{
HilbertSfc3D(x, y, z,
cx2, cy2, cz2,
ax2, ay2, az2,
bx, by, bz, coords);
HilbertSfc3D(x+cx2, y+cy2, z+cz2,
ax, ay, az,
bx, by, bz,
cx-cx2, cy-cy2, cz-cz2, coords);
HilbertSfc3D(x+(ax-dax)+(cx2-dcx),
y+(ay-day)+(cy2-dcy),
z+(az-daz)+(cz2-dcz),
-cx2, -cy2, -cz2,
-(ax-ax2), -(ay-ay2), -(az-az2),
bx, by, bz, coords);
}
// regular case, split in all w/h/d
else
{
HilbertSfc3D(x, y, z,
bx2, by2, bz2,
cx2, cy2, cz2,
ax2, ay2, az2, coords);
HilbertSfc3D(x+bx2, y+by2, z+bz2,
cx, cy, cz,
ax2, ay2, az2,
bx-bx2, by-by2, bz-bz2, coords);
HilbertSfc3D(x+(bx2-dbx)+(cx-dcx),
y+(by2-dby)+(cy-dcy),
z+(bz2-dbz)+(cz-dcz),
ax, ay, az,
-bx2, -by2, -bz2,
-(cx-cx2), -(cy-cy2), -(cz-cz2), coords);
HilbertSfc3D(x+(ax-dax)+bx2+(cx-dcx),
y+(ay-day)+by2+(cy-dcy),
z+(az-daz)+bz2+(cz-dcz),
-cx, -cy, -cz,
-(ax-ax2), -(ay-ay2), -(az-az2),
bx-bx2, by-by2, bz-bz2, coords);
HilbertSfc3D(x+(ax-dax)+(bx2-dbx),
y+(ay-day)+(by2-dby),
z+(az-daz)+(bz2-dbz),
-bx2, -by2, -bz2,
cx2, cy2, cz2,
-(ax-ax2), -(ay-ay2), -(az-az2), coords);
}
}
void NCMesh::GridSfcOrdering2D(int width, int height, Array<int> &coords)
{
coords.SetSize(0);
coords.Reserve(2*width*height);
if (width >= height)
{
HilbertSfc2D(0, 0, width, 0, 0, height, coords);
}
else
{
HilbertSfc2D(0, 0, 0, height, width, 0, coords);
}
}
void NCMesh::GridSfcOrdering3D(int width, int height, int depth,
Array<int> &coords)
{
coords.SetSize(0);
coords.Reserve(3*width*height*depth);
if (width >= height && width >= depth)
{
HilbertSfc3D(0, 0, 0,
width, 0, 0,
0, height, 0,
0, 0, depth, coords);
}
else if (height >= width && height >= depth)
{
HilbertSfc3D(0, 0, 0,
0, height, 0,
width, 0, 0,
0, 0, depth, coords);
}
else // depth >= width && depth >= height
{
HilbertSfc3D(0, 0, 0,
0, 0, depth,
width, 0, 0,
0, height, 0, coords);
}
}
//// Utility ///////////////////////////////////////////////////////////////////
void NCMesh::GetEdgeVertices(const MeshId &edge_id, int vert_index[2],
bool oriented) const
{
const Element &el = elements[edge_id.element];
const GeomInfo& gi = GI[el.Geom()];
const int* ev = gi.edges[(int) edge_id.local];
int n0 = el.node[ev[0]], n1 = el.node[ev[1]];
if (n0 > n1) { std::swap(n0, n1); }
vert_index[0] = nodes[n0].vert_index;
vert_index[1] = nodes[n1].vert_index;
if (oriented && vert_index[0] > vert_index[1])
{
std::swap(vert_index[0], vert_index[1]);
}
}
int NCMesh::GetEdgeNCOrientation(const NCMesh::MeshId &edge_id) const
{
const Element &el = elements[edge_id.element];
const GeomInfo& gi = GI[el.Geom()];
const int* ev = gi.edges[(int) edge_id.local];
int v0 = nodes[el.node[ev[0]]].vert_index;
int v1 = nodes[el.node[ev[1]]].vert_index;
return ((v0 < v1 && ev[0] > ev[1]) || (v0 > v1 && ev[0] < ev[1])) ? -1 : 1;
}
int NCMesh::GetFaceVerticesEdges(const MeshId &face_id,
int vert_index[4], int edge_index[4],
int edge_orientation[4]) const
{
MFEM_ASSERT(Dim >= 3, "");
const Element &el = elements[face_id.element];
const GeomInfo& gi = GI[el.Geom()];
const int *fv = gi.faces[(int) face_id.local];
const int nfv = gi.nfv[(int) face_id.local];
vert_index[3] = edge_index[3] = -1;
edge_orientation[3] = 0;
for (int i = 0; i < nfv; i++)
{
vert_index[i] = nodes[el.node[fv[i]]].vert_index;
}
for (int i = 0; i < nfv; i++)
{
int j = i+1;
if (j >= nfv) { j = 0; }
int n1 = el.node[fv[i]];
int n2 = el.node[fv[j]];
const Node* en = nodes.Find(n1, n2);
MFEM_ASSERT(en != NULL, "edge not found.");
edge_index[i] = en->edge_index;
edge_orientation[i] = (vert_index[i] < vert_index[j]) ? 1 : -1;
}
return nfv;
}
int NCMesh::GetEdgeMaster(int node) const
{
MFEM_ASSERT(node >= 0, "edge node not found.");
const Node &nd = nodes[node];
int p1 = nd.p1, p2 = nd.p2;
MFEM_ASSERT(p1 != p2, "invalid edge node.");
const Node &n1 = nodes[p1], &n2 = nodes[p2];
int n1p1 = n1.p1, n1p2 = n1.p2;
int n2p1 = n2.p1, n2p2 = n2.p2;
if ((n2p1 != n2p2) && (p1 == n2p1 || p1 == n2p2))
{
// n1 is parent of n2: (n1)--(nd)--(n2)------(*)
if (n2.HasEdge()) { return p2; }
else { return GetEdgeMaster(p2); }
}
if ((n1p1 != n1p2) && (p2 == n1p1 || p2 == n1p2))
{
// n2 is parent of n1: (n2)--(nd)--(n1)------(*)
if (n1.HasEdge()) { return p1; }
else { return GetEdgeMaster(p1); }
}
return -1;
}
int NCMesh::GetEdgeMaster(int v1, int v2) const
{
int node = nodes.FindId(vertex_nodeId[v1], vertex_nodeId[v2]);
MFEM_ASSERT(node >= 0 && nodes[node].HasEdge(), "(v1, v2) is not an edge.");
int master = GetEdgeMaster(node);
return (master >= 0) ? nodes[master].edge_index : -1;
}
int NCMesh::GetElementDepth(int i) const
{
int elem = leaf_elements[i];
int depth = 0, parent;
while ((parent = elements[elem].parent) != -1)
{
elem = parent;
depth++;
}
return depth;
}
int NCMesh::GetElementSizeReduction(int i) const
{
int elem = leaf_elements[i];
int parent, reduction = 1;
while ((parent = elements[elem].parent) != -1)
{
if (elements[parent].ref_type & 1) { reduction *= 2; }
if (elements[parent].ref_type & 2) { reduction *= 2; }
if (elements[parent].ref_type & 4) { reduction *= 2; }
elem = parent;
}
return reduction;
}
void NCMesh::GetElementFacesAttributes(int leaf_elem,
Array<int> &face_indices,
Array<int> &face_attribs) const
{
const Element &el = elements[leaf_elements[leaf_elem]];
const GeomInfo& gi = GI[el.Geom()];
face_indices.SetSize(gi.nf);
face_attribs.SetSize(gi.nf);
for (int i = 0; i < gi.nf; i++)
{
const int* fv = gi.faces[i];
const Face *face = faces.Find(el.node[fv[0]], el.node[fv[1]],
el.node[fv[2]], el.node[fv[3]]);
MFEM_ASSERT(face, "face not found");
face_indices[i] = face->index;
face_attribs[i] = face->attribute;
}
}
void NCMesh::FindFaceNodes(int face, int node[4]) const
{
auto tmp = FindFaceNodes(face);
std::copy(tmp.begin(), tmp.end(), node);
}
std::array<int, 4> NCMesh::FindFaceNodes(int face) const
{
return FindFaceNodes(faces[face]);
}
std::array<int, 4> NCMesh::FindFaceNodes(const Face &fa) const
{
// Obtain face nodes from one of its elements (note that face->p1, p2, p3
// cannot be used directly since they are not in order and p4 is missing).
int elem = fa.elem[0];
if (elem < 0) { elem = fa.elem[1]; }
MFEM_ASSERT(elem >= 0, "Face has no elements?");
const Element &el = elements[elem];
int f = find_local_face(el.Geom(),
find_node(el, fa.p1),
find_node(el, fa.p2),
find_node(el, fa.p3));
const int* fv = GI[el.Geom()].faces[f];
std::array<int, 4> node;
for (int i = 0; i < 4; i++)
{
node[i] = el.node[fv[i]];
}
return node;
}
void NCMesh::GetBoundaryClosure(const Array<int> &bdr_attr_is_ess,
Array<int> &bdr_vertices, Array<int> &bdr_edges,
Array<int> &bdr_faces)
{
bdr_vertices.SetSize(0);
bdr_edges.SetSize(0);
bdr_faces.SetSize(0);
if (Dim == 3)
{
GetFaceList(); // make sure 'boundary_faces' is up to date
for (int f : boundary_faces)
{
if (bdr_attr_is_ess[faces[f].attribute - 1])
{
auto node = FindFaceNodes(f);
int nfv = (node[3] < 0) ? 3 : 4;
for (int j = 0; j < nfv; j++)
{
bdr_vertices.Append(nodes[node[j]].vert_index);
int enode = nodes.FindId(node[j], node[(j+1) % nfv]);
MFEM_ASSERT(enode >= 0 && nodes[enode].HasEdge(), "Edge not found.");
bdr_edges.Append(nodes[enode].edge_index);
while ((enode = GetEdgeMaster(enode)) >= 0)
{
// append master edges that may not be accessible from any
// boundary element, this happens in 3D in re-entrant corners
bdr_edges.Append(nodes[enode].edge_index);
}
}
// If the face is a slave face, collect its non-ghost master face
const Face &face = faces[f];
const auto id_and_type = GetFaceList().GetMeshIdAndType(face.index);
if (id_and_type.type == NCList::MeshIdType::SLAVE)
{
// A slave face must mark its masters
const auto &slave_face_id = static_cast<const Slave&>(*id_and_type.id);
bdr_faces.Append(slave_face_id.master);
}
}
}
}
else if (Dim == 2)
{
GetFaceList();
GetEdgeList(); // make sure 'boundary_faces' is up to date
for (int f : boundary_faces)
{
Face &face = faces[f];
if (bdr_attr_is_ess[face.attribute - 1])
{
bdr_vertices.Append(nodes[face.p1].vert_index);
bdr_vertices.Append(nodes[face.p3].vert_index);
}
const auto id_and_type = GetEdgeList().GetMeshIdAndType(face.index);
if (id_and_type.type == NCList::MeshIdType::SLAVE)
{
// A slave face must mark its masters
const auto &slave_edge_id = static_cast<const Slave&>(*id_and_type.id);
bdr_edges.Append(slave_edge_id.master);
}
}
}
// Filter, sort and unique an array, so it contains only local unique values.
auto FilterSortUnique = [](Array<int> &v, int N)
{
// Perform the O(N) filter before the O(NlogN) sort. begin -> it is only
// entries < N.
auto it = std::remove_if(v.begin(), v.end(), [N](int i) { return i >= N; });
std::sort(v.begin(), it);
v.SetSize(std::distance(v.begin(), std::unique(v.begin(), it)));
};
FilterSortUnique(bdr_vertices, NVertices);
FilterSortUnique(bdr_edges, NEdges);
FilterSortUnique(bdr_faces, NFaces);
}
int NCMesh::EdgeSplitLevel(int vn1, int vn2) const
{
int mid = nodes.FindId(vn1, vn2);
if (mid < 0 || !nodes[mid].HasVertex()) { return 0; }
return 1 + std::max(EdgeSplitLevel(vn1, mid), EdgeSplitLevel(mid, vn2));
}
int NCMesh::TriFaceSplitLevel(int vn1, int vn2, int vn3) const
{
int mid[3];
if (TriFaceSplit(vn1, vn2, vn3, mid) &&
faces.FindId(vn1, vn2, vn3) < 0)
{
return 1 + max(TriFaceSplitLevel(vn1, mid[0], mid[2]),
TriFaceSplitLevel(mid[0], vn2, mid[1]),
TriFaceSplitLevel(mid[2], mid[1], vn3),
TriFaceSplitLevel(mid[0], mid[1], mid[2]));
}
return 0; // not split
}
void NCMesh::QuadFaceSplitLevel(int vn1, int vn2, int vn3, int vn4,
int& h_level, int& v_level) const
{
int hl1, hl2, vl1, vl2;
int mid[5];
real_t scale;
switch (QuadFaceSplitType(vn1, vn2, vn3, vn4, scale, mid))
{
case 0: // not split
h_level = v_level = 0;
break;
case 1: // vertical
QuadFaceSplitLevel(vn1, mid[0], mid[2], vn4, hl1, vl1);
QuadFaceSplitLevel(mid[0], vn2, vn3, mid[2], hl2, vl2);
h_level = std::max(hl1, hl2);
v_level = std::max(vl1, vl2) + 1;
break;
default: // horizontal
QuadFaceSplitLevel(vn1, vn2, mid[1], mid[3], hl1, vl1);
QuadFaceSplitLevel(mid[3], mid[1], vn3, vn4, hl2, vl2);
h_level = std::max(hl1, hl2) + 1;
v_level = std::max(vl1, vl2);
}
}
int NCMesh::QuadFaceSplitLevel(int vn1, int vn2, int vn3, int vn4) const
{
int h_level, v_level;
QuadFaceSplitLevel(vn1, vn2, vn3, vn4, h_level, v_level);
return h_level + v_level;
}
void NCMesh::CountSplits(int elem, int splits[3]) const
{
const Element &el = elements[elem];
const int* node = el.node;
GeomInfo& gi = GI[el.Geom()];
int elevel[MaxElemEdges];
for (int i = 0; i < gi.ne; i++)
{
const int* ev = gi.edges[i];
elevel[i] = EdgeSplitLevel(node[ev[0]], node[ev[1]]);
}
int flevel[MaxElemFaces][2];
if (Dim >= 3)
{
for (int i = 0; i < gi.nf; i++)
{
const int* fv = gi.faces[i];
if (gi.nfv[i] == 4)
{
QuadFaceSplitLevel(node[fv[0]], node[fv[1]],
node[fv[2]], node[fv[3]],
flevel[i][1], flevel[i][0]);
}
else
{
flevel[i][1] = 0;
flevel[i][0] =
TriFaceSplitLevel(node[fv[0]], node[fv[1]], node[fv[2]]);
}
}
}
if (el.Geom() == Geometry::CUBE)
{
splits[0] = max(flevel[0][0], flevel[1][0], flevel[3][0], flevel[5][0],
elevel[0], elevel[2], elevel[4], elevel[6]);
splits[1] = max(flevel[0][1], flevel[2][0], flevel[4][0], flevel[5][1],
elevel[1], elevel[3], elevel[5], elevel[7]);
splits[2] = max(flevel[1][1], flevel[2][1], flevel[3][1], flevel[4][1],
elevel[8], elevel[9], elevel[10], elevel[11]);
}
else if (el.Geom() == Geometry::PRISM)
{
splits[0] = splits[1] = max(flevel[0][0], flevel[1][0], 0,
flevel[2][0], flevel[3][0], flevel[4][0],
elevel[0], elevel[1], elevel[2],
elevel[3], elevel[4], elevel[5]);
splits[2] = max(flevel[2][1], flevel[3][1], flevel[4][1],
elevel[6], elevel[7], elevel[8]);
}
else if (el.Geom() == Geometry::PYRAMID)
{
splits[0] = max(flevel[0][0], flevel[1][0], 0,
flevel[2][0], flevel[3][0], flevel[4][0],
elevel[0], elevel[1], elevel[2],
elevel[3], elevel[4], elevel[5],
elevel[6], elevel[7]);
splits[1] = splits[0];
splits[2] = splits[0];
}
else if (el.Geom() == Geometry::TETRAHEDRON)
{
splits[0] = max(flevel[0][0], flevel[1][0], flevel[2][0], flevel[3][0],
elevel[0], elevel[1], elevel[2], elevel[3], elevel[4], elevel[5]);
splits[1] = splits[0];
splits[2] = splits[0];
}
else if (el.Geom() == Geometry::SQUARE)
{
splits[0] = max(elevel[0], elevel[2]);
splits[1] = max(elevel[1], elevel[3]);
}
else if (el.Geom() == Geometry::TRIANGLE)
{
splits[0] = max(elevel[0], elevel[1], elevel[2]);
splits[1] = splits[0];
}
else
{
MFEM_ABORT("Unsupported element geometry.");
}
}
void NCMesh::GetLimitRefinements(Array<Refinement> &refinements, int max_level)
{
for (int i = 0; i < leaf_elements.Size(); i++)
{
if (IsGhost(elements[leaf_elements[i]])) { break; } // TODO: NElements
int splits[3];
CountSplits(leaf_elements[i], splits);
char ref_type = 0;
for (int k = 0; k < Dim; k++)
{
if (splits[k] > max_level)
{
ref_type |= (1 << k);
}
}
if (ref_type)
{
if (Iso)
{
// iso meshes should only be modified by iso refinements
ref_type = 7;
}
refinements.Append(Refinement(i, ref_type));
}
}
}
void NCMesh::LimitNCLevel(int max_nc_level)
{
MFEM_VERIFY(max_nc_level >= 1, "'max_nc_level' must be 1 or greater.");
while (1)
{
Array<Refinement> refinements;
GetLimitRefinements(refinements, max_nc_level);
if (!refinements.Size()) { break; }
Refine(refinements);
}
}
//// I/O ///////////////////////////////////////////////////////////////////////
int NCMesh::PrintVertexParents(std::ostream *os) const
{
if (!os)
{
// count vertex nodes with parents
int nv = 0;
for (auto node = nodes.cbegin(); node != nodes.cend(); ++node)
{
if (node->HasVertex() && node->p1 != node->p2) { nv++; }
}
return nv;
}
else
{
// print the relations
bool uniform_scaling = true; // Check whether all nodes have scale 0.5
for (auto node = nodes.cbegin(); node != nodes.cend(); ++node)
{
if (node->HasVertex() && node->p1 != node->p2)
{
MFEM_ASSERT(nodes[node->p1].HasVertex(), "");
MFEM_ASSERT(nodes[node->p2].HasVertex(), "");
(*os) << node.index() << " " << node->p1 << " " << node->p2;
if (node->GetScale() != 0.5) { uniform_scaling = false; }
if (using_scaling) { (*os) << " " << node->GetScale(); }
(*os) << "\n";
}
}
MFEM_VERIFY(using_scaling || uniform_scaling, "NCMesh has nonuniform "
"scaling. Call Mesh::SetScaledNCMesh first.");
return 0;
}
}
void NCMesh::LoadVertexParents(std::istream &input)
{
int nv;
input >> nv;
while (nv--)
{
int id, p1, p2;
real_t s{0.5};
input >> id >> p1 >> p2;
if (using_scaling) { input >> s; }
MFEM_VERIFY(input, "problem reading vertex parents.");
MFEM_VERIFY(nodes.IdExists(id), "vertex " << id << " not found.");
MFEM_VERIFY(nodes.IdExists(p1), "parent " << p1 << " not found.");
MFEM_VERIFY(nodes.IdExists(p2), "parent " << p2 << " not found.");
int check = nodes.FindId(p1, p2);
MFEM_VERIFY(check < 0, "parents (" << p1 << ", " << p2 << ") already "
"assigned to node " << check);
// assign new parents for the node
nodes.Reparent(id, p1, p2);
nodes[id].SetScale(s);
}
}
void NCMesh::LoadVertexToKnotSpan(std::istream &input)
{
if (Dim == 2) { LoadVertexToKnotSpan2D(input); }
else { LoadVertexToKnotSpan3D(input); }
}
void NCMesh::LoadVertexToKnotSpan2D(std::istream &input)
{
int nv;
input >> nv;
MFEM_VERIFY(0 <= nv, "Invalid vertex-to-knot data");
vertex_to_knotspan.SetSize(2, nv);
for (int i=0; i<nv; ++i)
{
int id, ks;
std::array<int, 2> pv;
input >> id >> ks >> pv[0] >> pv[1];
const bool idsExist = nodes.IdExists(id) && nodes.IdExists(pv[0])
&& nodes.IdExists(pv[1]);
MFEM_VERIFY(idsExist && 0 < ks, "Invalid index");
vertex_to_knotspan.SetVertex2D(i, id, ks, pv);
}
}
void NCMesh::LoadVertexToKnotSpan3D(std::istream &input)
{
int nv;
input >> nv;
MFEM_VERIFY(0 <= nv, "Invalid vertex-to-knot data");
vertex_to_knotspan.SetSize(3, nv);
for (int i=0; i<nv; ++i)
{
int id;
std::array<int, 2> ks;
std::array<int, 4> pv; // Parent vertex indices
input >> id >> ks[0] >> ks[1] >> pv[0] >> pv[1] >> pv[2] >> pv[3];
#ifdef MFEM_DEBUG
bool idsExist = nodes.IdExists(id);
for (int j=0; j<4; ++j)
{
idsExist = idsExist && nodes.IdExists(pv[j]);
}
const bool validKnotIds = (0 <= ks[0] || 0 <= ks[1]) &&
(0 < ks[0] || 0 < ks[1]);
MFEM_ASSERT(idsExist && validKnotIds, "Invalid index");
#endif
vertex_to_knotspan.SetVertex3D(i, id, ks, pv);
}
}
int NCMesh::PrintBoundary(std::ostream *os) const
{
static const int nfv2geom[5] =
{
Geometry::INVALID, Geometry::POINT, Geometry::SEGMENT,
Geometry::TRIANGLE, Geometry::SQUARE
};
int deg = (Dim == 2) ? 2 : 1; // for degenerate faces in 2D
int count = 0;
for (int i = 0; i < elements.Size(); i++)
{
const Element &el = elements[i];
if (!el.IsLeaf()) { continue; }
GeomInfo& gi = GI[el.Geom()];
for (int k = 0; k < gi.nf; k++)
{
const int* fv = gi.faces[k];
const int nfv = gi.nfv[k];
const Face* face = faces.Find(el.node[fv[0]], el.node[fv[1]],
el.node[fv[2]], el.node[fv[3]]);
MFEM_ASSERT(face != NULL, "face not found");
if (face->Boundary())
{
if (!os) { count++; continue; }
(*os) << face->attribute << " " << nfv2geom[nfv];
for (int j = 0; j < nfv; j++)
{
(*os) << " " << el.node[fv[j*deg]];
}
(*os) << "\n";
}
}
}
return count;
}
void NCMesh::LoadBoundary(std::istream &input)
{
int nb, attr, geom;
input >> nb;
for (int i = 0; i < nb; i++)
{
input >> attr >> geom;
int v1, v2, v3, v4;
if (geom == Geometry::SQUARE)
{
input >> v1 >> v2 >> v3 >> v4;
Face* face = faces.Get(v1, v2, v3, v4);
face->attribute = attr;
}
else if (geom == Geometry::TRIANGLE)
{
input >> v1 >> v2 >> v3;
Face* face = faces.Get(v1, v2, v3);
face->attribute = attr;
}
else if (geom == Geometry::SEGMENT)
{
input >> v1 >> v2;
Face* face = faces.Get(v1, v1, v2, v2);
face->attribute = attr;
}
else if (geom == Geometry::POINT)
{
input >> v1;
Face* face = faces.Get(v1, v1, v1, v1);
face->attribute = attr;
}
else
{
MFEM_ABORT("unsupported boundary element geometry: " << geom);
}
}
}
void NCMesh::PrintCoordinates(std::ostream &os) const
{
int nv = coordinates.Size()/3;
os << nv << "\n";
if (!nv) { return; }
os << spaceDim << "\n";
for (int i = 0; i < nv; i++)
{
os << coordinates[3*i];
for (int j = 1; j < spaceDim; j++)
{
os << " " << coordinates[3*i + j];
}
os << "\n";
}
}
void NCMesh::LoadCoordinates(std::istream &input)
{
int nv;
input >> nv;
if (!nv) { return; }
input >> spaceDim;
coordinates.SetSize(nv * 3);
coordinates = 0.0;
for (int i = 0; i < nv; i++)
{
for (int j = 0; j < spaceDim; j++)
{
input >> coordinates[3*i + j];
MFEM_VERIFY(input.good(), "unexpected EOF");
}
}
}
bool NCMesh::ZeroRootStates() const
{
for (int i = 0; i < root_state.Size(); i++)
{
if (root_state[i]) { return false; }
}
return true;
}
void NCMesh::Print(std::ostream &os, const std::string &comments,
bool nurbs) const
{
if (nurbs)
{
os << "MFEM NURBS NC-patch mesh v1.0\n\n";
}
else if (using_scaling)
{
os << "MFEM NC mesh v1.1\n\n";
}
else
{
os << "MFEM NC mesh v1.0\n\n";
}
if (!comments.empty()) { os << comments << "\n\n"; }
os <<
"# NCMesh supported geometry types:\n"
"# SEGMENT = 1\n"
"# TRIANGLE = 2\n"
"# SQUARE = 3\n"
"# TETRAHEDRON = 4\n"
"# CUBE = 5\n"
"# PRISM = 6\n"
"# PYRAMID = 7\n";
os << "\ndimension\n" << Dim << "\n";
#ifndef MFEM_USE_MPI
if (MyRank != 0) // don't print this section in serial: default rank is 0
#endif
{
os << "\nrank\n" << MyRank << "\n";
}
os << "\n# rank attr geom ref_type nodes/children";
os << "\nelements\n" << elements.Size() << "\n";
for (int i = 0; i < elements.Size(); i++)
{
const Element &el = elements[i];
os << el.rank << " " << el.attribute << " ";
if (el.parent == -2) { os << "-1\n"; continue; } // unused element
os << int(el.geom) << " " << int(el.ref_type);
for (int j = 0; j < MaxElemNodes && el.node[j] >= 0; j++)
{
os << " " << el.node[j];
}
os << "\n";
}
int nb = PrintBoundary(NULL);
if (nb)
{
os << "\n# attr geom nodes";
os << "\nboundary\n" << nb << "\n";
PrintBoundary(&os);
}
int nvp = PrintVertexParents(NULL);
if (nvp)
{
os << "\n# vert_id p1 p2";
os << "\nvertex_parents\n" << nvp << "\n";
PrintVertexParents(&os);
}
if (!ZeroRootStates()) // root_state section is optional
{
os << "\n# root element orientation";
os << "\nroot_state\n" << root_state.Size() << "\n";
for (int i = 0; i < root_state.Size(); i++)
{
os << root_state[i] << "\n";
}
}
if (nurbs && vertex_to_knotspan.Size() > 0)
{
os << "\nvertex_to_knotspan\n";
vertex_to_knotspan.Print(os);
}
if (coordinates.Size())
{
os << "\n# top-level node coordinates";
os << "\ncoordinates\n";
PrintCoordinates(os);
}
else
{
// 'nodes' will be printed one level up by Mesh::Printer()
}
}
void NCMesh::InitRootElements()
{
// initialize Element::parent
for (int i = 0; i < elements.Size(); i++)
{
Element &el = elements[i];
if (el.ref_type)
{
for (int j = 0; j < MaxElemChildren && el.child[j] >= 0; j++)
{
int child = el.child[j];
MFEM_VERIFY(child < elements.Size(), "invalid mesh file: "
"element " << i << " references invalid child " << child);
elements[child].parent = i;
}
}
}
// count the root elements
int nroots = 0;
for (const auto &e : elements)
if (e.parent == -1)
{
++nroots;
}
MFEM_VERIFY(nroots > 0 ||
elements.Size() == 0,
"invalid mesh file: no root elements in non-empty mesh found.");
// check that only the first 'nroot' elements are roots (have no parent)
for (int i = nroots; i < elements.Size(); i++)
{
MFEM_VERIFY(elements[i].parent != -1,
"invalid mesh file: only the first M elements can be roots. "
"Found element " << i << " with no parent.");
}
// default root state is zero
root_state.SetSize(nroots);
root_state = 0;
}
int NCMesh::CountTopLevelNodes() const
{
int ntop = 0;
for (auto node = nodes.cbegin(); node != nodes.cend(); ++node)
{
if (node->p1 == node->p2) { ntop = node.index() + 1; }
}
return ntop;
}
NCMesh::NCMesh(std::istream &input, int version, int &curved, int &is_nc)
: spaceDim(0), MyRank(0), Iso(true), Legacy(false),
using_scaling(version == 11)
{
is_nc = 1;
if (version == 1) // old MFEM mesh v1.1 format
{
LoadLegacyFormat(input, curved, is_nc);
Legacy = true;
return;
}
MFEM_ASSERT(version == 10 || version == 11, "");
std::string ident;
int count;
// Skip the version string
skip_comment_lines(input, 'M');
// load dimension
skip_comment_lines(input, '#');
input >> ident;
MFEM_VERIFY(ident == "dimension", "Invalid mesh file: " << ident);
input >> Dim;
// load rank, if present
skip_comment_lines(input, '#');
input >> ident;
if (ident == "rank")
{
input >> MyRank;
MFEM_VERIFY(MyRank >= 0, "Invalid rank");
skip_comment_lines(input, '#');
input >> ident;
}
// load file SFC version, if present (for future changes to SFC ordering)
if (ident == "sfc_version")
{
int sfc_version; // TODO future: store as class member
input >> sfc_version;
MFEM_VERIFY(sfc_version == 0,
"Unsupported mesh file SFC version (" << sfc_version << "). "
"Please update MFEM.");
skip_comment_lines(input, '#');
input >> ident;
}
// load elements
MFEM_VERIFY(ident == "elements", "Invalid mesh file: " << ident);
input >> count;
for (int i = 0; i < count; i++)
{
int rank, attr, geom, ref_type;
input >> rank >> attr >> geom;
Geometry::Type type = Geometry::Type(geom);
elements.Append(Element(type, attr));
MFEM_ASSERT(elements.Size() == i+1, "");
Element &el = elements[i];
el.rank = rank;
if (geom >= 0)
{
CheckSupportedGeom(type);
GI[geom].InitGeom(type);
input >> ref_type;
MFEM_VERIFY(ref_type >= 0 && ref_type < 8, "");
el.ref_type = ref_type;
if (ref_type) // refined element
{
for (int j = 0; j < ref_type_num_children[ref_type]; j++)
{
input >> el.child[j];
}
if (Dim == 3 && ref_type != 7) { Iso = false; }
}
else // leaf element
{
for (int j = 0; j < GI[geom].nv; j++)
{
int id;
input >> id;
el.node[j] = id;
nodes.Alloc(id, id, id);
// NOTE: nodes that won't get parents assigned will stay hashed
// with p1 == p2 == id (top-level nodes)
}
}
}
else
{
el.parent = -2; // mark as unused
free_element_ids.Append(i);
}
}
InitRootElements();
InitGeomFlags();
skip_comment_lines(input, '#');
input >> ident;
// load boundary
if (ident == "boundary")
{
LoadBoundary(input);
skip_comment_lines(input, '#');
input >> ident;
}
// load vertex hierarchy
if (ident == "vertex_parents")
{
LoadVertexParents(input);
skip_comment_lines(input, '#');
input >> ident;
}
// load map from hanging patch vertices to patch edge knots
if (ident == "vertex_to_knotspan")
{
LoadVertexToKnotSpan(input);
skip_comment_lines(input, '#');
input >> ident;
}
// load root states
if (ident == "root_state")
{
input >> count;
MFEM_VERIFY(count <= root_state.Size(), "Too many root states");
for (int i = 0; i < count; i++)
{
input >> root_state[i];
}
skip_comment_lines(input, '#');
input >> ident;
}
// load coordinates or nodes
if (ident == "coordinates")
{
LoadCoordinates(input);
MFEM_VERIFY(coordinates.Size() >= 3*CountTopLevelNodes(),
"Invalid mesh file: not all top-level nodes are covered by "
"the 'coordinates' section of the mesh file: " << coordinates.Size() << ' ' <<
3*CountTopLevelNodes());
curved = 0;
}
else if (ident == "nodes")
{
coordinates.SetSize(0); // this means the mesh is curved
// prepare to read the nodes
input >> std::ws;
curved = 1;
}
else
{
MFEM_ABORT("Invalid mesh file: either 'coordinates' or "
"'nodes' must be present");
}
// create edge nodes and faces
nodes.UpdateUnused();
for (int i = 0; i < elements.Size(); i++)
{
if (elements[i].IsLeaf())
{
ReferenceElement(i);
RegisterFaces(i);
}
}
Update();
}
void NCMesh::CopyElements(int elem,
const BlockArray<Element> &tmp_elements)
{
Element &el = elements[elem];
if (el.ref_type)
{
for (int i = 0; i < MaxElemChildren && el.child[i] >= 0; i++)
{
int old_id = el.child[i];
// here we know 'free_element_ids' is empty
int new_id = elements.Append(tmp_elements[old_id]);
el.child[i] = new_id;
elements[new_id].parent = elem;
CopyElements(new_id, tmp_elements);
}
}
}
void NCMesh::LoadCoarseElements(std::istream &input)
{
int ne;
input >> ne;
bool iso = true;
// load the coarse elements
while (ne--)
{
int ref_type;
input >> ref_type;
int elem = AddElement(Geometry::INVALID, 0);
Element &el = elements[elem];
el.ref_type = ref_type;
if (Dim == 3 && ref_type != 7) { iso = false; }
// load child IDs and make parent-child links
int nch = ref_type_num_children[ref_type];
for (int i = 0, id; i < nch; i++)
{
input >> id;
MFEM_VERIFY(id >= 0, "");
MFEM_VERIFY(id < elements.Size(),
"coarse element cannot be referenced before it is "
"defined (id=" << id << ").");
Element &child = elements[id];
MFEM_VERIFY(child.parent == -1,
"element " << id << " cannot have two parents.");
el.child[i] = id;
child.parent = elem;
if (!i) // copy geom and attribute from first child
{
el.geom = child.geom;
el.attribute = child.attribute;
}
}
}
// prepare for reordering the elements
BlockArray<Element> tmp_elements;
elements.Swap(tmp_elements);
// copy roots, they need to be at the beginning of 'elements'
int root_count = 0;
for (auto el = tmp_elements.begin(); el != tmp_elements.end(); ++el)
{
if (el->parent == -1)
{
elements.Append(*el); // same as AddElement()
root_count++;
}
}
// copy the rest of the hierarchy
for (int i = 0; i < root_count; i++)
{
CopyElements(i, tmp_elements);
}
// set the Iso flag (must be false if there are 3D aniso refinements)
Iso = iso;
InitRootState(root_count);
}
void NCMesh::LoadLegacyFormat(std::istream &input, int &curved, int &is_nc)
{
MFEM_ASSERT(elements.Size() == 0, "");
MFEM_ASSERT(nodes.Size() == 0, "");
MFEM_ASSERT(free_element_ids.Size() == 0, "");
std::string ident;
int count, attr, geom;
// load dimension
skip_comment_lines(input, '#');
input >> ident;
MFEM_VERIFY(ident == "dimension", "invalid mesh file");
input >> Dim;
// load elements
skip_comment_lines(input, '#');
input >> ident;
MFEM_VERIFY(ident == "elements", "invalid mesh file");
input >> count;
for (int i = 0; i < count; i++)
{
input >> attr >> geom;
Geometry::Type type = Geometry::Type(geom);
CheckSupportedGeom(type);
GI[geom].InitGeom(type);
int eid = AddElement(type, attr);
MFEM_ASSERT(eid == i, "");
Element &el = elements[eid];
for (int j = 0; j < GI[geom].nv; j++)
{
int id;
input >> id;
el.node[j] = id;
nodes.Alloc(id, id, id); // see comment in NCMesh::NCMesh
}
el.index = i; // needed for file leaf order below
}
// load boundary
skip_comment_lines(input, '#');
input >> ident;
MFEM_VERIFY(ident == "boundary", "invalid mesh file");
LoadBoundary(input);
// load vertex hierarchy
skip_comment_lines(input, '#');
input >> ident;
if (ident == "vertex_parents")
{
LoadVertexParents(input);
is_nc = 1;
skip_comment_lines(input, '#');
input >> ident;
}
else
{
// no "vertex_parents" section: this file needs to be treated as a
// conforming mesh for complete backward compatibility with MFEM 4.2
is_nc = 0;
}
// load element hierarchy
if (ident == "coarse_elements")
{
LoadCoarseElements(input);
skip_comment_lines(input, '#');
input >> ident;
}
else
{
// no element hierarchy -> all elements are roots
InitRootState(elements.Size());
}
InitGeomFlags();
// load vertices
MFEM_VERIFY(ident == "vertices", "invalid mesh file");
int nvert;
input >> nvert;
input >> std::ws >> ident;
if (ident != "nodes")
{
spaceDim = atoi(ident.c_str());
coordinates.SetSize(3*nvert);
coordinates = 0.0;
for (int i = 0; i < nvert; i++)
{
for (int j = 0; j < spaceDim; j++)
{
input >> coordinates[3*i + j];
MFEM_VERIFY(input.good(), "unexpected EOF");
}
}
// truncate extra coordinates (legacy vertices section is longer)
int ntop = CountTopLevelNodes();
if (3*ntop < coordinates.Size())
{
coordinates.SetSize(3*ntop);
}
}
else
{
coordinates.SetSize(0);
// prepare to read the nodes
input >> std::ws;
curved = 1;
}
// create edge nodes and faces
nodes.UpdateUnused();
int leaf_count = 0;
for (int i = 0; i < elements.Size(); i++)
{
if (elements[i].IsLeaf())
{
ReferenceElement(i);
RegisterFaces(i);
leaf_count++;
}
}
// v1.1 honors file leaf order on load, prepare legacy 'leaf_elements'
Array<int> file_leaf_elements(leaf_count);
file_leaf_elements = -1;
for (int i = 0; i < elements.Size(); i++)
{
if (elements[i].IsLeaf())
{
file_leaf_elements[elements[i].index] = i;
}
}
MFEM_ASSERT(file_leaf_elements.Min() >= 0, "");
Update();
// force file leaf order
Swap(leaf_elements, file_leaf_elements);
// make sure Mesh::NVertices is equal to "nvert" from the file (in case of
// unused vertices), see also GetMeshComponents
if (nvert > vertex_nodeId.Size())
{
vertex_nodeId.SetSize(nvert, -1);
}
}
void NCMesh::LegacyToNewVertexOrdering(Array<int> &order) const
{
order.SetSize(NVertices);
order = -1;
int count = 0;
for (auto node = nodes.cbegin(); node != nodes.cend(); ++node)
{
if (node->HasVertex())
{
MFEM_ASSERT(node.index() >= 0, "");
MFEM_ASSERT(node.index() < order.Size(), "");
MFEM_ASSERT(order[node.index()] == -1, "");
order[node.index()] = node->vert_index;
count++;
}
}
MFEM_ASSERT(count == order.Size(), "");
MFEM_CONTRACT_VAR(count);
}
////////////////////////////////////////////////////////////////////////////////
void NCMesh::Trim()
{
vertex_list.Clear();
face_list.Clear();
edge_list.Clear();
boundary_faces.DeleteAll();
element_vertex.Clear();
ClearTransforms();
// TODO future: consider trimming unused blocks at the end of 'elements' and
// maybe also of 'nodes' and 'faces'.
}
long NCMesh::NCList::MemoryUsage() const
{
int pm_size = 0;
for (int i = 0; i < Geometry::NumGeom; i++)
{
for (int j = 0; j < point_matrices[i].Size(); i++)
{
pm_size += static_cast<int>(point_matrices[i][j]->MemoryUsage());
}
pm_size += static_cast<int>(point_matrices[i].MemoryUsage());
}
return conforming.MemoryUsage() +
masters.MemoryUsage() +
slaves.MemoryUsage() +
pm_size;
}
long CoarseFineTransformations::MemoryUsage() const
{
long mem = embeddings.MemoryUsage();
for (int i = 0; i < Geometry::NumGeom; i++)
{
mem += point_matrices[i].MemoryUsage();
}
return mem;
}
long NCMesh::MemoryUsage() const
{
return nodes.MemoryUsage() +
faces.MemoryUsage() +
elements.MemoryUsage() +
free_element_ids.MemoryUsage() +
root_state.MemoryUsage() +
coordinates.MemoryUsage() +
leaf_elements.MemoryUsage() +
leaf_sfc_index.MemoryUsage() +
vertex_nodeId.MemoryUsage() +
face_list.MemoryUsage() +
edge_list.MemoryUsage() +
vertex_list.MemoryUsage() +
boundary_faces.MemoryUsage() +
element_vertex.MemoryUsage() +
ref_stack.MemoryUsage() +
derefinements.MemoryUsage() +
transforms.MemoryUsage() +
coarse_elements.MemoryUsage() +
sizeof(*this);
}
int NCMesh::PrintMemoryDetail() const
{
nodes.PrintMemoryDetail(); mfem::out << " nodes\n";
faces.PrintMemoryDetail(); mfem::out << " faces\n";
mfem::out << elements.MemoryUsage() << " elements\n"
<< free_element_ids.MemoryUsage() << " free_element_ids\n"
<< root_state.MemoryUsage() << " root_state\n"
<< coordinates.MemoryUsage() << " top_vertex_pos\n"
<< leaf_elements.MemoryUsage() << " leaf_elements\n"
<< leaf_sfc_index.MemoryUsage() << " leaf_sfc_index\n"
<< vertex_nodeId.MemoryUsage() << " vertex_nodeId\n"
<< face_list.MemoryUsage() << " face_list\n"
<< edge_list.MemoryUsage() << " edge_list\n"
<< vertex_list.MemoryUsage() << " vertex_list\n"
<< boundary_faces.MemoryUsage() << " boundary_faces\n"
<< element_vertex.MemoryUsage() << " element_vertex\n"
<< ref_stack.MemoryUsage() << " ref_stack\n"
<< derefinements.MemoryUsage() << " derefinements\n"
<< transforms.MemoryUsage() << " transforms\n"
<< coarse_elements.MemoryUsage() << " coarse_elements\n"
<< sizeof(*this) << " NCMesh"
<< std::endl;
return elements.Size() - free_element_ids.Size();
}
#ifdef MFEM_DEBUG
void NCMesh::DebugLeafOrder(std::ostream &os) const
{
tmp_vertex = new TmpVertex[nodes.NumIds()];
for (int i = 0; i < leaf_elements.Size(); i++)
{
const Element* elem = &elements[leaf_elements[i]];
for (int j = 0; j < Dim; j++)
{
real_t sum = 0.0;
int count = 0;
for (int k = 0; k < MaxElemNodes; k++)
{
if (elem->node[k] >= 0)
{
sum += CalcVertexPos(elem->node[k])[j];
count++;
}
}
os << sum / count << " ";
}
os << "\n";
}
delete [] tmp_vertex;
}
void NCMesh::DebugDump(std::ostream &os) const
{
// dump nodes
tmp_vertex = new TmpVertex[nodes.NumIds()];
os << nodes.Size() << "\n";
for (auto node = nodes.cbegin(); node != nodes.cend(); ++node)
{
const real_t *pos = CalcVertexPos(node.index());
os << node.index() << " "
<< pos[0] << " " << pos[1] << " " << pos[2] << " "
<< node->p1 << " " << node->p2 << " "
<< node->vert_index << " " << node->edge_index << " "
<< 0 << "\n";
}
delete [] tmp_vertex;
os << "\n";
// dump elements
int nleaves = 0;
for (int i = 0; i < elements.Size(); i++)
{
if (elements[i].IsLeaf()) { nleaves++; }
}
os << nleaves << "\n";
for (int i = 0; i < elements.Size(); i++)
{
const Element &el = elements[i];
if (el.IsLeaf())
{
const GeomInfo& gi = GI[el.Geom()];
os << gi.nv << " ";
for (int j = 0; j < gi.nv; j++)
{
os << el.node[j] << " ";
}
os << el.attribute << " " << el.rank << " " << i << "\n";
}
}
os << "\n";
// dump faces
os << faces.Size() << "\n";
for (const auto &face : faces)
{
int elem = face.elem[0];
if (elem < 0) { elem = face.elem[1]; }
MFEM_ASSERT(elem >= 0, "");
const Element &el = elements[elem];
int lf = find_local_face(el.Geom(),
find_node(el, face.p1),
find_node(el, face.p2),
find_node(el, face.p3));
const int* fv = GI[el.Geom()].faces[lf];
const int nfv = GI[el.Geom()].nfv[lf];
os << nfv;
for (int i = 0; i < nfv; i++)
{
os << " " << el.node[fv[i]];
}
//os << " # face " << face.index() << ", index " << face.index << "\n";
os << "\n";
}
}
#endif
} // namespace mfem