ENH: additional handling for multiply-connected finite-area edges (#2771)

- multiply-connected edges can arise at the centre of a "star"
  connection or because the patch faces are actually baffles.

- In the serial case these internal edges are also rather dubious in
  terms of modelling. However, when they are split across multiple
  processors there can only be a single processor-to-processor
  connectivity.

  We don't necessary have enough information to know how things should
  be connected, so connect pair-wise as the first remedial solution

- Any extra dangle edges are relegated to an 'ignore' faPatch
  to tag as needing different handling.
This commit is contained in:
Mark Olesen
2023-05-05 15:49:43 +02:00
parent b62e4b06fa
commit b3fa59db92
2 changed files with 351 additions and 111 deletions

View File

@ -28,10 +28,47 @@ License
#include "faMesh.H"
#include "faPatchData.H"
#include "processorPolyPatch.H"
#include "emptyFaPatch.H"
#include "ignoreFaPatch.H"
#include "processorFaPatch.H"
#include "processorPolyPatch.H"
#include "foamVtkLineWriter.H"
// * * * * * * * * * * * * * * * Local Functions * * * * * * * * * * * * * * //
namespace Foam
{
// Write edges in VTK format
template<class PatchType>
static void vtkWritePatchEdges
(
const PatchType& p,
const labelList& selectEdges,
const fileName& outputPath,
const word& outputName
)
{
edgeList dumpEdges(p.edges(), selectEdges);
vtk::lineWriter writer
(
p.localPoints(),
dumpEdges,
outputPath/outputName
);
writer.writeGeometry();
// CellData
writer.beginCellData();
writer.writeProcIDs();
writer.close();
}
} // End namespace Foam
// * * * * * * * * * * * * * * * Member Functions * * * * * * * * * * * * * //
void Foam::faMesh::addFaPatches
@ -156,7 +193,7 @@ Foam::faPatchList Foam::faMesh::createPatchList
{
auto& patchDef = faPatchDefs.emplace_back();
patchDef.name_ = emptyPatchName;
patchDef.type_ = "empty";
patchDef.type_ = emptyFaPatch::typeName_();
}
label nWarnUndefinedPatch(5);
@ -183,6 +220,14 @@ Foam::faPatchList Foam::faMesh::createPatchList
}
}
// Placeholder for any undefined edges
const label ignorePatchIndex = faPatchDefs.size();
{
auto& patchDef = faPatchDefs.emplace_back();
patchDef.name_ = "_ignore_edges_";
patchDef.type_ = ignoreFaPatch::typeName_();
}
// ----------------------------------------------------------------------
// Get edge connections (in globally consistent ordering)
@ -213,7 +258,9 @@ Foam::faPatchList Foam::faMesh::createPatchList
if (!a.valid() || !b.valid())
{
// Skip checking pairs where either is not valid
// If either is invalid, mark as an 'ignore' edge
patchDefLookup[connecti] = ignorePatchIndex;
patchDefsUsed.insert(ignorePatchIndex);
continue;
}
else if (a.is_finiteArea())
@ -289,17 +336,34 @@ Foam::faPatchList Foam::faMesh::createPatchList
patchDefsUsed.insert(bestPatchDefi);
}
// Remove undefPatchIndex if not actually needed anywhere
bool reportBadEdges = false;
// Skip undefPatchIndex if not actually needed anywhere
if (!returnReduceOr(patchDefsUsed.found(undefPatchIndex)))
{
faPatchDefs.remove(undefPatchIndex);
faPatchDefs[undefPatchIndex].clear();
}
else
{
patchDefsUsed.insert(undefPatchIndex); // Parallel consistency
reportBadEdges = true;
}
// Report locations of undefined edges
// Skip ignorePatchIndex if not actually needed anywhere
if (!returnReduceOr(patchDefsUsed.found(ignorePatchIndex)))
{
faPatchDefs[ignorePatchIndex].clear();
}
else
{
patchDefsUsed.insert(ignorePatchIndex); // Parallel consistency
reportBadEdges = true;
}
// Report locations of undefined edges
if (reportBadEdges)
{
badEdges.clear();
forAll(patchDefLookup, connecti)
{
@ -324,8 +388,11 @@ Foam::faPatchList Foam::faMesh::createPatchList
Pout<< "Undefined connection: "
<< "(patch:" << a.realPatchi()
<< " face:" << a.meshFacei()
<< " proc:" << a.procNo()
<< ") and (patch:" << b.realPatchi()
<< " face:" << b.meshFacei() << ") patch:"
<< " face:" << b.meshFacei()
<< " proc:" << b.procNo()
<< ") patch:"
<<
(
a.realPatchi() >= 0
@ -360,27 +427,102 @@ Foam::faPatchList Foam::faMesh::createPatchList
if (nWarnUndefinedPatch)
{
edgeList dumpEdges(patch().edges(), badEdges.sortedToc());
labelList selectEdges(badEdges.sortedToc());
word outputName("faMesh-construct.undefEdges");
vtk::lineWriter writer
vtkWritePatchEdges
(
patch().localPoints(),
dumpEdges,
fileName
(
mesh().time().globalPath()
/ ("faMesh-construct.undefEdges")
)
patch(),
selectEdges,
mesh().time().globalPath(),
outputName
);
writer.writeGeometry();
InfoInFunction
<< "(debug) wrote " << outputName << nl;
}
}
}
// CellData
writer.beginCellData();
writer.writeProcIDs();
// Report locations of undefined edges
if (reportBadEdges)
{
badEdges.clear();
forAll(patchDefLookup, connecti)
{
if (patchDefLookup[connecti] == ignorePatchIndex)
{
const auto& connection = bndEdgeConnections[connecti];
const auto& a = connection.first();
const auto& b = connection.second();
if (a.is_localProc() && a.is_finiteArea())
{
badEdges.insert(a.patchEdgei());
}
else if (b.is_localProc() && b.is_finiteArea())
{
badEdges.insert(b.patchEdgei());
}
if (badEdges.size() <= nWarnUndefinedPatch)
{
Pout<< "Illegal connection: "
<< "(patch:" << a.realPatchi()
<< " face:" << a.meshFacei()
<< " proc:" << a.procNo()
<< ") and (patch:" << b.realPatchi()
<< " face:" << b.meshFacei()
<< " proc:" << b.procNo()
<< ") patch:"
<<
(
a.realPatchi() >= 0
? pbm[a.realPatchi()].name()
: word::null
)
<< " and patch:"
<<
(
b.realPatchi() >= 0
? pbm[b.realPatchi()].name()
: word::null
)
<< nl;
}
}
}
if (returnReduceOr(badEdges.size()))
{
// Report directly as Info, not InfoInFunction
// since it can also be an expected result when
// nWarnUndefinedPatch == 0
Info<< nl
<< "Had "
<< returnReduce(badEdges.size(), sumOp<label>()) << '/'
<< returnReduce(patch().nBoundaryEdges(), sumOp<label>())
<< " illegal edge connections, added to "
<< faPatchDefs[ignorePatchIndex].name_ << nl << nl
<< "==> Could indicate a non-manifold patch geometry" << nl
<< nl;
if (nWarnUndefinedPatch)
{
labelList selectEdges(badEdges.sortedToc());
word outputName("faMesh-construct.ignoreEdges");
vtkWritePatchEdges
(
patch(),
selectEdges,
mesh().time().globalPath(),
outputName
);
InfoInFunction
<< "(debug) wrote " << writer.output().name() << nl;
<< "(debug) wrote " << outputName << nl;
}
}
}
@ -426,13 +568,13 @@ Foam::faPatchList Foam::faMesh::createPatchList
if (a.is_localProc() && a.is_finiteArea())
{
selectEdges.append(a.patchEdgei());
selectEdges.push_back(a.patchEdgei());
}
else if (b.is_localProc() && b.is_finiteArea())
{
selectEdges.append(b.patchEdgei());
selectEdges.push_back(b.patchEdgei());
}
else
else if (a.valid() && b.valid())
{
FatalErrorInFunction
<< "Error in programming logic" << nl
@ -486,11 +628,11 @@ Foam::faPatchList Foam::faMesh::createPatchList
if (a.is_localProc())
{
selectEdges.append(a.patchEdgei());
selectEdges.push_back(a.patchEdgei());
}
else if (b.is_localProc())
{
selectEdges.append(b.patchEdgei());
selectEdges.push_back(b.patchEdgei());
}
else
{
@ -516,6 +658,11 @@ Foam::faPatchList Foam::faMesh::createPatchList
for (faPatchData& patchDef : faPatchDefs)
{
if (!patchDef.good())
{
continue;
}
newPatches.set
(
nPatches,
@ -532,7 +679,7 @@ Foam::faPatchList Foam::faMesh::createPatchList
newPatches[nPatches].resetEdges(std::move(patchDef.edgeLabels_));
++nPatches;
}
newPatches.resize(nPatches);
if (debug > 1)
{

View File

@ -68,6 +68,34 @@ static void printPatchEdges
}
}
// Write edges in VTK format
template<class PatchType>
static void vtkWritePatchEdges
(
const PatchType& p,
const labelList& selectEdges,
const fileName& outputPath,
const word& outputName
)
{
edgeList dumpEdges(p.edges(), selectEdges);
vtk::lineWriter writer
(
p.localPoints(),
dumpEdges,
outputPath/outputName
);
writer.writeGeometry();
// CellData
writer.beginCellData();
writer.writeProcIDs();
writer.close();
}
} // End namespace Foam
@ -90,6 +118,7 @@ Foam::faMesh::getBoundaryEdgeConnections() const
EdgeMap<label> edgeToBoundaryIndex(2*nBoundaryEdges);
labelHashSet badEdges(2*nBoundaryEdges);
labelHashSet danglingEdges(2*nBoundaryEdges);
{
// Local collection structure for accounting of patch pairs.
@ -163,40 +192,30 @@ Foam::faMesh::getBoundaryEdgeConnections() const
if (returnReduceOr(badEdges.size()))
{
edgeList dumpEdges(patch().edges(), badEdges.sortedToc());
labelList selectEdges(badEdges.sortedToc());
word outputName("faMesh-construct.nonManifoldEdges");
vtk::lineWriter writer
vtkWritePatchEdges
(
patch().localPoints(),
dumpEdges,
fileName
(
mesh().time().globalPath()
/ ("faMesh-construct.nonManifoldEdges")
)
patch(),
selectEdges,
mesh().time().globalPath(),
outputName
);
writer.writeGeometry();
// CellData
writer.beginCellData();
writer.writeProcIDs();
InfoInFunction
<< "(debug) wrote " << writer.output().name() << nl;
writer.close(); // Flush writer before raising FatalError
<< "(debug) wrote " << outputName << nl;
FatalErrorInFunction
<< "Boundary edges not singly connected: "
<< returnReduce(badEdges.size(), sumOp<label>()) << '/'
<< returnReduce(selectEdges.size(), sumOp<label>()) << '/'
<< nBoundaryEdges << nl;
printPatchEdges
(
FatalError,
patch(),
badEdges.sortedToc()
selectEdges
);
FatalError << abort(FatalError);
@ -322,40 +341,30 @@ Foam::faMesh::getBoundaryEdgeConnections() const
if (returnReduceOr(badEdges.size()))
{
edgeList dumpEdges(patch().edges(), badEdges.sortedToc());
labelList selectEdges(badEdges.sortedToc());
word outputName("faMesh-construct.invalidEdges");
vtk::lineWriter writer
vtkWritePatchEdges
(
patch().localPoints(),
dumpEdges,
fileName
(
mesh().time().globalPath()
/ ("faMesh-construct.invalidEdges")
)
patch(),
selectEdges,
mesh().time().globalPath(),
outputName
);
writer.writeGeometry();
// CellData
writer.beginCellData();
writer.writeProcIDs();
InfoInFunction
<< "(debug) wrote " << writer.output().name() << nl;
writer.close(); // Flush writer before raising FatalError
<< "(debug) wrote " << outputName << nl;
FatalErrorInFunction
<< "Boundary edges with missing/invalid neighbours: "
<< returnReduce(badEdges.size(), sumOp<label>()) << '/'
<< returnReduce(selectEdges.size(), sumOp<label>()) << '/'
<< nBoundaryEdges << nl;
printPatchEdges
(
FatalError,
patch(),
badEdges.sortedToc()
selectEdges
);
FatalError << abort(FatalError);
@ -546,9 +555,10 @@ Foam::faMesh::getBoundaryEdgeConnections() const
// Pick out gathered connections and add into primary bookkeeping
badEdges.clear();
danglingEdges.clear();
for (label cppEdgei = 0; cppEdgei < nCoupledEdges; ++cppEdgei)
{
const auto& gathered = gatheredConnections[cppEdgei];
auto& gathered = gatheredConnections[cppEdgei];
const label bndEdgei =
edgeToBoundaryIndex.lookup(cpp.meshEdge(cppEdgei), -1);
@ -558,7 +568,12 @@ Foam::faMesh::getBoundaryEdgeConnections() const
// A boundary finiteEdge edge (known from this side)
auto& connection = bndEdgeConnections[bndEdgei];
if (gathered.size() == 2)
if (gathered.size() == 1)
{
// Dangling edge!!
danglingEdges.insert(cppEdgei);
}
else if (gathered.size() == 2)
{
// Copy second side of connection
const auto& a = gathered[0];
@ -568,7 +583,57 @@ Foam::faMesh::getBoundaryEdgeConnections() const
}
else if (gathered.size() > 2)
{
// Multiply connected!! - this needs to be addressed
// Multiply connected!!
// ++nUnresolved;
// Extra safety (but should already be consistently ordered)
Foam::sort(gathered);
// These connections can arise at the centre of a
// "star" connection, or because the patch faces are
// actually baffles.
// We don't necessary have enough information to know how
// things should be connected, so connect pair-wise
// as the first remedial solution
const label myProci = UPstream::myProcNo();
label myIndex = -1;
label otherIndex = -1;
forAll(gathered, sloti)
{
if (gathered[sloti].procNo() == myProci)
{
myIndex = sloti;
otherIndex =
(
(sloti % 2)
? (sloti - 1) // ie, connect (1 -> 0)
: (sloti + 1) // ie, connect (0 -> 1)
);
break;
}
}
if
(
myIndex >= 0
&& otherIndex >= 0
&& otherIndex < gathered.size()
)
{
// Copy second side of connection
const auto& a = gathered[myIndex];
const auto& b = gathered[otherIndex];
connection.second() = (connection.first() == b) ? a : b;
}
// Mark as 'bad' even if somehow resolved. If we fail
// to make any connection, these will still be
// flagged later.
badEdges.insert(cppEdgei);
}
}
@ -607,6 +672,53 @@ Foam::faMesh::getBoundaryEdgeConnections() const
}
}
if (returnReduceOr(danglingEdges.size()))
{
WarningInFunction
<< nl << "Dangling edges detected" << endl;
// Print out edges as point pairs
// These are globally synchronised - so only output on master
constexpr label maxOutput = 10;
label nOutput = 0;
for (const label cppEdgei : danglingEdges.sortedToc())
{
const edge e(cpp.meshEdge(cppEdgei));
const auto& gathered = gatheredConnections[cppEdgei];
Info<< "connection: ";
gathered.writeList(Info) << nl;
Info<<" edge : "
<< cpp.points()[e.first()] << ' '
<< cpp.points()[e.second()] << nl;
++nOutput;
if (maxOutput > 0 && nOutput >= maxOutput)
{
Info<< " ... suppressing further output" << nl;
break;
}
}
labelList selectEdges(danglingEdges.sortedToc());
word outputName("faMesh-construct.danglingEdges");
vtkWritePatchEdges
(
cpp,
selectEdges,
mesh().time().globalPath(),
outputName
);
InfoInFunction
<< "(debug) wrote " << outputName << nl;
}
// Check missing/invalid
badEdges.clear();
@ -680,43 +792,33 @@ Foam::faMesh::getBoundaryEdgeConnections() const
// Verbose report of missing edges
if (returnReduceOr(badEdges.size()))
{
edgeList dumpEdges(patch().edges(), badEdges.sortedToc());
labelList selectEdges(badEdges.sortedToc());
word outputName("faMesh-construct.invalidEdges");
vtk::lineWriter writer
vtkWritePatchEdges
(
patch().localPoints(),
dumpEdges,
fileName
(
mesh().time().globalPath()
/ ("faMesh-construct.invalidEdges")
)
patch(),
selectEdges,
mesh().time().globalPath(),
outputName
);
writer.writeGeometry();
// CellData
writer.beginCellData();
writer.writeProcIDs();
InfoInFunction
<< "(debug) wrote " << writer.output().name() << nl;
writer.close(); // Flush writer before raising FatalError
<< "(debug) wrote " << outputName << nl;
FatalErrorInFunction
<< "Boundary edges with missing/invalid neighbours: "
<< returnReduce(badEdges.size(), sumOp<label>()) << '/'
<< returnReduce(selectEdges.size(), sumOp<label>()) << '/'
<< nBoundaryEdges << nl;
printPatchEdges
(
FatalError,
patch(),
badEdges.sortedToc()
selectEdges
);
FatalError << abort(FatalError);
// Delay until later... FatalError << abort(FatalError);
}
@ -806,35 +908,26 @@ void Foam::faMesh::setBoundaryConnections
}
}
edgeList dumpEdges(patch().edges(), badEdges.sortedToc());
labelList selectEdges(badEdges.sortedToc());
word outputName("faMesh-construct.invalidMatches");
vtk::lineWriter writer
vtkWritePatchEdges
(
patch().localPoints(),
dumpEdges,
fileName
(
mesh().time().globalPath()
/ ("faMesh-construct.invalidMatches")
)
patch(),
selectEdges,
mesh().time().globalPath(),
outputName
);
writer.writeGeometry();
// CellData
writer.beginCellData();
writer.writeProcIDs();
InfoInFunction
<< "(debug) wrote " << writer.output().name() << nl;
writer.close(); // Flush writer before raising FatalError
<< "(debug) wrote " << outputName << nl;
FatalErrorInFunction
<< "Did not properly match "
<< returnReduce(nInvalid, sumOp<label>())
<< " boundary edges" << nl
<< abort(FatalError);
<< " boundary edges" << nl;
// Delay until later... FatalError << abort(FatalError);
}
}