diff --git a/applications/utilities/finiteArea/checkFaMesh/checkFaMesh.C b/applications/utilities/finiteArea/checkFaMesh/checkFaMesh.C
index 2aaede0be8a00318b58143e1335baf50d56b22d2..bb34931e9a50ade7a1a5d060a76cfd6cd5e992ea 100644
--- a/applications/utilities/finiteArea/checkFaMesh/checkFaMesh.C
+++ b/applications/utilities/finiteArea/checkFaMesh/checkFaMesh.C
@@ -6,7 +6,7 @@
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
     Copyright (C) 2016-2017 Wikki Ltd
-    Copyright (C) 2021 OpenCFD Ltd.
+    Copyright (C) 2021-2022 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -46,6 +46,8 @@ Original Authors
 #include "edgeFields.H"
 #include "processorFaPatch.H"
 #include "foamVtkIndPatchWriter.H"
+#include "foamVtkLineWriter.H"
+#include "faMeshTools.H"
 
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
 
@@ -66,11 +68,27 @@ int main(int argc, char *argv[])
         "Write mesh as a vtp (vtk) file for display or debugging"
     );
 
+    argList::addOption
+    (
+        "geometryOrder",
+        "N",
+        "Test different geometry order - experimental!!",
+        true  // Advanced option
+    );
+
     #include "addRegionOption.H"
     #include "setRootCase.H"
     #include "createTime.H"
     #include "createNamedPolyMesh.H"
 
+    int geometryOrder(1);
+    if (args.readIfPresent("geometryOrder", geometryOrder))
+    {
+        Info<< "Setting faMesh::geometryOrder = " << geometryOrder << nl
+            << "(experimental)" << nl << endl;
+
+        faMesh::geometryOrder(geometryOrder);
+    }
 
     // Create
     faMesh aMesh(mesh);
diff --git a/applications/utilities/finiteArea/checkFaMesh/faMeshWriteVTK.H b/applications/utilities/finiteArea/checkFaMesh/faMeshWriteVTK.H
index 1df8b4d3125de819817c38c6821c45f5f96b74e1..c6edf85a3e0aef20c499f82247f9a4eee071fca2 100644
--- a/applications/utilities/finiteArea/checkFaMesh/faMeshWriteVTK.H
+++ b/applications/utilities/finiteArea/checkFaMesh/faMeshWriteVTK.H
@@ -5,7 +5,7 @@
     \\  /    A nd           | www.openfoam.com
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
-    Copyright (C) 2021 OpenCFD Ltd.
+    Copyright (C) 2021-2022 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM, distributed under GPL-3.0-or-later.
@@ -16,7 +16,7 @@ Description
 \*---------------------------------------------------------------------------*/
 
 {
-    // finiteArea
+    // finiteArea - faces
     vtk::uindirectPatchWriter writer
     (
         aMesh.patch(),
@@ -29,9 +29,23 @@ Description
 
     writer.writeGeometry();
 
+    globalIndex procAddr(aMesh.nFaces());
+    labelList cellIDs;
+
+    if (Pstream::master())
+    {
+        cellIDs.resize(procAddr.totalSize());
+        for (const labelRange& range : procAddr.ranges())
+        {
+            auto slice = cellIDs.slice(range);
+            slice = identity(range);
+        }
+    }
+
     // CellData
-    writer.beginCellData(3);
+    writer.beginCellData(4);
     writer.writeProcIDs();
+    writer.write("cellID", cellIDs);
     writer.write("area", aMesh.S().field());
     writer.write("normal", aMesh.faceAreaNormals());
 
@@ -43,5 +57,79 @@ Description
         << "Wrote faMesh in vtk format: " << writer.output().name() << nl;
 }
 
+{
+    // finiteArea - edges
+    vtk::lineWriter writer
+    (
+        aMesh.points(),
+        aMesh.edges(),
+        // vtk::formatType::INLINE_ASCII,
+        fileName
+        (
+            aMesh.mesh().time().globalPath() / "finiteArea-edges"
+        )
+    );
+
+    writer.writeGeometry();
+
+    // CellData
+    writer.beginCellData(4);
+    writer.writeProcIDs();
+    {
+        // Use primitive patch order
+        Field<scalar> fld
+        (
+            faMeshTools::flattenEdgeField(aMesh.magLe(), true)
+        );
+        writer.write("magLe", fld);
+    }
+
+    // PointData
+    writer.beginPointData(1);
+    writer.write("normal", aMesh.pointAreaNormals());
+
+    Info<< nl
+        << "Wrote faMesh in vtk format: " << writer.output().name() << nl;
+}
+
+{
+    // finiteArea - edgeCentres
+    // (no other convenient way to display vectors on the edges)
+    vtk::lineWriter writer
+    (
+        aMesh.edgeCentres(),
+        edgeList::null(),
+        // vtk::formatType::INLINE_ASCII,
+        fileName
+        (
+            aMesh.mesh().time().globalPath() / "finiteArea-edgesCentres"
+        )
+    );
+
+    writer.writeGeometry();
+
+    // PointData
+    writer.beginPointData(4);
+    {
+        // Use primitive patch order
+        Field<vector> fld
+        (
+            faMeshTools::flattenEdgeField(aMesh.Le(), true)
+        );
+        writer.write("Le", fld);
+    }
+    {
+        // Use primitive patch order
+        Field<vector> fld
+        (
+            faMeshTools::flattenEdgeField(aMesh.edgeAreaNormals(), true)
+        );
+        writer.write("normal", fld);
+    }
+
+    Info<< nl
+        << "Wrote faMesh in vtk format: " << writer.output().name() << nl;
+}
+
 
 // ************************************************************************* //
diff --git a/applications/utilities/finiteArea/checkFaMesh/printMeshSummary.H b/applications/utilities/finiteArea/checkFaMesh/printMeshSummary.H
index 256d2b1371f1219ddc4dfdcb35d22bb93fa258e8..e3d255d961323dc71cf145ebd085d64678b31699 100644
--- a/applications/utilities/finiteArea/checkFaMesh/printMeshSummary.H
+++ b/applications/utilities/finiteArea/checkFaMesh/printMeshSummary.H
@@ -5,7 +5,7 @@
     \\  /    A nd           | www.openfoam.com
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
-    Copyright (C) 2021 OpenCFD Ltd.
+    Copyright (C) 2021-2022 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM, distributed under GPL-3.0-or-later.
@@ -20,24 +20,7 @@ Description
     const label nNonProcessor = patches.nNonProcessor();
     const label nPatches = patches.size();
 
-    Info<< "----------------" << nl
-        << "Mesh Information" << nl
-        << "----------------" << nl
-        << "  " << "boundingBox: " << boundBox(aMesh.points()) << nl;
-
-    Info<< "  Number of points: "
-        << returnReduce(aMesh.nPoints(), sumOp<label>()) << nl
-        << "  Number of faces: "
-        << returnReduce(aMesh.nFaces(), sumOp<label>()) << nl;
-
-    Info<< "  Number of edges: "
-        << returnReduce(aMesh.nEdges(), sumOp<label>()) << nl
-        << "  Number of internal edges: "
-        << returnReduce(aMesh.nInternalEdges(), sumOp<label>()) << nl;
-
-
-    label nProcEdges = 0;
-
+    label nLocalProcEdges = 0;
     if (Pstream::parRun())
     {
         for (const faPatch& fap : patches)
@@ -46,22 +29,84 @@ Description
 
             if (cpp)
             {
-                nProcEdges += fap.nEdges();
+                nLocalProcEdges += fap.nEdges();
             }
         }
     }
 
-    const label nBoundEdges = aMesh.nBoundaryEdges() - nProcEdges;
+    const labelList nFaces
+    (
+        UPstream::listGatherValues<label>(aMesh.nFaces())
+    );
+    const labelList nPoints
+    (
+        UPstream::listGatherValues<label>(aMesh.nPoints())
+    );
+    const labelList nEdges
+    (
+        UPstream::listGatherValues<label>(aMesh.nEdges())
+    );
+    const labelList nIntEdges
+    (
+        UPstream::listGatherValues<label>(aMesh.nInternalEdges())
+    );
+
+    // The "real" (non-processor) boundary edges
+    const labelList nBndEdges
+    (
+        UPstream::listGatherValues<label>
+        (
+            aMesh.nBoundaryEdges() - nLocalProcEdges
+        )
+    );
+    const labelList nProcEdges
+    (
+        UPstream::listGatherValues<label>(nLocalProcEdges)
+    );
+
+
+    // Format output as
+    //  Number of faces: ...
+    //         per-proc: (...)
+
+    const auto reporter =
+        [&](const char* tag, const labelList& list)
+        {
+            Info<< "  Number of " << tag << ": " << sum(list) << nl;
+            if (Pstream::parRun())
+            {
+                int padding = static_cast<int>
+                (
+                    // strlen("  Number of ") - strlen("per-proc")
+                    (12 - 8)
+                  + strlen(tag)
+                );
 
-    Info<< "  Number of boundary edges: "
-        << returnReduce(nBoundEdges - nProcEdges, sumOp<label>()) << nl;
+                do { Info<< ' '; } while (--padding > 0);
 
-    if (Pstream::parRun())
+                Info<< "per-proc: " << flatOutput(list) << nl;
+            }
+        };
+
+
+    Info<< "----------------" << nl
+        << "Mesh Information" << nl
+        << "----------------" << nl
+        << "  " << "boundingBox: " << boundBox(aMesh.points()) << nl;
+
+    if (Pstream::master())
     {
-        Info<< "  Number of processor edges: "
-            << returnReduce(nProcEdges, sumOp<label>()) << nl;
-    }
+        reporter("faces", nFaces);
+        reporter("points", nPoints);
+        reporter("edges", nEdges);
+        reporter("internal edges", nIntEdges);
+        reporter("boundary edges", nBndEdges);
 
+        if (Pstream::parRun())
+        {
+            reporter("processor edges", nProcEdges);
+        }
+    }
 
     Info<< "----------------" << nl
         << "Patches" << nl
diff --git a/src/finiteArea/Make/files b/src/finiteArea/Make/files
index 99d971d4dbf1afe7ed97befbfd111583e140369c..7db25ed3f3a44519926667ad39279aa5c9015877 100644
--- a/src/finiteArea/Make/files
+++ b/src/finiteArea/Make/files
@@ -9,6 +9,10 @@ faMesh/faMeshBoundaryHalo.C
 faMesh/faBoundaryMesh/faBoundaryMesh.C
 faMesh/faBoundaryMesh/faBoundaryMeshEntries.C
 
+faMesh/faMeshSubset/faMeshSubset.C
+faMesh/faMeshTools/faMeshTools.C
+faMesh/faMeshTools/faMeshToolsProcAddr.C
+
 faPatches = faMesh/faPatches
 $(faPatches)/faPatch/faPatch.C
 $(faPatches)/faPatch/faPatchData.C
diff --git a/src/finiteArea/faMesh/faBoundaryMesh/faBoundaryMesh.C b/src/finiteArea/faMesh/faBoundaryMesh/faBoundaryMesh.C
index 5e3af562802e9e9bf85f8aaa8f560c35d565f1e3..d6787152decf4bcb585369439d6a01625147dd2a 100644
--- a/src/finiteArea/faMesh/faBoundaryMesh/faBoundaryMesh.C
+++ b/src/finiteArea/faMesh/faBoundaryMesh/faBoundaryMesh.C
@@ -235,6 +235,22 @@ void Foam::faBoundaryMesh::calcGeometry()
 }
 
 
+Foam::UPtrList<const Foam::labelUList>
+Foam::faBoundaryMesh::edgeLabels() const
+{
+    const faPatchList& patches = *this;
+
+    UPtrList<const labelUList> list(patches.size());
+
+    forAll(list, patchi)
+    {
+        list.set(patchi, &patches[patchi].edgeLabels());
+    }
+
+    return list;
+}
+
+
 Foam::UPtrList<const Foam::labelUList>
 Foam::faBoundaryMesh::edgeFaces() const
 {
diff --git a/src/finiteArea/faMesh/faBoundaryMesh/faBoundaryMesh.H b/src/finiteArea/faMesh/faBoundaryMesh/faBoundaryMesh.H
index 1f17979b57413db694aedf17604a34e38c9abf9f..d6cf8d2a752594c28cf2638078002b466f220cc1 100644
--- a/src/finiteArea/faMesh/faBoundaryMesh/faBoundaryMesh.H
+++ b/src/finiteArea/faMesh/faBoundaryMesh/faBoundaryMesh.H
@@ -132,6 +132,9 @@ public:
             return mesh_;
         }
 
+        //- Return a list of edgeLabels for each patch
+        UPtrList<const labelUList> edgeLabels() const;
+
         //- Return a list of edgeFaces for each patch
         UPtrList<const labelUList> edgeFaces() const;
 
diff --git a/src/finiteArea/faMesh/faMesh.C b/src/finiteArea/faMesh/faMesh.C
index 32e767ee265ce46f941c4bbcc37092bffea3637a..32de35d34a6362d9ad3fb227535debf967bb617d 100644
--- a/src/finiteArea/faMesh/faMesh.C
+++ b/src/finiteArea/faMesh/faMesh.C
@@ -337,10 +337,12 @@ Foam::faMesh::faMesh
         *this
     ),
     comm_(Pstream::worldComm),
+    curTimeIndex_(time().timeIndex()),
+
     patchPtr_(nullptr),
     bndConnectPtr_(nullptr),
     lduPtr_(nullptr),
-    curTimeIndex_(time().timeIndex()),
+
     SPtr_(nullptr),
     S0Ptr_(nullptr),
     S00Ptr_(nullptr),
@@ -439,10 +441,12 @@ Foam::faMesh::faMesh
         label(0)
     ),
     comm_(Pstream::worldComm),
