From 45a0f70a2801723fa3af8929d6e441b60cb22c12 Mon Sep 17 00:00:00 2001
From: mattijs <mattijs>
Date: Thu, 9 Jan 2020 11:19:57 +0000
Subject: [PATCH] ENH: shortestPathSet: snappyHexMesh leak detection. Improved
 walking.

---
 .../sampledSet/shortestPath/shortestPathSet.C | 1028 ++++++++++++-----
 .../sampledSet/shortestPath/shortestPathSet.H |   64 +-
 2 files changed, 819 insertions(+), 273 deletions(-)

diff --git a/src/sampling/sampledSet/shortestPath/shortestPathSet.C b/src/sampling/sampledSet/shortestPath/shortestPathSet.C
index 6e218cf09b0..6208a361a3b 100644
--- a/src/sampling/sampledSet/shortestPath/shortestPathSet.C
+++ b/src/sampling/sampledSet/shortestPath/shortestPathSet.C
@@ -37,6 +37,7 @@ License
 #include "OBJstream.H"
 #include "PatchTools.H"
 #include "foamVtkSurfaceWriter.H"
+#include "uindirectPrimitivePatch.H"
 
 // * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * //
 
@@ -105,7 +106,7 @@ Foam::label Foam::shortestPathSet::findMinFace
         }
         else
         {
-            // Avoid leak points
+            // Avoid leak points i.e. preferentially stay away from the wall
             label minLeakPoints = labelMax;
             forAll(cFaces2, i)
             {
@@ -192,8 +193,8 @@ void Foam::shortestPathSet::calculateDistance
     {
         const fvMesh& fm = refCast<const fvMesh>(mesh);
 
-        const_cast<fvMesh&>(fm).setInstance(fm.time().timeName());
-        fm.write();
+        //const_cast<fvMesh&>(fm).setInstance(fm.time().timeName());
+        //fm.write();
         volScalarField fld
         (
             IOobject
@@ -224,6 +225,8 @@ void Foam::shortestPathSet::calculateDistance
         }
         //Note: do not swap cell values so do not do
         //fld.correctBoundaryConditions();
+        Pout<< "Writing distance field for initial cell " << cellI
+            << " to " << fld.objectPath() << endl;
         fld.write();
     }
 }
@@ -291,7 +294,10 @@ bool Foam::shortestPathSet::touchesWall
     const face& f = mesh.faces()[facei];
     forAll(f, fp)
     {
-        if (isLeakPoint[f[fp]])
+        const label nextFp = f.fcIndex(fp);
+
+        if (isLeakPoint[f[fp]] && isLeakPoint[f[nextFp]])   // edge on boundary
+        //if (isLeakPoint[f[fp]])                           // point on boundary
         {
             if (isLeakFace.set(facei))
             {
@@ -304,344 +310,688 @@ bool Foam::shortestPathSet::touchesWall
 }
 
 
-void Foam::shortestPathSet::genSamples
+Foam::bitSet Foam::shortestPathSet::pathFaces
+(
+    const polyMesh& mesh,
+    const bitSet& isLeakCell
+) const
+{
+    // Calculates the list of faces inbetween leak cells.
+    // Does not account for the fact that the cells might be from different
+    // paths...
+
+    const polyBoundaryMesh& patches = mesh.boundaryMesh();
+    const labelList& own = mesh.faceOwner();
+    const labelList& nbr = mesh.faceNeighbour();
+
+    // Get remote value of bitCell. Note: keep uncoupled boundary faces false.
+    boolList nbrLeakCell(mesh.nBoundaryFaces(), false);
+    {
+        for (const polyPatch& pp : patches)
+        {
+            if (pp.coupled())
+            {
+                label bFacei = pp.start()-mesh.nInternalFaces();
+
+                const labelUList& faceCells = pp.faceCells();
+
+                for (const label celli : faceCells)
+                {
+                    nbrLeakCell[bFacei] = isLeakCell[celli];
+                    ++bFacei;
+                }
+            }
+        }
+
+        syncTools::swapBoundaryFaceList
+        (
+            mesh,
+            nbrLeakCell
+        );
+    }
+
+
+    bitSet isLeakFace(mesh.nFaces());
+
+    // Internal faces
+    for (label facei = 0; facei < mesh.nInternalFaces(); facei++)
+    {
+        if (isLeakCell[own[facei]] && isLeakCell[nbr[facei]])
+        {
+            isLeakFace.set(facei);
+        }
+    }
+    // Boundary faces
+    for (label facei = mesh.nInternalFaces(); facei < mesh.nFaces(); facei++)
+    {
+        if (isLeakCell[own[facei]] && nbrLeakCell[facei-mesh.nInternalFaces()])
+        {
+            isLeakFace.set(facei);
+        }
+    }
+    return isLeakFace;
+}
+
+
+bool Foam::shortestPathSet::genSingleLeakPath
 (
     const bool markLeakPath,
-    const label maxIter,
+    const label iter,
     const polyMesh& mesh,
-    const boolList& isBlockedFace,
+    const PackedBoolList& isBlockedFace,
     const point& insidePoint,
     const label insideCelli,
     const point& outsidePoint,
+    const label outsideCelli,
 
+    // Generated sampling points
+    const scalar trackLength,
     DynamicList<point>& samplingPts,
     DynamicList<label>& samplingCells,
     DynamicList<label>& samplingFaces,
     DynamicList<label>& samplingSegments,
     DynamicList<scalar>& samplingCurveDist,
+
+    // State of current leak paths
     PackedBoolList& isLeakCell,
     PackedBoolList& isLeakFace,
-    PackedBoolList& isLeakPoint
+    PackedBoolList& isLeakPoint,
+
+    // Work storage
+    List<topoDistanceData>& allFaceInfo,
+    List<topoDistanceData>& allCellInfo
 ) const
 {
+    const polyBoundaryMesh& pbm = mesh.boundaryMesh();
     const topoDistanceData maxData(labelMax, labelMax);
 
-    // Get the target point
-    label outsideCelli = mesh.findCell(outsidePoint);
 
-    // Maintain overall track length. Used to make curveDist continuous.
-    label trackLength = 0;
+    allFaceInfo.setSize(mesh.nFaces());
+    allFaceInfo = topoDistanceData();
+    allCellInfo.setSize(mesh.nCells());
+    allCellInfo = topoDistanceData();
 
-    for (label iter = 0; iter < maxIter; iter++)
+    // Mark blocked faces with high distance
+    forAll(isBlockedFace, facei)
     {
-        List<topoDistanceData> allFaceInfo(mesh.nFaces());
-        List<topoDistanceData> allCellInfo(mesh.nCells());
-
-        // Mark blocked faces with high distance
-        forAll(isBlockedFace, facei)
+        if (isBlockedFace[facei])
         {
-            if (isBlockedFace[facei])
-            {
-                allFaceInfo[facei] = maxData;
-            }
+            allFaceInfo[facei] = maxData;
         }
+    }
 
-        if (markLeakPath)
+    if (markLeakPath)
+    {
+        // Mark any previously found leak path. This marks all
+        // faces of all cells on the path. This will make sure that
+        // ultimately the path will go through another gap.
+        forAll(isLeakCell, celli)
         {
-            // Mark any previously found leak path. This marks all
-            // faces of all cells on the path. This will make sure that
-            // ultimately the path will go through another gap.
-            forAll(isLeakCell, celli)
+            if (celli != insideCelli && celli != outsideCelli)
             {
-                if (celli != insideCelli && celli != outsideCelli)
+                if (isLeakCell[celli])
                 {
-                    if (isLeakCell[celli])
-                    {
-                        allCellInfo[celli] = maxData;
-                        const cell& cFaces = mesh.cells()[celli];
-                        for (auto facei : cFaces)
-                        {
-                            allFaceInfo[facei] = maxData;
-                        }
-                    }
+                    allCellInfo[celli] = maxData;
+                    //- Mark all faces of the cell
+                    //const cell& cFaces = mesh.cells()[celli];
+                    //for (auto facei : cFaces)
+                    //{
+                    //    allFaceInfo[facei] = maxData;
+                    //}
                 }
             }
         }
 
-        // Mark any previously found leak faces. These are faces that
-        // are (point-)connected to an existing boundary.
-        forAll(isLeakFace, facei)
+        //- Mark only faces inbetween two leak cells
+        bitSet isLeakCellWithout(isLeakCell);
+        if (insideCelli != -1)
+        {
+            isLeakCellWithout.unset(insideCelli);
+        }
+        if (outsideCelli != -1)
         {
-            if (isLeakFace[facei])
+            isLeakCellWithout.unset(outsideCelli);
+        }
+        const bitSet isPathFace(pathFaces(mesh, isLeakCellWithout));
+        forAll(isPathFace, facei)
+        {
+            if (isPathFace[facei])
             {
                 allFaceInfo[facei] = maxData;
             }
         }
+    }
 
+    // Mark any previously found leak faces. These are faces that
+    // are (point- or edge-)connected to an existing boundary.
+    forAll(isLeakFace, facei)
+    {
+        if (isLeakFace[facei])
+        {
+            allFaceInfo[facei] = maxData;
+        }
+    }
 
-        // Pass1: Get distance to insideCelli
-
-        calculateDistance(iter, mesh, insideCelli, allFaceInfo, allCellInfo);
 
+    // Pass1: Get distance to insideCelli
 
+    calculateDistance(iter, mesh, insideCelli, allFaceInfo, allCellInfo);
 
-        // Pass2: walk from outside points backwards. Note: could be done
-        //        using FaceCellWave as well but is overly complex since
-        //        does not allow logic comparing all faces of a cell.
 
-        const polyBoundaryMesh& pbm = mesh.boundaryMesh();
 
+    // Pass2: walk from outside points backwards. Note: could be done
+    //        using FaceCellWave as well but is overly complex since
+    //        does not allow logic comparing all faces of a cell.
 
-        bool targetFound = false;
-        if (outsideCelli != -1)
-        {
-            int dummyTrackData;
-            targetFound = allCellInfo[outsideCelli].valid(dummyTrackData);
-            if (!targetFound)
-            {
-                WarningInFunction
-                    << "Point " << outsidePoint
-                    << " not reachable by walk from " << insidePoint
-                    << ". Probably mesh has island/regions."
-                    << " Skipped route detection." << endl;
-            }
-        }
-        reduce(targetFound, orOp<bool>());
-        if (!targetFound)
+    bool targetFound = false;
+    if (outsideCelli != -1)
+    {
+        int dummyTrackData;
+        targetFound = allCellInfo[outsideCelli].valid(dummyTrackData);
+        if (iter == 0 && !targetFound)
         {
-            break;
+            WarningInFunction
+                << "Point " << outsidePoint
+                << " not reachable by walk from " << insidePoint
+                << ". Probably mesh has island/regions."
+                << " Skipped route detection." << endl;
         }
+    }
+    reduce(targetFound, orOp<bool>());
+    if (!targetFound)
+    {
+        //Pout<< "now :"
+        //    << " nLeakCell:"
+        //    << returnReduce(isLeakCell.count(), sumOp<label>())
+        //    << " nLeakFace:"
+        //    << returnReduce(isLeakFace.count(), sumOp<label>())
+        //    << " nLeakPoint:"
+        //    << returnReduce(isLeakPoint.count(), sumOp<label>())
+        //    << endl;
+
+        return false;
+    }
 
 
-        // Start with given target cell and walk back
-        // If point happens to be on multiple processors, random pick
-        label frontCellI = outsideCelli;
-        point origin(outsidePoint);
-        bool findMinDistance = true;
+    // Start with given target cell and walk back
+    // If point happens to be on multiple processors, random pick
+    label frontCellI = outsideCelli;
+    point origin(outsidePoint);
+    bool findMinDistance = true;
 
-        while (true)
+    while (true)
+    {
+        // We have a single face front (frontFaceI). Walk from face to cell
+        // to face etc until we reach the destination cell.
+
+        label frontFaceI = -1;
+
+        // Work within same processor
+        if (frontCellI != -1)
         {
-            label frontFaceI = -1;
+            // Find face with lowest distance from seed. In below figure the
+            // seed cell is marked with distance 0. It is surrounded by faces
+            // and cells with distance 1. The layer outside is marked with
+            // distance 2 etc etc.
+            //
+            //   x  |  x  2  1  2  2  |  x  |  x
+            //  --- + --- + -1- + -2- + --- + ---
+            //   x  |  1  1  0  1  1  |  x  |  x
+            //  --- + --- + -1- + -2- + --- + ---
+            //   x  |  x  2  1  2  2  3  3  4  4
+            //  --- + --- + --- + -3- + -4- + -5-
+            //   x  |  x  3  2  3  3  4  4  5  5
+            //
+            // e.g. if we start backwards search from cell with value = 4,
+            // we have neighbour faces 4, 4, 5, 5. Choose 4 (least distance
+            // to seed) and continue...
+
+            frontFaceI = findMinFace
+            (
+                mesh,
+                frontCellI,
+                allFaceInfo,
+                isLeakPoint,
+                findMinDistance,    // mode : find min or find max
+                origin
+            );
+
 
-            // Work within same processor
-            if (frontCellI != -1)
+            // Loop until we hit a boundary face
+            PackedBoolList isNewLeakPoint(isLeakPoint);
+            while (mesh.isInternalFace(frontFaceI))
             {
-                // Find face with lowest distance from seed
-                //   x  |  x  2  1  2  2  |  x  |  x
-                //  --- + --- + -1- + -2- + --- + ---
-                //   x  |  1  1  0  1  1  |  x  |  x
-                //  --- + --- + -1- + -2- + --- + ---
-                //   x  |  x  2  1  2  2  3  3  4  4
-                //  --- + --- + --- + -3- + -4- + -5-
-                //   x  |  x  3  2  3  3  4  4  5  5
-                // e.g. if we start from cell with value = 4, we have
-                // neighbour faces 4, 4, 5, 5. Choose 4 (least distance
-                // to seed) and continue...
+                if (isBlockedFace.size() && isBlockedFace[frontFaceI])
+                {
+                    // This should not happen since we never walk through
+                    // a blocked face. However ...
+                    // Pout<< " Found blocked face" << endl;
+                    frontCellI = -1;
+                    break;
+                }
 
+                // Step to neighbouring cell
+                label nbrCellI = mesh.faceOwner()[frontFaceI];
+                if (nbrCellI == frontCellI)
+                {
+                    nbrCellI = mesh.faceNeighbour()[frontFaceI];
+                }
+
+                if (nbrCellI == insideCelli)
+                {
+                    // Reached destination. This is the normal exit.
+                    frontCellI = -1;
+                    break;
+                }
+
+                frontCellI = nbrCellI;
+
+                // Pick best face on cell
                 frontFaceI = findMinFace
                 (
                     mesh,
                     frontCellI,
                     allFaceInfo,
                     isLeakPoint,
-                    findMinDistance,    // mode : find min or find max
+                    findMinDistance,
                     origin
                 );
 
+                const topoDistanceData& cInfo = allCellInfo[frontCellI];
+                const topoDistanceData& fInfo = allFaceInfo[frontFaceI];
 
-                // Loop until we hit a boundary face
-                PackedBoolList isNewLeakPoint(isLeakPoint);
-                while (mesh.isInternalFace(frontFaceI))
+                if (fInfo.distance() <= cInfo.distance())
                 {
-                    if (isBlockedFace.size() && isBlockedFace[frontFaceI])
-                    {
-                        // Pout<< " Found blocked face" << endl;
-                        frontCellI = -1;
-                        break;
-                    }
+                    // Found valid next cell,face. Mark on path
+                    samplingPts.append(mesh.cellCentres()[frontCellI]);
+                    samplingCells.append(frontCellI);
+                    samplingFaces.append(-1);
+                    samplingSegments.append(iter);
+                    samplingCurveDist.append
+                    (
+                        trackLength+cInfo.distance()
+                    );
+                    isLeakCell.set(frontCellI);
 
-                    // Step to neighbouring cell
-                    label nbrCellI = mesh.faceOwner()[frontFaceI];
-                    if (nbrCellI == frontCellI)
+                    // Check if front face uses a boundary point.
+                    if
+                    (
+                        touchesWall
+                        (
+                            mesh,
+                            frontFaceI,
+                            isLeakFace,
+                            isLeakPoint
+                        )
+                    )
                     {
-                        nbrCellI = mesh.faceNeighbour()[frontFaceI];
+                        //Pout<< "** added leak face:" << frontFaceI << " at:"
+                        //<< mesh.faceCentres()[frontFaceI] << endl;
+                        isNewLeakPoint.set(mesh.faces()[frontFaceI]);
+                        origin = mesh.faceCentres()[frontFaceI];
+                        findMinDistance = false;
                     }
+                }
+            }
+            isLeakPoint.transfer(isNewLeakPoint);
+        }
 
-                    if (nbrCellI == insideCelli)
-                    {
-                        // Pout<< " Found connection seed cell!" << endl;
-                        frontCellI = -1;
-                        break;
-                    }
+        // Situation 1: we found the destination cell (do nothing),
+        // frontCellI is -1 on all processors
+        if (returnReduce(frontCellI == -1, andOp<bool>()))
+        {
+            //Pout<< "now :"
+            //    << " nLeakCell:"
+            //    << returnReduce(isLeakCell.count(), sumOp<label>())
+            //    << " nLeakFace:"
+            //    << returnReduce(isLeakFace.count(), sumOp<label>())
+            //    << " nLeakPoint:"
+            //    << returnReduce(isLeakPoint.count(), sumOp<label>())
+            //    << endl;
 
-                    frontCellI = nbrCellI;
+            break;
+        }
 
-                    // Pick best face on cell
-                    frontFaceI = findMinFace
-                    (
-                        mesh,
-                        frontCellI,
-                        allFaceInfo,
-                        isLeakPoint,
-                        findMinDistance,
-                        origin
-                    );
+        // Situation 2: we're on a coupled patch and might need to
+        //              switch processor/cell. We need to transfer:
+        //              -frontface -frontdistance -leak points/faces
+        boolList isFront(mesh.nBoundaryFaces(), false);
 
-                    const topoDistanceData& cInfo = allCellInfo[frontCellI];
-                    const topoDistanceData& fInfo = allFaceInfo[frontFaceI];
+        if (frontFaceI != -1)
+        {
+            isFront[frontFaceI-mesh.nInternalFaces()] = true;
+        }
+        syncTools::swapBoundaryFaceList(mesh, isFront);
 
-                    if (fInfo.distance() <= cInfo.distance())
-                    {
-                        samplingPts.append(mesh.cellCentres()[frontCellI]);
-                        samplingCells.append(frontCellI);
-                        samplingFaces.append(-1);
-                        samplingSegments.append(iter);
-                        samplingCurveDist.append
-                        (
-                            trackLength+cInfo.distance()
-                        );
-                        isLeakCell.set(frontCellI);
+        label minCellDistance = labelMax;
+        if (frontCellI != -1)
+        {
+            minCellDistance = allCellInfo[frontCellI].distance();
+        }
+        reduce(minCellDistance, minOp<label>());
 
-                        if
-                        (
-                            touchesWall
-                            (
-                                mesh,
-                                frontFaceI,
-                                isLeakFace,
-                                isLeakPoint
-                            )
-                        )
-                        {
-                            isNewLeakPoint.set(mesh.faces()[frontFaceI]);
-                            origin = mesh.faceCentres()[frontFaceI];
-                            findMinDistance = false;
-                        }
+        // Sync all leak data
+        sync
+        (
+            mesh,
+            isLeakFace,
+            isLeakPoint,
+            frontCellI,
+            origin,
+            findMinDistance
+        );
 
-                    }
+
+        // Bit tricky:
+        // - processor might get frontFaceI/frontCellI in through multiple faces
+        //   (even through different patches?)
+        // - so loop over all (coupled) patches and pick the best frontCellI
+
+        const label oldFrontFaceI = frontFaceI;
+        frontCellI = -1;
+        frontFaceI = -1;
+        forAll(pbm, patchI)
+        {
+            const polyPatch& pp = pbm[patchI];
+            forAll(pp, i)
+            {
+                label faceI = pp.start()+i;
+                if
+                (
+                    oldFrontFaceI == -1
+                 && isFront[faceI-mesh.nInternalFaces()]
+                 && (isBlockedFace.empty() || !isBlockedFace[faceI])
+                )
+                {
+                    frontFaceI = faceI;
+                    frontCellI = pp.faceCells()[i];
+                    break;
                 }
-                isLeakPoint.transfer(isNewLeakPoint);
             }
 
-            // Situation 1: we found the destination cell (do nothing),
-            // frontCellI is -1 on all processors
-            if (returnReduce(frontCellI == -1, andOp<bool>()))
+            if
+            (
+                frontCellI != -1
+             && allCellInfo[frontCellI].distance() < minCellDistance
+            )
             {
-                break;
-            }
+                const topoDistanceData& cInfo = allCellInfo[frontCellI];
 
-            // Situation 2: we're on a coupled patch and might need to
-            //              switch processor/cell. We need to transfer:
-            //              -frontface -frontdistance -leak points/faces
-            boolList isFront(mesh.nBoundaryFaces(), false);
+                samplingPts.append(mesh.cellCentres()[frontCellI]);
+                samplingCells.append(frontCellI);
+                samplingFaces.append(-1);
+                samplingSegments.append(iter);
+                samplingCurveDist.append
+                (
+                    trackLength+cInfo.distance()
+                );
+                isLeakCell.set(frontCellI);
 
-            if (frontFaceI != -1)
-            {
-                isFront[frontFaceI-mesh.nInternalFaces()] = true;
+                // Check if frontFacei touches leakPoint
+                if
+                (
+                    touchesWall
+                    (
+                        mesh,
+                        frontFaceI,
+                        isLeakFace,
+                        isLeakPoint
+                    )
+                )
+                {
+                    //Pout<< "** added leak BOUNDARY face:" << frontFaceI
+                    // << " at:" << mesh.faceCentres()[frontFaceI] << endl;
+                    isLeakPoint.set(mesh.faces()[frontFaceI]);
+                    origin = mesh.faceCentres()[frontFaceI];
+                    findMinDistance = false;
+                }
+
+                break;
             }
-            syncTools::swapBoundaryFaceList(mesh, isFront);
 
-            label minCellDistance = labelMax;
-            if (frontCellI != -1)
+            // When seed cell is isolated by processor boundaries
+            if (insideCelli != -1 && frontCellI == insideCelli)
             {
-                minCellDistance = allCellInfo[frontCellI].distance();
+                // Pout<< " Found connection seed cell!" << endl;
+                frontCellI = -1;
+                break;
             }
-            reduce(minCellDistance, minOp<label>());
+        }
 
-            // Sync all leak data
-            sync
-            (
-                mesh,
-                isLeakFace,
-                isLeakPoint,
-                frontCellI,
-                origin,
-                findMinDistance
-            );
+        // Sync all leak data
+        sync
+        (
+            mesh,
+            isLeakFace,
+            isLeakPoint,
+            frontCellI,
+            origin,
+            findMinDistance
+        );
+    }
+
+    return true;
+}
+
+
+// Clean up leak faces (erode open edges). These are leak faces which are
+// not connected to another leak face or leak point. Parallel consistent.
+// Returns overall number of faces deselected.
+Foam::label Foam::shortestPathSet::erodeFaceSet
+(
+    const polyMesh& mesh,
+    const PackedBoolList& isBlockedPoint,
+    PackedBoolList& isLeakFace
+) const
+{
+    if
+    (
+        (isBlockedPoint.size() != mesh.nPoints())
+     || (isLeakFace.size() != mesh.nFaces())
+    )
+    {
+        FatalErrorInFunction << "Problem :"
+            << " isBlockedPoint:" << isBlockedPoint.size()
+            << " isLeakFace:" << isLeakFace.size()
+            << exit(FatalError);
+    }
+
+    const globalMeshData& globalData = mesh.globalData();
+    const mapDistribute& map = globalData.globalEdgeSlavesMap();
+    const indirectPrimitivePatch& cpp = mesh.globalData().coupledPatch();
+
+
+    label nTotalEroded = 0;
+
+    while (true)
+    {
+        PackedBoolList newIsLeakFace(isLeakFace);
+
+        // Get number of edges
+
+        const labelList meshFaceIDs(isLeakFace.toc());
+        const uindirectPrimitivePatch pp
+        (
+            UIndirectList<face>(mesh.faces(), meshFaceIDs),
+            mesh.points()
+        );
+
+        // Count number of faces per edge
+        const labelListList& edgeFaces = pp.edgeFaces();
+        labelList nEdgeFaces(edgeFaces.size());
+        forAll(edgeFaces, edgei)
+        {
+            nEdgeFaces[edgei] = edgeFaces[edgei].size();
+        }
 
+        // Match pp edges to coupled edges
+        labelList patchEdges;
+        labelList coupledEdges;
+        PackedBoolList sameEdgeOrientation;
+        PatchTools::matchEdges
+        (
+            pp,
+            cpp,
+            patchEdges,
+            coupledEdges,
+            sameEdgeOrientation
+        );
 
 
-            const label oldFrontFaceI = frontFaceI;
-            frontCellI = -1;
-            frontFaceI = -1;
-            forAll(pbm, patchI)
+        // Convert patch-edge data into cpp-edge data
+        labelList coupledNEdgeFaces(map.constructSize(), Zero);
+        UIndirectList<label>(coupledNEdgeFaces, coupledEdges) =
+            UIndirectList<label>(nEdgeFaces, patchEdges);
+
+        // Synchronise
+        globalData.syncData
+        (
+            coupledNEdgeFaces,
+            globalData.globalEdgeSlaves(),
+            globalData.globalEdgeTransformedSlaves(),
+            map,
+            plusEqOp<label>()
+        );
+
+        // Convert back from cpp-edge to patch-edge
+        UIndirectList<label>(nEdgeFaces, patchEdges) =
+            UIndirectList<label>(coupledNEdgeFaces, coupledEdges);
+
+        // Remove any open edges
+        label nEroded = 0;
+        forAll(nEdgeFaces, edgei)
+        {
+            if (nEdgeFaces[edgei] == 1)
             {
-                const polyPatch& pp = pbm[patchI];
-                forAll(pp, i)
+                const edge& e = pp.edges()[edgei];
+                const label mp0 = pp.meshPoints()[e[0]];
+                const label mp1 = pp.meshPoints()[e[1]];
+
+                if (!isBlockedPoint[mp0] || !isBlockedPoint[mp1])
                 {
-                    label faceI = pp.start()+i;
-                    if
-                    (
-                        oldFrontFaceI == -1
-                     && isFront[faceI-mesh.nInternalFaces()]
-                     && (isBlockedFace.empty() || !isBlockedFace[faceI])
-                    )
+                    // Edge is not on wall so is open
+                    const label patchFacei = edgeFaces[edgei][0];
+                    const label meshFacei = pp.addressing()[patchFacei];
+                    //Pout<< "Eroding face:" << meshFacei
+                    //    << " at:" << mesh.faceCentres()[meshFacei]
+                    //    << " since has open edge:" << mesh.points()[mp0]
+                    //    << mesh.points()[mp1] << endl;
+
+                    if (newIsLeakFace.unset(meshFacei))
                     {
-                        frontFaceI = faceI;
-                        frontCellI = pp.faceCells()[i];
-                        break;
+                        nEroded++;
                     }
                 }
+            }
+        }
 
-                if
-                (
-                    frontCellI != -1
-                 && allCellInfo[frontCellI].distance() < minCellDistance
-                )
-                {
-                    const topoDistanceData& cInfo = allCellInfo[frontCellI];
+        reduce(nEroded, sumOp<label>());
+        nTotalEroded += nEroded;
 
-                    samplingPts.append(mesh.cellCentres()[frontCellI]);
-                    samplingCells.append(frontCellI);
-                    samplingFaces.append(-1);
-                    samplingSegments.append(iter);
-                    samplingCurveDist.append
-                    (
-                        trackLength+cInfo.distance()
-                    );
-                    isLeakCell.set(frontCellI);
+        if (nEroded == 0)
+        {
+            break;
+        }
 
-                    // Check if frontFacei touches leakPoint
-                    if
-                    (
-                        touchesWall
-                        (
-                            mesh,
-                            frontFaceI,
-                            isLeakFace,
-                            isLeakPoint
-                        )
-                    )
-                    {
-                        isLeakPoint.set(mesh.faces()[frontFaceI]);
-                        origin = mesh.faceCentres()[frontFaceI];
-                        findMinDistance = false;
-                    }
+        // Make sure we make the same decision on both sides
+        syncTools::syncFaceList
+        (
+            mesh,
+            newIsLeakFace,
+            orEqOp<unsigned int>()
+        );
 
-                    break;
-                }
+        isLeakFace = std::move(newIsLeakFace);
+    }
 
-                // When seed cell is isolated by processor boundaries
-                if (insideCelli != -1 && frontCellI == insideCelli)
-                {
-                    // Pout<< " Found connection seed cell!" << endl;
-                    frontCellI = -1;
-                    break;
-                }
-            }
+    return nTotalEroded;
+}
 
-            // Sync all leak data
-            sync
-            (
-                mesh,
-                isLeakFace,
-                isLeakPoint,
-                frontCellI,
-                origin,
-                findMinDistance
-            );
-        }
 
+void Foam::shortestPathSet::genSamples
+(
+    const bool addLeakPath,
+    const label maxIter,
+    const polyMesh& mesh,
+    const PackedBoolList& isBoundaryFace,
+    const point& insidePoint,
+    const label insideCelli,
+    const point& outsidePoint,
+
+    DynamicList<point>& samplingPts,
+    DynamicList<label>& samplingCells,
+    DynamicList<label>& samplingFaces,
+    DynamicList<label>& samplingSegments,
+    DynamicList<scalar>& samplingCurveDist,
+    PackedBoolList& isLeakCell,
+    PackedBoolList& isLeakFace,
+    PackedBoolList& isLeakPoint
+) const
+{
+    // Mark all paths needed to close a single combination of insidePoint,
+    // outsidePoint. The output is
+    // - isLeakCell : is cell along a leak path
+    // - isLeakFace : is face along a leak path (so inbetween two leakCells)
+    // - isLeakPoint : is point on a leakFace
+
+
+    const topoDistanceData maxData(labelMax, labelMax);
+
+    // Get the target point
+    const label outsideCelli = mesh.findCell(outsidePoint);
+
+    // Maintain overall track length. Used to make curveDist continuous.
+    scalar trackLength = 0;
+
+    List<topoDistanceData> allFaceInfo(mesh.nFaces());
+    List<topoDistanceData> allCellInfo(mesh.nCells());
+
+
+    // Boundary face + additional temporary blocks (to force leakpath to
+    // outside)
+    autoPtr<PackedBoolList> isBlockedFace;
+
+    label iter;
+    bool markLeakPath = false;
+
+
+    for (iter = 0; iter < maxIter; iter++)
+    {
+        const label nOldLeakFaces = returnReduce
+        (
+            isLeakFace.count(),
+            sumOp<label>()
+        );
+        const label oldNSamplingPts(samplingPts.size());
+
+        bool foundPath = genSingleLeakPath
+        (
+            markLeakPath,
+            iter,
+            mesh,
+            (isBlockedFace.valid() ? isBlockedFace() : isBoundaryFace),
+            insidePoint,
+            insideCelli,
+            outsidePoint,
+            outsideCelli,
+
+            // Generated sampling points
+            trackLength,
+            samplingPts,
+            samplingCells,
+            samplingFaces,
+            samplingSegments,
+            samplingCurveDist,
+
+            // State of current leak paths
+            isLeakCell,
+            isLeakFace,
+            isLeakPoint,
+
+            // Work storage
+            allFaceInfo,
+            allCellInfo
+        );
 
         // Recalculate the overall trackLength
         trackLength = returnReduce
@@ -654,33 +1004,140 @@ void Foam::shortestPathSet::genSamples
             maxOp<scalar>()
         );
 
+        const label nLeakFaces = returnReduce
+        (
+            isLeakFace.count(),
+            sumOp<label>()
+        );
 
-        if (debug)
+        if (!foundPath && !markLeakPath)
         {
-            const fvMesh& fm = refCast<const fvMesh>(mesh);
+            // In mark-boundary-face-only mode and already fully blocked the
+            // path to outsideCell so we're good
+            break;
+        }
 
-            const_cast<fvMesh&>(fm).setInstance(fm.time().timeName());
-            volScalarField fld
-            (
-                IOobject
+
+        if (nLeakFaces > nOldLeakFaces)
+        {
+            // Normal operation: walking has closed some wall-connected faces
+            // If previous iteration was markLeakPath-mode make sure to revert
+            // to normal operation (i.e. face marked in isLeakFace)
+            if (isBlockedFace.valid())
+            {
+                isBlockedFace.clear();
+            }
+            markLeakPath = false;
+        }
+        else
+        {
+            // Did not mark any additional faces/points. Save current state
+            // and add faces/points on leakpath to force next walk
+            // to pass outside of leakpath. This is done until the leakpath
+            // 'touchesWall' (i.e. edge connected to an original boundary face)
+
+            if (markLeakPath && !foundPath)
+            {
+                // Is marking all faces on all paths and no additional path
+                // found. Also nothing new marked (in isLeakFace) since
+                // nLeakFaces == nOldLeakFaces) so we're
+                // giving up. Convert all path faces into leak faces
+                //Pout<< "** giving up" << endl;
+                break;
+            }
+
+
+            // Revert to boundaryFaces only
+            if (!isBlockedFace.valid())
+            {
+                //Pout<< "** Starting from original boundary faces." << endl;
+                isBlockedFace.set(new PackedBoolList(isBoundaryFace));
+            }
+
+            markLeakPath = true;
+
+
+            if (debug & 2)
+            {
+                const pointField leakCcs(mesh.cellCentres(), isLeakCell.toc());
+                mkDir(mesh.time().timePath());
+                OBJstream str
                 (
-                    "isLeakCell",
-                    fm.time().timeName(),
-                    fm,
-                    IOobject::NO_READ,
-                    IOobject::NO_WRITE
-                ),
-                fm,
-                dimensionedScalar(dimless, Zero)
-            );
-            forAll(isLeakCell, celli)
+                    mesh.time().timePath()
+                   /"isLeakCell" + Foam::name(iter) + ".obj"
+                );
+                Pout<< "Writing new isLeakCell to " << str.name() << endl;
+                forAll(leakCcs, i)
+                {
+                    str.write(leakCcs[i]);
+                }
+            }
+            if (debug & 2)
             {
-                fld[celli] = isLeakCell[celli];
+                OBJstream str
+                (
+                    mesh.time().timePath()
+                   /"leakPath" + Foam::name(iter) + ".obj"
+                );
+                Pout<< "Writing new leak-path to " << str.name() << endl;
+                for
+                (
+                    label samplei = oldNSamplingPts+1;
+                    samplei < samplingPts.size();
+                    samplei++
+                )
+                {
+                    Pout<< "    passing through cell "
+                        << samplingCells[samplei]
+                        << " at:" << mesh.cellCentres()[samplingCells[samplei]]
+                        << " distance:" << samplingCurveDist[samplei]
+                        << endl;
+
+                    str.write
+                    (
+                        linePointRef
+                        (
+                            samplingPts[samplei-1],
+                            samplingPts[samplei]
+                        )
+                    );
+                }
             }
-            fld.correctBoundaryConditions();
-            fld.write();
         }
     }
