diff --git a/applications/utilities/finiteArea/makeFaMesh/Make/options b/applications/utilities/finiteArea/makeFaMesh/Make/options
index 98ba4f7225daeac475fec261a9a17f85c892c959..7a28b2c4b67d5b655edcd69b39ea0baae8c47e33 100644
--- a/applications/utilities/finiteArea/makeFaMesh/Make/options
+++ b/applications/utilities/finiteArea/makeFaMesh/Make/options
@@ -1,8 +1,13 @@
 EXE_INC = \
     -I$(LIB_SRC)/finiteArea/lnInclude \
-    -I$(LIB_SRC)/finiteVolume/lnInclude \
-    -I$(LIB_SRC)/cfdTools/general/lnInclude
+    -I$(LIB_SRC)/fileFormats/lnInclude \
+    -I$(LIB_SRC)/meshTools/lnInclude \
+    -I$(LIB_SRC)/parallel/decompose/faDecompose/lnInclude \
+    -I$(LIB_SRC)/parallel/reconstruct/faReconstruct/lnInclude
 
 EXE_LIBS = \
     -lfiniteArea \
-    -lfiniteVolume
+    -lfileFormats \
+    -lmeshTools \
+    -lfaDecompose \
+    -lfaReconstruct
diff --git a/applications/utilities/finiteArea/makeFaMesh/decomposeFaFields.H b/applications/utilities/finiteArea/makeFaMesh/decomposeFaFields.H
new file mode 100644
index 0000000000000000000000000000000000000000..5c38c9b4f13471be875283634693cbfcf3c72fc7
--- /dev/null
+++ b/applications/utilities/finiteArea/makeFaMesh/decomposeFaFields.H
@@ -0,0 +1,94 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  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
+    Decompose area fields, when mesh was generated in parallel
+
+\*---------------------------------------------------------------------------*/
+
+if (Pstream::parRun())
+{
+    faMeshReconstructor reconstructor(areaMesh);
+    reconstructor.writeAddressing();
+
+    // Handle area fields
+    // ------------------
+
+    PtrList<areaScalarField> areaScalarFields;
+    PtrList<areaVectorField> areaVectorFields;
+    PtrList<areaSphericalTensorField> areaSphTensorFields;
+    PtrList<areaSymmTensorField> areaSymmTensorFields;
+    PtrList<areaTensorField> areaTensorFields;
+
+    const faMesh& fullMesh = reconstructor.mesh();
+
+    {
+        // Use uncollated (or master uncollated) file handler here.
+        // - each processor is reading in the identical serial fields.
+        // - nothing should be parallel-coordinated.
+
+        // Similarly, if we write the serial finite-area mesh, this is only
+        // done from one processor!
+
+        reconstructor.writeMesh();
+
+        const bool oldDistributed = fileHandler().distributed();
+        auto oldHandler = fileHandler(fileOperation::NewUncollated());
+        fileHandler().distributed(true);
+
+        IOobjectList objects(fullMesh.time(), runTime.timeName());
+
+        faFieldDecomposer::readFields(fullMesh, objects, areaScalarFields);
+        faFieldDecomposer::readFields(fullMesh, objects, areaVectorFields);
+        faFieldDecomposer::readFields(fullMesh, objects, areaSphTensorFields);
+        faFieldDecomposer::readFields(fullMesh, objects, areaSymmTensorFields);
+        faFieldDecomposer::readFields(fullMesh, objects, areaTensorFields);
+
+        // Restore old settings
+        if (oldHandler)
+        {
+            fileHandler(std::move(oldHandler));
+        }
+        fileHandler().distributed(oldDistributed);
+    }
+
+    const label nAreaFields =
+    (
+        areaScalarFields.size()
+      + areaVectorFields.size()
+      + areaSphTensorFields.size()
+      + areaSymmTensorFields.size()
+      + areaTensorFields.size()
+    );
+
+    if (nAreaFields)
+    {
+        Info<< "Decomposing " << nAreaFields << " area fields" << nl << endl;
+
+        faFieldDecomposer fieldDecomposer
+        (
+            fullMesh,
+            areaMesh,
+            reconstructor.edgeProcAddressing(),
+            reconstructor.faceProcAddressing(),
+            reconstructor.boundaryProcAddressing()
+        );
+
+        fieldDecomposer.decomposeFields(areaScalarFields);
+        fieldDecomposer.decomposeFields(areaVectorFields);
+        fieldDecomposer.decomposeFields(areaSphTensorFields);
+        fieldDecomposer.decomposeFields(areaSymmTensorFields);
+        fieldDecomposer.decomposeFields(areaTensorFields);
+    }
+}
+
+// ************************************************************************* //
diff --git a/applications/utilities/finiteArea/makeFaMesh/faMeshWriteEdgesOBJ.H b/applications/utilities/finiteArea/makeFaMesh/faMeshWriteEdgesOBJ.H
new file mode 100644
index 0000000000000000000000000000000000000000..a50d18df3c3e492fe2809b0116b8a3ae77bf83d3
--- /dev/null
+++ b/applications/utilities/finiteArea/makeFaMesh/faMeshWriteEdgesOBJ.H
@@ -0,0 +1,43 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  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
+    OBJ output of faMesh edges
+
+\*---------------------------------------------------------------------------*/
+
+{
+    Info<< "Writing edges in obj format" << endl;
+
+    word outputName("faMesh-edges.obj");
+
+    if (Pstream::parRun())
+    {
+        outputName = word
+        (
+            "faMesh-edges-" + Foam::name(Pstream::myProcNo()) + ".obj"
+        );
+    }
+
+    OBJstream os(runTime.globalPath()/outputName);
+
+    os.writeQuoted
+    (
+        ("# " + outputName + "\n"),
+        false
+    );
+
+    os.write(areaMesh.patch().edges(), areaMesh.patch().localPoints());
+}
+
+
+// ************************************************************************* //
diff --git a/applications/utilities/finiteArea/makeFaMesh/findMeshDefinitionDict.H b/applications/utilities/finiteArea/makeFaMesh/findMeshDefinitionDict.H
new file mode 100644
index 0000000000000000000000000000000000000000..8a678fdcc8c789a5de4eb4151d2d3b7fe0282330
--- /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 0010be3dabc1a6a7758d642769210d1fadbf41e0..e80ed303d9f65195f5d0e59b1a3b1eff77d0c1e8 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.
@@ -28,6 +29,8 @@ Application
 
 Description
     A mesh generator for finiteArea mesh.
+    When called in parallel, it will also try to act like decomposePar,
+    create procAddressing and decompose serial finite-area fields.
 
 Author
     Zeljko Tukovic, FAMENA
@@ -35,35 +38,19 @@ Author
 
 \*---------------------------------------------------------------------------*/
 
-#include "objectRegistry.H"
 #include "Time.H"
 #include "argList.H"
 #include "OSspecific.H"
 #include "faMesh.H"
-#include "fvMesh.H"
+#include "IOdictionary.H"
+#include "IOobjectList.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)
-    {}
-};
+#include "areaFields.H"
+#include "faFieldDecomposer.H"
+#include "faMeshReconstructor.H"
+#include "OBJstream.H"
 
+using namespace Foam;
 
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
 
@@ -73,286 +60,64 @@ int main(int argc, char *argv[])
     (
         "A mesh generator for finiteArea mesh"
     );
-
-    #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
+    argList::addOption
     (
-        faMeshDefinition.get<wordList>("polyMeshPatches")
+        "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");
 
-    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 ... ";
-
-    faMesh areaMesh
+    argList::addBoolOption
     (
-        mesh,
-        faceLabels
+        "write-edges-obj",
+        "Write mesh edges as obj files and exit",
+        false  // could make an advanced option
     );
-    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;
-    }
+    #include "addRegionOption.H"
+    #include "setRootCase.H"
+    #include "createTime.H"
+    #include "createNamedPolyMesh.H"
 
-    // Check for undefined edges
-    SLList<label> tmpList;
+    // Reading faMeshDefinition dictionary
+    #include "findMeshDefinitionDict.H"
 
-    forAll(bndEdgeFaPatchIDs, eI)
+    // Inject/overwrite name for optional 'empty' patch
+    word patchName;
+    if (args.readIfPresent("empty-patch", patchName))
     {
-        if (bndEdgeFaPatchIDs[eI] == -1)
-        {
-            tmpList.append(nInternalEdges + eI);
-        }
+        meshDefDict.add("emptyPatch", patchName, true);
     }
 
-    if (tmpList.size() > 0)
-    {
-        label pI = faPatches.size()-1;
-
-        faPatches[pI].name_ = "undefined";
-        faPatches[pI].type_ = "patch";
-        faPatches[pI].edgeLabels_ = tmpList;
-    }
+    // Create
+    faMesh areaMesh(mesh, meshDefDict);
 
-    // Add good patches to faMesh
-    SLList<faPatch*> faPatchLst;
+    bool quickExit = false;
 
-    for (label pI = 0; pI < faPatches.size(); ++pI)
+    if (args.found("write-edges-obj"))
     {
-        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()
-            );
-        }
+        quickExit = true;
+        #include "faMeshWriteEdgesOBJ.H"
     }
 
-    word emptyPatchName;
-    if (args.readIfPresent("addEmptyPatch", emptyPatchName))
+    if (quickExit)
     {
-        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<< "\nEnd\n" << endl;
+        return 0;
     }
 
-    Info << "Add faPatches ... ";
-    areaMesh.addFaPatches(List<faPatch*>(faPatchLst));
-    Info << "Done" << endl;
+    // Set the precision of the points data to 10
+    IOstream::defaultPrecision(10);
 
-    // Writing faMesh
-    Info << "Write finite area mesh ... ";
+    Info<< nl << "Write finite area mesh." << nl;
     areaMesh.write();
+    Info<< endl;
+
+    #include "decomposeFaFields.H"
 
-    Info << "\nEnd" << endl;
+    Info << "\nEnd\n" << endl;
 
     return 0;
 }
diff --git a/applications/utilities/parallelProcessing/decomposePar/Make/files b/applications/utilities/parallelProcessing/decomposePar/Make/files
index 03abb7247f26cd5ae2498557e2ded310f474c2ed..d8bc47a3151310dffc671ff0de244ac13d44fa7e 100644
--- a/applications/utilities/parallelProcessing/decomposePar/Make/files
+++ b/applications/utilities/parallelProcessing/decomposePar/Make/files
@@ -1,4 +1,5 @@
 decomposePar.C
+
 domainDecomposition.C
 domainDecompositionMesh.C
 domainDecompositionDistribute.C
@@ -7,10 +8,6 @@ domainDecompositionWrite.C
 domainDecompositionDryRun.C
 domainDecompositionDryRunWrite.C
 
-dimFieldDecomposer.C
-pointFieldDecomposer.C
-faMeshDecomposition.C
-faFieldDecomposer.C
 lagrangianFieldDecomposer.C
 
 EXE = $(FOAM_APPBIN)/decomposePar
diff --git a/applications/utilities/parallelProcessing/decomposePar/Make/options b/applications/utilities/parallelProcessing/decomposePar/Make/options
index 104ca56d7c66b44bb916a3f1649c0e33422f0f5d..4acbf00e42e6142854f1cbeb7e47b383055aeac2 100644
--- a/applications/utilities/parallelProcessing/decomposePar/Make/options
+++ b/applications/utilities/parallelProcessing/decomposePar/Make/options
@@ -6,7 +6,8 @@ EXE_INC = \
     -I$(LIB_SRC)/lagrangian/basic/lnInclude \
     -I$(LIB_SRC)/dynamicMesh/lnInclude \
     -I$(LIB_SRC)/parallel/decompose/decompositionMethods/lnInclude \
-    -I$(LIB_SRC)/parallel/decompose/decompose/lnInclude
+    -I$(LIB_SRC)/parallel/decompose/decompose/lnInclude \
+    -I$(LIB_SRC)/parallel/decompose/faDecompose/lnInclude
 
 EXE_LIBS = \
     -lfiniteArea \
@@ -18,5 +19,6 @@ EXE_LIBS = \
     -lgenericPatchFields \
     -ldecompositionMethods \
     -ldecompose \
+    -lfaDecompose \
     -L$(FOAM_LIBBIN)/dummy \
     -lkahipDecomp -lmetisDecomp -lscotchDecomp
diff --git a/applications/utilities/parallelProcessing/decomposePar/decomposePar.C b/applications/utilities/parallelProcessing/decomposePar/decomposePar.C
index 7c9784ce8db8b298d397034cc1ff7135daecdccb..292e694f71e8d19d31c7473f7dd062bcfa81a961 100644
--- a/applications/utilities/parallelProcessing/decomposePar/decomposePar.C
+++ b/applications/utilities/parallelProcessing/decomposePar/decomposePar.C
@@ -162,7 +162,6 @@ Usage
 #include "regionProperties.H"
 
 #include "readFields.H"
-#include "dimFieldDecomposer.H"
 #include "fvFieldDecomposer.H"
 #include "pointFieldDecomposer.H"
 #include "lagrangianFieldDecomposer.H"
@@ -173,11 +172,52 @@ Usage
 #include "faMeshDecomposition.H"
 #include "faFieldDecomposer.H"
 
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+// * * * * * * * * * * * * * * * Local Functions * * * * * * * * * * * * * * //
 
 namespace Foam
 {
 
+// Read proc addressing at specific instance.
+// Uses polyMesh/fvMesh meshSubDir by default
+autoPtr<labelIOList> procAddressing
+(
+    const fvMesh& procMesh,
+    const word& name,
+    const word& instance,
+    const word& local = fvMesh::meshSubDir
+)
+{
+    return autoPtr<labelIOList>::New
+    (
+        IOobject
+        (
+            name,
+            instance,
+            local,
+            procMesh,
+            IOobject::MUST_READ,
+            IOobject::NO_WRITE,
+            false  // do not register
+        )
+    );
+}
+
+
+// Read proc addressing at specific instance.
+// Uses the finiteArea meshSubDir
+autoPtr<labelIOList> faProcAddressing
+(
+    const fvMesh& procMesh,
+    const word& name,
+    const word& instance,
+    const word& local = faMesh::meshSubDir
+)
+{
+    return procAddressing(procMesh, name, instance, local);
+}
+
+
+// Return cached or read proc addressing from facesInstance
 const labelIOList& procAddressing
 (
     const PtrList<fvMesh>& procMeshList,
@@ -193,19 +233,7 @@ const labelIOList& procAddressing
         procAddressingList.set
         (
             proci,
-            new labelIOList
-            (
-                IOobject
-                (
-                    name,
-                    procMesh.facesInstance(),
-                    procMesh.meshSubDir,
-                    procMesh,
-                    IOobject::MUST_READ,
-                    IOobject::NO_WRITE,
-                    false
-                )
-            )
+            procAddressing(procMesh, name, procMesh.facesInstance())
         );
     }
     return procAddressingList[proci];
@@ -275,7 +303,7 @@ void decomposeUniform
     }
 }
 
-}
+} // End namespace Foam
 
 
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
@@ -682,7 +710,6 @@ int main(int argc, char *argv[])
             PtrList<labelIOList> cellProcAddressingList(mesh.nProcs());
             PtrList<labelIOList> boundaryProcAddressingList(mesh.nProcs());
             PtrList<fvFieldDecomposer> fieldDecomposerList(mesh.nProcs());
-            PtrList<dimFieldDecomposer> dimFieldDecomposerList(mesh.nProcs());
             PtrList<labelIOList> pointProcAddressingList(mesh.nProcs());
             PtrList<pointFieldDecomposer> pointFieldDecomposerList
             (
@@ -1047,7 +1074,6 @@ int main(int argc, char *argv[])
                 {
                     Info<< "Processor " << proci << ": field transfer" << endl;
 
-
                     // open the database
                     if (!processorDbList.set(proci))
                     {
@@ -1112,7 +1138,7 @@ int main(int argc, char *argv[])
                     );
 
 
-                    // FV fields
+                    // FV fields: volume, surface, internal
                     {
                         if (!fieldDecomposerList.set(proci))
                         {
@@ -1132,6 +1158,7 @@ int main(int argc, char *argv[])
                         const fvFieldDecomposer& fieldDecomposer =
                             fieldDecomposerList[proci];
 
+                        // vol fields
                         fieldDecomposer.decomposeFields(volScalarFields);
                         fieldDecomposer.decomposeFields(volVectorFields);
                         fieldDecomposer.decomposeFields
@@ -1141,6 +1168,7 @@ int main(int argc, char *argv[])
                         fieldDecomposer.decomposeFields(volSymmTensorFields);
                         fieldDecomposer.decomposeFields(volTensorFields);
 
+                        // surface fields
                         fieldDecomposer.decomposeFields(surfaceScalarFields);
                         fieldDecomposer.decomposeFields(surfaceVectorFields);
                         fieldDecomposer.decomposeFields
@@ -1153,6 +1181,13 @@ int main(int argc, char *argv[])
                         );
                         fieldDecomposer.decomposeFields(surfaceTensorFields);
 
+                        // internal fields
+                        fieldDecomposer.decomposeFields(dimScalarFields);
+                        fieldDecomposer.decomposeFields(dimVectorFields);
+                        fieldDecomposer.decomposeFields(dimSphericalTensorFields);
+                        fieldDecomposer.decomposeFields(dimSymmTensorFields);
+                        fieldDecomposer.decomposeFields(dimTensorFields);
+
                         if (times.size() == 1)
                         {
                             // Clear cached decomposer
@@ -1160,37 +1195,6 @@ int main(int argc, char *argv[])
                         }
                     }
 
-                    // Dimensioned fields
-                    {
-                        if (!dimFieldDecomposerList.set(proci))
-                        {
-                            dimFieldDecomposerList.set
-                            (
-                                proci,
-                                new dimFieldDecomposer
-                                (
-                                    mesh,
-                                    procMesh,
-                                    faceProcAddressing,
-                                    cellProcAddressing
-                                )
-                            );
-                        }
-                        const dimFieldDecomposer& dimDecomposer =
-                            dimFieldDecomposerList[proci];
-
-                        dimDecomposer.decomposeFields(dimScalarFields);
-                        dimDecomposer.decomposeFields(dimVectorFields);
-                        dimDecomposer.decomposeFields(dimSphericalTensorFields);
-                        dimDecomposer.decomposeFields(dimSymmTensorFields);
-                        dimDecomposer.decomposeFields(dimTensorFields);
-
-                        if (times.size() == 1)
-                        {
-                            dimFieldDecomposerList.set(proci, nullptr);
-                        }
-                    }
-
 
                     // Point fields
                     if
@@ -1366,18 +1370,24 @@ int main(int argc, char *argv[])
                     faMesh::meshSubDir,
                     mesh,
                     IOobject::READ_IF_PRESENT,
-                    IOobject::NO_WRITE
+                    IOobject::NO_WRITE,
+                    false  // not registered
                 );
 
 
                 if (faMeshBoundaryIOobj.typeHeaderOk<faBoundaryMesh>(true))
                 {
-                    Info << "\nFinite area mesh decomposition" << endl;
+                    Info<< "\nFinite area mesh decomposition" << endl;
 
-                    faMeshDecomposition aMesh(mesh);
+                    // Always based on the volume decomposition!
+                    faMeshDecomposition aMesh
+                    (
+                        mesh,
+                        mesh.nProcs(),
+                        mesh.model()
+                    );
 
                     aMesh.decomposeMesh();
-
                     aMesh.writeDecomposition();
 
 
@@ -1404,13 +1414,29 @@ int main(int argc, char *argv[])
                     PtrList<edgeScalarField> edgeScalarFields;
                     readFields(aMesh, objects, edgeScalarFields);
 
-                    Info << endl;
+                    const label nAreaFields =
+                    (
+                        areaScalarFields.size()
+                      + areaVectorFields.size()
+                      + areaSphericalTensorFields.size()
+                      + areaSymmTensorFields.size()
+                      + areaTensorFields.size()
+                      + edgeScalarFields.size()
+                    );
+
+                    Info<< endl;
+                    Info<< "Finite area field transfer: "
+                        << nAreaFields << " fields" << endl;
 
                     // Split the fields over processors
-                    for (label procI = 0; procI < mesh.nProcs(); procI++)
+                    for
+                    (
+                        label proci = 0;
+                        nAreaFields && proci < mesh.nProcs();
+                        ++proci
+                    )
                     {
-                        Info<< "Processor " << procI
-                            << ": finite area field transfer" << endl;
+                        Info<< "    Processor " << proci << endl;
 
                         // open the database
                         Time processorDb
@@ -1418,7 +1444,7 @@ int main(int argc, char *argv[])
                             Time::controlDictName,
                             args.rootPath(),
                             args.caseName()
-                          / ("processor" + Foam::name(procI))
+                          / ("processor" + Foam::name(proci))
                         );
 
                         processorDb.setTime(runTime);
@@ -1441,7 +1467,7 @@ int main(int argc, char *argv[])
                         //     procAddressing
                         //     (
                         //         procMeshList,
-                        //         procI,
+                        //         proci,
                         //         "faceProcAddressing",
                         //         faceProcAddressingList
                         //     );
@@ -1450,84 +1476,59 @@ int main(int argc, char *argv[])
                         //     procAddressing
                         //     (
                         //         procMeshList,
-                        //         procI,
+                        //         proci,
                         //         "boundaryProcAddressing",
                         //         boundaryProcAddressingList
                         //     );
 
-                        labelIOList faceProcAddressing
-                        (
-                            IOobject
-                            (
-                                "faceProcAddressing",
-                                "constant",
-                                procMesh.meshSubDir,
-                                procFvMesh,
-                                IOobject::MUST_READ,
-                                IOobject::NO_WRITE
-                            )
-                        );
+                        // Addressing from faMesh (not polyMesh) meshSubDir
 
-                        labelIOList boundaryProcAddressing
-                        (
-                            IOobject
+                        autoPtr<labelIOList> tfaceProcAddr =
+                            faProcAddressing
                             (
-                                "boundaryProcAddressing",
-                                "constant",
-                                procMesh.meshSubDir,
                                 procFvMesh,
-                                IOobject::MUST_READ,
-                                IOobject::NO_WRITE
-                            )
-                        );
-
-                        // FA fields
-                        if
-                        (
-                            areaScalarFields.size()
-                         || areaVectorFields.size()
-                         || areaSphericalTensorFields.size()
-                         || areaSymmTensorFields.size()
-                         || areaTensorFields.size()
-                         || edgeScalarFields.size()
-                        )
-                        {
-                            labelIOList edgeProcAddressing
-                            (
-                                IOobject
-                                (
-                                    "edgeProcAddressing",
-                                    "constant",
-                                    procMesh.meshSubDir,
-                                    procFvMesh,
-                                    IOobject::MUST_READ,
-                                    IOobject::NO_WRITE
-                                )
+                                "faceProcAddressing",
+                                runTime.constant()
                             );
+                        auto& faceProcAddressing = *tfaceProcAddr;
 
-                            faFieldDecomposer fieldDecomposer
+                        autoPtr<labelIOList> tboundaryProcAddr =
+                            faProcAddressing
                             (
-                                aMesh,
-                                procMesh,
-                                edgeProcAddressing,
-                                faceProcAddressing,
-                                boundaryProcAddressing
+                                procFvMesh,
+                                "boundaryProcAddressing",
+                                runTime.constant()
                             );
+                        auto& boundaryProcAddressing = *tboundaryProcAddr;
 
-                            fieldDecomposer.decomposeFields(areaScalarFields);
-                            fieldDecomposer.decomposeFields(areaVectorFields);
-                            fieldDecomposer.decomposeFields
+                        autoPtr<labelIOList> tedgeProcAddr =
+                            faProcAddressing
                             (
-                                areaSphericalTensorFields
-                            );
-                            fieldDecomposer.decomposeFields
-                            (
-                                areaSymmTensorFields
+                                procFvMesh,
+                                "edgeProcAddressing",
+                                runTime.constant()
                             );
-                            fieldDecomposer.decomposeFields(areaTensorFields);
+                        const auto& edgeProcAddressing = *tedgeProcAddr;
 
-                            fieldDecomposer.decomposeFields(edgeScalarFields);
-                        }
+                        faFieldDecomposer fieldDecomposer
+                        (
+                            aMesh,
+                            procMesh,
+                            edgeProcAddressing,
+                            faceProcAddressing,
+                            boundaryProcAddressing
+                        );
+
+                        fieldDecomposer.decomposeFields(areaScalarFields);
+                        fieldDecomposer.decomposeFields(areaVectorFields);
+                        fieldDecomposer.decomposeFields
+                        (
+                            areaSphericalTensorFields
+                        );
+                        fieldDecomposer.decomposeFields(areaSymmTensorFields);
+                        fieldDecomposer.decomposeFields(areaTensorFields);
+
+                        fieldDecomposer.decomposeFields(edgeScalarFields);
                     }
                 }
             }
diff --git a/applications/utilities/parallelProcessing/decomposePar/domainDecomposition.C b/applications/utilities/parallelProcessing/decomposePar/domainDecomposition.C
index 40efd741514c9c903bc8a273c8e99d918b50ef26..f3e7c2dfc7ff682b14e46c54a648807da89972ca 100644
--- a/applications/utilities/parallelProcessing/decomposePar/domainDecomposition.C
+++ b/applications/utilities/parallelProcessing/decomposePar/domainDecomposition.C
@@ -120,16 +120,27 @@ Foam::domainDecomposition::domainDecomposition
     procProcessorPatchSubPatchIDs_(nProcs_),
     procProcessorPatchSubPatchStarts_(nProcs_)
 {
-    decompositionModel::New
-    (
-        *this,
-        decompDictFile
-    ).readIfPresent("distributed", distributed_);
+    updateParameters(this->model());
 }
 
 
 // * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //
 
+const Foam::decompositionModel& Foam::domainDecomposition::model() const
+{
+    return decompositionModel::New(*this, decompDictFile_);
+}
+
+
+void Foam::domainDecomposition::updateParameters
+(
+    const dictionary& params
+)
+{
+    params.readIfPresent("distributed", distributed_);
+}
+
+
 bool Foam::domainDecomposition::writeDecomposition(const bool decomposeSets)
 {
     Info<< "\nConstructing processor meshes" << endl;
@@ -408,10 +419,9 @@ bool Foam::domainDecomposition::writeDecomposition(const bool decomposeSets)
             nInterProcPatches += curSubPatchIDs[procPatchi].size();
         }
 
-        List<polyPatch*> procPatches
+        PtrList<polyPatch> procPatches
         (
-            curPatchSizes.size() + nInterProcPatches,
-            reinterpret_cast<polyPatch*>(0)
+            curPatchSizes.size() + nInterProcPatches
         );
 
         label nPatches = 0;
@@ -434,13 +444,17 @@ bool Foam::domainDecomposition::writeDecomposition(const bool decomposeSets)
             );
 
             // Map existing patches
-            procPatches[nPatches] = meshPatch.clone
+            procPatches.set
             (
-                procMesh.boundaryMesh(),
                 nPatches,
-                patchMapper.directAddressing(),
-                curPatchStarts[patchi]
-            ).ptr();
+                meshPatch.clone
+                (
+                    procMesh.boundaryMesh(),
+                    nPatches,
+                    patchMapper.directAddressing(),
+                    curPatchStarts[patchi]
+                )
+            );
 
             nPatches++;
         }
@@ -464,7 +478,9 @@ bool Foam::domainDecomposition::writeDecomposition(const bool decomposeSets)
                 if (subPatchID[i] == -1)
                 {
                     // From internal faces
-                    procPatches[nPatches] =
+                    procPatches.set
+                    (
+                        nPatches,
                         new processorPolyPatch
                         (
                             size,
@@ -473,7 +489,8 @@ bool Foam::domainDecomposition::writeDecomposition(const bool decomposeSets)
                             procMesh.boundaryMesh(),
                             proci,
                             curNeighbourProcessors[procPatchi]
-                        );
+                        )
+                    );
                 }
                 else
                 {
@@ -483,7 +500,9 @@ bool Foam::domainDecomposition::writeDecomposition(const bool decomposeSets)
                               boundaryMesh()[subPatchID[i]]
                           );
 
-                    procPatches[nPatches] =
+                    procPatches.set
+                    (
+                        nPatches,
                         new processorCyclicPolyPatch
                         (
                             size,
@@ -494,12 +513,12 @@ bool Foam::domainDecomposition::writeDecomposition(const bool decomposeSets)
                             curNeighbourProcessors[procPatchi],
                             pcPatch.name(),
                             pcPatch.transform()
-                        );
+                        )
+                    );
                 }
 
                 curStart += size;