+    curTimeIndex_(time().timeIndex()),
+
     patchPtr_(nullptr),
     bndConnectPtr_(nullptr),
     lduPtr_(nullptr),
-    curTimeIndex_(time().timeIndex()),
+
     SPtr_(nullptr),
     S0Ptr_(nullptr),
     S00Ptr_(nullptr),
diff --git a/src/finiteArea/faMesh/faMesh.H b/src/finiteArea/faMesh/faMesh.H
index c6de146f0fb51296b66075bbb37492c382a92312..4d59b4ac7963ca3190d83b1eb3ab790d9f6e454a 100644
--- a/src/finiteArea/faMesh/faMesh.H
+++ b/src/finiteArea/faMesh/faMesh.H
@@ -209,37 +209,43 @@ class faMesh
         faBoundaryMesh boundary_;
 
 
-        // Primitive mesh data
+    // Primitive mesh data
 
-            //- Edges, addressing into local point list
-            edgeList edges_;
+        //- Edges, addressing into local point list
+        edgeList edges_;
 
-            //- Edge owner
-            labelList edgeOwner_;
+        //- Edge owner
+        labelList edgeOwner_;
 
-            //- Edge neighbour
-            labelList edgeNeighbour_;
+        //- Edge neighbour
+        labelList edgeNeighbour_;
 
 
-        // Primitive size data
+    // Primitive size data
 
-            //- Total number of points
-            mutable label nPoints_;
+        //- Total number of points
+        mutable label nPoints_;
 
-            //- Total number of edges
-            mutable label nEdges_;
+        //- Total number of edges
+        mutable label nEdges_;
 
-            //- Number of internal edges
-            mutable label nInternalEdges_;
+        //- Number of internal edges
+        mutable label nInternalEdges_;
 
-            //- Number of faces
-            mutable label nFaces_;
+        //- Number of faces
+        mutable label nFaces_;
 
 
-        // Communication support
+    // Communication support, updating
 
-            //- Communicator used for parallel communication
-            label comm_;
+        //- Communicator used for parallel communication
+        label comm_;
+
+        //- Current time index for motion
+        //  Note.  The whole mechanism will be replaced once the
+        //  dimensionedField is created and the dimensionedField
+        //  will take care of the old-time levels.
+        mutable label curTimeIndex_;
 
 
     // Demand-driven data
@@ -253,11 +259,8 @@ class faMesh
         //- Ldu addressing data
         mutable faMeshLduAddressing* lduPtr_;
 
-        //- Current time index for motion
-        //  Note.  The whole mechanism will be replaced once the
-        //  dimensionedField is created and the dimensionedField
-        //  will take care of the old-time levels.
-        mutable label curTimeIndex_;
+
+    // Geometric Data
 
         //- Face areas
         mutable DimensionedField<scalar, areaMesh>* SPtr_;
@@ -545,6 +548,22 @@ public:
 
     // Static Functions
 
+        //- Return the current geometry treatment (0: primitive, 1: standard)
+        //  A zero level is with restricted neighbour information
+        static int geometryOrder() noexcept
+        {
+            return geometryOrder_;
+        }
+
+        //- Set the preferred geometry treatment
+        //  \return the previous value
+        static int geometryOrder(const int order) noexcept
+        {
+            int old(geometryOrder_);
+            geometryOrder_ = order;
+            return old;
+        }
+
         //- Read construction from polyMesh if all files are available
         static autoPtr<faMesh> TryNew(const polyMesh& pMesh);
 
@@ -571,73 +590,79 @@ public:
         bool init(const bool doInit);
 
 
-        // Database
+    // Database
+
+        //- Return access to polyMesh
+        inline const polyMesh& mesh() const;
 
-            //- Return access to polyMesh
-            inline const polyMesh& mesh() const;
+        //- Interface to referenced polyMesh (similar to GeoMesh)
+        const polyMesh& operator()() const { return mesh(); }
 
-            //- Interface to referenced polyMesh (similar to GeoMesh)
-            const polyMesh& operator()() const { return mesh(); }
+        //- Return the local mesh directory (dbDir()/meshSubDir)
+        fileName meshDir() const;
 
-            //- Return the local mesh directory (dbDir()/meshSubDir)
-            fileName meshDir() const;
+        //- Return reference to time
+        const Time& time() const;
 
-            //- Return reference to time
-            const Time& time() const;
+        //- Return the current instance directory for points
+        //  Used in the construction of geometric mesh data dependent
+        //  on points
+        const fileName& pointsInstance() const;
 
-            //- Return the current instance directory for points
-            //  Used in the construction of geometric mesh data dependent
-            //  on points
-            const fileName& pointsInstance() const;
+        //- Return the current instance directory for faces
+        const fileName& facesInstance() const;
 
-            //- Return the current instance directory for faces
-            const fileName& facesInstance() const;
 
+    // Communication support
 
-        // Communication support
+        //- Return communicator used for parallel communication
+        inline label comm() const noexcept;
 
-            //- Return communicator used for parallel communication
-            inline label comm() const noexcept;
+        //- Return communicator used for parallel communication
+        inline label& comm() noexcept;
 
-            //- Return communicator used for parallel communication
-            inline label& comm() noexcept;
 
+    // Access: Mesh size parameters
 
-            // Mesh size parameters
+        //- Number of local mesh points
+        inline label nPoints() const noexcept;
 
-                //- Number of local mesh points
-                inline label nPoints() const noexcept;
+        //- Number of local mesh edges
+        inline label nEdges() const noexcept;
 
-                //- Number of local mesh edges
-                inline label nEdges() const noexcept;
+        //- Number of internal faces
+        inline label nInternalEdges() const noexcept;
 
-                //- Number of internal faces
-                inline label nInternalEdges() const noexcept;
+        //- Number of boundary edges (== nEdges - nInternalEdges)
+        inline label nBoundaryEdges() const noexcept;
 
-                //- Number of boundary edges (== nEdges - nInternalEdges)
-                inline label nBoundaryEdges() const noexcept;
+        //- Number of patch faces
+        inline label nFaces() const noexcept;
 
-                //- Number of patch faces
-                inline label nFaces() const noexcept;
 
+    // Access: Primitive mesh data
 
-            // Primitive mesh data
+        //- Return local points
+        inline const pointField& points() const;
 
-                //- Return local patch points
-                inline const pointField& points() const;
+        //- Return local edges with reordered boundary
+        inline const edgeList& edges() const noexcept;
 
-                //- Return local patch edges with reordered boundary
-                inline const edgeList& edges() const noexcept;
+        //- Sub-list of local internal edges
+        inline const edgeList::subList internalEdges() const;
 
-                //- Return local patch faces
-                inline const faceList& faces() const;
+        //- Return local faces
+        inline const faceList& faces() const;
 
-                //- Edge owner addressing
-                inline const labelList& edgeOwner() const noexcept;
+        //- Edge owner addressing
+        inline const labelList& edgeOwner() const noexcept;
 
-                //- Edge neighbour addressing
-                inline const labelList& edgeNeighbour() const noexcept;
+        //- Edge neighbour addressing
+        inline const labelList& edgeNeighbour() const noexcept;
 
+        //- True if the internalEdges use an ordering that does not
+        //- correspond 1-to-1 with the patch internalEdges.
+        inline bool hasInternalEdgeLabels() const noexcept;
 
 
         // Access
@@ -649,7 +674,7 @@ public:
             virtual const objectRegistry& thisDb() const;
 
             //- Name function is needed to disambiguate those inherited
-            //  from base classes
+            //- from base classes
             const word& name() const
             {
                 return thisDb().name();
@@ -688,7 +713,7 @@ public:
             }
 
             //- True if given edge label is internal to the mesh
-            bool isInternalEdge(const label edgeIndex) const
+            bool isInternalEdge(const label edgeIndex) const noexcept
             {
                 return (edgeIndex < nInternalEdges_);
             }
@@ -817,6 +842,9 @@ public:
             boolList& correctPatchPointNormals() const;
 
 
+        // Storage management
+
+
         //- Write mesh
         virtual bool write(const bool valid = true) const;
 
diff --git a/src/finiteArea/faMesh/faMeshDemandDrivenData.C b/src/finiteArea/faMesh/faMeshDemandDrivenData.C
index c6af8c0499e1bfcc5f5768d33bc1e7673ef1ef94..fe9016edee789ee02a8447b491878180a3251965 100644
--- a/src/finiteArea/faMesh/faMeshDemandDrivenData.C
+++ b/src/finiteArea/faMesh/faMeshDemandDrivenData.C
@@ -37,6 +37,7 @@ License
 #include "scalarMatrices.H"
 #include "processorFaPatchFields.H"
 #include "emptyFaPatchFields.H"
+#include "triPointRef.H"
 
 // * * * * * * * * * * * * * * * Local Functions * * * * * * * * * * * * * * //
 
@@ -102,6 +103,121 @@ static inline vector areaInvDistSqrWeightedNormalDualEdge
     );
 }
 
+
+// Calculate transform tensor with reference vector (unitAxis1)
+// and direction vector (axis2).
+//
+// This is nearly identical to the meshTools axesRotation
+// with an E3_E1 transformation with the following exceptions:
+//
+// - axis1 (e3 == unitAxis1): is already normalized (unit vector)
+// - axis2 (e1 == dirn): no difference
+// - transformation is row-vectors, not column-vectors
+static inline tensor rotation_e3e1
+(
+    const vector& unitAxis1,
+    vector dirn
+)
+{
+    dirn.removeCollinear(unitAxis1);
+    dirn.normalise();
+
+    // Set row vectors
+    return tensor
+    (
+        dirn,
+        (unitAxis1^dirn),
+        unitAxis1
+    );
+}
+
+
+// Simple area-weighted normal calculation for the specified edge vector
+// and its owner/neighbour face centres (internal edges).
+//
+// Uses four triangles since adjacent faces may be non-planar
+// and/or the edge centre is skewed from the face centres.
+
+/*---------------------------------------------------------------------------*\
+ *          *           |
+ *         /|\          | Triangles (0,1) are on the owner side.
+ *        / | \         |
+ *       /  |  \        | Triangles (2,3) are on the neighbour side.
+ *      /  1|3  \       |
+ * own *----|----* nbr  | Boundary edges are the same, but without a neighbour
+ *      \  0|2  /       |
+ *       \  |  /        |
+ *        \ | /         |
+ *         \|/          |
+ *          *           |
+\*---------------------------------------------------------------------------*/
+
+static inline vector calcEdgeNormalFromFace
+(
+    const linePointRef& edgeVec,
+    const point& ownCentre,
+    const point& neiCentre
+)
+{
+    const scalar magEdge(edgeVec.mag());
+
+    if (magEdge < ROOTVSMALL)
+    {
+        return Zero;
+    }
+
+    const vector edgeCtr = edgeVec.centre();
+
+    vector edgeNorm
+    (
+        // owner
+        triPointRef(edgeCtr, ownCentre, edgeVec.first()).areaNormal()
+      + triPointRef(edgeCtr, edgeVec.last(), ownCentre).areaNormal()
+        // neighbour
+      + triPointRef(edgeCtr, edgeVec.first(), neiCentre).areaNormal()
+      + triPointRef(edgeCtr, neiCentre, edgeVec.last()).areaNormal()
+    );
+
+    // Requires a unit-vector (already tested its mag)
+    edgeNorm.removeCollinear(edgeVec.vec()/magEdge);
+    edgeNorm.normalise();
+
+    // Scale with the original edge length
+    return magEdge*edgeNorm;
+}
+
+
+// As above, but for boundary edgess (no neighbour)
+static inline vector calcEdgeNormalFromFace
+(
+    const linePointRef& edgeVec,
+    const point& ownCentre
+)
+{
+    const scalar magEdge(edgeVec.mag());
+
+    if (magEdge < ROOTVSMALL)
+    {
+        return Zero;
+    }
+
+    const vector edgeCtr = edgeVec.centre();
+
+    vector edgeNorm
+    (
+        // owner
+        triPointRef(edgeCtr, ownCentre, edgeVec.first()).areaNormal()
+      + triPointRef(edgeCtr, edgeVec.last(), ownCentre).areaNormal()
+    );
+
+    // Requires a unit-vector (already tested its mag)
+    edgeNorm.removeCollinear(edgeVec.vec()/magEdge);
+    edgeNorm.normalise();
+
+    // Scale with the original edge length
+    return magEdge*edgeNorm;
+}
+
 } // End namespace Foam
 
 
@@ -189,71 +305,187 @@ void Foam::faMesh::calcLe() const
 
     edgeVectorField& Le = *LePtr_;
 