+
+    if (debug)
+    {
+        const fvMesh& fm = refCast<const fvMesh>(mesh);
+
+        const_cast<fvMesh&>(fm).setInstance(fm.time().timeName());
+        volScalarField fld
+        (
+            IOobject
+            (
+                "isLeakCell",
+                fm.time().timeName(),
+                fm,
+                IOobject::NO_READ,
+                IOobject::NO_WRITE
+            ),
+            fm,
+            dimensionedScalar(dimless, Zero)
+        );
+        forAll(isLeakCell, celli)
+        {
+            fld[celli] = isLeakCell[celli];
+        }
+        fld.correctBoundaryConditions();
+        fld.write();
+    }
+
+    if (maxIter > 1 && iter == maxIter)
+    {
+        WarningInFunction << "Did not manage to close gap using " << iter
+            << " leak paths" << nl << "This can cause problems when using the"
+            << " paths to close leaks" << endl;
+    }
 }
 
 
@@ -690,7 +1147,7 @@ void Foam::shortestPathSet::genSamples
     const label maxIter,
     const polyMesh& mesh,
     const labelUList& wallPatches,
-    const boolList& isBlockedFace
+    const PackedBoolList& isBlockedFace
 )
 {
     // Storage for sample points
@@ -701,8 +1158,7 @@ void Foam::shortestPathSet::genSamples
     DynamicList<scalar> samplingCurveDist;
 
     // Seed faces and points on 'real' boundary
-    PackedBoolList isLeakFace(mesh.nFaces());
-    PackedBoolList isLeakPoint(mesh.nPoints());
+    PackedBoolList isBlockedPoint(mesh.nPoints());
     {
         // Real boundaries
         const polyBoundaryMesh& pbm = mesh.boundaryMesh();
@@ -711,7 +1167,7 @@ void Foam::shortestPathSet::genSamples
             const polyPatch& pp = pbm[patchi];
             forAll(pp, i)
             {
-                isLeakPoint.set(pp[i]);
+                isBlockedPoint.set(pp[i]);
             }
         }
 
@@ -720,20 +1176,35 @@ void Foam::shortestPathSet::genSamples
         {
             if (isBlockedFace[facei])
             {
-                isLeakPoint.set(mesh.faces()[facei]);
+                isBlockedPoint.set(mesh.faces()[facei]);
             }
         }
 
         syncTools::syncPointList
         (
             mesh,
-            isLeakPoint,
+            isBlockedPoint,
             orEqOp<unsigned int>(),
             0u
         );
+
+        if (debug)
+        {
+            mkDir(mesh.time().timePath());
+            OBJstream str(mesh.time().timePath()/"isBlockedPoint.obj");
+            for (const auto& pointi : isBlockedPoint)
+            {
+                str.write(mesh.points()[pointi]);
+            }
+            Pout<< "Writing " << str.nVertices() << " points to " << str.name()
+                << endl;
+        }
     }
 
 