-
-                nPatches++;
+                ++nPatches;
             }
         }
 
diff --git a/applications/utilities/parallelProcessing/decomposePar/domainDecompositionDistribute.C b/applications/utilities/parallelProcessing/decomposePar/domainDecompositionDistribute.C
index aedbbb34e59d5d557d0184c939c4a7c35a4c8a2d..a448982b1f681126e000aaf91b5542af39eb17ac 100644
--- a/applications/utilities/parallelProcessing/decomposePar/domainDecompositionDistribute.C
+++ b/applications/utilities/parallelProcessing/decomposePar/domainDecompositionDistribute.C
@@ -26,12 +26,8 @@ License
 \*---------------------------------------------------------------------------*/
 
 #include "domainDecomposition.H"
-#include "decompositionMethod.H"
 #include "cpuTime.H"
-#include "cellSet.H"
-#include "regionSplit.H"
-#include "Tuple2.H"
-#include "faceSet.H"
+#include "decompositionMethod.H"
 #include "decompositionModel.H"
 
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
diff --git a/applications/utilities/parallelProcessing/decomposePar/faFieldDecomposer.C b/applications/utilities/parallelProcessing/decomposePar/faFieldDecomposer.C
deleted file mode 100644
index 2ee76091d3e757214371c93c4832b560d90cdf56..0000000000000000000000000000000000000000
--- a/applications/utilities/parallelProcessing/decomposePar/faFieldDecomposer.C
+++ /dev/null
@@ -1,238 +0,0 @@
-/*---------------------------------------------------------------------------*\
-  =========                 |
-  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
-   \\    /   O peration     |
-    \\  /    A nd           | www.openfoam.com
-     \\/     M anipulation  |
--------------------------------------------------------------------------------
-    Copyright (C) 2016-2017 Wikki 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 "faFieldDecomposer.H"
-
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
-
-namespace Foam
-{
-
-// * * * * * * * * * * * * * * * * Constructors  * * * * * * * * * * * * * * //
-
-faFieldDecomposer::patchFieldDecomposer::patchFieldDecomposer
-(
-    const label sizeBeforeMapping,
-    const labelUList& addressingSlice,
-    const label addressingOffset
-)
-:
-    sizeBeforeMapping_(sizeBeforeMapping),
-    directAddressing_(addressingSlice)
-{
-    forAll(directAddressing_, i)
-    {
-        // Subtract one to align addressing.
-        // directAddressing_[i] -= addressingOffset + 1;
-        // ZT, 12/Nov/2010
-        directAddressing_[i] -= addressingOffset;
-    }
-}
-
-
-faFieldDecomposer::processorAreaPatchFieldDecomposer::
-processorAreaPatchFieldDecomposer
-(
-    const faMesh& mesh,
-    const labelUList& addressingSlice
-)
-:
-    sizeBeforeMapping_(mesh.nFaces()),
-    addressing_(addressingSlice.size()),
-    weights_(addressingSlice.size())
-{
-    const scalarField& weights = mesh.weights().internalField();
-    const labelList& own = mesh.edgeOwner();
-    const labelList& neighb = mesh.edgeNeighbour();
-
-    forAll(addressing_, i)
-    {
-        // Subtract one to align addressing.
-        label ai = addressingSlice[i];
-//         label ai = mag(addressingSlice[i]) - 1;
-
-        if (ai < neighb.size())
-        {
-            // This is a regular edge. it has been an internal edge
-            // of the original mesh and now it has become a edge
-            // on the parallel boundary
-            addressing_[i].setSize(2);
-            weights_[i].setSize(2);
-
-            addressing_[i][0] = own[ai];
-            addressing_[i][1] = neighb[ai];
-
-            weights_[i][0] = weights[ai];
-            weights_[i][1] = 1.0 - weights[ai];
-        }
-        else
-        {
-            // This is a edge that used to be on a cyclic boundary
-            // but has now become a parallel patch edge. I cannot
-            // do the interpolation properly (I would need to look
-            // up the different (edge) list of data), so I will
-            // just grab the value from the owner face
-            //
-            addressing_[i].setSize(1);
-            weights_[i].setSize(1);
-
-            addressing_[i][0] = own[ai];
-
-            weights_[i][0] = 1.0;
-        }
-    }
-}
-
-
-faFieldDecomposer::processorEdgePatchFieldDecomposer::
-processorEdgePatchFieldDecomposer
-(
-    label sizeBeforeMapping,
-    const labelUList& addressingSlice
-)
-:
-    sizeBeforeMapping_(sizeBeforeMapping),
-    addressing_(addressingSlice.size()),
-    weights_(addressingSlice.size())
-{
-    forAll(addressing_, i)
-    {
-        addressing_[i].setSize(1);
-        weights_[i].setSize(1);
-
-        addressing_[i][0] = mag(addressingSlice[i]) - 1;
-        weights_[i][0] = sign(addressingSlice[i]);
-    }
-}
-
-
-faFieldDecomposer::faFieldDecomposer
-(
-    const faMesh& completeMesh,
-    const faMesh& procMesh,
-    const labelList& edgeAddressing,
-    const labelList& faceAddressing,
-    const labelList& boundaryAddressing
-)
-:
-    completeMesh_(completeMesh),
-    procMesh_(procMesh),
-    edgeAddressing_(edgeAddressing),
-    faceAddressing_(faceAddressing),
-    boundaryAddressing_(boundaryAddressing),
-    patchFieldDecomposerPtrs_
-    (
-        procMesh_.boundary().size(),
-        static_cast<patchFieldDecomposer*>(NULL)
-    ),
-    processorAreaPatchFieldDecomposerPtrs_
-    (
-        procMesh_.boundary().size(),
-        static_cast<processorAreaPatchFieldDecomposer*>(NULL)
-    ),
-    processorEdgePatchFieldDecomposerPtrs_
-    (
-        procMesh_.boundary().size(),
-        static_cast<processorEdgePatchFieldDecomposer*>(NULL)
-    )
-{
-    forAll(boundaryAddressing_, patchi)
-    {
-        if (boundaryAddressing_[patchi] >= 0)
-        {
-            patchFieldDecomposerPtrs_[patchi] = new patchFieldDecomposer
-            (
-                completeMesh_.boundary()[boundaryAddressing_[patchi]].size(),
-                procMesh_.boundary()[patchi].patchSlice(edgeAddressing_),
-//                 completeMesh_.boundaryMesh()
-                completeMesh_.boundary()
-                [
-                    boundaryAddressing_[patchi]
-                ].start()
-            );
-        }
-        else
-        {
-            processorAreaPatchFieldDecomposerPtrs_[patchi] =
-                new processorAreaPatchFieldDecomposer
-                (
-                    completeMesh_,
-                    procMesh_.boundary()[patchi].patchSlice(edgeAddressing_)
-                );
-
-            processorEdgePatchFieldDecomposerPtrs_[patchi] =
-                new processorEdgePatchFieldDecomposer
-                (
-                    procMesh_.boundary()[patchi].size(),
-                    static_cast<const labelUList&>
-                    (
-                        procMesh_.boundary()[patchi].patchSlice
-                        (
-                            edgeAddressing_
-                        )
-                    )
-                );
-        }
-    }
-}
-
-
-// * * * * * * * * * * * * * * * * Destructor  * * * * * * * * * * * * * * * //
-
-faFieldDecomposer::~faFieldDecomposer()
-{
-    forAll(patchFieldDecomposerPtrs_, patchi)
-    {
-        if (patchFieldDecomposerPtrs_[patchi])
-        {
-            delete patchFieldDecomposerPtrs_[patchi];
-        }
-    }
-
-    forAll(processorAreaPatchFieldDecomposerPtrs_, patchi)
-    {
-        if (processorAreaPatchFieldDecomposerPtrs_[patchi])
-        {
-            delete processorAreaPatchFieldDecomposerPtrs_[patchi];
-        }
-    }
-
-    forAll(processorEdgePatchFieldDecomposerPtrs_, patchi)
-    {
-        if (processorEdgePatchFieldDecomposerPtrs_[patchi])
-        {
-            delete processorEdgePatchFieldDecomposerPtrs_[patchi];
-        }
-    }
-}
-
-
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
-
-} // End namespace Foam
-
-// ************************************************************************* //
diff --git a/applications/utilities/parallelProcessing/decomposePar/lagrangianFieldDecomposer.H b/applications/utilities/parallelProcessing/decomposePar/lagrangianFieldDecomposer.H
index 31e209c871bab8077901011c6e9875f7b34c8a76..18a1746cb4e00feb0421241df72e6a44bf51df92 100644
--- a/applications/utilities/parallelProcessing/decomposePar/lagrangianFieldDecomposer.H
+++ b/applications/utilities/parallelProcessing/decomposePar/lagrangianFieldDecomposer.H
@@ -31,7 +31,7 @@ Description
 
 SourceFiles
     lagrangianFieldDecomposer.C
-    lagrangianFieldDecomposerDecomposeFields.C
+    lagrangianFieldDecomposerFields.C
 
 \*---------------------------------------------------------------------------*/
 
@@ -84,7 +84,7 @@ public:
         //- Construct from components
         lagrangianFieldDecomposer
         (
-            const polyMesh& mesh,
+            const polyMesh& mesh,       //<! unused
             const polyMesh& procMesh,
             const labelList& faceProcAddressing,
             const labelList& cellProcAddressing,
@@ -156,7 +156,7 @@ public:
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
 
 #ifdef NoRepository
-    #include "lagrangianFieldDecomposerDecomposeFields.C"
+    #include "lagrangianFieldDecomposerFields.C"
 #endif
 
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
diff --git a/applications/utilities/parallelProcessing/decomposePar/lagrangianFieldDecomposerDecomposeFields.C b/applications/utilities/parallelProcessing/decomposePar/lagrangianFieldDecomposerFields.C
similarity index 100%
rename from applications/utilities/parallelProcessing/decomposePar/lagrangianFieldDecomposerDecomposeFields.C
rename to applications/utilities/parallelProcessing/decomposePar/lagrangianFieldDecomposerFields.C
diff --git a/applications/utilities/parallelProcessing/decomposePar/readFields.C b/applications/utilities/parallelProcessing/decomposePar/readFields.C
index 231981bcee1fe41e1a4d994f6d779f991f0d8a6e..1b0c948c9c1507338bfc139e3fb012eb27169e5a 100644
--- a/applications/utilities/parallelProcessing/decomposePar/readFields.C
+++ b/applications/utilities/parallelProcessing/decomposePar/readFields.C
@@ -42,7 +42,7 @@ void Foam::readFields
     typedef GeometricField<Type, PatchField, GeoMesh> GeoField;
 
     // Search list of objects for fields of type GeoField
-    IOobjectList fieldObjects(objects.lookupClass(GeoField::typeName));
+    IOobjectList fieldObjects(objects.lookupClass<GeoField>());
 
     // Remove the cellDist field
     auto iter = fieldObjects.find("cellDist");
@@ -51,12 +51,12 @@ void Foam::readFields
         fieldObjects.erase(iter);
     }
 
-    // Get sorted set of names (different processors might read objects in
-    // different order)
+    // Use sorted set of names
+    // (different processors might read objects in different order)
     const wordList masterNames(fieldObjects.sortedNames());
 
     // Construct the fields
-    fields.setSize(masterNames.size());
+    fields.resize(masterNames.size());
 
     forAll(masterNames, i)
     {
@@ -76,17 +76,14 @@ void Foam::readFields
 )
 {
     // Search list of objects for fields of type GeomField
-    IOobjectList fieldObjects(objects.lookupClass(GeoField::typeName));
+    IOobjectList fieldObjects(objects.lookupClass<GeoField>());
 
-    // Construct the fields
-    fields.setSize(fieldObjects.size());
-
-    // Get sorted set of names (different processors might read objects in
-    // different order)
+    // Use sorted set of names
+    // (different processors might read objects in different order)
     const wordList masterNames(fieldObjects.sortedNames());
 
     // Construct the fields
-    fields.setSize(masterNames.size());
+    fields.resize(masterNames.size());
 
     forAll(masterNames, i)
     {
diff --git a/applications/utilities/parallelProcessing/reconstructPar/Make/files b/applications/utilities/parallelProcessing/reconstructPar/Make/files
index 660641033b0b5ba9bfb380cb125176cc5e285806..37acabe62dbc9c7eb1c485f2ee7df91c734e1bc2 100644
--- a/applications/utilities/parallelProcessing/reconstructPar/Make/files
+++ b/applications/utilities/parallelProcessing/reconstructPar/Make/files
@@ -1,5 +1,3 @@
-processorFaMeshes.C
-faFieldReconstructor.C
 reconstructPar.C
 
 EXE = $(FOAM_APPBIN)/reconstructPar
diff --git a/applications/utilities/parallelProcessing/reconstructPar/Make/options b/applications/utilities/parallelProcessing/reconstructPar/Make/options
index 75f62d17291a44a32bcb0efbb11e542378497eb8..a526d94fbc4eacbf7f58eb18eef3dd4c882d4405 100644
--- a/applications/utilities/parallelProcessing/reconstructPar/Make/options
+++ b/applications/utilities/parallelProcessing/reconstructPar/Make/options
@@ -4,7 +4,8 @@ EXE_INC = \
     -I$(LIB_SRC)/meshTools/lnInclude \
     -I$(LIB_SRC)/lagrangian/basic/lnInclude \
     -I$(LIB_SRC)/dynamicMesh/lnInclude \
-    -I$(LIB_SRC)/parallel/reconstruct/reconstruct/lnInclude
+    -I$(LIB_SRC)/parallel/reconstruct/reconstruct/lnInclude \
+    -I$(LIB_SRC)/parallel/reconstruct/faReconstruct/lnInclude
 
 EXE_LIBS = \
     -lfiniteArea \
@@ -13,4 +14,5 @@ EXE_LIBS = \
     -llagrangian \
     -lgenericPatchFields \
     -ldynamicMesh \
-    -lreconstruct
+    -lreconstruct \
+    -lfaReconstruct
diff --git a/applications/utilities/postProcessing/dataConversion/foamToVTK/writeAreaFields.H b/applications/utilities/postProcessing/dataConversion/foamToVTK/writeAreaFields.H
index 959881e65a45d083c9de5233270576f9fb2b4cd9..9bd2cb8c37c6f0dd479d9fe02669ef51df2b596d 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/bin/tools/CleanFunctions b/bin/tools/CleanFunctions
index d001d44bde72c603c0d07da3a28ed824f9b93b3e..f23d7f05675d5606f380e742812cbefc2f4e832c 100644
--- a/bin/tools/CleanFunctions
+++ b/bin/tools/CleanFunctions
@@ -6,7 +6,7 @@
 #    \\/     M anipulation  |
 #------------------------------------------------------------------------------
 #     Copyright (C) 2011-2016 OpenFOAM Foundation
-#     Copyright (C) 2015-2020 OpenCFD Ltd.
+#     Copyright (C) 2015-2021 OpenCFD Ltd.
 #------------------------------------------------------------------------------
 # License
 #     This file is part of OpenFOAM, distributed under GPL-3.0-or-later.
@@ -138,15 +138,16 @@ cleanCase()
         then
             rm -f polyMesh/blockMeshDict
             echo
-            echo "Warning: not removing constant/polyMesh/ "
-            echo "   it contains a blockMeshDict, which should normally be under system/ instead"
+            echo "Warning: not removing constant/polyMesh/"
+            echo "    It contains a 'blockMeshDict.m4' file."
+            echo "    Relocate the file to system/ to avoid this warning"
             echo
         elif [ -e polyMesh/blockMeshDict ]
         then
             echo
-            echo "Warning: not removing constant/polyMesh/ "
-            echo "   it contains a blockMeshDict, which should normally be under system/ instead"
-            echo
+            echo "Warning: not removing constant/polyMesh/"
+            echo "    It contains a 'blockMeshDict' file."
+            echo "    Relocate the file to system/ to avoid this warning"
         else
             # Remove polyMesh entirely if there is no blockMeshDict
             rm -rf polyMesh
@@ -187,12 +188,26 @@ cleanUcomponents()
 }
 
 
-cleanFaMesh ()
+cleanFaMesh()
 {
-    rm -rf \
-        constant/faMesh/faceLabels* \
-        constant/faMesh/faBoundary* \
-    ;
+    (
+        cd constant 2>/dev/null || exit 0
+
+        # Old constant/polyMesh location for blockMeshDict still in use?
+        # - emit a gentle warning
+        if [ -e faMesh/faMeshDefinition ]
+        then
+            rm -f faMesh/faceLabels* faMesh/faBoundary*
+            echo
+            echo "Warning: not removing the constant/faMesh/ directory"
+            echo "    It contains a 'faMeshDefinition' file"
+            echo "    Relocate the file to system/ to avoid this warning"
+            echo
+        else
+            # Remove faMesh/ entirely if there is no faMeshDefinition
+            rm -rf faMesh
+        fi
+    )
 }
 
 
diff --git a/src/dynamicFaMesh/interfaceTrackingFvMesh/interfaceTrackingFvMesh.C b/src/dynamicFaMesh/interfaceTrackingFvMesh/interfaceTrackingFvMesh.C
index bf90e5220abc223071128c4ee322d9efd72c5194..5e2cb8a3caa3cc3cce2982b1988718819a5fbc41 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/faOptions/faOption/faOption.C b/src/faOptions/faOption/faOption.C
index 708f73885113285098a924d9105967a6391ff50e..9f98da33fe5691a5e9ae556d20a2024ed863d34b 100644
--- a/src/faOptions/faOption/faOption.C
+++ b/src/faOptions/faOption/faOption.C
@@ -5,7 +5,7 @@
     \\  /    A nd           | www.openfoam.com
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
-    Copyright (C) 2019-2020 OpenCFD Ltd.
+    Copyright (C) 2019-2021 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -40,17 +40,6 @@ namespace Foam
 }
 
 
-// * * * * * * * * * * * * Private Member Functions  * * * * * * * * * * * * //
-
-void Foam::fa::option::constructMeshObjects()
-{
-    regionMeshPtr_.reset(new faMesh(mesh_));
-
-    vsmPtr_.reset(new volSurfaceMapping(regionMeshPtr_()));
-}
-
-
-
 // * * * * * * * * * * * * * * * * Constructors  * * * * * * * * * * * * * * //
 
 Foam::fa::option::option
@@ -67,16 +56,15 @@ Foam::fa::option::option
     patch_(patch),
     dict_(dict),
     coeffs_(dict.optionalSubDict(modelType + "Coeffs")),
-    active_(dict.getOrDefault<Switch>("active", true)),
     fieldNames_(),
     applied_(),
     regionName_(dict.get<word>("region")),
     regionMeshPtr_(nullptr),
-    vsmPtr_(nullptr)
+    vsmPtr_(nullptr),
+    active_(dict.getOrDefault("active", true)),
+    log(true)
 {
-    constructMeshObjects();
-
-    Info<< incrIndent << indent << "Source: " << name_ << endl << decrIndent;
+    Log << incrIndent << indent << "Source: " << name_ << endl << decrIndent;
 }
 
 
diff --git a/src/faOptions/faOption/faOption.H b/src/faOptions/faOption/faOption.H
index 253c26e2b1536ab21fb5e0340378cbfb899aaeb7..c8d69e473f999b9de9a202f7fbda15599033aee5 100644
--- a/src/faOptions/faOption/faOption.H
+++ b/src/faOptions/faOption/faOption.H
@@ -5,7 +5,7 @@
     \\  /    A nd           | www.openfoam.com
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
-    Copyright (C) 2019-2020 OpenCFD Ltd.
+    Copyright (C) 2019-2021 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -75,13 +75,12 @@ SourceFiles
 #ifndef faOption_H
 #define faOption_H
 
-#include "faMatrices.H"
-#include "areaFields.H"
+#include "faMatricesFwd.H"
+#include "areaFieldsFwd.H"
 #include "dictionary.H"
-#include "Switch.H"
-#include "runTimeSelectionTables.H"
 #include "fvMesh.H"
 #include "volSurfaceMapping.H"
+#include "runTimeSelectionTables.H"
 
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
 
@@ -124,27 +123,35 @@ protected:
         //- Dictionary containing source coefficients
         dictionary coeffs_;
 
-        //- Source active flag
-        Switch active_;
-
         //- Field names to apply source to - populated by derived models
         wordList fieldNames_;
 
         //- Applied flag list - corresponds to each fieldNames_ entry
         List<bool> applied_;
 
-         //- Region name
+        //- Region name (finite-area)
         word regionName_;
 
-        //- Pointer to the region mesh database
-        autoPtr<faMesh> regionMeshPtr_;
+private:
 
-        //-Volume-to surface mapping
-        autoPtr<volSurfaceMapping> vsmPtr_;
+    // Private Data
+
+        //- Demand-driven: pointer to region mesh database
+        mutable autoPtr<faMesh> regionMeshPtr_;
+
+        //- Demand-driven: volume-to-surface mapping
+        mutable autoPtr<volSurfaceMapping> vsmPtr_;
+
+        //- Source active flag
+        bool active_;
 
 
 public:
 
+    //- Switch write log to Info
+    bool log;
+
+
     //- Runtime type information
     TypeName("option");
 
@@ -188,7 +195,6 @@ public:
         //- on the freestore from an Istream
         class iNew
         {
-
             //- Reference to the patch
             const fvPatch& patch_;
 
@@ -239,37 +245,37 @@ public:
         // Access
 
             //- Return const access to the source name
-            inline const word& name() const;
+            inline const word& name() const noexcept;
 
             //- Return const access to the mesh database
-            inline const fvMesh& mesh() const;
+            inline const fvMesh& mesh() const noexcept;
 
             //- Return const access to fvPatch
-            inline const fvPatch& patch() const;
+            inline const fvPatch& patch() const noexcept;
 
             //- Return dictionary
-            inline const dictionary& coeffs() const;
+            inline const dictionary& coeffs() const noexcept;
 
             //- Return const access to the source active flag
-            inline bool active() const;
+            inline bool active() const noexcept;
 
             //- Set the applied flag to true for field index fieldi
             inline void setApplied(const label fieldi);
 
-            //- Return the region mesh database
+            //- The region name
+            inline const word& regionName() const noexcept;
+
+            //- Return the region mesh database (demand-driven)
             inline const faMesh& regionMesh() const;
 
-             //- Return volSurfaceMapping
+            //- Return volSurfaceMapping (demand-driven)
             inline const volSurfaceMapping& vsm() const;
 
-            //- Region name
-            inline const word& regionName() const;
-
 
         // Edit
 
-            //- Return access to the source active flag
-            inline Switch& active();
+            //- Change source active flag, return previous value
+            inline bool active(const bool on) noexcept;
 
 
         // Checks
diff --git a/src/faOptions/faOption/faOptionI.H b/src/faOptions/faOption/faOptionI.H
index a541123f2752e01909762b7faed3d1420c7cdd2e..42ef920af60712fbbb6d692c4e559006c5a1d5e2 100644
--- a/src/faOptions/faOption/faOptionI.H
+++ b/src/faOptions/faOption/faOptionI.H
@@ -5,7 +5,7 @@
     \\  /    A nd           | www.openfoam.com
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
-    Copyright (C) 2019-2020 OpenCFD Ltd.
+    Copyright (C) 2019-2021 OpenCFD Ltd.
 ------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -27,49 +27,51 @@ License
 
 // * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //
 
-inline const Foam::word& Foam::fa::option::name() const
+inline const Foam::word& Foam::fa::option::name() const noexcept
 {
     return name_;
 }
 
 
-inline const Foam::fvMesh& Foam::fa::option::mesh() const
+inline const Foam::fvMesh& Foam::fa::option::mesh() const noexcept
 {
     return mesh_;
 }
 
 
-inline const Foam::fvPatch& Foam::fa::option::patch() const
+inline const Foam::fvPatch& Foam::fa::option::patch() const noexcept
 {
     return patch_;
 }
 
 
-inline const Foam::dictionary& Foam::fa::option::coeffs() const
+inline const Foam::dictionary& Foam::fa::option::coeffs() const noexcept
 {
     return coeffs_;
 }
 
 
-inline bool Foam::fa::option::active() const
+inline bool Foam::fa::option::active() const noexcept
 {
     return active_;
 }
 
 
-inline void Foam::fa::option::setApplied(const label fieldi)
+inline bool Foam::fa::option::active(const bool on) noexcept
 {
-    applied_[fieldi] = true;
+    bool old(active_);
+    active_ = on;
+    return old;
 }
 
 
-inline Foam::Switch& Foam::fa::option::active()
+inline void Foam::fa::option::setApplied(const label fieldi)
 {
-    return active_;
+    applied_[fieldi] = true;
 }
 
 
-inline const Foam::word& Foam::fa::option::regionName() const
+inline const Foam::word& Foam::fa::option::regionName() const noexcept
 {
     return regionName_;
 }
@@ -77,31 +79,21 @@ inline const Foam::word& Foam::fa::option::regionName() const
 
 inline const Foam::faMesh& Foam::fa::option::regionMesh() const
 {
-    if (regionMeshPtr_.valid())
+    if (!regionMeshPtr_)
     {
-        return regionMeshPtr_();
+        regionMeshPtr_.reset(new faMesh(mesh_));
     }
-    else
-    {
-        FatalErrorInFunction
-            << "Region mesh not available" << abort(FatalError);
-    }
-    return *(new faMesh(mesh_));
+    return *regionMeshPtr_;
 }
 
 
 inline const Foam::volSurfaceMapping& Foam::fa::option::vsm() const
 {
-    if (vsmPtr_.valid())
-    {
-        return vsmPtr_();
-    }
-    else
+    if (!vsmPtr_)
     {
-        FatalErrorInFunction
-            << "vsmPtr not available" << abort(FatalError);
+        vsmPtr_.reset(new volSurfaceMapping(this->regionMesh()));
     }
-    return *(new volSurfaceMapping(regionMeshPtr_()));
+    return *vsmPtr_;
 }
 
 
diff --git a/src/faOptions/faOption/faOptionList.C b/src/faOptions/faOption/faOptionList.C
index 80de3cf682efd2dbed3808cd6dd4c3699f4041a4..b722e457a6f91031ec4e6ca79f3b3fcd5b4ee342 100644
--- a/src/faOptions/faOption/faOptionList.C
+++ b/src/faOptions/faOption/faOptionList.C
@@ -92,7 +92,7 @@ Foam::fa::optionList::optionList
     const dictionary& dict
 )
 :
-    PtrList<option>(),
+    PtrList<fa::option>(),
     mesh_(p.boundaryMesh().mesh()),
     patch_(p),
     checkTimeIndex_(mesh_.time().startTimeIndex() + 2)
@@ -103,7 +103,7 @@ Foam::fa::optionList::optionList
 
 Foam::fa::optionList::optionList(const fvPatch& p)
 :
-    PtrList<option>(),
+    PtrList<fa::option>(),
     mesh_(p.boundaryMesh().mesh()),
     patch_(p),
     checkTimeIndex_(mesh_.time().startTimeIndex() + 2)
diff --git a/src/faOptions/faOption/faOptionList.H b/src/faOptions/faOption/faOptionList.H
index 290dc794a6596c960307fee872f9109fec70bad2..a479c9719667fea3bebc34f301462b6db50bfd72 100644
--- a/src/faOptions/faOption/faOptionList.H
+++ b/src/faOptions/faOption/faOptionList.H
@@ -46,10 +46,9 @@ SourceFile
 
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
 