+    const pointField& localPoints = points();
+
+    if (faMesh::geometryOrder() < 1)
+    {
+        // Simple (primitive) edge normal calculations.
+        // These are primarly designed to avoid any communication
+        // but are thus necessarily inconsistent across processor boundaries!
+
+        // Reasonable to assume that the volume mesh already has faceCentres
+        // eg, used magSf somewhere.
+        // Can use these instead of triggering our calcAreaCentres().
 
-    const pointField& pPoints = points();
-    const edgeList& pEdges = edges();
+        WarningInFunction
+            << "Using geometryOrder < 1 : "
+               "simplified edge area-normals for Le() calculation"
+            << endl;
+
+        UIndirectList<vector> fCentres(mesh().faceCentres(), faceLabels());
+
+        // Flat addressing
+        vectorField edgeNormals(nEdges_);
+
+        // Internal (edge normals)
+        for (label edgei = 0; edgei < nInternalEdges_; ++edgei)
+        {
+            edgeNormals[edgei] =
+                calcEdgeNormalFromFace
+                (
+                    edges_[edgei].line(localPoints),
+                    fCentres[owner()[edgei]],
+                    fCentres[neighbour()[edgei]]
+                );
+        }
+
+        // Boundary (edge normals). Like above, but only has owner
+        for (label edgei = nInternalEdges_; edgei < nEdges_; ++edgei)
+        {
+            edgeNormals[edgei] =
+                calcEdgeNormalFromFace
+                (
+                    edges_[edgei].line(localPoints),
+                    fCentres[owner()[edgei]]
+                );
+        }
+
+
+        // Now use these edge normals for calculating Le
+
+        // Internal (edge vector)
+        {
+            vectorField& internalFld = Le.ref();
+            for (label edgei = 0; edgei < nInternalEdges_; ++edgei)
+            {
+                vector& leVector = internalFld[edgei];
+
+                vector edgeVec = edges_[edgei].vec(localPoints);
+                const scalar magEdge(mag(edgeVec));
+
+                if (magEdge < ROOTVSMALL)
+                {
+                    // Too small
+                    leVector = Zero;
+                    continue;
+                }
+
+                const vector edgeCtr = edges_[edgei].centre(localPoints);
+                const vector& edgeNorm = edgeNormals[edgei];
+                const vector& ownCentre = fCentres[owner()[edgei]];
+
+                leVector = magEdge*normalised(edgeVec ^ edgeNorm);
+                leVector *= sign(leVector & (edgeCtr - ownCentre));
+            }
+        }
+
+        // Boundary (edge vector)
+
+        forAll(boundary(), patchi)
+        {
+            const labelUList& bndEdgeFaces = boundary()[patchi].edgeFaces();
+
+            const edgeList::subList bndEdges =
+                boundary()[patchi].patchSlice(edges_);
+
+            vectorField& patchLe = Le.boundaryFieldRef()[patchi];
+
+            forAll(patchLe, bndEdgei)
+            {
+                vector& leVector = patchLe[bndEdgei];
+                const label meshEdgei(boundary()[patchi].start() + bndEdgei);
+
+                vector edgeVec = bndEdges[bndEdgei].vec(localPoints);
+                const scalar magEdge(mag(edgeVec));
+
+                if (magEdge < ROOTVSMALL)
+                {
+                    // Too small
+                    leVector = Zero;
+                    continue;
+                }
+
+                const vector edgeCtr = bndEdges[bndEdgei].centre(localPoints);
+                const vector& edgeNorm = edgeNormals[meshEdgei];
+                const vector& ownCentre = fCentres[bndEdgeFaces[bndEdgei]];
+
+                leVector = magEdge*normalised(edgeVec ^ edgeNorm);
+                leVector *= sign(leVector & (edgeCtr - ownCentre));
+            }
+        }
+
+        // Done
+        return;
+    }
+
+
+    // Longer forms.
+    // Using edgeAreaNormals, which uses pointAreaNormals (communication!)
 
     const edgeVectorField& eCentres = edgeCentres();
     const areaVectorField& fCentres = areaCentres();
-
     const edgeVectorField& edgeNormals = edgeAreaNormals();
 
-    vectorField& leInternal = Le.ref();
-    const vectorField& edgeNormalsInternal = edgeNormals.internalField();
-    const vectorField& fCentresInternal = fCentres.internalField();
-    const vectorField& eCentresInternal = eCentres.internalField();
-    const scalarField& magLeInternal = magLe().internalField();
+    // Internal (edge vector)
 
-    forAll(leInternal, edgeI)
     {
-        leInternal[edgeI] =
-            pEdges[edgeI].vec(pPoints) ^ edgeNormalsInternal[edgeI];
+        vectorField& internalFld = Le.ref();
+        for (label edgei = 0; edgei < nInternalEdges_; ++edgei)
+        {
+            vector& leVector = internalFld[edgei];
 
-        leInternal[edgeI] *=
-          - sign
-            (
-                leInternal[edgeI] &
-                (
-                    fCentresInternal[owner()[edgeI]]
-                  - eCentresInternal[edgeI]
-                )
-            );
+            vector edgeVec = edges_[edgei].vec(localPoints);
+            const scalar magEdge(mag(edgeVec));
 
-        leInternal[edgeI] *=
-            magLeInternal[edgeI]/mag(leInternal[edgeI]);
+            if (magEdge < ROOTVSMALL)
+            {
+                // Too small
+                leVector = Zero;
+                continue;
+            }
+
+            const vector& edgeCtr = eCentres[edgei];
+            const vector& edgeNorm = edgeNormals[edgei];
+            const vector& ownCentre = fCentres[owner()[edgei]];
+
+            leVector = magEdge*normalised(edgeVec ^ edgeNorm);
+            leVector *= sign(leVector & (edgeCtr - ownCentre));
+        }
     }
 
-    forAll(boundary(), patchI)
+    forAll(boundary(), patchi)
     {
-        const labelUList& bndEdgeFaces = boundary()[patchI].edgeFaces();
+        const labelUList& bndEdgeFaces = boundary()[patchi].edgeFaces();
 
         const edgeList::subList bndEdges =
-            boundary()[patchI].patchSlice(pEdges);
+            boundary()[patchi].patchSlice(edges_);
 
         const vectorField& bndEdgeNormals =
-            edgeNormals.boundaryField()[patchI];
+            edgeNormals.boundaryField()[patchi];
 
-        vectorField& patchLe = Le.boundaryFieldRef()[patchI];
-        const vectorField& patchECentres = eCentres.boundaryField()[patchI];
+        vectorField& patchLe = Le.boundaryFieldRef()[patchi];
+        const vectorField& patchECentres = eCentres.boundaryField()[patchi];
 
-        forAll(patchLe, edgeI)
+        forAll(patchLe, bndEdgei)
         {
-            patchLe[edgeI] =
-                bndEdges[edgeI].vec(pPoints) ^ bndEdgeNormals[edgeI];
+            vector& leVector = patchLe[bndEdgei];
 
-            patchLe[edgeI] *=
-              - sign
-                (
-                    patchLe[edgeI]&
-                    (
-                        fCentresInternal[bndEdgeFaces[edgeI]]
-                      - patchECentres[edgeI]
-                    )
-                );
+            vector edgeVec = bndEdges[bndEdgei].vec(localPoints);
+            const scalar magEdge(mag(edgeVec));
+
+            if (magEdge < ROOTVSMALL)
+            {
+                // Too small
+                leVector = Zero;
+                continue;
+            }
 
-            patchLe[edgeI] *=
-                magLe().boundaryField()[patchI][edgeI]
-                /mag(patchLe[edgeI]);
+            const vector& edgeCtr = patchECentres[bndEdgei];
+            const vector& edgeNorm = bndEdgeNormals[bndEdgei];
+            const vector& ownCentre = fCentres[bndEdgeFaces[bndEdgei]];
+
+            leVector = magEdge*normalised(edgeVec ^ edgeNorm);
+            leVector *= sign(leVector & (edgeCtr - ownCentre));
         }
     }
 }
@@ -289,23 +521,30 @@ void Foam::faMesh::calcMagLe() const
 
     const pointField& localPoints = points();
 
-    label edgei = 0;
-    for (const edge& e : patch().internalEdges())
+    // Internal (edge length)
     {
-        magLe.ref()[edgei] = e.mag(localPoints);
-        ++edgei;
-    }
+        auto iter = magLe.primitiveFieldRef().begin();
 
+        for (const edge& e : internalEdges())
+        {
+            *iter = e.mag(localPoints);
+            ++iter;
+        }
+    }
 
-    forAll(boundary(), patchI)
+    // Boundary (edge length)
     {
-        const edgeList::subList patchEdges =
-            boundary()[patchI].patchSlice(edges());
+        auto& bfld = magLe.boundaryFieldRef();
 
-        forAll(patchEdges, edgeI)
+        forAll(boundary(), patchi)
         {
-            magLe.boundaryFieldRef()[patchI][edgeI] =
-                patchEdges[edgeI].mag(localPoints);
+            auto iter = bfld[patchi].begin();
+
+            for (const edge& e : boundary()[patchi].patchSlice(edges_))
+            {
+                *iter = e.mag(localPoints);
+                ++iter;
+            }
         }
     }
 }
@@ -343,21 +582,25 @@ void Foam::faMesh::calcAreaCentres() const
     const faceList& localFaces = faces();
 
     // Internal (face centres)
-    forAll(localFaces, faceI)
+    // Could also obtain from volume mesh faceCentres()
+    forAll(localFaces, facei)
     {
-        centres.ref()[faceI] = localFaces[faceI].centre(localPoints);
+        centres.ref()[facei] = localFaces[facei].centre(localPoints);
     }
 
     // Boundary (edge centres)
-    forAll(boundary(), patchI)
     {
-        const edgeList::subList patchEdges =
-            boundary()[patchI].patchSlice(edges());
+        auto& bfld = centres.boundaryFieldRef();
 
-        forAll(patchEdges, edgeI)
+        forAll(boundary(), patchi)
         {
-            centres.boundaryFieldRef()[patchI][edgeI] =
-                patchEdges[edgeI].centre(localPoints);
+            auto iter = bfld[patchi].begin();
+
+            for (const edge& e : boundary()[patchi].patchSlice(edges_))
+            {
+                *iter = e.centre(localPoints);
+                ++iter;
+            }
         }
     }
 }
@@ -389,28 +632,34 @@ void Foam::faMesh::calcEdgeCentres() const
             dimLength
         );
 
-    edgeVectorField& edgeCentres = *edgeCentresPtr_;
+    edgeVectorField& centres = *edgeCentresPtr_;
 
     const pointField& localPoints = points();
 
-    // Internal edges
-    label edgei = 0;
-    for (const edge& e : patch().internalEdges())
+    // Internal (edge centres)
     {
-        edgeCentres.ref()[edgei] = e.centre(localPoints);
-        ++edgei;
+        auto iter = centres.primitiveFieldRef().begin();
+
+        for (const edge& e : internalEdges())
+        {
+            *iter = e.centre(localPoints);
+            ++iter;
+        }
     }
 
-    // Boundary edges
-    forAll(boundary(), patchI)
+    // Boundary (edge centres)
     {
-        const edgeList::subList patchEdges =
-            boundary()[patchI].patchSlice(edges());
+        auto& bfld = centres.boundaryFieldRef();
 
-        forAll(patchEdges, edgeI)
+        forAll(boundary(), patchi)
         {
-            edgeCentres.boundaryFieldRef()[patchI][edgeI] =
-                patchEdges[edgeI].centre(localPoints);
+            auto iter = bfld[patchi].begin();
+
+            for (const edge& e : boundary()[patchi].patchSlice(edges_))
+            {
+                *iter = e.centre(localPoints);
+                ++iter;
+            }
         }
     }
 }
@@ -446,9 +695,10 @@ void Foam::faMesh::calcS() const
     const pointField& localPoints = points();
     const faceList& localFaces = faces();
 
-    forAll(S, faceI)
+    // Could also obtain from volume mesh faceAreas()
+    forAll(S, facei)
     {
-        S[faceI] = localFaces[faceI].mag(localPoints);
+        S[facei] = localFaces[facei].mag(localPoints);
     }
 }
 
@@ -485,6 +735,7 @@ void Foam::faMesh::calcFaceAreaNormals() const
     const faceList& localFaces = faces();
 
     // Internal (faces)
+    // Could also obtain from volume mesh Sf() + normalise
     vectorField& nInternal = faceNormals.ref();
     forAll(localFaces, faceI)
     {
@@ -493,8 +744,7 @@ void Foam::faMesh::calcFaceAreaNormals() const
 
     // Boundary - copy from edges
 
-    const edgeVectorField::Boundary& edgeNormalsBoundary =
-        edgeAreaNormals().boundaryField();
+    const auto& edgeNormalsBoundary = edgeAreaNormals().boundaryField();
 
     forAll(boundary(), patchI)
     {
@@ -528,46 +778,50 @@ void Foam::faMesh::calcEdgeAreaNormals() const
             *this,
             dimless
         );
-
     edgeVectorField& edgeAreaNormals = *edgeAreaNormalsPtr_;
 
 
-    // Point area normals
+    // Starting from point area normals
     const vectorField& pointNormals = pointAreaNormals();
 
+
+    // Internal edges
     forAll(edgeAreaNormals.internalField(), edgei)
     {
-        const edge& e = edges()[edgei];
+        const edge& e = edges_[edgei];
         const vector edgeVec = e.unitVec(points());
 
-        vector& n = edgeAreaNormals.ref()[edgei];
-
-        n = (pointNormals[e.first()] + pointNormals[e.second()]);
+        vector& edgeNorm = edgeAreaNormals.ref()[edgei];
 
-        n -= edgeVec*(edgeVec & n);
+        // Average of both ends
+        edgeNorm = (pointNormals[e.first()] + pointNormals[e.second()]);
 
-        n.normalise();
+        edgeNorm.removeCollinear(edgeVec);
+        edgeNorm.normalise();
     }
 
+    // Boundary
+    auto& bfld = edgeAreaNormals.boundaryFieldRef();
+
     forAll(boundary(), patchi)
     {
-        const edgeList::subList patchEdges =
-            boundary()[patchi].patchSlice(edges());
+        auto& pfld = bfld[patchi];
 
-        vectorField& edgeNorms = edgeAreaNormals.boundaryFieldRef()[patchi];
+        const edgeList::subList patchEdges =
+            boundary()[patchi].patchSlice(edges_);
 
-        forAll(patchEdges, edgei)
+        forAll(patchEdges, bndEdgei)
         {
-            const edge& e = patchEdges[edgei];
+            const edge& e = patchEdges[bndEdgei];
             const vector edgeVec = e.unitVec(points());
 
-            vector& n = edgeNorms[edgei];
-
-            n = (pointNormals[e.first()] + pointNormals[e.second()]);
+            vector& edgeNorm = pfld[bndEdgei];
 
-            n -= edgeVec*(edgeVec & n);
+            // Average of both ends
+            edgeNorm = (pointNormals[e.first()] + pointNormals[e.second()]);
 
-            n.normalise();
+            edgeNorm.removeCollinear(edgeVec);
+            edgeNorm.normalise();
         }
     }
 }
