ENH: add global topology check in checkMesh and makeFaMesh (#2771)

- detect when boundary patches are multiply connected across edges

STYLE: initialize some faMesh values
This commit is contained in:
Mark Olesen
2023-05-03 16:01:59 +02:00
parent 1bef57d018
commit 783934ccad
8 changed files with 619 additions and 116 deletions

View File

@ -0,0 +1,164 @@
/*---------------------------------------------------------------------------*\
========= |
\\ / F ield | OpenFOAM: The Open Source CFD Toolbox
\\ / O peration |
\\ / A nd | www.openfoam.com
\\/ M anipulation |
-------------------------------------------------------------------------------
Copyright (C) 2023 OpenCFD Ltd.
-------------------------------------------------------------------------------
License
This file is part of OpenFOAM, distributed under GPL-3.0-or-later.
Description
Check topology of poly patches used for finite-area.
Input
mesh (polyMesh)
meshDefDict (system/faMeshDefintion)
\*---------------------------------------------------------------------------*/
// Preliminary checks
{
typedef typename uindirectPrimitivePatch::surfaceTopo TopoType;
const polyBoundaryMesh& pbm = mesh.boundaryMesh();
wordRes polyPatchNames;
meshDefDict.readIfPresent("polyMeshPatches", polyPatchNames);
const labelList patchIDs
(
pbm.patchSet
(
polyPatchNames,
false, // warnNotFound
true // useGroups
).sortedToc()
);
label nFaceLabels = 0;
for (const label patchi : patchIDs)
{
nFaceLabels += pbm[patchi].size();
}
labelList faceLabels(nFaceLabels);
nFaceLabels = 0;
for (const label patchi : patchIDs)
{
for (const label facei : pbm[patchi].range())
{
faceLabels[nFaceLabels] = facei;
++nFaceLabels;
}
}
uindirectPrimitivePatch pp
(
UIndirectList<face>(mesh.faces(), faceLabels),
mesh.points()
);
// Non-manifold
labelHashSet badEdges(pp.nEdges()/20);
labelHashSet* pointSetPtr = nullptr;
labelHashSet* badEdgesPtr = &badEdges;
bool foundError = false;
if (returnReduceAnd(pp.empty()))
{
// Empty
}
else if (UPstream::parRun())
{
const labelList meshEdges
(
pp.meshEdges(mesh.edges(), mesh.pointEdges())
);
// Parallel - use mesh edges
// - no check for point-pinch
// - no check for consistent orientation (if that is posible to
// check?)
// Count number of edge/face connections (globally)
labelList nEdgeConnections(mesh.nEdges(), Zero);
const labelListList& edgeFaces = pp.edgeFaces();
forAll(edgeFaces, edgei)
{
nEdgeConnections[meshEdges[edgei]] = edgeFaces[edgei].size();
}
// Synchronise across coupled edges
syncTools::syncEdgeList
(
mesh,
nEdgeConnections,
plusEqOp<label>(),
label(0) // null value
);
label labelTyp = TopoType::MANIFOLD;
forAll(meshEdges, edgei)
{
const label meshEdgei = meshEdges[edgei];
const label numNbrs = nEdgeConnections[meshEdgei];
if (numNbrs == 1)
{
//if (pointSetPtr) pointSetPtr->insert(mesh.edges()[meshEdgei]);
labelTyp = max(labelTyp, TopoType::OPEN);
}
else if (numNbrs == 0 || numNbrs > 2)
{
if (pointSetPtr) pointSetPtr->insert(mesh.edges()[meshEdgei]);
if (badEdgesPtr) badEdgesPtr->insert(edgei);
labelTyp = max(labelTyp, TopoType::ILLEGAL);
}
}
reduce(labelTyp, maxOp<label>());
foundError = (labelTyp == TopoType::ILLEGAL);
}
else
{
const TopoType pTyp = pp.surfaceType(badEdgesPtr);
foundError = (pTyp == TopoType::ILLEGAL);
}
if (foundError)
{
edgeList dumpEdges(pp.edges(), badEdges.sortedToc());
vtk::lineWriter writer
(
pp.localPoints(),
dumpEdges,
fileName
(
mesh.time().globalPath()
/ ("faMesh-construct.illegalEdges")
)
);
writer.writeGeometry();
// CellData
writer.beginCellData();
writer.writeProcIDs();
Info<< "Wrote "
<< returnReduce(dumpEdges.size(), sumOp<label>())
<< " bad edges: " << writer.output().name() << nl;
writer.close();
}
}
// ************************************************************************* //

View File

@ -16,10 +16,10 @@ Description
\*---------------------------------------------------------------------------*/
// Embed do-while to support early termination
if (doDecompose && Pstream::parRun())
if (doDecompose && UPstream::parRun())
do
{
faMeshReconstructor reconstructor(aMesh, IOobjectOption::READ_IF_PRESENT);
faMeshReconstructor reconstructor(aMesh, IOobjectOption::LAZY_READ);
if (!reconstructor.good())
{
@ -61,16 +61,16 @@ do
auto oldHandler = fileHandler(fileOperation::NewUncollated());
fileHandler().distributed(true);
if (Pstream::master())
if (UPstream::master())
{
haveMeshOnProc.set(Pstream::myProcNo());
haveMeshOnProc.set(UPstream::myProcNo());
subsetter.reset(new faMeshSubset(serialMesh));
const bool oldParRun = Pstream::parRun(false);
const bool oldParRun = UPstream::parRun(false);
objects = IOobjectList(serialMesh.time(), runTime.timeName());
Pstream::parRun(oldParRun);
UPstream::parRun(oldParRun);
}
// Restore old settings

View File

@ -21,12 +21,12 @@ Description
word outputName("finiteArea-edges.obj");
if (Pstream::parRun())
if (UPstream::parRun())
{
outputName = word
(
"finiteArea-edges-proc"
+ Foam::name(Pstream::myProcNo())
+ Foam::name(UPstream::myProcNo())
+ ".obj"
);
}

View File

@ -6,7 +6,7 @@
\\/ M anipulation |
-------------------------------------------------------------------------------
Copyright (C) 2016-2017 Wikki Ltd
Copyright (C) 2021-2022 OpenCFD Ltd.
Copyright (C) 2021-2023 OpenCFD Ltd.
-------------------------------------------------------------------------------
License
This file is part of OpenFOAM.
@ -51,7 +51,9 @@ Original Authors
#include "faMeshReconstructor.H"
#include "faMeshSubset.H"
#include "PtrListOps.H"
#include "foamVtkLineWriter.H"
#include "foamVtkIndPatchWriter.H"
#include "syncTools.H"
#include "OBJstream.H"
using namespace Foam;
@ -128,6 +130,8 @@ int main(int argc, char *argv[])
meshDefDict.add("emptyPatch", patchName, true);
}
// Preliminary checks
#include "checkPatchTopology.H"
// Create
faMesh aMesh(mesh, meshDefDict);

View File

@ -6,7 +6,7 @@
\\/ M anipulation |
-------------------------------------------------------------------------------
Copyright (C) 2011-2016 OpenFOAM Foundation
Copyright (C) 2017-2022 OpenCFD Ltd.
Copyright (C) 2017-2023 OpenCFD Ltd.
-------------------------------------------------------------------------------
License
This file is part of OpenFOAM.
@ -36,6 +36,7 @@ License
#include "IOmanip.H"
#include "emptyPolyPatch.H"
#include "processorPolyPatch.H"
#include "foamVtkLineWriter.H"
#include "vtkCoordSetWriter.H"
#include "vtkSurfaceWriter.H"
#include "checkTools.H"
@ -44,24 +45,34 @@ License
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
// NEWER CODE
// ~~~~~~~~~~
// Return true on error
template<class PatchType>
void Foam::checkPatch
bool Foam::checkPatch
(
const bool allGeometry,
const word& name,
const std::string& name,
const polyMesh& mesh,
const PatchType& pp,
const labelList& meshFaces,
const labelList& meshEdges,
pointSet& points
const labelUList& meshEdges,
labelHashSet* pointSetPtr,
labelHashSet* badEdgesPtr
)
{
if (badEdgesPtr)
{
badEdgesPtr->clear();
}
typedef typename PatchType::surfaceTopo TopoType;
bool foundError = false;
const label globalSize = returnReduce(pp.size(), sumOp<label>());
Info<< " "
<< setw(20) << name
<< setw(20) << name.c_str()
<< setw(9) << globalSize
<< setw(9) << returnReduce(pp.nPoints(), sumOp<label>());
@ -69,13 +80,169 @@ void Foam::checkPatch
{
Info<< setw(34) << "ok (empty)";
}
else if (Pstream::parRun())
else if (UPstream::parRun())
{
// Parallel - use mesh edges
// - no check for point-pinch
// - no check for consistent orientation (if that is posible to
// check?)
// Count number of edge/face connections (globally)
labelList nEdgeConnections(mesh.nEdges(), Zero);
const labelListList& edgeFaces = pp.edgeFaces();
forAll(edgeFaces, edgei)
{
nEdgeConnections[meshEdges[edgei]] = edgeFaces[edgei].size();
}
// Synchronise across coupled edges
syncTools::syncEdgeList
(
mesh,
nEdgeConnections,
plusEqOp<label>(),
label(0) // null value
);
label labelTyp = TopoType::MANIFOLD;
forAll(meshEdges, edgei)
{
const label meshEdgei = meshEdges[edgei];
const label numNbrs = nEdgeConnections[meshEdgei];
if (numNbrs == 1)
{
//if (pointSetPtr) pointSetPtr->insert(mesh.edges()[meshEdgei]);
labelTyp = max(labelTyp, TopoType::OPEN);
}
else if (numNbrs == 0 || numNbrs > 2)
{
if (pointSetPtr) pointSetPtr->insert(mesh.edges()[meshEdgei]);
if (badEdgesPtr) badEdgesPtr->insert(edgei);
labelTyp = max(labelTyp, TopoType::ILLEGAL);
}
}
reduce(labelTyp, maxOp<label>());
if (labelTyp == TopoType::MANIFOLD)
{
Info<< setw(34) << "ok (closed singly connected)";
}
else if (labelTyp == TopoType::OPEN)
{
Info<< setw(34) << "ok (non-closed singly connected)";
}
else
{
Info<< setw(34) << "multiply connected (shared edge)";
}
foundError = (labelTyp == TopoType::ILLEGAL);
}
else
{
const TopoType pTyp = pp.surfaceType(badEdgesPtr);
if (pTyp == TopoType::MANIFOLD)
{
if (pp.checkPointManifold(true, pointSetPtr))
{
Info<< setw(34) << "multiply connected (shared point)";
}
else
{
Info<< setw(34) << "ok (closed singly connected)";
}
if (pointSetPtr)
{
// Add points on non-manifold edges to make set complete
pp.checkTopology(false, pointSetPtr);
}
}
else
{
if (pointSetPtr)
{
pp.checkTopology(false, pointSetPtr);
}
if (pTyp == TopoType::OPEN)
{
Info<< setw(34) << "ok (non-closed singly connected)";
}
else
{
Info<< setw(34) << "multiply connected (shared edge)";
}
}
foundError = (pTyp == TopoType::ILLEGAL);
}
if (allGeometry)
{
boundBox bb(pp.box());
bb.reduce();
if (bb.good())
{
Info<< ' ' << bb;
}
}
return foundError;
}
// OLDER CODE
// ~~~~~~~~~~
// Return true on error
template<class PatchType>
bool Foam::checkPatch
(
const bool allGeometry,
const std::string& name,
const polyMesh& mesh,
const PatchType& pp,
const labelUList& meshFaces,
const labelUList& meshEdges,
labelHashSet* pointSetPtr,
labelHashSet* badEdgesPtr
)
{
if (badEdgesPtr)
{
badEdgesPtr->clear();
}
typedef typename PatchType::surfaceTopo TopoType;
bool foundError = false;
const label globalSize = returnReduce(pp.size(), sumOp<label>());
Info<< " "
<< setw(20) << name.c_str()
<< setw(9) << globalSize
<< setw(9) << returnReduce(pp.nPoints(), sumOp<label>());
if (globalSize == 0)
{
Info<< setw(34) << "ok (empty)";
}
else if (UPstream::parRun())
{
// Parallel - use mesh edges
// - no check for point-pinch
// - no check for consistent orientation (if that is posible to
// check?)
// OLDER CODE
// ~~~~~~~~~~
// Synchronise connected faces using global face numbering
//
// (see addPatchCellLayer::globalEdgeFaces)
// From mesh edge to global face labels. Non-empty sublists only for
// pp edges.
@ -88,12 +255,12 @@ void Foam::checkPatch
forAll(edgeFaces, edgei)
{
label meshEdgei = meshEdges[edgei];
const label meshEdgei = meshEdges[edgei];
const labelList& eFaces = edgeFaces[edgei];
// Store face and processor as unique tag.
labelList& globalEFaces = globalEdgeFaces[meshEdgei];
globalEFaces.setSize(eFaces.size());
globalEFaces.resize(eFaces.size());
forAll(eFaces, i)
{
globalEFaces[i] = globalFaces.toGlobal(meshFaces[eFaces[i]]);
@ -104,29 +271,30 @@ void Foam::checkPatch
// << endl;
}
// Synchronise across coupled edges.
// Synchronise across coupled edges
syncTools::syncEdgeList
(
mesh,
globalEdgeFaces,
ListOps::uniqueEqOp<label>(),
labelList() // null value
labelList() // null value
);
label labelTyp = TopoType::MANIFOLD;
forAll(meshEdges, edgei)
{
const label meshEdgei = meshEdges[edgei];
const labelList& globalEFaces = globalEdgeFaces[meshEdgei];
if (globalEFaces.size() == 1)
const label numNbrs = globalEFaces.size();
if (numNbrs == 1)
{
//points.insert(mesh.edges()[meshEdgei]);
//if (pointSetPtr) pointSetPtr->insert(mesh.edges()[meshEdgei]);
labelTyp = max(labelTyp, TopoType::OPEN);
}
else if (globalEFaces.size() == 0 || globalEFaces.size() > 2)
else if (numNbrs == 0 || numNbrs > 2)
{
points.insert(mesh.edges()[meshEdgei]);
if (pointSetPtr) pointSetPtr->insert(mesh.edges()[meshEdgei]);
if (badEdgesPtr) badEdgesPtr->insert(edgei);
labelTyp = max(labelTyp, TopoType::ILLEGAL);
}
}
@ -138,61 +306,68 @@ void Foam::checkPatch
}
else if (labelTyp == TopoType::OPEN)
{
Info<< setw(34)
<< "ok (non-closed singly connected)";
Info<< setw(34) << "ok (non-closed singly connected)";
}
else
{
Info<< setw(34)
<< "multiply connected (shared edge)";
Info<< setw(34) << "multiply connected (shared edge)";
}
foundError = (labelTyp == TopoType::ILLEGAL);
}
else
{
TopoType pTyp = pp.surfaceType();
const TopoType pTyp = pp.surfaceType(badEdgesPtr);
if (pTyp == TopoType::MANIFOLD)
{
if (pp.checkPointManifold(true, &points))
if (pp.checkPointManifold(true, pointSetPtr))
{
Info<< setw(34)
<< "multiply connected (shared point)";
Info<< setw(34) << "multiply connected (shared point)";
}
else
{
Info<< setw(34) << "ok (closed singly connected)";
}
// Add points on non-manifold edges to make set complete
pp.checkTopology(false, &points);
if (pointSetPtr)
{
// Add points on non-manifold edges to make set complete
pp.checkTopology(false, pointSetPtr);
}
}
else
{
pp.checkTopology(false, &points);
if (pointSetPtr)
{
pp.checkTopology(false, pointSetPtr);
}
if (pTyp == TopoType::OPEN)
{
Info<< setw(34)
<< "ok (non-closed singly connected)";
Info<< setw(34) << "ok (non-closed singly connected)";
}
else
{
Info<< setw(34)
<< "multiply connected (shared edge)";
Info<< setw(34) << "multiply connected (shared edge)";
}
}
foundError = (pTyp == TopoType::ILLEGAL);
}
if (allGeometry)
{
const labelList& mp = pp.meshPoints();
boundBox bb(pp.box());
bb.reduce();
if (returnReduceOr(mp.size()))
if (bb.good())
{
boundBox bb(pp.points(), mp, true); // reduce
Info<< ' ' << bb;
}
}
return foundError;
}
@ -524,7 +699,7 @@ Foam::label Foam::checkTopology
<< mesh.time().timeName()/"cellToRegion"
<< endl;
labelIOList ctr
IOListRef<label>
(
IOobject
(
@ -532,11 +707,11 @@ Foam::label Foam::checkTopology
mesh.time().timeName(),
mesh,
IOobject::NO_READ,
IOobject::NO_WRITE
IOobject::NO_WRITE,
IOobject::NO_REGISTER
),
rs
);
ctr.write();
).write();
// Points in multiple regions
@ -651,7 +826,7 @@ Foam::label Foam::checkTopology
}
// Non-manifold points
pointSet points
pointSet nonManifoldPoints
(
mesh,
"nonManifoldPoints",
@ -670,17 +845,15 @@ Foam::label Foam::checkTopology
<< setw(20) << "Patch"
<< setw(9) << "Faces"
<< setw(9) << "Points"
<< "Surface topology";
<< " Surface topology";
if (allGeometry)
{
Info<< " Bounding box";
Info<< " Bounding box";
}
Info<< endl;
forAll(patches, patchi)
for (const polyPatch& pp : patches)
{
const polyPatch& pp = patches[patchi];
if (!isA<processorPolyPatch>(pp))
{
checkPatch
@ -689,14 +862,78 @@ Foam::label Foam::checkTopology
pp.name(),
mesh,
pp,
identity(pp.size(), pp.start()),
// identity(pp.size(), pp.start()),
pp.meshEdges(),
points
&nonManifoldPoints
);
Info<< endl;
}
}
// All non-processor boundary patches
{
labelList faceLabels
(
identity
(
(
patches.range(patches.nNonProcessor()-1).end_value()
- patches.start()
),
patches.start()
)
);
uindirectPrimitivePatch pp
(
UIndirectList<face>(mesh.faces(), faceLabels),
mesh.points()
);
// Non-manifold
labelHashSet badEdges(pp.nEdges()/20);
bool hadBadEdges = checkPatch
(
allGeometry,
"## ALL BOUNDARIES ##",
mesh,
pp,
// faceLabels,
pp.meshEdges(mesh.edges(), mesh.pointEdges()),
nullptr, // No point set
&badEdges
);
Info<< nl << endl;
if (hadBadEdges)
{
edgeList dumpEdges(pp.edges(), badEdges.sortedToc());
vtk::lineWriter writer
(
pp.localPoints(),
dumpEdges,
fileName
(
mesh.time().globalPath()
/ ("checkMesh-illegal-edges")
)
);
writer.writeGeometry();
// CellData
writer.beginCellData();
writer.writeProcIDs();
Info<< "Wrote "
<< returnReduce(dumpEdges.size(), sumOp<label>())
<< " bad edges: " << writer.output().name() << nl;
writer.close();
}
}
//Info.setf(ios_base::right);
}
@ -729,9 +966,9 @@ Foam::label Foam::checkTopology
fz.name(),
mesh,
fz(), // patch
fz, // mesh face labels
// fz, // mesh face labels
fz.meshEdges(), // mesh edge labels
points
&nonManifoldPoints
);
Info<< endl;
}
@ -761,17 +998,20 @@ Foam::label Foam::checkTopology
}
}
const label nPoints = returnReduce(points.size(), sumOp<label>());
const label nPoints =
returnReduce(nonManifoldPoints.size(), sumOp<label>());
if (nPoints)
{
Info<< " <<Writing " << nPoints
<< " conflicting points to set " << points.name() << endl;
points.instance() = mesh.pointsInstance();
points.write();
<< " conflicting points to set " << nonManifoldPoints.name()
<< endl;
nonManifoldPoints.instance() = mesh.pointsInstance();
nonManifoldPoints.write();
if (setWriter && setWriter->enabled())
{
mergeAndWrite(*setWriter, points);
mergeAndWrite(*setWriter, nonManifoldPoints);
}
}