+    PackedBoolList isLeakPoint(isBlockedPoint);
+    // Newly closed faces
+    PackedBoolList isLeakFace(mesh.nFaces());
     // All cells along leak paths
     PackedBoolList isLeakCell(mesh.nCells());
 
@@ -784,11 +1255,19 @@ void Foam::shortestPathSet::genSamples
         }
     }
 
+    // Clean up leak faces (erode open edges). These are leak faces which are
+    // not connected to another leak face or leak point
+    erodeFaceSet(mesh, isBlockedPoint, isLeakFace);
+
+    leakFaces_ = isLeakFace.sortedToc();
+
+
     if (debug)
     {
-        const faceList leakFaces(mesh.faces(), isLeakFace.sortedToc());
+        const faceList leakFaces(mesh.faces(), leakFaces_);
 
-        OBJstream str(mesh.time().path()/"isLeakFace.obj");
+        mkDir(mesh.time().timePath());
+        OBJstream str(mesh.time().timePath()/"isLeakFace.obj");
         str.write(leakFaces, mesh.points(), false);
         Pout<< "Writing " << leakFaces.size() << " faces to " << str.name()
             << endl;
@@ -914,7 +1393,14 @@ Foam::shortestPathSet::shortestPathSet
         }
     }
 
-    genSamples(markLeakPath, maxIter, mesh, wallPatches, isBlockedFace);
+    genSamples
+    (
+        markLeakPath,
+        maxIter,
+        mesh,
+        wallPatches,
+        PackedBoolList(isBlockedFace)
+    );
 }
 
 