@@ -624,9 +878,14 @@ void Foam::faMesh::calcEdgeTransformTensors() const
             << abort(FatalError);
     }
 
-    edgeTransformTensorsPtr_ = new FieldField<Field, tensor>(nEdges());
-    FieldField<Field, tensor>& edgeTransformTensors =
-        *edgeTransformTensorsPtr_;
+    edgeTransformTensorsPtr_ = new FieldField<Field, tensor>(nEdges_);
+    auto& edgeTransformTensors = *edgeTransformTensorsPtr_;
+
+    // Initialize all transformation tensors
+    for (label edgei = 0; edgei < nEdges_; ++edgei)
+    {
+        edgeTransformTensors.set(edgei, new Field<tensor>(3, tensor::I));
+    }
 
     const areaVectorField& Nf = faceAreaNormals();
     const areaVectorField& Cf = areaCentres();
@@ -635,193 +894,129 @@ void Foam::faMesh::calcEdgeTransformTensors() const
     const edgeVectorField& Ce = edgeCentres();
 
     // Internal edges transformation tensors
-    for (label edgeI=0; edgeI<nInternalEdges(); ++edgeI)
+    for (label edgei = 0; edgei < nInternalEdges_; ++edgei)
     {
-        edgeTransformTensors.set(edgeI, new Field<tensor>(3, I));
+        const label ownFacei = owner()[edgei];
+        const label neiFacei = neighbour()[edgei];
+        auto& tensors = edgeTransformTensors[edgei];
 
-        vector E = Ce.internalField()[edgeI];
+        vector edgeCtr = Ce.internalField()[edgei];
 
         if (skew())
         {
-            E -= skewCorrectionVectors().internalField()[edgeI];
+            edgeCtr -= skewCorrectionVectors().internalField()[edgei];
         }
 
         // Edge transformation tensor
-        vector il = E - Cf.internalField()[owner()[edgeI]];
-
-        il -= Ne.internalField()[edgeI]
-            *(Ne.internalField()[edgeI]&il);
-
-        il /= mag(il);
-
-        vector kl = Ne.internalField()[edgeI];
-        vector jl = kl^il;
-
-        edgeTransformTensors[edgeI][0] =
-            tensor
+        tensors[0] =
+            rotation_e3e1
             (
-                il.x(), il.y(), il.z(),
-                jl.x(), jl.y(), jl.z(),
-                kl.x(), kl.y(), kl.z()
+                Ne.internalField()[edgei],
+                (edgeCtr - Cf.internalField()[ownFacei])
             );
 
         // Owner transformation tensor
-        il = E - Cf.internalField()[owner()[edgeI]];
-
-        il -= Nf.internalField()[owner()[edgeI]]
-            *(Nf.internalField()[owner()[edgeI]]&il);
-
-        il /= mag(il);
-
-        kl = Nf.internalField()[owner()[edgeI]];
-        jl = kl^il;
-
-        edgeTransformTensors[edgeI][1] =
-            tensor
+        tensors[1] =
+            rotation_e3e1
             (
-                il.x(), il.y(), il.z(),
-                jl.x(), jl.y(), jl.z(),
-                kl.x(), kl.y(), kl.z()
+                Nf.internalField()[ownFacei],
+                (edgeCtr - Cf.internalField()[ownFacei])
             );
 
         // Neighbour transformation tensor
-        il = Cf.internalField()[neighbour()[edgeI]] - E;
-
-        il -= Nf.internalField()[neighbour()[edgeI]]
-            *(Nf.internalField()[neighbour()[edgeI]]&il);
-
-        il /= mag(il);
-
-        kl = Nf.internalField()[neighbour()[edgeI]];
-        jl = kl^il;
-
-        edgeTransformTensors[edgeI][2] =
-            tensor
+        tensors[2] =
+            rotation_e3e1
             (
-                il.x(), il.y(), il.z(),
-                jl.x(), jl.y(), jl.z(),
-                kl.x(), kl.y(), kl.z()
+                Nf.internalField()[neiFacei],
+                (Cf.internalField()[neiFacei] - edgeCtr)
             );
     }
 
     // Boundary edges transformation tensors
-    forAll(boundary(), patchI)
+    forAll(boundary(), patchi)
     {
-        if (boundary()[patchI].coupled())
-        {
-            const labelUList& edgeFaces =
-                boundary()[patchI].edgeFaces();
-
-            vectorField ngbCf(Cf.boundaryField()[patchI].patchNeighbourField());
+        const labelUList& edgeFaces = boundary()[patchi].edgeFaces();
 
-            vectorField ngbNf(Nf.boundaryField()[patchI].patchNeighbourField());
+        if (boundary()[patchi].coupled())
+        {
+            vectorField nbrCf(Cf.boundaryField()[patchi].patchNeighbourField());
+            vectorField nbrNf(Nf.boundaryField()[patchi].patchNeighbourField());
 
-            forAll(edgeFaces, edgeI)
+            forAll(edgeFaces, bndEdgei)
             {
-                edgeTransformTensors.set
-                (
-                    boundary()[patchI].start() + edgeI,
-                    new Field<tensor>(3, I)
-                );
+                const label ownFacei = edgeFaces[bndEdgei];
+                const label meshEdgei(boundary()[patchi].start() + bndEdgei);
+
+                auto& tensors = edgeTransformTensors[meshEdgei];
 
-                vector E = Ce.boundaryField()[patchI][edgeI];
+                vector edgeCtr = Ce.boundaryField()[patchi][bndEdgei];
 
                 if (skew())
                 {
-                    E -= skewCorrectionVectors()
-                        .boundaryField()[patchI][edgeI];
+                    edgeCtr -= skewCorrectionVectors()
+                        .boundaryField()[patchi][bndEdgei];
                 }
 
                 // Edge transformation tensor
-                vector il = E - Cf.internalField()[edgeFaces[edgeI]];
-
-                il -= Ne.boundaryField()[patchI][edgeI]
-                   *(Ne.boundaryField()[patchI][edgeI]&il);
-
-                il /= mag(il);
-
-                vector kl = Ne.boundaryField()[patchI][edgeI];
-                vector jl = kl^il;
-
-                edgeTransformTensors[boundary()[patchI].start() + edgeI][0] =
-                    tensor(il, jl, kl);
+                tensors[0] =
+                    rotation_e3e1
+                    (
+                        Ne.boundaryField()[patchi][bndEdgei],
+                        (edgeCtr - Cf.internalField()[ownFacei])
+                    );
 
                 // Owner transformation tensor
-                il = E - Cf.internalField()[edgeFaces[edgeI]];
-
-                il -= Nf.internalField()[edgeFaces[edgeI]]
-                   *(Nf.internalField()[edgeFaces[edgeI]]&il);
-
-                il /= mag(il);
-
-                kl = Nf.internalField()[edgeFaces[edgeI]];
-                jl = kl^il;
-
-                edgeTransformTensors[boundary()[patchI].start() + edgeI][1] =
-                    tensor(il, jl, kl);
+                tensors[1] =
+                    rotation_e3e1
+                    (
+                        Nf.internalField()[ownFacei],
+                        (edgeCtr - Cf.internalField()[ownFacei])
+                    );
 
                 // Neighbour transformation tensor
-                il = ngbCf[edgeI] - E;
-
-                il -= ngbNf[edgeI]*(ngbNf[edgeI]&il);
-
-                il /= mag(il);
-
-                kl = ngbNf[edgeI];
-
-                jl = kl^il;
-
-                edgeTransformTensors[boundary()[patchI].start() + edgeI][2] =
-                    tensor(il, jl, kl);
+                tensors[2] =
+                    rotation_e3e1
+                    (
+                        nbrNf[bndEdgei],
+                        (nbrCf[bndEdgei] - edgeCtr)
+                    );
             }
         }
         else
         {
-            const labelUList& edgeFaces = boundary()[patchI].edgeFaces();
-
-            forAll(edgeFaces, edgeI)
+            forAll(edgeFaces, bndEdgei)
             {
-                edgeTransformTensors.set
-                (
-                    boundary()[patchI].start() + edgeI,
-                    new Field<tensor>(3, I)
-                );
+                const label ownFacei = edgeFaces[bndEdgei];
+                const label meshEdgei(boundary()[patchi].start() + bndEdgei);
+
+                auto& tensors = edgeTransformTensors[meshEdgei];
 
-                vector E = Ce.boundaryField()[patchI][edgeI];
+                vector edgeCtr = Ce.boundaryField()[patchi][bndEdgei];
 
                 if (skew())
                 {
-                    E -= skewCorrectionVectors()
-                        .boundaryField()[patchI][edgeI];
+                    edgeCtr -= skewCorrectionVectors()
+                        .boundaryField()[patchi][bndEdgei];
                 }
 
                 // Edge transformation tensor
-                vector il = E - Cf.internalField()[edgeFaces[edgeI]];
-
-                il -= Ne.boundaryField()[patchI][edgeI]
-                   *(Ne.boundaryField()[patchI][edgeI]&il);
-
-                il /= mag(il);
-
-                vector kl = Ne.boundaryField()[patchI][edgeI];
-                vector jl = kl^il;
-
-                edgeTransformTensors[boundary()[patchI].start() + edgeI][0] =
-                    tensor(il, jl, kl);
+                tensors[0] =
+                    rotation_e3e1
+                    (
+                        Ne.boundaryField()[patchi][bndEdgei],
+                        (edgeCtr - Cf.internalField()[ownFacei])
+                    );
 
                 // Owner transformation tensor
-                il = E - Cf.internalField()[edgeFaces[edgeI]];
-
-                il -= Nf.internalField()[edgeFaces[edgeI]]
-                   *(Nf.internalField()[edgeFaces[edgeI]]&il);
-
-                il /= mag(il);
-
-                kl = Nf.internalField()[edgeFaces[edgeI]];
-                jl = kl^il;
+                tensors[1] =
+                    rotation_e3e1
+                    (
+                        Nf.internalField()[ownFacei],
+                        (edgeCtr - Cf.internalField()[ownFacei])
+                    );
 
-                edgeTransformTensors[boundary()[patchI].start() + edgeI][1] =
-                    tensor(il, jl, kl);
+                // Neighbour transformation tensor
+                tensors[2] = tensor::I;
             }
         }
     }
@@ -882,7 +1077,7 @@ void Foam::faMesh::calcPointAreaNormals(vectorField& result) const
     DebugInFunction
         << "Calculating pointAreaNormals : face dual method" << endl;
 
-    result.resize(nPoints());
+    result.resize_nocopy(nPoints());
     result = Zero;
 
     const pointField& points = patch().localPoints();
@@ -945,7 +1140,7 @@ void Foam::faMesh::calcPointAreaNormals(vectorField& result) const
 
             for (const label pti : patchPoints)
             {
-                result[pti] -= N*(N&result[pti]);
+                result[pti].removeCollinear(N);
             }
 
             // Axis point correction
@@ -1124,17 +1319,13 @@ void Foam::faMesh::calcPointAreaNormals(vectorField& result) const
         forAllConstIters(fpNormals, iter)
         {
             const label pointi = iter.key();
-            vector fpnorm = iter.val();
+            vector fpnorm = normalised(iter.val());
 
-            fpnorm.normalise();
-            result[pointi] -= fpnorm*(fpnorm & result[pointi]);
+            result[pointi].removeCollinear(fpnorm);
         }
     }
 
-    for (vector& n : result)
-    {
-        n.normalise();
-    }
+    result.normalise();
 }
 
 
@@ -1469,7 +1660,7 @@ void Foam::faMesh::calcPointAreaNormalsByQuadricsFit(vectorField& result) const
                 const vector& origin = points[curPoint];
                 const vector axis = normalised(result[curPoint]);
                 vector dir(allPoints[0] - points[curPoint]);
-                dir -= axis*(axis&dir);
+                dir.removeCollinear(axis);
                 dir.normalise();
 
                 const coordinateSystem cs("cs", origin, axis, dir);
@@ -1633,7 +1824,7 @@ void Foam::faMesh::calcPointAreaNormalsByQuadricsFit(vectorField& result) const
                 const vector& origin = points[curPoint];
                 const vector axis = normalised(result[curPoint]);
                 vector dir(allPoints[0] - points[curPoint]);
-                dir -= axis*(axis&dir);
+                dir.removeCollinear(axis);
                 dir.normalise();
 
                 coordinateSystem cs("cs", origin, axis, dir);
@@ -1710,22 +1901,19 @@ Foam::tmp<Foam::edgeScalarField> Foam::faMesh::edgeLengthCorrection() const
     DebugInFunction
         << "Calculating edge length correction" << endl;
 
-    tmp<edgeScalarField> tcorrection
+    auto tcorrection = tmp<edgeScalarField>::New
     (
-        new edgeScalarField
+        IOobject
         (
-            IOobject
-            (
-                "edgeLengthCorrection",
-                mesh().pointsInstance(),
-                meshSubDir,
-                mesh()
-            ),
-            *this,
-            dimless
-        )
+            "edgeLengthCorrection",
+            mesh().pointsInstance(),
+            meshSubDir,
+            mesh()
+        ),
+        *this,
+        dimless
     );
-    edgeScalarField& correction = tcorrection.ref();
+    auto& correction = tcorrection.ref();
 
     const vectorField& pointNormals = pointAreaNormals();
 
