From aad6c1ba3c0f030ad73c95ad241f3f9d7ab826e0 Mon Sep 17 00:00:00 2001
From: Mark Olesen <Mark.Olesen@esi-group.com>
Date: Tue, 11 May 2021 07:30:00 +0200
Subject: [PATCH] ENH: initial revamp of faMesh to improve modularity (#2084)

- improved separation of patch creation that is also parallel-aware,
  which now allows creation in parallel

- memory-safe use of PtrList for adding patches, with a more generalized
  faPatchData helper

- use uindirectPrimitivePatch instead of indirectPrimitivePatch
  for internal patch handling.

- align boundary methods with polyMesh equivalents

- system/faMeshDefinition instead of constant/faMesh/faMeshDefinition
  as per blockMesh convention. Easier to manage definitions, easier
  for cleanup.

- drop inheritence from GeoMesh.
---
 .../finiteArea/makeFaMesh/Make/options        |   6 +-
 .../finiteArea/makeFaMesh/decomposeFaFields.H |  23 +
 .../makeFaMesh/findMeshDefinitionDict.H       |  97 +++
 .../finiteArea/makeFaMesh/makeFaMesh.C        | 307 +------
 .../decomposePar/faMeshDecomposition.C        | 122 +--
 .../foamToVTK/writeAreaFields.H               |   4 +-
 .../interfaceTrackingFvMesh.C                 |   4 +-
 src/finiteArea/Make/files                     |   2 +
 .../faMesh/faBoundaryMesh/faBoundaryMesh.C    | 109 ++-
 .../faMesh/faBoundaryMesh/faBoundaryMesh.H    | 117 ++-
 src/finiteArea/faMesh/faMesh.C                | 811 ++++--------------
 src/finiteArea/faMesh/faMesh.H                | 196 +++--
 .../faMesh/faMeshDemandDrivenData.C           | 109 +--
 src/finiteArea/faMesh/faMeshI.H               | 142 +++
 src/finiteArea/faMesh/faMeshPatches.C         | 572 ++++++++++++
 src/finiteArea/faMesh/faMeshUpdate.C          |   9 +-
 .../faPatches/basic/coupled/coupledFaPatch.C  |   6 -
 .../faPatches/basic/coupled/coupledFaPatch.H  |   2 +-
 .../faPatches/constraint/empty/emptyFaPatch.C |  41 +-
 .../faPatches/constraint/empty/emptyFaPatch.H |  20 +-
 .../constraint/processor/processorFaPatch.H   |   3 +-
 .../faPatches/constraint/wedge/wedgeFaPatch.C |  16 +-
 .../faMesh/faPatches/faPatch/faPatch.C        |  68 +-
 .../faMesh/faPatches/faPatch/faPatch.H        |  35 +-
 .../faMesh/faPatches/faPatch/faPatchData.C    | 130 +++
 .../faMesh/faPatches/faPatch/faPatchData.H    |  88 +-
 .../faPatch/faPatchFaMeshTemplates.C          |   7 +-
 27 files changed, 1727 insertions(+), 1319 deletions(-)
 create mode 100644 applications/utilities/finiteArea/makeFaMesh/decomposeFaFields.H
 create mode 100644 applications/utilities/finiteArea/makeFaMesh/findMeshDefinitionDict.H
 create mode 100644 src/finiteArea/faMesh/faMeshI.H
 create mode 100644 src/finiteArea/faMesh/faMeshPatches.C
 create mode 100644 src/finiteArea/faMesh/faPatches/faPatch/faPatchData.C

diff --git a/applications/utilities/finiteArea/makeFaMesh/Make/options b/applications/utilities/finiteArea/makeFaMesh/Make/options
index 98ba4f7225d..3300bd23e78 100644
--- a/applications/utilities/finiteArea/makeFaMesh/Make/options
+++ b/applications/utilities/finiteArea/makeFaMesh/Make/options
@@ -1,8 +1,12 @@
 EXE_INC = \
     -I$(LIB_SRC)/finiteArea/lnInclude \
     -I$(LIB_SRC)/finiteVolume/lnInclude \
+    -I$(LIB_SRC)/fileFormats/lnInclude \
+    -I$(LIB_SRC)/meshTools/lnInclude \
     -I$(LIB_SRC)/cfdTools/general/lnInclude
 
 EXE_LIBS = \
     -lfiniteArea \
-    -lfiniteVolume
+    -lfiniteVolume \
+    -lfileFormats \
+    -lmeshTools
diff --git a/applications/utilities/finiteArea/makeFaMesh/decomposeFaFields.H b/applications/utilities/finiteArea/makeFaMesh/decomposeFaFields.H
new file mode 100644
index 00000000000..23b0c2c1e6f
--- /dev/null
+++ b/applications/utilities/finiteArea/makeFaMesh/decomposeFaFields.H
@@ -0,0 +1,23 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
+   \\    /   O peration     |
+    \\  /    A nd           | www.openfoam.com
+     \\/     M anipulation  |
+-------------------------------------------------------------------------------
+    Copyright (C) 2021 OpenCFD Ltd.
+-------------------------------------------------------------------------------
+License
+    This file is part of OpenFOAM, distributed under GPL-3.0-or-later.
+
+Description
+    placeholder for decomposing fields
+
+\*---------------------------------------------------------------------------*/
+
+if (Pstream::parRun())
+{
+}
+
+
+// ************************************************************************* //
diff --git a/applications/utilities/finiteArea/makeFaMesh/findMeshDefinitionDict.H b/applications/utilities/finiteArea/makeFaMesh/findMeshDefinitionDict.H
new file mode 100644
index 00000000000..8a678fdcc8c
--- /dev/null
+++ b/applications/utilities/finiteArea/makeFaMesh/findMeshDefinitionDict.H
@@ -0,0 +1,97 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
+   \\    /   O peration     |
+    \\  /    A nd           | www.openfoam.com
+     \\/     M anipulation  |
+-------------------------------------------------------------------------------
+    Copyright (C) 2021 OpenCFD Ltd.
+-------------------------------------------------------------------------------
+License
+    This file is part of OpenFOAM, distributed under GPL-3.0-or-later.
+
+Description
+    Search for the appropriate faMeshDefinition dictionary....
+
+\*---------------------------------------------------------------------------*/
+
+const word dictName("faMeshDefinition");
+
+autoPtr<IOdictionary> meshDictPtr;
+
+{
+    fileName dictPath;
+
+    const word& regionDir =
+        (regionName == polyMesh::defaultRegion ? word::null : regionName);
+
+    if (args.readIfPresent("dict", dictPath))
+    {
+        // Dictionary specified on the command-line ...
+
+        if (isDir(dictPath))
+        {
+            dictPath /= dictName;
+        }
+    }
+    else if
+    (
+        // Check global location
+        exists
+        (
+            runTime.path()/runTime.caseConstant()
+            /regionDir/faMesh::meshSubDir/dictName
+        )
+    )
+    {
+        // Dictionary present in constant faMesh directory (old-style)
+
+        dictPath =
+            runTime.constant()
+            /regionDir/faMesh::meshSubDir/dictName;
+
+        // Warn that constant/faMesh/faMeshDefinition was used
+        // instead of system/faMeshDefinition
+        #if 0
+        WarningIn(args.executable())
+            << "Using the old faMeshDefinition location: "
+            << dictPath << nl
+            << "    instead of default location: "
+            << runTime.system()/regionDir/dictName << nl
+            << endl;
+        #endif
+    }
+    else
+    {
+        // Assume dictionary is in the system directory
+
+        dictPath = runTime.system()/regionDir/dictName;
+    }
+
+    IOobject meshDictIO
+    (
+        dictPath,
+        runTime,
+        IOobject::MUST_READ,
+        IOobject::NO_WRITE,
+        false,  // no registerObject
+        true    // is globalObject
+    );
+
+    if (!meshDictIO.typeHeaderOk<IOdictionary>(true))
+    {
+        FatalErrorInFunction
+            << meshDictIO.objectPath() << nl
+            << exit(FatalError);
+    }
+
+    Info<< "Creating faMesh from definition: "
+        << runTime.relativePath(meshDictIO.objectPath()) << endl;
+
+    meshDictPtr = autoPtr<IOdictionary>::New(meshDictIO);
+}
+
+IOdictionary& meshDefDict = *meshDictPtr;
+
+
+// ************************************************************************* //
diff --git a/applications/utilities/finiteArea/makeFaMesh/makeFaMesh.C b/applications/utilities/finiteArea/makeFaMesh/makeFaMesh.C
index 0010be3dabc..2751ca31546 100644
--- a/applications/utilities/finiteArea/makeFaMesh/makeFaMesh.C
+++ b/applications/utilities/finiteArea/makeFaMesh/makeFaMesh.C
@@ -6,6 +6,7 @@
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
     Copyright (C) 2016-2017 Wikki Ltd
+    Copyright (C) 2021 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -41,30 +42,12 @@ Author
 #include "OSspecific.H"
 #include "faMesh.H"
 #include "fvMesh.H"
+#include "IOdictionary.H"
+#include "globalIndex.H"
+#include "globalMeshData.H"
 
 using namespace Foam;
 
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
-
-class faPatchData
-{
-public:
-    word name_;
-    word type_;
-    dictionary dict_;
-    label ownPolyPatchID_;
-    label ngbPolyPatchID_;
-    labelList edgeLabels_;
-    faPatchData()
-    :
-        name_(word::null),
-        type_(word::null),
-        ownPolyPatchID_(-1),
-        ngbPolyPatchID_(-1)
-    {}
-};
-
-
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
 
 int main(int argc, char *argv[])
@@ -73,286 +56,40 @@ int main(int argc, char *argv[])
     (
         "A mesh generator for finiteArea mesh"
     );
+    argList::addOption
+    (
+        "empty-patch",
+        "name",
+        "Specify name for a default empty patch",
+        false  // An advanced option, but not enough to worry about that
+    );
+    argList::addOption("dict", "file", "Alternative faMeshDefinition");
 
     #include "addRegionOption.H"
-    argList::noParallel();
-
     #include "setRootCase.H"
     #include "createTime.H"
     #include "createNamedMesh.H"
 
     // Reading faMeshDefinition dictionary
-    IOdictionary faMeshDefinition
-    (
-        IOobject
-        (
-            "faMeshDefinition",
-            runTime.constant(),
-            "faMesh",
-            mesh,
-            IOobject::MUST_READ,
-            IOobject::NO_WRITE
-        )
-    );
-
-    wordList polyMeshPatches
-    (
-        faMeshDefinition.get<wordList>("polyMeshPatches")
-    );
-
-    const dictionary& bndDict = faMeshDefinition.subDict("boundary");
-
-    const wordList faPatchNames(bndDict.toc());
-
-    List<faPatchData> faPatches(faPatchNames.size()+1);
-
-    forAll(faPatchNames, patchI)
-    {
-        const dictionary& curPatchDict = bndDict.subDict(faPatchNames[patchI]);
-
-        faPatches[patchI].name_ = faPatchNames[patchI];
-
-        faPatches[patchI].type_ = curPatchDict.get<word>("type");
-
-        const word ownName(curPatchDict.get<word>("ownerPolyPatch"));
-
-        faPatches[patchI].ownPolyPatchID_ =
-            mesh.boundaryMesh().findPatchID(ownName);
-
-        if (faPatches[patchI].ownPolyPatchID_ < 0)
-        {
-            FatalErrorIn("makeFaMesh:")
-                << "neighbourPolyPatch " << ownName << " does not exist"
-                << exit(FatalError);
-        }
-
-        const word neiName(curPatchDict.get<word>("neighbourPolyPatch"));
-
-        faPatches[patchI].ngbPolyPatchID_ =
-            mesh.boundaryMesh().findPatchID(neiName);
-
-        if (faPatches[patchI].ngbPolyPatchID_ < 0)
-        {
-            FatalErrorIn("makeFaMesh:")
-                << "neighbourPolyPatch " << neiName << " does not exist"
-                << exit(FatalError);
-        }
-    }
-
-    // Setting faceLabels list size
-    label size = 0;
-
-    labelList patchIDs(polyMeshPatches.size(), -1);
-
-    forAll(polyMeshPatches, patchI)
-    {
-        patchIDs[patchI] =
-            mesh.boundaryMesh().findPatchID(polyMeshPatches[patchI]);
-
-        if (patchIDs[patchI] < 0)
-        {
-            FatalErrorIn("makeFaMesh:")
-                << "Patch " << polyMeshPatches[patchI] << " does not exist"
-                << exit(FatalError);
-        }
-
-        size += mesh.boundaryMesh()[patchIDs[patchI]].size();
-    }
-
-    labelList faceLabels(size, -1);
-
-    sort(patchIDs);
-
-
-    // Filling of faceLabels list
-    label faceI = -1;
-
-    forAll(polyMeshPatches, patchI)
-    {
-        label start = mesh.boundaryMesh()[patchIDs[patchI]].start();
-
-        label size  = mesh.boundaryMesh()[patchIDs[patchI]].size();
-
-        for (label i = 0; i < size; ++i)
-        {
-            faceLabels[++faceI] = start + i;
-        }
-    }
-
-    // Creating faMesh
-    Info << "Create faMesh ... ";
+    #include "findMeshDefinitionDict.H"
 
-    faMesh areaMesh
-    (
-        mesh,
-        faceLabels
-    );
-    Info << "Done" << endl;
-
-
-    // Determination of faPatch ID for each boundary edge.
-    // Result is in the bndEdgeFaPatchIDs list
-    const indirectPrimitivePatch& patch = areaMesh.patch();
-
-    labelList faceCells(faceLabels.size(), -1);
-
-    forAll(faceCells, faceI)
-    {
-        label faceID = faceLabels[faceI];
-
-        faceCells[faceI] = mesh.faceOwner()[faceID];
-    }
-
-    labelList meshEdges =
-        patch.meshEdges
-        (
-            mesh.edges(),
-            mesh.cellEdges(),
-            faceCells
-        );
-
-    const labelListList& edgeFaces = mesh.edgeFaces();
-
-    const label nTotalEdges = patch.nEdges();
-    const label nInternalEdges = patch.nInternalEdges();
-
-    labelList bndEdgeFaPatchIDs(nTotalEdges - nInternalEdges, -1);
-
-    for (label edgeI = nInternalEdges; edgeI < nTotalEdges; ++edgeI)
-    {
-        label curMeshEdge = meshEdges[edgeI];
-
-        labelList curEdgePatchIDs(2, label(-1));
-
-        label patchI = -1;
-
-        forAll(edgeFaces[curMeshEdge], faceI)
-        {
-            label curFace = edgeFaces[curMeshEdge][faceI];
-
-            label curPatchID = mesh.boundaryMesh().whichPatch(curFace);
-
-            if (curPatchID != -1)
-            {
-                curEdgePatchIDs[++patchI] = curPatchID;
-            }
-        }
-
-        for (label pI = 0; pI < faPatches.size() - 1; ++pI)
-        {
-            if
-            (
-                (
-                    curEdgePatchIDs[0] == faPatches[pI].ownPolyPatchID_
-                 && curEdgePatchIDs[1] == faPatches[pI].ngbPolyPatchID_
-                )
-                ||
-                (
-                    curEdgePatchIDs[1] == faPatches[pI].ownPolyPatchID_
-                 && curEdgePatchIDs[0] == faPatches[pI].ngbPolyPatchID_
-                )
-            )
-            {
-                bndEdgeFaPatchIDs[edgeI - nInternalEdges] = pI;
-                break;
-            }
-        }
-    }
-
-
-    // Set edgeLabels for each faPatch
-    for (label pI=0; pI<(faPatches.size()-1); ++pI)
-    {
-        SLList<label> tmpList;
-
-        forAll(bndEdgeFaPatchIDs, eI)
-        {
-            if (bndEdgeFaPatchIDs[eI] == pI)
-            {
-                tmpList.append(nInternalEdges + eI);
-            }
-        }
-
-        faPatches[pI].edgeLabels_ = tmpList;
-    }
-
-    // Check for undefined edges
-    SLList<label> tmpList;
-
-    forAll(bndEdgeFaPatchIDs, eI)
-    {
-        if (bndEdgeFaPatchIDs[eI] == -1)
-        {
-            tmpList.append(nInternalEdges + eI);
-        }
-    }
-
-    if (tmpList.size() > 0)
+    // Inject/overwrite name for optional 'empty' patch
+    word patchName;
+    if (args.readIfPresent("empty-patch", patchName))
     {
-        label pI = faPatches.size()-1;
-
-        faPatches[pI].name_ = "undefined";
-        faPatches[pI].type_ = "patch";
-        faPatches[pI].edgeLabels_ = tmpList;
+        meshDefDict.add("emptyPatch", patchName, true);
     }
 
-    // Add good patches to faMesh
-    SLList<faPatch*> faPatchLst;
-
-    for (label pI = 0; pI < faPatches.size(); ++pI)
-    {
-        faPatches[pI].dict_.add("type", faPatches[pI].type_);
-        faPatches[pI].dict_.add("edgeLabels", faPatches[pI].edgeLabels_);
-        faPatches[pI].dict_.add
-        (
-            "ngbPolyPatchIndex",
-            faPatches[pI].ngbPolyPatchID_
-        );
-
-        if(faPatches[pI].edgeLabels_.size() > 0)
-        {
-            faPatchLst.append
-            (
-                faPatch::New
-                (
-                    faPatches[pI].name_,
-                    faPatches[pI].dict_,
-                    pI,
-                    areaMesh.boundary()
-                ).ptr()
-            );
-        }
-    }
-
-    word emptyPatchName;
-    if (args.readIfPresent("addEmptyPatch", emptyPatchName))
-    {
-        dictionary emptyPatchDict;
-        emptyPatchDict.add("type", "empty");
-        emptyPatchDict.add("edgeLabels", labelList());
-        emptyPatchDict.add("ngbPolyPatchIndex", -1);
-
-        faPatchLst.append
-        (
-            faPatch::New
-            (
-                emptyPatchName,
-                emptyPatchDict,
-                faPatchLst.size(),
-                areaMesh.boundary()
-            ).ptr()
-        );
-    }
-
-    Info << "Add faPatches ... ";
-    areaMesh.addFaPatches(List<faPatch*>(faPatchLst));
-    Info << "Done" << endl;
+    // Creation
+    faMesh areaMesh(mesh, meshDefDict);
 
     // Writing faMesh
     Info << "Write finite area mesh ... ";
     areaMesh.write();
 
-    Info << "\nEnd" << endl;
+    #include "decomposeFaFields.H"
+
+    Info << "\nEnd\n" << endl;
 
     return 0;
 }
