Files
mfem/mesh/ncnurbs.cpp
T
2026-01-14 12:22:47 -08:00

3865 lines
119 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 "ncnurbs.hpp"
namespace mfem
{
using namespace std;
// Helper functions for NC-NURBS
void GetShiftedGridPoints2D(int m, int n, int i, int j, int signedShift,
int& sm, int& sn, int& si, int& sj);
void GetInverseShiftedDimensions2D(int signedShift, int sm, int sn, int &m,
int &n);
int GetFaceOrientation(const Mesh *mesh, const int face,
const std::array<int, 4> &verts);
bool Reorder2D(int ori, std::array<int, 2> &s0);
std::pair<int, int> QuadrupleToPair(const std::array<int, 4> &q);
NCNURBSExtension::NCNURBSExtension(std::istream &input, bool spacing)
{
// Read topology
patchTopo = new Mesh;
patchTopo->LoadNonconformingPatchTopo(input, edge_to_ukv);
nonconformingPT = true;
Load(input, spacing);
}
NCNURBSExtension::NCNURBSExtension(const NCNURBSExtension &orig)
: NURBSExtension(orig),
aux_e_meshOffsets(orig.aux_e_meshOffsets),
aux_f_meshOffsets(orig.aux_f_meshOffsets),
aux_e_spaceOffsets(orig.aux_e_spaceOffsets),
aux_f_spaceOffsets(orig.aux_f_spaceOffsets),
auxEdges(orig.auxEdges),
auxFaces(orig.auxFaces),
auxef(orig.auxef)
{ }
void NCNURBSExtension::GetMasterEdgeEntities(
int edge, Array<int> &edgeV, Array<int> &edgeE, Array<int> &edgeVki)
{
const int mid = masterEdgeToId.at(edge);
const std::size_t nes = masterEdgeInfo[mid].slaves.size();
MFEM_ASSERT(masterEdgeInfo[mid].vertices.size() + 1 == nes, "");
// Vertices in masterEdgeVerts[mid] are ordered starting
// from the master edge endpoint with lower vertex index.
Array<int> everts;
patchTopo->GetEdgeVertices(edge, everts);
edgeV.Append(everts[0]);
edgeVki.Append(0);
MFEM_ASSERT(masterEdgeInfo[mid].vertices.size() ==
masterEdgeInfo[mid].ks.size(), "");
for (std::size_t i=0; i<masterEdgeInfo[mid].vertices.size(); ++i)
{
edgeV.Append(masterEdgeInfo[mid].vertices[i]);
edgeVki.Append(masterEdgeInfo[mid].ks[i]);
}
const int nelem = KnotVec(edge)->GetNE();
edgeV.Append(everts[1]);
edgeVki.Append(nelem);
for (std::size_t i=0; i<nes; ++i)
{
const int edge_i = slaveEdges[masterEdgeInfo[mid].slaves[i]];
edgeE.Append(edge_i);
Array<int> sverts(2);
if (edge_i >= 0) // If a slave edge
{
patchTopo->GetEdgeVertices(edge_i, sverts);
}
else
{
const int auxEdge = FlipIndexSign(edge_i);
GetAuxEdgeVertices(auxEdge, sverts);
}
MFEM_ASSERT((sverts[0] == edgeV[i] &&
sverts[1] == edgeV[i+1]) ||
(sverts[1] == edgeV[i] &&
sverts[0] == edgeV[i+1]), "");
}
}
void NCNURBSExtension::FindAdditionalFacesSA(
std::map<std::pair<int, int>, int> &v2f,
std::set<int> &addParentFaces,
std::vector<FacePairInfo> &facePairs)
{
for (int f=0; f<patchTopo->GetNFaces(); ++f)
{
if (masterFaces.find(f) != masterFaces.end())
{
continue; // Already a master face
}
Array<int> edges, ori, verts;
patchTopo->GetFaceEdges(f, edges, ori);
patchTopo->GetFaceVertices(f, verts);
MFEM_ASSERT(edges.Size() == 4 && verts.Size() == 4, "");
const int fn1 = KnotVec(edges[0])->GetNE();
const int fn2 = KnotVec(edges[1])->GetNE();
// Loop over the 2 pairs of opposite sides
for (int p=0; p<2; ++p) // Pair p
{
std::array<int, 2> oppEdges;
const int sideEdge0 = edges[1 - p];
bool bothMaster = true;
for (int s=0; s<2; ++s)
{
oppEdges[s] = edges[p + 2*s];
bool isTrueMasterEdge = false;
if (masterEdges.count(oppEdges[s]) > 0)
{
const int mid = masterEdgeToId.at(oppEdges[s]);
if (masterEdgeInfo[mid].slaves.size() != 0) { isTrueMasterEdge = true; }
}
if (!isTrueMasterEdge) { bothMaster = false; }
}
if (!bothMaster) { continue; }
// Possibly define auxiliary and/or slave faces on this face
// Check for auxiliary and slave edges
std::vector<Array<int>> sideAuxEdges(2);
std::vector<Array<int>> sideSlaveEdges(2);
for (int s=0; s<2; ++s)
{
const int mid = masterEdgeToId.at(oppEdges[s]);
for (auto edge : masterEdgeInfo[mid].slaves)
{
if (edge < 0)
{
sideAuxEdges[s].Append(FlipIndexSign(edge));
}
else
{
sideSlaveEdges[s].Append(edge);
}
}
}
const bool hasAux = sideAuxEdges[0].Size() > 0;
const bool hasSlave = sideSlaveEdges[0].Size() > 0;
// Find patchTopo vertices in the interior of each side
if (hasAux || hasSlave)
{
std::vector<Array<int>> edgeV(2);
std::vector<Array<int>> edgeE(2);
std::vector<Array<int>> edgeVki(2);
for (int s=0; s<2; ++s)
{
GetMasterEdgeEntities(oppEdges[s], edgeV[s], edgeE[s], edgeVki[s]);
}
// Check whether the number and types of edges on opposite
// sides match. If not, skip this pair of sides.
if (edgeE[0].Size() != edgeE[1].Size()) { continue; }
const int nes = edgeE[0].Size();
{
bool matching = true;
for (int i=0; i<nes; ++i)
{
if ((edgeE[0][i] >= 0) != (edgeE[1][i] >= 0))
{
matching = false;
break;
}
}
if (!matching) { continue; }
}
// Check whether edgeV[s] are in the same order or reversed, for
// s=0,1.
bool rev = true;
{
Array<int> sideVerts0;
patchTopo->GetEdgeVertices(sideEdge0, sideVerts0);
sideVerts0.Sort();
std::array<bool, 2> found{false, false};
Array<int> ep(2);
for (int e=0; e<2; ++e) // Loop over ends
{
for (int s=0; s<2; ++s) // Loop over sides
{
ep[s] = edgeV[s][e * (edgeV[s].Size() - 1)];
for (int i=0; i<2; ++i)
{
if (ep[s] == sideVerts0[i])
{
found[i] = true;
}
}
}
ep.Sort();
if (ep == sideVerts0)
{
rev = false;
}
}
MFEM_ASSERT(found[0] && found[1], "");
}
// Find auxiliary or slave subfaces of face f.
// Note that there may be no master faces in patchTopo->ncmesh.
for (int i=0; i<2; ++i)
{
MFEM_ASSERT(edgeV[i].Size() == nes + 1, "");
}
for (int e=0; e<nes; ++e) // Loop over edges
{
std::array<int, 4> fverts{edgeV[0][e], edgeV[0][e + 1],
edgeV[1][rev ? nes - e - 1 : e + 1],
edgeV[1][rev ? nes - e : e]};
// Get indices with respect to the edge.
const int eki = edgeVki[0][e];
const int eki1 = edgeVki[0][e + 1];
const int e1ki = edgeVki[1][rev ? nes - e : e];
const int e1ki1 = edgeVki[1][rev ? nes - e - 1 : e + 1];
// ori_f is the signed shift such that fverts[abs1(ori_f)] is
// closest to vertex 0, verts[0], of parent face f, and the
// relative direction of the ordering is encoded in the sign.
int ori_f = 0;
// Set the 2D knot-span indices of the 4 vertices in fverts,
// ordered with respect to the face f.
Array2D<int> fki(4,2);
{
// Use eki to get the 2D face knot-span index.
// Number of elements on the edge.
const int eNE = edgeVki[0][edgeVki[0].Size() - 1];
if (p == 0)
{
MFEM_ASSERT(edgeV[0][0] == verts[0] ||
edgeV[0][edgeV[0].Size() - 1] == verts[0],
"");
MFEM_ASSERT(edgeV[1][0] == verts[3] ||
edgeV[1][edgeV[0].Size() - 1] == verts[3],
"");
MFEM_ASSERT(eNE == fn1, "");
MFEM_ASSERT(edgeVki[1][edgeVki[1].Size() - 1] == fn1,
"");
const bool rev0 = edgeV[0][0] != verts[0];
fki(0,0) = rev0 ? eNE - eki : eki;
fki(0,1) = 0;
fki(1,0) = rev0 ? eNE - eki1 : eki1;
fki(1,1) = 0;
if (rev0) { ori_f = -2; }
// Other side
const bool rev1 = edgeV[1][0] != verts[3];
fki(2,0) = rev1 ? eNE - e1ki1 : e1ki1;
fki(2,1) = fn2;
fki(3,0) = rev1 ? eNE - e1ki : e1ki;
fki(3,1) = fn2;
MFEM_ASSERT(fki(0,0) == fki(3,0) &&
fki(1,0) == fki(2,0), "");
}
else
{
MFEM_ASSERT(edgeV[0][0] == verts[1] ||
edgeV[0][edgeV[0].Size() - 1] == verts[1],
"");
MFEM_ASSERT(edgeV[1][0] == verts[0] ||
edgeV[1][edgeV[0].Size() - 1] == verts[0],
"");
MFEM_ASSERT(eNE == fn2, "");
MFEM_ASSERT(edgeVki[1][edgeVki[1].Size() - 1] == fn2,
"");
const bool rev0 = edgeV[0][0] != verts[1];
fki(0,0) = fn1;
fki(0,1) = rev0 ? eNE - eki : eki;
fki(1,0) = fn1;
fki(1,1) = rev0 ? eNE - eki1 : eki1;
if (rev0)
{
ori_f = -3;
}
else
{
ori_f = 3;
}
// Other side
const bool rev1 = edgeV[1][0] != verts[0];
fki(2,0) = 0;
fki(2,1) = rev1 ? fn2 - e1ki1 : e1ki1;
fki(3,0) = 0;
fki(3,1) = rev1 ? fn2 - e1ki : e1ki;
MFEM_ASSERT(fki(0,1) == fki(3,1) &&
fki(1,1) == fki(2,1), "");
}
}
// Returns the vertex with minimum knot-span indices.
auto VertexMinKI = [&fki]()
{
int id = -1;
{
std::array<int, 2> kiMin;
for (int j=0; j<2; ++j)
{
kiMin[j] = fki(0,j);
for (int i=1; i<4; ++i)
{
if (fki(i,j) < kiMin[j]) { kiMin[j] = fki(i,j); }
}
}
for (int i=0; i<4; ++i)
{
if (fki(i,0) == kiMin[0] && fki(i,1) == kiMin[1])
{
MFEM_ASSERT(id == -1, "");
id = i;
}
}
}
MFEM_ASSERT(id >= 0, "");
return id;
};
const std::pair<int, int> vpair = QuadrupleToPair(fverts);
if (edgeE[0][e] >= 0)
{
const bool vPairTopo = v2f.count(vpair) > 0;
if (!vPairTopo) { continue; }
const int sface = v2f.at(vpair);
addParentFaces.insert(f);
// Set facePairs
// Find the vertex with minimum knot-span indices.
const int vMinID = VertexMinKI();
std::array<int, 4> fvertsMasterOrdering;
for (int i=0; i<4; ++i)
{
if (ori_f >= 0)
{
fvertsMasterOrdering[i] = fverts[(vMinID + i) % 4];
}
else
{
fvertsMasterOrdering[i] = fverts[(vMinID + 4 - i) % 4];
}
}
const int ori_sface = GetFaceOrientation(patchTopo, sface,
fvertsMasterOrdering);
facePairs.emplace_back(FacePairInfo{fverts[vMinID], f,
SlaveFaceInfo{sface, ori_sface,
{fki(vMinID,0), fki(vMinID,1)},
{
fki((vMinID + 2) % 4,0) - fki(vMinID,0),
fki((vMinID + 2) % 4,1) - fki(vMinID,1)
}}});
}
else // Auxiliary face
{
const int afid = auxv2f.count(vpair) > 0 ?
auxv2f.at(vpair) : -1;
addParentFaces.insert(f);
const int vMinID = VertexMinKI();
if (afid >= 0)
{
// Find orientation of ordered vertices for this face,
// in fvertsOrdered, w.r.t. the auxFaces ordering.
std::array<int, 4> fvertsOrdered, afverts;
int ori_f2 = -1;
for (int i=0; i<4; ++i)
{
afverts[i] = auxFaces[afid].v[i];
if (ori_f >= 0)
{
fvertsOrdered[i] = fverts[(vMinID + i) % 4];
}
else
{
fvertsOrdered[i] = fverts[(vMinID + 4 - i) % 4];
}
if (fvertsOrdered[i] == afverts[0]) { ori_f2 = i; }
}
MFEM_ASSERT(ori_f2 >= 0, "");
if (fvertsOrdered[(ori_f2 + 1) % 4] != afverts[1])
{
for (int j=0; j<4; ++j)
{
MFEM_ASSERT(fvertsOrdered[(ori_f2 + 4 - j) % 4]
== afverts[j], "");
}
ori_f2 = FlipIndexSign(ori_f2);
}
else
{
for (int j=0; j<4; ++j)
{
MFEM_ASSERT(fvertsOrdered[(ori_f2 + j) % 4]
== afverts[j], "");
}
}
facePairs.emplace_back(FacePairInfo{fverts[vMinID], f,
SlaveFaceInfo{FlipIndexSign(afid), ori_f2,
{fki(vMinID,0), fki(vMinID,1)},
{
fki((vMinID + 2) % 4,0) - fki(vMinID,0),
fki((vMinID + 2) % 4,1) - fki(vMinID,1)
}}});
}
else
{
// Create a new auxiliary face.
const int auxFaceId = auxFaces.size();
// Find the knot-span indices of the vertices in fverts,
// with respect to the parent face.
AuxiliaryFace auxFace;
for (int i=0; i<4; ++i)
{
if (ori_f >= 0)
{
auxFace.v[i] = fverts[(vMinID + i) % 4];
}
else
{
auxFace.v[i] = fverts[(vMinID + 4 - i) % 4];
}
}
// Orientation is defined as 0 for a new auxiliary face.
ori_f = 0;
auxFace.parent = f;
auxFace.ori = ori_f;
for (int i=0; i<2; ++i)
{
auxFace.ksi0[i] = fki(vMinID,i);
auxFace.ksi1[i] = fki((vMinID + 2) % 4,i);
}
auxv2f[vpair] = auxFaces.size();
auxFaces.push_back(auxFace);
facePairs.emplace_back(FacePairInfo{fverts[vMinID], f,
SlaveFaceInfo{FlipIndexSign(auxFaceId), ori_f,
{fki(vMinID,0), fki(vMinID,1)},
{
fki((vMinID + 2) % 4,0) - fki(vMinID,0),
fki((vMinID + 2) % 4,1) - fki(vMinID,1)
}}});
}
}
}
}
} // Pair (p) loop
} // f
}
void NCNURBSExtension::ProcessFacePairs(int start, int midStart,
const std::vector<std::array<int, 2>> &parentSize,
std::vector<int> &parentVerts,
const std::vector<FacePairInfo> &facePairs)
{
const int nfpairs = facePairs.size();
const bool is3D = Dimension() == 3;
MFEM_VERIFY(nfpairs > 0 || !is3D, "");
int midPrev = -1;
int orientation = 0;
for (int q=start; q<nfpairs; ++q)
{
// We assume that j is the fast index in (i,j).
// Note that facePairs is set by ProcessVertexToKnot3D.
const int i = facePairs[q].info.ksi[0];
const int j = facePairs[q].info.ksi[1];
const int nfe1 = facePairs[q].info.ne[0]; // Number of elements, direction 1
const int nfe2 = facePairs[q].info.ne[1]; // Number of elements, direction 2
const int v0 = facePairs[q].v0; // Bottom-left corner vertex of child face
const int childFace = facePairs[q].info.index;
const int parentFace = facePairs[q].parent;
const int cpori =
facePairs[q].info.ori; // Orientation for childFace w.r.t. parentFace
const int mid = masterFaceToId.at(parentFace);
// Ignore data about master faces already processed.
if (mid < midStart) { continue; }
MFEM_ASSERT(0 <= i && i < parentSize[mid][0] && 0 <= j &&
j < parentSize[mid][1], "");
if (mid != midPrev) // Next parent face
{
std::array<int, 4> pv;
for (int k=0; k<4; ++k) { pv[k] = parentVerts[(4*mid) + k]; }
const int ori = GetFaceOrientation(patchTopo, parentFace, pv);
// Ori is the signed shift such that pv[abs1(ori)] is vertex 0 of
// parentFace, and the relative direction of the ordering is encoded in
// the sign.
if (q > start && midPrev >= 0)
{
// For the previous parentFace, use previous orientation to reorder
// masterFaceSlaves, masterFaceSlaveCorners, masterFaceSizes.
std::array<int, 2> s0;
masterFaceInfo[midPrev].rev = Reorder2D(orientation, s0);
masterFaceInfo[midPrev].s0 = s0;
}
orientation = ori;
midPrev = mid;
} // next parent face
slaveFaces.emplace_back(SlaveFaceInfo{childFace, cpori, {i, j},
{nfe1, nfe2}});
const int si = slaveFaces.size() - 1;
masterFaceInfo[mid].slaves.push_back(si);
masterFaceInfo[mid].slaveCorners.push_back(v0);
masterFaceInfo[mid].ne[0] = parentSize[mid][0];
masterFaceInfo[mid].ne[1] = parentSize[mid][1];
} // Loop (q) over facePairs
if (midPrev >= 0)
{
std::array<int, 2> s0;
masterFaceInfo[midPrev].rev = Reorder2D(orientation, s0);
masterFaceInfo[midPrev].s0 = s0;
}
}
void NCNURBSExtension::GetAuxEdgeVertices(int auxEdge, Array<int> &verts) const
{
verts.SetSize(2);
for (int i=0; i<2; ++i) { verts[i] = auxEdges[auxEdge].v[i]; }
}
void NCNURBSExtension::GetAuxFaceVertices(int auxFace, Array<int> &verts) const
{
verts.SetSize(4);
for (int i=0; i<4; ++i) { verts[i] = auxFaces[auxFace].v[i]; }
}
void NCNURBSExtension::GetAuxFaceEdges(int auxFace, Array<int> &edges) const
{
edges.SetSize(4);
Array<int> verts(2);
for (int i=0; i<4; ++i)
{
for (int j=0; j<2; ++j) { verts[j] = auxFaces[auxFace].v[(i + j) % 4]; }
verts.Sort();
const std::pair<int, int> edge_v(verts[0], verts[1]);
// Note that v2e is a map only for conforming patchTopo->ncmesh edges.
// Auxiliary edges are in auxv2e, not in v2e.
if (v2e.count(edge_v) > 0)
{
edges[i] = v2e.at(edge_v); // patchTopo edge
}
else // Auxiliary edge
{
edges[i] = FlipIndexSign(auxv2e.at(edge_v));
}
}
}
// Negative indices are for array `b`. Nonnegative indices are for array `a`,
// except for index `a.Size()`, corresponding to `b[0]`.
int OffsetHelper(int i, int j, const Array<int> &a, const Array<int> &b)
{
if (i < 0)
{
return b[FlipIndexSign(i) + j];
}
else if (i + j < a.Size())
{
return a[i + j];
}
else
{
return b[0];
}
}
int NCNURBSExtension::GetEdgeOffset(bool dof, int edge, int increment) const
{
return OffsetHelper(edge, increment, dof ? e_spaceOffsets : e_meshOffsets,
dof ? aux_e_spaceOffsets : aux_e_meshOffsets);
}
int NCNURBSExtension::GetFaceOffset(bool dof, int face, int increment) const
{
return OffsetHelper(face, increment, dof ? f_spaceOffsets : f_meshOffsets,
dof ? aux_f_spaceOffsets : aux_f_meshOffsets);
}
void NCNURBSExtension::GetMasterEdgeDofs(bool dof, int me,
Array<int> &dofs) const
{
MFEM_ASSERT(masterEdges.count(me) > 0, "Not a master edge");
const int mid = masterEdgeToId.at(me);
MFEM_ASSERT(masterEdgeInfo[mid].vertices.size() ==
masterEdgeInfo[mid].slaves.size() - 1, "");
const Array<int>& v_offsets = dof ? v_spaceOffsets : v_meshOffsets;
const std::size_t nes = masterEdgeInfo[mid].slaves.size();
for (std::size_t s=0; s<nes; ++s)
{
const int slaveId = slaveEdges[masterEdgeInfo[mid].slaves[s]];
Array<int> svert;
if (slaveId >= 0)
{
patchTopo->GetEdgeVertices(slaveId, svert);
}
else // Auxiliary edge
{
GetAuxEdgeVertices(FlipIndexSign(slaveId), svert);
}
bool reverse = false;
if (nes > 1)
{
const int mev = masterEdgeInfo[mid].vertices[std::max((int) s - 1,0)];
MFEM_ASSERT(mev == svert[0] || mev == svert[1], "");
if (s == 0)
{
// In this case, mev is the second vertex of the edge.
if (svert[0] == mev) { reverse = true; }
}
else
{
// In this case, mev is the first vertex of the edge.
if (svert[1] == mev) { reverse = true; }
}
}
const int eos = GetEdgeOffset(dof, slaveId, 0);
const int eos1 = GetEdgeOffset(dof, slaveId, 1);
const int nvs = eos1 - eos;
MFEM_ASSERT(nvs >= 0, "");
// Add all slave edge vertices/DOFs
Array<int> sdofs(nvs);
for (int j=0; j<nvs; ++j) { sdofs[j] = reverse ? eos1 - 1 - j : eos + j; }
dofs.Append(sdofs);
if (s < masterEdgeInfo[mid].slaves.size() - 1)
{
// Add interior vertex DOF
dofs.Append(v_offsets[masterEdgeInfo[mid].vertices[s]]);
}
}
}
// Set masterDofs.
void NURBSPatchMap::SetMasterEdges(bool dof, const KnotVector *kv[])
{
edgeMaster.SetSize(edges.Size());
edgeMasterOffset.SetSize(edges.Size());
masterDofs.SetSize(0);
int mos = 0;
for (int i=0; i<edges.Size(); ++i)
{
edgeMaster[i] = Ext->IsMasterEdge(edges[i]);
edgeMasterOffset[i] = mos;
if (edgeMaster[i])
{
Array<int> mdof;
Ext->GetMasterEdgeDofs(dof, edges[i], mdof);
masterDofs.Append(mdof);
mos += mdof.Size();
}
}
}
void NCNURBSExtension::GetFaceOrdering(int sf, int n1, int n2, int v0,
int e1, int e2, Array<int> &perm) const
{
perm.SetSize(n1 * n2);
// The ordering of entities in the face is based on the vertices.
Array<int> faceEdges, ori, evert, e2vert, vert;
patchTopo->GetFaceEdges(sf, faceEdges, ori);
patchTopo->GetFaceVertices(sf, vert);
patchTopo->GetEdgeVertices(faceEdges[e1], evert);
MFEM_ASSERT(evert[0] == v0 || evert[1] == v0, "");
bool d[2];
d[0] = (evert[0] == v0);
const int v10 = d[0] ? evert[1] : evert[0];
// The face has {fn1,fn2} interior entities, with ordering based on `vert`.
// Now we find these sizes by first finding the edge with vertices [v0, v10].
int e0 = -1;
for (int i=0; i<4; ++i)
{
patchTopo->GetEdgeVertices(faceEdges[i], evert);
if ((evert[0] == v0 && evert[1] == v10) ||
(evert[1] == v0 && evert[0] == v10)) { e0 = i; }
}
MFEM_ASSERT(e0 >= 0, "");
const bool tr = e0 % 2 == 1; // True means (fn1,fn2) == (n2,n1)
patchTopo->GetEdgeVertices(faceEdges[e2], evert);
MFEM_ASSERT(evert[0] == v10 || evert[1] == v10, "");
d[1] = (evert[0] == v10);
const int v11 = d[1] ? evert[1] : evert[0];
int v01 = -1;
for (int i=0; i<4; ++i)
{
if (vert[i] != v0 && vert[i] != v10 && vert[i] != v11) { v01 = vert[i]; }
}
MFEM_ASSERT(v01 >= 0 && v01 == vert.Sum() - v0 - v10 - v11, "");
// Translate indices [v0, v10, v11, v01] to pairs of indices in {0,1}.
constexpr char ipair[4][2] = {{0, 0}, {1, 0}, {1, 1}, {0, 1}};
int f00[2];
int allv[4] = {v0, v10, v11, v01};
int locv[4];
for (int i=0; i<4; ++i)
{
locv[i] = -1;
for (int j=0; j<4; ++j)
{
if (vert[j] == allv[i])
{
locv[i] = j;
}
}
MFEM_ASSERT(locv[i] >= 0, "");
}
for (int i=0; i<2; ++i) { f00[i] = ipair[locv[0]][i]; }
const int i0 = f00[0];
const int j0 = f00[1];
for (int i=0; i<n1; ++i)
for (int j=0; j<n2; ++j)
{
// Entity perm[i] of the face should be entity i in the master face
// ordering. The master face ordering varies faster in the direction
// from v0 to v10, and slower in the direction from v10 to v11, or
// equivalently, from v0 to v01.
if (tr)
{
const int fi = i0 == 0 ? j : n2 - 1 - j;
const int fj = j0 == 0 ? i : n1 - 1 - i;
const int p = fi + (fj * n2); // Index in the slave face ordering
const int m = i + (j * n1); // Index in the master face ordering
perm[m] = p;
}
else
{
const int fi = i0 == 0 ? i : n1 - 1 - i;
const int fj = j0 == 0 ? j : n2 - 1 - j;
const int p = fi + (fj * n1); // Index in the slave face ordering
const int m = i + (j * n1); // Index in the master face ordering
perm[m] = p;
}
}
}
// Set an integer, with a check that it is uninitialized (-1) or unchanged.
bool ConsistentlySetEntry(int v, int &e)
{
const bool consistent = e == -1 || e == v;
e = v;
return consistent;
}
// Reorder a 2D array to start at a corner given by (i0,j0) in {0,1}^2.
void ReorderArray2D(int i0, int j0, const Array2D<int> &a,
Array2D<int> &b)
{
const int m = a.NumRows();
const int n = a.NumCols();
b.SetSize(m, n);
const int s0 = i0 == 0 ? 1 : -1;
const int s1 = j0 == 0 ? 1 : -1;
for (int i=0; i<m; ++i)
{
const int ia = (i0 * (m - 1)) + (s0 * i);
for (int j=0; j<n; ++j)
{
const int ja = (j0 * (n - 1)) + (s1 * j);
b(i, j) = a(ia, ja);
}
}
}
// Set a quadrilateral vertex index permutation for a given orientation.
void GetVertexOrdering(int ori, std::array<int, 4> &perm)
{
const int oriAbs = UnsignIndex(ori);
for (int i=0; i<4; ++i)
{
if (ori < 0)
{
perm[i] = (oriAbs - i + 4) % 4;
}
else
{
perm[i] = (ori + i) % 4;
}
}
}
// Append master face DOFs to masterDofs.
void NURBSPatchMap::SetMasterFaces(bool dof)
{
faceMaster.SetSize(faces.Size());
faceMasterOffset.SetSize(faces.Size());
// The loop over master edges is already done by SetMasterEdges, and now we
// append face DOFs to masterDofs.
int mos = masterDofs.Size();
for (int i=0; i<faces.Size(); ++i)
{
faceMaster[i] = Ext->IsMasterFace(faces[i]);
faceMasterOffset[i] = mos;
if (!faceMaster[i]) { continue; }
Array2D<int> mdof;
Ext->GetMasterFaceDofs(dof, faces[i], mdof);
if (mdof.NumRows() == 0)
{
faceMaster[i] = false;
continue;
}
for (int j=0; j<mdof.NumCols(); ++j)
for (int k=0; k<mdof.NumRows(); ++k)
{
masterDofs.Append(mdof(k,j));
}
mos += mdof.NumRows() * mdof.NumCols();
} // loop (i) over faces
}
void NCNURBSExtension::GetMasterFaceDofs(bool dof, int mf,
Array2D<int> &dofs) const
{
const int mid = masterFaceToId.at(mf);
const bool rev = masterFaceInfo[mid].rev;
const int s0i = masterFaceInfo[mid].s0[0];
const int s0j = masterFaceInfo[mid].s0[1];
const int n1orig = masterFaceInfo[mid].ne[0];
const int n2orig = masterFaceInfo[mid].ne[1];
// Skip master faces with no slave faces (only having slave edges).
if (n1orig == 0 && n2orig == 0) { return; }
const int n1 = rev ? n2orig : n1orig;
const int n2 = rev ? n1orig : n2orig;
MFEM_ASSERT((n1 > 1 || n2 > 1) &&
n1 * n2 >= (int) masterFaceInfo[mid].slaves.size(),
"Inconsistent number of faces");
int fcnt = 0;
for (auto slaveId : masterFaceInfo[mid].slaves)
{
fcnt += slaveFaces[slaveId].ne[0] * slaveFaces[slaveId].ne[1];
}
MFEM_VERIFY(fcnt == n1 * n2, "");
MFEM_VERIFY((int) masterFaceInfo[mid].slaveCorners.size() <= n1 * n2, "");
// Set an array of vertices or DOFs for the interior of this master face.
// Set master face entity dimensions.
int mnf1, mnf2;
Array<int> medges;
{
Array<int> mori;
patchTopo->GetFaceEdges(mf, medges, mori);
}
MFEM_ASSERT(medges.Size() == 4, "");
if (dof)
{
mnf1 = KnotVec(medges[0])->GetNCP() - 2;
mnf2 = KnotVec(medges[1])->GetNCP() - 2;
}
else
{
mnf1 = KnotVec(medges[0])->GetNE() - 1;
mnf2 = KnotVec(medges[1])->GetNE() - 1;
}
// Set dimensions for a single mesh edge.
const int sne1 = (mnf1 - n1 + 1) / n1;
const int sne2 = (mnf2 - n2 + 1) / n2;
MFEM_ASSERT(sne1 * n1 == mnf1 - n1 + 1, "");
MFEM_ASSERT(sne2 * n2 == mnf2 - n2 + 1, "");
const Array<int> &v_offsets = dof ? v_spaceOffsets : v_meshOffsets;
Array2D<int> mdof(mnf1, mnf2);
mdof = -1;
bool consistent = true;
for (std::size_t s=0; s<masterFaceInfo[mid].slaves.size(); ++s)
{
const int sId = masterFaceInfo[mid].slaves[s];
const int slaveId = slaveFaces[sId].index;
const int v0 = masterFaceInfo[mid].slaveCorners[s];
const int ori = slaveFaces[sId].ori;
// ori gives the orientation and index of cv matching the first vertex of
// childFace, where cv is the array of slave face vertices in CCW order
// with respect to the master face.
const int sI = slaveFaces[sId].ksi[0];
const int sJ = slaveFaces[sId].ksi[1];
const int ne1 = slaveFaces[sId].ne[0];
const int ne2 = slaveFaces[sId].ne[1];
const int fos = GetFaceOffset(dof, slaveId, 0);
const int fos1 = GetFaceOffset(dof, slaveId, 1);
const int nvs = fos1 - fos;
// These offsets are for the interior entities of the face. To get the
// lower edge, subtract 1.
const int os1 = (sI * sne1) + sI;
const int os2 = (sJ * sne2) + sJ;
std::array<int, 4> orderedVertices;
std::array<bool, 4> edgeBdry;
std::set<int> vbdry;
int nf1 = 0, nf2 = 0;
auto SetEdgeEntries = [&](int eidx, int edge, const Array<int> &evert,
int &vstart)
{
const bool reverse = (vstart == evert[1]);
const int vend = evert.Sum() - vstart;
vstart = vend;
// Skip edges on the boundary of the master face.
if (edgeBdry[eidx])
{
for (auto v : evert) { vbdry.insert(v); }
return;
}
const bool horizontal = (eidx % 2 == 0);
const int nf_e = horizontal ? nf1 : nf2;
// Edge entities
const int eos = GetEdgeOffset(dof, edge, 0);
#ifdef MFEM_DEBUG
const int eos1 = GetEdgeOffset(dof, edge, 1);
#endif
const bool edgeIsMaster = masterEdges.count(edge) > 0;
Array<int> edofs;
if (edgeIsMaster)
{
// This edge is a slave edge and a master edge. Instead of
// getting DOFs from e_offsets, take them from the slave edges
// of this edge.
GetMasterEdgeDofs(dof, edge, edofs);
}
else
{
MFEM_ASSERT(eos1 - eos == nf_e, "");
edofs.SetSize(nf_e);
for (int j=0; j<nf_e; ++j)
{
edofs[j] = eos + j;
}
}
MFEM_ASSERT(edofs.Size() == nf_e, "");
for (int j=0; j<nf_e; ++j)
{
int m1, m2;
if (eidx == 0)
{
m1 = os1 + j;
m2 = os2 - 1;
}
else if (eidx == 1)
{
m1 = os1 + nf1;
m2 = os2 + j;
}
else if (eidx == 2)
{
m1 = os1 + nf1 - 1 - j;
m2 = os2 + nf2;
}
else
{
m1 = os1 - 1;
m2 = os2 + nf2 - 1 - j;
}
if (!ConsistentlySetEntry(reverse ? edofs[nf_e - 1 - j]
: edofs[j], mdof(m1, m2)))
{
consistent = false;
}
}
};
if (slaveId < 0)
{
// Auxiliary face
const int auxFace = FlipIndexSign(slaveId);
// Set slave face entity dimensions.
if (dof)
{
nf1 = (sne1 * ne1) + ne1 - 1;
nf2 = (sne2 * ne2) + ne2 - 1;
}
else
{
nf1 = auxFaces[auxFace].ksi1[0] - auxFaces[auxFace].ksi0[0] - 1;
nf2 = auxFaces[auxFace].ksi1[1] - auxFaces[auxFace].ksi0[1] - 1;
}
MFEM_VERIFY(sne1 * ne1 == nf1 - ne1 + 1 &&
sne2 * ne2 == nf2 - ne2 + 1 &&
nvs == nf1 * nf2, "");
// If ori >= 0, then vertex ori of the aux face is closest to vertex 0
// of the parent face. If ori < 0, then the orientations of the aux face
// and parent face are reversed.
// NOTE: When an aux face is first defined, it is constructed with
// orientation 0 w.r.t. its parent face. However, it can be part of
// another parent face. In this case, the original aux face index is
// used, but it is paired with a different parent face, with
// possibly different orientation w.r.t. that parent face. In
// NURBSPatchMap::SetMasterFaces, the DOFs are set on the parent
// face in its knot-span indices, with an aux face of possibly nonzero
// orientation. When the orientation is nonzero, the aux face DOFs
// must be reordered for the parent face, based on orientation.
int onf1, onf2;
GetInverseShiftedDimensions2D(ori, nf1, nf2, onf1, onf2);
for (int k=0; k<onf2; ++k)
for (int j=0; j<onf1; ++j)
{
int sm, sn, sj, sk;
GetShiftedGridPoints2D(onf1, onf2, j, k, ori, sm, sn, sj, sk);
const int q = j + (k * onf1);
if (!ConsistentlySetEntry(fos + q, mdof(os1 + sj, os2 + sk)))
{
consistent = false;
}
}
// Set entries on edges of this face, if interior to the master face.
// Horizontal edges
edgeBdry[0] = sJ == 0;
edgeBdry[2] = sJ + ne2 == n2;
// Vertical edges
edgeBdry[1] = sI + ne1 == n1;
edgeBdry[3] = sI == 0;
Array<int> faceEdges;
GetAuxFaceEdges(auxFace, faceEdges);
std::array<int, 4> perm;
GetVertexOrdering(ori, perm);
int vstart = v0;
for (int eidx=0; eidx<4; ++eidx)
{
orderedVertices[eidx] = vstart;
MFEM_ASSERT(orderedVertices[eidx] ==
auxFaces[auxFace].v[perm[eidx]], "");
const int eperm = ori < 0 ? (perm[eidx] - 1 + 4) % 4 : perm[eidx];
const int edge = faceEdges[eperm];
Array<int> evert;
if (edge >= 0)
{
patchTopo->GetEdgeVertices(edge, evert);
}
else
{
const int auxEdge = FlipIndexSign(edge);
GetAuxEdgeVertices(auxEdge, evert);
}
MFEM_ASSERT(evert[0] == vstart || evert[1] == vstart, "");
SetEdgeEntries(eidx, edge, evert, vstart);
} // eidx
}
else // slaveId >= 0
{
// Determine which slave face edges are in the first and second
// dimensions of the master face, by using ori.
int e1 = -1, e2 = -1;
{
const int aori = UnsignIndex(ori);
if (aori % 2 == 0)
{
e1 = 0;
e2 = 1;
}
else
{
e1 = 1;
e2 = 0;
}
if (ori < 0)
{
// Swap e1, e2
const int sw = e1;
e1 = e2;
e2 = sw;
}
}
// Now, e1 is one of the two horizontal edges in the master face
// directions. If it does not touch v0, then take the other horizontal
// edge. Do the same for e2.
Array<int> sedges;
{
Array<int> sori;
patchTopo->GetFaceEdges(slaveId, sedges, sori);
}
int v1 = -1;
{
Array<int> evert;
patchTopo->GetEdgeVertices(sedges[e1], evert);
if (evert.Find(v0) == -1)
{
e1 += 2;
patchTopo->GetEdgeVertices(sedges[e1], evert);
}
const int idv0 = evert.Find(v0);
MFEM_ASSERT(idv0 >= 0, "");
v1 = evert[1 - idv0];
patchTopo->GetEdgeVertices(sedges[e2], evert);
if (evert.Find(v1) == -1)
{
e2 += 2;
patchTopo->GetEdgeVertices(sedges[e2], evert);
}
MFEM_ASSERT(evert.Find(v1) >= 0, "");
}
// Set slave face entity dimensions.
if (dof)
{
nf1 = KnotVec(sedges[e1])->GetNCP() - 2;
nf2 = KnotVec(sedges[e2])->GetNCP() - 2;
}
else
{
nf1 = KnotVec(sedges[e1])->GetNE() - 1;
nf2 = KnotVec(sedges[e2])->GetNE() - 1;
}
MFEM_ASSERT(sne1 * ne1 == nf1 - ne1 + 1, "");
MFEM_ASSERT(sne2 * ne2 == nf2 - ne2 + 1, "");
MFEM_ASSERT(nvs == nf1 * nf2, "");
// Find the DOFs of the slave face ordered for the master face. We know
// that e1 and e2 are the local indices of the slave face edges on the
// bottom and right side, with respect to the master face directions.
Array<int> perm;
GetFaceOrdering(slaveId, nf1, nf2, v0, e1, e2, perm);
for (int k=0; k<nf2; ++k)
for (int j=0; j<nf1; ++j)
{
const int q = j + (k * nf1);
if (!ConsistentlySetEntry(fos + perm[q],
mdof(os1 + j, os2 + k)))
{
consistent = false;
}
}
// Set entries on edges of this face, if interior to the master face.
std::array<int, 4> edgeOrder{e1, e2, (e1 + 2) % 4, (e2 + 2) % 4};
// Horizontal edges
edgeBdry[0] = sJ == 0;
edgeBdry[2] = sJ + ne2 == n2;
// Vertical edges
edgeBdry[1] = sI + ne1 == n1;
edgeBdry[3] = sI == 0;
int vstart = v0;
for (int eidx=0; eidx<4; ++eidx)
{
orderedVertices[eidx] = vstart;
const int edge = sedges[edgeOrder[eidx]];
Array<int> evert;
patchTopo->GetEdgeVertices(edge, evert);
SetEdgeEntries(eidx, edge, evert, vstart);
} // eidx
}
// Set entries at vertices of this face, if interior to the master face.
for (int vidx=0; vidx<4; ++vidx)
{
const int v = orderedVertices[vidx];
if (vbdry.count(v) == 0) // If not on the master face boundary.
{
int m1, m2;
if (vidx == 0)
{
m1 = os1 - 1;
m2 = os2 - 1;
}
else if (vidx == 1)
{
m1 = os1 + nf1;
m2 = os2 - 1;
}
else if (vidx == 2)
{
m1 = os1 + nf1;
m2 = os2 + nf2;
}
else
{
m1 = os1 - 1;
m2 = os2 + nf2;
}
if (!ConsistentlySetEntry(v_offsets[v], mdof(m1, m2)))
{
consistent = false;
}
}
} // vidx
} // Loop (s) over slave faces.
// Let `ori` be the signed shift such that pv[abs1(ori)] is vertex 0 of
// parentFace, and the relative direction of the ordering is encoded in the
// sign. Here, pv means parent vertices, as ordered in the mesh file. Then
// Reorder2D takes `ori` and computes (s0i, s0j) as the corresponding integer
// coordinates in {0,1}x{0,1}. Thus reference vertex (s0i, s0j) of pv (parent
// vertices in mesh file) is vertex (0, 0) of parentFace. Currently, mdof is
// in the ordering of pv, and the entries are now reordered, according to
// parentFace vertex ordering, for appending to masterDofs. That means the
// first entry appended to masterDofs should be the entry of mdof
// corresponding to (s0i, s0j).
ReorderArray2D(s0i, s0j, mdof, dofs);
const bool all_set = dofs.NumRows() * dofs.NumCols() == 0 || dofs.Min() >= 0;
MFEM_VERIFY(all_set && consistent, "");
}
int NURBSPatchMap::GetMasterEdgeDof(const int e, const int i) const
{
const int os = edgeMasterOffset[e];
return masterDofs[os + i];
}
int NURBSPatchMap::GetMasterFaceDof(const int f, const int i) const
{
const int os = faceMasterOffset[f];
return masterDofs[os + i];
}
void NCNURBSExtension::ProcessVertexToKnot2D(const VertexToKnotSpan &v2k,
std::set<int> &reversedParents,
std::vector<EdgePairInfo> &edgePairs)
{
auxEdges.clear();
auxv2e.clear();
const int nv2k = v2k.Size();
int prevParent = -1;
int prevV = -1;
int prevKI = -1;
for (int i=0; i<nv2k; ++i)
{
int tv, ks;
std::array<int, 2> pv;
v2k.GetVertex2D(i, tv, ks, pv);
// Given that the parent Mesh is not yet constructed, and all we have at
// this point is patchTopo->ncmesh, we should only define master/slave
// edges by indices in patchTopo->ncmesh, as done in the case of nonempty
// nce.masters. Now find the edge in patchTopo->ncmesh with vertices
// (pv[0], pv[1]), and define it as a master edge.
const std::pair<int, int> parentPair(pv[0] < pv[1] ? pv[0] : pv[1],
pv[0] < pv[1] ? pv[1] : pv[0]);
MFEM_ASSERT(v2e.count(parentPair) > 0, "Vertex pair not found");
const int parentEdge = v2e[parentPair];
masterEdges.insert(parentEdge);
const int kv = KnotInd(parentEdge);
parentToKV[parentPair] = std::array<int, 2> {kv, -1};
const bool rev = pv[1] < pv[0];
if (rev) { reversedParents.insert(parentEdge); }
// Note that the logic here assumes that the "vertex_to_knotspan" data in
// the mesh file has vertices in order of ascending knotIndex.
const bool newParentEdge = (prevParent != parentEdge);
const int v0 = newParentEdge ? pv[0] : prevV;
if (ks == 1) { MFEM_ASSERT(newParentEdge, ""); }
// Find the edge in patchTopo->ncmesh with vertices (v0, tv), and define
// it as a slave edge.
const std::pair<int, int> childPair(v0 < tv ? v0 : tv, v0 < tv ? tv : v0);
const bool childPairTopo = v2e.count(childPair) > 0;
if (!childPairTopo)
{
// Check whether childPair is in auxEdges.
if (auxv2e.count(childPair) == 0)
{
// Create a new auxiliary edge
auxv2e[childPair] = auxEdges.size();
auxEdges.emplace_back(AuxiliaryEdge{pv[0] < pv[1] ? parentEdge :
FlipIndexSign(parentEdge),
{childPair.first, childPair.second},
{newParentEdge ? 0 : prevKI, ks}});
}
}
const int childEdge = childPairTopo ? v2e[childPair] :
FlipIndexSign(auxv2e[childPair]);
// Check whether this is the final vertex in this parent edge. Note that
// the logic for comparing (pv[0],pv[1]) to the next parents assumes the
// ordering does not change, which is ensured by the assumption that the
// knot-span index is increasing.
bool finalVertex = (i == nv2k-1);
if (i < nv2k-1)
{
int tv_next, ks_next;
std::array<int, 2> pv_next;
v2k.GetVertex2D(i + 1, tv_next, ks_next, pv_next);
if (pv_next[0] != pv[0] || pv_next[1] != pv[1])
{
finalVertex = true;
}
}
edgePairs.emplace_back(tv, ks, childEdge, parentEdge);
if (finalVertex)
{
// Also find the edge with vertices (tv, pv[1]), and define it as a
// slave edge.
const std::pair<int, int> finalChildPair(tv < pv[1] ? tv : pv[1],
tv < pv[1] ? pv[1] : tv);
const bool finalChildPairTopo = v2e.count(finalChildPair) > 0;
if (!finalChildPairTopo)
{
// Check whether finalChildPair is in auxEdges.
if (auxv2e.count(finalChildPair) == 0)
{
// Create a new auxiliary edge
auxv2e[finalChildPair] = auxEdges.size();
// -1 denotes `ne` at endpoint
auxEdges.emplace_back(AuxiliaryEdge{pv[0] < pv[1] ?
FlipIndexSign(parentEdge) :
parentEdge,
{finalChildPair.first, finalChildPair.second},
{ks, -1}});
}
}
const int finalChildEdge = finalChildPairTopo ? v2e[finalChildPair] :
FlipIndexSign(auxv2e[finalChildPair]);
edgePairs.emplace_back(-1, -1, finalChildEdge, parentEdge);
}
prevV = tv;
prevKI = ks;
prevParent = parentEdge;
} // loop over vertices in vertex_to_knotspan
}
void NCNURBSExtension::ProcessVertexToKnot3D(
const VertexToKnotSpan &v2k,
const std::map<std::pair<int, int>, int> &v2f,
std::vector<std::array<int, 2>> &parentSize,
std::vector<EdgePairInfo> &edgePairs,
std::vector<FacePairInfo> &facePairs,
std::vector<int> &parentFaces,
std::vector<int> &parentVerts)
{
auxEdges.clear();
auxFaces.clear();
auxv2e.clear();
auxv2f.clear();
const int nv2k = v2k.Size();
// Note that the logic here assumes that the "vertex_to_knotspan" data in the
// mesh file has vertices in order of ascending (k1,k2), with k2 being the
// fast variable, and with corners skipped.
// Find parentOffset, which stores the indices in v2k at which parent faces
// start.
int prevParent = -1;
std::vector<int> parentOffset;
std::vector<bool> parentV2Kedge;
int n1 = 0;
int n2 = 0;
int n1min = 0;
int n2min = 0;
for (int i = 0; i < nv2k; ++i)
{
int tv;
std::array<int, 2> ks;
std::array<int, 4> pv;
v2k.GetVertex3D(i, tv, ks, pv);
// The face with vertices (pv[0], pv[1], pv[2], pv[3]) is defined as a
// parent face.
const std::pair<int, int> parentPair = QuadrupleToPair(pv);
const int parentFace = v2f.at(parentPair);
const bool newParentFace = (prevParent != parentFace);
if (newParentFace)
{
parentOffset.push_back(i);
parentFaces.push_back(parentFace);
// Find the knotvectors for the first two edges this face.
{
Array<int> edges, ori, verts;
patchTopo->GetFaceEdges(parentFace, edges, ori);
patchTopo->GetFaceVertices(parentFace, verts);
std::array<int,2> kv = {-1, -1};
for (int e=0; e<2; ++e)
{
// Find the edge with vertices pv[e] and pv[e+1].
for (auto edge : edges)
{
Array<int> evert;
patchTopo->GetEdgeVertices(edge, evert);
const bool matching = (evert[0] == pv[e] && evert[1] == pv[e+1]) ||
(evert[1] == pv[e] && evert[0] == pv[e+1]);
if (matching) { kv[e] = KnotInd(edge); }
}
MFEM_ASSERT(kv[e] >= 0, "");
}
parentToKV[parentPair] = std::array<int, 2> {kv[0], kv[1]};
}
if (i > 0)
{
// In the case of only 1 element in the 1-direction, it is assumed
// that the 2-direction has more than 1 element, so there are
// knot-spans (0, ki2) and (1, ki2) for 0 < ki2 < n2. This will
// result in n1 = 0, which should be 1. Also, n2 will be 1 less than
// it should be. Similarly for the situation with directions
// reversed.
const int n1range = n1 - n1min;
const int n2range = n2 - n2min;
parentV2Kedge.push_back(n1range == 0 || n2range == 0);
}
auto getEdgeNE = [&](int d)
{
Array<int> ev(2);
for (int j=0; j<2; ++j) { ev[j] = pv[j + d]; }
ev.Sort();
return KnotVecNE(v2e.at(std::pair<int, int>(ev[0], ev[1])));
};
parentSize.emplace_back(std::array<int, 2> {getEdgeNE(0), getEdgeNE(1)});
n1 = ks[0]; // Finding max of ks[0]
n2 = ks[1]; // Finding max of ks[1]
n1min = n1;
n2min = n2;
}
else
{
n1 = std::max(n1, ks[0]); // Finding max of ks[0]
n2 = std::max(n2, ks[1]); // Finding max of ks[1]
n1min = std::min(n1min, ks[0]);
n2min = std::min(n2min, ks[1]);
}
prevParent = parentFace;
}
{
const int n1range = n1 - n1min;
const int n2range = n2 - n2min;
parentV2Kedge.push_back(n1range == 0 || n2range == 0);
}
const int numParents = parentOffset.size();
parentOffset.push_back(nv2k);
std::set<int> visitedParentEdges;
std::map<int, int> edgePairOS;
bool consistent = true;
for (int parent = 0; parent < numParents; ++parent)
{
const int parentFace = parentFaces[parent];
int parentEdges[4];
bool parentEdgeRev[4];
int tvi;
std::array<int, 2> ks;
std::array<int, 4> pv;
v2k.GetVertex3D(parentOffset[parent], tvi, ks, pv);
// Set all 4 edges of the parent face as master edges.
{
Array<int> ev(2);
for (int i=0; i<4; ++i)
{
for (int j=0; j<2; ++j)
{
ev[j] = pv[(i + j) % 4];
}
const bool reverse = (ev[1] < ev[0]);
parentEdgeRev[i] = reverse;
ev.Sort();
const std::pair<int, int> edge_i(ev[0], ev[1]);
const int parentEdge = v2e.at(edge_i);
masterEdges.insert(parentEdge);
parentEdges[i] = parentEdge;
}
}
n1 = parentSize[parent][0];
n2 = parentSize[parent][1];
Array2D<int> gridVertex(n1 + 1, n2 + 1);
gridVertex = -1;
gridVertex(0,0) = pv[0];
gridVertex(n1,0) = pv[1];
gridVertex(n1,n2) = pv[2];
gridVertex(0,n2) = pv[3];
for (int i=0; i<4; ++i) { parentVerts.push_back(pv[i]); }
int r1min = -1;
int r1max = -1;
int r2min = -1;
int r2max = -1;
for (int i = parentOffset[parent]; i < parentOffset[parent + 1]; ++i)
{
v2k.GetVertex3D(i, tvi, ks, pv);
gridVertex(ks[0], ks[1]) = tvi;
if (i == parentOffset[parent])
{
// Initialize min/max
r1min = ks[0];
r1max = ks[0];
r2min = ks[1];
r2max = ks[1];
}
else
{
r1min = std::min(r1min, ks[0]);
r1max = std::max(r1max, ks[0]);
r2min = std::min(r2min, ks[1]);
r2max = std::max(r2max, ks[1]);
}
} // loop over vertices in v2k
MFEM_ASSERT((r1max - r1min + 1) * (r2max - r2min + 1) >=
parentOffset[parent + 1] - parentOffset[parent], "");
std::array<int,2> kvi;
if (kvf.size() > 0)
{
const std::pair<int, int> parentPair = v2k.GetVertexParentPair(
parentOffset[parent]);
std::array<int, 2> kv = parentToKV.at(parentPair);
for (int i=0; i<2; ++i) { kvi[i] = kv[i]; }
}
// Default refinement factor
const int rf = ref_factors.Size() == 3 ? ref_factors[0] : 1;
int n1orig = kvf_coarse.size() > 0 ? kvf_coarse[kvi[0]].Size() : n1 / rf;
int n2orig = kvf_coarse.size() > 0 ? kvf_coarse[kvi[1]].Size() : n2 / rf;
if (kvf.size() > 0 && kvf_coarse.size() == 0)
{
n1orig = kvf[kvi[0]].Size();
n2orig = kvf[kvi[1]].Size();
}
if (kvf.size() > 0)
{
MFEM_ASSERT(kvf[kvi[0]].Sum() == n1 && kvf[kvi[1]].Sum() == n2, "");
}
std::vector<Array<int>> cgrid(2);
std::array<int,2> n_orig = {n1orig, n2orig};
for (int dir=0; dir<2; ++dir)
{
cgrid[dir].SetSize(n_orig[dir] + 1);
cgrid[dir][0] = 0;
for (int ii = 0; ii < n_orig[dir]; ++ii)
{
const int iir = parentEdgeRev[dir] ? n_orig[dir] - 1 - ii : ii;
int d = 1; // refinement factor
if (kvf_coarse.size() > 0)
{
d = kvf_coarse[kvi[dir]][iir];
}
else if (kvf.size() > 0)
{
d = kvf[kvi[dir]][iir];
}
cgrid[dir][ii + 1] = cgrid[dir][ii] + d;
}
}
MFEM_ASSERT(cgrid[0][n_orig[0]] == n1 && cgrid[1][n_orig[1]] == n2, "");
bool allset = true;
bool hasSlaveFaces = false;
bool hasAuxFace = false;
for (int ii=0; ii<=n_orig[0]; ++ii)
{
const int i = cgrid[0][ii];
for (int jj=0; jj<=n_orig[1]; ++jj)
{
const int j = cgrid[1][jj];
if (gridVertex(i,j) < 0)
{
allset = false;
}
else if (0 < i && i < n1 && 0 < j && j < n2)
{
hasSlaveFaces = true;
}
}
}
auto SetFacePairOnGridRange = [&](int i0, int i1, int j0, int j1)
{
std::array<int, 4> cv{gridVertex(i0, j0), gridVertex(i1, j0),
gridVertex(i1, j1), gridVertex(i0, j1)};
const std::pair<int, int> childPair = QuadrupleToPair(cv);
// min(cv) may be negative, if gridVertex is not set everywhere.
if (childPair.first < 0) { return; }
const int d0 = i1 - i0;
const int d1 = j1 - j0;
const bool childPairTopo = v2f.count(childPair) > 0;
if (childPairTopo)
{
const int childFace = v2f.at(childPair);
const int ori = GetFaceOrientation(patchTopo, childFace, cv);
// ori gives the orientation and index of cv matching the first
// vertex of childFace.
facePairs.emplace_back(
FacePairInfo{cv[0], parentFace, SlaveFaceInfo{childFace,
ori, {i0, j0}, {d0, d1}}});
}
else
{
// Check whether the parent face is on the boundary.
const Mesh::FaceInformation faceInfo = patchTopo->GetFaceInformation(
parentFace);
const bool bdryParentFace = faceInfo.IsBoundary();
if (!allset && !bdryParentFace)
{
hasAuxFace = true;
// Check whether childPair is in auxFaces.
if (auxv2f.count(childPair) == 0)
{
// Create a new auxiliary face
auxv2f[childPair] = auxFaces.size();
AuxiliaryFace auxFace;
for (int k=0; k<4; ++k) { auxFace.v[k] = cv[k]; }
auxFace.parent = parentFace;
// Orientation is defined as 0 for a new auxiliary face.
auxFace.ori = 0;
auxFace.ksi0[0] = i0;
auxFace.ksi0[1] = j0;
auxFace.ksi1[0] = i1;
auxFace.ksi1[1] = j1;
auxFaces.push_back(auxFace);
facePairs.emplace_back(
FacePairInfo{cv[0], parentFace,
SlaveFaceInfo{FlipIndexSign(auxv2f[childPair]),
0, {i0, j0}, {d0, d1}}});
}
}
}
};
// Loop over child faces and set facePairs, as well as auxiliary faces.
for (int ii=0; ii<n_orig[0]; ++ii)
{
const int i = cgrid[0][ii];
const int i1 = cgrid[0][ii + 1];
for (int jj=0; jj<n_orig[1]; ++jj)
{
const int j = cgrid[1][jj];
const int j1 = cgrid[1][jj + 1];
SetFacePairOnGridRange(i, i1, j, j1);
}
}
// Loop over child boundary edges and set edgePairs.
for (int dir=1; dir<=2; ++dir)
{
const int ne = dir == 1 ? n1 : n2;
for (int s=0; s<2; ++s) // Loop over 2 sides for this direction.
{
const int parentEdge = parentEdges[dir == 1 ? 2*s : (2*s) + 1];
const bool reverse_p = parentEdgeRev[dir == 1 ? 2*s : (2*s) + 1];
// Sides with s=1 are reversed in defining parentEdgeRev.
const bool reverse = s == 0 ? reverse_p : !reverse_p;
const bool parentVisited = visitedParentEdges.count(parentEdge) > 0;
if (!parentVisited)
{
edgePairOS[parentEdge] = edgePairs.size();
edgePairs.resize(edgePairs.size() + ne);
}
int tvprev = -1;
int kiprev = -1;
bool lagTV = false;
int firstEdge = -1;
int os_e = 0;
// Loop edges in direction `dir`
for (int e_orig = 0; e_orig < n_orig[dir-1]; ++e_orig)
{
const int e_i = os_e;
const int e_orig_rev = reverse ? n_orig[dir-1] - 1 - e_orig : e_orig;
int de = rf;
if (kvf_coarse.size() > 0)
{
de = kvf_coarse[kvi[dir - 1]][e_orig_rev];
}
else if (kvf.size() > 0)
{
de = kvf[kvi[dir - 1]][e_orig_rev];
}
os_e += de;
// For both directions, side s=0 has increasing indices and s=1
// has decreasing indices.
const int i0 = e_i;
const int i1 = e_i + de;
// Edge index with respect to the master edge.
const int e_idx = reverse ? ne - e_i - de : e_i;
Array<int> cv(2);
if (dir == 1)
{
cv[0] = gridVertex(i0,s*n2);
cv[1] = gridVertex(i1,s*n2);
}
else
{
cv[0] = gridVertex((1-s)*n1, i0);
cv[1] = gridVertex((1-s)*n1, i1);
}
const int cv0 = cv[0];
int tv_int = -1; // Top-vertex interior to the master edge
int ki = -1; // Knot-span index of tv_int, w.r.t. the master edge
if (lagTV)
{
tv_int = tvprev;
ki = kiprev;
}
if (tvprev == -1)
{
kiprev = (i0 == 0 || i0 == ne) ? i1 : i0;
// Top-vertex interior to the master edge
tvprev = (i0 == 0 || i0 == ne) ? cv[1] : cv[0];
}
else if (e_i < ne - 1) // Don't set to the endpoint
{
kiprev = (tvprev == cv[0]) ? i1 : i0;
// Next interior vertex along the master edge
tvprev = (tvprev == cv[0]) ? cv[1] : cv[0];
}
if (!lagTV)
{
tv_int = tvprev;
ki = kiprev;
}
cv.Sort();
if (cv[0] < 0) // may occur if gridVertex is not set everywhere.
{
continue;
}
else if (firstEdge == -1)
{
firstEdge = e_i;
if (e_i > 0)
{
tv_int = cv0;
ki = i0;
tvprev = cv.Sum() - cv0; // cv1
kiprev = i1;
lagTV = true;
}
}
const int tv = (e_idx == ne - de) ? -1 : tv_int;
const int tvki = (e_idx == ne - de) ? -1 : (reverse ? ne - ki : ki);
const std::pair<int, int> edge_i(cv[0], cv[1]);
const int childEdge = v2e.at(edge_i);
if (tv == -1) { lagTV = true; }
if (!parentVisited)
{
// edgePairs is ordered starting from the vertex of lower index.
edgePairs[edgePairOS[parentEdge] + e_idx].Set(tv, tvki,
childEdge,
parentEdge);
}
else
{
// Consistency check
const int os = edgePairOS[parentEdge];
if (edgePairs[os + e_idx].child != childEdge ||
edgePairs[os + e_idx].parent != parentEdge)
{
consistent = false;
}
}
}
visitedParentEdges.insert(parentEdge);
}
} // dir
// Set auxiliary and patch-slave faces outside the set gridVertex. Here,
// patch-slave refers to a slave face that is a face of a neighboring
// patch and may contain multiple mesh faces. In general, there can be at
// most 8 = 3^2 - 1 such faces.
std::array<int, 4> gv1 = {0, r1min, r1max, n1};
std::array<int, 4> gv2 = {0, r2min, r2max, n2};
if (hasSlaveFaces && !allset)
{
for (int i=0; i<3; ++i)
for (int j=0; j<3; ++j)
{
// Skip the middle, which is covered by gridVertex.
if (i == 1 && j == 1) { continue; }
// Skip degenerate faces
if (gv1[i] == gv1[i+1] || gv2[j] == gv2[j+1]) { continue; }
// Define auxiliary face (gv1[i], gv1[i+1]) x (gv2[j], gv2[j+1])
SetFacePairOnGridRange(gv1[i], gv1[i+1], gv2[j], gv2[j+1]);
}
}
// Set auxiliary edges outside the gridVertex, on the boundary of the
// parent face. Note that auxiliary edges cannot simply be found as edges
// of auxiliary faces, because the faces above are defined only on parent
// faces listed in V2K data. Other auxiliary faces will be defined in
// FindAdditionalFacesSA by using auxiliary edges found below.
// Auxiliary edges in first and second directions
for (int d=0; d<2; ++d)
{
for (int i=0; i<3; ++i)
{
if (i == 1) // Skip the middle, which is covered by gridVertex.
{
continue;
}
for (int s=0; s<2; ++s) // Loop over 2 sides in this direction
{
// Set gv1 (d == 0) or gv2 (d == 1) for this edge.
// Set range of set knot-span indices for this edge.
int rmin = -1;
int rmax = -1;
const int n_d = d == 0 ? n1 : n2;
for (int j=1; j<n_d; ++j)
{
const int tv = d == 0 ? gridVertex(j,s*n2) :
gridVertex((1-s)*n1,j);
if (tv >= 0)
{
if (rmin == -1)
{
// Initialize range
rmin = j;
rmax = j;
}
else
{
rmin = std::min(rmin, j);
rmax = std::max(rmax, j);
}
}
}
if (rmax == -1)
{
// No vertices set in gridVertex on the interior of this edge.
continue;
}
if (d == 0)
{
gv1[1] = rmin;
gv1[2] = rmax;
}
else
{
gv2[1] = rmin;
gv2[2] = rmax;
}
const int pid = d == 0 ? 2*s : (2*s) + 1; // Parent index
const bool reverse_p = parentEdgeRev[pid];
// Sides with s=1 are reversed in defining parentEdgeRev.
const bool reverse = s == 0 ? reverse_p : !reverse_p;
// Define an auxiliary edge (gv1[i], gv1[i+1])
Array<int> cv(2);
std::array<int, 2> ki;
if (d == 0)
{
cv[0] = gridVertex(gv1[i],s*n2);
cv[1] = gridVertex(gv1[i+1],s*n2);
ki[0] = gv1[i];
ki[1] = gv1[i+1];
}
else
{
cv[0] = gridVertex((1-s)*n1,gv2[i]);
cv[1] = gridVertex((1-s)*n1,gv2[i+1]);
ki[0] = gv2[i];
ki[1] = gv2[i+1];
}
if (cv[0] == cv[1])
{
continue;
}
// Top-vertex interior to the master edge.
const int tv = i == 0 ? cv[1] : cv[0];
const int tvki_f = i == 0 ? ki[1] : ki[0]; // face index
const int tvki = reverse ? n_d - tvki_f : tvki_f; // edge index
cv.Sort();
MFEM_ASSERT(cv[0] >= 0, "");
const std::pair<int, int> childPair(cv[0], cv[1]);
const bool childPairTopo = v2e.count(childPair) > 0;
if (!childPairTopo)
{
const int pv0 = d == 0 ? gridVertex(0,s*n2) :
gridVertex((1-s)*n1,0);
const int pv1 = d == 0 ? gridVertex(n1,s*n2) :
gridVertex((1-s)*n1,n2);
const std::pair<int, int> parentPair(pv0 < pv1 ? pv0 : pv1,
pv0 < pv1 ? pv1 : pv0);
const int parentEdge = v2e.at(parentPair);
MFEM_ASSERT(parentEdges[pid] == parentEdge, "");
// Check whether childPair is in auxEdges.
if (auxv2e.count(childPair) == 0)
{
const int knotIndex0 = (d == 0) ? gv1[i] : gv2[i];
const int knotIndex1 = (d == 0) ? gv1[i+1] : gv2[i+1];
// Create a new auxiliary edge
auxv2e[childPair] = auxEdges.size();
auxEdges.emplace_back(AuxiliaryEdge{pv0 < pv1 ?
parentEdge :
FlipIndexSign(parentEdge),
{childPair.first, childPair.second},
{knotIndex0, knotIndex1}});
}
const bool start = (i == 0 && !reverse) || (i != 0 && reverse);
int end_idx = kvf_coarse.size() > 0 ?
(start ? 0 : kvf_coarse[kvi[d]].Size() - 1) : 0;
int de = kvf_coarse.size() > 0 ? kvf_coarse[kvi[d]][end_idx] : rf;
if (kvf.size() > 0 && kvf_coarse.size() == 0)
{
end_idx = start ? 0 : kvf[kvi[d]].Size() - 1;
de = kvf[kvi[d]][end_idx];
}
const int e_idx_i = i == 0 ? 0 : n_d - de;
const int e_idx = reverse ? n_d - de - e_idx_i : e_idx_i;
const EdgePairInfo ep_e((e_idx == n_d - de) ? -1 : tv,
(e_idx == n_d - de) ? -1 : tvki,
FlipIndexSign(auxv2e[childPair]),
parentEdge);
const bool unset = !edgePairs[edgePairOS[parentEdge] + e_idx].isSet;
if (unset)
{
edgePairs[edgePairOS[parentEdge] + e_idx] = ep_e;
}
else
{
// Verify matching
MFEM_ASSERT(edgePairs[edgePairOS[parentEdge] + e_idx] == ep_e, "");
}
}
else // childPairTopo == true, so this edge is a slave edge.
{
const int childEdge = v2e.at(childPair);
const int pv0 = d == 0 ? gridVertex(0,s*n2) : gridVertex((1-s)*n1,0);
const int pv1 = d == 0 ? gridVertex(n1,s*n2) : gridVertex((1-s)*n1,n2);
const std::pair<int, int> parentPair(pv0 < pv1 ? pv0 : pv1,
pv0 < pv1 ? pv1 : pv0);
const int parentEdge = v2e.at(parentPair);
MFEM_ASSERT(parentEdges[pid] == parentEdge, "");
const bool start = (i == 0 && !reverse) || (i != 0 && reverse);
int end_idx = kvf_coarse.size() > 0 ?
(start ? 0 : kvf_coarse[kvi[d]].Size() - 1) : 0;
int de = kvf_coarse.size() > 0 ? kvf_coarse[kvi[d]][end_idx] : rf;
if (kvf.size() > 0 && kvf_coarse.size() == 0)
{
end_idx = start ? 0 : kvf[kvi[d]].Size() - 1;
de = kvf[kvi[d]][end_idx];
}
const int e_idx_i = i == 0 ? 0 : n_d - de;
const int e_idx = reverse ? n_d - de - e_idx_i : e_idx_i;
const int tv_e = (e_idx == n_d - de) ? -1 : tv;
const int tv_ki = (e_idx == n_d - de) ? -1 : tvki;
const EdgePairInfo ep_e(tv_e, tv_ki, childEdge, parentEdge);
#ifdef MFEM_DEBUG
const bool unset =
!edgePairs[edgePairOS[parentEdge] + e_idx].isSet;
const bool matching =
edgePairs[edgePairOS[parentEdge] + e_idx] == ep_e;
MFEM_ASSERT(unset || matching, "");
#endif
edgePairs[edgePairOS[parentEdge] + e_idx] = ep_e;
}
}
}
}
if (hasSlaveFaces || hasAuxFace) { masterFaces.insert(parentFace); }
} // loop over parents
MFEM_VERIFY(consistent, "");
}
void NCNURBSExtension::GetAuxFaceToPatchTable(Array2D<int> &auxface2patch)
{
auxface2patch.SetSize(auxFaces.size(), 2);
if (auxFaces.size() == 0) { return; }
auxface2patch = -1;
const int dim = Dimension();
bool consistent = true;
for (int p=0; p<num_structured_patches; ++p)
{
Array<int> faces, orient;
if (dim == 2) { patchTopo->GetElementEdges(p, faces, orient); }
else { patchTopo->GetElementFaces(p, faces, orient); }
for (auto face : faces)
{
const bool isMaster = dim == 2 ? masterEdgeToId.count(face) > 0 :
masterFaceToId.count(face) > 0;
if (isMaster) // If a master face
{
const int mid = dim == 2 ? masterEdgeToId.at(face) :
masterFaceToId.at(face);
const std::vector<int> &slaves = dim == 2 ? masterEdgeInfo[mid].slaves :
masterFaceInfo[mid].slaves;
for (auto s : slaves)
{
if (s < 0)
{
// Auxiliary face.
const int aux = FlipIndexSign(s);
if (auxface2patch(aux, 0) >= 0)
{
if (auxface2patch(aux, 1) != -1) { consistent = false; }
auxface2patch(aux, 1) = p;
}
else
{
auxface2patch(aux, 0) = p;
}
}
}
}
}
}
MFEM_VERIFY(consistent, "");
}
void NCNURBSExtension::GetSlaveFaceToPatchTable(Array2D<int> &sface2patch)
{
const int dim = Dimension();
const int numUnique = dim == 2 ? slaveEdgesUnique.Size() :
slaveFacesUnique.Size();
sface2patch.SetSize(numUnique, 2);
if (numUnique == 0) { return; }
sface2patch = -1;
bool consistent = true;
for (int p=0; p<num_structured_patches; ++p)
{
Array<int> faces, orient;
if (dim == 2) { patchTopo->GetElementEdges(p, faces, orient); }
else { patchTopo->GetElementFaces(p, faces, orient); }
for (auto face : faces)
{
const bool isMaster = dim == 2 ? masterEdgeToId.count(face) > 0 :
masterFaceToId.count(face) > 0;
if (isMaster) // If a master face
{
const int mid = dim == 2 ? masterEdgeToId.at(face) :
masterFaceToId.at(face);
const std::vector<int> &slaves = dim == 2 ? masterEdgeInfo[mid].slaves :
masterFaceInfo[mid].slaves;
for (auto id : slaves)
{
if (id >= 0)
{
const int s = dim == 2 ? slaveEdges[id] : slaveFaces[id].index;
const int u = dim == 2 ? slaveEdgesToUnique[s] :
slaveFacesToUnique[s];
if (sface2patch(u, 0) >= 0)
{
if (sface2patch(u, 1) != -1) { consistent = false; }
sface2patch(u, 1) = p;
}
else
{
sface2patch(u, 0) = p;
}
}
}
}
}
}
MFEM_VERIFY(consistent, "");
}
void RemapKnotIndex(bool rev, const Array<int> &rf, int &k)
{
const int ne = rf.Size();
const int k0 = k;
k = 0;
for (int p=0; p<k0; ++p)
{
const int rp = rev ? ne - 1 - p : p;
k += rf[rp];
}
}
void NCNURBSExtension::UpdateAuxiliaryKnotSpans(const Array<int> &rf)
{
for (auto auxEdge : auxEdges)
{
const int p = auxEdge.parent;
const int parent = UnsignIndex(p);
const int kv = KnotInd(parent);
for (int i=0; i<2; ++i)
{
RemapKnotIndex(false, kvf[kv], auxEdge.ksi[i]);
}
}
for (auto auxFace : auxFaces)
{
Array<int> pv;
std::array<int, 4> quad;
patchTopo->GetFaceVertices(auxFace.parent, pv);
MFEM_ASSERT(pv.Size() == 4, "");
for (int i=0; i<4; ++i) { quad[i] = pv[i]; }
// The face with vertices (pv0, pv1, pv2, pv3) is defined as a parent face.
const std::pair<int, int> parentPair = QuadrupleToPair(quad);
const std::array<int, 2> kv = parentToKV.at(parentPair);
RemapKnotIndex(false, kvf[kv[0]], auxFace.ksi0[0]);
RemapKnotIndex(false, kvf[kv[0]], auxFace.ksi1[0]);
RemapKnotIndex(false, kvf[kv[1]], auxFace.ksi0[1]);
RemapKnotIndex(false, kvf[kv[1]], auxFace.ksi1[1]);
}
}
const int NURBSExtension::unsetFactor;
void NCNURBSExtension::LoadFactorsForKV(const std::string &filename)
{
if (kvf_coarse.size() == 0) { kvf_coarse = kvf; }
if (kvf.size() == 0) { kvf.resize(NumOfKnotVectors); }
for (int kv=0; kv<NumOfKnotVectors; ++kv)
{
kvf[kv].SetSize(knotVectors[kv]->GetNE());
kvf[kv] = unsetFactor;
}
if (filename.empty()) { return; }
ifstream f(filename);
int nkv;
f >> nkv;
for (int i=0; i<nkv; ++i)
{
int kv, nf, rf;
f >> kv >> nf;
MFEM_ASSERT(nf == 1, ""); // TODO: support input of multiple factors.
kvf[kv] = unsetFactor;
for (int j=0; j<nf; ++j) { f >> rf; }
for (int j=0; j<kvf[kv].Size(); ++j) { kvf[kv][j] = rf; }
}
f.close();
}
int NCNURBSExtension::AuxiliaryEdgeNE(int aux_edge)
{
const int signedParentEdge = auxEdges[aux_edge].parent;
const int ki0 = auxEdges[aux_edge].ksi[0];
const int ki1raw = auxEdges[aux_edge].ksi[1];
const int ki1 = ki1raw == -1 ? KnotVec(UnsignIndex(signedParentEdge))->GetNE()
: ki1raw;
return ki1 - ki0;
}
// parentVerts are ordered with ascending knot-span index in parent edge, with
// knots from lower edge endpoint vertex to higher.
void NCNURBSExtension::SlaveEdgeToParent(int se, int parent,
const Array<int> &os,
const std::vector<int> &parentVerts,
Array<int> &edges)
{
Array<int> sev(2);
if (se < 0) // Auxiliary edge
{
for (int i=0; i<2; ++i) { sev[i] = auxEdges[FlipIndexSign(se)].v[i]; }
}
else
{
patchTopo->GetEdgeVertices(se, sev);
}
// Number of slave and auxiliary edges, not mesh edges
const int nedge = parentVerts.size() + 1;
MFEM_ASSERT((int) parentVerts.size() + 2 == os.Size(), "");
Array<int> parentEndpoints;
patchTopo->GetEdgeVertices(parent, parentEndpoints);
bool found = false;
for (int i=0; i<nedge; ++i)
{
const bool first = (i == 0);
const bool last = (i == nedge - 1);
const int v0 = first ? parentEndpoints[0] : parentVerts[i - 1];
const int v1 = last ? parentEndpoints[1] : parentVerts[i];
if (sev[0] == v0 && sev[1] == v1)
{
found = true;
MFEM_ASSERT(edges.Size() == os[i + 1] - os[i], "");
for (int j=0; j<edges.Size(); ++j) { edges[j] = os[i] + j; }
}
else if (sev[0] == v1 && sev[1] == v0)
{
found = true;
MFEM_ASSERT(edges.Size() == os[i + 1] - os[i], "");
for (int j=0; j<edges.Size(); ++j) { edges[j] = os[i + 1] - 1 - j; }
}
}
MFEM_VERIFY(found, "");
}
void NCNURBSExtension::GetMasterEdgePieceOffsets(int mid, Array<int> &os)
{
const int np = masterEdgeInfo[mid].slaves.size();
MFEM_VERIFY(np > 0, "");
os.SetSize(np + 1);
os[0] = 0;
for (int i=0; i<np; ++i)
{
const int p = masterEdgeInfo[mid].slaves[i];
const int s = slaveEdges[p];
int nes = 0;
if (s >= 0)
{
nes = knotVectors[KnotInd(s)]->GetNE();
}
else
{
nes = AuxiliaryEdgeNE(FlipIndexSign(s));
}
os[i+1] = os[i] + nes;
}
}
Array<int> CoarseToFineFactors(const Array<int> &rf)
{
Array<int> frf(rf.Sum());
int os = 0;
for (auto f : rf)
{
for (int i=0; i<f; ++i)
{
frf[os + i] = f;
}
os += f;
}
return frf;
}
int NCNURBSExtension::SetPatchFactors(int p)
{
constexpr char dirEdges3D[3][4] = {{0, 2, 4, 6}, {1, 3, 5, 7}, {8, 9, 10, 11}};
constexpr char dirEdges2D[2][2] = {{0, 2}, {1, 3}};
Array<int> edges, oedges;
patchTopo->GetElementEdges(p, edges, oedges);
const int dim = Dimension();
const int nedge = dim == 3 ? 4 : 2;
int dirSet = 0;
bool partialChange = false;
bool consistent = true;
auto SetFactorsDirection = [&](int j, int os_final, int af, Array<int> &rf)
{
if (af == unsetFactor) { return; }
if (rf.Size() == 0)
{
rf.SetSize(os_final);
rf = unsetFactor;
}
if (rf[j] != unsetFactor && af != rf[j]) { consistent = false; }
rf[j] = af;
};
auto SetFactorsEdge = [&](int j, int rf, Array<int> &pf)
{
if (rf == unsetFactor) { return; }
if (pf[j] != unsetFactor && pf[j] != rf) { consistent = false; }
pf[j] = rf;
};
auto LoopEdgesForDirection = [&](int d, Array<int> &rf, bool first)
{
for (int i=0; i<nedge; ++i)
{
const int edgeIndex = dim == 3 ? dirEdges3D[d][i] : dirEdges2D[d][i];
const int edge = edges[edgeIndex];
const bool isMaster = IsMasterEdge(edge);
const int kv = KnotInd(edge);
const bool rev = KnotSign(edge) < 0;
if (first)
{
const int nfe = knotVectors[kv]->GetNE();
const bool fullSize = kvf.size() > 0 && kvf[kv].Size() == nfe;
const Array<int> rf_i = fullSize ? kvf[kv] :
CoarseToFineFactors(kvf[kv]);
if (rf_i.Size() > 0 && rf.Size() == 0) { rf = rf_i; }
}
else
{
if (kvf[kv] != rf) { partialChange = true; }
kvf[kv] = rf;
}
if (isMaster)
{
// Check whether slave edges have factors set.
const int mid = masterEdgeToId.at(edge);
const int numPieces = masterEdgeInfo[mid].slaves.size();
Array<int> os;
GetMasterEdgePieceOffsets(mid, os);
for (int piece=0; piece<numPieces; ++piece)
{
const int e = masterEdgeInfo[mid].slaves[piece];
const int s = slaveEdges[e];
Array<int> parentEdges;
Array<int> *pf; // Refinement factors for this piece
if (s >= 0) // Slave edge
{
const int kvs = KnotInd(s);
parentEdges.SetSize(kvf[kvs].Size());
pf = &kvf[kvs];
}
else // Aux edge
{
const int aux_edge = FlipIndexSign(s);
if (auxef[aux_edge].Size() == 0)
{
auxef[aux_edge].SetSize(AuxiliaryEdgeNE(aux_edge));
auxef[aux_edge] = unsetFactor;
}
parentEdges.SetSize(auxef[aux_edge].Size());
pf = &auxef[aux_edge];
}
if (first && parentEdges.Size() == 0) { continue; }
SlaveEdgeToParent(s, edge, os, masterEdgeInfo[mid].vertices, parentEdges);
MFEM_ASSERT(parentEdges.Size() == os[piece + 1] - os[piece], "");
for (int j = os[piece]; j < os[piece + 1]; ++j)
{
const int jj = parentEdges[j - os[piece]];
const int jr = rev ? rf.Size() - 1 - jj : jj;
if (first)
{
SetFactorsDirection(jr, os[numPieces],
(*pf)[j - os[piece]], rf);
}
else
{
SetFactorsEdge(j - os[piece], rf[jr], *pf);
}
}
}
}
}
};
for (int d=0; d<dim; ++d)
{
// Find the array of factors for direction d
Array<int> rf;
LoopEdgesForDirection(d, rf, true);
if (rf.Size() == 0) { continue; } // This direction is unset
// Set the same factor for all knotvectors in direction d.
LoopEdgesForDirection(d, rf, false);
if (rf.Min() > unsetFactor) { dirSet += static_cast<int>(pow(2, d)); }
}
MFEM_VERIFY(consistent, "");
return partialChange ? FlipIndexSign(dirSet) : dirSet;
}
void NCNURBSExtension::PropagateFactorsForKV(int rf_default)
{
const int dim = Dimension();
if (dim == 1 || num_structured_patches < 1)
{
for (size_t i=0; i<kvf.size(); ++i)
{
kvf[i] = rf_default;
}
return;
}
// Note that a slave edge can be a patchTopo edge (nonnegative index) or an
// AuxiliaryEdge (negative index). A slave edge can be contained in multiple
// overlapping master edges.
// First, set slaveEdgesUnique.
{
slaveEdgesUnique.SetSize(0);
slaveEdgesUnique.Reserve(slaveEdges.size());
for (auto s : slaveEdges)
{
if (slaveEdgesToUnique.count(s) == 0)
{
slaveEdgesToUnique[s] = slaveEdgesUnique.Size();
slaveEdgesUnique.Append(s);
}
}
}
// Set slaveFacesUnique.
{
slaveFacesUnique.SetSize(0);
slaveFacesUnique.Reserve(slaveFaces.size());
for (auto s : slaveFaces)
{
if (slaveFacesToUnique.count(s.index) == 0)
{
slaveFacesToUnique[s.index] = slaveFacesUnique.Size();
slaveFacesUnique.Append(s.index);
}
}
}
// Initialize a set of patches to visit, using face-neighbors of the first
// patch.
const Table *face2elem = patchTopo->GetFaceToElementTable();
Array2D<int> auxface2patch, sface2patch;
GetAuxFaceToPatchTable(auxface2patch);
GetSlaveFaceToPatchTable(sface2patch);
Array<int> faces, orient;
auto faceNeighbors = [&](int p, std::set<int> &nghb)
{
if (dim == 2) { patchTopo->GetElementEdges(p, faces, orient); }
else { patchTopo->GetElementFaces(p, faces, orient); }
for (auto face : faces)
{
Array<int> row;
face2elem->GetRow(face, row);
const bool isSlave = dim == 2 ? slaveEdgesToUnique.count(
face) > 0 : slaveFacesToUnique.count(face) > 0;
if (isSlave)
{
const int u = dim == 2 ? slaveEdgesToUnique[face] :
slaveFacesToUnique[face];
for (int i=0; i<2; ++i)
{
const int elem = sface2patch(u, i);
if (elem >= 0 && elem != p) { nghb.insert(elem); }
}
}
for (auto elem : row) { nghb.insert(elem); }
}
};
auto masterFaceNeighbors = [&](int p, std::set<int> &nghb)
{
if (dim == 2) { patchTopo->GetElementEdges(p, faces, orient); }
else { patchTopo->GetElementFaces(p, faces, orient); }
for (auto face : faces)
{
const bool isMaster = dim == 2 ? masterEdgeToId.count(face) > 0 :
masterFaceToId.count(face) > 0;
if (isMaster) // If a master face
{
const int mid = dim == 2 ? masterEdgeToId.at(face) :
masterFaceToId.at(face);
const std::vector<int> &slaves =
dim == 2 ? masterEdgeInfo[mid].slaves : masterFaceInfo[mid].slaves;
for (auto s : slaves)
{
if (s < 0)
{
// Auxiliary face.
const int aux = FlipIndexSign(s);
for (int i=0; i<2; ++i)
{
const int patch = auxface2patch(aux, i);
if (patch >= 0) { nghb.insert(patch); }
}
}
else
{
// Slave face in patchTopo.
Array<int> row;
face2elem->GetRow(s, row);
for (auto elem : row) { nghb.insert(elem); }
}
}
}
}
};
const int npatchall = patches.Size();
Array<int> patchState(npatchall);
patchState = 0;
auxef.resize(auxEdges.size());
std::set<int> nextPatches, unchanged;
const int dirAllSet = dim == 3 ? 7 : 3;
int lastChanged = 0;
int iter = 0;
bool done = false;
while (iter < 100 && !done)
{
// Start each iteration at the patch last changed
nextPatches.clear();
nextPatches.insert(lastChanged);
std::set<int> visited; // Visit each patch only once per iteration
iter++;
while (nextPatches.size() > 0)
{
const int p = *nextPatches.begin();
nextPatches.erase(p);
visited.insert(p);
const int dirSetSigned = SetPatchFactors(p);
const bool partialChange = dirSetSigned < 0;
const int dirSet = UnsignIndex(dirSetSigned);
const bool changed = (patchState[p] != dirSet) || partialChange;
patchState[p] = dirSet;
// Find neighbors of patch p
std::set<int> neighbors;
// First, find neighbors sharing a conforming face, via face2elem.
faceNeighbors(p, neighbors);
// Second, find neighbors sharing a slave/auxiliary face in patchTopo.
masterFaceNeighbors(p, neighbors);
// Add neighbors not done to nextPatches. Note that a patch can be
// added to nextPatches on multiple iterations, to propagate factors in
// different directions, on multiple sweeps.
for (auto n : neighbors)
{
if (n < npatchall && n != p && patchState[n] != dirAllSet &&
visited.count(n) == 0)
{
nextPatches.insert(n);
}
}
if (changed)
{
unchanged.erase(p);
lastChanged = p;
}
else
{
unchanged.insert(p);
}
if (unchanged.size() == (size_t) npatchall)
{
// Make another pass through all patches to check for changes
for (int i=0; i<npatchall; ++i)
{
const int dirSetSigned_i = SetPatchFactors(i);
const bool partialChange_i = dirSetSigned_i < 0;
const int dirSet_i = partialChange_i ?
FlipIndexSign(dirSetSigned_i) : dirSetSigned_i;
const bool changed_i = (patchState[i] != dirSet_i) ||
partialChange_i;
patchState[p] = dirSet_i;
if (changed_i)
{
unchanged.erase(i);
lastChanged = i;
}
}
}
if (unchanged.size() == (size_t) npatchall)
{
done = true;
break;
}
}
}
delete face2elem;
// For any unset entries of kvf, set to default refinement factor rf_default.
for (size_t i=0; i<kvf.size(); ++i)
{
if (kvf[i].Size() == 0)
{
kvf[i].SetSize(knotVectors[i]->GetNE());
kvf[i] = rf_default;
}
else
{
for (int j=0; j<kvf[i].Size(); ++j)
{
if (kvf[i][j] == unsetFactor) { kvf[i][j] = rf_default; }
}
}
if (knotVectors[i]->spacing)
{
PiecewiseSpacingFunction *pws = dynamic_cast<PiecewiseSpacingFunction*>
(knotVectors[i]->spacing.get());
if (pws)
{
Array<int> pwn = pws->RelativePieceSizes();
const bool rev = pws->GetReverse();
const int np = pwn.Size();
const int f = kvf[i].Size() / pwn.Sum();
MFEM_ASSERT(kvf[i].Size() == f * pwn.Sum(), "");
Array<int> os(np + 1);
os[0] = 0;
for (int j=1; j<np+1; ++j)
{
const int jp = rev ? np - j : j - 1;
os[j] = os[j-1] + (f * pwn[jp]);
}
Array<int> pwf(np);
for (int j=0; j<np; ++j)
{
pwf[j] = kvf[i][os[j]];
for (int r=os[j]+1; r<os[j+1]; ++r)
{
MFEM_ASSERT(kvf[i][r] == pwf[j], "");
}
}
pws->ScalePartition(pwf, true);
}
}
}
}
void UpdateFactors(Array<int> &f)
{
Array<int> rf(f.Sum());
int os = 0;
for (int i=0; i<f.Size(); ++i)
{
const int f_i = f[i];
for (int j=0; j<f_i; ++j) { rf[os + j] = f_i; }
os += f_i;
}
MFEM_ASSERT(os == rf.Size(), "");
f = rf;
}
void NCNURBSExtension::RefineWithKVFactors(int rf,
const std::string &kvf_filename,
bool coarsened)
{
if (ref_factors.Size() > 0)
{
MFEM_VERIFY(ref_factors.Size() == Dimension(), "");
for (int i=0; i<ref_factors.Size(); ++i) { ref_factors[i] *= rf; }
}
else
{
ref_factors.SetSize(Dimension());
ref_factors = rf;
}
LoadFactorsForKV(kvf_filename);
PropagateFactorsForKV(rf);
Refine(coarsened);
}
void NCNURBSExtension::ReadCoarsePatchCP(std::istream &input)
{
input >> num_structured_patches;
const int maxOrder = mOrders.Max();
// For degree maxOrder, there are 2*(maxOrder + 1) knots for a single
// element, and the number of control points in each dimension is
// 2*(maxOrder + 1) - maxOrder - 1
const int ncp1D = maxOrder + 1;
const int ncp = static_cast<int>(pow(ncp1D, Dimension()));
patchCP.SetSize(num_structured_patches, ncp, Dimension());
for (int p=0; p<num_structured_patches; ++p)
for (int i=0; i<ncp; ++i)
for (int j=0; j<Dimension(); ++j) { input >> patchCP(p, i, j); }
}
void NCNURBSExtension::PrintCoarsePatches(std::ostream &os)
{
const int maxOrder = mOrders.Max();
const int patchCP_size1 = patchCP.GetSize1();
MFEM_VERIFY(patchCP_size1 == num_structured_patches || patchCP_size1 == 0,
"");
if (patchCP_size1 == 0) { return; }
// For degree maxOrder, there are 2*(maxOrder + 1) knots for a single element,
// and the number of control points in each dimension is
// 2*(maxOrder + 1) - maxOrder - 1
const int ncp1D = maxOrder + 1;
const int ncp = static_cast<int>(pow(ncp1D, Dimension()));
os << "\npatch_cp\n" << num_structured_patches << "\n";
for (int p=0; p<num_structured_patches; ++p)
{
for (int i=0; i<ncp; ++i)
{
os << patchCP(p, i, 0);
for (int j=1; j<Dimension(); ++j)
{
os << ' ' << patchCP(p, i, j);
}
os << '\n';
}
}
}
void ApplyFineToCoarse(const Array<int> &f, Array<int> &c)
{
MFEM_ASSERT(f.Size() == c.Sum(), "");
bool consistent = true;
int os = 0;
for (int j=0; j<c.Size(); ++j)
{
const int cf = c[j];
const int ff = f[os];
for (int i=0; i<cf; ++i)
{
if (f[os + i] != ff) { consistent = false; }
}
c[j] *= ff;
os += cf;
}
MFEM_VERIFY(consistent, "");
}
void NCNURBSExtension::UpdateCoarseKVF()
{
if (kvf_coarse.size() == 0) { return; }
for (int k=0; k<NumOfKnotVectors; ++k)
{
ApplyFineToCoarse(kvf[k], kvf_coarse[k]);
}
}
int GetFaceOrientation(const Mesh *mesh, const int face,
const std::array<int, 4> &verts)
{
Array<int> fverts;
mesh->GetFaceVertices(face, fverts);
MFEM_ASSERT(fverts.Size() == 4, "");
// Verify that verts and fvert have the same entries as sets, by deep-copying
// and sorting.
{
Array<int> s1(4);
Array<int> s2(fverts);
for (int i=0; i<4; ++i) { s1[i] = verts[i]; }
s1.Sort(); s2.Sort();
MFEM_ASSERT(s1 == s2, "");
}
// Find the shift of the first vertex.
int s = -1;
for (int i=0; i<4; ++i)
{
if (verts[i] == fverts[0]) { s = i; }
}
// Check whether ordering is reversed.
const bool rev = verts[(s + 1) % 4] != fverts[1];
if (rev) { s = FlipIndexSign(s); } // Reversed order is encoded by the sign.
return s;
}
// The 2D array `a` is of size n1*n2, with index j + n2*i corresponding to (i,j)
// with the fast index j, for 0 <= i < n1 and 0 <= j < n2. We assume that j is
// the fast index in (i,j). The orientation is encoded by ori, defining a shift
// and relative direction, such that a quad face F1, on which the ordering of
// `a` is based, has vertex with index `shift` matching vertex 0 of the new quad
// face F2, on which the new ordering of `a` should be based. For more details,
// see GetFaceOrientation.
bool Reorder2D(int ori, std::array<int, 2> &s0)
{
const int shift = UnsignIndex(ori);
// Shift is an F1 index in the counter-clockwise ordering of 4 quad vertices.
// Now find the (i,j) indices of this index, with i,j in {0,1}.
const int s0i = (shift == 0 || shift == 3) ? 0 : 1;
const int s0j = (shift < 2) ? 0 : 1;
s0[0] = s0i;
s0[1] = s0j;
// Determine whether the dimensions of F1 and F2 are reversed. Do this by
// finding the (i,j) indices of s1, which is the next vertex on F1.
const int shift1 = ori < 0 ? shift - 1: shift + 1;
const int s1 = (shift1 + 4) % 4;
const int s1i = (s1 == 0 || s1 == 3) ? 0 : 1;
const bool dimReverse = s0i == s1i;
return dimReverse;
}
void GetInverseShiftedDimensions2D(int signedShift, int sm, int sn, int &m,
int &n)
{
const bool rev = (signedShift < 0);
const int shift = UnsignIndex(signedShift);
MFEM_ASSERT(0 <= shift && shift < 4, "");
// We consider 8 cases for the possible values of rev and shift.
if (rev)
{
if (shift == 0)
{
// New: 3 2 Old: 1 2
// 0 1 0 3
n = sm;
m = sn;
}
else if (shift == 1)
{
// New: 3 2 Old: 2 3
// 0 1 1 0
m = sm;
n = sn;
}
else if (shift == 2)
{
// New: 3 2 Old: 3 0
// 0 1 2 1
n = sm;
m = sn;
}
else // shift == 3
{
// New: 3 2 Old: 0 1
// 0 1 3 2
m = sm;
n = sn;
}
}
else
{
if (shift == 0)
{
// New: 3 2 Old: 3 2
// 0 1 0 1
m = sm;
n = sn;
}
else if (shift == 1)
{
// New: 3 2 Old: 0 3
// 0 1 1 2
n = sm;
m = sn;
}
else if (shift == 2)
{
// New: 3 2 Old: 1 0
// 0 1 2 3
m = sm;
n = sn;
}
else // shift == 3
{
// New: 3 2 Old: 2 1
// 0 1 3 0
n = sm;
m = sn;
}
}
}
void GetShiftedGridPoints2D(int m, int n, int i, int j, int signedShift,
int& sm, int& sn, int& si, int& sj)
{
const bool rev = (signedShift < 0);
const int shift = UnsignIndex(signedShift);
MFEM_ASSERT(0 <= shift && shift < 4, "");
// (0,0) <= (i,j) < (m,n) are old indices, and old vertex [shift] maps
// to new vertex 0 in counter-clockwise quad ordering.
// We consider 8 cases for the possible values of rev and shift.
if (rev)
{
if (shift == 0)
{
// New: 3 2 Old: 1 2
// 0 1 0 3
sm = n;
sn = m;
si = j;
sj = i;
}
else if (shift == 1)
{
// New: 3 2 Old: 2 3
// 0 1 1 0
sm = m;
sn = n;
si = m - 1 - i;
sj = j;
}
else if (shift == 2)
{
// New: 3 2 Old: 3 0
// 0 1 2 1
sm = n;
sn = m;
si = n - 1 - j;
sj = m - 1 - i;
}
else // shift == 3
{
// New: 3 2 Old: 0 1
// 0 1 3 2
sm = m;
sn = n;
si = i;
sj = n - 1 - j;
}
}
else
{
if (shift == 0)
{
// New: 3 2 Old: 3 2
// 0 1 0 1
sm = m;
sn = n;
si = i;
sj = j;
}
else if (shift == 1)
{
// New: 3 2 Old: 0 3
// 0 1 1 2
sm = n;
sn = m;
si = j;
sj = m - 1 - i;
}
else if (shift == 2)
{
// New: 3 2 Old: 1 0
// 0 1 2 3
sm = m;
sn = n;
si = m - 1 - i;
sj = n - 1 - j;
}
else // shift == 3
{
// New: 3 2 Old: 2 1
// 0 1 3 0
sm = n;
sn = m;
si = n - 1 - j;
sj = i;
}
}
}
// Given a quadruple in q, return the pair (q_i, q_j), where q_i is the minimum
// entry of q, and q_j is the entry two indices away from q_i. When q contains
// indices of the vertices of a quadrilateral, the returned pair represents the
// unique diagonal touching the vertex of minimum index, which is a more concise
// way of representing the quadrilateral, facilitating the search of faces.
std::pair<int, int> QuadrupleToPair(const std::array<int, 4> &q)
{
const auto qmin = std::min_element(q.begin(), q.end());
const int idmin = std::distance(q.begin(), qmin);
return std::pair<int, int>(q[idmin], q[(idmin + 2) % 4]);
}
void VertexToKnotSpan::SetSize(int dimension, int numVertices)
{
dim = dimension;
MFEM_ASSERT((dim == 2 || dim == 3) && numVertices > 0, "Invalid size");
data.SetSize(numVertices, dim == 3 ? 7 : 4);
}
void VertexToKnotSpan::SetVertex2D(int index, int v, int ks,
const std::array<int, 2> &pv)
{
data(index,0) = v;
data(index,1) = ks;
data(index,2) = pv[0];
data(index,3) = pv[1];
}
void VertexToKnotSpan::SetVertex3D(int index, int v,
const std::array<int, 2> &ks,
const std::array<int, 4> &pv)
{
data(index,0) = v;
data(index,1) = ks[0];
data(index,2) = ks[1];
data(index,3) = pv[0];
data(index,4) = pv[1];
data(index,5) = pv[2];
data(index,6) = pv[3];
}
void VertexToKnotSpan::SetKnotSpan2D(int index, int ks)
{
data(index,1) = ks;
}
void VertexToKnotSpan::SetKnotSpans3D(int index, const std::array<int, 2> &ks)
{
data(index,1) = ks[0];
data(index,2) = ks[1];
}
void VertexToKnotSpan::GetVertex2D(int index, int &v, int &ks,
std::array<int, 2> &pv) const
{
v = data(index,0);
ks = data(index,1);
pv[0] = data(index,2);
pv[1] = data(index,3);
}
void VertexToKnotSpan::GetVertex3D(int index, int &v, std::array<int, 2> &ks,
std::array<int, 4> &pv) const
{
v = data(index,0);
ks[0] = data(index,1);
ks[1] = data(index,2);
pv[0] = data(index,3);
pv[1] = data(index,4);
pv[2] = data(index,5);
pv[3] = data(index,6);
}
void VertexToKnotSpan::Print(std::ostream &os) const
{
const int nv = data.NumRows();
const int m = data.NumCols();
os << nv << "\n";
for (int i = 0; i < nv; i++)
{
os << data(i,0);
for (int j = 1; j < m; j++)
{
os << " " << data(i,j);
}
os << "\n";
}
}
std::pair<int, int> VertexToKnotSpan::GetVertexParentPair(int index) const
{
if (dim == 3)
{
std::array<int, 4> pv;
for (int i=0; i<4; ++i) { pv[i] = data(index, 3 + i); }
// The face with vertices (pv[0], pv[1], pv[2], pv[3]) is defined as a
// parent face.
return QuadrupleToPair(pv);
}
int c0 = data(index, 2);
int c1 = data(index, 3);
if (c0 > c1) { std::swap(c0, c1); }
return std::pair<int, int>(c0, c1);
}
void NCNURBSExtension::UniformRefinement(const Array<int> &rf)
{
MFEM_VERIFY(!nonconformingPT,
"NURBS NC-patch meshes cannot use this method of refinement");
if (ref_factors.Size())
{
MFEM_VERIFY(ref_factors.Size() == rf.Size(), "");
for (int i=0; i<rf.Size(); ++i) { ref_factors[i] *= rf[i]; }
}
else
{
ref_factors = rf;
}
Refine(false, &rf);
}
void NCNURBSExtension::Refine(bool coarsened, const Array<int> *rf)
{
const int maxOrder = mOrders.Max();
const int dim = Dimension();
for (int p = 0; p < patches.Size(); p++)
{
if (nonconformingPT)
{
std::vector<Array<int>> prf(dim);
Array<KnotVector*> pkv(dim);
Array<int> edges, orient;
patchTopo->GetElementEdges(p, edges, orient);
if (dim == 3)
{
constexpr char e3[3] = {0, 3, 8};
for (int i=0; i<3; ++i)
{
prf[i] = kvf[KnotInd(edges[e3[i]])];
pkv[i] = knotVectors[KnotInd(edges[e3[i]])];
}
}
else
{
MFEM_VERIFY(dim == 2, "");
for (int i=0; i<2; ++i)
{
prf[i] = kvf[KnotInd(edges[i])];
pkv[i] = knotVectors[KnotInd(edges[i])];
}
}
if (p >= num_structured_patches)
{
for (int i=0; i<dim; ++i)
{
// Collapse prf[i] to a single factor
MFEM_VERIFY(prf[i].IsConstant(), "");
prf[i].SetSize(1);
}
}
patches[p]->UpdateSpacingPartitions(pkv);
patches[p]->UniformRefinement(prf, coarsened, maxOrder);
}
else
{
patches[p]->UniformRefinement(*rf);
}
}
if (nonconformingPT)
{
patchTopo->ncmesh->RefineVertexToKnotSpan(kvf, knotVectors, parentToKV);
UpdateAuxiliaryKnotSpans(ref_factors);
UpdateCoarseKVF();
}
}
void NCNURBSExtension::SetDofToPatch()
{
dof2patch.SetSize(NumOfDofs);
dof2patch = -1;
const int dim = Dimension();
if (dim == 1) { return; }
Array<int> edges, faces, orient;
const int np = patchTopo->GetNE();
for (int p = 0; p < np; p++)
{
patchTopo->GetElementEdges(p, edges, orient);
for (auto e : edges)
{
if (masterEdges.count(e) > 0)
{
Array<int> mdof;
GetMasterEdgeDofs(true, e, mdof);
for (auto dof : mdof) { dof2patch[dof] = p; }
}
}
if (dim == 3)
{
patchTopo->GetElementFaces(p, faces, orient);
for (auto f : faces)
{
if (masterFaces.count(f) > 0)
{
Array2D<int> mdof;
GetMasterFaceDofs(true, f, mdof);
for (int j=0; j<mdof.NumCols(); ++j)
for (int k=0; k<mdof.NumRows(); ++k)
{
dof2patch[mdof(k,j)] = p;
}
}
}
}
}
}
// This function assumes a uniform number of control points per element in kv.
int GetNCPperEdge(const KnotVector *kv)
{
const int ne = kv->GetNE();
// Total number of CP on edge, excluding vertex CP.
const int totalEdgeCP = kv->GetNCP() - 2 - ne + 1;
const int perEdgeCP = totalEdgeCP / ne;
MFEM_VERIFY(perEdgeCP * ne == totalEdgeCP, "");
return perEdgeCP;
}
void NCNURBSExtension::GenerateOffsets()
{
const int nv = patchTopo->GetNV();
const int ne = patchTopo->GetNEdges();
const int nf = patchTopo->GetNFaces();
const int np = patchTopo->GetNE();
int meshCounter, spaceCounter, dim = Dimension();
std::set<int> reversedParents;
if (patchTopo->ncmesh)
{
// Note that master or slave entities exist only for a mesh with
// vertex_parents, not for the vertex_to_knotspan case. Currently, a mesh
// is not allowed to have both cases, see the MFEM_VERIFY below.
const NCMesh::NCList& nce = patchTopo->ncmesh->GetNCList(1);
const NCMesh::NCList& ncf = patchTopo->ncmesh->GetNCList(2);
masterEdges.clear();
masterFaces.clear();
slaveEdges.clear();
slaveFaces.clear();
masterEdgeToId.clear();
masterFaceToId.clear();
MFEM_VERIFY(nce.masters.Size() > 0 ||
patchTopo->ncmesh->GetVertexToKnotSpan().Size() > 0, "");
MFEM_VERIFY(!(nce.masters.Size() > 0 &&
patchTopo->ncmesh->GetVertexToKnotSpan().Size() > 0), "");
std::vector<EdgePairInfo> edgePairs;
std::vector<FacePairInfo> facePairs;
std::vector<int> parentFaces, parentVerts;
std::vector<std::array<int, 2>> parentSize;
const bool is3D = dim == 3;
std::map<std::pair<int, int>, int> v2f;
if (patchTopo->ncmesh->GetVertexToKnotSpan().Size() > 0)
{
// Intersections of master edges may not be edges in patchTopo->ncmesh,
// so we represent them in auxEdges, to account for their vertices and
// DOFs.
{
int vert_index[2];
const NCMesh::NCList& EL = patchTopo->ncmesh->GetEdgeList();
for (auto edgeID : EL.conforming)
{
patchTopo->ncmesh->GetEdgeVertices(edgeID, vert_index);
v2e[std::pair<int, int> (vert_index[0], vert_index[1])] = edgeID.index;
}
}
if (is3D)
{
Array<int> vert;
for (int i=0; i<patchTopo->GetNumFaces(); ++i)
{
patchTopo->GetFaceVertices(i, vert);
const int vmin = vert.Min();
const int idmin = vert.Find(vmin);
v2f[std::pair<int, int> (vert[idmin], vert[(idmin + 2) % 4])] = i;
}
}
const VertexToKnotSpan &v2k = patchTopo->ncmesh->GetVertexToKnotSpan();
if (is3D)
ProcessVertexToKnot3D(v2k, v2f, parentSize, edgePairs,
facePairs, parentFaces, parentVerts);
else
{
ProcessVertexToKnot2D(v2k, reversedParents, edgePairs);
}
} // if using vertex_to_knotspan
const int numMasters = is3D ? ncf.masters.Size() : nce.masters.Size();
if (is3D)
{
for (auto masterFace : ncf.masters)
{
masterFaces.insert(masterFace.index);
}
}
for (auto masterEdge : nce.masters)
{
masterEdges.insert(masterEdge.index);
}
masterEdgeIndex.SetSize(masterEdges.size());
int cnt = 0;
for (auto medge : masterEdges)
{
masterEdgeIndex[cnt] = medge;
masterEdgeToId[medge] = cnt;
cnt++;
}
MFEM_VERIFY(cnt == masterEdgeIndex.Size(), "");
Array<int> masterFaceIndex(parentFaces.size());
// Note that masterFaces is a subset of parentFaces.
MFEM_VERIFY(masterFaces.size() <= parentFaces.size(), "");
cnt = 0;
for (auto mface : parentFaces)
{
masterFaceIndex[cnt] = mface;
masterFaceToId[mface] = cnt;
cnt++;
}
MFEM_VERIFY(cnt == masterFaceIndex.Size(), "");
masterEdgeInfo.clear();
masterEdgeInfo.resize(masterEdgeIndex.Size());
masterFaceInfo.clear();
masterFaceInfo.resize(masterFaceIndex.Size());
if (patchTopo->ncmesh->GetVertexToKnotSpan().Size() > 0)
{
// Note that this is used in 2D and 3D.
const int npairs = edgePairs.size();
for (int i=0; i<npairs; ++i)
{
if (!edgePairs[i].isSet) { continue; }
const int v = edgePairs[i].v;
const int s = edgePairs[i].child;
const int m = edgePairs[i].parent;
const int ksi = edgePairs[i].ksi;
slaveEdges.push_back(s);
const int mid = masterEdgeToId[m];
const int si = slaveEdges.size() - 1;
masterEdgeInfo[mid].slaves.push_back(si);
if (v >= 0)
{
masterEdgeInfo[mid].vertices.push_back(v);
masterEdgeInfo[mid].ks.push_back(ksi);
}
}
ProcessFacePairs(0, 0, parentSize, parentVerts, facePairs);
}
for (int i=0; i<nce.slaves.Size(); ++i)
{
const NCMesh::Slave& slaveEdge = nce.slaves[i];
int vert_index[2];
patchTopo->ncmesh->GetEdgeVertices(slaveEdge, vert_index);
slaveEdges.push_back(slaveEdge.index);
const int mid = masterEdgeToId[slaveEdge.master];
masterEdgeInfo[mid].slaves.push_back(i);
}
if (!is3D)
{
for (int m=0; m<numMasters; ++m)
{
// Order the slaves of each master edge, from the first to second
// vertex of the master edge.
const int numSlaves = masterEdgeInfo[m].slaves.size();
MFEM_ASSERT(numSlaves > 0, "");
int mvert[2];
int svert[2];
patchTopo->ncmesh->GetEdgeVertices(nce.masters[m], mvert);
std::vector<int> orderedSlaves(numSlaves);
std::set<int> used;
int vi = mvert[0];
for (int s=0; s<numSlaves; ++s)
{
// Find the slave edge containing vertex vi.
// This has quadratic complexity, but numSlaves is small.
orderedSlaves[s] = -1;
for (int t=0; t<numSlaves; ++t)
{
const int sid = masterEdgeInfo[m].slaves[t];
if (used.count(sid) > 0) { continue; }
patchTopo->ncmesh->GetEdgeVertices(nce.slaves[sid], svert);
if (svert[0] == vi || svert[1] == vi)
{
orderedSlaves[s] = sid;
used.insert(sid);
break;
}
}
MFEM_ASSERT(orderedSlaves[s] >= 0, "");
// Update vi to the next vertex
vi = (svert[0] == vi) ? svert[1] : svert[0];
if (s < numSlaves - 1)
{
masterEdgeInfo[m].vertices.push_back(vi);
masterEdgeInfo[m].ks.push_back(-1); // Used only in 3D.
}
}
masterEdgeInfo[m].slaves = orderedSlaves;
} // m
}
if (is3D)
{
// Remove edges from masterEdges if they do not have any slave edges.
std::vector<int> falseMasterEdges;
for (auto me : masterEdges)
{
const int mid = masterEdgeToId.at(me);
if (masterEdgeInfo[mid].slaves.size() <= 1)
{
falseMasterEdges.push_back(me);
}
}
for (auto me : falseMasterEdges) { masterEdges.erase(me); }
// Find slave and auxiliary faces not yet defined.
const int nfp0 = facePairs.size();
std::set<int> addParentFaces;
FindAdditionalFacesSA(v2f, addParentFaces, facePairs);
cnt = parentFaces.size();
const int npf0 = cnt;
for (auto pf : addParentFaces)
{
if (masterFaces.count(pf) == 0)
{
masterFaces.insert(pf);
masterFaceIndex.Append(pf);
masterFaceToId[pf] = cnt;
cnt++;
{
Array<int> edges, ori, verts;
patchTopo->GetFaceEdges(pf, edges, ori);
patchTopo->GetFaceVertices(pf, verts);
MFEM_ASSERT(edges.Size() == 4 && verts.Size() == 4, "");
parentSize.emplace_back(std::array<int, 2>
{
KnotVec(edges[0])->GetNE(),
KnotVec(edges[1])->GetNE()
});
masterFaceInfo.push_back(
MasterFaceInfo(KnotVec(edges[0])->GetNE(),
KnotVec(edges[1])->GetNE()));
for (int i=0; i<4; ++i) { parentVerts.push_back(verts[i]); }
}
}
}
MFEM_VERIFY(cnt == masterFaceIndex.Size(), "");
ProcessFacePairs(nfp0, npf0, parentSize, parentVerts, facePairs);
}
}
for (auto rp : reversedParents)
{
masterEdgeInfo[masterEdgeToId[rp]].Reverse();
}
Array<int> edges, orient;
v_meshOffsets.SetSize(nv);
e_meshOffsets.SetSize(ne);
f_meshOffsets.SetSize(nf);
p_meshOffsets.SetSize(np);
v_spaceOffsets.SetSize(nv);
e_spaceOffsets.SetSize(ne);
f_spaceOffsets.SetSize(nf);
p_spaceOffsets.SetSize(np);
// Get vertex offsets
for (meshCounter = 0; meshCounter < nv; meshCounter++)
{
v_meshOffsets[meshCounter] = meshCounter;
v_spaceOffsets[meshCounter] = meshCounter;
}
spaceCounter = meshCounter;
// Get edge offsets
for (int e = 0; e < ne; e++)
{
e_meshOffsets[e] = meshCounter;
e_spaceOffsets[e] = spaceCounter;
if (masterEdges.count(e) == 0) // If not a master edge
{
meshCounter += KnotVec(e)->GetNE() - 1;
spaceCounter += KnotVec(e)->GetNCP() - 2;
}
}
const int nauxe = auxEdges.size();
aux_e_meshOffsets.SetSize(nauxe + 1);
aux_e_spaceOffsets.SetSize(nauxe + 1);
for (int e = 0; e < nauxe; e++)
{
aux_e_meshOffsets[e] = meshCounter;
aux_e_spaceOffsets[e] = spaceCounter;
// Find the number of elements and CP in this auxiliary edge, which is
// defined only on part of the master edge knotvector.
const int signedParentEdge = auxEdges[e].parent;
const int ki0 = auxEdges[e].ksi[0];
const int ki1raw = auxEdges[e].ksi[1];
const int parentEdge = UnsignIndex(signedParentEdge);
const int masterNE = KnotVec(parentEdge)->GetNE();
const int ki1 = ki1raw == -1 ? masterNE : ki1raw;
const int perEdgeCP = GetNCPperEdge(KnotVec(e));
const int auxne = ki1 - ki0;
MFEM_ASSERT(auxne > 0, "");
meshCounter += auxne - 1;
spaceCounter += (auxne * perEdgeCP) + auxne - 1;
}
aux_e_meshOffsets[nauxe] = meshCounter;
aux_e_spaceOffsets[nauxe] = spaceCounter;
// Get face offsets
for (int f = 0; f < nf; f++)
{
f_meshOffsets[f] = meshCounter;
f_spaceOffsets[f] = spaceCounter;
if (masterFaces.count(f) == 0) // If not a master face
{
patchTopo->GetFaceEdges(f, edges, orient);
meshCounter +=
(KnotVec(edges[0])->GetNE() - 1) *
(KnotVec(edges[1])->GetNE() - 1);
spaceCounter +=
(KnotVec(edges[0])->GetNCP() - 2) *
(KnotVec(edges[1])->GetNCP() - 2);
}
}
const int nauxf = auxFaces.size();
aux_f_meshOffsets.SetSize(nauxf + 1);
aux_f_spaceOffsets.SetSize(nauxf + 1);
for (int f = 0; f < nauxf; f++)
{
aux_f_meshOffsets[f] = meshCounter;
aux_f_spaceOffsets[f] = spaceCounter;
const int parentFace = auxFaces[f].parent;
patchTopo->GetFaceEdges(parentFace, edges, orient);
// Number of control points per edge, in first and second directions.
const int perEdgeCP0 = GetNCPperEdge(KnotVec(edges[0]));
const int perEdgeCP1 = GetNCPperEdge(KnotVec(edges[1]));
const int auxne0 = auxFaces[f].ksi1[0] - auxFaces[f].ksi0[0];
const int auxne1 = auxFaces[f].ksi1[1] - auxFaces[f].ksi0[1];
meshCounter += (auxne0 - 1) * (auxne1 - 1);
spaceCounter += ((auxne0 * perEdgeCP0) + auxne0 - 1) *
((auxne1 * perEdgeCP1) + auxne1 - 1);
}
aux_f_meshOffsets[nauxf] = meshCounter;
aux_f_spaceOffsets[nauxf] = spaceCounter;
// Get patch offsets
GetPatchOffsets(meshCounter, spaceCounter);
NumOfVertices = meshCounter;
NumOfDofs = spaceCounter;
SetDofToPatch();
}
} // namespace mfem