diff --git a/src/finiteArea/faMesh/faMeshI.H b/src/finiteArea/faMesh/faMeshI.H
index dc1b238c3d4a077b4f90e0675a76ba7e1be5be31..dfdbc00f796c06756067542a1c9e2c82ee7a2806 100644
--- a/src/finiteArea/faMesh/faMeshI.H
+++ b/src/finiteArea/faMesh/faMeshI.H
@@ -6,7 +6,7 @@
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
     Copyright (C) 2016-2017 Wikki Ltd
-    Copyright (C) 2021 OpenCFD Ltd.
+    Copyright (C) 2021-2022 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -73,7 +73,7 @@ inline Foam::label Foam::faMesh::nInternalEdges() const noexcept
 
 inline Foam::label Foam::faMesh::nBoundaryEdges() const noexcept
 {
-    return nEdges_ - nInternalEdges_;
+    return (nEdges_ - nInternalEdges_);
 }
 
 
@@ -95,6 +95,12 @@ inline const Foam::edgeList& Foam::faMesh::edges() const noexcept
 }
 
 
+inline const Foam::edgeList::subList Foam::faMesh::internalEdges() const
+{
+    return edgeList::subList(edges_, nInternalEdges_);
+}
+
+
 inline const Foam::faceList& Foam::faMesh::faces() const
 {
     return patch().localFaces();
@@ -139,6 +145,12 @@ inline Foam::uindirectPrimitivePatch& Foam::faMesh::patch()
 }
 
 