@@ -945,7 +1431,7 @@ Foam::shortestPathSet::shortestPathSet
         }
     }
 
-    genSamples(markLeakPath, maxIter, mesh, wallPatches, boolList());
+    genSamples(markLeakPath, maxIter, mesh, wallPatches, PackedBoolList());
 }
 
 
diff --git a/src/sampling/sampledSet/shortestPath/shortestPathSet.H b/src/sampling/sampledSet/shortestPath/shortestPathSet.H
index d2ac1b52492..13a5cda0e3f 100644
--- a/src/sampling/sampledSet/shortestPath/shortestPathSet.H
+++ b/src/sampling/sampledSet/shortestPath/shortestPathSet.H
@@ -100,6 +100,9 @@ class shortestPathSet
         //- Destination set of points
         const pointField outsidePoints_;
 
+        //- Local faces that were closed
+        labelList leakFaces_;
+
 
     // Private Member Functions
 
@@ -146,6 +149,53 @@ class shortestPathSet
             const PackedBoolList& isLeakPoint
         ) const;
 
+        //- Removes open-edged faces from set. Ignores edges with both points
+        //  in isBlockedPoint. Returns global number of faces removed from set.
+        label erodeFaceSet
+        (
+            const polyMesh& mesh,
+            const PackedBoolList& isBlockedPoint,
+            PackedBoolList& isLeakFace
+        ) const;
+
+        //- Mark faces inbetween leak cells
+        bitSet pathFaces(const polyMesh& mesh, const bitSet& isLeakCell) const;
+
+        //- Calculate path between insideCelli (-1 if not on current processor)
+        //  and outsidePoint. Appends cellcentres on path to track.
+        //      isLeakCell  : track passes through cell
+        //      isLeakFace  : faces of leak cells that are also on boundary
+        //      isLeakPoint : points  of leak faces       ,,
+        // Returns true if new path found, false otherwise
+        bool genSingleLeakPath
+        (
+            const bool markLeakPath,
+            const label iter,
+            const polyMesh& mesh,
+            const PackedBoolList& isBlockedFace,
+            const point& insidePoint,
+            const label insideCelli,
+            const point& outsidePoint,
+            const label outsideCelli,
+
+            // Generated sampling points
+            const scalar trackLength,
+            DynamicList<point>& samplingPts,
+            DynamicList<label>& samplingCells,
+            DynamicList<label>& samplingFaces,
+            DynamicList<label>& samplingSegments,
+            DynamicList<scalar>& samplingCurveDist,
+
+            // State of current leak paths
+            PackedBoolList& isLeakCell,
+            PackedBoolList& isLeakFace,
+            PackedBoolList& isLeakPoint,
+
+            // Work storage
+            List<topoDistanceData>& allFaceInfo,
+            List<topoDistanceData>& allCellInfo
+        ) const;
+
         //- Calculate path between insideCelli (-1 if not on current processor)
         //  and outsidePoint. Appends cellcentres on path to track.
         //      isLeakCell  : track passes through cell
@@ -156,7 +206,7 @@ class shortestPathSet
             const bool markLeakPath,
             const label maxIter,
             const polyMesh& mesh,
-            const boolList& isBlockedFace,
+            const PackedBoolList& isBlockedFace,
             const point& insidePoint,
             const label insideCelli,
             const point& outsidePoint,
@@ -182,7 +232,7 @@ class shortestPathSet
             const label maxIter,
             const polyMesh& mesh,
             const labelUList& wallPatches,
-            const boolList& blockedFace
+            const PackedBoolList& blockedFace
         );
 
 
@@ -222,6 +272,16 @@ public:
 
     //- Destructor
     virtual ~shortestPathSet() = default;
+
+
+    // Member Functions
+
+        //- Set of mesh faces needed to close the gap. Will close the gap but
+        //  might be bigger than required
+        const labelList& leakFaces() const
+        {
+            return leakFaces_;
+        }
 };
 
 
-- 
GitLab