View File

@ -1,3 +1,20 @@
/*---------------------------------------------------------------------------*\
========= |
\\ / F ield | OpenFOAM: The Open Source CFD Toolbox
\\ / O peration |
\\ / A nd | www.openfoam.com
\\/ M anipulation |
-------------------------------------------------------------------------------
Copyright (C) 2023 OpenCFD Ltd.
-------------------------------------------------------------------------------
License
This file is part of OpenFOAM, distributed under GPL-3.0-or-later.
Description
Global functions for topology checking
\*---------------------------------------------------------------------------*/
#include "labelList.H"
#include "autoPtr.H"
#include "ZoneMesh.H"
@ -5,38 +22,62 @@
namespace Foam
{
// Forward Declarations
class polyMesh;
class pointSet;
class coordSetWriter;
class surfaceWriter;
template<class PatchType>
void checkPatch
(
const bool allGeometry,
const word& name,
const polyMesh& mesh,
const PatchType& pp,
const labelList& meshFaces,
const labelList& meshEdges,
pointSet& points
);
// Forward Declarations
class polyMesh;
class pointSet;
class coordSetWriter;
class surfaceWriter;
template<class Zone>
label checkZones
(
const polyMesh& mesh,
const ZoneMesh<Zone, polyMesh>& zones,
topoSet& set
);
label checkTopology
(
const polyMesh& mesh,
const bool allTopology,
const bool allGeometry,
autoPtr<surfaceWriter>& surfWriter,
autoPtr<coordSetWriter>& setWriter
);
}
// Check patch topology.
// In parallel, uses count of edge connections
template<class PatchType>
bool checkPatch
(
const bool allGeometry,
const std::string& name,
const polyMesh& mesh,
const PatchType& pp,
const labelUList& meshEdges,
labelHashSet* pointSetPtr = nullptr,
labelHashSet* badEdgesPtr = nullptr
);
// OLDER CODE
// ~~~~~~~~~~
// Check patch topology.
// In parallel, uses mesh face ids and global face numbering
template<class PatchType>
bool checkPatch
(
const bool allGeometry,
const std::string& name,
const polyMesh& mesh,
const PatchType& pp,
const labelUList& meshFaces,
const labelUList& meshEdges,
labelHashSet* pointSetPtr = nullptr,
labelHashSet* badEdgesPtr = nullptr
);
template<class Zone>
label checkZones
(
const polyMesh& mesh,
const ZoneMesh<Zone, polyMesh>& zones,
topoSet& set
);
label checkTopology
(
const polyMesh& mesh,
const bool allTopology,
const bool allGeometry,
autoPtr<surfaceWriter>& surfWriter,
autoPtr<coordSetWriter>& setWriter
);
} // End namespace Foam
// ************************************************************************* //