+inline bool Foam::faMesh::hasInternalEdgeLabels() const noexcept
+{
+    return false;
+}
+
+
 inline const Foam::List<Foam::labelPair>&
 Foam::faMesh::boundaryConnections() const
 {
diff --git a/src/finiteArea/faMesh/faMeshSubset/faMeshSubset.C b/src/finiteArea/faMesh/faMeshSubset/faMeshSubset.C
new file mode 100644
index 0000000000000000000000000000000000000000..5a088e3a239bb6ead5690eb1be8744b39a1980bf
--- /dev/null
+++ b/src/finiteArea/faMesh/faMeshSubset/faMeshSubset.C
@@ -0,0 +1,152 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
+   \\    /   O peration     |
+    \\  /    A nd           | www.openfoam.com
+     \\/     M anipulation  |
+-------------------------------------------------------------------------------
+    Copyright (C) 2022 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 "faMeshSubset.H"
+#include "boolList.H"
+#include "BitOps.H"
+#include "Pstream.H"
+#include "emptyFaPatch.H"
+#include "cyclicFaPatch.H"
+
+// * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * //
+
+Foam::word Foam::faMeshSubset::exposedPatchName("oldInternalEdges");
+
+
+// * * * * * * * * * * * * * Private Member Functions  * * * * * * * * * * * //
+
+bool Foam::faMeshSubset::checkHasSubMesh() const
+{
+    if (!subMeshPtr_)
+    {
+        FatalErrorInFunction
+            << "Mesh is not subsetted!" << nl
+            << abort(FatalError);
+
+        return false;
+    }
+
+    return true;
+}
+
+
+// * * * * * * * * * * * * * * * * Constructors  * * * * * * * * * * * * * * //
+
+Foam::faMeshSubset::faMeshSubset(const faMesh& baseMesh)
+:
+    baseMesh_(baseMesh),
+    subMeshPtr_(nullptr),
+    edgeFlipMapPtr_(nullptr),
+    pointMap_(),
+    faceMap_(),
+    cellMap_(),
+    patchMap_()
+{}
+
+
+Foam::faMeshSubset::faMeshSubset(const faMesh& baseMesh, const Foam::zero)
+:
+    faMeshSubset(baseMesh)
+{
+    reset(Foam::zero{});
+}
+
+
+// * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //
+
+void Foam::faMeshSubset::clear()
+{
+    subMeshPtr_.reset(nullptr);
+    edgeFlipMapPtr_.reset(nullptr);
+
+    pointMap_.clear();
+    faceMap_.clear();
+    cellMap_.clear();
+    patchMap_.clear();
+}
+
+
+void Foam::faMeshSubset::reset()
+{
+    clear();
+}
+
+
+void Foam::faMeshSubset::reset(const Foam::zero)
+{
+    clear();
+
+    // Create zero-sized subMesh
+    subMeshPtr_.reset
+    (
+        new faMesh
+        (
+            baseMesh_.mesh(),   // The polyMesh
+            // IOobject
+            // (
+            //     baseMesh_.name(),
+            //     baseMesh_.time().timeName(),
+            //     baseMesh_.time(),
+            //     IOobject::READ_IF_PRESENT,  // Read fa* if present
+            //     IOobject::NO_WRITE
+            // ),
+            Foam::zero{}                    // zero-sized
+        )
+    );
+    auto& newSubMesh = subMeshPtr_();
+
+
+    // Clone non-processor patches
+    {
+        const faBoundaryMesh& oldBoundary = baseMesh_.boundary();
+        const faBoundaryMesh& newBoundary = newSubMesh.boundary();
+
+        faPatchList newPatches(oldBoundary.nNonProcessor());
+
+        patchMap_ = identity(newPatches.size());
+
+        forAll(newPatches, patchi)
+        {
+            newPatches.set
+            (
+                patchi,
+                oldBoundary[patchi].clone
+                (
+                    newBoundary,
+                    labelList(),   // edgeLabels
+                    patchi,
+                    oldBoundary[patchi].ngbPolyPatchIndex()
+                )
+            );
+        }
+
+        newSubMesh.addFaPatches(newPatches);
+    }
+}
+
+
+// ************************************************************************* //
diff --git a/src/finiteArea/faMesh/faMeshSubset/faMeshSubset.H b/src/finiteArea/faMesh/faMeshSubset/faMeshSubset.H
new file mode 100644
index 0000000000000000000000000000000000000000..c02ed434f1a27cef9fc9587c5e3aac5a2ba4d428
--- /dev/null
+++ b/src/finiteArea/faMesh/faMeshSubset/faMeshSubset.H
@@ -0,0 +1,222 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
+   \\    /   O peration     |
+    \\  /    A nd           | www.openfoam.com
+     \\/     M anipulation  |
+-------------------------------------------------------------------------------
+    Copyright (C) 2022 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::faMeshSubset
+
+Description
+    Holds a reference to the original mesh (the baseMesh)
+    and optionally to a subset of that mesh (the subMesh)
+    with mapping lists for points, faces, and cells.
+
+Caution
+    Currently not really functional for subsetting beyond handling
+    a simple zero-sized mesh.
+
+SourceFiles
+    faMeshSubset.C
+    faMeshSubsetI.H
+    faMeshSubsetTemplates.C
+
+\*---------------------------------------------------------------------------*/
+
+#ifndef Foam_faMeshSubset_H
+#define Foam_faMeshSubset_H
+
+#include "areaFaMesh.H"
+#include "edgeFaMesh.H"
+#include "GeometricField.H"
+#include "bitSet.H"
+#include "HashSet.H"
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+namespace Foam
+{
+
+// Forward Declarations
+class mapDistributePolyMesh;
+
+/*---------------------------------------------------------------------------*\
+                        Class faMeshSubset Declaration
+\*---------------------------------------------------------------------------*/
+
+class faMeshSubset
+{
+    // Private Data
+
+        //- The base mesh to subset from
+        const faMesh& baseMesh_;
+
+        //- Demand-driven subset mesh (pointer)
+        autoPtr<faMesh> subMeshPtr_;
+
+        //- Optional edge mapping array with flip encoded (-1/+1)
+        mutable autoPtr<labelList> edgeFlipMapPtr_;
+
+        //- Point mapping array
+        labelList pointMap_;
+
+        //- Face mapping array
+        labelList faceMap_;
+
+        //- Cell mapping array
+        labelList cellMap_;
+
+        //- Patch mapping array
+        labelList patchMap_;
+
+
+    // Private Member Functions
+
+        //- Calculate face flip map
+        void calcEdgeFlipMap() const;
+
+
+protected:
+
+    // Protected Member Functions
+
+        //- FatalError if subset has not been performed
+        bool checkHasSubMesh() const;
+
+        //- No copy construct
+        faMeshSubset(const faMeshSubset&) = delete;
+
+        //- No copy assignment
+        void operator=(const faMeshSubset&) = delete;
+
+
+public:
+
+    // Static Data Members
+
+        //- Name for exposed internal edges (default: oldInternalEdges)
+        static word exposedPatchName;
+
+
+    // Constructors
+
+        //- Construct using the entire mesh (no subset)
+        explicit faMeshSubset(const faMesh& baseMesh);
+
+        //- Construct a zero-sized subset mesh, non-processor patches only
+        faMeshSubset(const faMesh& baseMesh, const Foam::zero);
+
+
+    // Member Functions
+
+    // Access
+
+        //- Original mesh
+        inline const faMesh& baseMesh() const noexcept;
+
+        //- Return baseMesh or subMesh, depending on the current state.
+        inline const faMesh& mesh() const noexcept;
+
+        //- Have subMesh?
+        inline bool hasSubMesh() const noexcept;
+
+        //- Return reference to subset mesh
+        inline const faMesh& subMesh() const;
+
+        //- Return reference to subset mesh
+        inline faMesh& subMesh();
+
+        //- Return point map
+        inline const labelList& pointMap() const;
+
+        //- Return face map
+        inline const labelList& faceMap() const;
+
+        //- Return edge map with sign to encode flipped edges
+        inline const labelList& edgeFlipMap() const;
+
+        //- Return cell map
+        inline const labelList& cellMap() const;
+
+        //- Return patch map
+        inline const labelList& patchMap() const;
+
+
+    // Edit
+
+        //- Reset subMesh and all maps
+        void clear();
+
+        //- Reset subMesh and all maps. Same as clear()
+        void reset();
+
+        //- Reset to a zero-sized subset mesh, non-processor patches only
+        void reset(const Foam::zero);
+
+
+    // Field Mapping (static functions)
+
+        //- Map area field.
+        //  Optionally allow unmapped faces not to produce a warning
+        template<class Type>
+        static tmp<GeometricField<Type, faPatchField, areaMesh>>
+        interpolate
+        (
+            const GeometricField<Type, faPatchField, areaMesh>&,
+            const faMesh& sMesh,
+            const bool allowUnmapped = false
+        );
+
+
+    // Field Mapping
+
+        //- Map area field.
+        //  Optionally allow unmapped faces not to produce a warning
+        template<class Type>
+        tmp<GeometricField<Type, faPatchField, areaMesh>>
+        interpolate
+        (
+            const GeometricField<Type, faPatchField, areaMesh>&,
+            const bool allowUnmapped = false
+        ) const;
+};
+
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+} // End namespace Foam
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+#include "faMeshSubsetI.H"
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+#ifdef NoRepository
+    #include "faMeshSubsetTemplates.C"
+#endif
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+#endif
+
+// ************************************************************************* //
diff --git a/src/finiteArea/faMesh/faMeshSubset/faMeshSubsetI.H b/src/finiteArea/faMesh/faMeshSubset/faMeshSubsetI.H
new file mode 100644
index 0000000000000000000000000000000000000000..8a1d67cd39030b211735c7af9f2f4523ceb9c5de
--- /dev/null
+++ b/src/finiteArea/faMesh/faMeshSubset/faMeshSubsetI.H
@@ -0,0 +1,107 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
+   \\    /   O peration     |
+    \\  /    A nd           | www.openfoam.com
+     \\/     M anipulation  |
+-------------------------------------------------------------------------------
+    Copyright (C) 2022 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::faMesh& Foam::faMeshSubset::baseMesh() const noexcept
+{
+    return baseMesh_;
+}
+
+
+inline const Foam::faMesh& Foam::faMeshSubset::mesh() const noexcept
+{
+    return subMeshPtr_ ? *subMeshPtr_ : baseMesh_;
+}
+
+
+inline bool Foam::faMeshSubset::hasSubMesh() const noexcept
+{
+    return bool(subMeshPtr_);
+}
+
+
+inline const Foam::faMesh& Foam::faMeshSubset::subMesh() const
+{
+    checkHasSubMesh();
+
+    return *subMeshPtr_;
+}
+
+
+inline Foam::faMesh& Foam::faMeshSubset::subMesh()
+{
+    checkHasSubMesh();
+
+    return *subMeshPtr_;
+}
+
+
+inline const Foam::labelList& Foam::faMeshSubset::pointMap() const
+{
+    checkHasSubMesh();
+
+    return pointMap_;
+}
+
+
+inline const Foam::labelList& Foam::faMeshSubset::faceMap() const
+{
+    checkHasSubMesh();
+
+    return faceMap_;
+}
+
+
+inline const Foam::labelList& Foam::faMeshSubset::edgeFlipMap() const
+{
+    if (!edgeFlipMapPtr_)
+    {
+        // calcEdgeFlipMap();
+    }
+
+    return *edgeFlipMapPtr_;
+}
+
+
+inline const Foam::labelList& Foam::faMeshSubset::cellMap() const
+{
+    checkHasSubMesh();
+
+    return cellMap_;
+}
+
+
+inline const Foam::labelList& Foam::faMeshSubset::patchMap() const
+{
+    checkHasSubMesh();
+
+    return patchMap_;
+}
+
+
+// ************************************************************************* //
diff --git a/src/finiteArea/faMesh/faMeshSubset/faMeshSubsetTemplates.C b/src/finiteArea/faMesh/faMeshSubset/faMeshSubsetTemplates.C
new file mode 100644
index 0000000000000000000000000000000000000000..c245505ca7d4dd7e18ae2c339a954b28e572271c
--- /dev/null
+++ b/src/finiteArea/faMesh/faMeshSubset/faMeshSubsetTemplates.C
@@ -0,0 +1,169 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
+   \\    /   O peration     |
+    \\  /    A nd           | www.openfoam.com
+     \\/     M anipulation  |
+-------------------------------------------------------------------------------
+    Copyright (C) 2022 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 "faMeshSubset.H"
+#include "areaFaMesh.H"
+#include "edgeFaMesh.H"
+#include "areaFields.H"
+#include "edgeFields.H"
+#include "emptyFaPatchFields.H"
+#include "directFaPatchFieldMapper.H"
+#include "flipOp.H"
+
+// * * * * * * * * * * * * * Static Member Functions * * * * * * * * * * * * //
+
+template<class Type>
+Foam::tmp
+<
+    Foam::GeometricField<Type, Foam::faPatchField, Foam::areaMesh>
+>
+Foam::faMeshSubset::interpolate
+(
+    const GeometricField<Type, faPatchField, areaMesh>& vf,
+    const faMesh& sMesh,
+    const bool allowUnmapped
+)
+{
+    // 1. Create the complete field with dummy patch fields
+    PtrList<faPatchField<Type>> patchFields(sMesh.boundary().size());
+
+    forAll(patchFields, patchi)
+    {
+        patchFields.set
+        (
+            patchi,
+            faPatchField<Type>::New
+            (
+                calculatedFaPatchField<Type>::typeName,
+                sMesh.boundary()[patchi],
+                DimensionedField<Type, areaMesh>::null()
+            )
+        );
+    }
+
+    auto tresult = tmp<GeometricField<Type, faPatchField, areaMesh>>::New
+    (
+        IOobject
+        (
+            "subset"+vf.name(),
+            sMesh.time().timeName(),
+            sMesh.thisDb(),
+            IOobject::NO_READ,
+            IOobject::NO_WRITE
+        ),
+        sMesh,
+        vf.dimensions(),
+        Field<Type>(),
+        // Field<Type>(vf.primitiveField(), cellMap),
+        patchFields
+    );
+    auto& result = tresult.ref();
+    result.oriented() = vf.oriented();
+
+
+    // 2. Change the faPatchFields to the correct type using a mapper
+    //  constructor (with reference to the now correct internal field)
+
+    auto& bf = result.boundaryFieldRef();
+
+    forAll(bf, patchi)
+    {
+        // Construct addressing
+        const faPatch& subPatch = sMesh.boundary()[patchi];
+
+        labelList directAddressing;
+        directFaPatchFieldMapper mapper(directAddressing);
+
+        // allowUnmapped : special mode for if we do not want to be
+        // warned for unmapped faces (e.g. from faMeshDistribute).
+        const bool hasUnmapped = mapper.hasUnmapped();
+        if (allowUnmapped)
+        {
+            mapper.hasUnmapped() = false;
+        }
+
+        bf.set
+        (
+            patchi,
+            faPatchField<Type>::New
+            (
+                vf.boundaryField()[patchi],
+                subPatch,
+                result(),
+                mapper
+            )
+        );
+
+        if (allowUnmapped && hasUnmapped)
+        {
+            // Set unmapped values to zeroGradient. This is the default
+            // action for unmapped faPatchFields. Note that this bypasses
+            // any special logic for handling unmapped faPatchFields but
+            // since this is only used inside faMeshDistribute ...
+
+            tmp<Field<Type>> tfld(bf[patchi].patchInternalField());
+            const Field<Type>& fld = tfld();
+
+            Field<Type> value(bf[patchi]);
+            forAll(directAddressing, i)
+            {
+                if (directAddressing[i] == -1)
+                {
+                    value[i] = fld[i];
+                }
+            }
+            bf[patchi].faPatchField<Type>::operator=(value);
+        }
+    }
+
+    return tresult;
+}
+
+
+// * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //
+
+template<class Type>
+Foam::tmp
+<
+    Foam::GeometricField<Type, Foam::faPatchField, Foam::areaMesh>
+>
+Foam::faMeshSubset::interpolate
+(
+    const GeometricField<Type, faPatchField, areaMesh>& vf,
+    const bool allowUnmapped
+) const
+{
+    if (subMeshPtr_)
+    {
+        return interpolate(vf, *subMeshPtr_);
+    }
+
+    return vf;
+}
+
+
+// ************************************************************************* //
diff --git a/src/finiteArea/faMesh/faMeshTools/faMeshTools.C b/src/finiteArea/faMesh/faMeshTools/faMeshTools.C
new file mode 100644
index 0000000000000000000000000000000000000000..24b46edf5e162eae5fd35d95bbb502cc2725fb6e
--- /dev/null
+++ b/src/finiteArea/faMesh/faMeshTools/faMeshTools.C
@@ -0,0 +1,470 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
+   \\    /   O peration     |
+    \\  /    A nd           | www.openfoam.com
+     \\/     M anipulation  |
+-------------------------------------------------------------------------------
+    Copyright (C) 2012-2016 OpenFOAM Foundation
+    Copyright (C) 2015-2022 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 "faMeshTools.H"
+#include "faBoundaryMeshEntries.H"
+#include "areaFields.H"
+#include "edgeFields.H"
+#include "polyMesh.H"
+#include "processorFaPatch.H"
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+void Foam::faMeshTools::unregisterMesh(const faMesh& mesh)
+{
+    auto& obr = const_cast<objectRegistry&>(mesh.thisDb());
+
+    // Checkout by name (casting ambiguity)
+    obr.checkOut(faMesh::typeName);
+    obr.checkOut("faBoundaryMesh");
+    obr.checkOut("faSchemes");
+    obr.checkOut("faSolution");
+}
+
+
+void Foam::faMeshTools::forceDemandDriven(faMesh& mesh)
+{
+    (void)mesh.globalData();
+
+    (void)mesh.Le();
+    (void)mesh.magLe();
+    (void)mesh.areaCentres();
+    (void)mesh.edgeCentres();
+
+    (void)mesh.faceAreaNormals();
+    (void)mesh.edgeAreaNormals();
+    (void)mesh.pointAreaNormals();
+    (void)mesh.faceCurvatures();
+    (void)mesh.edgeTransformTensors();
+}
+
+
+Foam::autoPtr<Foam::faMesh> Foam::faMeshTools::newMesh
+(
+    const IOobject& io,
+    const polyMesh& pMesh,
+    const bool masterOnlyReading,
+    const bool verbose
+)
+{
+    // Region name
+    // ~~~~~~~~~~~
+
+    const fileName meshSubDir
+    (
+        (pMesh.name() == polyMesh::defaultRegion ? word::null : pMesh.name())
+      / faMesh::meshSubDir
+    );
+
+
+    fileName facesInstance;
+
+    // Patch types
+    // ~~~~~~~~~~~
+    // Read and scatter master patches (without reading master mesh!)
+
+    PtrList<entry> patchEntries;
+    if (Pstream::master())
+    {
+        const bool oldParRun = Pstream::parRun(false);
+
+        facesInstance = io.time().findInstance
+        (
+            meshSubDir,
+            "faceLabels",
+            IOobject::MUST_READ
+        );
+
+        patchEntries = faBoundaryMeshEntries
+        (
+            IOobject
+            (
+                "faBoundary",
+                facesInstance,
+                meshSubDir,
+                io.db(),
+                IOobject::MUST_READ,
+                IOobject::NO_WRITE,
+                false
+            )
+        );
+
+        Pstream::parRun(oldParRun);
+    }
+
+    // Broadcast information to all
+    Pstream::broadcasts
+    (
+        UPstream::worldComm,
+        patchEntries,
+        facesInstance
+    );
+
+
+    // Dummy meshes
+    // ~~~~~~~~~~~~
+
+    // Set up to read-if-present. Note: does not search for mesh so set
+    // instance explicitly
+
+    IOobject meshIO(io);
+    meshIO.instance() = facesInstance;
+    meshIO.readOpt(IOobject::READ_IF_PRESENT);
+
+    // For mesh components (faceLabels, ...)
+    IOobject cmptIO(meshIO, "faceLabels", meshSubDir);
+    cmptIO.readOpt(IOobject::MUST_READ);
+    cmptIO.writeOpt(IOobject::NO_WRITE);
+    cmptIO.registerObject(false);
+
+
+    // Check who has a mesh
+
+    const fileName meshDir = io.time().path()/facesInstance/meshSubDir;
+    bool haveMesh = isDir(meshDir);
+    if (masterOnlyReading && !Pstream::master())
+    {
+        haveMesh = false;
+        meshIO.readOpt(IOobject::NO_READ);
+    }
+
+    if (!haveMesh)
+    {
+        cmptIO.readOpt(IOobject::NO_READ);
+    }
+
+
+    // Read mesh
+    // ~~~~~~~~~
+    // Now all processors use supplied points,faces etc
+    // Note: solution, schemes are also using the supplied IOobject so
+    //       on slave will be NO_READ, on master READ_IF_PRESENT. This will
+    //       conflict with e.g. timeStampMaster reading so switch off.
+
+    const auto oldCheckType = IOobject::fileModificationChecking;
+    IOobject::fileModificationChecking = IOobject::timeStamp;
+
+
+    // faceLabels
+    cmptIO.rename("faceLabels");
+    labelIOList faceLabels(cmptIO);
+
+
+    auto meshPtr = autoPtr<faMesh>::New
+    (
+        pMesh,
+        std::move(faceLabels),
+        meshIO
+    );
+    auto& mesh = *meshPtr;
+
+    IOobject::fileModificationChecking = oldCheckType;
+
+
+    // Some processors without patches? - add patches
+
+    if (returnReduce(mesh.boundary().empty(), orOp<bool>()))
+    {
+        // Use patchEntries, which were read on master and broadcast
+
+        faPatchList patches(patchEntries.size());
+        label nPatches = 0;
+
+        const bool isEmptyMesh = (mesh.faceLabels().empty());
+
+        forAll(patchEntries, patchi)
+        {
+            const entry& e = patchEntries[patchi];
+            const word type(e.dict().get<word>("type"));
+            const word& name = e.keyword();
+
+            if
+            (
+                type == processorFaPatch::typeName
+            )
+            {
+                // Stop at the first processor patch.
+                // - logic will not work with inter-mixed proc-patches anyhow
+                break;
+            }
+            else
+            {
+                dictionary patchDict(e.dict());
+
+                if (isEmptyMesh)
+                {
+                    patchDict.set("edgeLabels", labelList());
+                }
+
+                patches.set
+                (
+                    patchi,
+                    faPatch::New
+                    (
+                        name,
+                        patchDict,
+                        nPatches++,
+                        mesh.boundary()
+                    )
+                );
+            }
+        }
+
+        patches.resize(nPatches);
+        mesh.addFaPatches(patches, false);  // No parallel comms
+    }
+
+    // Recreate basic geometry, globalMeshData etc.
+    mesh.init(false);
+    (void)mesh.globalData();
+
+    return meshPtr;
+}
+
+
+Foam::autoPtr<Foam::faMesh> Foam::faMeshTools::loadOrCreateMesh
+(
+    const IOobject& io,
+    const polyMesh& pMesh,
+    const bool decompose,
+    const bool verbose
+)
+{
+    // Region name
+    // ~~~~~~~~~~~
+
+    const fileName meshSubDir
+    (
+        (pMesh.name() == polyMesh::defaultRegion ? word::null : pMesh.name())
+      / faMesh::meshSubDir
+    );
+
+
+    // Patch types
+    // ~~~~~~~~~~~
+    // Read and scatter master patches (without reading master mesh!)
+
+    PtrList<entry> patchEntries;
+    if (Pstream::master())
+    {
+        const bool oldParRun = Pstream::parRun(false);
+
+        patchEntries = faBoundaryMeshEntries
+        (
+            IOobject
+            (
+                "faBoundary",
+                io.instance(),
+                meshSubDir,
+                io.db(),
+                IOobject::MUST_READ,
+                IOobject::NO_WRITE,
+                false
+            )
+        );
+
+        Pstream::parRun(oldParRun);
+    }
+
+    // Broadcast: send patches to all
+    Pstream::broadcast(patchEntries);  // == worldComm;
+
+    /// Info<< patchEntries << nl;
+
+    // Dummy meshes
+    // ~~~~~~~~~~~~
+
+    // Check who has or needs a mesh.
+    // For 'decompose', only need mesh on master.
+    // Otherwise check for presence of the "faceLabels" file
+
+    bool haveMesh =
+    (
+        decompose
+      ? Pstream::master()
+      : fileHandler().isFile
+        (
+            fileHandler().filePath
+            (
+                io.time().path()/io.instance()/meshSubDir/"faceLabels"
+            )
+        )
+    );
+
+
+    if (!haveMesh)
+    {
+        const bool oldParRun = Pstream::parRun(false);
+
+        // Create dummy mesh - on procs that don't already have a mesh
+        faMesh dummyMesh
+        (
+            pMesh,
+            labelList(),
+            IOobject(io, IOobject::NO_READ, IOobject::AUTO_WRITE)
+        );
+
+        // Add patches
+        faPatchList patches(patchEntries.size());
+        label nPatches = 0;
+
+        forAll(patchEntries, patchi)
+        {
+            const entry& e = patchEntries[patchi];
+            const word type(e.dict().get<word>("type"));
+            const word& name = e.keyword();
+
+            if
+            (
+                type == processorFaPatch::typeName
+            )
+            {
+                // Stop at the first processor patch.
+                // - logic will not work with inter-mixed proc-patches anyhow
+                break;
+            }
+            else
+            {
+                dictionary patchDict(e.dict());
+                patchDict.set("edgeLabels", labelList());
+
+                patches.set
+                (
+                    patchi,
+                    faPatch::New
+                    (
+                        name,
+                        patchDict,
+                        nPatches++,
+                        dummyMesh.boundary()
+                    )
+                );
+            }
+        }
+
+        patches.resize(nPatches);
+        dummyMesh.addFaPatches(patches, false);  // No parallel comms
+
+        // Bad hack, but the underlying polyMesh is NO_WRITE
+        // so it does not create the faMesh subDir for us...
+        Foam::mkDir(dummyMesh.boundary().path());
+
+        //Pout<< "Writing dummy mesh to " << dummyMesh.boundary().path()
+        //    << endl;
+        dummyMesh.write();
+
+        Pstream::parRun(oldParRun);  // Restore parallel state
+    }
+
+    // Read mesh
+    // ~~~~~~~~~
+    // Now all processors have a (possibly zero size) mesh so read in
+    // parallel
+
+    /// Pout<< "Reading area mesh from " << io.objectRelPath() << endl;
+    auto meshPtr = autoPtr<faMesh>::New(pMesh, false);
+    faMesh& mesh = *meshPtr;
+
+    // Sync patches
+    // ~~~~~~~~~~~~
+
+    if (!Pstream::master() && haveMesh)
+    {
+        // Check master names against mine
+
+        const faBoundaryMesh& patches = mesh.boundary();
+
+        forAll(patchEntries, patchi)
+        {
+            const entry& e = patchEntries[patchi];
+            const word type(e.dict().get<word>("type"));
+            const word& name = e.keyword();
+
+            if
+            (
+                type == processorFaPatch::typeName
+            )
+            {
+                break;
+            }
+
+            if (patchi >= patches.size())
+            {
+                FatalErrorInFunction
+                    << "Non-processor patches not synchronised."
+                    << endl
+                    << "Processor " << Pstream::myProcNo()
+                    << " has only " << patches.size()
+                    << " patches, master has "
+                    << patchi
+                    << exit(FatalError);
+            }
+
+            if
+            (
+                type != patches[patchi].type()
+             || name != patches[patchi].name()
+            )
+            {
+                FatalErrorInFunction
+                    << "Non-processor patches not synchronised."
+                    << endl
+                    << "Master patch " << patchi
+                    << " name:" << type
+                    << " type:" << type << endl
+                    << "Processor " << Pstream::myProcNo()
+                    << " patch " << patchi
+                    << " has name:" << patches[patchi].name()
+                    << " type:" << patches[patchi].type()
+                    << exit(FatalError);
+            }
+        }
+    }
+
+
+    // Recreate basic geometry, globalMeshData etc.
+    mesh.init(false);
+    (void)mesh.globalData();
+
+    /// #if 0
+    /// faMeshTools::forceDemandDriven(mesh);
+    /// faMeshTools::unregisterMesh(mesh);
+    /// #endif
+
+    // Do some checks.
+
+    // Check if the boundary definition is unique
+    // and processor patches are correct
+    mesh.boundary().checkDefinition(verbose);
+    mesh.boundary().checkParallelSync(verbose);
+
+    return meshPtr;
+}
+
+
+// ************************************************************************* //
diff --git a/src/finiteArea/faMesh/faMeshTools/faMeshTools.H b/src/finiteArea/faMesh/faMeshTools/faMeshTools.H
new file mode 100644
index 0000000000000000000000000000000000000000..82a6513a7822bc6685f617bd430e6d2c986237dc
--- /dev/null
+++ b/src/finiteArea/faMesh/faMeshTools/faMeshTools.H
@@ -0,0 +1,154 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
+   \\    /   O peration     |
+    \\  /    A nd           | www.openfoam.com
+     \\/     M anipulation  |
+-------------------------------------------------------------------------------
+    Copyright (C) 2022 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::faMeshTools
+
+Description
+    A collection of tools for operating on an faMesh.
+
+SourceFiles
+    faMeshTools.C
+    faMeshToolsProcAddr.C
+    faMeshToolsTemplates.C
+
+\*---------------------------------------------------------------------------*/
+
+#ifndef Foam_faMeshTools_H
+#define Foam_faMeshTools_H
+
+#include "faMesh.H"
+#include "areaFieldsFwd.H"
+#include "edgeFieldsFwd.H"
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+namespace Foam
+{
+
+// Forward Declarations
+class mapDistributePolyMesh;
+class writeHandler;
+class polyMesh;
+class IOobject;
+
+/*---------------------------------------------------------------------------*\
+                         Class faMeshTools Declaration
+\*---------------------------------------------------------------------------*/
+
+class faMeshTools
+{
+public:
+
+    //- Unregister the faMesh from its associated polyMesh
+    //- to prevent triggering on polyMesh changes etc.
+    static void unregisterMesh(const faMesh& mesh);
+
+    //- Force creation of everything that might vaguely be used by patches.
+    //  This is fairly horrible.
+    static void forceDemandDriven(faMesh& mesh);
+
+
+    //- Read mesh or create dummy mesh (0 faces, >0 patches).
+    //  Works in two modes according to masterOnlyReading:
+    //  true : create a dummy mesh for all procs
+    //  false: checks locally for mesh directories and only creates dummy mesh
+    //         if not present
+    static autoPtr<faMesh> newMesh
+    (
+        const IOobject& io,
+        const polyMesh& pMesh,
+        const bool masterOnlyReading,
+        const bool verbose = false
+    );
+
+    // Read mesh if available. Otherwise create empty mesh with same non-proc
+    // patches as proc0 mesh. Requires:
+    //  - all processors to have all patches (and in same order).
+    //  - io.instance() set to facesInstance
+    static autoPtr<faMesh> loadOrCreateMesh
+    (
+        const IOobject& io,
+        const polyMesh& pMesh,
+        const bool decompose,
+        const bool verbose = false
+    );
+
+
+    //- Read decompose/reconstruct addressing
+    static mapDistributePolyMesh readProcAddressing
+    (
+        const faMesh& mesh,
+        const autoPtr<faMesh>& baseMeshPtr
+    );
+
+    //- Write decompose/reconstruct addressing
+    //
+    //  \param mesh
+    //  \param faDistMap
+    //  \param decompose  running in decompose vs reconstruct mode
+    //  \param writeHandler file handler
+    //  \param procMesh   (optional) processor mesh in reconstruct mode
+    //
+    //  \note Since the faMesh holds a reference to a polyMesh,
+    //  in reconstruct mode it will refer to the base mesh, but
+    //  we need a means to proc addressing into the processor locations.
+    //  This is the purpose of the additional procMesh reference
+    static void writeProcAddressing
+    (
+        const faMesh& mesh,
+        const mapDistributePolyMesh& faDistMap,
+        const bool decompose,
+        autoPtr<fileOperation>&& writeHandler,
+        const faMesh* procMesh = nullptr
+    );
+
+
+    //- Flatten an edge field into linear addressing
+    //  Optionally use primitive patch edge ordering
+    template<class Type>
+    static tmp<Field<Type>> flattenEdgeField
+    (
+        const GeometricField<Type, faePatchField, edgeMesh>& fld,
+        const bool primitiveOrdering = false
+    );
+};
+
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+} // End namespace Foam
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+#ifdef NoRepository
+    #include "faMeshToolsTemplates.C"
+#endif
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+#endif
+
+// ************************************************************************* //
diff --git a/src/finiteArea/faMesh/faMeshTools/faMeshToolsProcAddr.C b/src/finiteArea/faMesh/faMeshTools/faMeshToolsProcAddr.C
new file mode 100644
index 0000000000000000000000000000000000000000..b8a42738d08ae141c21eb02661ca02889143a8a6
--- /dev/null
+++ b/src/finiteArea/faMesh/faMeshTools/faMeshToolsProcAddr.C
@@ -0,0 +1,468 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
+   \\    /   O peration     |
+    \\  /    A nd           | www.openfoam.com
+     \\/     M anipulation  |
+-------------------------------------------------------------------------------
+    Copyright (C) 2022 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 "faMeshTools.H"
+#include "BitOps.H"
+#include "fileOperation.H"
+#include "areaFields.H"
+#include "edgeFields.H"
+#include "IOmapDistributePolyMesh.H"
+
+// * * * * * * * * * * * * * * * Local Functions * * * * * * * * * * * * * * //
+
+namespace Foam
+{
+
+// Create a reconstruct map.
+
+static mapDistributePolyMesh createReconstructMap
+(
+    const faMesh& mesh,
+    const autoPtr<faMesh>& baseMeshPtr,
+    const labelUList& faceProcAddr,
+    const labelUList& edgeProcAddr,
+    const labelUList& pointProcAddr,
+    const labelUList& boundaryProcAddr
+)
+{
+    const label nOldPoints = mesh.nPoints();
+    const label nOldFaces = mesh.nFaces();
+    const label nOldEdges = mesh.nEdges();
+
+    ///Pout<< "old sizes"
+    ///    << " points:" << nOldPoints
+    ///    << " faces:" << nOldFaces
+    ///    << " edges:" << nOldEdges << nl;
+
+    const faBoundaryMesh& oldBndMesh = mesh.boundary();
+    labelList oldPatchStarts(oldBndMesh.patchStarts());
+
+    // Patches: purge -1 entries
+    labelList patchProcAddr
+    (
+        IndirectList<label>::subset_if
+        (
+            boundaryProcAddr,
+            labelRange::ge0()
+        )
+    );
+
+
+    labelListList faceSubMap(Pstream::nProcs());
+    faceSubMap[Pstream::masterNo()] = identity(nOldFaces);
+
+    labelListList edgeSubMap(Pstream::nProcs());
+    edgeSubMap[Pstream::masterNo()] = identity(nOldEdges);
+
+    labelListList pointSubMap(Pstream::nProcs());
+    pointSubMap[Pstream::masterNo()] = identity(nOldPoints);
+
+    labelListList patchSubMap(Pstream::nProcs());
+    patchSubMap[Pstream::masterNo()] = patchProcAddr;
+
+
+    // Gather addressing on the master
+    labelListList faceAddressing(Pstream::nProcs());
+    faceAddressing[Pstream::myProcNo()] = faceProcAddr;
+    Pstream::gatherList(faceAddressing);
+
+    labelListList edgeAddressing(Pstream::nProcs());
+    edgeAddressing[Pstream::myProcNo()] = edgeProcAddr;
+    Pstream::gatherList(edgeAddressing);
+
+    labelListList pointAddressing(Pstream::nProcs());
+    pointAddressing[Pstream::myProcNo()] = pointProcAddr;
+    Pstream::gatherList(pointAddressing);
+
+    labelListList patchAddressing(Pstream::nProcs());
+    patchAddressing[Pstream::myProcNo()] = patchProcAddr;
+    Pstream::gatherList(patchAddressing);
+
+
+    // NB: can only have a reconstruct on master!
+    if (Pstream::master() && baseMeshPtr && baseMeshPtr->nFaces())
+    {
+        const faMesh& baseMesh = *baseMeshPtr;
+
+        const label nNewPoints = baseMesh.nPoints();
+        const label nNewFaces = baseMesh.nFaces();
+        const label nNewEdges = baseMesh.nEdges();
+        const label nNewPatches = baseMesh.boundary().size();
+
+        /// Pout<< "new sizes"
+        ///     << " points:" << nNewPoints
+        ///     << " faces:" << nNewFaces
+        ///     << " edges:" << nNewEdges << nl;
+
+        mapDistribute faFaceMap
+        (
+            nNewFaces,
+            std::move(faceSubMap),
+            std::move(faceAddressing),
+            false,  // subHasFlip
+            false   // constructHasFlip
+        );
+
+        mapDistribute faEdgeMap
+        (
+            nNewEdges,
+            std::move(edgeSubMap),
+            std::move(edgeAddressing),
+            false,  // subHasFlip
+            false   // constructHasFlip
+        );
+
+        mapDistribute faPointMap
+        (
+            nNewPoints,
+            std::move(pointSubMap),
+            std::move(pointAddressing)
+        );
+
+        mapDistribute faPatchMap
+        (
+            nNewPatches,
+            std::move(patchSubMap),
+            std::move(patchAddressing)
+        );
+
+        return mapDistributePolyMesh
+        (
+            // Mesh before changes
+            nOldPoints,
+            nOldEdges,          // area: nOldEdges (volume: nOldFaces)
+            nOldFaces,          // area: nOldFaces (volume: nOldCells)
+
+            std::move(oldPatchStarts),
+            labelList(),        // oldPatchNMeshPoints [unused]
+
+            mapDistribute(std::move(faPointMap)),
+            mapDistribute(std::move(faEdgeMap)), // edgeMap (volume: faceMap)
+            mapDistribute(std::move(faFaceMap)), // faceMap (volume: cellMap)
+            mapDistribute(std::move(faPatchMap))
+        );
+    }
+    else
+    {
+        mapDistribute faFaceMap
+        (
+            0,  // nNewFaces
+            std::move(faceSubMap),
+            labelListList(Pstream::nProcs()),   // constructMap
+            false,  // subHasFlip
+            false   // constructHasFlip
+        );
+
+        mapDistribute faEdgeMap
+        (
+            0,  // nNewEdges
+            std::move(edgeSubMap),
+            labelListList(Pstream::nProcs()),   // constructMap
+            false,  // subHasFlip
+            false   // constructHasFlip
+        );
+
+        mapDistribute faPointMap
+        (
+            0,  // nNewPoints
+            std::move(pointSubMap),
+            labelListList(Pstream::nProcs())    // constructMap
+        );
+
+        mapDistribute faPatchMap
+        (
+            0,  // nNewPatches
+            std::move(patchSubMap),
+            labelListList(Pstream::nProcs())    // constructMap
+        );
+
+        return mapDistributePolyMesh
+        (
+            // Mesh before changes
+            nOldPoints,
+            nOldEdges,          // area: nOldEdges (volume: nOldFaces)
+            nOldFaces,          // area: nOldFaces (volume: nOldCells)
+
+            std::move(oldPatchStarts),
+            labelList(),        // oldPatchNMeshPoints [unused]
+
+            mapDistribute(std::move(faPointMap)),
+            mapDistribute(std::move(faEdgeMap)), // edgeMap (volume: faceMap)
+            mapDistribute(std::move(faFaceMap)), // faceMap (volume: cellMap)
+            mapDistribute(std::move(faPatchMap))
+        );
+    }
+}
+
+} // End namespace Foam
+
+
+// * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //
+
+Foam::mapDistributePolyMesh
+Foam::faMeshTools::readProcAddressing
+(
+    const faMesh& mesh,
+    const autoPtr<faMesh>& baseMeshPtr
+)
+{
+    IOobject ioAddr
+    (
+        "procAddressing",
+        mesh.facesInstance(),
+        faMesh::meshSubDir,
+        mesh.thisDb(),
+        IOobject::READ_IF_PRESENT,
+        IOobject::NO_WRITE,
+        false  // no register
+    );
+
+    //if (ioAddr.typeHeaderOk<labelIOList>(true))
+    //{
+    //    Pout<< "Reading addressing from " << io.name() << " at "
+    //        << mesh.facesInstance() << nl << endl;
+    //    distMap.reset(new IOmapDistributePolyMesh(io));
+    //}
+    //else
+
+    {
+        Info<< "Reading (face|edge|face|point|boundary)ProcAddressing from "
+            << mesh.facesInstance().c_str() << '/'
+            << faMesh::meshSubDir << nl << endl;
+
+        ioAddr.rename("faceProcAddressing");
+        labelIOList faceProcAddressing(ioAddr, Zero);
+
+        ioAddr.rename("edgeProcAddressing");
+        labelIOList edgeProcAddressing(ioAddr, Zero);
+
+        ioAddr.rename("pointProcAddressing");
+        labelIOList pointProcAddressing(ioAddr, Zero);
+
+        ioAddr.rename("boundaryProcAddressing");
+        labelIOList boundaryProcAddressing(ioAddr, Zero);
+
+        if
+        (
+            mesh.nFaces() != faceProcAddressing.size()
+         || mesh.nEdges() != edgeProcAddressing.size()
+         || mesh.nPoints() != pointProcAddressing.size()
+         || mesh.boundary().size() != boundaryProcAddressing.size()
+        )
+        {
+            FatalErrorInFunction
+                << "Read addressing inconsistent with mesh sizes" << nl
+                << "faces:" << mesh.nFaces()
+                << " addressing:" << faceProcAddressing.objectRelPath()
+                << " size:" << faceProcAddressing.size() << nl
+                << "edges:" << mesh.nEdges()
+                << " addressing:" << edgeProcAddressing.objectRelPath()
+                << " size:" << edgeProcAddressing.size() << nl
+                << "points:" << mesh.nPoints()
+                << " addressing:" << pointProcAddressing.objectRelPath()
+                << " size:" << pointProcAddressing.size()
+                << "patches:" << mesh.boundary().size()
+                << " addressing:" << boundaryProcAddressing.objectRelPath()
+                << " size:" << boundaryProcAddressing.size()
+                << exit(FatalError);
+        }
+
+        return createReconstructMap
+        (
+            mesh,
+            baseMeshPtr,
+            faceProcAddressing,
+            edgeProcAddressing,
+            pointProcAddressing,
+            boundaryProcAddressing
+        );
+    }
+}
+
+
+void Foam::faMeshTools::writeProcAddressing
+(
+    const faMesh& mesh,
+    const mapDistributePolyMesh& map,
+    const bool decompose,
+    autoPtr<fileOperation>&& writeHandler,
+    const faMesh* procMesh
+)
+{
+    Info<< "Writing ("
+        << (decompose ? "decompose" : "reconstruct")
+        << ") procAddressing files to "
+        << mesh.facesInstance().c_str() << '/'
+        << faMesh::meshSubDir << endl;
+
+    IOobject ioAddr
+    (
+        "procAddressing",
+        mesh.facesInstance(),
+        faMesh::meshSubDir,
+        (procMesh && !decompose ? procMesh->thisDb() : mesh.thisDb()),
+        IOobject::NO_READ,
+        IOobject::NO_WRITE,
+        false  // no register
+    );
+
+
+    // faceProcAddressing (faMesh)
+    ioAddr.rename("faceProcAddressing");
+    labelIOList faceMap(ioAddr, Zero);
+
+    // edgeProcAddressing (faMesh)
+    ioAddr.rename("edgeProcAddressing");
+    labelIOList edgeMap(ioAddr, Zero);
+
+    // pointProcAddressing (faMesh)
+    ioAddr.rename("pointProcAddressing");
+    labelIOList pointMap(ioAddr, Zero);
+
+    // boundaryProcAddressing (faMesh)
+    ioAddr.rename("boundaryProcAddressing");
+    labelIOList patchMap(ioAddr, Zero);
+
+    if (decompose)
+    {
+        // Decompose
+        // - forward map:  [undecomposed] -> [decomposed]
+
+        // area:faces (volume:cells)
+        faceMap = identity(map.nOldCells());
+        map.cellMap().distribute(faceMap);
+
+        // area:edges (volume:faces)
+        edgeMap = identity(map.nOldFaces());
+        map.faceMap().distribute(edgeMap);
+
+        pointMap = identity(map.nOldPoints());
+        map.distributePointData(pointMap);
+
+        patchMap = identity(map.patchMap().constructSize());
+        map.patchMap().mapDistributeBase::distribute
+        (
+            Pstream::commsTypes::nonBlocking,
+            label(-1),  // nullValue for new patches...
+            patchMap,
+            flipOp()    // negate op
+        );
+    }
+    else    // reconstruct
+    {
+        // Reconstruct
+        // - reverse map:  [undecomposed] <- [decomposed]
+
+        // area:faces (volume:cells)
+        faceMap = identity(mesh.nFaces());
+        map.cellMap().reverseDistribute(map.nOldCells(), faceMap);
+
+        // area:edges (volume:faces)
+        edgeMap = identity(mesh.patch().nEdges());
+        map.faceMap().reverseDistribute(map.nOldFaces(), edgeMap);
+
+        pointMap = identity(mesh.nPoints());
+        map.pointMap().reverseDistribute(map.nOldPoints(), pointMap);
+
+        patchMap = identity(mesh.boundary().size());
+        map.patchMap().mapDistributeBase::reverseDistribute
+        (
+            Pstream::commsTypes::nonBlocking,
+            map.oldPatchSizes().size(),
+            label(-1),  // nullValue for unmapped patches...
+            patchMap
+        );
+    }
+
+    autoPtr<fileOperation> defaultHandler;
+    if (writeHandler)
+    {
+        defaultHandler = fileHandler(std::move(writeHandler));
+    }
+
+
+    // If we want procAddressing, need to manually write it ourselves
+    // since it was not registered anywhere
+
+    IOmapDistributePolyMeshRef procAddrMap
+    (
+        IOobject
+        (
+            "procAddressing",
+            mesh.facesInstance(),
+            faMesh::meshSubDir,
+            mesh.thisDb(),
+            IOobject::NO_READ,
+            IOobject::NO_WRITE,
+            false  // no register
+        ),
+        map
+    );
+
+
+    if (decompose)
+    {
+        // Write into proc directories
+        procAddrMap.write();
+    }
+    else
+    {
+        // Reconstruct: "procAddressing" only meaningful for rank 0
+        // and written into base (serial) location (if at all).
+
+        if (Pstream::master())
+        {
+            const bool oldParRun = Pstream::parRun(false);
+            procAddrMap.write();
+            Pstream::parRun(oldParRun);
+        }
+    }
+
+
+    const bool faceOk = faceMap.write();
+    const bool edgeOk = edgeMap.write();
+    const bool pointOk = pointMap.write();
+    const bool patchOk = patchMap.write();
+
+    if (defaultHandler)
+    {
+        writeHandler = fileHandler(std::move(defaultHandler));
+    }
+
+    if (!edgeOk || !faceOk || !pointOk || !patchOk)
+    {
+        WarningInFunction
+            << "Failed to write some of "
+            << faceMap.objectRelPath() << ", "
+            << edgeMap.objectRelPath() << ", "
+            << pointMap.objectRelPath() << ", "
+            << patchMap.objectRelPath() << endl;
+    }
+}
+
+
+// ************************************************************************* //
diff --git a/src/finiteArea/faMesh/faMeshTools/faMeshToolsTemplates.C b/src/finiteArea/faMesh/faMeshTools/faMeshToolsTemplates.C
new file mode 100644
index 0000000000000000000000000000000000000000..74d482a1d9c5b315fad795efd32e479e26030a1e
--- /dev/null
+++ b/src/finiteArea/faMesh/faMeshTools/faMeshToolsTemplates.C
@@ -0,0 +1,80 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
+   \\    /   O peration     |
+    \\  /    A nd           | www.openfoam.com
+     \\/     M anipulation  |
+-------------------------------------------------------------------------------
+    Copyright (C) 2022 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 "edgeFields.H"
+
+// * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //
+
+template<class Type>
+Foam::tmp<Foam::Field<Type>> Foam::faMeshTools::flattenEdgeField
+(
+    const GeometricField<Type, faePatchField, edgeMesh>& fld,
+    const bool primitiveOrdering
+)
+{
+    const faMesh& mesh = fld.mesh();
+
+    auto tresult = tmp<Field<Type>>::New(mesh.nEdges(), Zero);
+    auto& result = tresult.ref();
+
+    // Internal field
+    result.slice(0, fld.size()) = fld;
+
+    if (primitiveOrdering)
+    {
+        // Boundary field in primitive patch order
+
+        forAll(fld.boundaryField(), patchi)
+        {
+            UIndirectList<Type>
+            (
+                result,
+                mesh.boundary()[patchi].edgeLabels()
+            ) = fld.boundaryField()[patchi];
+        }
+    }
+    else
+    {
+        // Boundary field in sub-list (slice) order
+
+        label start = fld.size();
+
+        forAll(fld.boundaryField(), patchi)
+        {
+            const label len = mesh.boundary()[patchi].size();
+
+            result.slice(start, len) = fld.boundaryField()[patchi];
+
+            start += len;
+        }
+    }
+
+    return tresult;
+}
+
+
+// ************************************************************************* //
diff --git a/src/finiteArea/faMesh/faMeshUpdate.C b/src/finiteArea/faMesh/faMeshUpdate.C
index 1e8593b261f0d9a09283321059a1db4b752d5e3e..b1136484bfb5afb8cf24ce508d14be002c17deba 100644
--- a/src/finiteArea/faMesh/faMeshUpdate.C
+++ b/src/finiteArea/faMesh/faMeshUpdate.C
@@ -37,8 +37,6 @@ License
 
 void Foam::faMesh::updateMesh(const mapPolyMesh& mpm)
 {
-    DebugInFunction << "Updating mesh" << endl;
-
     // if (!mpm.morphing())
     // {
     //     // No topo change
@@ -48,18 +46,14 @@ void Foam::faMesh::updateMesh(const mapPolyMesh& mpm)
     // Create fa mesh mapper, using the old mesh
     const faMeshMapper mapper(*this, mpm);
 
-
     // Rebuild mesh
-
-    // Cast away const for interface reasons.  HJ, 12/Aug/2011
-    faMesh& m = const_cast<faMesh&>(*this);
-
+    // ~~~~~~~~~~~~
 
     // Clear existing mesh data
     clearOut();
 
     // Set new labels
-    m.faceLabels_ = mapper.areaMap().newFaceLabels();
+    faceLabels_ = mapper.areaMap().newFaceLabels();
 
     const uindirectPrimitivePatch& bp = patch();
 
@@ -121,12 +115,12 @@ void Foam::faMesh::updateMesh(const mapPolyMesh& mpm)
     }
 
     // Set new edges for all patches
-    forAll(m.boundary_, patchI)
+    forAll(boundary_, patchI)
     {
-        m.boundary_[patchI].resetEdges(patchEdges[patchI]);
+        boundary_[patchI].resetEdges(patchEdges[patchI]);
     }
 
-    m.setPrimitiveMeshData();
+    setPrimitiveMeshData();
 
     // Create global mesh data
     if (Pstream::parRun())
@@ -135,10 +129,10 @@ void Foam::faMesh::updateMesh(const mapPolyMesh& mpm)
     }
 
     // Calculate topology for the patches (processor-processor comms etc.)