diff --git a/applications/utilities/parallelProcessing/decomposePar/faMeshDecomposition.C b/applications/utilities/parallelProcessing/decomposePar/faMeshDecomposition.C
index c08daf4b48b..bdbb92dc79e 100644
--- a/applications/utilities/parallelProcessing/decomposePar/faMeshDecomposition.C
+++ b/applications/utilities/parallelProcessing/decomposePar/faMeshDecomposition.C
@@ -6,7 +6,7 @@
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
     Copyright (C) 2016-2017 Wikki Ltd
-    Copyright (C) 2018-2019 OpenCFD Ltd.
+    Copyright (C) 2018-2021 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -41,6 +41,8 @@ License
 
 void Foam::faMeshDecomposition::distributeFaces()
 {
+    const word& polyMeshRegionName = mesh().name();
+
     Info<< "\nCalculating distribution of faces" << endl;
 
     cpuTime decompositionTime;
@@ -58,7 +60,7 @@ void Foam::faMeshDecomposition::distributeFaces()
         (
             IOobject
             (
-                GeoMesh<polyMesh>::mesh_.name(),
+                polyMeshRegionName,
                 processorDb.timeName(),
                 processorDb
             )
@@ -214,6 +216,8 @@ void Foam::faMeshDecomposition::decomposeMesh()
     // Decide which cell goes to which processor
     distributeFaces();
 
+    const word& polyMeshRegionName = mesh().name();
+
     Info<< "\nDistributing faces to processors" << endl;
 
     // Memory management
@@ -224,9 +228,9 @@ void Foam::faMeshDecomposition::decomposeMesh()
         {
             if (faceToProc_[faceI] >= nProcs())
             {
-                FatalErrorIn("Finite area mesh decomposition")
+                FatalErrorInFunction
                     << "Impossible processor label " << faceToProc_[faceI]
-                    << "for face " << faceI
+                    << "for face " << faceI << nl
                     << abort(FatalError);
             }
             else
@@ -258,7 +262,7 @@ void Foam::faMeshDecomposition::decomposeMesh()
         (
             IOobject
             (
-                GeoMesh<polyMesh>::mesh_.name(),
+                polyMeshRegionName,
                 processorDb.timeName(),
                 processorDb
             )
@@ -314,7 +318,7 @@ void Foam::faMeshDecomposition::decomposeMesh()
                 fvFaceProcAddressingHash.find
                 (
                     faceLabels()[curProcFaceAddressing[faceI]] + 1
-                )();
+                ).val();
         }
 
         // create processor finite area mesh
@@ -324,38 +328,35 @@ void Foam::faMeshDecomposition::decomposeMesh()
             procFaceLabels_[procI]
         );
 
-        const indirectPrimitivePatch& patch = this->patch();
+        const uindirectPrimitivePatch& patch = this->patch();
         const Map<label>& map = patch.meshPointMap();
 
         EdgeMap<label> edgesHash;
 
-        label edgeI = -1;
-
         const label nIntEdges = patch.nInternalEdges();
 
-        for (label curEdge = 0; curEdge < nIntEdges; curEdge++)
+        for (label edgei = 0; edgei < nIntEdges; ++edgei)
         {
-            edgesHash.insert(patch.edges()[curEdge], ++edgeI);
+            edgesHash.insert(patch.edges()[edgei], edgesHash.size());
         }
 
-        forAll(boundary(), patchI)
+        forAll(boundary(), patchi)
         {
             // Include emptyFaPatch
+            const label size = boundary()[patchi].labelList::size();
 
-            const label size = boundary()[patchI].labelList::size();
-
-            for(int eI=0; eI<size; eI++)
+            for (label edgei=0; edgei < size; ++edgei)
             {
                 edgesHash.insert
                 (
-                    patch.edges()[boundary()[patchI][eI]],
-                    ++edgeI
+                    patch.edges()[boundary()[patchi][edgei]],
+                    edgesHash.size()
                 );
             }
         }
 
 
-        const indirectPrimitivePatch& procPatch = procMesh.patch();
+        const uindirectPrimitivePatch& procPatch = procMesh.patch();
         const vectorField& procPoints = procPatch.localPoints();
         const labelList& procMeshPoints = procPatch.meshPoints();
         const edgeList& procEdges = procPatch.edges();
@@ -370,21 +371,18 @@ void Foam::faMeshDecomposition::decomposeMesh()
         }
 
         labelList& curPatchEdgeAddressing = procPatchEdgeAddressing_[procI];
-        curPatchEdgeAddressing.setSize(procEdges.size(), -1);
+        curPatchEdgeAddressing.resize(procEdges.size(), -1);
+
+        Map<label>& curMap = procMeshEdgesMap_[procI];
+        curMap.clear();
+        curMap.resize(2*procEdges.size());
 
         forAll(procEdges, edgeI)
         {
-            edge curGlobalEdge = procEdges[edgeI];
-            curGlobalEdge[0] = curPatchPointAddressing[curGlobalEdge[0]];
-            curGlobalEdge[1] = curPatchPointAddressing[curGlobalEdge[1]];
-
-            curPatchEdgeAddressing[edgeI] = edgesHash.find(curGlobalEdge)();
+            edge curGlobalEdge(curPatchPointAddressing, procEdges[edgeI]);
+            curPatchEdgeAddressing[edgeI] = edgesHash.find(curGlobalEdge).val();
         }
 