+// Forward Declarations
 namespace Foam
 {
-
-// Forward declaration of friend functions and operators
 namespace fa
 {
     class optionList;
@@ -66,7 +65,7 @@ namespace fa
 
 class optionList
 :
-    public PtrList<option>
+    public PtrList<fa::option>
 {
 protected:
 
@@ -118,7 +117,7 @@ public:
 
     // Constructors
 
-        //- Construct null
+        //- Construct from patch
         optionList(const fvPatch& p);
 
         //- Construct from mesh and dictionary
@@ -126,8 +125,7 @@ public:
 
 
     //- Destructor
-    virtual ~optionList()
-    {}
+    virtual ~optionList() = default;
 
 
     // Member Functions
diff --git a/src/faOptions/faOption/faOptionListTemplates.C b/src/faOptions/faOption/faOptionListTemplates.C
index 1a954bcce453918c6aad227c2aba433930bd5001..96a266e5a38105d20979dbfbf8f88d55b4e5c5ac 100644
--- a/src/faOptions/faOption/faOptionListTemplates.C
+++ b/src/faOptions/faOption/faOptionListTemplates.C
@@ -26,6 +26,7 @@ License
 \*---------------------------------------------------------------------------*/
 
 #include "profiling.H"
+#include "areaFields.H"
 
 // * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //
 
diff --git a/src/faOptions/faceSetOption/faceSetOption.H b/src/faOptions/faceSetOption/faceSetOption.H
index edbf26ed53b90f7b6b9a29da625338ca722dd07c..559cacdae264fa444b8d29ca4818081b3fd843c3 100644
--- a/src/faOptions/faceSetOption/faceSetOption.H
+++ b/src/faOptions/faceSetOption/faceSetOption.H
@@ -5,7 +5,7 @@
     \\  /    A nd           | www.openfoam.com
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
-    Copyright (C) 2019-2020 OpenCFD Ltd.
+    Copyright (C) 2019-2021 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -102,7 +102,7 @@ namespace fa
 
 class faceSetOption
 :
-    public option
+    public fa::option
 {
 public:
 
diff --git a/src/faOptions/sources/derived/contactHeatFluxSource/contactHeatFluxSource.H b/src/faOptions/sources/derived/contactHeatFluxSource/contactHeatFluxSource.H
index 699c5214c0c52407e96d3d9ebbbc1b1c0c9c9c24..b5aeb34bb39ddf51ddac2520bc73832d5b3520cb 100644
--- a/src/faOptions/sources/derived/contactHeatFluxSource/contactHeatFluxSource.H
+++ b/src/faOptions/sources/derived/contactHeatFluxSource/contactHeatFluxSource.H
@@ -5,7 +5,7 @@
     \\  /    A nd           | www.openfoam.com
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
-    Copyright (C) 2019-2020 OpenCFD Ltd.
+    Copyright (C) 2019-2021 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -98,7 +98,7 @@ namespace fa
 
 class contactHeatFluxSource
 :
-    public faceSetOption,
+    public fa::faceSetOption,
     public temperatureCoupledBase
 {
     // Private Data
diff --git a/src/faOptions/sources/derived/externalFileSource/externalFileSource.C b/src/faOptions/sources/derived/externalFileSource/externalFileSource.C
index f5a95479d23288d4df5861a6b943fcbbd405d045..81e93caa40eae7e80f831d807f34bacf220e1c1b 100644
--- a/src/faOptions/sources/derived/externalFileSource/externalFileSource.C
+++ b/src/faOptions/sources/derived/externalFileSource/externalFileSource.C
@@ -5,7 +5,7 @@
     \\  /    A nd           | www.openfoam.com
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
-    Copyright (C) 2019-2020 OpenCFD Ltd.
+    Copyright (C) 2019-2021 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -26,8 +26,8 @@ License
 \*---------------------------------------------------------------------------*/
 
 #include "externalFileSource.H"
-#include "faMatrices.H"
-#include "faCFD.H"
+#include "fam.H"
+#include "faScalarMatrix.H"
 #include "zeroGradientFaPatchFields.H"
 #include "addToRunTimeSelectionTable.H"
 
diff --git a/src/faOptions/sources/derived/externalFileSource/externalFileSource.H b/src/faOptions/sources/derived/externalFileSource/externalFileSource.H
index 6d6c0ec2dadfd0feed004bf0a6808d07a1709959..974f23925a100a23fe66c1796aa5d0c983c018ab 100644
--- a/src/faOptions/sources/derived/externalFileSource/externalFileSource.H
+++ b/src/faOptions/sources/derived/externalFileSource/externalFileSource.H
@@ -5,7 +5,7 @@
     \\  /    A nd           | www.openfoam.com
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
-    Copyright (C) 2019-2020 OpenCFD Ltd.
+    Copyright (C) 2019-2021 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -89,7 +89,7 @@ namespace fa
 
 class externalFileSource
 :
-    public faceSetOption
+    public fa::faceSetOption
 {
     // Private Data
 
diff --git a/src/faOptions/sources/derived/externalHeatFluxSource/externalHeatFluxSource.C b/src/faOptions/sources/derived/externalHeatFluxSource/externalHeatFluxSource.C
index 478fc81b5cecb3f82c4b78f4e05cf0ded6076f02..22c8404e198428af53acfc82d9ebccfb070b372a 100644
--- a/src/faOptions/sources/derived/externalHeatFluxSource/externalHeatFluxSource.C
+++ b/src/faOptions/sources/derived/externalHeatFluxSource/externalHeatFluxSource.C
@@ -26,7 +26,8 @@ License
 \*---------------------------------------------------------------------------*/
 
 #include "externalHeatFluxSource.H"
-#include "addToRunTimeSelectionTable.H"
+#include "fam.H"
+#include "faScalarMatrix.H"
 #include "physicoChemicalConstants.H"
 #include "zeroGradientFaPatchFields.H"
 #include "addToRunTimeSelectionTable.H"
diff --git a/src/faOptions/sources/derived/externalHeatFluxSource/externalHeatFluxSource.H b/src/faOptions/sources/derived/externalHeatFluxSource/externalHeatFluxSource.H
index 97873b29ffd9fbd0178c9c12e896960646424624..26e8385276e7a47a3334c74ff4cc2d72025cd68a 100644
--- a/src/faOptions/sources/derived/externalHeatFluxSource/externalHeatFluxSource.H
+++ b/src/faOptions/sources/derived/externalHeatFluxSource/externalHeatFluxSource.H
@@ -121,7 +121,6 @@ SourceFiles
 #include "Function1.H"
 #include "areaFields.H"
 #include "faceSetOption.H"
-#include "faCFD.H"
 
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
 
@@ -136,7 +135,7 @@ namespace fa
 
 class externalHeatFluxSource
 :
-    public faceSetOption
+    public fa::faceSetOption
 {
 public:
 
diff --git a/src/faOptions/sources/derived/jouleHeatingSource/jouleHeatingSource.C b/src/faOptions/sources/derived/jouleHeatingSource/jouleHeatingSource.C
index 2d9cfc63218bb8a7993052feb939d735caa5d8e8..a752b74eff6e023ebf017e61a149c28a141ba05b 100644
--- a/src/faOptions/sources/derived/jouleHeatingSource/jouleHeatingSource.C
+++ b/src/faOptions/sources/derived/jouleHeatingSource/jouleHeatingSource.C
@@ -26,8 +26,8 @@ License
 \*---------------------------------------------------------------------------*/
 
 #include "jouleHeatingSource.H"
-#include "faMatrices.H"
-#include "faCFD.H"
+#include "fam.H"
+#include "faScalarMatrix.H"
 #include "addToRunTimeSelectionTable.H"
 
 // * * * * * * * * * * * * * Static Member Functions * * * * * * * * * * * * //
diff --git a/src/faOptions/sources/derived/jouleHeatingSource/jouleHeatingSource.H b/src/faOptions/sources/derived/jouleHeatingSource/jouleHeatingSource.H
index 4a5a279a145362059d9906fdd3dd5fc1f647c723..e3c99f68d1bc1aa0ea2384b56241433f325f362d 100644
--- a/src/faOptions/sources/derived/jouleHeatingSource/jouleHeatingSource.H
+++ b/src/faOptions/sources/derived/jouleHeatingSource/jouleHeatingSource.H
@@ -5,7 +5,7 @@
     \\  /    A nd           | www.openfoam.com
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
-    Copyright (C) 2019-2020 OpenCFD Ltd.
+    Copyright (C) 2019-2021 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -156,7 +156,7 @@ namespace fa
 
 class jouleHeatingSource
 :
-    public faceSetOption
+    public fa::faceSetOption
 {
     // Private Data
 
diff --git a/src/finiteArea/Make/files b/src/finiteArea/Make/files
index e21879123296469a793dc1f06c1320c4b4164fcb..7a96d8c4a1ef5c25e0f3b7611f220daccd7c9d14 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/faMatrices/faMatricesFwd.H b/src/finiteArea/faMatrices/faMatricesFwd.H
new file mode 100644
index 0000000000000000000000000000000000000000..7383f960d0e0a09ddc95b995d33d85c11078c0a3
--- /dev/null
+++ b/src/finiteArea/faMatrices/faMatricesFwd.H
@@ -0,0 +1,59 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  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/>.
+
+Description
+    Forward declarations of standard faMatrix types/specializations.
+
+\*---------------------------------------------------------------------------*/
+
+#ifndef faMatricesFwd_H
+#define faMatricesFwd_H
+
+#include "fieldTypes.H"
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+namespace Foam
+{
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+template<class Type> class faMatrix;
+
+typedef faMatrix<scalar> faScalarMatrix;
+typedef faMatrix<vector> faVectorMatrix;
+typedef faMatrix<sphericalTensor> faSphericalTensorMatrix;
+typedef faMatrix<symmTensor> faSymmTensorMatrix;
+typedef faMatrix<tensor> faTensorMatrix;
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+} // End namespace Foam
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+#endif
+
+// ************************************************************************* //
diff --git a/src/finiteArea/faMesh/faBoundaryMesh/faBoundaryMesh.C b/src/finiteArea/faMesh/faBoundaryMesh/faBoundaryMesh.C
index b436ecf9962bb54bac90a2d2e7d62d3774cc979c..da7962e51b101bb62f0f06704f188d47bc05d6c0 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 4c2c69ead5b3f234234dd76bff74e694f1181d32..397719aedf1ad492ad327e7e0dffa08f8c245185 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 1e06c4cc03c57ee7f76940574a87e70193d52ebe..d53be6060464c32cc5fbebd450c88df3ab00544f 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 8715a22f010b1f84c72cac3bbb014157eda06aef..8a9b16501181a82973c33de49a4e98f833713a46 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,11 @@ Author
 namespace Foam
 {
 
-// Class forward declarations
+// Forward Declarations
 class faMeshLduAddressing;
 class faMeshMapper;
+class faPatchData;
+template<class T> class LabelledItem;
 
 /*---------------------------------------------------------------------------*\
                            Class faMesh Declaration
@@ -77,7 +79,6 @@ class faMeshMapper;
 
 class faMesh
 :
-    public GeoMesh<polyMesh>,
     public MeshObject<polyMesh, Foam::UpdateableMeshObject, faMesh>,
     public lduMesh,
     public edgeInterpolation,
@@ -85,7 +86,7 @@ class faMesh
     public faSolution,
     public data
 {
-    // Private data
+    // Private Data
 
         //- Face labels
         labelIOList faceLabels_;
@@ -130,7 +131,7 @@ class faMesh
     // Demand-driven data
 
         //- Primitive patch
-        mutable indirectPrimitivePatch* patchPtr_;
+        mutable uindirectPrimitivePatch* patchPtr_;
 
         //- Ldu addressing data
         mutable faMeshLduAddressing* lduPtr_;
@@ -186,8 +187,8 @@ class faMesh
 
         // Other mesh-related data
 
-            //- Parallel info
-            mutable faGlobalMeshData* globalMeshDataPtr_;
+        //- Parallel info
+        mutable autoPtr<faGlobalMeshData> globalMeshDataPtr_;
 
 
     // Static Private Data
@@ -204,6 +205,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 +265,39 @@ class faMesh
             //- Clear demand-driven data
             void clearOut() const;
 
+
+    // Helpers
+
+        //- Get the polyPatch pairs for the boundary edges (natural order)
+        List<LabelledItem<edge>> getBoundaryEdgePatchPairs() const;
+
+        //- 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 List<LabelledItem<edge>>& bndEdgePatchPairs
+        ) const;
+
+
 public:
 
-    // Public typedefs
+    // Public Typedefs
 
         typedef faMesh Mesh;
         typedef faBoundaryMesh BoundaryMesh;
@@ -273,36 +306,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 +347,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 +387,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 +449,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 +480,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 +514,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 +573,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 +592,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 7a363fd2ab6b9a84d34bc18d6d3b1ac1d85cc234..63ec9815bdc7ac037e5bbec5d081ef185f9962d4 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();
 }
 
 
@@ -1168,7 +1137,7 @@ void Foam::faMesh::calcPointAreaNormals() const
 
         if (correctPatchPointNormals(patchI) && !fap.coupled())
         {
-            if (fap.ngbPolyPatchIndex() == -1)
+            if (fap.ngbPolyPatchIndex() < 0)
             {
                 FatalErrorInFunction
                     << "Neighbour polyPatch index is not defined "
diff --git a/src/finiteArea/faMesh/faMeshI.H b/src/finiteArea/faMesh/faMeshI.H
new file mode 100644
index 0000000000000000000000000000000000000000..a3d79328606337f6c291467b5bec28d6ce0e0346
--- /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 0000000000000000000000000000000000000000..889bdd7826c301813a9eb69d7d4b2aa3fb7c023d
--- /dev/null
+++ b/src/finiteArea/faMesh/faMeshPatches.C
@@ -0,0 +1,954 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  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 "globalMeshData.H"
+#include "indirectPrimitivePatch.H"
+#include "edgeHashes.H"
+#include "LabelledItem.H"
+
+// * * * * * * * * * * * * * * * Local Functions * * * * * * * * * * * * * * //
+
+namespace Foam
+{
+
+// Manage patch pairs with a 'labelled' edge.
+// The edge first/second correspond to the owner/neighbour patches.
+// The index is a face index on the neighbour patch (FUTURE).
+
+// Local typedefs
+
+typedef LabelledItem<edge> patchPairInfo;
+typedef List<patchPairInfo> patchPairInfoList;
+typedef UIndirectList<patchPairInfo> patchPairInfoUIndList;
+
+
+// Handling of dangling coupled edges.
+// Tag values to "push" with special -(patchId+2)
+struct combineDanglingEdge
+{
+    const label upperLimit;
+
+    // Set dangling patchId from real patchId
+    static void setDangling(patchPairInfo& pairing, const label patchId)
+    {
+        pairing.first() = pairing.second() = -(patchId + 2);
+        pairing.setIndex(-1);  // Invalidate
+    }
+
+    // Convert dangling patchId to real patchId
+    static void correct(patchPairInfo& pairing)
+    {
+        if (pairing.first() < -1)
+        {
+            pairing.first() = -(pairing.first() + 2);
+        }
+        if (pairing.second() < -1)
+        {
+            pairing.second() = -(pairing.second() + 2);
+        }
+    }
+
+    //- Construct with upper limit (the number of non-processor patches)
+    explicit combineDanglingEdge(const label nNonProcessor)
+    :
+        upperLimit(nNonProcessor)
+    {}
+
+
+    // Combine operation: overwrite unused or processor patches with
+    // 'dangling' patch information only
+    void operator()(patchPairInfo& x, const patchPairInfo& y) const
+    {
+        if (y.first() < -1 && edge::compare(x, y) == 0)
+        {
+            if (x.first() == -1 || x.first() >= upperLimit)
+            {
+                x.first() = y.first();
+            }
+            if (x.second() == -1 || x.second() >= upperLimit)
+            {
+                x.second() = y.first();
+                x.index() = y.index();
+            }
+        }
+    }
+};
+
+
+// Populate patch pairings according to the boundary edges
+void findEdgePatchPairing
+(
+    const polyBoundaryMesh& pbm,
+    const EdgeMap<label>& edgeToIndex,
+    patchPairInfoList& patchPairs,
+    label nMissing = -1
+)
+{
+    // Count how many slots (both sides) to be filled
+    if (nMissing < 0)
+    {
+        nMissing = 0;
+        for (const patchPairInfo& pairing : patchPairs)
+        {
+            if (pairing.first() == -1) ++nMissing;
+            if (pairing.second() == -1) ++nMissing;
+        }
+    }
+
+    forAll(pbm, patchID)
+    {
+        if (!nMissing) break;  // Everything filled
+
+        const polyPatch& pp = pbm[patchID];
+
+        const bool isProcPatch = isA<processorPolyPatch>(pp);
+
+        // Examine neighbour boundary edges
+        for (label edgei = pp.nInternalEdges(); edgei < pp.nEdges(); ++edgei)
+        {
+            // Lookup global edge of this neighbour,
+            // find matching owner boundary edge
+
+            const label edgeIndex =
+                edgeToIndex.lookup(pp.meshEdge(edgei), -1);
+
+            if (edgeIndex != -1)
+            {
+                // Add patchId.
+                // - hash-like so will only insert once
+                // - also add in the attached patch face (there is only one),
+                //   saved in mesh face numbering for processor patches
+
+                patchPairInfo& pairing = patchPairs[edgeIndex];
+
+                if (pairing.insert(patchID))
+                {
+                    if (isProcPatch)
+                    {
+                        // Save the mesh face
+
+                        const label patchFacei = pp.edgeFaces()[edgei][0];
+
+                        pairing.index() = (pp.start() + patchFacei);
+                    }
+
+                    --nMissing;
+
+                    if (!nMissing) break;  // Early exit
+                }
+            }
+        }
+    }
+}
+
+} // End namespace Foam
+
+
+// * * * * * * * * * * * * * Private Member Functions  * * * * * * * * * * * //
+
+void Foam::faMesh::reorderProcEdges
+(
+    faPatchData& patchDef,
+    const List<LabelledItem<edge>>& bndEdgePatchPairs
+) const
+{
+    if (!patchDef.coupled() || patchDef.edgeLabels_.empty())
+    {
+        return;
+    }
+
+    const polyBoundaryMesh& pbm = mesh().boundaryMesh();
+
+    const label procPatchID = patchDef.neighPolyPatchId_;
+
+    const auto* procPatch = isA<processorPolyPatch>(pbm[procPatchID]);
+
+    if (!procPatch)
+    {
+        FatalErrorInFunction
+            << "Internal addressing error. Patch " << procPatchID
+            << " is not a processor patch" << nl
+            << abort(FatalError);
+    }
+
+    // Reorder processor edges using order of neighbour processorPolyPatch
+
+    const label nProcEdges = patchDef.edgeLabels_.size();
+
+    labelList procFaces(nProcEdges, -1);
+
+    forAll(procFaces, edgei)
+    {
+        const label bndEdgei =
+            (patchDef.edgeLabels_[edgei] - patch().nInternalEdges());
+
+        procFaces[edgei] = bndEdgePatchPairs[bndEdgei].index();
+    }
+
+    // Ascending proc-face numbering
+    const labelList sortIndices(Foam::sortedOrder(procFaces));
+
+    if (procFaces.size() && procFaces[sortIndices[0]] < 0)
+    {
+        FatalErrorInFunction
+            << "Internal addressing error. Patch " << procPatchID
+            << " with negative face" << nl
+            << abort(FatalError);
+    }
+
+
+    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 meshFacei = procFaces[sortIndices[edgei]];
+
+        // Find all identical faces
+        label endEdgei = edgei + 1;  // one beyond
+        while
+        (
+            (endEdgei < nProcEdges)
+         && (meshFacei == 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 patch local edgeId
+            for (label i = edgei; i < endEdgei; ++i)
+            {
+                const label patchEdgei = oldEdgeLabels[sortIndices[i]];
+
+                // The edge in mesh numbering
+                multihit.insert
+                (
+                    patch().meshEdge(patchEdgei),
+                    patchEdgei
+                );
+            }
+
+            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()[meshFacei];
+
+            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: " << meshFacei
+                    << " ... indicates serious geometry issue" << nl
+                    << multihit << nl
+                    << abort(FatalError);
+            }
+            if (!multihit.empty())
+            {
+                FatalErrorInFunction
+                    << "Missed edges for face: " << meshFacei
+                    << " ... 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,
+        word::null,     // Name for empty patch placeholder
+        &onePatchDict   // Definitions for defaultPatch
+    );
+}
+
+
+Foam::List<Foam::LabelledItem<Foam::edge>>
+Foam::faMesh::getBoundaryEdgePatchPairs() const
+{
+    const polyBoundaryMesh& pbm = mesh().boundaryMesh();
+
+    const label nInternalEdges = patch().nInternalEdges();
+    const label nBoundaryEdges = patch().nBoundaryEdges();
+
+    // Map edges (mesh numbering) back to a boundary index
+    EdgeMap<label> edgeToBoundaryIndex(2*nBoundaryEdges);
+
+    // Use labelled 'edge' for accounting of patch pairs
+    patchPairInfoList bndEdgePatchPairs(nBoundaryEdges);
+
+
+    // Pass 1:
+    // - setup lookup (edge -> bnd index)
+    // - add owner patch for each boundary edge
+    {
+        const SubList<labelList> bndEdgeToPatchFace
+        (
+            patch().edgeFaces(),
+            patch().nBoundaryEdges(),
+            patch().nInternalEdges()
+        );
+
+        for (label bndEdgei = 0; bndEdgei < nBoundaryEdges; ++bndEdgei)
+        {
+            edgeToBoundaryIndex.insert
+            (
+                patch().meshEdge(bndEdgei + nInternalEdges),
+                edgeToBoundaryIndex.size()
+            );
+
+            // The attached patch face (there is only one):
+            const label patchFacei = bndEdgeToPatchFace[bndEdgei][0];
+
+            const label meshFacei = faceLabels_[patchFacei];
+
+            const label patchId = pbm.whichPatch(meshFacei);
+
+            bndEdgePatchPairs[bndEdgei].insert(patchId);
+        }
+    }
+
+    // Pass 2:
+    // - Add in first neighbour patch for the boundary edges
+    // - examine all possible connecting neighbours
+
+    findEdgePatchPairing
+    (
+        pbm,
+        edgeToBoundaryIndex,
+        bndEdgePatchPairs,
+        edgeToBoundaryIndex.size()  // Number of places to still fill
+    );
+
+
+    // Nothing dangling if running in serial - can return already
+    if (!Pstream::parRun())
+    {
+        return bndEdgePatchPairs;
+    }
+
+    // In parallel need to check for "dangling" edges, which are finiteArea
+    // boundary edges that only exist on one side of a proc boundary.
+    // Eg, proc boundary coincides with the outer extent of the finiteArea
+
+    const globalMeshData& globalData = mesh().globalData();
+    const indirectPrimitivePatch& cpp = globalData.coupledPatch();
+    const mapDistribute& map = globalData.globalEdgeSlavesMap();
+
+    // Construct coupled edge usage with all data
+    List<unsigned char> coupledEdgesUsed(map.constructSize(), 0u);
+
+    forAll(cpp.edges(), coupledEdgei)
+    {
+        const auto iter =
+            edgeToBoundaryIndex.cfind(cpp.meshEdge(coupledEdgei));
+
+        // Used from this side or other other side
+        coupledEdgesUsed[coupledEdgei] = (iter.found() ? 1 : 2);
+    }
+
+    // Save the original (pre-sync) coupling state
+    const List<unsigned char> coupledEdgesOrig(coupledEdgesUsed);
+
+    globalData.syncData
+    (
+        coupledEdgesUsed,
+        globalData.globalEdgeSlaves(),
+        globalData.globalEdgeTransformedSlaves(),  // probably not used
+        map,
+        bitOrEqOp<unsigned char>()
+    );
+
+
+    // Check for one-sided edge coupling (coupled value == 3)
+    // original == 1:
+    // - coupled to a real finiteArea edge.
+    // - receive a patch-pair value
+    //
+    // original == 2:
+    // - a "dangled" edge. Information required for other procs.
+    // - push a patch-pair value
+
+    // Map edges (mesh numbering) back to a coupled index.
+    // These are the edges to 'push' information for.
+
+    EdgeMap<label> edgeToCoupledIndex;
+
+    label nEdgesPull = 0;
+
+    forAll(coupledEdgesUsed, coupledEdgei)
+    {
+        if (coupledEdgesUsed[coupledEdgei] == 3)
+        {
+            if (coupledEdgesOrig[coupledEdgei] == 1)
+            {
+                // Coupled side with finiteArea
+                ++nEdgesPull;
+            }
+            else if (coupledEdgesOrig[coupledEdgei] == 2)
+            {
+                // Coupled side without finiteArea
+                edgeToCoupledIndex.insert
+                (
+                    cpp.meshEdge(coupledEdgei),
+                    coupledEdgei
+                );
+            }
+        }
+    }
+
+    // Nothing to do - can return already
+    if (returnReduce(edgeToCoupledIndex.empty(), andOp<bool>()))
+    {
+        return bndEdgePatchPairs;
+    }
+
+    // Data locations to pull
+    labelList patchEdgeLabels(nEdgesPull);
+    labelList coupledEdgeLabels(nEdgesPull);
+
+    // Populate the locations
+    {
+        nEdgesPull = 0;
+
+        forAll(cpp.edges(), coupledEdgei)
+        {
+            if
+            (
+                coupledEdgesUsed[coupledEdgei] == 3
+             && coupledEdgesOrig[coupledEdgei] == 1
+            )
+            {
+                // Pull this edge
+                const auto iter =
+                    edgeToBoundaryIndex.cfind(cpp.meshEdge(coupledEdgei));
+
+                if (iter.found())
+                {
+                    patchEdgeLabels[nEdgesPull] = iter.val();
+                    coupledEdgeLabels[nEdgesPull] = coupledEdgei;
+                    ++nEdgesPull;
+                }
+                else
+                {
+                    // Should be impossible to fail here
+                    FatalErrorInFunction
+                        << "Failed on second lookup of "
+                        << cpp.meshEdge(coupledEdgei) << nl
+                        << abort(FatalError);
+                }
+            }
+        }
+
+        if (nEdgesPull != coupledEdgeLabels.size())
+        {
+            FatalErrorInFunction
+                << "Failed lookup of some coupled edges" << nl
+                << abort(FatalError);
+        }
+    }
+
+    //- Construct edge sync with all data
+    patchPairInfoList cppEdgeData(map.constructSize());
+
+    // Fill in for 'push' locations. Only really interested in the owner
+    // (corresponds to non-proc connection), but grab everything
+    findEdgePatchPairing
+    (
+        pbm,
+        edgeToCoupledIndex,
+        cppEdgeData,
+        2*edgeToCoupledIndex.size()  // Accept both sides?
+    );
+
+
+    const label nNonProcessor = pbm.nNonProcessor();
+
+    // Adjust patch information to reflect dangling patch neighbour
+    // Tag with -(value+2)
+    forAllConstIters(edgeToCoupledIndex, iter)
+    {
+        const edge& e = iter.key();
+        const label coupledEdgei = iter.val();
+
+        patchPairInfo& pairing = cppEdgeData[coupledEdgei];
+        const label ownerPatchId = pairing.first();
+
+        // Some sanity checks
+        if (ownerPatchId < 0)
+        {
+            FatalErrorInFunction
+                << "Error finding dangling edge at "
+                << cpp.points()[e.first()] << ' '
+                << cpp.points()[e.second()] << nl
+                << abort(FatalError);
+        }
+        else if (ownerPatchId >= nNonProcessor)
+        {
+            FatalErrorInFunction
+                << "Cannot handle edge on processor-processor connection at "
+                << cpp.points()[e.first()] << ' '
+                << cpp.points()[e.second()] << nl
+                << abort(FatalError);
+        }
+
+        combineDanglingEdge::setDangling(pairing, ownerPatchId);
+
+        // TBD:
+        // may wish to remember the corresponding proc number,
+        // if we wish to bridge across 'fan-like' connections.
+        //
+        // pairing.setIndex(-(Pstream::myProcNo() + 2));
+    }
+
+
+    // Synchronize edge information
+
+    const combineDanglingEdge edgeCombineOp(nNonProcessor);
+
+    globalMeshData::syncData
+    (
+        cppEdgeData,
+        globalData.globalEdgeSlaves(),
+        globalData.globalEdgeTransformedSlaves(),  // probably not used
+        map,
+        edgeCombineOp
+    );
+
+
+    // Combine back from pushed cpp-edge data
+
+    forAll(patchEdgeLabels, i)
+    {
+        patchPairInfo& pairing = bndEdgePatchPairs[patchEdgeLabels[i]];
+        const patchPairInfo& other = cppEdgeData[coupledEdgeLabels[i]];
+
+        edgeCombineOp(pairing, other);
+
+        // Resolve special tagging
+        combineDanglingEdge::correct(pairing);
+    }
+
+    return bndEdgePatchPairs;
+}
+
+
+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_);
+        }
+    }
+
+    // ----------------------------------------------------------------------
+
+    const label nInternalEdges = patch().nInternalEdges();
+    const label nBoundaryEdges = patch().nBoundaryEdges();
+
+    patchPairInfoList bndEdgePatchPairs(this->getBoundaryEdgePatchPairs());
+
+    labelList bndEdgeFaPatchIDs(nBoundaryEdges, -1);
+
+    for (label bndEdgei = 0; bndEdgei < nBoundaryEdges; ++bndEdgei)
+    {
+        const patchPairInfo& patchPair = bndEdgePatchPairs[bndEdgei];
+
+        if (patchPair.valid())
+        {
+            // Non-negative, unique pairing
+            // - find corresponding definition
+
+            for (label patchi = 0; patchi < faPatchDefs.size(); ++patchi)
+            {
+                if (faPatchDefs[patchi].foundPatchPair(patchPair))
+                {
+                    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 patchEdgei = undefinedEdges[edgei];
+            const label bndEdgei = (patchEdgei - nInternalEdges);
+
+            edgeNbrPolyPatch[edgei] = bndEdgePatchPairs[bndEdgei].second();
+        }
+
+        // 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, bndEdgePatchPairs);
+            patchDef.neighPolyPatchId_ = -1; // No lookup of neighbour faces
+        }
+    }
+
+    // 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 cb518d77eff128dcbcb63f70f7c7feb247c4393a..1e8593b261f0d9a09283321059a1db4b752d5e3e 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 bbc073bfc2105aed7e55976bf4e2ad6884a28826..8ca96bbdaa68aeb647f177a7ccda8327d8845a4d 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 d5424a42a4ad82902108f53b68ca03deaf2ce169..7f5ddc489be08b6fb3e769cd77d3334d897bc370 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 5b4c4bb971e89553655511df023be8241f505782..93c69622e8d4b4f06a5c99944edba05b75dd6bb0 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 0ff674f5663b3956853c1c18e496354202df95a3..e9ecd37363eaedeaf7fd5f17e3eaad16a61b1b1c 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 4bf3f9d68331eb2740b7a23efd2f4e7294d74799..c48962843c6bdcc1b5136f0cc0640ff4c4af84f6 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/symmetry/symmetryFaPatch.C b/src/finiteArea/faMesh/faPatches/constraint/symmetry/symmetryFaPatch.C
index 437b03474b2e9ce19aef86b63e21fb265df7272a..bdf45fac31a395603ad7ca966e8d8650f2298bb6 100644
--- a/src/finiteArea/faMesh/faPatches/constraint/symmetry/symmetryFaPatch.C
+++ b/src/finiteArea/faMesh/faPatches/constraint/symmetry/symmetryFaPatch.C
@@ -70,7 +70,7 @@ Foam::symmetryFaPatch::symmetryFaPatch
 :
     faPatch(name, dict, index, bm)
 {
-    if (ngbPolyPatchIndex() == -1)
+    if (ngbPolyPatchIndex() < 0)
     {
         FatalErrorInFunction
             << "Neighbour polyPatch index is not specified for faPatch "
diff --git a/src/finiteArea/faMesh/faPatches/constraint/wedge/wedgeFaPatch.C b/src/finiteArea/faMesh/faPatches/constraint/wedge/wedgeFaPatch.C
index 8cca67583a7fd040bd1e8ee4b1c09b67d5ae33e2..c841637f4591865f41ab8a5afe23285ed5ef139f 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.
@@ -87,22 +88,21 @@ Foam::wedgeFaPatch::wedgeFaPatch
     axisPoint_(-1),
     axisPointChecked_(false)
 {
-    if (ngbPolyPatchIndex() == -1)
+    if (ngbPolyPatchIndex() < 0)
     {
         FatalErrorInFunction
             << "Neighbour polyPatch index is not specified for faPatch "
             << 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 52f7a3ba6247d45e950bb2f58e34282bd940a269..a050e497a84077104fce80d4ebbc5ce8b3e6087f 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_ < 0)
     {
-        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;
             }
@@ -308,14 +292,14 @@ Foam::labelList Foam::faPatch::ngbPolyPatchFaces() const
 
 Foam::tmp<Foam::vectorField> Foam::faPatch::ngbPolyPatchFaceNormals() const
 {
-    auto tfN = tmp<vectorField>::New();
-    auto& fN = tfN.ref();
-
-    if (ngbPolyPatchIndex() == -1)
+    if (nbrPolyPatchId_ < 0)
     {
-        return tfN;
+        return tmp<vectorField>::New();
     }
 
+    auto tfN = tmp<vectorField>::New();
+    auto& fN = tfN.ref();
+
     fN.setSize(faPatch::size());
 
     labelList ngbFaces = ngbPolyPatchFaces();
@@ -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_ < 0)
     {
         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 52fb33e613a85966c8978da476aa6d4dca8718ea..98bb9efe764d2decfdf002567567c79a44750ebe 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 0000000000000000000000000000000000000000..7d712c99efd0dedaef42878732ea8956a5187cf8
--- /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 c8d46697913293b4097f369885274a924cc1a08e..4f785b351562ef63647effede34ec1299ed10dfb 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 6e3fab5ee86b6ac4590f372ff9a00c68bd650668..ce4a007e11a64f3bc08a54da665ec6b43b7e20dc 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)
     );
 }
 
diff --git a/src/finiteVolume/cfdTools/general/fvOptions/fvOption.C b/src/finiteVolume/cfdTools/general/fvOptions/fvOption.C
index e2c5e73ed7edf9c63060c0a995f1a15f6a06e1ee..4614b1b8acc5e09e524e107598b20f406648883e 100644
--- a/src/finiteVolume/cfdTools/general/fvOptions/fvOption.C
+++ b/src/finiteVolume/cfdTools/general/fvOptions/fvOption.C
@@ -58,9 +58,9 @@ Foam::fv::option::option
     mesh_(mesh),
     dict_(dict),
     coeffs_(dict.optionalSubDict(modelType + "Coeffs")),
-    active_(dict_.getOrDefault<Switch>("active", true)),
     fieldNames_(),
     applied_(),
+    active_(dict_.getOrDefault("active", true)),
     log(true)
 {
     Log << incrIndent << indent << "Source: " << name_ << endl << decrIndent;
@@ -101,7 +101,7 @@ Foam::autoPtr<Foam::fv::option> Foam::fv::option::New
         ) << exit(FatalIOError);
     }
 
-    return autoPtr<option>(cstrIter()(name, modelType, coeffs, mesh));
+    return autoPtr<fv::option>(cstrIter()(name, modelType, coeffs, mesh));
 }
 
 
diff --git a/src/finiteVolume/cfdTools/general/fvOptions/fvOption.H b/src/finiteVolume/cfdTools/general/fvOptions/fvOption.H
index e1859a714da3f46219afd6dc34f5e82c89d5a99b..84539964ac498733f4724d67f547b3546ecc9aef 100644
--- a/src/finiteVolume/cfdTools/general/fvOptions/fvOption.H
+++ b/src/finiteVolume/cfdTools/general/fvOptions/fvOption.H
@@ -113,23 +113,25 @@ protected:
         //- Dictionary containing source coefficients
         dictionary coeffs_;
 
-        //- Source active flag
-        Switch active_;
-
         //- Field names to apply source to - populated by derived models
         wordList fieldNames_;
 
         //- Applied flag list - corresponds to each fieldNames_ entry
         List<bool> applied_;
 
+        //- Source active flag
+        bool active_;
+
 
 public:
 
+    //- Switch write log to Info
+    bool log;
+
+
     //- Runtime type information
     TypeName("option");
 
-    //- Switch write log to Info
-    bool log;
 
     // Declare run-time constructor selection table
 
@@ -219,16 +221,16 @@ public:
         // Access
 
             //- Return const access to the source name
-            inline const word& name() const;
+            inline const word& name() const noexcept;
 
             //- Return const access to the mesh database
-            inline const fvMesh& mesh() const;
+            inline const fvMesh& mesh() const noexcept;
 
             //- Return dictionary
-            inline const dictionary& coeffs() const;
+            inline const dictionary& coeffs() const noexcept;
 
-            //- Return const access to the source active flag
-            inline bool active() const;
+            //- True if source is active
+            inline bool active() const noexcept;
 
             //- Set the applied flag to true for field index fieldi
             inline void setApplied(const label fieldi);
@@ -236,8 +238,8 @@ public:
 
         // Edit
 
-            //- Return access to the source active flag
-            inline Switch& active();
+            //- Change source active flag, return previous value
+            inline bool active(const bool on) noexcept;
 
 
         // Checks
diff --git a/src/finiteVolume/cfdTools/general/fvOptions/fvOptionI.H b/src/finiteVolume/cfdTools/general/fvOptions/fvOptionI.H
index f15e73aa172d547ba2ac7a5265e9ebb2b8f8d7ab..586024e2e6bdf31f0e420956167c339654af4e60 100644
--- a/src/finiteVolume/cfdTools/general/fvOptions/fvOptionI.H
+++ b/src/finiteVolume/cfdTools/general/fvOptions/fvOptionI.H
@@ -6,6 +6,7 @@
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
     Copyright (C) 2011-2016 OpenFOAM Foundation
+    Copyright (C) 2021 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -27,39 +28,41 @@ License
 
 // * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //
 
-inline const Foam::word& Foam::fv::option::name() const
+inline const Foam::word& Foam::fv::option::name() const noexcept
 {
     return name_;
 }
 
 
-inline const Foam::fvMesh& Foam::fv::option::mesh() const
+inline const Foam::fvMesh& Foam::fv::option::mesh() const noexcept
 {
     return mesh_;
 }
 
 
-inline const Foam::dictionary& Foam::fv::option::coeffs() const
+inline const Foam::dictionary& Foam::fv::option::coeffs() const noexcept
 {
     return coeffs_;
 }
 
 
-inline bool Foam::fv::option::active() const
+inline bool Foam::fv::option::active() const noexcept
 {
     return active_;
 }
 
 
-inline void Foam::fv::option::setApplied(const label fieldi)
+inline bool Foam::fv::option::active(const bool on) noexcept
 {
-    applied_[fieldi] = true;
+    bool old(active_);
+    active_ = on;
+    return old;
 }
 
 
-inline Foam::Switch& Foam::fv::option::active()
+inline void Foam::fv::option::setApplied(const label fieldi)
 {
-    return active_;
+    applied_[fieldi] = true;
 }
 
 
diff --git a/src/finiteVolume/cfdTools/general/fvOptions/fvOptionList.C b/src/finiteVolume/cfdTools/general/fvOptions/fvOptionList.C
index 78f4a6fa58a25c18b68bfc93d33d1eebb2d2f313..2fe08194b560182ac3ab1f444adb89351243fba1 100644
--- a/src/finiteVolume/cfdTools/general/fvOptions/fvOptionList.C
+++ b/src/finiteVolume/cfdTools/general/fvOptions/fvOptionList.C
@@ -89,7 +89,7 @@ void Foam::fv::optionList::checkApplied() const
 
 Foam::fv::optionList::optionList(const fvMesh& mesh, const dictionary& dict)
 :
-    PtrList<option>(),
+    PtrList<fv::option>(),
     mesh_(mesh),
     checkTimeIndex_(mesh_.time().startTimeIndex() + 2)
 {
@@ -99,7 +99,7 @@ Foam::fv::optionList::optionList(const fvMesh& mesh, const dictionary& dict)
 
 Foam::fv::optionList::optionList(const fvMesh& mesh)
 :
-    PtrList<option>(),
+    PtrList<fv::option>(),
     mesh_(mesh),
     checkTimeIndex_(mesh_.time().startTimeIndex() + 2)
 {}
diff --git a/src/finiteVolume/cfdTools/general/fvOptions/fvOptionList.H b/src/finiteVolume/cfdTools/general/fvOptions/fvOptionList.H
index a6cd374fae4c438968738b671d484339535a8798..1cbca46098bc8e6a50a2f3a905e3197bb4e39f26 100644
--- a/src/finiteVolume/cfdTools/general/fvOptions/fvOptionList.H
+++ b/src/finiteVolume/cfdTools/general/fvOptions/fvOptionList.H
@@ -69,7 +69,7 @@ namespace fv
 
 class optionList
 :
-    public PtrList<option>
+    public PtrList<fv::option>
 {
 protected:
 
diff --git a/src/fvOptions/cellSetOption/cellSetOption.H b/src/fvOptions/cellSetOption/cellSetOption.H
index 32f1609518e7aaa00fc24f02932c08b9debf5c0f..02b2931fdc908451016d44fd2a14908a973e00cd 100644
--- a/src/fvOptions/cellSetOption/cellSetOption.H
+++ b/src/fvOptions/cellSetOption/cellSetOption.H
@@ -117,7 +117,7 @@ namespace fv
 
 class cellSetOption
 :
-    public option
+    public fv::option
 {
 public:
 
diff --git a/src/fvOptions/interRegionOption/interRegionOption.H b/src/fvOptions/interRegionOption/interRegionOption.H
index 3496f0b70c5bcea40f8060f994c6b31dd928250f..315f9808b726aa90713029330b3821aedff6442b 100644
--- a/src/fvOptions/interRegionOption/interRegionOption.H
+++ b/src/fvOptions/interRegionOption/interRegionOption.H
@@ -87,7 +87,7 @@ namespace fv
 
 class interRegionOption
 :
-    public option
+    public fv::option
 {
 protected:
 
diff --git a/src/fvOptions/sources/derived/buoyancyEnergy/buoyancyEnergy.H b/src/fvOptions/sources/derived/buoyancyEnergy/buoyancyEnergy.H
index d40d1ed239977ab28955ee2054aa3e814e1347c7..361853bd8e7c8c95fab3b83ddd2e9255c991da40 100644
--- a/src/fvOptions/sources/derived/buoyancyEnergy/buoyancyEnergy.H
+++ b/src/fvOptions/sources/derived/buoyancyEnergy/buoyancyEnergy.H
@@ -118,7 +118,7 @@ namespace fv
 
 class buoyancyEnergy
 :
-    public option
+    public fv::option
 {
     // Private Data
 
diff --git a/src/fvOptions/sources/derived/buoyancyForce/buoyancyForce.H b/src/fvOptions/sources/derived/buoyancyForce/buoyancyForce.H
index 0ef9438b85bce14f47118119a09a956528a1379c..07f785ab337f983a041d962f2155489e4e5a01ce 100644
--- a/src/fvOptions/sources/derived/buoyancyForce/buoyancyForce.H
+++ b/src/fvOptions/sources/derived/buoyancyForce/buoyancyForce.H
@@ -110,7 +110,7 @@ namespace fv
 
 class buoyancyForce
 :
-    public option
+    public fv::option
 {
     // Private Data
 
diff --git a/src/fvOptions/sources/derived/jouleHeatingSource/jouleHeatingSource.H b/src/fvOptions/sources/derived/jouleHeatingSource/jouleHeatingSource.H
index b2ea0e06ad076ed7f521dff0a4e70287ac5a2287..d748dcdab84dec42084d8e866dc58ba50a7793ce 100644
--- a/src/fvOptions/sources/derived/jouleHeatingSource/jouleHeatingSource.H
+++ b/src/fvOptions/sources/derived/jouleHeatingSource/jouleHeatingSource.H
@@ -175,7 +175,7 @@ namespace fv
 
 class jouleHeatingSource
 :
-    public option
+    public fv::option
 {
     // Private Data
 
diff --git a/src/fvOptions/sources/derived/multiphaseStabilizedTurbulence/multiphaseStabilizedTurbulence.H b/src/fvOptions/sources/derived/multiphaseStabilizedTurbulence/multiphaseStabilizedTurbulence.H
index 4d0e7db9e7564a14f18699735146f24789f34392..033669bfd4fa0ab9e9ccdf149f839ed5c9e51ca2 100644
--- a/src/fvOptions/sources/derived/multiphaseStabilizedTurbulence/multiphaseStabilizedTurbulence.H
+++ b/src/fvOptions/sources/derived/multiphaseStabilizedTurbulence/multiphaseStabilizedTurbulence.H
@@ -133,7 +133,7 @@ namespace fv
 
 class multiphaseStabilizedTurbulence
 :
-    public option
+    public fv::option
 {
     // Private Data
 
diff --git a/src/fvOptions/sources/derived/phaseLimitStabilization/PhaseLimitStabilization.H b/src/fvOptions/sources/derived/phaseLimitStabilization/PhaseLimitStabilization.H
index 8ea0073ba4c3d80d965d338a64007aff64718a21..234d7d7caf361e68508dd1e594b652a35eca5a3f 100644
--- a/src/fvOptions/sources/derived/phaseLimitStabilization/PhaseLimitStabilization.H
+++ b/src/fvOptions/sources/derived/phaseLimitStabilization/PhaseLimitStabilization.H
@@ -108,7 +108,7 @@ namespace fv
 template<class Type>
 class PhaseLimitStabilization
 :
-    public option
+    public fv::option
 {
     // Private Data
 
diff --git a/src/fvOptions/sources/derived/tabulatedAccelerationSource/tabulatedAccelerationSource.H b/src/fvOptions/sources/derived/tabulatedAccelerationSource/tabulatedAccelerationSource.H
index 6541bb805bd17beffd8afb703b919fa35afe97c2..5a6007bb0929d3b4cc7c4076f090ff1e94f00eda 100644
--- a/src/fvOptions/sources/derived/tabulatedAccelerationSource/tabulatedAccelerationSource.H
+++ b/src/fvOptions/sources/derived/tabulatedAccelerationSource/tabulatedAccelerationSource.H
@@ -87,7 +87,7 @@ namespace fv
 
 class tabulatedAccelerationSource
 :
-    public option
+    public fv::option
 {
 protected:
 
diff --git a/src/fvOptions/sources/derived/viscousDissipation/viscousDissipation.H b/src/fvOptions/sources/derived/viscousDissipation/viscousDissipation.H
index 805bf12c11edf072ce24bfa2e857846904b19b7b..b8740163c52442611b9e26d1e876f2f73cf94a5a 100644
--- a/src/fvOptions/sources/derived/viscousDissipation/viscousDissipation.H
+++ b/src/fvOptions/sources/derived/viscousDissipation/viscousDissipation.H
@@ -111,7 +111,7 @@ namespace fv
 
 class viscousDissipation
 :
-    public option
+    public fv::option
 {
     // Private Data
 
diff --git a/src/parallel/decompose/Allwclean b/src/parallel/decompose/Allwclean
index e6b78d10c682bc6b2c5d0fd61bce888bcf751f83..a0a7d6335f81ae79bf8e0fc50b0f79f8935bfb18 100755
--- a/src/parallel/decompose/Allwclean
+++ b/src/parallel/decompose/Allwclean
@@ -8,6 +8,7 @@ wclean kahipDecomp
 wclean scotchDecomp
 wclean decompositionMethods
 wclean decompose
+wclean faDecompose
 
 ./Allwclean-mpi
 
diff --git a/src/parallel/decompose/Allwmake b/src/parallel/decompose/Allwmake
index c9bc6b610af90330d172de8f6460a64e9199adf7..b1681aa0b7f03d08c166e47ea65824333ada3cbc 100755
--- a/src/parallel/decompose/Allwmake
+++ b/src/parallel/decompose/Allwmake
@@ -12,6 +12,7 @@ export FOAM_EXT_LIBBIN
 
 wmake $targetType decompositionMethods
 wmake $targetType decompose
+wmake $targetType faDecompose
 
 if have_kahip
 then
diff --git a/src/parallel/decompose/decompose/Make/files b/src/parallel/decompose/decompose/Make/files
index 225a27dfad890ce4248954ff1be2aca2c4dfbf16..e55de05d2e5d5a56b14b936672d4c578a1e8b379 100644
--- a/src/parallel/decompose/decompose/Make/files
+++ b/src/parallel/decompose/decompose/Make/files
@@ -1,5 +1,8 @@
 decompositionInformation.C
 decompositionModel.C
+
+dimFieldDecomposer.C
 fvFieldDecomposer.C
+pointFieldDecomposer.C
 
 LIB = $(FOAM_LIBBIN)/libdecompose
diff --git a/applications/utilities/parallelProcessing/decomposePar/dimFieldDecomposer.C b/src/parallel/decompose/decompose/dimFieldDecomposer.C
similarity index 82%
rename from applications/utilities/parallelProcessing/decomposePar/dimFieldDecomposer.C
rename to src/parallel/decompose/decompose/dimFieldDecomposer.C
index 391d2bf5c8576948fc7dfb8cb8c6b78fd9aad365..dbef4df61ed9c78126677e6f9ffb8d5d0196e1cd 100644
--- a/applications/utilities/parallelProcessing/decomposePar/dimFieldDecomposer.C
+++ b/src/parallel/decompose/decompose/dimFieldDecomposer.C
@@ -6,6 +6,7 @@
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
     Copyright (C) 2011 OpenFOAM Foundation
+    Copyright (C) 2021 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -27,27 +28,30 @@ License
 
 #include "dimFieldDecomposer.H"
 
-
 // * * * * * * * * * * * * * * * * Constructors  * * * * * * * * * * * * * * //
 
 Foam::dimFieldDecomposer::dimFieldDecomposer
 (
-    const fvMesh& completeMesh,
     const fvMesh& procMesh,
-    const labelList& faceAddressing,
     const labelList& cellAddressing
 )
 :
-    completeMesh_(completeMesh),
     procMesh_(procMesh),
-    faceAddressing_(faceAddressing),
     cellAddressing_(cellAddressing)
 {}
 
 
-// * * * * * * * * * * * * * * * * Destructor  * * * * * * * * * * * * * * * //
-
-Foam::dimFieldDecomposer::~dimFieldDecomposer()
+Foam::dimFieldDecomposer::dimFieldDecomposer
+(
+    const fvMesh& /* unused: completeMesh */,
+    const fvMesh& procMesh,
+    const labelList& /* unused: faceAddressing */,
+    const labelList& cellAddressing
+)
+:
+    procMesh_(procMesh),
+    //UNUSED: faceAddressing_(faceAddressing),
+    cellAddressing_(cellAddressing)
 {}
 
 
diff --git a/applications/utilities/parallelProcessing/decomposePar/dimFieldDecomposer.H b/src/parallel/decompose/decompose/dimFieldDecomposer.H
similarity index 77%
rename from applications/utilities/parallelProcessing/decomposePar/dimFieldDecomposer.H
rename to src/parallel/decompose/decompose/dimFieldDecomposer.H
index 26710ce5e8aad31991c096d1bab0b32198bad583..ec80c621cd9cb8e28bd6cd80fb93d0c72e82ba10 100644
--- a/applications/utilities/parallelProcessing/decomposePar/dimFieldDecomposer.H
+++ b/src/parallel/decompose/decompose/dimFieldDecomposer.H
@@ -6,6 +6,7 @@
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
     Copyright (C) 2011-2016 OpenFOAM Foundation
+    Copyright (C) 2021 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -31,7 +32,7 @@ Description
 
 SourceFiles
     dimFieldDecomposer.C
-    dimFieldDecomposerDecomposeFields.C
+    dimFieldDecomposerFields.C
 
 \*---------------------------------------------------------------------------*/
 
@@ -46,69 +47,68 @@ SourceFiles
 namespace Foam
 {
 
-class IOobjectList;
-
 /*---------------------------------------------------------------------------*\
                     Class fvFieldDecomposer Declaration
 \*---------------------------------------------------------------------------*/
 
 class dimFieldDecomposer
 {
-private:
-
-    // Private data
-
-        //- Reference to complete mesh
-        const fvMesh& completeMesh_;
+    // Private Data
 
         //- Reference to processor mesh
         const fvMesh& procMesh_;
 
         //- Reference to face addressing
-        const labelList& faceAddressing_;
+        //UNUSED: const labelList& faceAddressing_;
 
         //- Reference to cell addressing
         const labelList& cellAddressing_;
 
 
-    // Private Member Functions
-
-        //- No copy construct
-        dimFieldDecomposer(const dimFieldDecomposer&) = delete;
+public:
 
-        //- No copy assignment
-        void operator=(const dimFieldDecomposer&) = delete;
+    //- No copy construct
+    dimFieldDecomposer(const dimFieldDecomposer&) = delete;
 
+    //- No copy assignment
+    void operator=(const dimFieldDecomposer&) = delete;
 
-public:
 
     // Constructors
 
-        //- Construct from components
+        //- Construct from minimal components
+        dimFieldDecomposer
+        (
+            const fvMesh& procMesh,
+            const labelList& cellAddressing
+        );
+
+        //- Construct from components with API as per fvFieldDecomposer
         dimFieldDecomposer
         (
-            const fvMesh& completeMesh,
+            const fvMesh& completeMesh,         //!< unused
             const fvMesh& procMesh,
-            const labelList& faceAddressing,
+            const labelList& faceAddressing,    //!< unused
             const labelList& cellAddressing
         );
 
 
     //- Destructor
-    ~dimFieldDecomposer();
+    ~dimFieldDecomposer() = default;
 
 
     // Member Functions
 
         //- Decompose field
         template<class Type>
-        tmp<DimensionedField<Type, volMesh>> decomposeField
+        tmp<DimensionedField<Type, volMesh>>
+        decomposeField
         (
             const DimensionedField<Type, volMesh>& field
         ) const;
 
 
-        //- Decompose llist of fields
+        //- Decompose list of fields
         template<class GeoField>
         void decomposeFields(const PtrList<GeoField>& fields) const;
 };
@@ -121,7 +121,7 @@ public:
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
 
 #ifdef NoRepository
-    #include "dimFieldDecomposerDecomposeFields.C"
+    #include "dimFieldDecomposerFields.C"
 #endif
 
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
diff --git a/applications/utilities/parallelProcessing/decomposePar/dimFieldDecomposerDecomposeFields.C b/src/parallel/decompose/decompose/dimFieldDecomposerFields.C
similarity index 91%
rename from applications/utilities/parallelProcessing/decomposePar/dimFieldDecomposerDecomposeFields.C
rename to src/parallel/decompose/decompose/dimFieldDecomposerFields.C
index 2d4c2bf98784953d334e81df0d7a2f5b6323b8cf..7da59d8d1964a9490cfae0cc903558c44dff3981 100644
--- a/applications/utilities/parallelProcessing/decomposePar/dimFieldDecomposerDecomposeFields.C
+++ b/src/parallel/decompose/decompose/dimFieldDecomposerFields.C
@@ -6,6 +6,7 @@
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
     Copyright (C) 2011-2016 OpenFOAM Foundation
+    Copyright (C) 2021 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -40,9 +41,8 @@ Foam::dimFieldDecomposer::decomposeField
     Field<Type> mappedField(field, cellAddressing_);
 
     // Create the field for the processor
-    return tmp<DimensionedField<Type, volMesh>>
-    (
-        new DimensionedField<Type, volMesh>
+    return
+        tmp<DimensionedField<Type, volMesh>>::New
         (
             IOobject
             (
@@ -55,9 +55,8 @@ Foam::dimFieldDecomposer::decomposeField
             ),
             procMesh_,
             field.dimensions(),
-            mappedField
-        )
-    );
+            std::move(mappedField)
+        );
 }
 
 
@@ -67,9 +66,9 @@ void Foam::dimFieldDecomposer::decomposeFields
     const PtrList<GeoField>& fields
 ) const
 {
-    forAll(fields, fieldi)
+    for (const auto& fld : fields)
     {
-        decomposeField(fields[fieldi])().write();
+        decomposeField(fld)().write();
     }
 }
 
diff --git a/src/parallel/decompose/decompose/fvFieldDecomposer.C b/src/parallel/decompose/decompose/fvFieldDecomposer.C
index 8cc5acdf68396132be9dad392e489a6b582ac20c..61ade1be3bc6a567abdbe8e110407d727dfc432e 100644
--- a/src/parallel/decompose/decompose/fvFieldDecomposer.C
+++ b/src/parallel/decompose/decompose/fvFieldDecomposer.C
@@ -6,6 +6,7 @@
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
     Copyright (C) 2011-2016 OpenFOAM Foundation
+    Copyright (C) 2021 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -27,7 +28,6 @@ License
 
 #include "fvFieldDecomposer.H"
 
-
 // * * * * * * * * * * * * * * * * Constructors  * * * * * * * * * * * * * * //
 
 Foam::fvFieldDecomposer::patchFieldDecomposer::patchFieldDecomposer
@@ -49,21 +49,19 @@ Foam::fvFieldDecomposer::patchFieldDecomposer::patchFieldDecomposer
 Foam::fvFieldDecomposer::processorVolPatchFieldDecomposer::
 processorVolPatchFieldDecomposer
 (
-    const fvMesh& mesh,
+    const labelUList& owner,  // == mesh.faceOwner()
+    const labelUList& neigh,  // == mesh.faceNeighbour()
     const labelUList& addressingSlice
 )
 :
     directAddressing_(addressingSlice.size())
 {
-    const labelList& own = mesh.faceOwner();
-    const labelList& neighb = mesh.faceNeighbour();
-
     forAll(directAddressing_, i)
     {
         // Subtract one to align addressing.
         label ai = mag(addressingSlice[i]) - 1;
 
-        if (ai < neighb.size())
+        if (ai < neigh.size())
         {
             // This is a regular face. it has been an internal face
             // of the original mesh and now it has become a face
@@ -73,11 +71,11 @@ processorVolPatchFieldDecomposer
             if (addressingSlice[i] >= 0)
             {
                 // I have the owner so use the neighbour value
-                directAddressing_[i] = neighb[ai];
+                directAddressing_[i] = neigh[ai];
             }
             else
             {
-                directAddressing_[i] = own[ai];
+                directAddressing_[i] = owner[ai];
             }
         }
         else
@@ -88,12 +86,28 @@ processorVolPatchFieldDecomposer
             // up the different (face) list of data), so I will
             // just grab the value from the owner cell
 
-            directAddressing_[i] = own[ai];
+            directAddressing_[i] = owner[ai];
         }
     }
 }
 
 
+Foam::fvFieldDecomposer::processorVolPatchFieldDecomposer::
+processorVolPatchFieldDecomposer
+(
+    const fvMesh& mesh,
+    const labelUList& addressingSlice
+)
+:
+    processorVolPatchFieldDecomposer
+    (
+        mesh.faceOwner(),
+        mesh.faceNeighbour(),
+        addressingSlice
+    )
+{}
+
+
 Foam::fvFieldDecomposer::processorSurfacePatchFieldDecomposer::
 processorSurfacePatchFieldDecomposer
 (
@@ -105,8 +119,8 @@ processorSurfacePatchFieldDecomposer
 {
     forAll(addressing_, i)
     {
-        addressing_[i].setSize(1);
-        weights_[i].setSize(1);
+        addressing_[i].resize(1);
+        weights_[i].resize(1);
 
         addressing_[i][0] = mag(addressingSlice[i]) - 1;
         weights_[i][0] = 1;
@@ -116,31 +130,113 @@ processorSurfacePatchFieldDecomposer
 
 Foam::fvFieldDecomposer::fvFieldDecomposer
 (
-    const fvMesh& completeMesh,
+    const Foam::zero,
     const fvMesh& procMesh,
     const labelList& faceAddressing,
     const labelList& cellAddressing,
     const labelList& boundaryAddressing
 )
 :
-    completeMesh_(completeMesh),
     procMesh_(procMesh),
     faceAddressing_(faceAddressing),
     cellAddressing_(cellAddressing),
     boundaryAddressing_(boundaryAddressing),
-    patchFieldDecomposerPtrs_(procMesh_.boundary().size()),
-    processorVolPatchFieldDecomposerPtrs_(procMesh_.boundary().size()),
-    processorSurfacePatchFieldDecomposerPtrs_(procMesh_.boundary().size()),
-    faceSign_(procMesh_.boundary().size())
+    // Mappers
+    patchFieldDecomposerPtrs_(),
+    processorVolPatchFieldDecomposerPtrs_(),
+    processorSurfacePatchFieldDecomposerPtrs_(),
+    faceSign_()
+{}
+
+
+Foam::fvFieldDecomposer::fvFieldDecomposer
+(
+    const fvMesh& completeMesh,
+    const fvMesh& procMesh,
+    const labelList& faceAddressing,
+    const labelList& cellAddressing,
+    const labelList& boundaryAddressing
+)
+:
+    fvFieldDecomposer
+    (
+        zero{},
+        procMesh,
+        faceAddressing,
+        cellAddressing,
+        boundaryAddressing
+    )
 {
+    reset(completeMesh);
+}
+
+
+Foam::fvFieldDecomposer::fvFieldDecomposer
+(
+    const List<labelRange>& boundaryRanges,
+    const labelUList& faceOwner,
+    const labelUList& faceNeighbour,
+
+    const fvMesh& procMesh,
+    const labelList& faceAddressing,
+    const labelList& cellAddressing,
+    const labelList& boundaryAddressing
+)
+:
+    fvFieldDecomposer
+    (
+        zero{},
+        procMesh,
+        faceAddressing,
+        cellAddressing,
+        boundaryAddressing
+    )
+{
+    reset(boundaryRanges, faceOwner, faceNeighbour);
+}
+
+
+// * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //
+
+bool Foam::fvFieldDecomposer::empty() const
+{
+    return patchFieldDecomposerPtrs_.empty();
+}
+
+
+void Foam::fvFieldDecomposer::clear()
+{
+    patchFieldDecomposerPtrs_.clear();
+    processorVolPatchFieldDecomposerPtrs_.clear();
+    processorSurfacePatchFieldDecomposerPtrs_.clear();
+    faceSign_.clear();
+}
+
+
+void Foam::fvFieldDecomposer::reset
+(
+    const List<labelRange>& boundaryRanges,
+    const labelUList& faceOwner,
+    const labelUList& faceNeighbour
+)
+{
+    clear();
+    const label nMappers = procMesh_.boundary().size();
+    patchFieldDecomposerPtrs_.resize(nMappers);
+    processorVolPatchFieldDecomposerPtrs_.resize(nMappers);
+    processorSurfacePatchFieldDecomposerPtrs_.resize(nMappers);
+    faceSign_.resize(nMappers);
+
     forAll(boundaryAddressing_, patchi)
     {
+        const label oldPatchi = boundaryAddressing_[patchi];
         const fvPatch& fvp = procMesh_.boundary()[patchi];
+        const labelSubList localPatchSlice(fvp.patchSlice(faceAddressing_));
 
         if
         (
-            boundaryAddressing_[patchi] >= 0
-        && !isA<processorLduInterface>(procMesh.boundary()[patchi])
+            oldPatchi >= 0
+        && !isA<processorLduInterface>(procMesh_.boundary()[patchi])
         )
         {
             patchFieldDecomposerPtrs_.set
@@ -148,11 +244,8 @@ Foam::fvFieldDecomposer::fvFieldDecomposer
                 patchi,
                 new patchFieldDecomposer
                 (
-                    fvp.patchSlice(faceAddressing_),
-                    completeMesh_.boundaryMesh()
-                    [
-                        boundaryAddressing_[patchi]
-                    ].start()
+                    localPatchSlice,
+                    boundaryRanges[oldPatchi].start()
                 )
             );
         }
@@ -163,8 +256,9 @@ Foam::fvFieldDecomposer::fvFieldDecomposer
                 patchi,
                 new processorVolPatchFieldDecomposer
                 (
-                    completeMesh_,
-                    fvp.patchSlice(faceAddressing_)
+                    faceOwner,
+                    faceNeighbour,
+                    localPatchSlice
                 )
             );
 
@@ -173,28 +267,21 @@ Foam::fvFieldDecomposer::fvFieldDecomposer
                 patchi,
                 new processorSurfacePatchFieldDecomposer
                 (
-                    static_cast<const labelUList&>
-                    (
-                        fvp.patchSlice
-                        (
-                            faceAddressing_
-                        )
-                    )
+                    static_cast<const labelUList&>(localPatchSlice)
                 )
             );
 
             faceSign_.set
             (
                 patchi,
-                new scalarField(fvp.patchSlice(faceAddressing_).size())
+                new scalarField(localPatchSlice.size())
             );
 
             {
-                const SubList<label> fa = fvp.patchSlice(faceAddressing_);
                 scalarField& s = faceSign_[patchi];
                 forAll(s, i)
                 {
-                    s[i] = sign(fa[i]);
+                    s[i] = sign(localPatchSlice[i]);
                 }
             }
         }
@@ -202,10 +289,74 @@ Foam::fvFieldDecomposer::fvFieldDecomposer
 }
 
 
-// * * * * * * * * * * * * * * * * Destructor  * * * * * * * * * * * * * * * //
+void Foam::fvFieldDecomposer::reset(const fvMesh& completeMesh)
+{
+    clear();
+    const label nMappers = procMesh_.boundary().size();
+    patchFieldDecomposerPtrs_.resize(nMappers);
+    processorVolPatchFieldDecomposerPtrs_.resize(nMappers);
+    processorSurfacePatchFieldDecomposerPtrs_.resize(nMappers);
+    faceSign_.resize(nMappers);
 
-Foam::fvFieldDecomposer::~fvFieldDecomposer()
-{}
+    forAll(boundaryAddressing_, patchi)
+    {
+        const label oldPatchi = boundaryAddressing_[patchi];
+        const fvPatch& fvp = procMesh_.boundary()[patchi];
+        const labelSubList localPatchSlice(fvp.patchSlice(faceAddressing_));
+
+        if
+        (
+            oldPatchi >= 0
+        && !isA<processorLduInterface>(procMesh_.boundary()[patchi])
+        )
+        {
+            patchFieldDecomposerPtrs_.set
+            (
+                patchi,
+                new patchFieldDecomposer
+                (
+                    localPatchSlice,
+                    completeMesh.boundaryMesh()[oldPatchi].start()
+                )
+            );
+        }
+        else
+        {
+            processorVolPatchFieldDecomposerPtrs_.set
+            (
+                patchi,
+                new processorVolPatchFieldDecomposer
+                (
+                    completeMesh,
+                    localPatchSlice
+                )
+            );
+
+            processorSurfacePatchFieldDecomposerPtrs_.set
+            (
+                patchi,
+                new processorSurfacePatchFieldDecomposer
+                (
+                    static_cast<const labelUList&>(localPatchSlice)
+                )
+            );
+
+            faceSign_.set
+            (
+                patchi,
+                new scalarField(localPatchSlice.size())
+            );
+
+            {
+                scalarField& s = faceSign_[patchi];
+                forAll(s, i)
+                {
+                    s[i] = sign(localPatchSlice[i]);
+                }
+            }
+        }
+    }
+}
 
 
 // ************************************************************************* //
diff --git a/src/parallel/decompose/decompose/fvFieldDecomposer.H b/src/parallel/decompose/decompose/fvFieldDecomposer.H
index 34a0904a580e0abac6d21b001a1021ecd2f7010a..a0dab98a4df0df00256937431aba7c6037643520 100644
--- a/src/parallel/decompose/decompose/fvFieldDecomposer.H
+++ b/src/parallel/decompose/decompose/fvFieldDecomposer.H
@@ -6,6 +6,7 @@
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
     Copyright (C) 2011-2016 OpenFOAM Foundation
+    Copyright (C) 2021 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -31,7 +32,7 @@ Description
 
 SourceFiles
     fvFieldDecomposer.C
-    fvFieldDecomposerDecomposeFields.C
+    fvFieldDecomposerFields.C
 
 \*---------------------------------------------------------------------------*/
 
@@ -47,8 +48,6 @@ SourceFiles
 namespace Foam
 {
 
-class IOobjectList;
-
 /*---------------------------------------------------------------------------*\
                     Class fvFieldDecomposer Declaration
 \*---------------------------------------------------------------------------*/
@@ -62,7 +61,7 @@ public:
         :
             public fvPatchFieldMapper
         {
-            // Private data
+            // Private Data
 
                 labelList directAddressing_;
 
@@ -78,7 +77,7 @@ public:
                 );
 
 
-            // Member functions
+            // Member Functions
 
                 label size() const
                 {
@@ -110,13 +109,21 @@ public:
         :
             public fvPatchFieldMapper
         {
-            // Private data
+            // Private Data
 
                 labelList directAddressing_;
 
         public:
 
-            //- Construct given addressing
+            //- Construct addressing from details
+            processorVolPatchFieldDecomposer
+            (
+                const labelUList& faceOwner,
+                const labelUList& faceNeigbour,
+                const labelUList& addressingSlice
+            );
+
+            //- Construct given addressing from complete mesh
             processorVolPatchFieldDecomposer
             (
                 const fvMesh& mesh,
@@ -124,7 +131,7 @@ public:
             );
 
 
-            // Member functions
+            // Member Functions
 
                 label size() const
                 {
@@ -199,10 +206,7 @@ public:
 
 private:
 
-    // Private data
-
-        //- Reference to complete mesh
-        const fvMesh& completeMesh_;
+    // Private Data
 
         //- Reference to processor mesh
         const fvMesh& procMesh_;
@@ -225,24 +229,31 @@ private:
         PtrList<processorSurfacePatchFieldDecomposer>
             processorSurfacePatchFieldDecomposerPtrs_;
 
-
         PtrList<scalarField> faceSign_;
 
 
-    // Private Member Functions
-
-        //- No copy construct
-        fvFieldDecomposer(const fvFieldDecomposer&) = delete;
+public:
 
-        //- No copy assignment
-        void operator=(const fvFieldDecomposer&) = delete;
+    //- No copy construct
+    fvFieldDecomposer(const fvFieldDecomposer&) = delete;
 
+    //- No copy assignment
+    void operator=(const fvFieldDecomposer&) = delete;
 
-public:
 
     // Constructors
 
-        //- Construct from components
+        //- Construct without mappers, added later with reset()
+        fvFieldDecomposer
+        (
+            const Foam::zero,
+            const fvMesh& procMesh,
+            const labelList& faceAddressing,
+            const labelList& cellAddressing,
+            const labelList& boundaryAddressing
+        );
+
+        //- Construct from components using information from the complete mesh
         fvFieldDecomposer
         (
             const fvMesh& completeMesh,
@@ -252,13 +263,56 @@ public:
             const labelList& boundaryAddressing
         );
 
+        //- Construct from components without the complete mesh
+        fvFieldDecomposer
+        (
+            // Information about the complete mesh
+            const List<labelRange>& boundaryRanges,
+            const labelUList& faceOwner,
+            const labelUList& faceNeigbour,
+
+            // Addressing for processor mesh
+            const fvMesh& procMesh,
+            const labelList& faceAddressing,
+            const labelList& cellAddressing,
+            const labelList& boundaryAddressing
+        );
+
 
     //- Destructor
-    ~fvFieldDecomposer();
+    ~fvFieldDecomposer() = default;
 
 
     // Member Functions
 
+        //- True if no mappers have been allocated
+        bool empty() const;
+
+        //- Remove all mappers
+        void clear();
+
+        //- Reset mappers using information from the complete mesh
+        void reset(const fvMesh& completeMesh);
+
+        //- Reset mapper using information about the complete mesh
+        void reset
+        (
+            const List<labelRange>& boundaryRanges,
+            const labelUList& faceOwner,
+            const labelUList& faceNeigbour
+        );
+
+
+    // Mapping
+
+        //- Decompose internal field
+        template<class Type>
+        tmp<DimensionedField<Type, volMesh>>
+        decomposeField
+        (
+            const DimensionedField<Type, volMesh>& field
+        ) const;
+
         //- Decompose volume field
         template<class Type>
         tmp<GeometricField<Type, fvPatchField, volMesh>>
@@ -276,6 +330,7 @@ public:
             const GeometricField<Type, fvsPatchField, surfaceMesh>& field
         ) const;
 
+        //- Decompose list of fields
         template<class GeoField>
         void decomposeFields(const PtrList<GeoField>& fields) const;
 };
@@ -288,7 +343,7 @@ public:
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
 
 #ifdef NoRepository
-    #include "fvFieldDecomposerDecomposeFields.C"
+    #include "fvFieldDecomposerFields.C"
 #endif
 
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
diff --git a/src/parallel/decompose/decompose/fvFieldDecomposerDecomposeFields.C b/src/parallel/decompose/decompose/fvFieldDecomposerFields.C
similarity index 92%
rename from src/parallel/decompose/decompose/fvFieldDecomposerDecomposeFields.C
rename to src/parallel/decompose/decompose/fvFieldDecomposerFields.C
index 2e9bcd9a5a5b6134dd375505a1c32dc6faf2591d..05c730a27dd30eb42432c250b442a9803069f028 100644
--- a/src/parallel/decompose/decompose/fvFieldDecomposerDecomposeFields.C
+++ b/src/parallel/decompose/decompose/fvFieldDecomposerFields.C
@@ -6,6 +6,7 @@
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
     Copyright (C) 2011-2016 OpenFOAM Foundation
+    Copyright (C) 2021 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -34,6 +35,36 @@ License
 
 // * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //
 
+template<class Type>
+Foam::tmp<Foam::DimensionedField<Type, Foam::volMesh>>
+Foam::fvFieldDecomposer::decomposeField
+(
+    const DimensionedField<Type, volMesh>& field
+) const
+{
+    // Create and map the internal field values
+    Field<Type> mappedField(field, cellAddressing_);
+
+    // Create the field for the processor
+    return
+        tmp<DimensionedField<Type, volMesh>>::New
+        (
+            IOobject
+            (
+                field.name(),
+                procMesh_.time().timeName(),
+                procMesh_,
+                IOobject::NO_READ,
+                IOobject::NO_WRITE,
+                false
+            ),
+            procMesh_,
+            field.dimensions(),
+            std::move(mappedField)
+        );
+}
+
+
 template<class Type>
 Foam::tmp<Foam::GeometricField<Type, Foam::fvPatchField, Foam::volMesh>>
 Foam::fvFieldDecomposer::decomposeField
@@ -338,9 +369,9 @@ void Foam::fvFieldDecomposer::decomposeFields
     const PtrList<GeoField>& fields
 ) const
 {
-    forAll(fields, fieldi)
+    for (const auto& fld : fields)
     {
-        decomposeField(fields[fieldi])().write();
+        decomposeField(fld)().write();
     }
 }
 
diff --git a/applications/utilities/parallelProcessing/decomposePar/pointFieldDecomposer.C b/src/parallel/decompose/decompose/pointFieldDecomposer.C
similarity index 72%
rename from applications/utilities/parallelProcessing/decomposePar/pointFieldDecomposer.C
rename to src/parallel/decompose/decompose/pointFieldDecomposer.C
index a4c40ee980697cf2161506376bc9b923ae1b9603..4b1a29a1c51e70c67001127875068bd1dc2f5b25 100644
--- a/applications/utilities/parallelProcessing/decomposePar/pointFieldDecomposer.C
+++ b/src/parallel/decompose/decompose/pointFieldDecomposer.C
@@ -6,6 +6,7 @@
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
     Copyright (C) 2011-2016 OpenFOAM Foundation
+    Copyright (C) 2021 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -78,51 +79,82 @@ Foam::pointFieldDecomposer::patchFieldDecomposer::patchFieldDecomposer
 
 Foam::pointFieldDecomposer::pointFieldDecomposer
 (
-    const pointMesh& completeMesh,
+    const Foam::zero,
     const pointMesh& procMesh,
     const labelList& pointAddressing,
     const labelList& boundaryAddressing
 )
 :
-    completeMesh_(completeMesh),
     procMesh_(procMesh),
     pointAddressing_(pointAddressing),
     boundaryAddressing_(boundaryAddressing),
-    patchFieldDecomposerPtrs_
+    // Mappers
+    patchFieldDecomposerPtrs_()
+{}
+
+
+Foam::pointFieldDecomposer::pointFieldDecomposer
+(
+    const pointMesh& completeMesh,
+    const pointMesh& procMesh,
+    const labelList& pointAddressing,
+    const labelList& boundaryAddressing
+)
+:
+    pointFieldDecomposer
     (
-        procMesh_.boundary().size(),
-        static_cast<patchFieldDecomposer*>(nullptr)
+        zero{},
+        procMesh,
+        pointAddressing,
+        boundaryAddressing
     )
 {
-    forAll(boundaryAddressing_, patchi)
-    {
-        if (boundaryAddressing_[patchi] >= 0)
-        {
-            patchFieldDecomposerPtrs_[patchi] = new patchFieldDecomposer
-            (
-                completeMesh_.boundary()[boundaryAddressing_[patchi]],
-                procMesh_.boundary()[patchi],
-                pointAddressing_
-            );
-        }
-    }
+    reset(completeMesh);
 }
 
 
-// * * * * * * * * * * * * * * * * Destructor  * * * * * * * * * * * * * * * //
+// * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //
+
+bool Foam::pointFieldDecomposer::empty() const
+{
+    return patchFieldDecomposerPtrs_.empty();
+}
+
 
-Foam::pointFieldDecomposer::~pointFieldDecomposer()
+void Foam::pointFieldDecomposer::clear()
 {
-    forAll(patchFieldDecomposerPtrs_, patchi)
+    patchFieldDecomposerPtrs_.clear();
+}
+
+
+void Foam::pointFieldDecomposer::reset
+(
+    const pointMesh& completeMesh
+)
+{
+    clear();
+    const label nMappers = procMesh_.boundary().size();
+    patchFieldDecomposerPtrs_.resize(nMappers);
+
+    forAll(boundaryAddressing_, patchi)
     {
-        if (patchFieldDecomposerPtrs_[patchi])
+        const label oldPatchi = boundaryAddressing_[patchi];
+
+        if (oldPatchi >= 0)
         {
-            delete patchFieldDecomposerPtrs_[patchi];
+            patchFieldDecomposerPtrs_.set
+            (
+                patchi,
+                new patchFieldDecomposer
+                (
+                    completeMesh.boundary()[oldPatchi],
+                    procMesh_.boundary()[patchi],
+                    pointAddressing_
+                )
+            );
         }
     }
 }
 
 
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
-
 // ************************************************************************* //
diff --git a/applications/utilities/parallelProcessing/decomposePar/pointFieldDecomposer.H b/src/parallel/decompose/decompose/pointFieldDecomposer.H
similarity index 80%
rename from applications/utilities/parallelProcessing/decomposePar/pointFieldDecomposer.H
rename to src/parallel/decompose/decompose/pointFieldDecomposer.H
index f59b4f33b200a2d91a53d94831b22aa70ee51e01..b4c730105998691ae90b6e36d19663376704e672 100644
--- a/applications/utilities/parallelProcessing/decomposePar/pointFieldDecomposer.H
+++ b/src/parallel/decompose/decompose/pointFieldDecomposer.H
@@ -6,6 +6,7 @@
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
     Copyright (C) 2011-2016 OpenFOAM Foundation
+    Copyright (C) 2021 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -31,7 +32,7 @@ Description
 
 SourceFiles
     pointFieldDecomposer.C
-    pointFieldDecomposerDecomposeFields.C
+    pointFieldDecomposerFields.C
 
 \*---------------------------------------------------------------------------*/
 
@@ -48,12 +49,11 @@ namespace Foam
 {
 
 /*---------------------------------------------------------------------------*\
-               Class pointFieldDecomposer Declaration
+                    Class pointFieldDecomposer Declaration
 \*---------------------------------------------------------------------------*/
 
 class pointFieldDecomposer
 {
-
 public:
 
         //- Point patch field decomposer class
@@ -107,10 +107,7 @@ public:
 
 private:
 
-    // Private data
-
-        //- Reference to complete mesh
-        const pointMesh& completeMesh_;
+    // Private Data
 
         //- Reference to processor mesh
         const pointMesh& procMesh_;
@@ -122,22 +119,29 @@ private:
         const labelList& boundaryAddressing_;
 
         //- List of patch field decomposers
-        List<patchFieldDecomposer*> patchFieldDecomposerPtrs_;
-
+        PtrList<patchFieldDecomposer> patchFieldDecomposerPtrs_;
 
-    // Private Member Functions
 
-        //- No copy construct
-        pointFieldDecomposer(const pointFieldDecomposer&) = delete;
+public:
 
-        //- No copy assignment
-        void operator=(const pointFieldDecomposer&) = delete;
+    //- No copy construct
+    pointFieldDecomposer(const pointFieldDecomposer&) = delete;
 
+    //- No copy assignment
+    void operator=(const pointFieldDecomposer&) = delete;
 
-public:
 
     // Constructors
 
+        //- Construct without mappers, added later with reset()
+        pointFieldDecomposer
+        (
+            const Foam::zero,
+            const pointMesh& procMesh,
+            const labelList& pointAddressing,
+            const labelList& boundaryAddressing
+        );
+
         //- Construct from components
         pointFieldDecomposer
         (
@@ -149,11 +153,23 @@ public:
 
 
     //- Destructor
-    ~pointFieldDecomposer();
+    ~pointFieldDecomposer() = default;
 
 
     // Member Functions
 
+        //- True if no mappers have been allocated
+        bool empty() const;
+
+        //- Remove all mappers
+        void clear();
+
+        //- Reset mappers using information from the complete mesh
+        void reset(const pointMesh& completeMesh);
+
+
+    // Mapping
+
         //- Decompose point field
         template<class Type>
         tmp<GeometricField<Type, pointPatchField, pointMesh>>
@@ -162,6 +178,7 @@ public:
             const GeometricField<Type, pointPatchField, pointMesh>&
         ) const;
 
+        //- Decompose list of fields
         template<class GeoField>
         void decomposeFields(const PtrList<GeoField>& fields) const;
 };
@@ -174,7 +191,7 @@ public:
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
 
 #ifdef NoRepository
-    #include "pointFieldDecomposerDecomposeFields.C"
+    #include "pointFieldDecomposerFields.C"
 #endif
 
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
diff --git a/applications/utilities/parallelProcessing/decomposePar/pointFieldDecomposerDecomposeFields.C b/src/parallel/decompose/decompose/pointFieldDecomposerFields.C
similarity index 90%
rename from applications/utilities/parallelProcessing/decomposePar/pointFieldDecomposerDecomposeFields.C
rename to src/parallel/decompose/decompose/pointFieldDecomposerFields.C
index bdbea0cb43cfc04858064af1363c2c53d17eda69..aea3d5770808935587d72bcfaa7407a1da7a94cf 100644
--- a/applications/utilities/parallelProcessing/decomposePar/pointFieldDecomposerDecomposeFields.C
+++ b/src/parallel/decompose/decompose/pointFieldDecomposerFields.C
@@ -6,6 +6,7 @@
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
     Copyright (C) 2011-2016 OpenFOAM Foundation
+    Copyright (C) 2021 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -46,7 +47,7 @@ Foam::pointFieldDecomposer::decomposeField
     // Create and map the patch field values
     forAll(boundaryAddressing_, patchi)
     {
-        if (patchFieldDecomposerPtrs_[patchi])
+        if (patchFieldDecomposerPtrs_.set(patchi))
         {
             patchFields.set
             (
@@ -56,7 +57,7 @@ Foam::pointFieldDecomposer::decomposeField
                     field.boundaryField()[boundaryAddressing_[patchi]],
                     procMesh_.boundary()[patchi],
                     DimensionedField<Type, pointMesh>::null(),
-                    *patchFieldDecomposerPtrs_[patchi]
+                    patchFieldDecomposerPtrs_[patchi]
                 )
             );
         }
@@ -75,9 +76,8 @@ Foam::pointFieldDecomposer::decomposeField
     }
 
     // Create the field for the processor
-    return tmp<GeometricField<Type, pointPatchField, pointMesh>>
-    (
-        new GeometricField<Type, pointPatchField, pointMesh>
+    return
+        tmp<GeometricField<Type, pointPatchField, pointMesh>>::New
         (
             IOobject
             (
@@ -92,8 +92,7 @@ Foam::pointFieldDecomposer::decomposeField
             field.dimensions(),
             internalField,
             patchFields
-        )
-    );
+        );
 }
 
 
@@ -103,9 +102,9 @@ void Foam::pointFieldDecomposer::decomposeFields
     const PtrList<GeoField>& fields
 ) const
 {
-    forAll(fields, fieldi)
+    for (const auto& fld : fields)
     {
-        decomposeField(fields[fieldi])().write();
+        decomposeField(fld)().write();
     }
 }
 
diff --git a/src/parallel/decompose/faDecompose/Make/files b/src/parallel/decompose/faDecompose/Make/files
new file mode 100644
index 0000000000000000000000000000000000000000..af2a87bf961960bbe653e2405ac209cb2f79a6fe
--- /dev/null
+++ b/src/parallel/decompose/faDecompose/Make/files
@@ -0,0 +1,4 @@
+faFieldDecomposer.C
+faMeshDecomposition.C
+
+LIB = $(FOAM_LIBBIN)/libfaDecompose
diff --git a/src/parallel/decompose/faDecompose/Make/options b/src/parallel/decompose/faDecompose/Make/options
new file mode 100644
index 0000000000000000000000000000000000000000..b5d6f595dc9eb1057e7ddc89e0ed226d27e17007
--- /dev/null
+++ b/src/parallel/decompose/faDecompose/Make/options
@@ -0,0 +1,9 @@
+EXE_INC = \
+    -I$(LIB_SRC)/finiteVolume/lnInclude \
+    -I$(LIB_SRC)/finiteArea/lnInclude \
+    -I$(LIB_SRC)/meshTools/lnInclude \
+
+LIB_LIBS = \
+    -lfiniteVolume \
+    -lfiniteArea \
+    -lmeshTools
diff --git a/src/parallel/decompose/faDecompose/faFieldDecomposer.C b/src/parallel/decompose/faDecompose/faFieldDecomposer.C
new file mode 100644
index 0000000000000000000000000000000000000000..f0966dfe47a7565a28b1a56cb6cec35798ead540
--- /dev/null
+++ b/src/parallel/decompose/faDecompose/faFieldDecomposer.C
@@ -0,0 +1,357 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  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/>.
+
+\*---------------------------------------------------------------------------*/
+
+#include "faFieldDecomposer.H"
+
+// * * * * * * * * * * * * * * * * Constructors  * * * * * * * * * * * * * * //
+
+Foam::faFieldDecomposer::patchFieldDecomposer::patchFieldDecomposer
+(
+    const label sizeBeforeMapping,
+    const labelUList& addressingSlice,
+    const label addressingOffset
+)
+:
+    sizeBeforeMapping_(sizeBeforeMapping),
+    directAddressing_(addressingSlice)
+{
+    forAll(directAddressing_, i)
+    {
+        // Subtract one to align addressing.
+        // directAddressing_[i] -= addressingOffset + 1;
+        // ZT, 12/Nov/2010
+        directAddressing_[i] -= addressingOffset;
+    }
+}
+
+
+Foam::faFieldDecomposer::processorAreaPatchFieldDecomposer::
+processorAreaPatchFieldDecomposer
+(
+    const label nTotalFaces,
+    const labelUList& owner,  // == mesh.edgeOwner()
+    const labelUList& neigh,  // == mesh.edgeNeighbour()
+    const labelUList& addressingSlice,
+    const scalarField& weights
+)
+:
+    sizeBeforeMapping_(nTotalFaces),
+    addressing_(addressingSlice.size()),
+    weights_(addressingSlice.size())
+{
+    forAll(addressing_, i)
+    {
+        // Subtract one to align addressing.
+        label ai = addressingSlice[i];
+//         label ai = mag(addressingSlice[i]) - 1;
+
+        if (ai < neigh.size())
+        {
+            // This is a regular edge. it has been an internal edge
+            // of the original mesh and now it has become a edge
+            // on the parallel boundary
+            addressing_[i].resize(2);
+            weights_[i].resize(2);
+
+            addressing_[i][0] = owner[ai];
+            addressing_[i][1] = neigh[ai];
+
+            if (ai < weights.size())
+            {
+                // Edge weights exist/are usable
+                weights_[i][0] = weights[ai];
+                weights_[i][1] = 1.0 - weights[ai];
+            }
+            else
+            {
+                // No edge weights. use equal weighting
+                weights_[i][0] = 0.5;
+                weights_[i][1] = 0.5;
+            }
+        }
+        else
+        {
+            // This is a edge that used to be on a cyclic boundary
+            // but has now become a parallel patch edge. I cannot
+            // do the interpolation properly (I would need to look
+            // up the different (edge) list of data), so I will
+            // just grab the value from the owner face
+
+            addressing_[i].resize(1);
+            weights_[i].resize(1);
+
+            addressing_[i][0] = owner[ai];
+
+            weights_[i][0] = 1.0;
+        }
+    }
+}
+
+
+Foam::faFieldDecomposer::processorAreaPatchFieldDecomposer::
+processorAreaPatchFieldDecomposer
+(
+    const faMesh& mesh,
+    const labelUList& addressingSlice
+)
+:
+    processorAreaPatchFieldDecomposer
+    (
+        mesh.nFaces(),
+        mesh.edgeOwner(),
+        mesh.edgeNeighbour(),
+        addressingSlice,
+        mesh.weights().internalField()
+    )
+{}
+
+
+Foam::faFieldDecomposer::processorEdgePatchFieldDecomposer::
+processorEdgePatchFieldDecomposer
+(
+    label sizeBeforeMapping,
+    const labelUList& addressingSlice
+)
+:
+    sizeBeforeMapping_(sizeBeforeMapping),
+    addressing_(addressingSlice.size()),
+    weights_(addressingSlice.size())
+{
+    forAll(addressing_, i)
+    {
+        addressing_[i].resize(1);
+        weights_[i].resize(1);
+
+        addressing_[i][0] = mag(addressingSlice[i]) - 1;
+        weights_[i][0] = sign(addressingSlice[i]);
+    }
+}
+
+
+// * * * * * * * * * * * * * * * * Constructors  * * * * * * * * * * * * * * //
+
+Foam::faFieldDecomposer::faFieldDecomposer
+(
+    const Foam::zero,
+    const faMesh& procMesh,
+    const labelList& edgeAddressing,
+    const labelList& faceAddressing,
+    const labelList& boundaryAddressing
+)
+:
+    procMesh_(procMesh),
+    edgeAddressing_(edgeAddressing),
+    faceAddressing_(faceAddressing),
+    boundaryAddressing_(boundaryAddressing),
+    // Mappers
+    patchFieldDecomposerPtrs_(),
+    processorAreaPatchFieldDecomposerPtrs_(),
+    processorEdgePatchFieldDecomposerPtrs_()
+{}
+
+
+Foam::faFieldDecomposer::faFieldDecomposer
+(
+    const faMesh& completeMesh,
+    const faMesh& procMesh,
+    const labelList& edgeAddressing,
+    const labelList& faceAddressing,
+    const labelList& boundaryAddressing
+)
+:
+    faFieldDecomposer
+    (
+        zero{},
+        procMesh,
+        edgeAddressing,
+        faceAddressing,
+        boundaryAddressing
+    )
+{
+    reset(completeMesh);
+}
+
+
+Foam::faFieldDecomposer::faFieldDecomposer
+(
+    const label nTotalFaces,
+    const List<labelRange>& boundaryRanges,
+    const labelUList& edgeOwner,
+    const labelUList& edgeNeigbour,
+
+    const faMesh& procMesh,
+    const labelList& edgeAddressing,
+    const labelList& faceAddressing,
+    const labelList& boundaryAddressing
+)
+:
+    faFieldDecomposer
+    (
+        zero{},
+        procMesh,
+        edgeAddressing,
+        faceAddressing,
+        boundaryAddressing
+    )
+{
+    reset(nTotalFaces, boundaryRanges, edgeOwner, edgeNeigbour);
+}
+
+
+// * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //
+
+bool Foam::faFieldDecomposer::empty() const
+{
+    return patchFieldDecomposerPtrs_.empty();
+}
+
+
+void Foam::faFieldDecomposer::clear()
+{
+    patchFieldDecomposerPtrs_.clear();
+    processorAreaPatchFieldDecomposerPtrs_.clear();
+    processorEdgePatchFieldDecomposerPtrs_.clear();
+}
+
+
+void Foam::faFieldDecomposer::reset
+(
+    const label nTotalFaces,
+    const List<labelRange>& boundaryRanges,
+    const labelUList& edgeOwner,
+    const labelUList& edgeNeigbour
+)
+{
+    clear();
+    const label nMappers = procMesh_.boundary().size();
+
+    patchFieldDecomposerPtrs_.resize(nMappers);
+    processorAreaPatchFieldDecomposerPtrs_.resize(nMappers);
+    processorEdgePatchFieldDecomposerPtrs_.resize(nMappers);
+
+    forAll(boundaryAddressing_, patchi)
+    {
+        const label oldPatchi = boundaryAddressing_[patchi];
+        const faPatch& fap = procMesh_.boundary()[patchi];
+        const labelSubList localPatchSlice(fap.patchSlice(edgeAddressing_));
+
+        if (oldPatchi >= 0)
+        {
+            patchFieldDecomposerPtrs_.set
+            (
+                patchi,
+                new patchFieldDecomposer
+                (
+                    boundaryRanges[oldPatchi].size(),
+                    localPatchSlice,
+                    boundaryRanges[oldPatchi].start()
+                )
+            );
+        }
+        else
+        {
+            processorAreaPatchFieldDecomposerPtrs_.set
+            (
+                patchi,
+                new processorAreaPatchFieldDecomposer
+                (
+                    nTotalFaces,
+                    edgeOwner,
+                    edgeNeigbour,
+                    localPatchSlice
+                )
+            );
+
+            processorEdgePatchFieldDecomposerPtrs_.set
+            (
+                patchi,
+                new processorEdgePatchFieldDecomposer
+                (
+                    procMesh_.boundary()[patchi].size(),
+                    static_cast<const labelUList&>(localPatchSlice)
+                )
+            );
+        }
+    }
+}
+
+
+void Foam::faFieldDecomposer::reset(const faMesh& completeMesh)
+{
+    clear();
+    const label nMappers = procMesh_.boundary().size();
+    patchFieldDecomposerPtrs_.resize(nMappers);
+    processorAreaPatchFieldDecomposerPtrs_.resize(nMappers);
+    processorEdgePatchFieldDecomposerPtrs_.resize(nMappers);
+
+    forAll(boundaryAddressing_, patchi)
+    {
+        const label oldPatchi = boundaryAddressing_[patchi];
+        const faPatch& fap = procMesh_.boundary()[patchi];
+        const labelSubList localPatchSlice(fap.patchSlice(edgeAddressing_));
+
+        if (oldPatchi >= 0)
+        {
+            patchFieldDecomposerPtrs_.set
+            (
+                patchi,
+                new patchFieldDecomposer
+                (
+                    completeMesh.boundary()[oldPatchi].size(),
+                    localPatchSlice,
+                    completeMesh.boundary()[oldPatchi].start()
+                )
+            );
+        }
+        else
+        {
+            processorAreaPatchFieldDecomposerPtrs_.set
+            (
+                patchi,
+                new processorAreaPatchFieldDecomposer
+                (
+                    completeMesh,
+                    localPatchSlice
+                )
+            );
+
+            processorEdgePatchFieldDecomposerPtrs_.set
+            (
+                patchi,
+                new processorEdgePatchFieldDecomposer
+                (
+                    procMesh_.boundary()[patchi].size(),
+                    static_cast<const labelUList&>(localPatchSlice)
+                )
+            );
+        }
+    }
+}
+
+
+// ************************************************************************* //
diff --git a/applications/utilities/parallelProcessing/decomposePar/faFieldDecomposer.H b/src/parallel/decompose/faDecompose/faFieldDecomposer.H
similarity index 70%
rename from applications/utilities/parallelProcessing/decomposePar/faFieldDecomposer.H
rename to src/parallel/decompose/faDecompose/faFieldDecomposer.H
index 16928957bb5730f1bb129b60c07e8f6201b08c6d..a6ff53cb474e6d498dd0edc234066754b5ad1ee2 100644
--- a/applications/utilities/parallelProcessing/decomposePar/faFieldDecomposer.H
+++ b/src/parallel/decompose/faDecompose/faFieldDecomposer.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.
@@ -35,7 +36,7 @@ Author
 
 SourceFiles
     faFieldDecomposer.C
-    faFieldDecomposerDecomposeFields.C
+    faFieldDecomposerFields.C
 
 \*---------------------------------------------------------------------------*/
 
@@ -51,6 +52,7 @@ SourceFiles
 namespace Foam
 {
 
+// Forward Declarations
 class IOobjectList;
 
 /*---------------------------------------------------------------------------*\
@@ -66,7 +68,7 @@ public:
         :
             public faPatchFieldMapper
         {
-            // Private data
+            // Private Data
 
                 label sizeBeforeMapping_;
                 labelList directAddressing_;
@@ -118,7 +120,7 @@ public:
         :
             public faPatchFieldMapper
         {
-            // Private data
+            // Private Data
 
                 label sizeBeforeMapping_;
                 labelListList addressing_;
@@ -126,7 +128,17 @@ public:
 
         public:
 
-            //- Construct given addressing
+            //- Construct addressing from details
+            processorAreaPatchFieldDecomposer
+            (
+                const label nTotalFaces,
+                const labelUList& edgeOwner,
+                const labelUList& edgeNeigbour,
+                const labelUList& addressingSlice,
+                const scalarField& edgeWeights = scalarField::null()
+            );
+
+            //- Construct given addressing from complete mesh
             processorAreaPatchFieldDecomposer
             (
                 const faMesh& mesh,
@@ -134,7 +146,7 @@ public:
             );
 
 
-            // Member functions
+            // Member Functions
 
                 label size() const
                 {
@@ -187,7 +199,7 @@ public:
             );
 
 
-            // Member functions
+            // Member Functions
 
                 label size() const
                 {
@@ -223,10 +235,7 @@ public:
 
 private:
 
-    // Private data
-
-        //- Reference to complete mesh
-        const faMesh& completeMesh_;
+    // Private Data
 
         //- Reference to processor mesh
         const faMesh& procMesh_;
@@ -241,12 +250,12 @@ private:
         const labelList& boundaryAddressing_;
 
         //- List of patch field decomposers
-        List<patchFieldDecomposer*> patchFieldDecomposerPtrs_;
+        PtrList<patchFieldDecomposer> patchFieldDecomposerPtrs_;
 
-        List<processorAreaPatchFieldDecomposer*>
+        PtrList<processorAreaPatchFieldDecomposer>
             processorAreaPatchFieldDecomposerPtrs_;
 
-        List<processorEdgePatchFieldDecomposer*>
+        PtrList<processorEdgePatchFieldDecomposer>
             processorEdgePatchFieldDecomposerPtrs_;
 
 
@@ -263,7 +272,17 @@ public:
 
     // Constructors
 
-        //- Construct from components
+        //- Construct without mappers, added later with reset()
+        faFieldDecomposer
+        (
+            const Foam::zero,
+            const faMesh& procMesh,
+            const labelList& edgeAddressing,
+            const labelList& faceAddressing,
+            const labelList& boundaryAddressing
+        );
+
+        //- Construct from components using information from the complete mesh
         faFieldDecomposer
         (
             const faMesh& completeMesh,
@@ -273,14 +292,50 @@ public:
             const labelList& boundaryAddressing
         );
 
+        //- Construct from components without the complete mesh
+        faFieldDecomposer
+        (
+            // Information about the complete mesh
+            const label nTotalFaces,
+            const List<labelRange>& boundaryRanges,
+            const labelUList& edgeOwner,
+            const labelUList& edgeNeigbour,
+
+            // Addressing for processor mesh
+            const faMesh& procMesh,
+            const labelList& edgeAddressing,
+            const labelList& faceAddressing,
+            const labelList& boundaryAddressing
+        );
 
-    // Destructor
 
-        ~faFieldDecomposer();
+    //- Destructor
+    ~faFieldDecomposer() = default;
 
 
     // Member Functions
 
+        //- True if no mappers have been allocated
+        bool empty() const;
+
+        //- Remove all mappers
+        void clear();
+
+        //- Reset mappers using information from the complete mesh
+        void reset(const faMesh& completeMesh);
+
+        //- Reset mapper using information about the complete mesh
+        void reset
+        (
+            const label nTotalFaces,
+            const List<labelRange>& boundaryRanges,
+            const labelUList& edgeOwner,
+            const labelUList& edgeNeigbour
+        );
+
+
+    // Mapping
+
         //- Decompose area field
         template<class Type>
         tmp<GeometricField<Type, faPatchField, areaMesh>>
@@ -299,6 +354,33 @@ public:
 
         template<class GeoField>
         void decomposeFields(const PtrList<GeoField>& fields) const;
+
+
+    // Reading helpers
+
+        //- Read the fields and hold on the pointer list
+        template
+        <
+            class Type,
+            template<class> class PatchField,
+            class GeoMesh
+        >
+        static void readFields
+        (
+            const typename GeoMesh::Mesh& mesh,
+            const IOobjectList& objects,
+            PtrList<GeometricField<Type, PatchField, GeoMesh>>& fields,
+            const bool readOldTime
+        );
+
+        //- Read fields and hold on the pointer list
+        template<class Mesh, class GeoField>
+        static void readFields
+        (
+            const Mesh& mesh,
+            const IOobjectList& objects,
+            PtrList<GeoField>& fields
+        );
 };
 
 
@@ -309,7 +391,8 @@ public:
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
 
 #ifdef NoRepository
-#   include "faFieldDecomposerDecomposeFields.C"
+    #include "faFieldDecomposerFields.C"
+    #include "faFieldDecomposerReadFields.C"
 #endif
 
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
diff --git a/applications/utilities/parallelProcessing/decomposePar/faFieldDecomposerDecomposeFields.C b/src/parallel/decompose/faDecompose/faFieldDecomposerFields.C
similarity index 81%
rename from applications/utilities/parallelProcessing/decomposePar/faFieldDecomposerDecomposeFields.C
rename to src/parallel/decompose/faDecompose/faFieldDecomposerFields.C
index ad0bebd67d83c03212d12631566de0d9ed020325..1b7eaea40f260387e5584a03cceab28d0af9af35 100644
--- a/applications/utilities/parallelProcessing/decomposePar/faFieldDecomposerDecomposeFields.C
+++ b/src/parallel/decompose/faDecompose/faFieldDecomposerFields.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.
@@ -29,16 +30,11 @@ License
 #include "processorFaPatchField.H"
 #include "processorFaePatchField.H"
 
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
-
-namespace Foam
-{
-
 // * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //
 
 template<class Type>
-tmp<GeometricField<Type, faPatchField, areaMesh>>
-faFieldDecomposer::decomposeField
+Foam::tmp<Foam::GeometricField<Type, Foam::faPatchField, Foam::areaMesh>>
+Foam::faFieldDecomposer::decomposeField
 (
     const GeometricField<Type, faPatchField, areaMesh>& field
 ) const
@@ -51,17 +47,19 @@ faFieldDecomposer::decomposeField
 
     forAll(boundaryAddressing_, patchi)
     {
-        if (boundaryAddressing_[patchi] >= 0)
+        const label oldPatchi = boundaryAddressing_[patchi];
+
+        if (oldPatchi >= 0)
         {
             patchFields.set
             (
                 patchi,
                 faPatchField<Type>::New
                 (
-                    field.boundaryField()[boundaryAddressing_[patchi]],
+                    field.boundaryField()[oldPatchi],
                     procMesh_.boundary()[patchi],
                     DimensionedField<Type, areaMesh>::null(),
-                    *patchFieldDecomposerPtrs_[patchi]
+                    patchFieldDecomposerPtrs_[patchi]
                 )
             );
         }
@@ -77,7 +75,7 @@ faFieldDecomposer::decomposeField
                     Field<Type>
                     (
                         field.internalField(),
-                        *processorAreaPatchFieldDecomposerPtrs_[patchi]
+                        processorAreaPatchFieldDecomposerPtrs_[patchi]
                     )
                 )
             );
@@ -85,9 +83,8 @@ faFieldDecomposer::decomposeField
     }
 
     // Create the field for the processor
-    return tmp<GeometricField<Type, faPatchField, areaMesh>>
-    (
-        new GeometricField<Type, faPatchField, areaMesh>
+    return
+        tmp<GeometricField<Type, faPatchField, areaMesh>>::New
         (
             IOobject
             (
@@ -101,14 +98,13 @@ faFieldDecomposer::decomposeField
             field.dimensions(),
             internalField,
             patchFields
-        )
-    );
+        );
 }
 
 
 template<class Type>
-tmp<GeometricField<Type, faePatchField, edgeMesh>>
-faFieldDecomposer::decomposeField
+Foam::tmp<Foam::GeometricField<Type, Foam::faePatchField, Foam::edgeMesh>>
+Foam::faFieldDecomposer::decomposeField
 (
     const GeometricField<Type, faePatchField, edgeMesh>& field
 ) const
@@ -147,7 +143,7 @@ faFieldDecomposer::decomposeField
 
     forAll(field.boundaryField(), patchi)
     {
-        const Field<Type> & p = field.boundaryField()[patchi];
+        const Field<Type>& p = field.boundaryField()[patchi];
 
         const label patchStart = field.mesh().boundary()[patchi].start();
 
@@ -162,17 +158,19 @@ faFieldDecomposer::decomposeField
 
     forAll(boundaryAddressing_, patchi)
     {
-        if (boundaryAddressing_[patchi] >= 0)
+        const label oldPatchi = boundaryAddressing_[patchi];
+
+        if (oldPatchi >= 0)
         {
             patchFields.set
             (
                 patchi,
                 faePatchField<Type>::New
                 (
-                    field.boundaryField()[boundaryAddressing_[patchi]],
+                    field.boundaryField()[oldPatchi],
                     procMesh_.boundary()[patchi],
                     DimensionedField<Type, edgeMesh>::null(),
-                    *patchFieldDecomposerPtrs_[patchi]
+                    patchFieldDecomposerPtrs_[patchi]
                 )
             );
         }
@@ -188,7 +186,7 @@ faFieldDecomposer::decomposeField
                     Field<Type>
                     (
                         allEdgeField,
-                        *processorEdgePatchFieldDecomposerPtrs_[patchi]
+                        processorEdgePatchFieldDecomposerPtrs_[patchi]
                     )
                 )
             );
@@ -196,9 +194,8 @@ faFieldDecomposer::decomposeField
     }
 
     // Create the field for the processor
-    return tmp<GeometricField<Type, faePatchField, edgeMesh>>
-    (
-        new GeometricField<Type, faePatchField, edgeMesh>
+    return
+        tmp<GeometricField<Type, faePatchField, edgeMesh>>::New
         (
             IOobject
             (
@@ -212,13 +209,12 @@ faFieldDecomposer::decomposeField
             field.dimensions(),
             internalField,
             patchFields
-        )
-    );
+        );
 }
 
 
 template<class GeoField>
-void faFieldDecomposer::decomposeFields
+void Foam::faFieldDecomposer::decomposeFields
 (
     const PtrList<GeoField>& fields
 ) const
@@ -230,8 +226,4 @@ void faFieldDecomposer::decomposeFields
 }
 
 
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
-
-} // End namespace Foam
-
 // ************************************************************************* //
diff --git a/src/parallel/decompose/faDecompose/faFieldDecomposerReadFields.C b/src/parallel/decompose/faDecompose/faFieldDecomposerReadFields.C
new file mode 100644
index 0000000000000000000000000000000000000000..43529e3172db2ebe147e0495abb615011d178ef0
--- /dev/null
+++ b/src/parallel/decompose/faDecompose/faFieldDecomposerReadFields.C
@@ -0,0 +1,99 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
+   \\    /   O peration     |
+    \\  /    A nd           | www.openfoam.com
+     \\/     M anipulation  |
+-------------------------------------------------------------------------------
+    Copyright (C) 2011-2016 OpenFOAM Foundation
+    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 "faFieldDecomposer.H"
+#include "GeometricField.H"
+#include "IOobjectList.H"
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+template<class Type, template<class> class PatchField, class GeoMesh>
+void Foam::faFieldDecomposer::readFields
+(
+    const typename GeoMesh::Mesh& mesh,
+    const IOobjectList& objects,
+    PtrList<GeometricField<Type, PatchField, GeoMesh>>& fields,
+    const bool readOldTime
+)
+{
+    typedef GeometricField<Type, PatchField, GeoMesh> GeoField;
+
+    // Search list of objects for fields of type GeoField
+    IOobjectList fieldObjects(objects.lookupClass<GeoField>());
+
+    /// // Remove the cellDist field
+    /// auto iter = fieldObjects.find("cellDist");
+    /// if (iter.found())
+    /// {
+    ///     fieldObjects.erase(iter);
+    /// }
+
+    // Use sorted set of names
+    // (different processors might read objects in different order)
+    const wordList masterNames(fieldObjects.sortedNames());
+
+    // Construct the fields
+    fields.resize(masterNames.size());
+
+    forAll(masterNames, i)
+    {
+        const IOobject& io = *fieldObjects[masterNames[i]];
+
+        fields.set(i, new GeoField(io, mesh, readOldTime));
+    }
+}
+
+
+template<class Mesh, class GeoField>
+void Foam::faFieldDecomposer::readFields
+(
+    const Mesh& mesh,
+    const IOobjectList& objects,
+    PtrList<GeoField>& fields
+)
+{
+    // Search list of objects for fields of type GeomField
+    IOobjectList fieldObjects(objects.lookupClass<GeoField>());
+
+    // Use sorted set of names
+    // (different processors might read objects in different order)
+    const wordList masterNames(fieldObjects.sortedNames());
+
+    // Construct the fields
+    fields.resize(masterNames.size());
+
+    forAll(masterNames, i)
+    {
+        const IOobject& io = *fieldObjects[masterNames[i]];
+
+        fields.set(i, new GeoField(io, mesh));
+    }
+}
+
+
+// ************************************************************************* //
diff --git a/applications/utilities/parallelProcessing/decomposePar/faMeshDecomposition.C b/src/parallel/decompose/faDecompose/faMeshDecomposition.C
similarity index 92%
rename from applications/utilities/parallelProcessing/decomposePar/faMeshDecomposition.C
rename to src/parallel/decompose/faDecompose/faMeshDecomposition.C
index c08daf4b48b1a669451956f34e77aa48c05f10c3..aecad1c65e80567c08445ebc74405ba52ff073d0 100644
--- a/applications/utilities/parallelProcessing/decomposePar/faMeshDecomposition.C
+++ b/src/parallel/decompose/faDecompose/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.
@@ -34,14 +34,16 @@ License
 #include "faMesh.H"
 #include "OSspecific.H"
 #include "Map.H"
+#include "SLList.H"
 #include "globalMeshData.H"
-#include "decompositionModel.H"
 
 // * * * * * * * * * * * * * Private Member Functions  * * * * * * * * * * * //
 
 void Foam::faMeshDecomposition::distributeFaces()
 {
-    Info<< "\nCalculating distribution of faces" << endl;
+    const word& polyMeshRegionName = mesh().name();
+
+    Info<< "\nCalculating distribution of finiteArea faces" << endl;
 
     cpuTime decompositionTime;
 
@@ -58,7 +60,7 @@ void Foam::faMeshDecomposition::distributeFaces()
         (
             IOobject
             (
-                GeoMesh<polyMesh>::mesh_.name(),
+                polyMeshRegionName,
                 processorDb.timeName(),
                 processorDb
             )
@@ -160,22 +162,12 @@ void Foam::faMeshDecomposition::distributeFaces()
 Foam::faMeshDecomposition::faMeshDecomposition
 (
     const fvMesh& mesh,
-    const fileName& decompDictFile
+    const label nProcessors,
+    const dictionary& params
 )
 :
     faMesh(mesh),
-    decompDictFile_(decompDictFile),
-    nProcs_
-    (
-        decompositionMethod::nDomains
-        (
-            decompositionModel::New
-            (
-                mesh,
-                decompDictFile
-            )
-        )
-    ),
+    nProcs_(nProcessors),
     distributed_(false),
     hasGlobalFaceZones_(false),
     faceToProc_(nFaces()),
@@ -193,55 +185,75 @@ Foam::faMeshDecomposition::faMeshDecomposition
     procNeighbourProcessors_(nProcs_),
     procProcessorPatchSize_(nProcs_),
     procProcessorPatchStartIndex_(nProcs_),
-    globallySharedPoints_(0),
+    globallySharedPoints_(),
     cyclicParallel_(false)
 {
-    const decompositionModel& model = decompositionModel::New
-    (
-        mesh,
-        decompDictFile
-    );
-
-    model.readIfPresent("distributed", distributed_);
-    hasGlobalFaceZones_ = model.found("globalFaceZones");
+    updateParameters(params);
 }
 
 
 // * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //
 
+void Foam::faMeshDecomposition::updateParameters
+(
+    const dictionary& params
+)
+{
+    params.readIfPresent("distributed", distributed_);
+    if (params.found("globalFaceZones"))
+    {
+        hasGlobalFaceZones_ = true;
+    }
+}
+
+
 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
+    labelList nLocalFaces(nProcs_, Zero);
+
+    // Pass 1: determine local sizes, sanity check
+
+    forAll(faceToProc_, facei)
     {
-        List<SLList<label>> procFaceList(nProcs());
+        const label proci = faceToProc_[facei];
 
-        forAll(faceToProc_, faceI)
+        if (proci < 0 || proci >= nProcs_)
         {
-            if (faceToProc_[faceI] >= nProcs())
-            {
-                FatalErrorIn("Finite area mesh decomposition")
-                    << "Impossible processor label " << faceToProc_[faceI]
-                    << "for face " << faceI
-                    << abort(FatalError);
-            }
-            else
-            {
-                procFaceList[faceToProc_[faceI]].append(faceI);
-            }
+            FatalErrorInFunction
+                << "Invalid processor label " << proci
+                << " for face " << facei << nl
+                << abort(FatalError);
         }
-
-        // Convert linked lists into normal lists
-        forAll(procFaceList, procI)
+        else
         {
-            procFaceAddressing_[procI] = procFaceList[procI];
+            ++nLocalFaces[proci];
         }
     }
 
+    // Adjust lengths
+    forAll(nLocalFaces, proci)
+    {
+        procFaceAddressing_[proci].resize(nLocalFaces[proci]);
+        nLocalFaces[proci] = 0;  // restart list
+    }
+
+    // Pass 2: fill in local lists
+    forAll(faceToProc_, facei)
+    {
+        const label proci = faceToProc_[facei];
+        const label localFacei = nLocalFaces[proci];
+        ++nLocalFaces[proci];
+
+        procFaceAddressing_[proci][localFacei] = facei;
+    }
+
 
     // Find processor mesh faceLabels and ...
 
@@ -258,7 +270,7 @@ void Foam::faMeshDecomposition::decomposeMesh()
         (
             IOobject
             (
-                GeoMesh<polyMesh>::mesh_.name(),
+                polyMeshRegionName,
                 processorDb.timeName(),
                 processorDb
             )
@@ -314,7 +326,7 @@ void Foam::faMeshDecomposition::decomposeMesh()
                 fvFaceProcAddressingHash.find
                 (
                     faceLabels()[curProcFaceAddressing[faceI]] + 1
-                )();
+                ).val();
         }
 
         // create processor finite area mesh
@@ -324,38 +336,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 +379,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 +1063,7 @@ void Foam::faMeshDecomposition::decomposeMesh()
         (
             IOobject
             (
-                GeoMesh<polyMesh>::mesh_.name(),
+                polyMeshRegionName,
                 processorDb.timeName(),
                 processorDb
             )
@@ -1083,12 +1089,11 @@ void Foam::faMeshDecomposition::decomposeMesh()
             procProcessorPatchSize_[procI];
 
         labelListList& curPatchEdgeLabels = procPatchEdgeLabels_[procI];
-        curPatchEdgeLabels =
-            labelListList
-            (
-                curPatchSize.size()
-              + curProcessorPatchSize.size()
-            );
+        curPatchEdgeLabels.resize
+        (
+            curPatchSize.size()
+          + curProcessorPatchSize.size()
+        );
 
         forAll(curPatchSize, patchI)
         {
@@ -1137,8 +1142,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 +1181,7 @@ bool Foam::faMeshDecomposition::writeDecomposition()
         (
             IOobject
             (
-                GeoMesh<polyMesh>::mesh_.name(),
+                polyMeshRegionName,
                 processorDb.timeName(),
                 processorDb
             )
@@ -1195,7 +1201,7 @@ bool Foam::faMeshDecomposition::writeDecomposition()
         );
 
 
-        // create finite area mesh
+        // Create finite area mesh
         faMesh procMesh
         (
             procFvMesh,
@@ -1219,11 +1225,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 +1236,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 +1302,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/parallelProcessing/decomposePar/faMeshDecomposition.H b/src/parallel/decompose/faDecompose/faMeshDecomposition.H
similarity index 78%
rename from applications/utilities/parallelProcessing/decomposePar/faMeshDecomposition.H
rename to src/parallel/decompose/faDecompose/faMeshDecomposition.H
index 3c0526820c34c6106e61c81fd285e66b68ab2771..44a5323353aee7b6dba301bc2b3c18a2b800b857 100644
--- a/applications/utilities/parallelProcessing/decomposePar/faMeshDecomposition.H
+++ b/src/parallel/decompose/faDecompose/faMeshDecomposition.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.
@@ -44,7 +45,6 @@ SourceFiles
 #include "fvMesh.H"
 #include "faMesh.H"
 #include "labelList.H"
-#include "SLList.H"
 #include "PtrList.H"
 #include "point.H"
 
@@ -59,10 +59,7 @@ class faMeshDecomposition
 :
     public faMesh
 {
-    // Private data
-
-        //- Optional non-standard file for decomposeParDict
-        const fileName decompDictFile_;
+    // Private Data
 
         //- Number of processors in decomposition
         label nProcs_;
@@ -86,7 +83,7 @@ class faMeshDecomposition
         labelList procNInternalEdges_;
 
         //- Edge labels for patches of processor meshes
-        List<List<List<label>>> procPatchEdgeLabels_;
+        List<labelListList> procPatchEdgeLabels_;
 
         //- Labels of points for each processor
         labelListList procPatchPointAddressing_;
@@ -131,18 +128,21 @@ class faMeshDecomposition
 
         void distributeFaces();
 
+
 public:
 
     // Constructors
 
         //- Construct from components.
+        //- Values will come from the volume decomposition
         //  \param mesh the fvMesh
-        //  \param decompDictFile optional non-standard location for the
-        //      decomposeParDict file
+        //  \param nProcessors the number of processors
+        //  \param params additional parameters, sent to updateParameters
         faMeshDecomposition
         (
             const fvMesh& mesh,
-            const fileName& decompDictFile = ""
+            const label nProcessors,
+            const dictionary& params = dictionary::null
         );
 
 
@@ -152,29 +152,63 @@ public:
 
     // Member Functions
 
+    // Settings
+
         //- Number of processor in decomposition
         label nProcs() const
         {
             return nProcs_;
         }
 
-        //- Is the decomposition data to be distributed for each processor
+        //- Is decomposition data to be distributed for each processor
         bool distributed() const
         {
             return distributed_;
         }
 
-        //- Decompose mesh
-        void decomposeMesh();
+        //- Change distributed flag
+        bool distributed(const bool on)
+        {
+            bool old(distributed_);
+            distributed_ = on;
+            return old;
+        }
 
-        //- Write decomposition
-        bool writeDecomposition();
+        //- Are global face zones used
+        bool useGlobalFaceZones() const
+        {
+            return distributed_;
+        }
 
-        //- Cell-processor decomposition labels
-        const labelList& faceToProc() const
+        //- Change global face zones flag
+        bool useGlobalFaceZones(const bool on)
+        {
+            bool old(hasGlobalFaceZones_);
+            hasGlobalFaceZones_ = on;
+            return old;
+        }
+
+        //- Update flags based on the decomposition model settings
+        //  Sets "distributed", detects presence of "globalFaceZones"
+        void updateParameters(const dictionary& params);
+
+
+    // Mappings
+
+        //- Face-processor decomposition labels
+        const labelList& faceToProc() const noexcept
         {
             return faceToProc_;
         }
+
+
+    // Decompose
+
+        //- Decompose mesh
+        void decomposeMesh();
+
+        //- Write decomposition
+        bool writeDecomposition();
 };
 
 
diff --git a/src/parallel/reconstruct/Allwmake b/src/parallel/reconstruct/Allwmake
index 77b67cd1ecb9178eabe0d9bc2f17742789cad26a..583901ce7c03b955be7b5d53f9b64524d625cb32 100755
--- a/src/parallel/reconstruct/Allwmake
+++ b/src/parallel/reconstruct/Allwmake
@@ -5,5 +5,6 @@ cd "${0%/*}" || exit                                # Run from this directory
 #------------------------------------------------------------------------------
 
 wmake $targetType reconstruct
+wmake $targetType faReconstruct
 
 #------------------------------------------------------------------------------
diff --git a/src/parallel/reconstruct/faReconstruct/Make/files b/src/parallel/reconstruct/faReconstruct/Make/files
new file mode 100644
index 0000000000000000000000000000000000000000..c0f15e7ecda4e911f54ec257a4e0c8de5e3d6bff
--- /dev/null
+++ b/src/parallel/reconstruct/faReconstruct/Make/files
@@ -0,0 +1,5 @@
+processorFaMeshes.C
+faFieldReconstructor.C
+faMeshReconstructor.C
+
+LIB = $(FOAM_LIBBIN)/libfaReconstruct
diff --git a/src/parallel/reconstruct/faReconstruct/Make/options b/src/parallel/reconstruct/faReconstruct/Make/options
new file mode 100644
index 0000000000000000000000000000000000000000..2526f4eb0e6970a6cc43123f2c5c752687ceb3e9
--- /dev/null
+++ b/src/parallel/reconstruct/faReconstruct/Make/options
@@ -0,0 +1,9 @@
+EXE_INC = \
+    -I$(LIB_SRC)/finiteArea/lnInclude \
+    -I$(LIB_SRC)/finiteVolume/lnInclude \
+    -I$(LIB_SRC)/meshTools/lnInclude
+
+EXE_LIBS = \
+    -lfiniteArea \
+    -lfiniteVolume \
+    -lmeshTools
diff --git a/applications/utilities/parallelProcessing/reconstructPar/faFieldReconstructor.C b/src/parallel/reconstruct/faReconstruct/faFieldReconstructor.C
similarity index 100%
rename from applications/utilities/parallelProcessing/reconstructPar/faFieldReconstructor.C
rename to src/parallel/reconstruct/faReconstruct/faFieldReconstructor.C
diff --git a/applications/utilities/parallelProcessing/reconstructPar/faFieldReconstructor.H b/src/parallel/reconstruct/faReconstruct/faFieldReconstructor.H
similarity index 98%
rename from applications/utilities/parallelProcessing/reconstructPar/faFieldReconstructor.H
rename to src/parallel/reconstruct/faReconstruct/faFieldReconstructor.H
index 77f312191dadecc20198d8ca09970af92a7a4820..0c1e0005732b959b7caacba2b8e695842a5fb635 100644
--- a/applications/utilities/parallelProcessing/reconstructPar/faFieldReconstructor.H
+++ b/src/parallel/reconstruct/faReconstruct/faFieldReconstructor.H
@@ -35,7 +35,7 @@ Author
 
 SourceFiles
     faFieldReconstructor.C
-    faFieldReconstructorReconstructFields.C
+    faFieldReconstructorFields.C
 
 \*---------------------------------------------------------------------------*/
 
@@ -194,7 +194,7 @@ public:
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
 
 #ifdef NoRepository
-#   include "faFieldReconstructorReconstructFields.C"
+#   include "faFieldReconstructorFields.C"
 #endif
 
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
diff --git a/applications/utilities/parallelProcessing/reconstructPar/faFieldReconstructorReconstructFields.C b/src/parallel/reconstruct/faReconstruct/faFieldReconstructorFields.C
similarity index 100%
rename from applications/utilities/parallelProcessing/reconstructPar/faFieldReconstructorReconstructFields.C
rename to src/parallel/reconstruct/faReconstruct/faFieldReconstructorFields.C
diff --git a/src/parallel/reconstruct/faReconstruct/faMeshReconstructor.C b/src/parallel/reconstruct/faReconstruct/faMeshReconstructor.C
new file mode 100644
index 0000000000000000000000000000000000000000..0f85a81d5e23c22c07c812eda60677631957deb2
--- /dev/null
+++ b/src/parallel/reconstruct/faReconstruct/faMeshReconstructor.C
@@ -0,0 +1,637 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  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 "faMeshReconstructor.H"
+#include "globalIndex.H"
+#include "globalMeshData.H"
+#include "edgeHashes.H"
+#include "Time.H"
+#include "PstreamCombineReduceOps.H"
+
+// * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * //
+
+int Foam::faMeshReconstructor::debug = 0;
+
+
+// * * * * * * * * * * * * * Private Member Functions  * * * * * * * * * * * //
+
+void Foam::faMeshReconstructor::calcAddressing
+(
+    const labelUList& fvFaceProcAddr
+)
+{
+    const globalIndex globalFaceNum(procMesh_.nFaces());
+
+    // ----------------------
+    // boundaryProcAddressing
+    //
+    // Trivial since non-processor boundary ordering is identical
+
+    const label nPatches = procMesh_.boundary().size();
+
+    faBoundaryProcAddr_ = identity(nPatches);
+
+    // Mark processor patches
+    for
+    (
+        label patchi = procMesh_.boundary().nNonProcessor();
+        patchi < nPatches;
+        ++patchi
+    )
+    {
+        faBoundaryProcAddr_[patchi] = -1;
+    }
+
+
+    // ------------------
+    // faceProcAddressing
+    //
+    // Transcribe/rewrite based on finiteVolume faceProcAddressing
+
+    faFaceProcAddr_ = procMesh_.faceLabels();
+
+    // From local faceLabels to proc values
+    for (label& facei : faFaceProcAddr_)
+    {
+        // Use finiteVolume info, ignoring face flips
+        facei = mag(fvFaceProcAddr[facei] - 1);
+    }
+
+
+    // Make global consistent.
+    // Starting at '0', without gaps in numbering etc.
+    // - the sorted order is the obvious solution
+    {
+        globalFaceNum.gather(faFaceProcAddr_, singlePatchFaceLabels_);
+
+        labelList order(Foam::sortedOrder(singlePatchFaceLabels_));
+
+        singlePatchFaceLabels_ = labelList(singlePatchFaceLabels_, order);
+
+        globalFaceNum.scatter(order, faFaceProcAddr_);
+    }
+
+    // Broadcast the same information everywhere
+    Pstream::scatter(singlePatchFaceLabels_);
+
+
+    // ------------------
+    // edgeProcAddressing
+    //
+    // This is more complicated.
+
+    // Create a single (serial) patch of the finiteArea mesh without a
+    // corresponding volume mesh, otherwise it would be the same as
+    // reconstructPar on the volume mesh (big, slow, etc).
+    //
+    // Do manual point-merge and relabeling to avoid dependency on
+    // finiteVolume pointProcAddressing
+
+    faPointProcAddr_.clear();  // Final size == procMesh_.nPoints()
+
+    // 1.
+    // - Topological point merge of the area meshes
+    // - use the local patch faces and points
+
+    // 2.
+    // - build a single (serial) patch of the finiteArea mesh only
+    //   without any point support from the volume mesh
+    // - it may be possible to skip this step, but not obvious how
+
+    // The collected faces are contiguous per processor, which gives us
+    // the possibility to easily identify their source by using the
+    // global face indexing (if needed).
+    // The ultimate face locations are handled with a separate ordering
+    // list.
+
+    const uindirectPrimitivePatch& procPatch = procMesh_.patch();
+
+
+    {
+        faceList singlePatchProcFaces;  // [proc0faces, proc1faces ...]
+        labelList uniqueMeshPointLabels;
+
+        // Local face points to compact merged point index
+        labelList pointToGlobal;
+
+        autoPtr<globalIndex> globalPointsPtr =
+            procMesh_.mesh().globalData().mergePoints
+            (
+                procPatch.meshPoints(),
+                procPatch.meshPointMap(),  // unused
+                pointToGlobal,
+                uniqueMeshPointLabels
+            );
+
+        // Gather faces, renumbered for the *merged* points
+        faceList tmpFaces(globalFaceNum.localSize());
+
+        forAll(tmpFaces, facei)
+        {
+            tmpFaces[facei] =
+                face(pointToGlobal, procPatch.localFaces()[facei]);
+        }
+
+        globalFaceNum.gather
+        (
+            tmpFaces,
+            singlePatchProcFaces,
+            UPstream::msgType(),
+            Pstream::commsTypes::scheduled
+        );
+
+        globalPointsPtr().gather
+        (
+            UIndirectList<point>
+            (
+                procPatch.points(),  // NB: mesh points (non-local)
+                uniqueMeshPointLabels
+            ),
+            singlePatchPoints_
+        );
+
+        // Transcribe into final assembled order
+        singlePatchFaces_.resize(singlePatchProcFaces.size());
+
+        // Target face ordering
+        labelList singlePatchProcAddr;
+        globalFaceNum.gather(faFaceProcAddr_, singlePatchProcAddr);
+
+        forAll(singlePatchProcAddr, facei)
+        {
+            const label targetFacei = singlePatchProcAddr[facei];
+            singlePatchFaces_[targetFacei] = singlePatchProcFaces[facei];
+        }
+
+        // Use initial equivalent "global" (serial) patch
+        // to establish the correct point and face walking order
+        //
+        // - only meaningful on master
+        const primitivePatch initialPatch
+        (
+            SubList<face>(singlePatchFaces_),
+            singlePatchPoints_
+        );
+
+        // Grab the faces/points in local point ordering
+        tmpFaces = initialPatch.localFaces();
+        pointField tmpPoints(initialPatch.localPoints());
+
+        // The meshPointMap is contiguous, so flatten as linear list
+        /// Map<label> mpm(initialPatch.meshPointMap());
+        labelList mpm(initialPatch.nPoints());
+        forAllConstIters(initialPatch.meshPointMap(), iter)
+        {
+            mpm[iter.key()] = iter.val();
+        }
+        Pstream::scatter(mpm);
+
+        // Rewrite pointToGlobal according to the correct point order
+        for (label& pointi : pointToGlobal)
+        {
+            pointi = mpm[pointi];
+        }
+
+        // Finalize. overwrite
+        faPointProcAddr_ = std::move(pointToGlobal);
+
+        singlePatchFaces_ = std::move(tmpFaces);
+        singlePatchPoints_ = std::move(tmpPoints);
+    }
+
+    // Broadcast the same information everywhere
+    Pstream::scatter(singlePatchFaces_);
+    Pstream::scatter(singlePatchPoints_);
+
+    // Now have enough global information to determine global edge mappings
+
+    // Equivalent "global" (serial) patch
+    const primitivePatch onePatch
+    (
+        SubList<face>(singlePatchFaces_),
+        singlePatchPoints_
+    );
+
+    faEdgeProcAddr_.clear();
+    faEdgeProcAddr_.resize(procMesh_.nEdges(), -1);
+
+    {
+        EdgeMap<label> globalEdgeMapping(2*onePatch.nEdges());
+
+        // Pass 1: edge-hash lookup with edges in "natural" patch order
+
+        // Can use local edges() directly without remap via meshPoints()
+        // since the previous sorting means that we are already working
+        // with faces that are in the local point order and even
+        // the points themselves are also in the local point order
+        for (const edge& e : onePatch.edges())
+        {
+            globalEdgeMapping.insert(e, globalEdgeMapping.size());
+        }
+
+        // Lookup proc local edges (in natural order) to global equivalent
+        for (label edgei = 0; edgei < procPatch.nEdges(); ++edgei)
+        {
+            const edge globalEdge(faPointProcAddr_, procPatch.edges()[edgei]);
+
+            const auto fnd = globalEdgeMapping.cfind(globalEdge);
+
+            if (fnd.found())
+            {
+                faEdgeProcAddr_[edgei] = fnd.val();
+            }
+            else
+            {
+                FatalErrorInFunction
+                    << "Failed to find edge " << globalEdge
+                    << " this indicates a programming error" << nl
+                    << exit(FatalError);
+            }
+        }
+    }
+
+    // Now have consistent global edge
+    // This of course would all be too easy.
+    // The finiteArea edge lists have been defined as their own sorted
+    // order with sublists etc.
+
+    // Gather edge ids for nonProcessor boundaries.
+    // These will also be in the serial geometry
+
+    Map<label> remapGlobal(2*onePatch.nEdges());
+    for (label edgei = 0; edgei < onePatch.nInternalEdges(); ++edgei)
+    {
+        remapGlobal.insert(edgei, remapGlobal.size());
+    }
+
+    //
+    singlePatchEdgeLabels_.clear();
+    singlePatchEdgeLabels_.resize(procMesh_.boundary().nNonProcessor());
+
+    forAll(singlePatchEdgeLabels_, patchi)
+    {
+        const faPatch& fap = procMesh_.boundary()[patchi];
+
+        labelList& patchEdgeLabels = singlePatchEdgeLabels_[patchi];
+        patchEdgeLabels = fap.edgeLabels();
+
+        // Renumber from local edges to global edges (natural order)
+        for (label& edgeId : patchEdgeLabels)
+        {
+            edgeId = faEdgeProcAddr_[edgeId];
+        }
+
+        // OR patchEdgeLabels =
+        // UIndirectList<label>(faEdgeProcAddr_, fap.edgeLabels());
+
+
+        // Collect from all processors
+        combineReduce
+        (
+            patchEdgeLabels,
+            ListOps::appendEqOp<label>()
+        );
+
+        // Sorted order will be the original non-decomposed order
+        Foam::sort(patchEdgeLabels);
+
+        for (const label sortedEdgei : patchEdgeLabels)
+        {
+            remapGlobal.insert(sortedEdgei, remapGlobal.size());
+        }
+    }
+
+    {
+        // Use the map to rewrite the local faEdgeProcAddr_
+
+        labelList newEdgeProcAddr(faEdgeProcAddr_);
+
+        label edgei = procMesh_.nInternalEdges();
+
+        for (const faPatch& fap : procMesh_.boundary())
+        {
+            for (const label patchEdgei : fap.edgeLabels())
+            {
+                const label globalEdgei = faEdgeProcAddr_[patchEdgei];
+
+                const auto fnd = remapGlobal.cfind(globalEdgei);
+                if (fnd.found())
+                {
+                    newEdgeProcAddr[edgei] = fnd.val();
+                    ++edgei;
+                }
+                else
+                {
+                    FatalErrorInFunction
+                        << "Failed to find edge " << globalEdgei
+                        << " this indicates a programming error" << nl
+                        << exit(FatalError);
+                }
+            }
+        }
+
+        faEdgeProcAddr_ = std::move(newEdgeProcAddr);
+    }
+}
+
+
+void Foam::faMeshReconstructor::initPatch() const
+{
+    serialPatchPtr_.reset
+    (
+        new primitivePatch
+        (
+            SubList<face>(singlePatchFaces_),
+            singlePatchPoints_
+        )
+    );
+}
+
+
+void Foam::faMeshReconstructor::createMesh()
+{
+    const Time& runTime = procMesh_.mesh().time();
+
+    // Time for non-parallel case (w/o functionObjects or libs)
+    serialRunTime_ = Time::New(runTime.globalPath().toAbsolute());
+
+
+    // Trivial polyMesh only containing points and faces.
+    // This is valid, provided we don't use it for anything complicated.
+
+    serialVolMesh_.reset
+    (
+        new polyMesh
+        (
+            IOobject
+            (
+                procMesh_.mesh().name(),  // Volume region name
+                procMesh_.mesh().facesInstance(),
+
+                *serialRunTime_,
+
+                IOobject::NO_READ,
+                IOobject::NO_WRITE,
+                false  // not registered
+            ),
+            pointField(singlePatchPoints_),  // copy
+            faceList(singlePatchFaces_),     // copy
+            labelList(singlePatchFaces_.size(), Zero),  // faceOwner
+            labelList(),  // faceNeighbour
+            false  // no syncPar!
+        )
+    );
+
+    // A simple area-mesh with one-to-one mapping of faceLabels
+    serialAreaMesh_.reset
+    (
+        new faMesh
+        (
+            *serialVolMesh_,
+            identity(singlePatchFaces_.size())  // faceLabels
+        )
+    );
+
+    auto& completeMesh = *serialAreaMesh_;
+
+    // Add in non-processor boundary patches
+    PtrList<faPatch> completePatches(singlePatchEdgeLabels_.size());
+    forAll(completePatches, patchi)
+    {
+        const labelList& patchEdgeLabels = singlePatchEdgeLabels_[patchi];
+
+        const faPatch& fap = procMesh_.boundary()[patchi];
+
+        const label neiPolyPatchId = fap.ngbPolyPatchIndex();
+
+        completePatches.set
+        (
+            patchi,
+            fap.clone
+            (
+                completeMesh.boundary(),
+                patchEdgeLabels,
+                patchi,  // index
+                neiPolyPatchId
+            )
+        );
+    }
+
+    completeMesh.addFaPatches(completePatches);
+}
+
+
+// * * * * * * * * * * * * * * * * Constructors  * * * * * * * * * * * * * * //
+
+Foam::faMeshReconstructor::faMeshReconstructor
+(
+    const faMesh& procMesh
+)
+:
+    procMesh_(procMesh)
+{
+    if (!Pstream::parRun())
+    {
+        FatalErrorInFunction
+            << "Can only be called in parallel!!" << nl
+            << exit(FatalError);
+    }
+
+    // Require faceProcAddressing from finiteVolume decomposition
+    labelIOList fvFaceProcAddressing
+    (
+        IOobject
+        (
+            "faceProcAddressing",
+            procMesh_.mesh().facesInstance(),
+
+            // Or search?
+            // procMesh_.time().findInstance
+            // (
+            //     // Search for polyMesh face instance
+            //     // mesh.facesInstance()
+            //     procMesh_.mesh().meshDir(),
+            //     "faceProcAddressing"
+            // ),
+
+            polyMesh::meshSubDir,
+            procMesh_.mesh(),    // The polyMesh db
+            IOobject::MUST_READ,
+            IOobject::NO_WRITE,
+            false  // not registered
+        )
+    );
+
+    calcAddressing(fvFaceProcAddressing);
+}
+
+
+Foam::faMeshReconstructor::faMeshReconstructor
+(
+    const faMesh& procMesh,
+    const labelUList& fvFaceProcAddressing
+)
+:
+    procMesh_(procMesh)
+{
+    if (!Pstream::parRun())
+    {
+        FatalErrorInFunction
+            << "Can only be called in parallel!!" << nl
+            << exit(FatalError);
+    }
+
+    calcAddressing(fvFaceProcAddressing);
+}
+
+
+// * * * * * * * * * * * * * * * * Destructor  * * * * * * * * * * * * * * * //
+
+Foam::faMeshReconstructor::~faMeshReconstructor()
+{}
+
+
+// * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //
+
+void Foam::faMeshReconstructor::clearGeom()
+{
+    serialAreaMesh_.reset(nullptr);
+    serialVolMesh_.reset(nullptr);
+    serialRunTime_.reset(nullptr);
+}
+
+
+const Foam::primitivePatch& Foam::faMeshReconstructor::patch() const
+{
+    if (!serialPatchPtr_)
+    {
+        initPatch();
+    }
+
+    return *serialPatchPtr_;
+}
+
+
+Foam::primitivePatch& Foam::faMeshReconstructor::patch()
+{
+    if (!serialPatchPtr_)
+    {
+        initPatch();
+    }
+
+    return *serialPatchPtr_;
+}
+
+
+const Foam::faMesh& Foam::faMeshReconstructor::mesh() const
+{
+    if (!serialAreaMesh_)
+    {
+        const_cast<faMeshReconstructor&>(*this).createMesh();
+    }
+
+    return *serialAreaMesh_;
+}
+
+
+void Foam::faMeshReconstructor::writeAddressing() const
+{
+    writeAddressing(procMesh_.mesh().facesInstance());
+}
+
+
+void Foam::faMeshReconstructor::writeAddressing(const word& timeName) const
+{
+    // Write copies
+
+    IOobject ioAddr
+    (
+        "procAddressing",
+        timeName,
+        faMesh::meshSubDir,
+        procMesh_.mesh(),  // The polyMesh
+        IOobject::NO_READ,
+        IOobject::NO_WRITE,
+        false  // not registered
+    );
+
+    // boundaryProcAddressing
+    ioAddr.rename("boundaryProcAddressing");
+    labelIOList(ioAddr, faBoundaryProcAddr_).write();
+
+    // faceProcAddressing
+    ioAddr.rename("faceProcAddressing");
+    labelIOList(ioAddr, faFaceProcAddr_).write();
+
+    // pointProcAddressing
+    ioAddr.rename("pointProcAddressing");
+    labelIOList(ioAddr, faPointProcAddr_).write();
+
+    // edgeProcAddressing
+    ioAddr.rename("edgeProcAddressing");
+    labelIOList(ioAddr, faEdgeProcAddr_).write();
+}
+
+
+void Foam::faMeshReconstructor::writeMesh() const
+{
+    writeMesh(procMesh_.mesh().facesInstance());
+}
+
+
+void Foam::faMeshReconstructor::writeMesh(const word& timeName) const
+{
+    const faMesh& fullMesh = this->mesh();
+
+    const bool oldDistributed = fileHandler().distributed();
+    auto oldHandler = fileHandler(fileOperation::NewUncollated());
+    fileHandler().distributed(true);
+
+    if (Pstream::master())
+    {
+        const bool oldParRun = Pstream::parRun(false);
+
+        IOobject io(fullMesh.boundary());
+
+        io.rename("faceLabels");
+        labelIOList(io, singlePatchFaceLabels_).write();
+
+        fullMesh.boundary().write();
+
+        Pstream::parRun(oldParRun);
+    }
+
+    // Restore old settings
+    if (oldHandler)
+    {
+        fileHandler(std::move(oldHandler));
+    }
+    fileHandler().distributed(oldDistributed);
+}
+
+
+// ************************************************************************* //
diff --git a/src/parallel/reconstruct/faReconstruct/faMeshReconstructor.H b/src/parallel/reconstruct/faReconstruct/faMeshReconstructor.H
new file mode 100644
index 0000000000000000000000000000000000000000..8cf5154f448e0730de47c9cf05c38c5ac57633e2
--- /dev/null
+++ b/src/parallel/reconstruct/faReconstruct/faMeshReconstructor.H
@@ -0,0 +1,222 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  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/>.
+
+Class
+    Foam::faMeshReconstructor
+
+Description
+    A bare-bones reconstructor for finiteArea meshes when processor
+    meshes are available (in parallel) but an equivalent serial faMesh
+    is needed for reconstruction or decomposition.
+    In these situations, a serial version of the faMesh is needed,
+    but preferably without reconstructing the entire volume mesh.
+
+    It uses the finiteVolume faceProcAddressing in addition to
+    the geometric information available from the underlying polyMesh.
+
+    The resulting equivalent faMesh can be used for basic operations,
+    but caution should be exercised before attempting large operations.
+
+SourceFiles
+    faMeshReconstructor.C
+
+\*---------------------------------------------------------------------------*/
+
+#ifndef faMeshReconstructor_H
+#define faMeshReconstructor_H
+
+#include "faMesh.H"
+#include "primitivePatch.H"
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+namespace Foam
+{
+
+// Forward Declarations
+class Time;
+
+/*---------------------------------------------------------------------------*\
+                     Class faMeshReconstructor Declaration
+\*---------------------------------------------------------------------------*/
+
+class faMeshReconstructor
+{
+    // Private Data
+
+        //- The processor-specific faMesh
+        const faMesh& procMesh_;
+
+
+    // Addressing
+
+        //- Processor face addressing, derived from finite volume information
+        labelList faFaceProcAddr_;
+
+        //- Processor boundary addressing
+        labelList faBoundaryProcAddr_;
+
+        //- Processor point addressing
+        labelList faPointProcAddr_;
+
+        //- Processor edge addressing
+        labelList faEdgeProcAddr_;
+
+
+    // Equivalent surface information
+
+        //- Faces labels for a single patch
+        labelList singlePatchFaceLabels_;
+
+        //- Faces for a single patch
+        faceList singlePatchFaces_;
+
+        //- Support points for a single patch
+        pointField singlePatchPoints_;
+
+        //- Lists of edge-labels (per edge patch) for the single patch
+        labelListList singlePatchEdgeLabels_;
+
+
+    // Demand-driven data
+
+        //- Primitive patch
+        mutable autoPtr<primitivePatch> serialPatchPtr_;
+
+        //- Time database for serial meshes
+        autoPtr<Time> serialRunTime_;
+
+        //- Dummy volume mesh, used for serial area mesh
+        autoPtr<polyMesh> serialVolMesh_;
+
+        //- Equivalent serial area mesh
+        autoPtr<faMesh> serialAreaMesh_;
+
+
+    // Private Member Functions
+
+        //- Calculate all addressing, using finiteVolume faceProcAddressing
+        void calcAddressing(const labelUList& fvFaceProcAddr);
+
+        //- Set primitive patch, removing any old one
+        void initPatch() const;
+
+        //- Create the serial geometry
+        void createMesh();
+
+        //- No copy construct
+        faMeshReconstructor(const faMeshReconstructor&) = delete;
+
+        //- No copy assignment
+        void operator=(const faMeshReconstructor&) = delete;
+
+
+public:
+
+    //- Debug flag
+    static int debug;
+
+
+    // Constructors
+
+        //- Construct from components
+        explicit faMeshReconstructor(const faMesh& procMesh);
+
+        //- Construct from components
+        faMeshReconstructor
+        (
+            const faMesh& procMesh,
+            const labelUList& fvFaceProcAddressing
+        );
+
+
+    //- Destructor
+    ~faMeshReconstructor();
+
+        void clearGeom();
+
+
+    // Member Functions
+
+        //- Processor point addressing
+        const labelList& pointProcAddressing() const noexcept
+        {
+            return faPointProcAddr_;
+        }
+
+        //- Processor edge addressing
+        const labelList& edgeProcAddressing() const noexcept
+        {
+            return faEdgeProcAddr_;
+        }
+
+        //- Processor face addressing
+        const labelList& faceProcAddressing() const noexcept
+        {
+            return faFaceProcAddr_;
+        }
+
+        //- Processor boundary addressing
+        const labelList& boundaryProcAddressing() const noexcept
+        {
+            return faBoundaryProcAddr_;
+        }
+
+
+        //- Serial equivalent patch
+        const primitivePatch& patch() const;
+
+        //- Serial equivalent patch
+        primitivePatch& patch();
+
+        //- Serial equivalent faMesh
+        const faMesh& mesh() const;
+
+
+    // Write
+
+        //- Write proc addressing at the polyMesh faceInstances time
+        void writeAddressing() const;
+
+        //- Write proc addressing at the given time
+        void writeAddressing(const word& timeName) const;
+
+        //- Write equivalent mesh information at the polyMesh faceInstances time
+        void writeMesh() const;
+
+        //- Write equivalent mesh information at the given time
+        void writeMesh(const word& timeName) const;
+};
+
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+} // End namespace Foam
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+#endif
+
+// ************************************************************************* //
diff --git a/applications/utilities/parallelProcessing/reconstructPar/processorFaMeshes.C b/src/parallel/reconstruct/faReconstruct/processorFaMeshes.C
similarity index 57%
rename from applications/utilities/parallelProcessing/reconstructPar/processorFaMeshes.C
rename to src/parallel/reconstruct/faReconstruct/processorFaMeshes.C
index 980d652ce41314994e75adde175e5c952e3a9812..645a166900b11935d542edb335a010a70393254c 100644
--- a/applications/utilities/parallelProcessing/reconstructPar/processorFaMeshes.C
+++ b/src/parallel/reconstruct/faReconstruct/processorFaMeshes.C
@@ -145,116 +145,4 @@ Foam::processorFaMeshes::processorFaMeshes
 }
 
 
-// * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //
-
-// Foam::fvMesh::readUpdateState Foam::processorFaMeshes::readUpdate()
-// {
-//     fvMesh::readUpdateState stat = fvMesh::UNCHANGED;
-
-//     forAll(databases_, procI)
-//     {
-//         // Check if any new meshes need to be read.
-//         fvMesh::readUpdateState procStat = meshes_[procI].readUpdate();
-
-//         /*
-//         if (procStat != fvMesh::UNCHANGED)
-//         {
-//             Info<< "Processor " << procI
-//                 << " at time " << databases_[procI].timeName()
-//                 << " detected mesh change " << procStat
-//                 << endl;
-//         }
-//         */
-
-//         // Combine into overall mesh change status
-//         if (stat == fvMesh::UNCHANGED)
-//         {
-//             stat = procStat;
-//         }
-//         else
-//         {
-//             if (stat != procStat)
-//             {
-//                 FatalErrorIn("processorFaMeshes::readUpdate()")
-//                     << "Processor " << procI
-//                     << " has a different polyMesh at time "
-//                     << databases_[procI].timeName()
-//                     << " compared to any previous processors." << nl
-//                     << "Please check time " << databases_[procI].timeName()
-//                     << " directories on all processors for consistent"
-//                     << " mesh files."
-//                     << exit(FatalError);
-//             }
-//         }
-//     }
-
-//     if
-//     (
-//         stat == fvMesh::TOPO_CHANGE
-//      || stat == fvMesh::TOPO_PATCH_CHANGE
-//     )
-//     {
-//         // Reread all meshes and addressing
-//         read();
-//     }
-//     return stat;
-// }
-
-
-// void Foam::processorFaMeshes::reconstructPoints(fvMesh& mesh)
-// {
-//     // Read the field for all the processors
-//     PtrList<pointIOField> procsPoints(meshes_.size());
-
-//     forAll(meshes_, procI)
-//     {
-//         procsPoints.set
-//         (
-//             procI,
-//             new pointIOField
-//             (
-//                 IOobject
-//                 (
-//                     "points",
-//                     meshes_[procI].time().timeName(),
-//                     polyMesh::meshSubDir,
-//                     meshes_[procI],
-//                     IOobject::MUST_READ,
-//                     IOobject::NO_WRITE
-//                 )
-//             )
-//         );
-//     }
-
-//     // Create the new points
-//     vectorField newPoints(mesh.nPoints());
-
-//     forAll(meshes_, procI)
-//     {
-//         const vectorField& procPoints = procsPoints[procI];
-
-//         // Set the cell values in the reconstructed field
-
-//         const labelList& pointProcAddressingI = pointProcAddressing_[procI];
-
-//         if (pointProcAddressingI.size() != procPoints.size())
-//         {
-//             FatalErrorIn("processorFaMeshes")
-//                 << "problem :"
-//                 << " pointProcAddressingI:" << pointProcAddressingI.size()
-//                 << " procPoints:" << procPoints.size()
-//                 << abort(FatalError);
-//         }
-
-//         forAll(pointProcAddressingI, pointI)
-//         {
-//             newPoints[pointProcAddressingI[pointI]] = procPoints[pointI];
-//         }
-//     }
-
-//     mesh.movePoints(newPoints);
-//     mesh.write();
-// }
-
-
 // ************************************************************************* //
diff --git a/applications/utilities/parallelProcessing/reconstructPar/processorFaMeshes.H b/src/parallel/reconstruct/faReconstruct/processorFaMeshes.H
similarity index 92%
rename from applications/utilities/parallelProcessing/reconstructPar/processorFaMeshes.H
rename to src/parallel/reconstruct/faReconstruct/processorFaMeshes.H
index b302a287d76259831fd16f1bf9c2a9f0227206e5..342beb837515d189f756e671b6a43f904b41f1f3 100644
--- a/applications/utilities/parallelProcessing/reconstructPar/processorFaMeshes.H
+++ b/src/parallel/reconstruct/faReconstruct/processorFaMeshes.H
@@ -27,7 +27,7 @@ Class
     Foam::processorFaMeshes
 
 Description
-    Container for processor mesh addressing.
+    Container for finite-area processor mesh addressing.
 
 Author
     Zeljko Tukovic, FSB Zagreb
@@ -41,8 +41,8 @@ SourceFiles
 #define processorFaMeshes_H
 
 #include "PtrList.H"
-#include "fvMesh.H"
 #include "faMesh.H"
+#include "fvMesh.H"
 #include "IOobjectList.H"
 #include "labelIOList.H"
 
@@ -100,29 +100,31 @@ public:
 
     // Member Functions
 
-        //- Update the meshes based on the mesh files saved in
-        //  time directories
-//         fvMesh::readUpdateState readUpdate();
-
-        //- Reconstruct point position after motion in parallel
-//         void reconstructPoints(faMesh& mesh);
+        const PtrList<faMesh>& meshes() const
+        {
+            return meshes_;
+        }
 
         PtrList<faMesh>& meshes()
         {
             return meshes_;
         }
+
         const PtrList<labelIOList>& pointProcAddressing() const
         {
             return pointProcAddressing_;
         }
+
         PtrList<labelIOList>& edgeProcAddressing()
         {
             return edgeProcAddressing_;
         }
+
         const PtrList<labelIOList>& faceProcAddressing() const
         {
             return faceProcAddressing_;
         }
+
         const PtrList<labelIOList>& boundaryProcAddressing() const
         {
             return boundaryProcAddressing_;
diff --git a/src/thermophysicalModels/radiation/fvOptions/radiation/radiation.H b/src/thermophysicalModels/radiation/fvOptions/radiation/radiation.H
index 723b53b2e4305c3fc3b24e6ab6b02d41923599d5..f70449ab59ad3dcabe51b243f6c4be9ec96988ec 100644
--- a/src/thermophysicalModels/radiation/fvOptions/radiation/radiation.H
+++ b/src/thermophysicalModels/radiation/fvOptions/radiation/radiation.H
@@ -80,7 +80,7 @@ namespace fv
 
 class radiation
 :
-    public option
+    public fv::option
 {
     // Private Data
 
diff --git a/src/waveModels/fvOptions/multiphaseMangrovesSource/multiphaseMangrovesSource.H b/src/waveModels/fvOptions/multiphaseMangrovesSource/multiphaseMangrovesSource.H
index 6d040ba0963a2017935b6c7cf5e32042fe5f8779..5c5cc232ebb2c7353775cb6879646e489f02e222 100644
--- a/src/waveModels/fvOptions/multiphaseMangrovesSource/multiphaseMangrovesSource.H
+++ b/src/waveModels/fvOptions/multiphaseMangrovesSource/multiphaseMangrovesSource.H
@@ -50,7 +50,7 @@ SourceFiles
 namespace Foam
 {
 
-// Forward declarations
+// Forward Declarations
 class MangrovesModel;
 
 namespace fv
@@ -62,7 +62,7 @@ namespace fv
 
 class multiphaseMangrovesSource
 :
-    public option
+    public fv::option
 {
     // Private Member Functions
 
diff --git a/src/waveModels/fvOptions/multiphaseMangrovesTurbulenceModel/multiphaseMangrovesTurbulenceModel.H b/src/waveModels/fvOptions/multiphaseMangrovesTurbulenceModel/multiphaseMangrovesTurbulenceModel.H
index 3be8a42214fd0bccd6d53355b1da4588cc8a0943..ae9c4911565ddcfb260805c2a0567cbb4c10a0fd 100644
--- a/src/waveModels/fvOptions/multiphaseMangrovesTurbulenceModel/multiphaseMangrovesTurbulenceModel.H
+++ b/src/waveModels/fvOptions/multiphaseMangrovesTurbulenceModel/multiphaseMangrovesTurbulenceModel.H
@@ -59,7 +59,7 @@ namespace fv
 
 class multiphaseMangrovesTurbulenceModel
 :
-    public option
+    public fv::option
 {
     // Private Member Functions
 
diff --git a/tutorials/compressible/acousticFoam/obliqueAirJet/main/constant/faMesh/faMeshDefinition b/tutorials/compressible/acousticFoam/obliqueAirJet/main/system/faMeshDefinition
similarity index 97%
rename from tutorials/compressible/acousticFoam/obliqueAirJet/main/constant/faMesh/faMeshDefinition
rename to tutorials/compressible/acousticFoam/obliqueAirJet/main/system/faMeshDefinition
index 1c7878defd4144f254e88dac980e7b48f0f44fc6..7bc2d201acae0c3aaa85e4cb1d2d3507a24b1c29 100644
--- a/tutorials/compressible/acousticFoam/obliqueAirJet/main/constant/faMesh/faMeshDefinition
+++ b/tutorials/compressible/acousticFoam/obliqueAirJet/main/system/faMeshDefinition
@@ -14,7 +14,7 @@ FoamFile
 }
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
 
-polyMeshPatches  1(window);
+polyMeshPatches  ( window );
 
 boundary
 {
diff --git a/tutorials/finiteArea/liquidFilmFoam/cylinder/Allrun-parallel b/tutorials/finiteArea/liquidFilmFoam/cylinder/Allrun-parallel
new file mode 100755
index 0000000000000000000000000000000000000000..3f019bcaa246a8435ee97246f38fb9fe96f11b24
--- /dev/null
+++ b/tutorials/finiteArea/liquidFilmFoam/cylinder/Allrun-parallel
@@ -0,0 +1,14 @@
+#!/bin/sh
+cd "${0%/*}" || exit                                # Run from this directory
+. ${WM_PROJECT_DIR:?}/bin/tools/RunFunctions        # Tutorial run functions
+#------------------------------------------------------------------------------
+
+runApplication blockMesh
+
+runApplication decomposePar
+
+runParallel makeFaMesh
+
+runParallel $(getApplication)
+
+#------------------------------------------------------------------------------
diff --git a/tutorials/finiteArea/liquidFilmFoam/cylinder/constant/faMesh/faMeshDefinition b/tutorials/finiteArea/liquidFilmFoam/cylinder/system/faMeshDefinition
similarity index 94%
rename from tutorials/finiteArea/liquidFilmFoam/cylinder/constant/faMesh/faMeshDefinition
rename to tutorials/finiteArea/liquidFilmFoam/cylinder/system/faMeshDefinition
index 12c4767c8d128cad7fa7b3a50171d1bd251fb1ec..66da6e5c0e5d3e14aa40e0ead8b8599737b034f2 100644
--- a/tutorials/finiteArea/liquidFilmFoam/cylinder/constant/faMesh/faMeshDefinition
+++ b/tutorials/finiteArea/liquidFilmFoam/cylinder/system/faMeshDefinition
@@ -10,12 +10,11 @@ FoamFile
     version     2.0;
     format      ascii;
     class       dictionary;
-    location    "constant/faMesh";
     object      faMeshDefinition;
 }
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
 
-polyMeshPatches  1( film );
+polyMeshPatches  ( film );
 
 boundary
 {
@@ -56,4 +55,11 @@ boundary
 }
 
 
+defaultPatch
+{
+    name    empty;
+    type    empty;
+}
+
+
 // ************************************************************************** //
diff --git a/tutorials/finiteArea/sphereSurfactantFoam/sphereTransport/constant/faMesh/faMeshDefinition b/tutorials/finiteArea/sphereSurfactantFoam/sphereTransport/system/faMeshDefinition
similarity index 96%
rename from tutorials/finiteArea/sphereSurfactantFoam/sphereTransport/constant/faMesh/faMeshDefinition
rename to tutorials/finiteArea/sphereSurfactantFoam/sphereTransport/system/faMeshDefinition
index a9d4ea6d56081363cebae691a205237c534a5246..ce76df0f704fea266824e10d19cdb6a7bf8a32d5 100644
--- a/tutorials/finiteArea/sphereSurfactantFoam/sphereTransport/constant/faMesh/faMeshDefinition
+++ b/tutorials/finiteArea/sphereSurfactantFoam/sphereTransport/system/faMeshDefinition
@@ -14,7 +14,7 @@ FoamFile
 }
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
 
-polyMeshPatches  1( outer );
+polyMeshPatches  ( outer );
 
 boundary
 {
diff --git a/tutorials/finiteArea/surfactantFoam/planeTransport/Allrun-parallel b/tutorials/finiteArea/surfactantFoam/planeTransport/Allrun-parallel
new file mode 100755
index 0000000000000000000000000000000000000000..93e66772a9e4956484ce22728b18bbafb7209795
--- /dev/null
+++ b/tutorials/finiteArea/surfactantFoam/planeTransport/Allrun-parallel
@@ -0,0 +1,19 @@
+#!/bin/sh
+cd "${0%/*}" || exit                                # Run from this directory
+. ${WM_PROJECT_DIR:?}/bin/tools/RunFunctions        # Tutorial run functions
+#------------------------------------------------------------------------------
+
+runApplication blockMesh
+
+decompDict="-decomposeParDict system/decomposeParDict-procBoundary8"
+fileHandler="-fileHandler collated"
+
+runApplication $decompDict decomposePar $fileHandler
+
+runParallel $decompDict makeFaMesh $fileHandler
+
+runParallel $decompDict $(getApplication) $fileHandler
+
+runApplication reconstructPar $fileHandler
+
+#------------------------------------------------------------------------------
diff --git a/tutorials/finiteArea/surfactantFoam/planeTransport/system/blockMeshDict b/tutorials/finiteArea/surfactantFoam/planeTransport/system/blockMeshDict
index fabc18b264b1d93d6cfab4843e64d6cfd2b5d547..6f5e501b6bef578d56b41dc59fdb81b7912171e5 100644
--- a/tutorials/finiteArea/surfactantFoam/planeTransport/system/blockMeshDict
+++ b/tutorials/finiteArea/surfactantFoam/planeTransport/system/blockMeshDict
@@ -14,27 +14,44 @@ FoamFile
 }
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
 
+// A flat plate that is only partial covered by the finiteArea mesh.
+// Depending on the decomposition, the outflow boundary may
+// coincide with a processor patch.
+
 scale   0.1;
 
+height  0.1;
+width   1;
+len     3;
+out     1;
+out     #eval{ $out + $len };
+
+nx 60;
+ny 20;
+nz 1;
+nout 20;
+
 vertices
 (
-    (0 0 0)
-    (3 0 0)
-    (3 1 0)
-    (0 1 0)
-    (0 0 0.1)
-    (3 0 0.1)
-    (3 1 0.1)
-    (0 1 0.1)
-);
+    /* 0*/ (0 0 0)
+    /* 1*/ ($len 0 0)
+    /* 2*/ ($len $width 0)
+    /* 3*/ (0 $width 0)
+    /* 4*/ (0 0 $height)
+    /* 5*/ ($len 0 $height)
+    /* 6*/ ($len $width $height)
+    /* 7*/ (0 $width $height)
 
-blocks
-(
-    hex (0 1 2 3 4 5 6 7) (60 20 1) simpleGrading (1 1 1)
+    /* 8*/ ($out 0 0)
+    /* 9*/ ($out 1 0)
+    /*10*/ ($out 0 $height)
+    /*11*/ ($out 1 $height)
 );
 
-edges
+blocks
 (
+    hex (0 1 2 3 4 5 6 7)   ($nx   $ny $nz) grading (1 1 1)
+    hex (1 8 9 2 5 10 11 6) ($nout $ny $nz) grading (1 1 1)
 );
 
 boundary
@@ -44,7 +61,7 @@ boundary
         type patch;
         faces
         (
-           (0 4 7 3)
+            (0 4 7 3)
         );
     }
 
@@ -53,8 +70,12 @@ boundary
         type wall;
         faces
         (
-           (3 7 6 2)
-           (1 5 4 0)
+            (3 7 6 2)
+            (1 5 4 0)
+
+            // outflow
+            (1 8 10 5)
+            (2 6 11 9)
         );
     }
 
@@ -63,7 +84,7 @@ boundary
         type patch;
         faces
         (
-            (2 6 5 1)
+            (8 9 10 11)
         );
     }
 
@@ -73,6 +94,7 @@ boundary
         faces
         (
             (0 3 2 1)
+            (1 8 9 2)
         );
     }
 
@@ -84,10 +106,15 @@ boundary
             (4 5 6 7)
         );
     }
-);
 