-    m.boundary_.updateMesh();
+    boundary_.updateMesh();
 
     // Calculate the geometry for the patches (transformation tensors etc.)
-    m.boundary_.calcGeometry();
+    boundary_.calcGeometry();
 
 
     // Map fields
@@ -156,7 +150,7 @@ void Foam::faMesh::updateMesh(const mapPolyMesh& mpm)
 
 void Foam::faMesh::mapFields(const faMeshMapper& mapper) const
 {
-    // Map all the areaFields in the objectRegistry
+    // Map areaFields in the objectRegistry
     MapGeometricFields<scalar, faPatchField, faMeshMapper, areaMesh>(mapper);
     MapGeometricFields<vector, faPatchField, faMeshMapper, areaMesh>(mapper);
     MapGeometricFields<sphericalTensor, faPatchField, faMeshMapper, areaMesh>
@@ -165,7 +159,7 @@ void Foam::faMesh::mapFields(const faMeshMapper& mapper) const
         (mapper);
     MapGeometricFields<tensor, faPatchField, faMeshMapper, areaMesh>(mapper);
 
-    // Map all the edgeFields in the objectRegistry
+    // Map edgeFields in the objectRegistry
     MapGeometricFields<scalar, faePatchField, faMeshMapper, edgeMesh>(mapper);
     MapGeometricFields<vector, faePatchField, faMeshMapper, edgeMesh>(mapper);
     MapGeometricFields<sphericalTensor, faePatchField, faMeshMapper, edgeMesh>
@@ -227,7 +221,6 @@ void Foam::faMesh::mapOldAreas(const faMeshMapper& mapper) const
             }
         }
     }
-
 }