-        Map<label>& curMap = procMeshEdgesMap_[procI];
-
-        curMap = Map<label>(2*procEdges.size());
-
         forAll(curPatchEdgeAddressing, edgeI)
         {
             curMap.insert(curPatchEdgeAddressing[edgeI], edgeI);
@@ -1057,7 +1055,7 @@ void Foam::faMeshDecomposition::decomposeMesh()
         (
             IOobject
             (
-                GeoMesh<polyMesh>::mesh_.name(),
+                polyMeshRegionName,
                 processorDb.timeName(),
                 processorDb
             )
@@ -1137,8 +1135,9 @@ void Foam::faMeshDecomposition::decomposeMesh()
 
 bool Foam::faMeshDecomposition::writeDecomposition()
 {
-    Info<< "\nConstructing processor FA meshes" << endl;
+    const word& polyMeshRegionName = mesh().name();
 
+    Info<< "\nConstructing processor FA meshes" << endl;
 
     // Make a lookup map for globally shared points
     Map<label> sharedPointLookup(2*globallySharedPoints_.size());
@@ -1175,7 +1174,7 @@ bool Foam::faMeshDecomposition::writeDecomposition()
         (
             IOobject
             (
-                GeoMesh<polyMesh>::mesh_.name(),
+                polyMeshRegionName,
                 processorDb.timeName(),
                 processorDb
             )
@@ -1195,7 +1194,7 @@ bool Foam::faMeshDecomposition::writeDecomposition()
         );
 
 
-        // create finite area mesh
+        // Create finite area mesh
         faMesh procMesh
         (
             procFvMesh,
@@ -1219,11 +1218,9 @@ bool Foam::faMeshDecomposition::writeDecomposition()
 
         const faPatchList& meshPatches = boundary();
 
-        List<faPatch*> procPatches
+        PtrList<faPatch> procPatches
         (
-            curPatchSizes.size()
-          + curProcessorPatchSizes.size(),
-            reinterpret_cast<faPatch*>(NULL)
+            curPatchSizes.size() + curProcessorPatchSizes.size()
         );
 
         label nPatches = 0;
@@ -1232,44 +1229,51 @@ bool Foam::faMeshDecomposition::writeDecomposition()
         {
             const labelList& curEdgeLabels = curPatchEdgeLabels[nPatches];
 
-            const label ngbPolyPatchIndex =
+            const label neiPolyPatchId =
                 fvBoundaryProcAddressing.find
                 (
                     meshPatches[curBoundaryAddressing[patchi]]
                     .ngbPolyPatchIndex()
                 );
 
-            procPatches[nPatches] =
+            procPatches.set
+            (
+                nPatches,
                 meshPatches[curBoundaryAddressing[patchi]].clone
                 (
                     procMesh.boundary(),
                     curEdgeLabels,
                     nPatches,
-                    ngbPolyPatchIndex
-                ).ptr();
-
-            nPatches++;
+                    neiPolyPatchId
+                )
+            );
+            ++nPatches;
         }
 
         forAll(curProcessorPatchSizes, procPatchI)
         {
             const labelList& curEdgeLabels = curPatchEdgeLabels[nPatches];
 
-            procPatches[nPatches] =
+            procPatches.set
+            (
+                nPatches,
                 new processorFaPatch
                 (
-                    word("procBoundary") + Foam::name(procI)
-                  + word("to")
-                  + Foam::name(curNeighbourProcessors[procPatchI]),
+                    processorPolyPatch::newName
+                    (
+                        procI,
+                        curNeighbourProcessors[procPatchI]
+                    ),
                     curEdgeLabels,
                     nPatches,
                     procMesh.boundary(),
                     -1,
                     procI,
                     curNeighbourProcessors[procPatchI]
-                );
+                )
+            );
 
-            nPatches++;
+            ++nPatches;
         }
 
         // Add boundary patches
@@ -1291,23 +1295,19 @@ bool Foam::faMeshDecomposition::writeDecomposition()
 
         forAll(procMesh.boundary(), patchi)
         {
-            if
-            (
-                procMesh.boundary()[patchi].type()
-             == processorFaPatch::typeName
-            )
+            const auto* ppp =
+                isA<processorFaPatch>(procMesh.boundary()[patchi]);
+
+            if (ppp)
             {
-                const processorFaPatch& ppp =
-                refCast<const processorFaPatch>
-                (
-                    procMesh.boundary()[patchi]
-                );
+                const auto& procPatch = *ppp;
 
                 Info<< "    Number of edges shared with processor "
-                    << ppp.neighbProcNo() << " = " << ppp.size() << endl;
+                    << procPatch.neighbProcNo() << " = "
+                    << procPatch.size() << endl;
 
-                nProcPatches++;
-                nProcEdges += ppp.size();
+                nProcEdges += procPatch.size();
+                ++nProcPatches;
             }
             else
             {
diff --git a/applications/utilities/postProcessing/dataConversion/foamToVTK/writeAreaFields.H b/applications/utilities/postProcessing/dataConversion/foamToVTK/writeAreaFields.H
index 959881e65a4..9bd2cb8c37c 100644
--- a/applications/utilities/postProcessing/dataConversion/foamToVTK/writeAreaFields.H
+++ b/applications/utilities/postProcessing/dataConversion/foamToVTK/writeAreaFields.H
@@ -39,7 +39,7 @@ SourceFiles
 #define writeAreaFields_H
 
 #include "readFields.H"
-#include "foamVtkIndPatchGeoFieldsWriter.H"
+#include "foamVtkUIndPatchGeoFieldsWriter.H"
 
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
 
@@ -47,7 +47,7 @@ namespace Foam
 {
 
 // Writer type for finite-area mesh + fields
-typedef vtk::indirectPatchGeoFieldsWriter vtkWriterType_areaMesh;
+typedef vtk::uindirectPatchGeoFieldsWriter vtkWriterType_areaMesh;
 
 template<class GeoField>
 bool writeAreaField
diff --git a/src/dynamicFaMesh/interfaceTrackingFvMesh/interfaceTrackingFvMesh.C b/src/dynamicFaMesh/interfaceTrackingFvMesh/interfaceTrackingFvMesh.C
index bf90e5220ab..5e2cb8a3caa 100644
--- a/src/dynamicFaMesh/interfaceTrackingFvMesh/interfaceTrackingFvMesh.C
+++ b/src/dynamicFaMesh/interfaceTrackingFvMesh/interfaceTrackingFvMesh.C
@@ -48,7 +48,7 @@ License
 #include "turbulentTransportModel.H"
 #include "demandDrivenData.H"
 #include "unitConversion.H"
-#include "foamVtkIndPatchWriter.H"
+#include "foamVtkUIndPatchWriter.H"
 
 // * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * //
 
@@ -2270,7 +2270,7 @@ bool Foam::interfaceTrackingFvMesh::update()
 
 void Foam::interfaceTrackingFvMesh::writeVTK() const
 {
-    vtk::indirectPatchWriter writer
+    vtk::uindirectPatchWriter writer
     (
         aMesh().patch(),
         vtk::formatType::LEGACY_ASCII,
diff --git a/src/finiteArea/Make/files b/src/finiteArea/Make/files
index e2187912329..7a96d8c4a1e 100644
--- a/src/finiteArea/Make/files
+++ b/src/finiteArea/Make/files
@@ -1,11 +1,13 @@
 faMesh/faGlobalMeshData/faGlobalMeshData.C
 faMesh/faMesh.C
 faMesh/faMeshDemandDrivenData.C
+faMesh/faMeshPatches.C
 faMesh/faMeshUpdate.C
 faMesh/faBoundaryMesh/faBoundaryMesh.C
 
 faPatches = faMesh/faPatches
 $(faPatches)/faPatch/faPatch.C
+$(faPatches)/faPatch/faPatchData.C
 $(faPatches)/faPatch/faPatchNew.C
 $(faPatches)/basic/coupled/coupledFaPatch.C
 $(faPatches)/constraint/empty/emptyFaPatch.C
diff --git a/src/finiteArea/faMesh/faBoundaryMesh/faBoundaryMesh.C b/src/finiteArea/faMesh/faBoundaryMesh/faBoundaryMesh.C
index b436ecf9962..da7962e51b1 100644
--- a/src/finiteArea/faMesh/faBoundaryMesh/faBoundaryMesh.C
+++ b/src/finiteArea/faMesh/faBoundaryMesh/faBoundaryMesh.C
@@ -54,7 +54,7 @@ Foam::faBoundaryMesh::faBoundaryMesh
     {
         faPatchList& patches = *this;
 
-        // Read polyPatchList
+        // Read faPatch list
         Istream& is = readStream(typeName);
 
         PtrList<entry> patchEntries(is);
@@ -117,12 +117,6 @@ void Foam::faBoundaryMesh::calcGeometry()
 }
 
 
-const Foam::faMesh& Foam::faBoundaryMesh::mesh() const
-{
-    return mesh_;
-}
-
-
 Foam::lduInterfacePtrsList Foam::faBoundaryMesh::interfaces() const
 {
     lduInterfacePtrsList interfaces(size());
@@ -143,6 +137,26 @@ Foam::lduInterfacePtrsList Foam::faBoundaryMesh::interfaces() const
 }
 
 
+Foam::label Foam::faBoundaryMesh::nNonProcessor() const
+{
+    const faPatchList& patches = *this;
+
+    label nonProc = 0;
+
+    for (const faPatch& p : patches)
+    {
+        if (isA<processorFaPatch>(p))
+        {
+            break;
+        }
+
+        ++nonProc;
+    }
+
+    return nonProc;
+}
+
+
 Foam::wordList Foam::faBoundaryMesh::names() const
 {
     return PtrListOps::get<word>(*this, nameOp<faPatch>());
@@ -155,6 +169,71 @@ Foam::wordList Foam::faBoundaryMesh::types() const
 }
 
 
+Foam::labelList Foam::faBoundaryMesh::patchStarts() const
+{
+    // Manually: faPatch does not have independent start() information
+
+    const faPatchList& patches = *this;
+
+    labelList list(patches.size());
+
+    label beg = mesh_.nInternalEdges();
+    forAll(patches, patchi)
+    {
+        const label len = patches[patchi].nEdges();
+        list[patchi] = beg;
+        beg += len;
+    }
+    return list;
+}
+
+
+Foam::labelList Foam::faBoundaryMesh::patchSizes() const
+{
+    return
+        PtrListOps::get<label>
+        (
+            *this,
+            [](const faPatch& p) { return p.nEdges(); }  // avoid virtual
+        );
+}
+
+
+Foam::List<Foam::labelRange> Foam::faBoundaryMesh::patchRanges() const
+{
+    const faPatchList& patches = *this;
+
+    List<labelRange> list(patches.size());
+
+    label beg = mesh_.nInternalEdges();
+    forAll(patches, patchi)
+    {
+        const label len = patches[patchi].nEdges();
+        list[patchi].reset(beg, len);
+        beg += len;
+    }
+    return list;
+}
+
+
+Foam::label Foam::faBoundaryMesh::start() const
+{
+    return mesh_.nInternalEdges();
+}
+
+
+Foam::label Foam::faBoundaryMesh::nEdges() const
+{
+    return mesh_.nBoundaryEdges();
+}
+
+
+Foam::labelRange Foam::faBoundaryMesh::range() const
+{
+    return labelRange(mesh_.nInternalEdges(), mesh_.nBoundaryEdges());
+}
+
+
 Foam::labelList Foam::faBoundaryMesh::indices
 (
     const wordRe& matcher,
@@ -355,6 +434,22 @@ bool Foam::faBoundaryMesh::writeData(Ostream& os) const
 }
 
 
+bool Foam::faBoundaryMesh::writeObject
+(
+    IOstreamOption streamOpt,
+    const bool valid
+) const
+{
+    // Allow/disallow compression?
+    // 1. keep readable
+    // 2. save some space
+    // ??? streamOpt.compression(IOstreamOption::UNCOMPRESSED);
+    return regIOobject::writeObject(streamOpt, valid);
+}
+
+
+// * * * * * * * * * * * * * * * IOstream Operators  * * * * * * * * * * * * //
+
 Foam::Ostream& Foam::operator<<(Ostream& os, const faBoundaryMesh& bm)
 {
     bm.writeData(os);
diff --git a/src/finiteArea/faMesh/faBoundaryMesh/faBoundaryMesh.H b/src/finiteArea/faMesh/faBoundaryMesh/faBoundaryMesh.H
index 4c2c69ead5b..397719aedf1 100644
--- a/src/finiteArea/faMesh/faBoundaryMesh/faBoundaryMesh.H
+++ b/src/finiteArea/faMesh/faBoundaryMesh/faBoundaryMesh.H
@@ -53,8 +53,6 @@ Author
 namespace Foam
 {
 
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
-
 // Forward Declarations
 class faMesh;
 class faBoundaryMesh;
@@ -89,14 +87,14 @@ public:
 
     // Constructors
 
-        //- Construct from dictionary
+        //- Construct from faMesh
         faBoundaryMesh
         (
             const IOobject& io,
             const faMesh& fam
         );
 
-        //- Construct given size
+        //- Construct from faMesh and given size
         faBoundaryMesh
         (
             const IOobject& io,
@@ -111,62 +109,95 @@ public:
 
     // Member Functions
 
-        // Access
+        //- Return the mesh reference
+        const faMesh& mesh() const noexcept
+        {
+            return mesh_;
+        }
+
+        //- Return a list of pointers for each patch
+        //- with only those pointing to interfaces being set
+        lduInterfacePtrsList interfaces() const;
+
+        //- The number of patches before the first processor patch.
+        label nNonProcessor() const;
+
+        //- Return a list of patch names
+        wordList names() const;
 
-            //- Calculate the geometry for the patches
-            //  (transformation tensors etc.)
-            void calcGeometry();
+        //- Return a list of patch types
+        wordList types() const;
 
-            //- Return the mesh reference
-            const faMesh& mesh() const;
+        //- Return a list of patch start indices
+        labelList patchStarts() const;
 
-            //- Return a list of pointers for each patch
-            //- with only those pointing to interfaces being set
-            lduInterfacePtrsList interfaces() const;
+        //- Return a list of patch sizes (number of edges in each patch)
+        labelList patchSizes() const;
 
-            //- Return a list of patch names
-            wordList names() const;
+        //- Return a list of patch ranges
+        List<labelRange> patchRanges() const;
 
-            //- Return a list of patch types
-            wordList types() const;
+        //- The start label of the edges in the faMesh edges list
+        //  Same as mesh.nInternalEdges()
+        label start() const;
 
-            //- Return patch indices for all matches.
-            //  A no-op (returns -1) for an empty key
-            //  \note Matching patchGroups currently not supported
-            labelList indices
-            (
-                const wordRe& matcher,
-                const bool useGroups = false  /* ignored */
-            ) const;
+        //- The number of boundary edges for the underlying mesh
+        //  Same as mesh.nBoundaryEdges()
+        label nEdges() const;
 
-            //- Return patch index for the first match, return -1 if not found
-            //  A no-op (returns -1) for an empty key
-            label findIndex(const wordRe& key) const;
+        //- The edge range for all boundary edges
+        //  Spans [nInternalEdges, nEdges) of the underlying mesh
+        labelRange range() const;
 
-            //- Find patch index given a name, return -1 if not found
-            //  A no-op (returns -1) for an empty name
-            label findPatchID(const word& patchName) const;
 
-            //- Return patch index for a given edge label
-            label whichPatch(const label edgeIndex) const;
+        //- Return patch indices for all matches.
+        //  A no-op (returns -1) for an empty key
+        //  \note Matching patchGroups currently not supported
+        labelList indices
+        (
+            const wordRe& matcher,
+            const bool useGroups = false  /* ignored */
+        ) const;
+
+        //- Return patch index for the first match, return -1 if not found
+        //  A no-op (returns -1) for an empty key
+        label findIndex(const wordRe& key) const;
+
+        //- Find patch index given a name, return -1 if not found
+        //  A no-op (returns -1) for an empty name
+        label findPatchID(const word& patchName) const;
+
+        //- Return patch index for a given edge label
+        label whichPatch(const label edgeIndex) const;
 
-            //- Check boundary definition
-            bool checkDefinition(const bool report = false) const;
+        //- Check boundary definition
+        bool checkDefinition(const bool report = false) const;
 
 
-        // Edit
+    // Edit
 
-            //- Correct faBoundaryMesh after moving points
-            void movePoints(const pointField&);
+        //- Calculate the geometry for the patches
+        //  (transformation tensors etc.)
+        void calcGeometry();
 
-            //- Correct faBoundaryMesh after topology update
-            void updateMesh();
+        //- Correct faBoundaryMesh after moving points
+        void movePoints(const pointField&);
 
-            //- writeData member function required by regIOobject
-            bool writeData(Ostream&) const;
+        //- Correct faBoundaryMesh after topology update
+        void updateMesh();
+
+        //- The writeData member function required by regIOobject
+        bool writeData(Ostream& os) const;
+
+        //- Write using stream options
+        virtual bool writeObject
+        (
+            IOstreamOption streamOpt,
+            const bool valid
+        ) const;
 
 
-    // Ostream operator
+    // Ostream Operator
 
         friend Ostream& operator<<(Ostream&, const faBoundaryMesh&);
 
diff --git a/src/finiteArea/faMesh/faMesh.C b/src/finiteArea/faMesh/faMesh.C
index 1e06c4cc03c..d53be606046 100644
--- a/src/finiteArea/faMesh/faMesh.C
+++ b/src/finiteArea/faMesh/faMesh.C
@@ -47,75 +47,122 @@ namespace Foam
     defineTypeNameAndDebug(faMesh, 0);
 }
 
+
+const Foam::word Foam::faMesh::prefix("finite-area");
+
 Foam::word Foam::faMesh::meshSubDir = "faMesh";
 
-const int Foam::faMesh::quadricsFit_ = 0;
+const int Foam::faMesh::quadricsFit_ = 0;  // Tuning
 
 
-// * * * * * * * * * * * * * Private Member Functions  * * * * * * * * * * * //
+// * * * * * * * * * * * * * * * Local Functions * * * * * * * * * * * * * * //
 
-void Foam::faMesh::setPrimitiveMeshData()
+namespace Foam
 {
-    DebugInFunction << "Setting primitive data" << endl;
 
-    const indirectPrimitivePatch& bp = patch();
-
-    // Set faMesh edges
-    edges_.setSize(bp.nEdges());
+// Convert patch names to face labels. Preserve patch order
+static labelList selectPatchFaces
+(
+    const polyBoundaryMesh& pbm,
+    const wordList& polyPatchNames
+)
+{
+    labelHashSet patchIDs;
 
-    label edgeI = -1;
+    label nFaceLabels = 0;
+    for (const word& patchName : polyPatchNames)
+    {
+        const label polyPatchi = pbm.findPatchID(patchName);
 
-    label nIntEdges = bp.nInternalEdges();
+        if (polyPatchi < 0)
+        {
+            FatalErrorInFunction
+                << "Patch " << patchName << " not found"
+                << exit(FatalError);
+        }
 
-    for (label curEdge = 0; curEdge < nIntEdges; ++curEdge)
-    {
-        edges_[++edgeI] = bp.edges()[curEdge];
+        if (patchIDs.insert(polyPatchi))
+        {
+            nFaceLabels += pbm[polyPatchi].size();
+        }
     }
 
-    forAll(boundary(), patchI)
-    {
-        const labelList& curP = boundary()[patchI];
+    labelList faceLabels(nFaceLabels);
 
-        forAll(curP, eI)
+    nFaceLabels = 0;
+    for (const label polyPatchi : patchIDs.sortedToc())
+    {
+        for (const label facei : pbm[polyPatchi].range())
         {
-            edges_[++edgeI] = bp.edges()[curP[eI]];
+            faceLabels[nFaceLabels] = facei;
+            ++nFaceLabels;
         }
     }
 
-    nEdges_ = edges_.size();
-    nInternalEdges_ = nIntEdges;
+    return faceLabels;
+}
+
+} // End namespace Foam
+
+
+// * * * * * * * * * * * * * Private Member Functions  * * * * * * * * * * * //
+
+void Foam::faMesh::initPatch() const
+{
+    if (patchPtr_)
+    {
+        delete patchPtr_;
+    }
+    patchPtr_ = new uindirectPrimitivePatch
+    (
+        UIndirectList<face>(mesh().faces(), faceLabels_),
+        mesh().points()
+    );
+}
+
+
+void Foam::faMesh::setPrimitiveMeshData()
+{
+    DebugInFunction << "Setting primitive data" << endl;
 
+    const uindirectPrimitivePatch& bp = patch();
+    const labelListList& edgeFaces = bp.edgeFaces();
 
-    // Set edge owner and neighbour
-    edgeOwner_.setSize(nEdges());
-    edgeNeighbour_.setSize(nInternalEdges());
+    // Dimensions
 
-    edgeI = -1;
+    nEdges_ = bp.nEdges();
+    nInternalEdges_ = bp.nInternalEdges();
+    nFaces_ = bp.size();
+    nPoints_ = bp.nPoints();
 
-    const labelListList& bpef = bp.edgeFaces();
+    edges_.resize(nEdges_);
+    edgeOwner_.resize(nEdges_);
+    edgeNeighbour_.resize(nInternalEdges_);
 
-    for (label curEdge = 0; curEdge < nIntEdges; ++curEdge)
+    // Internal edges
+    for (label edgei = 0; edgei < nInternalEdges_; ++edgei)
     {
-        edgeOwner_[++edgeI] = bpef[curEdge][0];
+        edges_[edgei] = bp.edges()[edgei];
+
+        edgeOwner_[edgei] = edgeFaces[edgei][0];
 
-        edgeNeighbour_[edgeI] = bpef[curEdge][1];
+        edgeNeighbour_[edgei] = edgeFaces[edgei][1];
     }
 
-    forAll(boundary(), patchI)
-    {
-        const labelList& curP = boundary()[patchI];
+    // Continue with boundary edges
+    label edgei = nInternalEdges_;
 
-        forAll(curP, eI)
+    for (const faPatch& p : boundary())
+    {
+        for (const label patchEdgei : p.edgeLabels())
         {
-            edgeOwner_[++edgeI] = bpef[curP[eI]][0];
-        }
-    }
+            edges_[edgei] = bp.edges()[patchEdgei];
 
-    // Set number of faces
-    nFaces_ = bp.size();
+            edgeOwner_[edgei] = edgeFaces[patchEdgei][0];
 
-    // Set number of points
-    nPoints_ = bp.nPoints();
+            ++edgei;
+        }
+    }
 }
 
 
@@ -161,15 +208,20 @@ void Foam::faMesh::clearOut() const
 {
     clearGeom();
     clearAddressing();
-    deleteDemandDrivenData(globalMeshDataPtr_);
+    globalMeshDataPtr_.reset(nullptr);
 }
 
 
 // * * * * * * * * * * * * * * * * Constructors  * * * * * * * * * * * * * * //
 
+Foam::faMesh::faMesh(const polyMesh& pMesh, const zero)
+:
+    faMesh(pMesh, labelList())
+{}
+
+
 Foam::faMesh::faMesh(const polyMesh& pMesh)
 :
-    GeoMesh<polyMesh>(pMesh),
     MeshObject<polyMesh, Foam::UpdateableMeshObject, faMesh>(pMesh),
     edgeInterpolation(*this),
     faSchemes(mesh()),
@@ -181,7 +233,7 @@ Foam::faMesh::faMesh(const polyMesh& pMesh)
         (
             "faceLabels",
             time().findInstance(meshDir(), "faceLabels"),
-            meshSubDir,
+            faMesh::meshSubDir,
             mesh(),
             IOobject::MUST_READ,
             IOobject::NO_WRITE
@@ -193,7 +245,7 @@ Foam::faMesh::faMesh(const polyMesh& pMesh)
         (
             "faBoundary",
             time().findInstance(meshDir(), "faBoundary"),
-            meshSubDir,
+            faMesh::meshSubDir,
             mesh(),
             IOobject::MUST_READ,
             IOobject::NO_WRITE
@@ -220,7 +272,7 @@ Foam::faMesh::faMesh(const polyMesh& pMesh)
     correctPatchPointNormalsPtr_(nullptr),
     globalMeshDataPtr_(nullptr)
 {
-    DebugInFunction << "Creating faMesh from IOobject" << endl;
+    DebugInFunction << "Creating from IOobject" << endl;
 
     setPrimitiveMeshData();
 
@@ -244,7 +296,7 @@ Foam::faMesh::faMesh(const polyMesh& pMesh)
             (
                 "S0",
                 time().timeName(),
-                meshSubDir,
+                faMesh::meshSubDir,
                 mesh(),
                 IOobject::MUST_READ,
                 IOobject::AUTO_WRITE
@@ -258,10 +310,9 @@ Foam::faMesh::faMesh(const polyMesh& pMesh)
 Foam::faMesh::faMesh
 (
     const polyMesh& pMesh,
-    const labelList& faceLabels
+    const UList<label>& faceLabels
 )
 :
-    GeoMesh<polyMesh>(pMesh),
     MeshObject<polyMesh, Foam::UpdateableMeshObject, faMesh>(pMesh),
     edgeInterpolation(*this),
     faSchemes(mesh()),
@@ -273,7 +324,7 @@ Foam::faMesh::faMesh
         (
             "faceLabels",
             mesh().facesInstance(),
-            meshSubDir,
+            faMesh::meshSubDir,
             mesh(),
             IOobject::NO_READ,
             IOobject::NO_WRITE
@@ -286,13 +337,13 @@ Foam::faMesh::faMesh
         (
             "faBoundary",
             mesh().facesInstance(),
-            meshSubDir,
+            faMesh::meshSubDir,
             mesh(),
             IOobject::NO_READ,
             IOobject::NO_WRITE
         ),
         *this,
-        0
+        label(0)
     ),
     comm_(Pstream::worldComm),
     patchPtr_(nullptr),
@@ -313,434 +364,77 @@ Foam::faMesh::faMesh
     edgeTransformTensorsPtr_(nullptr),
     correctPatchPointNormalsPtr_(nullptr),
     globalMeshDataPtr_(nullptr)
-{
-    DebugInFunction << "Creating faMesh from components" << endl;
-}
+{}
 
 
-Foam::faMesh::faMesh
-(
-    const polyMesh& pMesh,
-    const fileName& defFile
-)
+Foam::faMesh::faMesh(const polyPatch& pp)
 :
-    GeoMesh<polyMesh>(pMesh),
-    MeshObject<polyMesh, Foam::UpdateableMeshObject, faMesh>(pMesh),
-    edgeInterpolation(*this),
-    faSchemes(mesh()),
-    faSolution(mesh()),
-    data(mesh()),
-    faceLabels_
-    (
-        IOobject
-        (
-            "faceLabels",
-            mesh().facesInstance(),
-            meshSubDir,
-            mesh(),
-            IOobject::NO_READ,
-            IOobject::NO_WRITE
-        ),
-        List<label>(0)
-    ),
-    boundary_
+    faMesh
     (
-        IOobject
-        (
-            "faBoundary",
-            mesh().facesInstance(),
-            meshSubDir,
-            mesh(),
-            IOobject::NO_READ,
-            IOobject::NO_WRITE
-        ),
-        *this,
-        0
-    ),
-    comm_(Pstream::worldComm),
-    patchPtr_(nullptr),
-    lduPtr_(nullptr),
-    curTimeIndex_(time().timeIndex()),
-    SPtr_(nullptr),
-    S0Ptr_(nullptr),
-    S00Ptr_(nullptr),
-    patchStartsPtr_(nullptr),
-    LePtr_(nullptr),
-    magLePtr_(nullptr),
-    centresPtr_(nullptr),
-    edgeCentresPtr_(nullptr),
-    faceAreaNormalsPtr_(nullptr),
-    edgeAreaNormalsPtr_(nullptr),
-    pointAreaNormalsPtr_(nullptr),
-    faceCurvaturesPtr_(nullptr),
-    edgeTransformTensorsPtr_(nullptr),
-    correctPatchPointNormalsPtr_(nullptr),
-    globalMeshDataPtr_(nullptr)
+        pp.boundaryMesh().mesh(),
+        identity(pp.range())
+    )
 {
-    DebugInFunction << "Creating faMesh from definition file" << endl;
-
-    // Reading faMeshDefinition dictionary
-    IOdictionary faMeshDefinition
-    (
-        IOobject
-        (
-            defFile,
-            mesh().time().constant(),
-            meshSubDir,
-            mesh(),
-            IOobject::MUST_READ,
-            IOobject::NO_WRITE
-        )
-    );
+    DebugInFunction << "Creating from polyPatch:" << pp.name() << endl;
 
-    const wordList polyMeshPatches
+    // Add single faPatch "default", but with processor connections
+    PtrList<faPatch> newPatches
     (
-        faMeshDefinition.get<wordList>("polyMeshPatches")
+        createOnePatch("default")
     );
 
-    const dictionary& bndDict = faMeshDefinition.subDict("boundary");
-
-    const wordList faPatchNames(bndDict.toc());
-
-    List<faPatchData> faPatches(faPatchNames.size() + 1);
-
-    const polyBoundaryMesh& pbm = pMesh.boundaryMesh();
-
-    forAll(faPatchNames, patchI)
-    {
-        dictionary curPatchDict = bndDict.subDict(faPatchNames[patchI]);
-
-        faPatches[patchI].name_ = faPatchNames[patchI];
-
-        faPatches[patchI].type_ = curPatchDict.get<word>("type");
-
-        faPatches[patchI].ownPolyPatchID_ =
-            pbm.findPatchID(curPatchDict.get<word>("ownerPolyPatch"));
-
-        faPatches[patchI].ngbPolyPatchID_ =
-            pbm.findPatchID(curPatchDict.get<word>("neighbourPolyPatch"));
-    }
-
-
-    // Setting faceLabels list size
-    label size = 0;
-
-    labelList patchIDs(polyMeshPatches.size(), -1);
-
-    forAll(polyMeshPatches, patchI)
-    {
-        patchIDs[patchI] = pbm.findPatchID(polyMeshPatches[patchI]);
+    addFaPatches(newPatches);
 
-        size += pbm[patchIDs[patchI]].size();
-    }
-
-    faceLabels_ = labelList(size, -1);
-
-
-    // Filling of faceLabels list
-    label faceI = -1;
-
-    sort(patchIDs);
+    setPrimitiveMeshData();
 
-    forAll(polyMeshPatches, patchI)
+    // Create global mesh data
+    if (Pstream::parRun())
     {
-        label start = pbm[patchIDs[patchI]].start();
-        label size  = pbm[patchIDs[patchI]].size();
-
-        for (label i = 0; i < size; ++i)
-        {
-            faceLabels_[++faceI] = start + i;
-        }
+        globalData();
     }
 
+    // Calculate topology for the patches (processor-processor comms etc.)
+    boundary_.updateMesh();
 
-    // Determination of faPatch ID for each boundary edge.
-    // Result is in the bndEdgeFaPatchIDs list
-    labelList faceCells(faceLabels_.size(), -1);
-
-    forAll(faceCells, faceI)
-    {
-        label faceID = faceLabels_[faceI];
+    // Calculate the geometry for the patches (transformation tensors etc.)
+    boundary_.calcGeometry();
+}
 
-        faceCells[faceI] = mesh().faceOwner()[faceID];
-    }
 
-    labelList meshEdges =
-        patch().meshEdges
+Foam::faMesh::faMesh
+(
+    const polyMesh& pMesh,
+    const dictionary& faMeshDefinition
+)
+:
+    faMesh
+    (
+        pMesh,
+        selectPatchFaces
         (
-            mesh().edges(),
-            mesh().cellEdges(),
-            faceCells
-        );
-
-    const labelListList& edgeFaces = mesh().edgeFaces();
-
-    const label nTotalEdges = patch().nEdges();
-    const label nInternalEdges = patch().nInternalEdges();
-
-    labelList bndEdgeFaPatchIDs(nTotalEdges - nInternalEdges, -1);
-
-    for (label edgeI = nInternalEdges; edgeI < nTotalEdges; ++edgeI)
-    {
-        label curMeshEdge = meshEdges[edgeI];
-
-        labelList curEdgePatchIDs(2, label(-1));
-
-        label patchI = -1;
-
-        forAll(edgeFaces[curMeshEdge], faceI)
-        {
-            label curFace = edgeFaces[curMeshEdge][faceI];
-
-            label curPatchID = pbm.whichPatch(curFace);
-
-            if (curPatchID != -1)
-            {
-                curEdgePatchIDs[++patchI] = curPatchID;
-            }
-        }
-
-        for (label pI = 0; pI < faPatches.size() - 1; ++pI)
-        {
-            if
-            (
-                (
-                    curEdgePatchIDs[0] == faPatches[pI].ownPolyPatchID_
-                 && curEdgePatchIDs[1] == faPatches[pI].ngbPolyPatchID_
-                )
-             ||
-                (
-                    curEdgePatchIDs[1] == faPatches[pI].ownPolyPatchID_
-                 && curEdgePatchIDs[0] == faPatches[pI].ngbPolyPatchID_
-                )
-            )
-            {
-                bndEdgeFaPatchIDs[edgeI - nInternalEdges] = pI;
-                break;
-            }
-        }
-    }
-
-
-    // Set edgeLabels for each faPatch
-    for (label pI = 0; pI < (faPatches.size() - 1); ++pI)
-    {
-        SLList<label> tmpList;
-
-        forAll(bndEdgeFaPatchIDs, eI)
-        {
-            if (bndEdgeFaPatchIDs[eI] == pI)
-            {
-                tmpList.append(nInternalEdges + eI);
-            }
-        }
-
-        faPatches[pI].edgeLabels_ = tmpList;
-    }
-
-    // Check for undefined edges
-    SLList<label> tmpList;
-
-    forAll(bndEdgeFaPatchIDs, eI)
-    {
-        if (bndEdgeFaPatchIDs[eI] == -1)
-        {
-            tmpList.append(nInternalEdges + eI);
-        }
-    }
-
-    if (tmpList.size() > 0)
-    {
-        // Check for processor edges
-        labelList allUndefEdges(tmpList);
-        labelList ngbPolyPatch(allUndefEdges.size(), -1);
-        forAll(ngbPolyPatch, edgeI)
-        {
-            label curEdge = allUndefEdges[edgeI];
-
-            label curPMeshEdge = meshEdges[curEdge];
-
-            forAll(edgeFaces[curPMeshEdge], faceI)
-            {
-                label curFace = edgeFaces[curPMeshEdge][faceI];
-
-                if (faceLabels_.found(curFace))
-                {
-                    label polyPatchID = pbm.whichPatch(curFace);
-
-                    if (polyPatchID != -1)
-                    {
-                        ngbPolyPatch[edgeI] = polyPatchID;
-                    }
-                }
-            }
-        }
-
-        // Count ngb processorPolyPatch-es
-        labelHashSet processorPatchSet;
-        forAll(ngbPolyPatch, edgeI)
-        {
-            if (ngbPolyPatch[edgeI] != -1)
-            {
-                if (isA<processorPolyPatch>(pbm[ngbPolyPatch[edgeI]]))
-                {
-                    processorPatchSet.insert(ngbPolyPatch[edgeI]);
-                }
-            }
-        }
-        labelList processorPatches(processorPatchSet.toc());
-        faPatches.setSize(faPatches.size() + processorPatches.size());
-
-        for (label i = 0; i < processorPatches.size(); ++i)
-        {
-            SLList<label> tmpLst;
-
-            forAll(ngbPolyPatch, eI)
-            {
-                if (ngbPolyPatch[eI] == processorPatches[i])
-                {
-                    tmpLst.append(allUndefEdges[eI]);
-                }
-            }
-
-            faPatches[faPatchNames.size() + i].edgeLabels_ = tmpLst;
-
-            faPatches[faPatchNames.size() + i].name_ =
-                pbm[processorPatches[i]].name();
-
-            faPatches[faPatchNames.size() + i].type_ =
-                processorFaPatch::typeName;
-
-            faPatches[faPatchNames.size() + i].ngbPolyPatchID_ =
-                processorPatches[i];
-        }
-
-        // Remaining undefined edges
-        SLList<label> undefEdges;
-        forAll(ngbPolyPatch, eI)
-        {
-            if (ngbPolyPatch[eI] == -1)
-            {
-                undefEdges.append(allUndefEdges[eI]);
-            }
-            else if (!isA<processorPolyPatch>(pbm[ngbPolyPatch[eI]]))
-            {
-                undefEdges.append(allUndefEdges[eI]);
-            }
-        }
-
-        if (undefEdges.size() > 0)
-        {
-            label pI = faPatches.size()-1;
-
-            faPatches[pI].name_ = "undefined";
-            faPatches[pI].type_ = "patch";
-            faPatches[pI].edgeLabels_ = undefEdges;
-        }
-        else
-        {
-            faPatches.setSize(faPatches.size() - 1);
-        }
-    }
-    else
-    {
-        faPatches.setSize(faPatches.size() - 1);
-    }
-
-
-    // Reorder processorFaPatch using
-    // ordering of ngb processorPolyPatch
-    forAll(faPatches, patchI)
-    {
-        if (faPatches[patchI].type_ == processorFaPatch::typeName)
-        {
-            labelList ngbFaces(faPatches[patchI].edgeLabels_.size(), -1);
-
-            forAll(ngbFaces, edgeI)
-            {
-                label curEdge = faPatches[patchI].edgeLabels_[edgeI];
-
-                label curPMeshEdge = meshEdges[curEdge];
-
-                forAll(edgeFaces[curPMeshEdge], faceI)
-                {
-                    label curFace = edgeFaces[curPMeshEdge][faceI];
-
-                    label curPatchID = pbm.whichPatch(curFace);
-
-                    if (curPatchID == faPatches[patchI].ngbPolyPatchID_)
-                    {
-                        ngbFaces[edgeI] = curFace;
-                    }
-                }
-            }
-
-            SortableList<label> sortedNgbFaces(ngbFaces);
-            labelList reorderedEdgeLabels(ngbFaces.size(), -1);
-            for (label i = 0; i < reorderedEdgeLabels.size(); ++i)
-            {
-                reorderedEdgeLabels[i] =
-                    faPatches[patchI].edgeLabels_
-                    [
-                        sortedNgbFaces.indices()[i]
-                    ];
-            }
-
-            faPatches[patchI].edgeLabels_ = reorderedEdgeLabels;
-        }
-    }
-
-
-    // Add good patches to faMesh
-    SLList<faPatch*> faPatchLst;
+            pMesh.boundaryMesh(),
+            faMeshDefinition.get<wordList>("polyMeshPatches")
+        )
+    )
+{
+    DebugInFunction << "Creating from definition (dictionary)" << endl;
 
-    for (label pI = 0; pI < faPatches.size(); ++pI)
-    {
-        faPatches[pI].dict_.add("type", faPatches[pI].type_);
-        faPatches[pI].dict_.add("edgeLabels", faPatches[pI].edgeLabels_);
-        faPatches[pI].dict_.add
+    PtrList<faPatch> newPatches
+    (
+        createPatchList
         (
-            "ngbPolyPatchIndex",
-            faPatches[pI].ngbPolyPatchID_
-        );
+            faMeshDefinition.subDict("boundary"),
 
-        if (faPatches[pI].type_ == processorFaPatch::typeName)
-        {
-            if (faPatches[pI].ngbPolyPatchID_ == -1)
-            {
-                FatalErrorInFunction
-                    << "ngbPolyPatch is not defined for processorFaPatch: "
-                    << faPatches[pI].name_
-                    << abort(FatalError);
-            }
-
-            const processorPolyPatch& procPolyPatch =
-                refCast<const processorPolyPatch>
-                (
-                    pbm[faPatches[pI].ngbPolyPatchID_]
-                );
+            // Optional 'empty' patch
+            faMeshDefinition.getOrDefault<word>("emptyPatch", word::null),
 
-            faPatches[pI].dict_.add("myProcNo", procPolyPatch.myProcNo());
-            faPatches[pI].dict_.add
-            (
-                "neighbProcNo",
-                procPolyPatch.neighbProcNo()
-            );
-        }
+            // Optional specification for default patch
+            faMeshDefinition.findDict("defaultPatch")
+        )
+    );
 
-        faPatchLst.append
-        (
-            faPatch::New
-            (
-                faPatches[pI].name_,
-                faPatches[pI].dict_,
-                pI,
-                boundary()
-            ).ptr()
-        );
-    }
 
-    addFaPatches(List<faPatch*>(faPatchLst));
+    addFaPatches(newPatches);
 
     // Create global mesh data
     if (Pstream::parRun())
@@ -762,7 +456,7 @@ Foam::faMesh::faMesh
             (
                 "S0",
                 time().timeName(),
-                meshSubDir,
+                faMesh::meshSubDir,
                 mesh(),
                 IOobject::MUST_READ,
                 IOobject::AUTO_WRITE
@@ -773,111 +467,6 @@ Foam::faMesh::faMesh
 }
 
 
-Foam::faMesh::faMesh
-(
-    const polyMesh& pMesh,
-    const label polyPatchID
-)
-:
-    GeoMesh<polyMesh>(pMesh),
-    MeshObject<polyMesh, Foam::UpdateableMeshObject, faMesh>(pMesh),
-    edgeInterpolation(*this),
-    faSchemes(mesh()),
-    faSolution(mesh()),
-    data(mesh()),
-    faceLabels_
-    (
-        IOobject
-        (
-            "faceLabels",
-            mesh().facesInstance(),
-            meshSubDir,
-            mesh(),
-            IOobject::NO_READ,
-            IOobject::NO_WRITE
-        ),
-        labelList(pMesh.boundaryMesh()[polyPatchID].size(), -1)
-    ),
-    boundary_
-    (
-        IOobject
-        (
-            "faBoundary",
-            mesh().facesInstance(),
-            meshSubDir,
-            mesh(),
-            IOobject::NO_READ,
-            IOobject::NO_WRITE
-        ),
-        *this,
-        0
-    ),
-    comm_(Pstream::worldComm),
-    patchPtr_(nullptr),
-    lduPtr_(nullptr),
-    curTimeIndex_(time().timeIndex()),
-    SPtr_(nullptr),
-    S0Ptr_(nullptr),
-    S00Ptr_(nullptr),
-    patchStartsPtr_(nullptr),
-    LePtr_(nullptr),
-    magLePtr_(nullptr),
-    centresPtr_(nullptr),
-    edgeCentresPtr_(nullptr),
-    faceAreaNormalsPtr_(nullptr),
-    edgeAreaNormalsPtr_(nullptr),
-    pointAreaNormalsPtr_(nullptr),
-    faceCurvaturesPtr_(nullptr),
-    edgeTransformTensorsPtr_(nullptr),
-    correctPatchPointNormalsPtr_(nullptr),
-    globalMeshDataPtr_(nullptr)
-{
-    DebugInFunction << "Creating faMesh from polyPatch" << endl;
-
-    const polyBoundaryMesh& pbm = pMesh.boundaryMesh();
-
-    // Set faceLabels
-    forAll(faceLabels_, faceI)
-    {
-        faceLabels_[faceI] = pbm[polyPatchID].start() + faceI;
-    }
-
-    // Add one faPatch
-    const indirectPrimitivePatch& bp = patch();
-
-    const label nTotalEdges = bp.nEdges();
-
-    const label nInternalEdges = bp.nInternalEdges();
-
-    labelList edgeLabels(nTotalEdges - nInternalEdges, -1);
-
-    forAll(edgeLabels, edgeI)
-    {
-        edgeLabels[edgeI] = nInternalEdges + edgeI;
-    }
-
-    dictionary patchDict;
-
-    patchDict.add("type", "patch");
-    patchDict.add("edgeLabels", edgeLabels);
-    patchDict.add("ngbPolyPatchIndex", -1);
-
-    List<faPatch*> faPatchLst(1);
-
-    faPatchLst[0] = faPatch::New("default", patchDict, 0, boundary()).ptr();
-
-    addFaPatches(faPatchLst);
-
-    setPrimitiveMeshData();
-
-    // Calculate topology for the patches (processor-processor comms etc.)
-    boundary_.updateMesh();
-
-    // Calculate the geometry for the patches (transformation tensors etc.)
-    boundary_.calcGeometry();
-}
-
-
 // * * * * * * * * * * * * * * * * Destructor  * * * * * * * * * * * * * * * //
 
 Foam::faMesh::~faMesh()
@@ -890,7 +479,7 @@ Foam::faMesh::~faMesh()
 
 Foam::fileName Foam::faMesh::meshDir() const
 {
-    return mesh().dbDir()/meshSubDir;
+    return mesh().dbDir()/faMesh::meshSubDir;
 }
 
 
@@ -912,98 +501,6 @@ const Foam::fileName& Foam::faMesh::facesInstance() const
 }
 
 
-const Foam::indirectPrimitivePatch& Foam::faMesh::patch() const
-{
-    if (!patchPtr_)
-    {
-        patchPtr_ = new indirectPrimitivePatch
-        (
-            IndirectList<face>
-            (
-                mesh().faces(),
-                faceLabels_
-            ),
-            mesh().points()
-        );
-    }
-
-    return *patchPtr_;
-}
-
-
-Foam::indirectPrimitivePatch& Foam::faMesh::patch()
-{
-    if (!patchPtr_)
-    {
-        patchPtr_ = new indirectPrimitivePatch
-        (
-            IndirectList<face>
-            (
-                mesh().faces(),
-                faceLabels_
-            ),
-            mesh().points()
-        );
-    }
-
-    return *patchPtr_;
-}
-
-
-const Foam::pointField& Foam::faMesh::points() const
-{
-    return patch().localPoints();
-}
-
-
-const Foam::edgeList& Foam::faMesh::edges() const
-{
-    return edges_;
-}
-
-
-const Foam::faceList& Foam::faMesh::faces() const
-{
-    return patch().localFaces();
-}
-
-
-void Foam::faMesh::addFaPatches(const List<faPatch*>& p)
-{
-    DebugInFunction << "Adding patches to faMesh" << endl;
-
-    if (boundary().size() > 0)
-    {
-        FatalErrorInFunction
-            << "boundary already exists"
-            << abort(FatalError);
-    }
-
-    boundary_.setSize(p.size());
-
-    forAll(p, patchI)
-    {
-        boundary_.set(patchI, p[patchI]);
-    }
-
-    setPrimitiveMeshData();
-
-    boundary_.checkDefinition();
-}
-
-
-Foam::label Foam::faMesh::comm() const
-{
-    return comm_;
-}
-
-
-Foam::label& Foam::faMesh::comm()
-{
-    return comm_;
-}
-
-
 bool Foam::faMesh::hasDb() const
 {
     return true;
@@ -1016,12 +513,6 @@ const Foam::objectRegistry& Foam::faMesh::thisDb() const
 }
 
 
-const Foam::faBoundaryMesh& Foam::faMesh::boundary() const
-{
-    return boundary_;
-}
-
-
 const Foam::labelList& Foam::faMesh::patchStarts() const
 {
     if (!patchStartsPtr_)
@@ -1193,7 +684,7 @@ const Foam::faGlobalMeshData& Foam::faMesh::globalData() const
 {
     if (!globalMeshDataPtr_)
     {
-        globalMeshDataPtr_ = new faGlobalMeshData(*this);
+        globalMeshDataPtr_.reset(new faGlobalMeshData(*this));
     }
 
     return *globalMeshDataPtr_;
diff --git a/src/finiteArea/faMesh/faMesh.H b/src/finiteArea/faMesh/faMesh.H
index 8715a22f010..b6515b510fa 100644
--- a/src/finiteArea/faMesh/faMesh.H
+++ b/src/finiteArea/faMesh/faMesh.H
@@ -6,6 +6,7 @@
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
     Copyright (C) 2016-2017 Wikki Ltd
+    Copyright (C) 2021 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -42,7 +43,6 @@ Author
 #ifndef faMesh_H
 #define faMesh_H
 
-#include "GeoMesh.H"
 #include "MeshObject.H"
 #include "polyMesh.H"
 #include "lduMesh.H"
@@ -53,7 +53,7 @@ Author
 #include "DimensionedField.H"
 #include "areaFieldsFwd.H"
 #include "edgeFieldsFwd.H"
-#include "indirectPrimitivePatch.H"
+#include "uindirectPrimitivePatch.H"
 #include "edgeInterpolation.H"
 #include "labelIOList.H"
 #include "FieldFields.H"
@@ -67,9 +67,10 @@ Author
 namespace Foam
 {
 
-// Class forward declarations
+// Forward Declarations
 class faMeshLduAddressing;
 class faMeshMapper;
+class faPatchData;
 
 /*---------------------------------------------------------------------------*\
                            Class faMesh Declaration
@@ -77,7 +78,6 @@ class faMeshMapper;
 
 class faMesh
 :
-    public GeoMesh<polyMesh>,
     public MeshObject<polyMesh, Foam::UpdateableMeshObject, faMesh>,
     public lduMesh,
     public edgeInterpolation,
@@ -85,7 +85,7 @@ class faMesh
     public faSolution,
     public data
 {
-    // Private data
+    // Private Data
 
         //- Face labels
         labelIOList faceLabels_;
@@ -130,7 +130,7 @@ class faMesh
     // Demand-driven data
 
         //- Primitive patch
-        mutable indirectPrimitivePatch* patchPtr_;
+        mutable uindirectPrimitivePatch* patchPtr_;
 
         //- Ldu addressing data
         mutable faMeshLduAddressing* lduPtr_;
@@ -186,8 +186,8 @@ class faMesh
 
         // Other mesh-related data
 
-            //- Parallel info
-            mutable faGlobalMeshData* globalMeshDataPtr_;
+        //- Parallel info
+        mutable autoPtr<faGlobalMeshData> globalMeshDataPtr_;
 
 
     // Static Private Data
@@ -204,6 +204,8 @@ class faMesh
         //- No copy assignment
         void operator=(const faMesh&) = delete;
 
+        //- Set indirect patch, removing any old one
+        void initPatch() const;
 
         //- Set primitive mesh data
         void setPrimitiveMeshData();
@@ -262,9 +264,36 @@ class faMesh
             //- Clear demand-driven data
             void clearOut() const;
 
+
+    // Helpers
+
+        //- Create a single patch
+        PtrList<faPatch> createOnePatch
+        (
+            const word& patchName,
+            const word& patchType = ""
+        ) const;
+
+        //- Create list of patches from boundary definition
+        PtrList<faPatch> createPatchList
+        (
+            const dictionary& bndDict,
+            const word& emptyPatchName = "",
+            const dictionary* defaultPatchDefinition = nullptr
+        ) const;
+
+        //- Reorder processor edges using order of the
+        //- neighbour processorPolyPatch
+        void reorderProcEdges
+        (
+            faPatchData& patchDef,
+            const labelUList& meshEdges
+        ) const;
+
+
 public:
 
-    // Public typedefs
+    // Public Typedefs
 
         typedef faMesh Mesh;
         typedef faBoundaryMesh BoundaryMesh;
@@ -273,36 +302,38 @@ public:
     //- Runtime type information
     TypeName("faMesh");
 
+        //- The prefix to local: %finite-area
+        static const word prefix;
 
-    //- Return the mesh sub-directory name (usually "faMesh")
-    static word meshSubDir;
+        //- The mesh sub-directory name (usually "faMesh")
+        static word meshSubDir;
 
 
     // Constructors
 
+        //- Construct zero-sized from polyMesh
+        //  Boundary is added using addFaPatches() member function
+        faMesh(const polyMesh& pMesh, const Foam::zero);
+
         //- Construct from polyMesh
-        explicit faMesh(const polyMesh& m);
+        explicit faMesh(const polyMesh& pMesh);
 
-        //- Construct from components without boundary.
+        //- Construct for specified face labels without boundary.
         //  Boundary is added using addFaPatches() member function
         faMesh
         (
-            const polyMesh& m,
-            const labelList& faceLabels
+            const polyMesh& pMesh,
+            const UList<label>& faceLabels
         );
 
-        //- Construct from finite area mesh definition file
-        faMesh
-        (
-            const polyMesh& m,
-            const fileName& defFile
-        );
+        //- Construct from single polyPatch
+        explicit faMesh(const polyPatch& pp);
 
-        //- Construct from polyPatch
+        //- Construct from definition
         faMesh
         (
-            const polyMesh& m,
-            const label polyPatchID
+            const polyMesh& pMesh,
+            const dictionary& faMeshDefinition
         );
 
 
@@ -312,25 +343,30 @@ public:
 
     // Member Functions
 
-        // Helpers
+    // Helpers
 
-            //- Add boundary patches. Constructor helper
-            void addFaPatches(const List<faPatch*> &);
+        //- Add boundary patches. Constructor helper
+        void addFaPatches
+        (
+            PtrList<faPatch>& plist,
+            const bool validBoundary = true
+        );
+
+        //- Add boundary patches. Constructor helper
+        void addFaPatches
+        (
+            const List<faPatch*>& p,
+            const bool validBoundary = true
+        );
 
 
         // Database
 
             //- Return access to polyMesh
-            const polyMesh& mesh() const
-            {
-                return
-                    MeshObject
-                    <
-                        polyMesh,
-                        Foam::UpdateableMeshObject,
-                        faMesh
-                    >::mesh();
-            }
+            inline const polyMesh& mesh() const;
+
+            //- Interface to referenced polyMesh (similar to GeoMesh)
+            const polyMesh& operator()() const { return mesh(); }
 
             //- Return the local mesh directory (dbDir()/meshSubDir)
             fileName meshDir() const;
@@ -347,60 +383,50 @@ public:
             const fileName& facesInstance() const;
 
 
+        // Communication support
+
+            //- Return communicator used for parallel communication
+            inline label comm() const noexcept;
+
+            //- Return communicator used for parallel communication
+            inline label& comm() noexcept;
+
+
             // Mesh size parameters
 
-                inline label nPoints() const
-                {
-                    return nPoints_;
-                }
+                //- Number of local mesh points
+                inline label nPoints() const noexcept;
 
-                inline label nEdges() const
-                {
-                    return nEdges_;
-                }
+                //- Number of local mesh edges
+                inline label nEdges() const noexcept;
 
-                inline label nInternalEdges() const
-                {
-                    return nInternalEdges_;
-                }
+                //- Number of internal faces
+                inline label nInternalEdges() const noexcept;
 
-                inline label nFaces() const
-                {
-                    return nFaces_;
-                }
+                //- Number of boundary edges (== nEdges - nInternalEdges)
+                inline label nBoundaryEdges() const noexcept;
+
+                //- Number of patch faces
+                inline label nFaces() const noexcept;
 
 
             // Primitive mesh data
 
-                //- Return mesh points
-                const pointField& points() const;
+                //- Return local patch points
+                inline const pointField& points() const;
 
-                //- Return edges
-                const edgeList& edges() const;
+                //- Return local patch edges with reordered boundary
+                inline const edgeList& edges() const noexcept;
 
-                //- Return faces
-                const faceList& faces() const;
+                //- Return local patch faces
+                inline const faceList& faces() const;
 
                 //- Edge owner addressing
-                inline const labelList& edgeOwner() const
-                {
-                    return edgeOwner_;
-                }
+                inline const labelList& edgeOwner() const noexcept;
 
                 //- Edge neighbour addressing
-                inline const labelList& edgeNeighbour() const
-                {
-                    return edgeNeighbour_;
-                }
-
-
-            // Communication support
-
-                //- Return communicator used for parallel communication
-                label comm() const;
+                inline const labelList& edgeNeighbour() const noexcept;
 
-                //- Return communicator used for parallel communication
-                label& comm();
 
 
         // Access
@@ -419,13 +445,10 @@ public:
             }
 
             //- Return constant reference to boundary mesh
-            const faBoundaryMesh& boundary() const;
+            inline const faBoundaryMesh& boundary() const noexcept;
 
             //- Return faMesh face labels
-            const labelList& faceLabels() const
-            {
-                return faceLabels_;
-            }
+            inline const labelList& faceLabels() const noexcept;
 
 
             //- Return parallel info
@@ -453,10 +476,10 @@ public:
                 return lduAddr().upperAddr();
             }
 
-            //- Return true if given edge label is internal to the mesh
-            inline bool isInternalEdge(const label edgeIndex) const
+            //- True if given edge label is internal to the mesh
+            bool isInternalEdge(const label edgeIndex) const
             {
-                return edgeIndex < nInternalEdges();
+                return (edgeIndex < nInternalEdges_);
             }
 
 
@@ -487,10 +510,10 @@ public:
         // Demand-driven data
 
             //- Return constant reference to primitive patch
-            const indirectPrimitivePatch& patch() const;
+            inline const uindirectPrimitivePatch& patch() const;
 
             //- Return reference to primitive patch
-            indirectPrimitivePatch& patch();
+            inline uindirectPrimitivePatch& patch();
 
             //- Return patch starts
             const labelList& patchStarts() const;
@@ -546,6 +569,7 @@ public:
             //- Set whether point normals should be corrected for a patch
             boolList& correctPatchPointNormals() const;
 
+
         //- Write mesh
         virtual bool write(const bool valid = true) const;
 
@@ -564,6 +588,8 @@ public:
 
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
 
+#include "faMeshI.H"
+
 #ifdef NoRepository
     #include "faPatchFaMeshTemplates.C"
 #endif
diff --git a/src/finiteArea/faMesh/faMeshDemandDrivenData.C b/src/finiteArea/faMesh/faMeshDemandDrivenData.C
index 7a363fd2ab6..6fbeb549bc2 100644
--- a/src/finiteArea/faMesh/faMeshDemandDrivenData.C
+++ b/src/finiteArea/faMesh/faMeshDemandDrivenData.C
@@ -6,7 +6,7 @@
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
     Copyright (C) 2016-2017 Wikki Ltd
-    Copyright (C) 2018-2020 OpenCFD Ltd.
+    Copyright (C) 2018-2021 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -40,6 +40,30 @@ License
 #include "processorFaPatchFields.H"
 #include "emptyFaPatchFields.H"
 
+
+// * * * * * * * * * * * * * * * Local Functions * * * * * * * * * * * * * * //
+
+namespace Foam
+{
+
+// A bitSet (size patch nPoints()) with boundary points marked
+static Foam::bitSet markupBoundaryPoints(const uindirectPrimitivePatch& p)
+{
+    // Initially all unmarked
+    bitSet markPoints(p.nPoints());
+    for (const edge& e : p.boundaryEdges())
+    {
+        // Mark boundary points
+        markPoints.set(e.first());
+        markPoints.set(e.second());
+    }
+
+    return markPoints;
+}
+
+} // End namespace Foam
+
+
 // * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //
 
 void Foam::faMesh::calcLduAddressing() const
@@ -70,16 +94,7 @@ void Foam::faMesh::calcPatchStarts() const
             << abort(FatalError);
     }
 
-    patchStartsPtr_ = new labelList(boundary().size(), -1);
-    labelList& patchStarts = *patchStartsPtr_;
-
-    patchStarts[0] = nInternalEdges();
-
-    for (label i = 1; i < boundary().size(); ++i)
-    {
-        patchStarts[i] =
-            patchStarts[i - 1] + boundary()[i - 1].faPatch::size();
-    }
+    patchStartsPtr_ = new labelList(boundary().patchStarts());
 }
 
 
@@ -211,13 +226,11 @@ void Foam::faMesh::calcMagLe() const
 
     const pointField& localPoints = points();
 
-    const edgeList::subList internalEdges =
-        edgeList::subList(edges(), nInternalEdges());
-
-
-    forAll(internalEdges, edgeI)
+    label edgei = 0;
+    for (const edge& e : patch().internalEdges())
     {
-        magLe.ref()[edgeI] = internalEdges[edgeI].mag(localPoints);
+        magLe.ref()[edgei] = e.mag(localPoints);
+        ++edgei;
     }
 
 
@@ -331,13 +344,12 @@ void Foam::faMesh::calcEdgeCentres() const
 
     const pointField& localPoints = points();
 
-    const edgeList::subList internalEdges =
-        edgeList::subList(edges(), nInternalEdges());
-
 
-    forAll(internalEdges, edgeI)
+    label edgei = 0;
+    for (const edge& e : patch().internalEdges())
     {
-        edgeCentres.ref()[edgeI] = internalEdges[edgeI].centre(localPoints);
+        edgeCentres.ref()[edgei] = e.centre(localPoints);
+        ++edgei;
     }
 
 
@@ -850,31 +862,10 @@ Foam::labelList Foam::faMesh::internalPoints() const
     DebugInFunction
         << "Calculating internal points" << endl;
 
-    const edgeList& edges = patch().edges();
-    label nIntEdges = patch().nInternalEdges();
-
-    List<bool> internal(nPoints(), true);
-
-    for (label curEdge = nIntEdges; curEdge < edges.size(); ++curEdge)
-    {
-        internal[edges[curEdge].start()] = false;
+    bitSet markPoints(markupBoundaryPoints(this->patch()));
+    markPoints.flip();
 
-        internal[edges[curEdge].end()] = false;
-    }
-
-    SLList<label> internalPoints;
-
-    forAll(internal, pointI)
-    {
-        if (internal[pointI])
-        {
-            internalPoints.append(pointI);
-        }
-    }
-
-    labelList result(internalPoints);
-
-    return result;
+    return markPoints.sortedToc();
 }
 
 
@@ -883,31 +874,9 @@ Foam::labelList Foam::faMesh::boundaryPoints() const
     DebugInFunction
         << "Calculating boundary points" << endl;
 
-    const edgeList& edges = patch().edges();
-    label nIntEdges = patch().nInternalEdges();
-
-    List<bool> internal(nPoints(), true);
-
-    for (label curEdge = nIntEdges; curEdge < edges.size(); ++curEdge)
-    {
-        internal[edges[curEdge].start()] = false;
-
-        internal[edges[curEdge].end()] = false;
-    }
-
-    SLList<label> boundaryPoints;
-
-    forAll(internal, pointI)
-    {
-        if (!internal[pointI])
-        {
-            boundaryPoints.append(pointI);
-        }
-    }
-
-    labelList result(boundaryPoints);
+    bitSet markPoints(markupBoundaryPoints(this->patch()));
 
-    return result;
+    return markPoints.sortedToc();
 }
 
 
diff --git a/src/finiteArea/faMesh/faMeshI.H b/src/finiteArea/faMesh/faMeshI.H
new file mode 100644
index 00000000000..a3d79328606
--- /dev/null
+++ b/src/finiteArea/faMesh/faMeshI.H
@@ -0,0 +1,142 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
+   \\    /   O peration     |
+    \\  /    A nd           | www.openfoam.com
+     \\/     M anipulation  |
+-------------------------------------------------------------------------------
+    Copyright (C) 2016-2017 Wikki Ltd
+    Copyright (C) 2021 OpenCFD Ltd.
+-------------------------------------------------------------------------------
+License
+    This file is part of OpenFOAM.
+
+    OpenFOAM is free software: you can redistribute it and/or modify it
+    under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    OpenFOAM is distributed in the hope that it will be useful, but WITHOUT
+    ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+    FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+    for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with OpenFOAM.  If not, see <http://www.gnu.org/licenses/>.
+
+\*---------------------------------------------------------------------------*/
+
+// * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //
+
+inline const Foam::polyMesh& Foam::faMesh::mesh() const
+{
+    return
+        MeshObject<polyMesh, Foam::UpdateableMeshObject, faMesh>::mesh();
+}
+
+
+inline const Foam::faBoundaryMesh& Foam::faMesh::boundary() const noexcept
+{
+    return boundary_;
+}
+
+
+inline Foam::label Foam::faMesh::comm() const noexcept
+{
+    return comm_;
+}
+
+
+inline Foam::label& Foam::faMesh::comm() noexcept
+{
+    return comm_;
+}
+
+
+inline Foam::label Foam::faMesh::nPoints() const noexcept
+{
+    return nPoints_;
+}
+
+
+inline Foam::label Foam::faMesh::nEdges() const noexcept
+{
+    return nEdges_;
+}
+
+
+inline Foam::label Foam::faMesh::nInternalEdges() const noexcept
+{
+    return nInternalEdges_;
+}
+
+
+inline Foam::label Foam::faMesh::nBoundaryEdges() const noexcept
+{
+    return nEdges_ - nInternalEdges_;
+}
+
+
+inline Foam::label Foam::faMesh::nFaces() const noexcept
+{
+    return nFaces_;
+}
+
+
+inline const Foam::pointField& Foam::faMesh::points() const
+{
+    return patch().localPoints();
+}
+
+
+inline const Foam::edgeList& Foam::faMesh::edges() const noexcept
+{
+    return edges_;
+}
+
+
+inline const Foam::faceList& Foam::faMesh::faces() const
+{
+    return patch().localFaces();
+}
+
+
+inline const Foam::labelList& Foam::faMesh::edgeOwner() const noexcept
+{
+    return edgeOwner_;
+}
+
+
+inline const Foam::labelList& Foam::faMesh::edgeNeighbour() const noexcept
+{
+    return edgeNeighbour_;
+}
+
+
+inline const Foam::labelList& Foam::faMesh::faceLabels() const noexcept
+{
+    return faceLabels_;
+}
+
+
+inline const Foam::uindirectPrimitivePatch& Foam::faMesh::patch() const
+{
+    if (!patchPtr_)
+    {
+        initPatch();
+    }
+    return *patchPtr_;
+}
+
+
+inline Foam::uindirectPrimitivePatch& Foam::faMesh::patch()
+{
+    if (!patchPtr_)
+    {
+        initPatch();
+    }
+    return *patchPtr_;
+}
+
+
+// ************************************************************************* //
diff --git a/src/finiteArea/faMesh/faMeshPatches.C b/src/finiteArea/faMesh/faMeshPatches.C
new file mode 100644
index 00000000000..765252c6706
--- /dev/null
+++ b/src/finiteArea/faMesh/faMeshPatches.C
@@ -0,0 +1,572 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
+   \\    /   O peration     |
+    \\  /    A nd           | www.openfoam.com
+     \\/     M anipulation  |
+-------------------------------------------------------------------------------
+    Copyright (C) 2017 Wikki Ltd
+    Copyright (C) 2021 OpenCFD Ltd.
+-------------------------------------------------------------------------------
+License
+    This file is part of OpenFOAM.
+
+    OpenFOAM is free software: you can redistribute it and/or modify it
+    under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    OpenFOAM is distributed in the hope that it will be useful, but WITHOUT
+    ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+    FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+    for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with OpenFOAM.  If not, see <http://www.gnu.org/licenses/>.
+
+\*---------------------------------------------------------------------------*/
+
+#include "faMesh.H"
+#include "IndirectList.H"
+#include "faPatchData.H"
+#include "processorPolyPatch.H"
+#include "processorFaPatch.H"
+#include "edgeHashes.H"
+
+// * * * * * * * * * * * * * Private Member Functions  * * * * * * * * * * * //
+
+void Foam::faMesh::reorderProcEdges
+(
+    faPatchData& patchDef,
+    const labelUList& meshEdges
+) const
+{
+    if (!patchDef.coupled() || patchDef.edgeLabels_.empty())
+    {
+        return;
+    }
+
+    const polyBoundaryMesh& pbm = mesh().boundaryMesh();
+    const labelListList& edgeFaces = mesh().edgeFaces();
+
+    // Reorder processor edges using order of neighbour processorPolyPatch
+
+    const label procPatchID = patchDef.neighPolyPatchId_;
+    const label nProcEdges = patchDef.edgeLabels_.size();
+
+    labelList procFaces(nProcEdges, -1);
+
+    forAll(procFaces, edgei)
+    {
+        const label localEdgei = patchDef.edgeLabels_[edgei];
+        const label meshEdgei = meshEdges[localEdgei];
+
+        for (const label meshFacei : edgeFaces[meshEdgei])
+        {
+            if
+            (
+                !faceLabels_.found(meshFacei)
+             && (procPatchID == pbm.whichPatch(meshFacei))
+            )
+            {
+                // The edge's proc-face
+                procFaces[edgei] = meshFacei;
+                break;
+            }
+        }
+    }
+
+    // Ascending proc-face numbering
+    const labelList sortIndices(Foam::sortedOrder(procFaces));
+
+    const labelList& oldEdgeLabels = patchDef.edgeLabels_;
+    labelList newEdgeLabels(oldEdgeLabels.size());
+
+    // Most of the time, an individual proc-face will only be singly
+    // attached to the finite-area patch. In rarer case, there could
+    // multiple connections. For these cases, need to walk the face
+    // edges - the direction depends on owner vs neighbour side.
+
+    EdgeMap<label> multihit;
+
+    for (label edgei = 0; edgei < nProcEdges; /*nil*/)
+    {
+        const label procFacei = procFaces[sortIndices[edgei]];
+
+        // Find all identical faces
+        label endEdgei = edgei + 1;  // one beyond
+        while
+        (
+            (endEdgei < nProcEdges)
+         && (procFacei == procFaces[sortIndices[endEdgei]])
+        )
+        {
+            ++endEdgei;
+        }
+
+        if (edgei + 1 == endEdgei)
+        {
+            // Simplest case - a single connection
+
+            newEdgeLabels[edgei] = oldEdgeLabels[sortIndices[edgei]];
+        }
+        else
+        {
+            multihit.clear();
+
+            // Map from global edge to local edgeId
+            for (label i = edgei; i < endEdgei; ++i)
+            {
+                label localEdgei = oldEdgeLabels[sortIndices[i]];
+                label meshEdgei = meshEdges[localEdgei];
+
+                multihit.insert
+                (
+                    mesh().edges()[meshEdgei],
+                    localEdgei
+                );
+            }
+
+            if (multihit.size() != (endEdgei - edgei))
+            {
+                FatalErrorInFunction
+                    << "Could only hash " << multihit.size()
+                    << " edges from " << (endEdgei - edgei)
+                    << " ... indicates a non-manifold connection" << nl
+                    << multihit << nl
+                    << abort(FatalError);
+            }
+
+            const face& f = mesh().faces()[procFacei];
+
+            forAll(f, fedgei)  // Note size() == nEdges()
+            {
+                edge e =
+                (
+                    patchDef.owner()
+                  ? f.edge(fedgei)      // Forward walk
+                  : f.rcEdge(fedgei)    // Reverse walk
+                );
+
+                auto iter = multihit.find(e);
+                if (iter.found())
+                {
+                    newEdgeLabels[edgei++] = iter.val();
+                    multihit.erase(iter);
+                    if (multihit.empty())
+                    {
+                        break;
+                    }
+                }
+            }
+
+            if (edgei != endEdgei)
+            {
+                FatalErrorInFunction
+                    << "Missed " << (edgei < endEdgei)
+                    << " edges for face: " << procFacei
+                    << " ... indicates serious geometry issue" << nl
+                    << multihit << nl
+                    << abort(FatalError);
+            }
+            if (!multihit.empty())
+            {
+                FatalErrorInFunction
+                    << "Missed edges for face: " << procFacei
+                    << " ... indicates serious geometry issue" << nl
+                    << multihit << nl
+                    << abort(FatalError);
+            }
+        }
+        edgei = endEdgei;
+    }
+
+    patchDef.edgeLabels_.transfer(newEdgeLabels);
+}
+
+
+// * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //
+
+void Foam::faMesh::addFaPatches
+(
+    PtrList<faPatch>& plist,
+    const bool validBoundary
+)
+{
+    if (!boundary().empty())
+    {
+        FatalErrorInFunction
+            << "boundary already exists"
+            << abort(FatalError);
+    }
+
+    globalMeshDataPtr_.reset(nullptr);
+
+    boundary_.transfer(plist);
+
+    setPrimitiveMeshData();
+
+    if (validBoundary)
+    {
+        boundary_.checkDefinition();
+    }
+}
+
+
+void Foam::faMesh::addFaPatches
+(
+    const List<faPatch*>& p,
+    const bool validBoundary
+)
+{
+    // Acquire ownership of the pointers
+    PtrList<faPatch> plist(const_cast<List<faPatch*>&>(p));
+
+    addFaPatches(plist, validBoundary);
+}
+
+
+Foam::PtrList<Foam::faPatch> Foam::faMesh::createOnePatch
+(
+    const word& patchName,
+    const word& patchType
+) const
+{
+    dictionary onePatchDict;
+    if (!patchName.empty())
+    {
+        onePatchDict.add("name", patchName);
+    }
+    if (!patchType.empty())
+    {
+        onePatchDict.add("type", patchType);
+    }
+
+    return createPatchList
+    (
+        dictionary::null,
+        "",             // Name for empty patch placeholder
+        &onePatchDict   // Definitions for defaultPatch
+    );
+}
+
+
+Foam::PtrList<Foam::faPatch> Foam::faMesh::createPatchList
+(
+    const dictionary& bndDict,
+    const word& emptyPatchName,
+    const dictionary* defaultPatchDefinition
+) const
+{
+    const polyBoundaryMesh& pbm = mesh().boundaryMesh();
+
+    // Transcribe into patch definitions
+    DynamicList<faPatchData> faPatchDefs(bndDict.size() + 4);
+    for (const entry& dEntry : bndDict)
+    {
+        if (!dEntry.isDict())
+        {
+            WarningInFunction
+                << "Not a dictionary entry: " << dEntry.name() << nl;
+            continue;
+        }
+        const dictionary& patchDict = dEntry.dict();
+
+        // Add entry
+        faPatchDefs.append(faPatchData());
+
+        auto& patchDef = faPatchDefs.last();
+        patchDef.name_ = dEntry.keyword();
+        patchDef.type_ = patchDict.get<word>("type");
+
+        const word ownName(patchDict.get<word>("ownerPolyPatch"));
+        const word neiName(patchDict.get<word>("neighbourPolyPatch"));
+
+        patchDef.ownerPolyPatchId_ = pbm.findPatchID(ownName);
+        patchDef.neighPolyPatchId_ = pbm.findPatchID(neiName);
+
+        if (patchDef.ownerPolyPatchId_ < 0)
+        {
+            FatalErrorInFunction
+                << "ownerPolyPatch " << ownName << " not found"
+                << exit(FatalError);
+        }
+        if (patchDef.neighPolyPatchId_ < 0)
+        {
+            FatalErrorInFunction
+                << "neighbourPolyPatch " << neiName << " not found"
+                << exit(FatalError);
+        }
+    }
+
+    // Additional empty placeholder patch?
+    if (!emptyPatchName.empty())
+    {
+        faPatchDefs.append(faPatchData());
+
+        auto& patchDef = faPatchDefs.last();
+        patchDef.name_ = emptyPatchName;
+        patchDef.type_ = "empty";
+    }
+
+    // Placeholder for any undefined edges
+    const label undefPatchId = faPatchDefs.size();
+    {
+        faPatchDefs.append(faPatchData());
+
+        auto& patchDef = faPatchDefs.last();
+        patchDef.name_ = "undefined";
+        patchDef.type_ = "patch";
+
+        if (defaultPatchDefinition)
+        {
+            (*defaultPatchDefinition).readIfPresent("name", patchDef.name_);
+            (*defaultPatchDefinition).readIfPresent("type", patchDef.type_);
+        }
+    }
+
+    // ----------------------------------------------------------------------
+
+    // Determine faPatch ID for each boundary edge.
+    // Result is in the bndEdgeFaPatchIDs list
+
+    const labelList meshEdges
+    (
+        patch().meshEdges(mesh().edges(), mesh().pointEdges())
+    );
+
+    const labelListList& edgeFaces = mesh().edgeFaces();
+
+    const label nInternalEdges = patch().nInternalEdges();
+    const label nBoundaryEdges = patch().nBoundaryEdges();
+
+    labelList bndEdgeFaPatchIDs(nBoundaryEdges, -1);
+
+    for (label bndEdgei = 0; bndEdgei < nBoundaryEdges; ++bndEdgei)
+    {
+        const label patchEdgei = meshEdges[bndEdgei + nInternalEdges];
+
+        // Use 'edge' for accounting
+        edge curEdgePatchPair;
+
+        for (const label meshFacei : edgeFaces[patchEdgei])
+        {
+            const label polyPatchID = pbm.whichPatch(meshFacei);
+
+            if (polyPatchID != -1)
+            {
+                curEdgePatchPair.insert(polyPatchID);
+            }
+        }
+
+
+        if (curEdgePatchPair.valid())
+        {
+            // Non-negative, unique pairing
+            // - find corresponding definition
+
+            for (label patchi = 0; patchi < faPatchDefs.size(); ++patchi)
+            {
+                if (faPatchDefs[patchi].foundPatchPair(curEdgePatchPair))
+                {
+                    bndEdgeFaPatchIDs[bndEdgei] = patchi;
+                    break;
+                }
+            }
+        }
+    }
+
+
+    // Extract which edges map to which patch
+    // and set edgeLabels for each faPatch
+
+    DynamicList<label> selectEdges(bndEdgeFaPatchIDs.size());
+
+    for (label patchi = 0; patchi < faPatchDefs.size(); ++patchi)
+    {
+        auto& patchDef = faPatchDefs[patchi];
+
+        selectEdges.clear();
+
+        forAll(bndEdgeFaPatchIDs, bndEdgei)
+        {
+            if (bndEdgeFaPatchIDs[bndEdgei] == patchi)
+            {
+                selectEdges.append(bndEdgei + nInternalEdges);
+            }
+        }
+
+        patchDef.edgeLabels_ = selectEdges;
+    }
+
+    // Check for undefined edges
+    selectEdges.clear();
+
+    forAll(bndEdgeFaPatchIDs, bndEdgei)
+    {
+        if (bndEdgeFaPatchIDs[bndEdgei] == -1)
+        {
+            selectEdges.append(bndEdgei + nInternalEdges);
+        }
+    }
+
+    // Save the information
+    faPatchDefs[undefPatchId].edgeLabels_ = selectEdges;
+
+    bool hasUndefined = returnReduce(!selectEdges.empty(), orOp<bool>());
+
+    if (hasUndefined)
+    {
+        // The initial edges to consider
+        const labelList& undefinedEdges =
+            faPatchDefs[undefPatchId].edgeLabels_;
+
+        // Check for edges that butt against a processor (or other) patch
+
+        labelList edgeNbrPolyPatch(undefinedEdges.size(), -1);
+        forAll(edgeNbrPolyPatch, edgei)
+        {
+            const label localEdgei = undefinedEdges[edgei];
+            const label meshEdgei = meshEdges[localEdgei];
+            label polyPatchID;
+
+            for (const label meshFacei : edgeFaces[meshEdgei])
+            {
+                if
+                (
+                    !faceLabels_.found(meshFacei)
+                 && (polyPatchID = pbm.whichPatch(meshFacei)) != -1
+                )
+                {
+                    // Found the edge's off-patch neighbour face
+                    edgeNbrPolyPatch[edgei] = polyPatchID;
+                    break;
+                }
+            }
+        }
+
+        // Categorize as processor/non-processor associations
+        labelHashSet procPatchIDs;
+        labelHashSet nonProcPatchIDs;
+
+        for (const label polyPatchID : edgeNbrPolyPatch)
+        {
+            if (polyPatchID == -1)
+            {
+                nonProcPatchIDs.insert(polyPatchID);
+            }
+            else if
+            (
+                !nonProcPatchIDs.found(polyPatchID)
+             && !procPatchIDs.found(polyPatchID)
+            )
+            {
+                if (isA<processorPolyPatch>(pbm[polyPatchID]))
+                {
+                    procPatchIDs.insert(polyPatchID);
+                }
+                else
+                {
+                    nonProcPatchIDs.insert(polyPatchID);
+                }
+            }
+        }
+
+        // Select by processor association
+        for (const label polyPatchID : procPatchIDs.sortedToc())
+        {
+            selectEdges.clear();
+
+            forAll(edgeNbrPolyPatch, edgei)
+            {
+                if (edgeNbrPolyPatch[edgei] == polyPatchID)
+                {
+                    selectEdges.append(undefinedEdges[edgei]);
+                }
+            }
+
+            faPatchDefs.append(faPatchData());
+
+            auto& patchDef = faPatchDefs.last();
+            patchDef.name_ = pbm[polyPatchID].name();
+            patchDef.type_ = processorFaPatch::typeName;
+            patchDef.neighPolyPatchId_ = polyPatchID;  // Needed for reorder
+
+            const auto* ppp = isA<processorPolyPatch>(pbm[polyPatchID]);
+            if (ppp)
+            {
+                patchDef.ownerProcId_ = ppp->myProcNo();
+                patchDef.neighProcId_ = ppp->neighbProcNo();
+            }
+
+            patchDef.edgeLabels_ = selectEdges;
+        }
+
+
+        // Check for any remaining undefined edges
+        selectEdges.clear();
+
+        // Simply grab any/all (don't worry about which patch)
+        if (!nonProcPatchIDs.empty())
+        {
+            forAll(edgeNbrPolyPatch, edgei)
+            {
+                const label polyPatchID = edgeNbrPolyPatch[edgei];
+                if (nonProcPatchIDs.found(polyPatchID))
+                {
+                    selectEdges.append(undefinedEdges[edgei]);
+                }
+            }
+        }
+
+        // Complete the information
+        faPatchDefs[undefPatchId].edgeLabels_ = selectEdges;
+
+        hasUndefined = returnReduce(!selectEdges.empty(), orOp<bool>());
+    }
+
+    // Remove unnecessary entry
+    if (!hasUndefined)
+    {
+        faPatchDefs.remove(undefPatchId);
+    }
+
+    for (auto& patchDef : faPatchDefs)
+    {
+        if (patchDef.coupled())
+        {
+            reorderProcEdges(patchDef, meshEdges);
+            patchDef.neighPolyPatchId_ = -1; // No longer required + confusing
+        }
+    }
+
+
+    // Now convert list of definitions to list of patches
+
+    label nPatches = 0;
+    PtrList<faPatch> newPatches(faPatchDefs.size());
+
+    for (faPatchData& patchDef : faPatchDefs)
+    {
+        newPatches.set
+        (
+            nPatches,
+            faPatch::New
+            (
+                patchDef.name(),        // name
+                patchDef.dict(false),   // withEdgeLabels == false
+                nPatches,               // index
+                boundary()
+            )
+        );
+
+        // Transfer edge labels
+        newPatches[nPatches].resetEdges(std::move(patchDef.edgeLabels_));
+        ++nPatches;
+    }
+
+    return newPatches;
+}
+
+
+// ************************************************************************* //
diff --git a/src/finiteArea/faMesh/faMeshUpdate.C b/src/finiteArea/faMesh/faMeshUpdate.C
index cb518d77eff..1e8593b261f 100644
--- a/src/finiteArea/faMesh/faMeshUpdate.C
+++ b/src/finiteArea/faMesh/faMeshUpdate.C
@@ -6,7 +6,7 @@
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
     Copyright (C) 2016-2017 Wikki Ltd
-    Copyright (C) 2020 OpenCFD Ltd.
+    Copyright (C) 2020-2021 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -61,11 +61,12 @@ void Foam::faMesh::updateMesh(const mapPolyMesh& mpm)
     // Set new labels
     m.faceLabels_ = mapper.areaMap().newFaceLabels();
 
-    const indirectPrimitivePatch& bp = patch();
+    const uindirectPrimitivePatch& bp = patch();
 
     // Collect patch data
     const label nTotalEdges = bp.nEdges();
     const label nInternalEdges = bp.nInternalEdges();
+    const label nBoundaryEdges = bp.nBoundaryEdges();
     const labelListList& edgeFaces = bp.edgeFaces();
 
     labelListList patchEdges(boundary_.size());
@@ -73,7 +74,7 @@ void Foam::faMesh::updateMesh(const mapPolyMesh& mpm)
     // Special handling required for faces that have more than one edge
     // Each patch will be visited separately
 
-    labelList edgeToPatch(nTotalEdges - nInternalEdges, -1);
+    labelList edgeToPatch(nBoundaryEdges, -1);
     const labelList& newFaceLabelsMap = mapper.areaMap().newFaceLabelsMap();
 
     const labelListList& oldPatchEdgeFaces = mapper.oldPatchEdgeFaces();
@@ -81,7 +82,7 @@ void Foam::faMesh::updateMesh(const mapPolyMesh& mpm)
     forAll(oldPatchEdgeFaces, patchI)
     {
         labelList& curPatchEdges = patchEdges[patchI];
-        curPatchEdges.setSize(nTotalEdges - nInternalEdges);
+        curPatchEdges.resize(nBoundaryEdges);
         label nCurPatchEdges = 0;
 
         // Note: it is possible to pick up the old-to-new boundary patch
diff --git a/src/finiteArea/faMesh/faPatches/basic/coupled/coupledFaPatch.C b/src/finiteArea/faMesh/faPatches/basic/coupled/coupledFaPatch.C
index bbc073bfc21..8ca96bbdaa6 100644
--- a/src/finiteArea/faMesh/faPatches/basic/coupled/coupledFaPatch.C
+++ b/src/finiteArea/faMesh/faPatches/basic/coupled/coupledFaPatch.C
@@ -117,10 +117,4 @@ void Foam::coupledFaPatch::calcTransformTensors
 }
 
 
-// * * * * * * * * * * * * * * * * Destructor  * * * * * * * * * * * * * * * //
-
-Foam::coupledFaPatch::~coupledFaPatch()
-{}
-
-
 // ************************************************************************* //
diff --git a/src/finiteArea/faMesh/faPatches/basic/coupled/coupledFaPatch.H b/src/finiteArea/faMesh/faPatches/basic/coupled/coupledFaPatch.H
index d5424a42a4a..7f5ddc489be 100644
--- a/src/finiteArea/faMesh/faPatches/basic/coupled/coupledFaPatch.H
+++ b/src/finiteArea/faMesh/faPatches/basic/coupled/coupledFaPatch.H
@@ -135,7 +135,7 @@ public:
 
 
     //- Destructor
-    virtual ~coupledFaPatch();
+    virtual ~coupledFaPatch() = default;
 
 
     // Member Functions
diff --git a/src/finiteArea/faMesh/faPatches/constraint/empty/emptyFaPatch.C b/src/finiteArea/faMesh/faPatches/constraint/empty/emptyFaPatch.C
index 5b4c4bb971e..93c69622e8d 100644
--- a/src/finiteArea/faMesh/faPatches/constraint/empty/emptyFaPatch.C
+++ b/src/finiteArea/faMesh/faPatches/constraint/empty/emptyFaPatch.C
@@ -28,19 +28,50 @@ License
 #include "emptyFaPatch.H"
 #include "addToRunTimeSelectionTable.H"
 
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+// * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * //
 
 namespace Foam
 {
 
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
-
 // Patch name
 defineTypeNameAndDebug(emptyFaPatch, 0);
 
 // Add the patch constructor functions to the hash tables
 addToRunTimeSelectionTable(faPatch, emptyFaPatch, dictionary);
 
+} // End namespace Foam
+
+
+// * * * * * * * * * * * * * * * * Constructors  * * * * * * * * * * * * * * //
+
+Foam::emptyFaPatch::emptyFaPatch
+(
+    const word& name,
+    const label index,
+    const faBoundaryMesh& bm,
+    const label ngbPolyPatchIndex
+)
+:
+    emptyFaPatch(name, labelList(), index, bm, ngbPolyPatchIndex)
+{}
+
+
+Foam::emptyFaPatch::emptyFaPatch
+(
+    const word& name,
+    const labelList& edgeLabels,
+    const label index,
+    const faBoundaryMesh& bm,
+    const label ngbPolyPatchIndex
+)
+:
+    faPatch(name, edgeLabels, index, bm, ngbPolyPatchIndex)
+{}
+
+
+// * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //
+
+
 // Over-riding the face normals return from the underlying patch
 // This is the only piece of info used out of the underlying primitivePatch
 // I choose to store it there because it is used in primitive patch operations
@@ -54,8 +85,4 @@ addToRunTimeSelectionTable(faPatch, emptyFaPatch, dictionary);
 // }
 
 
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
-
-} // End namespace Foam
-
 // ************************************************************************* //
diff --git a/src/finiteArea/faMesh/faPatches/constraint/empty/emptyFaPatch.H b/src/finiteArea/faMesh/faPatches/constraint/empty/emptyFaPatch.H
index 0ff674f5663..e9ecd37363e 100644
--- a/src/finiteArea/faMesh/faPatches/constraint/empty/emptyFaPatch.H
+++ b/src/finiteArea/faMesh/faPatches/constraint/empty/emptyFaPatch.H
@@ -6,6 +6,7 @@
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
     Copyright (C) 2016-2017 Wikki Ltd
+    Copyright (C) 2021 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -57,7 +58,6 @@ class emptyFaPatch
 :
     public faPatch
 {
-
 public:
 
     //- Runtime type information
@@ -66,7 +66,16 @@ public:
 
     // Constructors
 
-         //- Construct from components
+        //- Minimal construct from components
+        emptyFaPatch
+        (
+            const word& name,
+            const label index,
+            const faBoundaryMesh& bm,
+            const label ngbPolyPatchIndex = -1
+        );
+
+        //- Construct from components
         emptyFaPatch
         (
             const word& name,
@@ -74,10 +83,7 @@ public:
             const label index,
             const faBoundaryMesh& bm,
             const label ngbPolyPatchIndex
-        )
-        :
-            faPatch(name, edgeLabels, index, bm, ngbPolyPatchIndex)
-        {}
+        );
 
         //- Construct from dictionary
         emptyFaPatch
@@ -114,6 +120,7 @@ public:
             );
         }
 
+
     // Member Functions
 
         virtual label size() const
@@ -124,7 +131,6 @@ public:
         //- Return face normals. Over-riding base class return to get zero size
         //
 //         virtual const vectorField& edgeNormals() const;
-
 };
 
 
diff --git a/src/finiteArea/faMesh/faPatches/constraint/processor/processorFaPatch.H b/src/finiteArea/faMesh/faPatches/constraint/processor/processorFaPatch.H
index 4bf3f9d6833..c48962843c6 100644
--- a/src/finiteArea/faMesh/faPatches/constraint/processor/processorFaPatch.H
+++ b/src/finiteArea/faMesh/faPatches/constraint/processor/processorFaPatch.H
@@ -122,7 +122,6 @@ protected:
 public:
 
     //- Runtime type information
-//     TypeName(processorPolyPatch::typeName_());
     TypeName("processor");
 
 
@@ -174,7 +173,7 @@ public:
     virtual ~processorFaPatch();
 
 
-    // Member functions
+    // Member Functions
 
         //- Return interface size
         virtual label interfaceSize() const
diff --git a/src/finiteArea/faMesh/faPatches/constraint/wedge/wedgeFaPatch.C b/src/finiteArea/faMesh/faPatches/constraint/wedge/wedgeFaPatch.C
index 8cca67583a7..65d1267281d 100644
--- a/src/finiteArea/faMesh/faPatches/constraint/wedge/wedgeFaPatch.C
+++ b/src/finiteArea/faMesh/faPatches/constraint/wedge/wedgeFaPatch.C
@@ -6,6 +6,7 @@
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
     Copyright (C) 2016-2017 Wikki Ltd
+    Copyright (C) 2021 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -94,15 +95,14 @@ Foam::wedgeFaPatch::wedgeFaPatch
             << this->name() << exit(FatalError);
     }
 
-    if (isA<wedgePolyPatch>(bm.mesh()().boundaryMesh()[ngbPolyPatchIndex()]))
-    {
-        const wedgePolyPatch& wedge =
-            refCast<const wedgePolyPatch>
-            (
-                bm.mesh()().boundaryMesh()[ngbPolyPatchIndex()]
-            );
+    const auto* wedgePtr = isA<wedgePolyPatch>
+    (
+        bm.mesh()().boundaryMesh()[ngbPolyPatchIndex()]
+    );
 
-        wedgePolyPatchPtr_ = &wedge;
+    if (wedgePtr)
+    {
+        wedgePolyPatchPtr_ = wedgePtr;
     }
     else
     {
diff --git a/src/finiteArea/faMesh/faPatches/faPatch/faPatch.C b/src/finiteArea/faMesh/faPatches/faPatch/faPatch.C
index 52f7a3ba624..95a84b1f742 100644
--- a/src/finiteArea/faMesh/faPatches/faPatch/faPatch.C
+++ b/src/finiteArea/faMesh/faPatches/faPatch/faPatch.C
@@ -6,7 +6,7 @@
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
     Copyright (C) 2016-2017 Wikki Ltd
-    Copyright (C) 2019-2020 OpenCFD Ltd.
+    Copyright (C) 2019-2021 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -21,7 +21,7 @@ License
     FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
     for more details.
 
-//     You should have received a copy of the GNU General Public License
+    You should have received a copy of the GNU General Public License
     along with OpenFOAM.  If not, see <http://www.gnu.org/licenses/>.
 
 \*---------------------------------------------------------------------------*/
@@ -68,7 +68,7 @@ Foam::faPatch::faPatch
 :
     labelList(edgeLabels),
     patchIdentifier(name, index),
-    ngbPolyPatchIndex_(ngbPolyPatchIndex),
+    nbrPolyPatchId_(ngbPolyPatchIndex),
     boundaryMesh_(bm),
     edgeFacesPtr_(nullptr),
     pointLabelsPtr_(nullptr),
@@ -86,7 +86,7 @@ Foam::faPatch::faPatch
 :
     labelList(dict.get<labelList>("edgeLabels")),
     patchIdentifier(name, dict, index),
-    ngbPolyPatchIndex_(dict.get<label>("ngbPolyPatchIndex")),
+    nbrPolyPatchId_(dict.get<label>("ngbPolyPatchIndex")),
     boundaryMesh_(bm),
     edgeFacesPtr_(nullptr),
     pointLabelsPtr_(nullptr),
@@ -98,7 +98,7 @@ Foam::faPatch::faPatch(const faPatch& p, const faBoundaryMesh& bm)
 :
     labelList(p),
     patchIdentifier(p, p.index()),
-    ngbPolyPatchIndex_(p.ngbPolyPatchIndex_),
+    nbrPolyPatchId_(p.nbrPolyPatchId_),
     boundaryMesh_(bm),
     edgeFacesPtr_(nullptr),
     pointLabelsPtr_(nullptr),
@@ -116,13 +116,13 @@ Foam::faPatch::~faPatch()
 
 // * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //
 
-Foam::label Foam::faPatch::ngbPolyPatchIndex() const
+Foam::label Foam::faPatch::ngbPolyPatchIndex() const noexcept
 {
-    return ngbPolyPatchIndex_;
+    return nbrPolyPatchId_;
 }
 
 
-const Foam::faBoundaryMesh& Foam::faPatch::boundaryMesh() const
+const Foam::faBoundaryMesh& Foam::faPatch::boundaryMesh() const noexcept
 {
     return boundaryMesh_;
 }
@@ -240,38 +240,22 @@ const Foam::labelListList& Foam::faPatch::pointEdges() const
 
 Foam::labelList Foam::faPatch::ngbPolyPatchFaces() const
 {
-    labelList ngbFaces;
-
-    if (ngbPolyPatchIndex() == -1)
+    if (nbrPolyPatchId_ == -1)
     {
-        return ngbFaces;
+        return labelList();
     }
 
-    ngbFaces.setSize(faPatch::size());
+    labelList ngbFaces(faPatch::size());
 
     const faMesh& aMesh = boundaryMesh().mesh();
-    const polyMesh& pMesh = aMesh();
-    const indirectPrimitivePatch& patch = aMesh.patch();
+    const polyMesh& pMesh = aMesh.mesh();
+    const auto& patch = aMesh.patch();
 
     const labelListList& edgeFaces = pMesh.edgeFaces();
 
-    labelList faceCells(patch.size(), -1);
-
-    forAll(faceCells, faceI)
-    {
-        label faceID = aMesh.faceLabels()[faceI];
-
-        faceCells[faceI] = pMesh.faceOwner()[faceID];
-    }
-
-    labelList meshEdges
+    const labelList meshEdges
     (
-        patch.meshEdges
-        (
-            pMesh.edges(),
-            pMesh.cellEdges(),
-            faceCells
-        )
+        patch.meshEdges(pMesh.edges(), pMesh.pointEdges())
     );
 
     forAll(ngbFaces, edgeI)
@@ -288,7 +272,7 @@ Foam::labelList Foam::faPatch::ngbPolyPatchFaces() const
 
             label curPatchID = pMesh.boundaryMesh().whichPatch(curFace);
 
-            if (curPatchID == ngbPolyPatchIndex())
+            if (curPatchID == nbrPolyPatchId_)
             {
                 ngbFaces[edgeI] = curFace;
             }
@@ -311,7 +295,7 @@ Foam::tmp<Foam::vectorField> Foam::faPatch::ngbPolyPatchFaceNormals() const
     auto tfN = tmp<vectorField>::New();
     auto& fN = tfN.ref();
 
-    if (ngbPolyPatchIndex() == -1)
+    if (nbrPolyPatchId_ == -1)
     {
         return tfN;
     }
@@ -336,7 +320,7 @@ Foam::tmp<Foam::vectorField> Foam::faPatch::ngbPolyPatchFaceNormals() const
 
 Foam::tmp<Foam::vectorField> Foam::faPatch::ngbPolyPatchPointNormals() const
 {
-    if (ngbPolyPatchIndex() == -1)
+    if (nbrPolyPatchId_ == -1)
     {
         return tmp<vectorField>::New();
     }
@@ -468,12 +452,17 @@ void Foam::faPatch::movePoints(const pointField& points)
 {}
 
 
-void Foam::faPatch::resetEdges(const labelList& newEdges)
+void Foam::faPatch::resetEdges(const UList<label>& newEdges)
 {
-    Info<< "Resetting patch edges" << endl;
-    labelList::operator=(newEdges);
+    clearOut();
+    static_cast<labelList&>(*this) = newEdges;
+}
+
 
+void Foam::faPatch::resetEdges(labelList&& newEdges)
+{
     clearOut();
+    static_cast<labelList&>(*this) = std::move(newEdges);
 }
 
 
@@ -483,9 +472,8 @@ void Foam::faPatch::write(Ostream& os) const
 
     patchIdentifier::write(os);
 
-    const labelList& edgeLabels = *this;
-    edgeLabels.writeEntry("edgeLabels", os);
-    os.writeEntry("ngbPolyPatchIndex", ngbPolyPatchIndex_);
+    os.writeEntry("ngbPolyPatchIndex", nbrPolyPatchId_);
+    static_cast<const labelList&>(*this).writeEntry("edgeLabels", os);
 }
 
 
diff --git a/src/finiteArea/faMesh/faPatches/faPatch/faPatch.H b/src/finiteArea/faMesh/faPatches/faPatch/faPatch.H
index 52fb33e613a..98bb9efe764 100644
--- a/src/finiteArea/faMesh/faPatches/faPatch/faPatch.H
+++ b/src/finiteArea/faMesh/faPatches/faPatch/faPatch.H
@@ -6,7 +6,7 @@
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
     Copyright (C) 2016-2017 Wikki Ltd
-    Copyright (C) 2020 OpenCFD Ltd.
+    Copyright (C) 2020-2021 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -58,6 +58,7 @@ SourceFiles
 namespace Foam
 {
 
+// Forward Declarations
 class faBoundaryMesh;
 class faPatch;
 Ostream& operator<<(Ostream&, const faPatch&);
@@ -74,7 +75,7 @@ class faPatch
     // Private Data
 
         //- Neighbour polyPatch index
-        const label ngbPolyPatchIndex_;
+        const label nbrPolyPatchId_;
 
         //- Reference to boundary mesh
         const faBoundaryMesh& boundaryMesh_;
@@ -100,7 +101,6 @@ class faPatch
         //- No copy assignment
         void operator=(const faPatch&) = delete;
 
-
         //- Clear out topological patch data
         void clearOut();
 
@@ -225,17 +225,31 @@ public:
 
     // Member Functions
 
-        //- Return number of patch points
+        //- Return the list of edges
+        const labelList& edgeLabels() const noexcept
+        {
+            return static_cast<const labelList&>(*this);
+        }
+
+        void edgeLabels(const UList<label>& newEdgeLabels);
+
+        //- Number of patch points
         label nPoints() const
         {
             return pointLabels().size();
         }
 
+        //- Number of edge labels (boundary edges) addressed by this patch
+        label nEdges() const noexcept
+        {
+            return labelList::size();
+        }
+
         //- Return neighbour polyPatch index
-        label ngbPolyPatchIndex() const;
+        label ngbPolyPatchIndex() const noexcept;
 
         //- Return boundaryMesh reference
-        const faBoundaryMesh& boundaryMesh() const;
+        const faBoundaryMesh& boundaryMesh() const noexcept;
 
         //- Return true if this patch is coupled
         virtual bool coupled() const
@@ -246,7 +260,7 @@ public:
         //- Patch start in edge list
         label start() const;
 
-        //- Patch size
+        //- Patch size is the number of edge labels
         virtual label size() const
         {
             return labelList::size();
@@ -330,8 +344,11 @@ public:
 
         // Topological changes
 
-            //- Reset edge list
-            void resetEdges(const labelList&);
+            //- Reset the list of edges (use with caution)
+            void resetEdges(const UList<label>& newEdges);
+
+            //- Reset the list of edges (use with caution)
+            void resetEdges(labelList&& newEdges);
 
 
         // Evaluation
diff --git a/src/finiteArea/faMesh/faPatches/faPatch/faPatchData.C b/src/finiteArea/faMesh/faPatches/faPatch/faPatchData.C
new file mode 100644
index 00000000000..7d712c99efd
--- /dev/null
+++ b/src/finiteArea/faMesh/faPatches/faPatch/faPatchData.C
@@ -0,0 +1,130 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
+   \\    /   O peration     |
+    \\  /    A nd           | www.openfoam.com
+     \\/     M anipulation  |
+-------------------------------------------------------------------------------
+    Copyright (C) 2021 OpenCFD Ltd.
+-------------------------------------------------------------------------------
+License
+    This file is part of OpenFOAM.
+
+    OpenFOAM is free software: you can redistribute it and/or modify it
+    under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    OpenFOAM is distributed in the hope that it will be useful, but WITHOUT
+    ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+    FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+    for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with OpenFOAM.  If not, see <http://www.gnu.org/licenses/>.
+
+\*---------------------------------------------------------------------------*/
+
+#include "faPatchData.H"
+#include "edge.H"
+#include "dictionary.H"
+#include "processorFaPatch.H"
+
+// * * * * * * * * * * * * * * * * Constructors  * * * * * * * * * * * * * * //
+
+Foam::faPatchData::faPatchData()
+:
+    ownerPolyPatchId_(-1),
+    neighPolyPatchId_(-1),
+    ownerProcId_(-1),
+    neighProcId_(-1)
+{}
+
+
+// * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //
+
+const Foam::word& Foam::faPatchData::name() const noexcept
+{
+    return name_;
+}
+
+
+Foam::dictionary Foam::faPatchData::dict(const bool withEdgeLabels) const
+{
+    dictionary patchDict;
+    patchDict.add("type", type_);
+
+    if (withEdgeLabels)
+    {
+        patchDict.add("edgeLabels", edgeLabels_);
+    }
+    else
+    {
+        patchDict.add("edgeLabels", labelList());
+    }
+    patchDict.add("ngbPolyPatchIndex", neighPolyPatchId_);
+
+    if (coupled())
+    {
+        patchDict.add("myProcNo", ownerProcId_);
+        patchDict.add("neighbProcNo", neighProcId_);
+    }
+
+    return patchDict;
+}
+
+
+void Foam::faPatchData::clear()
+{
+    name_.clear();
+    type_.clear();
+
+    ownerPolyPatchId_ = -1;
+    neighPolyPatchId_ = -1;
+
+    ownerProcId_ = -1;
+    neighProcId_ = -1;
+
+    edgeLabels_.clear();
+}
+
+
+void Foam::faPatchData::assign(const faPatch& fap)
+{
+    clear();
+
+    // Copy information
+    name_ = fap.name();
+    type_ = fap.type();
+
+    neighPolyPatchId_ = fap.ngbPolyPatchIndex();
+    edgeLabels_ = fap.edgeLabels();
+
+    const auto* fapp = isA<processorFaPatch>(fap);
+    if (fapp)
+    {
+        ownerProcId_ = fapp->myProcNo();
+        neighProcId_ = fapp->neighbProcNo();
+    }
+}
+
+
+bool Foam::faPatchData::foundPatchPair(const edge& patchPair) const
+{
+    // Same as edge::compare
+    return
+    (
+        (
+            ownerPolyPatchId_ == patchPair.first()
+         && neighPolyPatchId_ == patchPair.second()
+        )
+     ||
+        (
+            ownerPolyPatchId_ == patchPair.second()
+         && neighPolyPatchId_ == patchPair.first()
+        )
+    );
+}
+
+
+// ************************************************************************* //
diff --git a/src/finiteArea/faMesh/faPatches/faPatch/faPatchData.H b/src/finiteArea/faMesh/faPatches/faPatch/faPatchData.H
index c8d46697913..4f785b35156 100644
--- a/src/finiteArea/faMesh/faPatches/faPatch/faPatchData.H
+++ b/src/finiteArea/faMesh/faPatches/faPatch/faPatchData.H
@@ -6,6 +6,7 @@
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
     Copyright (C) 2016-2017 Wikki Ltd
+    Copyright (C) 2021 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -27,7 +28,8 @@ Class
     Foam::faPatchData
 
 Description
-    Class which holds data needed for faPatch construction
+    Helper class for holding data during faPatch construction.
+    Most data members are exposed at the moment.
 
 \*---------------------------------------------------------------------------*/
 
@@ -41,25 +43,81 @@ Description
 namespace Foam
 {
 
+// Forward Declarations
+class edge;
+class faPatch;
+class dictionary;
+
 /*---------------------------------------------------------------------------*\
                          Class faPatchData Declaration
 \*---------------------------------------------------------------------------*/
 
-struct faPatchData
+class faPatchData
 {
-    word name_;
-    word type_;
-    dictionary dict_;
-    label ownPolyPatchID_;
-    label ngbPolyPatchID_;
-    labelList edgeLabels_;
-    faPatchData()
-    :
-        name_(word::null),
-        type_(word::null),
-        ownPolyPatchID_(-1),
-        ngbPolyPatchID_(-1)
-    {}
+public:
+
+    // Data Members
+
+        word name_;
+        word type_;
+
+        label ownerPolyPatchId_;
+        label neighPolyPatchId_;
+
+        //- The owner/neighbour for processor patches
+        int ownerProcId_;
+        int neighProcId_;
+
+        // Storge (temporary or otherwise) for edge labels
+        labelList edgeLabels_;
+
+
+    // Constructors
+
+        //- Default construct
+        faPatchData();
+
+
+    // Member Functions
+
+    // Opaque read-only access
+
+        //- Return the name
+        const word& name() const noexcept;
+
+        //- Contents transcribed into a patch dictionary,
+        //- usually including the edge labels.
+        dictionary dict(const bool withEdgeLabels = true) const;
+
+
+    // Other Functions
+
+        //- Reset data
+        void clear();
+
+        //- Clear and populate with values from finiteArea patch
+        void assign(const faPatch& fap);
+
+        //- True if owner/neighbour processor ids are non-equal
+        bool coupled() const noexcept
+        {
+            return (ownerProcId_ != neighProcId_);
+        }
+
+        //- Does this side own the patch? Also true for non-coupled patches
+        bool owner() const noexcept
+        {
+            return (ownerProcId_ <= neighProcId_);
+        }
+
+        //- Does the other side own the patch?
+        bool neighbour() const noexcept
+        {
+            return !owner();
+        }
+
+        //- True it matches the owner/neighbour patch pair (any order)
+        bool foundPatchPair(const edge& patchPair) const;
 };
 
 
diff --git a/src/finiteArea/faMesh/faPatches/faPatch/faPatchFaMeshTemplates.C b/src/finiteArea/faMesh/faPatches/faPatch/faPatchFaMeshTemplates.C
index 6e3fab5ee86..ce4a007e11a 100644
--- a/src/finiteArea/faMesh/faPatches/faPatch/faPatchFaMeshTemplates.C
+++ b/src/finiteArea/faMesh/faPatches/faPatch/faPatchFaMeshTemplates.C
@@ -6,6 +6,7 @@
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
     Copyright (C) 2016-2017 Wikki Ltd
+    Copyright (C) 2021 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -39,10 +40,8 @@ const typename GeometricField::Patch& Foam::faPatch::lookupPatchField
 {
     return patchField<GeometricField, Type>
     (
-        boundaryMesh().mesh()().objectRegistry::lookupObject<GeometricField>
-        (
-            name
-        )
+        boundaryMesh().mesh().mesh().objectRegistry::template
+            lookupObject<GeometricField>(name)
     );
 }
 
-- 
GitLab