-mergePatchPairs
-(
+    outflow
+    {
+        type patch;
+        faces
+        (
+            (5 10 11 6)
+        );
+    }
 );
 
 
diff --git a/tutorials/finiteArea/surfactantFoam/planeTransport/system/decomposeParDict b/tutorials/finiteArea/surfactantFoam/planeTransport/system/decomposeParDict
index 792ca5965b6bd6145d7980fd2566eaa59204925c..bf5148c5d808cde097b94291c40d8f1e0da6d567 100644
--- a/tutorials/finiteArea/surfactantFoam/planeTransport/system/decomposeParDict
+++ b/tutorials/finiteArea/surfactantFoam/planeTransport/system/decomposeParDict
@@ -10,7 +10,6 @@ FoamFile
     version     2.0;
     format      ascii;
     class       dictionary;
-    location    "system";
     object      decomposeParDict;
 }
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
diff --git a/tutorials/finiteArea/surfactantFoam/planeTransport/system/decomposeParDict-procBoundary4 b/tutorials/finiteArea/surfactantFoam/planeTransport/system/decomposeParDict-procBoundary4
new file mode 100644
index 0000000000000000000000000000000000000000..30e0dbd4ae6ea8e3763dbbc1cdee23298e270d3b
--- /dev/null
+++ b/tutorials/finiteArea/surfactantFoam/planeTransport/system/decomposeParDict-procBoundary4
@@ -0,0 +1,28 @@
+/*--------------------------------*- C++ -*----------------------------------*\
+| =========                 |                                                 |
+| \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox           |
+|  \\    /   O peration     | Version:  v2012                                 |
+|   \\  /    A nd           | Website:  www.openfoam.com                      |
+|    \\/     M anipulation  |                                                 |
+\*---------------------------------------------------------------------------*/
+FoamFile
+{
+    version     2.0;
+    format      ascii;
+    class       dictionary;
+    object      decomposeParDict;
+}
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+// With 4 divisions in x-direction (coincides with finiteArea boundary)
+
+numberOfSubdomains 4;
+
+method      simple;
+
+coeffs
+{
+    n       ( 4 1 1 );
+}
+
+
+// ************************************************************************* //
diff --git a/tutorials/finiteArea/surfactantFoam/planeTransport/system/decomposeParDict-procBoundary8 b/tutorials/finiteArea/surfactantFoam/planeTransport/system/decomposeParDict-procBoundary8
new file mode 100644
index 0000000000000000000000000000000000000000..bc6a1ae7b79de5cb9a2c03b3d430b01314ade512
--- /dev/null
+++ b/tutorials/finiteArea/surfactantFoam/planeTransport/system/decomposeParDict-procBoundary8
@@ -0,0 +1,28 @@
+/*--------------------------------*- C++ -*----------------------------------*\
+| =========                 |                                                 |
+| \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox           |
+|  \\    /   O peration     | Version:  v2012                                 |
+|   \\  /    A nd           | Website:  www.openfoam.com                      |
+|    \\/     M anipulation  |                                                 |
+\*---------------------------------------------------------------------------*/
+FoamFile
+{
+    version     2.0;
+    format      ascii;
+    class       dictionary;
+    object      decomposeParDict;
+}
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+// With 4 divisions in x-direction (coincides with finiteArea boundary)
+
+numberOfSubdomains 8;
+
+method      simple;
+
+coeffs
+{
+    n       ( 4 2 1 );
+}
+
+
+// ************************************************************************* //
diff --git a/tutorials/finiteArea/surfactantFoam/planeTransport/constant/faMesh/faMeshDefinition b/tutorials/finiteArea/surfactantFoam/planeTransport/system/faMeshDefinition
similarity index 91%
rename from tutorials/finiteArea/surfactantFoam/planeTransport/constant/faMesh/faMeshDefinition
rename to tutorials/finiteArea/surfactantFoam/planeTransport/system/faMeshDefinition
index de49f774744e4c64c66527ebd7369d53e2392a9e..9558287443a608ef2b1dda567ea742e6b8b84291 100644
--- a/tutorials/finiteArea/surfactantFoam/planeTransport/constant/faMesh/faMeshDefinition
+++ b/tutorials/finiteArea/surfactantFoam/planeTransport/system/faMeshDefinition
@@ -14,7 +14,7 @@ FoamFile
 }
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
 
-polyMeshPatches  1( top );
+polyMeshPatches  ( top );
 
 boundary
 {
@@ -28,7 +28,8 @@ boundary
     {
         type patch;
         ownerPolyPatch top;
-        neighbourPolyPatch outlet;
+        // neighbourPolyPatch outlet;
+        neighbourPolyPatch outflow;
     }
     bound
     {
diff --git a/tutorials/incompressible/pimpleFoam/laminar/contactAngleCavity/constant/faMesh/faMeshDefinition b/tutorials/incompressible/pimpleFoam/laminar/contactAngleCavity/system/faMeshDefinition
similarity index 100%
rename from tutorials/incompressible/pimpleFoam/laminar/contactAngleCavity/constant/faMesh/faMeshDefinition
rename to tutorials/incompressible/pimpleFoam/laminar/contactAngleCavity/system/faMeshDefinition
diff --git a/tutorials/incompressible/pimpleFoam/laminar/contaminatedDroplet2D/constant/faMesh/faMeshDefinition b/tutorials/incompressible/pimpleFoam/laminar/contaminatedDroplet2D/system/faMeshDefinition
similarity index 100%
rename from tutorials/incompressible/pimpleFoam/laminar/contaminatedDroplet2D/constant/faMesh/faMeshDefinition
rename to tutorials/incompressible/pimpleFoam/laminar/contaminatedDroplet2D/system/faMeshDefinition
diff --git a/tutorials/incompressible/pimpleFoam/laminar/sloshing2D/constant/faMesh/faMeshDefinition b/tutorials/incompressible/pimpleFoam/laminar/sloshing2D/system/faMeshDefinition
similarity index 100%
rename from tutorials/incompressible/pimpleFoam/laminar/sloshing2D/constant/faMesh/faMeshDefinition
rename to tutorials/incompressible/pimpleFoam/laminar/sloshing2D/system/faMeshDefinition