diff --git a/applications/utilities/mesh/manipulation/stitchMesh/stitchMesh.C b/applications/utilities/mesh/manipulation/stitchMesh/stitchMesh.C
index 15dd2c30435eae153633c06de9a373c1fb7534b9..930163451e1c6184f66c595c92a145fd5964810a 100644
--- a/applications/utilities/mesh/manipulation/stitchMesh/stitchMesh.C
+++ b/applications/utilities/mesh/manipulation/stitchMesh/stitchMesh.C
@@ -622,7 +622,7 @@ int main(int argc, char *argv[])
         }
 
         // Execute all polyMeshModifiers
-        autoPtr<mapPolyMesh> morphMap = stitcher.changeMesh(true);
+        autoPtr<mapPolyMesh> morphMap = stitcher.changeMesh();
 
         mesh.movePoints(morphMap->preMotionPoints());
 
diff --git a/src/dynamicMesh/Make/files b/src/dynamicMesh/Make/files
index d87341be3e23180d22edb7b0099ca6721b3d8d45..ea83ff7cc4ad5fd2be8937fa9c949e49c37654b2 100644
--- a/src/dynamicMesh/Make/files
+++ b/src/dynamicMesh/Make/files
@@ -22,6 +22,7 @@ $(polyMeshModifier)/polyMeshModifier.C
 $(polyMeshModifier)/polyMeshModifierNew.C
 
 polyTopoChange/polyTopoChanger/polyTopoChanger.C
+polyTopoChange/polyTopoChanger/polyTopoChangerChangeMesh.C
 polyTopoChange/polyTopoChange/addPatchCellLayer.C
 polyTopoChange/polyTopoChange/pointEdgeCollapse/pointEdgeCollapse.C
 polyTopoChange/polyTopoChange/edgeCollapser.C
@@ -76,6 +77,10 @@ meshCut/refineCell/refineCell.C
 meshCut/wallLayerCells/wallLayerCells.C
 meshCut/wallLayerCells/wallNormalInfo/wallNormalInfo.C
 
+refinement/refinement/refinement.C
+refinement/polyhedralRefinement/polyhedralRefinement.C
+refinement/prismatic2DRefinement/prismatic2DRefinement.C
+
 polyTopoChange/attachPolyTopoChanger/attachPolyTopoChanger.C
 polyTopoChange/repatchPolyTopoChanger/repatchPolyTopoChanger.C
 
diff --git a/src/dynamicMesh/attachDetach/attachDetach.C b/src/dynamicMesh/attachDetach/attachDetach.C
index 51e9143730d19a5acad610e299c5b6d207794914..0a3c6d529f126da51d5feb99afb1451a4cdec051 100644
--- a/src/dynamicMesh/attachDetach/attachDetach.C
+++ b/src/dynamicMesh/attachDetach/attachDetach.C
@@ -31,7 +31,7 @@ License
 #include "polyMesh.H"
 #include "Time.H"
 #include "primitiveMesh.H"
-#include "polyTopoChange.H"
+#include "batchPolyTopoChange.H"
 #include "addToRunTimeSelectionTable.H"
 
 // * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * //
@@ -365,7 +365,7 @@ bool Foam::attachDetach::changeTopology() const
 }
 
 
-void Foam::attachDetach::setRefinement(polyTopoChange& ref) const
+void Foam::attachDetach::setRefinement(batchPolyTopoChange& ref) const
 {
     // Insert the attach/detach instructions into the topological change
 
diff --git a/src/dynamicMesh/attachDetach/attachDetach.H b/src/dynamicMesh/attachDetach/attachDetach.H
index eecc3f1ba07e4b340216869e34183af01f04c6e5..0d4c6465208921d3e110b9b1a7c5790f1d93fecc 100644
--- a/src/dynamicMesh/attachDetach/attachDetach.H
+++ b/src/dynamicMesh/attachDetach/attachDetach.H
@@ -122,10 +122,10 @@ class attachDetach
         // Topological changes
 
             //- Attach interface
-            void attachInterface(polyTopoChange&) const;
+            void attachInterface(batchPolyTopoChange&) const;
 
             //- Detach interface
-            void detachInterface(polyTopoChange&) const;
+            void detachInterface(batchPolyTopoChange&) const;
 
             //- Calculate point match addressing
             void calcPointMatchMap() const;
@@ -215,7 +215,7 @@ public:
 
         //- Insert the layer addition/removal instructions
         //  into the topological change
-        virtual void setRefinement(polyTopoChange&) const;
+        virtual void setRefinement(batchPolyTopoChange&) const;
 
         //- Modify motion points to comply with the topological change
         virtual void modifyMotionPoints(pointField& motionPoints) const;
diff --git a/src/dynamicMesh/attachDetach/attachInterface.C b/src/dynamicMesh/attachDetach/attachInterface.C
index 70e996d297e1f9ff4e7aa0a6959b5efc8a230381..1da8367b0d7c72becf4e1a2b77d38c65966dd236 100644
--- a/src/dynamicMesh/attachDetach/attachInterface.C
+++ b/src/dynamicMesh/attachDetach/attachInterface.C
@@ -29,7 +29,7 @@ License
 #include "attachDetach.H"
 #include "polyMesh.H"
 #include "primitiveMesh.H"
-#include "polyTopoChange.H"
+#include "batchPolyTopoChange.H"
 #include "polyTopoChanger.H"
 #include "polyRemovePoint.H"
 #include "polyRemoveFace.H"
@@ -43,7 +43,7 @@ const Foam::scalar Foam::attachDetach::positionDifference_ = 1e-8;
 
 void Foam::attachDetach::attachInterface
 (
-    polyTopoChange& ref
+    batchPolyTopoChange& ref
 ) const
 {
     // Algorithm:
@@ -62,7 +62,7 @@ void Foam::attachDetach::attachInterface
     if (debug)
     {
         Pout<< "void attachDetach::attachInterface("
-            << "polyTopoChange& ref) const "
+            << "batchPolyTopoChange& ref) const "
             << " for object " << name() << " : "
             << "Attaching interface" << endl;
     }
@@ -266,7 +266,7 @@ void Foam::attachDetach::attachInterface
     if (debug)
     {
         Pout<< "void attachDetach::attachInterface("
-            << "polyTopoChange& ref) const "
+            << "batchPolyTopoChange& ref) const "
             << " for object " << name() << " : "
             << "Finished attaching interface" << endl;
     }
diff --git a/src/dynamicMesh/attachDetach/detachInterface.C b/src/dynamicMesh/attachDetach/detachInterface.C
index 34f94d4cad4bc5f08ea8b6cff8260aa3e45c7127..35d06ec76124474396b77dddbc1e9cd3084fa822 100644
--- a/src/dynamicMesh/attachDetach/detachInterface.C
+++ b/src/dynamicMesh/attachDetach/detachInterface.C
@@ -28,7 +28,7 @@ License
 #include "attachDetach.H"
 #include "polyMesh.H"
 #include "primitiveMesh.H"
-#include "polyTopoChange.H"
+#include "batchPolyTopoChange.H"
 #include "polyTopoChanger.H"
 #include "polyAddPoint.H"
 #include "polyModifyFace.H"
@@ -38,7 +38,7 @@ License
 
 void Foam::attachDetach::detachInterface
 (
-    polyTopoChange& ref
+    batchPolyTopoChange& ref
 ) const
 {
     // Algorithm:
@@ -66,7 +66,7 @@ void Foam::attachDetach::detachInterface
     if (debug)
     {
         Pout<< "void attachDetach::detachInterface("
-            << "polyTopoChange& ref) const "
+            << "batchPolyTopoChange& ref) const "
             << " for object " << name() << " : "
             << "Detaching interface" << endl;
     }
@@ -75,7 +75,7 @@ void Foam::attachDetach::detachInterface
     const faceZoneMesh& zoneMesh = mesh.faceZones();
 
     // Check that zone is in increasing order (needed since adding faces
-    // in same order - otherwise polyTopoChange face ordering will mess up
+    // in same order - otherwise batchPolyTopoChange face ordering will mess up
     // correspondence)
     if (debug)
     {
@@ -467,7 +467,7 @@ void Foam::attachDetach::detachInterface
     if (debug)
     {
         Pout<< "void attachDetach::detachInterface("
-            << "polyTopoChange& ref) const "
+            << "batchPolyTopoChange& ref) const "
             << " for object " << name() << " : "
             << "Finished detaching interface" << endl;
     }
diff --git a/src/dynamicMesh/layerAdditionRemoval/addCellLayer.C b/src/dynamicMesh/layerAdditionRemoval/addCellLayer.C
index 2c6eb27b362e40754ea701c63d82a6653fa5e597..569f3891865ac7ed5f98c34ad9b7cba7ff767089 100644
--- a/src/dynamicMesh/layerAdditionRemoval/addCellLayer.C
+++ b/src/dynamicMesh/layerAdditionRemoval/addCellLayer.C
@@ -29,7 +29,7 @@ License
 #include "layerAdditionRemoval.H"
 #include "polyMesh.H"
 #include "primitiveMesh.H"
-#include "polyTopoChange.H"
+#include "batchPolyTopoChange.H"
 #include "polyTopoChanger.H"
 #include "polyAddPoint.H"
 #include "polyAddCell.H"
@@ -88,7 +88,7 @@ Foam::tmp<Foam::vectorField> Foam::layerAdditionRemoval::extrusionDir() const
 
 void Foam::layerAdditionRemoval::addCellLayer
 (
-    polyTopoChange& ref
+    batchPolyTopoChange& ref
 ) const
 {
     // Insert the layer addition instructions into the topological change
@@ -108,7 +108,7 @@ void Foam::layerAdditionRemoval::addCellLayer
     if (debug)
     {
         Pout<< "void layerAdditionRemoval::addCellLayer("
-            << "polyTopoChange& ref) const for object " << name() << " : "
+            << "batchPolyTopoChange& ref) const for object " << name() << " : "
             << "Adding cell layer" << endl;
     }
 
@@ -682,7 +682,7 @@ void Foam::layerAdditionRemoval::addCellLayer
 
     if (debug)
     {
-        Pout<< "void layerAdditionRemoval::addCellLayer(polyTopoChange&) const "
+        Pout<< "void layerAdditionRemoval::addCellLayer(batchPolyTopoChange&) const "
             << " for object " << name() << ": "
             << "Finished adding cell layer" << endl;
     }
diff --git a/src/dynamicMesh/layerAdditionRemoval/layerAdditionRemoval.C b/src/dynamicMesh/layerAdditionRemoval/layerAdditionRemoval.C
index d7baa813f4c85d505a70dcc0031f24bb57c25719..507d559c9871eadb58f4ff7e0098b35f975a7694 100644
--- a/src/dynamicMesh/layerAdditionRemoval/layerAdditionRemoval.C
+++ b/src/dynamicMesh/layerAdditionRemoval/layerAdditionRemoval.C
@@ -355,7 +355,7 @@ bool Foam::layerAdditionRemoval::changeTopology() const
 }
 
 
-void Foam::layerAdditionRemoval::setRefinement(polyTopoChange& ref) const
+void Foam::layerAdditionRemoval::setRefinement(batchPolyTopoChange& ref) const
 {
     // Insert the layer addition/removal instructions
     // into the topological change
@@ -367,7 +367,7 @@ void Foam::layerAdditionRemoval::setRefinement(polyTopoChange& ref) const
         // Clear addressing.  This also resets the addition/removal data
         if (debug)
         {
-            Pout<< "layerAdditionRemoval::setRefinement(polyTopoChange&) "
+            Pout<< "layerAdditionRemoval::setRefinement(batchPolyTopoChange&) "
                 << "for object " << name() << " : "
                 << "Clearing addressing after layer removal" << endl;
         }
@@ -383,7 +383,7 @@ void Foam::layerAdditionRemoval::setRefinement(polyTopoChange& ref) const
         // Clear addressing.  This also resets the addition/removal data
         if (debug)
         {
-            Pout<< "layerAdditionRemoval::setRefinement(polyTopoChange&) "
+            Pout<< "layerAdditionRemoval::setRefinement(batchPolyTopoChange&) "
                 << "for object " << name() << " : "
                 << "Clearing addressing after layer addition" << endl;
         }
diff --git a/src/dynamicMesh/layerAdditionRemoval/layerAdditionRemoval.H b/src/dynamicMesh/layerAdditionRemoval/layerAdditionRemoval.H
index 25e5e68327fc2e19adef268b049364b15ca204e2..45c983b5dc3a54f901d930807488fb229fe85b98 100644
--- a/src/dynamicMesh/layerAdditionRemoval/layerAdditionRemoval.H
+++ b/src/dynamicMesh/layerAdditionRemoval/layerAdditionRemoval.H
@@ -119,10 +119,10 @@ class layerAdditionRemoval
             tmp<vectorField> extrusionDir() const;
 
             //- Add a layer of cells
-            void addCellLayer(polyTopoChange&) const;
+            void addCellLayer(batchPolyTopoChange&) const;
 
             //- Remove a layer of cells
-            void removeCellLayer(polyTopoChange&) const;
+            void removeCellLayer(batchPolyTopoChange&) const;
 
             //- Clear addressing
             void clearAddressing() const;
@@ -179,7 +179,7 @@ public:
 
         //- Insert the layer addition/removal instructions
         //  into the topological change
-        virtual void setRefinement(polyTopoChange&) const;
+        virtual void setRefinement(batchPolyTopoChange&) const;
 
         //- Modify motion points to comply with the topological change
         virtual void modifyMotionPoints(pointField& motionPoints) const;
diff --git a/src/dynamicMesh/layerAdditionRemoval/removeCellLayer.C b/src/dynamicMesh/layerAdditionRemoval/removeCellLayer.C
index e04075d2e0c8def7d9369ae02fa149d62877dd6c..9dd6fa51cfef5716625ea9b23c6b1eadd43206b6 100644
--- a/src/dynamicMesh/layerAdditionRemoval/removeCellLayer.C
+++ b/src/dynamicMesh/layerAdditionRemoval/removeCellLayer.C
@@ -29,7 +29,7 @@ License
 #include "layerAdditionRemoval.H"
 #include "polyMesh.H"
 #include "primitiveMesh.H"
-#include "polyTopoChange.H"
+#include "batchPolyTopoChange.H"
 #include "oppositeFace.H"
 #include "polyTopoChanger.H"
 #include "polyRemoveCell.H"
@@ -83,7 +83,7 @@ bool Foam::layerAdditionRemoval::validCollapse() const
 
 void Foam::layerAdditionRemoval::removeCellLayer
 (
-    polyTopoChange& ref
+    batchPolyTopoChange& ref
 ) const
 {
     // Algorithm for layer removal.  Second phase: topological change
diff --git a/src/dynamicMesh/perfectInterface/perfectInterface.C b/src/dynamicMesh/perfectInterface/perfectInterface.C
index da1cc5130f2e6212dffc9e4f42e5e8af86cfbd57..b4c973f8e8fb7461e9f863197700811746514d6b 100644
--- a/src/dynamicMesh/perfectInterface/perfectInterface.C
+++ b/src/dynamicMesh/perfectInterface/perfectInterface.C
@@ -33,7 +33,7 @@ Description
 #include "perfectInterface.H"
 #include "polyTopoChanger.H"
 #include "polyMesh.H"
-#include "polyTopoChange.H"
+#include "batchPolyTopoChange.H"
 #include "addToRunTimeSelectionTable.H"
 #include "mapPolyMesh.H"
 #include "matchPoints.H"
@@ -154,7 +154,7 @@ void Foam::perfectInterface::setRefinement
 (
     const indirectPrimitivePatch& pp0,
     const indirectPrimitivePatch& pp1,
-    polyTopoChange& ref
+    batchPolyTopoChange& ref
 ) const
 {
     const polyMesh& mesh = topoChanger().mesh();
@@ -423,11 +423,11 @@ void Foam::perfectInterface::setRefinement
 }
 
 
-void Foam::perfectInterface::setRefinement(polyTopoChange& ref) const
+void Foam::perfectInterface::setRefinement(batchPolyTopoChange& ref) const
 {
     if (debug)
     {
-        Pout<< "bool perfectInterface::setRefinement(polyTopoChange&) const : "
+        Pout<< "bool perfectInterface::setRefinement(batchPolyTopoChange&) const : "
             << "for object " << name() << " : "
             << "masterPatchID_:" << masterPatchID_
             << " slavePatchID_:" << slavePatchID_
diff --git a/src/dynamicMesh/perfectInterface/perfectInterface.H b/src/dynamicMesh/perfectInterface/perfectInterface.H
index 20352c4bee1710333000cc79e3bc43cab557097b..f5895723dc8458c023f03390f0eec6890c261c19 100644
--- a/src/dynamicMesh/perfectInterface/perfectInterface.H
+++ b/src/dynamicMesh/perfectInterface/perfectInterface.H
@@ -129,7 +129,7 @@ public:
 
         //- Insert the layer addition/removal instructions
         //  into the topological change
-        virtual void setRefinement(polyTopoChange&) const;
+        virtual void setRefinement(batchPolyTopoChange&) const;
 
         //- Insert the layer addition/removal instructions
         //  into the topological change. Uses only mesh, not any of the
@@ -139,7 +139,7 @@ public:
         (
             const indirectPrimitivePatch& pp0,
             const indirectPrimitivePatch& pp1,
-            polyTopoChange&
+            batchPolyTopoChange&
         ) const;
 
         //- Modify motion points to comply with the topological change
diff --git a/src/dynamicMesh/polyTopoChange/attachPolyTopoChanger/attachPolyTopoChanger.C b/src/dynamicMesh/polyTopoChange/attachPolyTopoChanger/attachPolyTopoChanger.C
index a88b03e5d5a25d45e70a6b1c5d3019e0140e605e..a11a743f3220c527c5165d874d72c8bd35c8ac43 100644
--- a/src/dynamicMesh/polyTopoChange/attachPolyTopoChanger/attachPolyTopoChanger.C
+++ b/src/dynamicMesh/polyTopoChange/attachPolyTopoChanger/attachPolyTopoChanger.C
@@ -65,7 +65,10 @@ void Foam::attachPolyTopoChanger::attach(const bool removeEmptyPatches)
     const fileName oldInst = mesh_.facesInstance();
 
     // Execute all polyMeshModifiers
-    changeMesh(false);  // no inflation
+    // DISABLED - this functionality is lost
+    // Bad rewrite by Mattijs Janssens - completely misunderstood the interface
+    // Needs to use polyTopoChange, and not changer.
+    // changeMesh(false);  // no inflation
 
     const pointField p = mesh_.oldPoints();
 
diff --git a/src/dynamicMesh/polyTopoChange/polyMeshModifier/polyMeshModifier.H b/src/dynamicMesh/polyTopoChange/polyMeshModifier/polyMeshModifier.H
index 62bc93bcfc5fc8e4da9c717636a60a080938a6a4..48f9936795e8400402a1f5ef8cf2ce8d47eae48c 100644
--- a/src/dynamicMesh/polyTopoChange/polyMeshModifier/polyMeshModifier.H
+++ b/src/dynamicMesh/polyTopoChange/polyMeshModifier/polyMeshModifier.H
@@ -55,7 +55,7 @@ namespace Foam
 
 // Forward Declarations
 class polyTopoChanger;
-class polyTopoChange;
+class batchPolyTopoChange;
 class mapPolyMesh;
 class polyMeshModifier;
 
@@ -166,7 +166,7 @@ public:
         virtual bool changeTopology() const = 0;
 
         //- Insert the topological change instructions
-        virtual void setRefinement(polyTopoChange&) const = 0;
+        virtual void setRefinement(batchPolyTopoChange&) const = 0;
 
         //- Modify motion points to comply with the topological change
         virtual void modifyMotionPoints(pointField& motionPoints) const = 0;
diff --git a/src/dynamicMesh/polyTopoChange/polyTopoChange/removeFaces.C b/src/dynamicMesh/polyTopoChange/polyTopoChange/removeFaces.C
index 2c4cae838da812e90a188963908eadd266c7e42b..36f7ff456c58a33a6c8a99338dbc1f4ad25bd622 100644
--- a/src/dynamicMesh/polyTopoChange/polyTopoChange/removeFaces.C
+++ b/src/dynamicMesh/polyTopoChange/polyTopoChange/removeFaces.C
@@ -77,6 +77,62 @@ void Foam::removeFaces::changeCellRegion
 }
 
 
+Foam::label Foam::removeFaces::changeFaceRegion
+(
+    const labelList& cellRegion,
+    const boolList& removedFace,
+    const labelList& nFacesPerEdge,
+    const label faceI,
+    const label newRegion,
+
+    labelList& faceRegion
+) const
+{
+    // Count number of changed faces
+    label nChanged = 0;
+
+    if (faceRegion[faceI] == -1 && !removedFace[faceI])
+    {
+        faceRegion[faceI] = newRegion;
+
+        nChanged = 1;
+
+        // Get mesh data
+        const labelListList& meshFaceEdges = mesh_.faceEdges();
+        const labelListList& meshEdgeFaces = mesh_.edgeFaces();
+
+        // Step to neighbouring faces across edges that will get removed
+        const labelList& fEdges = meshFaceEdges[faceI];
+
+        forAll(fEdges, i)
+        {
+            const label& edgeI = fEdges[i];
+
+            if (nFacesPerEdge[edgeI] >= 0 && nFacesPerEdge[edgeI] <= 2)
+            {
+                const labelList& eFaces = meshEdgeFaces[edgeI];
+
+                forAll(eFaces, j)
+                {
+                    nChanged += changeFaceRegion
+                    (
+                        cellRegion,
+                        removedFace,
+                        nFacesPerEdge,
+                        eFaces[j],
+                        newRegion,
+
+                        faceRegion
+                    );
+                }
+            }
+        }
+    }
+
+    return nChanged;
+}
+
+
 // Changes region of connected set of faces. Returns number of changed faces.
 Foam::label Foam::removeFaces::changeFaceRegion
 (
@@ -231,187 +287,6 @@ void Foam::removeFaces::writeOBJ
 }
 
 
-// Inserts commands to merge faceLabels into one face.
-void Foam::removeFaces::mergeFaces
-(
-    const labelList& cellRegion,
-    const labelList& cellRegionMaster,
-    const labelHashSet& pointsToRemove,
-    const labelList& faceLabels,
-    polyTopoChange& meshMod
-) const
-{
-    // Construct addressing engine from faceLabels (in order of faceLabels as
-    // well)
-    indirectPrimitivePatch fp
-    (
-        IndirectList<face>
-        (
-            mesh_.faces(),
-            faceLabels
-        ),
-        mesh_.points()
-    );
-
-    // Get outside vertices (in local vertex numbering)
-
-    if (fp.edgeLoops().size() != 1)
-    {
-        writeOBJ(fp, mesh_.time().path()/"facesToBeMerged.obj");
-        FatalErrorInFunction
-            << "Cannot merge faces " << faceLabels
-            << " into single face since outside vertices " << fp.edgeLoops()
-            << " do not form single loop but form " << fp.edgeLoops().size()
-            << " loops instead." << abort(FatalError);
-    }
-
-    const labelList& edgeLoop = fp.edgeLoops()[0];
-
-    // Get outside vertices in order of one of the faces in faceLabels.
-    // (this becomes the master face)
-    // Find the first face that uses edgeLoop[0] and edgeLoop[1] as consecutive
-    // vertices.
-
-    label masterIndex = -1;
-    bool reverseLoop = false;
-
-    const labelList& pFaces = fp.pointFaces()[edgeLoop[0]];
-
-    // Find face among pFaces which uses edgeLoop[1]
-    forAll(pFaces, i)
-    {
-        label facei = pFaces[i];
-
-        const face& f = fp.localFaces()[facei];
-
-        label index1 = f.find(edgeLoop[1]);
-
-        if (index1 != -1)
-        {
-            // Check whether consecutive to edgeLoop[0]
-            label index0 = f.find(edgeLoop[0]);
-
-            if (index0 != -1)
-            {
-                if (index1 == f.fcIndex(index0))
-                {
-                    masterIndex = facei;
-                    reverseLoop = false;
-                    break;
-                }
-                else if (index1 == f.rcIndex(index0))
-                {
-                    masterIndex = facei;
-                    reverseLoop = true;
-                    break;
-                }
-            }
-        }
-    }
-
-    if (masterIndex == -1)
-    {
-        writeOBJ(fp, mesh_.time().path()/"facesToBeMerged.obj");
-        FatalErrorInFunction
-            << "Problem" << abort(FatalError);
-    }
-
-
-    // Modify the master face.
-    // ~~~~~~~~~~~~~~~~~~~~~~~
-
-    // Modify first face.
-    label facei = faceLabels[masterIndex];
-
-    label own = mesh_.faceOwner()[facei];
-
-    if (cellRegion[own] != -1)
-    {
-        own = cellRegionMaster[cellRegion[own]];
-    }
-
-    label patchID, zoneID, zoneFlip;
-
-    getFaceInfo(facei, patchID, zoneID, zoneFlip);
-
-    label nei = -1;
-
-    if (mesh_.isInternalFace(facei))
-    {
-        nei = mesh_.faceNeighbour()[facei];
-
-        if (cellRegion[nei] != -1)
-        {
-            nei = cellRegionMaster[cellRegion[nei]];
-        }
-    }
-
-
-    DynamicList<label> faceVerts(edgeLoop.size());
-
-    forAll(edgeLoop, i)
-    {
-        label pointi = fp.meshPoints()[edgeLoop[i]];
-
-        if (pointsToRemove.found(pointi))
-        {
-            //Pout<< "**Removing point " << pointi << " from "
-            //    << edgeLoop << endl;
-        }
-        else
-        {
-            faceVerts.append(pointi);
-        }
-    }
-
-    face mergedFace;
-    mergedFace.transfer(faceVerts);
-
-    if (reverseLoop)
-    {
-        reverse(mergedFace);
-    }
-
-    //{
-    //    Pout<< "Modifying masterface " << facei
-    //        << " from faces:" << faceLabels
-    //        << " old verts:" << UIndirectList<face>(mesh_.faces(), faceLabels)
-    //        << " for new verts:"
-    //        << mergedFace
-    //        << " possibly new owner " << own
-    //        << " or new nei " << nei
-    //        << endl;
-    //}
-
-    modFace
-    (
-        mergedFace,         // modified face
-        facei,              // label of face being modified
-        own,                // owner
-        nei,                // neighbour
-        false,              // face flip
-        patchID,            // patch for face
-        false,              // remove from zone
-        zoneID,             // zone for face
-        zoneFlip,           // face flip in zone
-
-        meshMod
-    );
-
-
-    // Remove all but master face.
-    forAll(faceLabels, patchFacei)
-    {
-        if (patchFacei != masterIndex)
-        {
-            //Pout<< "Removing face " << faceLabels[patchFacei] << endl;
-
-            meshMod.setAction(polyRemoveFace(faceLabels[patchFacei], facei));
-        }
-    }
-}
-
-
 // Get patch, zone info for facei
 void Foam::removeFaces::getFaceInfo
 (
@@ -471,91 +346,6 @@ Foam::face Foam::removeFaces::filterFace
 }
 
 
-// Wrapper for meshMod.modifyFace. Reverses face if own>nei.
-void Foam::removeFaces::modFace
-(
-    const face& f,
-    const label masterFaceID,
-    const label own,
-    const label nei,
-    const bool flipFaceFlux,
-    const label newPatchID,
-    const bool removeFromZone,
-    const label zoneID,
-    const bool zoneFlip,
-
-    polyTopoChange& meshMod
-) const
-{
-    if ((nei == -1) || (own < nei))
-    {
-//        if (debug)
-//        {
-//            Pout<< "ModifyFace (unreversed) :"
-//                << "  facei:" << masterFaceID
-//                << "  f:" << f
-//                << "  own:" << own
-//                << "  nei:" << nei
-//                << "  flipFaceFlux:" << flipFaceFlux
-//                << "  newPatchID:" << newPatchID
-//                << "  removeFromZone:" << removeFromZone
-//                << "  zoneID:" << zoneID
-//                << "  zoneFlip:" << zoneFlip
-//                << endl;
-//        }
-
-        meshMod.setAction
-        (
-            polyModifyFace
-            (
-                f,              // modified face
-                masterFaceID,   // label of face being modified
-                own,            // owner
-                nei,            // neighbour
-                flipFaceFlux,   // face flip
-                newPatchID,     // patch for face
-                removeFromZone, // remove from zone
-                zoneID,         // zone for face
-                zoneFlip        // face flip in zone
-            )
-        );
-    }
-    else
-    {
-//        if (debug)
-//        {
-//            Pout<< "ModifyFace (!reversed) :"
-//                << "  facei:" << masterFaceID
-//                << "  f:" << f.reverseFace()
-//                << "  own:" << nei
-//                << "  nei:" << own
-//                << "  flipFaceFlux:" << flipFaceFlux
-//                << "  newPatchID:" << newPatchID
-//                << "  removeFromZone:" << removeFromZone
-//                << "  zoneID:" << zoneID
-//                << "  zoneFlip:" << zoneFlip
-//                << endl;
-//        }
-
-        meshMod.setAction
-        (
-            polyModifyFace
-            (
-                f.reverseFace(),// modified face
-                masterFaceID,   // label of face being modified
-                nei,            // owner
-                own,            // neighbour
-                flipFaceFlux,   // face flip
-                newPatchID,     // patch for face
-                removeFromZone, // remove from zone
-                zoneID,         // zone for face
-                zoneFlip        // face flip in zone
-            )
-        );
-    }
-}
-
-
 // * * * * * * * * * * * * * * * * Constructors  * * * * * * * * * * * * * * //
 
 // Construct from mesh
@@ -759,766 +549,4 @@ Foam::label Foam::removeFaces::compatibleRemoves
 }
 
 
-void Foam::removeFaces::setRefinement
-(
-    const labelList& faceLabels,
-    const labelList& cellRegion,
-    const labelList& cellRegionMaster,
-    polyTopoChange& meshMod
-) const
-{
-    if (debug)
-    {
-        faceSet facesToRemove(mesh_, "facesToRemove", faceLabels);
-        Pout<< "Writing faces to remove to faceSet " << facesToRemove.name()
-            << endl;
-        facesToRemove.write();
-    }
-
-    // Make map of all faces to be removed
-    boolList removedFace(mesh_.nFaces(), false);
-
-    forAll(faceLabels, i)
-    {
-        label facei = faceLabels[i];
-
-        if (!mesh_.isInternalFace(facei))
-        {
-            FatalErrorInFunction
-                << "Face to remove is not internal face:" << facei
-                << abort(FatalError);
-        }
-
-        removedFace[facei] = true;
-    }
-
-
-    // Edges to be removed
-    // ~~~~~~~~~~~~~~~~~~~
-
-
-    // Edges to remove
-    labelHashSet edgesToRemove(faceLabels.size());
-
-    // Per face the region it is in. -1 for removed faces, -2 for regions
-    // consisting of single face only.
-    labelList faceRegion(mesh_.nFaces(), -1);
-
-    // Number of connected face regions
-    label nRegions = 0;
-
-    // Storage for on-the-fly addressing
-    DynamicList<label> fe;
-    DynamicList<label> ef;
-
-
-    {
-        const polyBoundaryMesh& patches = mesh_.boundaryMesh();
-
-        // Usage of edges by non-removed faces.
-        // See below about initialization.
-        labelList nFacesPerEdge(mesh_.nEdges(), -1);
-
-        // Count usage of edges by non-removed faces.
-        forAll(faceLabels, i)
-        {
-            label facei = faceLabels[i];
-
-            const labelList& fEdges = mesh_.faceEdges(facei, fe);
-
-            forAll(fEdges, i)
-            {
-                label edgeI = fEdges[i];
-
-                if (nFacesPerEdge[edgeI] == -1)
-                {
-                    nFacesPerEdge[edgeI] = mesh_.edgeFaces(edgeI, ef).size()-1;
-                }
-                else
-                {
-                    nFacesPerEdge[edgeI]--;
-                }
-            }
-        }
-
-        // Count usage for edges not on faces-to-be-removed.
-        // Note that this only needs to be done for possibly coupled edges
-        // so we could choose to loop only over boundary faces and use faceEdges
-        // of those.
-
-        forAll(mesh_.edges(), edgeI)
-        {
-            if (nFacesPerEdge[edgeI] == -1)
-            {
-                // Edge not yet handled in loop above so is not used by any
-                // face to be removed.
-
-                const labelList& eFaces = mesh_.edgeFaces(edgeI, ef);
-
-                if (eFaces.size() > 2)
-                {
-                    nFacesPerEdge[edgeI] = eFaces.size();
-                }
-                else if (eFaces.size() == 2)
-                {
-                    // nFacesPerEdge already -1 so do nothing.
-                }
-                else
-                {
-                    const edge& e = mesh_.edges()[edgeI];
-
-                    FatalErrorInFunction
-                        << "Problem : edge has too few face neighbours:"
-                        << eFaces << endl
-                        << "edge:" << edgeI
-                        << " vertices:" << e
-                        << " coords:" << mesh_.points()[e[0]]
-                        << mesh_.points()[e[1]]
-                        << abort(FatalError);
-                }
-            }
-        }
-
-
-
-        if (debug)
-        {
-            OFstream str(mesh_.time().path()/"edgesWithTwoFaces.obj");
-            Pout<< "Dumping edgesWithTwoFaces to " << str.name() << endl;
-            label vertI = 0;
-
-            forAll(nFacesPerEdge, edgeI)
-            {
-                if (nFacesPerEdge[edgeI] == 2)
-                {
-                    // Edge will get removed.
-                    const edge& e = mesh_.edges()[edgeI];
-
-                    meshTools::writeOBJ(str, mesh_.points()[e[0]]);
-                    vertI++;
-                    meshTools::writeOBJ(str, mesh_.points()[e[1]]);
-                    vertI++;
-                    str<< "l " << vertI-1 << ' ' << vertI << nl;
-                }
-            }
-        }
-
-
-        // Now all unaffected edges will have labelMax, all affected edges the
-        // number of unremoved faces.
-
-        // Filter for edges inbetween two remaining boundary faces that
-        // make too big an angle.
-        forAll(nFacesPerEdge, edgeI)
-        {
-            if (nFacesPerEdge[edgeI] == 2)
-            {
-                // Get the two face labels
-                label f0 = -1;
-                label f1 = -1;
-
-                const labelList& eFaces = mesh_.edgeFaces(edgeI, ef);
-
-                forAll(eFaces, i)
-                {
-                    label facei = eFaces[i];
-
-                    if (!removedFace[facei])
-                    {
-                        if (f0 == -1)
-                        {
-                            f0 = facei;
-                        }
-                        else
-                        {
-                            f1 = facei;
-                            break;
-                        }
-                    }
-                }
-
-                if (!mesh_.isInternalFace(f0) && !mesh_.isInternalFace(f1))
-                {
-                    // Edge has two boundary faces remaining.
-                    // See if should be merged.
-
-                    label patch0 = patches.whichPatch(f0);
-                    label patch1 = patches.whichPatch(f1);
-
-                    if (patch0 != patch1)
-                    {
-                        // Different patches. Do not merge edge.
-                        WarningInFunction
-                            << "not merging faces " << f0 << " and "
-                            << f1 << " across patch boundary edge " << edgeI
-                            << endl;
-
-                        // Mark so it gets preserved
-                        nFacesPerEdge[edgeI] = 3;
-                    }
-                    else if (minCos_ < 1 && minCos_ > -1)
-                    {
-                        const polyPatch& pp0 = patches[patch0];
-                        const vectorField& n0 = pp0.faceNormals();
-
-                        if
-                        (
-                            mag
-                            (
-                                n0[f0 - pp0.start()]
-                              & n0[f1 - pp0.start()]
-                            )
-                            < minCos_
-                        )
-                        {
-                            WarningInFunction
-                                << "not merging faces " << f0 << " and "
-                                << f1 << " across edge " << edgeI
-                                << endl;
-
-                            // Angle between two remaining faces too large.
-                            // Mark so it gets preserved
-                            nFacesPerEdge[edgeI] = 3;
-                        }
-                    }
-                }
-                else if (mesh_.isInternalFace(f0) != mesh_.isInternalFace(f1))
-                {
-                    const edge& e = mesh_.edges()[edgeI];
-
-                    // Only found one boundary face. Problem.
-                    FatalErrorInFunction
-                        << "Problem : edge would have one boundary face"
-                        << " and one internal face using it." << endl
-                        << "Your remove pattern is probably incorrect." << endl
-                        << "edge:" << edgeI
-                        << " nFaces:" << nFacesPerEdge[edgeI]
-                        << " vertices:" << e
-                        << " coords:" << mesh_.points()[e[0]]
-                        << mesh_.points()[e[1]]
-                        << " face0:" << f0
-                        << " face1:" << f1
-                        << abort(FatalError);
-                }
-                else
-                {
-                    // Both kept faces are internal. Mark edge for preserving
-                    // if inbetween different cells. If inbetween same cell
-                    // pair we probably want to merge them to
-                    //  - avoid upper-triangular ordering problems
-                    //  - allow hex unrefinement (expects single face inbetween
-                    //    cells)
-
-                    const edge ownEdge
-                    (
-                        cellRegion[mesh_.faceOwner()[f0]],
-                        cellRegion[mesh_.faceNeighbour()[f0]]
-                    );
-                    const edge neiEdge
-                    (
-                        cellRegion[mesh_.faceOwner()[f1]],
-                        cellRegion[mesh_.faceNeighbour()[f1]]
-                    );
-
-                    if (ownEdge != neiEdge)
-                    {
-                        nFacesPerEdge[edgeI] = 3;
-                    }
-                }
-            }
-        }
-
-
-
-        // Check locally (before synchronizing) for strangeness
-        forAll(nFacesPerEdge, edgeI)
-        {
-            if (nFacesPerEdge[edgeI] == 1)
-            {
-                const edge& e = mesh_.edges()[edgeI];
-
-                FatalErrorInFunction
-                    << "Problem : edge would get 1 face using it only"
-                    << " edge:" << edgeI
-                    << " nFaces:" << nFacesPerEdge[edgeI]
-                    << " vertices:" << e
-                    << " coords:" << mesh_.points()[e[0]]
-                    << ' ' << mesh_.points()[e[1]]
-                    << abort(FatalError);
-            }
-            // Could check here for boundary edge with <=1 faces remaining.
-        }
-
-
-        // Synchronize edge usage. This is to make sure that both sides remove
-        // (or not remove) an edge on the boundary at the same time.
-        //
-        // Coupled edges (edge0, edge1 are opposite each other)
-        // a. edge not on face to be removed, edge has >= 3 faces
-        // b.  ,,                             edge has 2 faces
-        // c. edge has >= 3 remaining faces
-        // d. edge has 2 remaining faces (assume angle>minCos already handled)
-        //
-        // - a + a: do not remove edge
-        // - a + b: do not remove edge
-        // - a + c: do not remove edge
-        // - a + d: do not remove edge
-        //
-        // - b + b: do not remove edge
-        // - b + c: do not remove edge
-        // - b + d: remove edge
-        //
-        // - c + c: do not remove edge
-        // - c + d: do not remove edge
-        // - d + d: remove edge
-        //
-        //
-        // So code situation a. with >= 3
-        //                   b. with -1
-        //                   c. with >=3
-        //                   d. with 2
-        // then do max and check result.
-        //
-        // a+a : max(3,3) = 3. do not remove
-        // a+b : max(3,-1) = 3. do not remove
-        // a+c : max(3,3) = 3. do not remove
-        // a+d : max(3,2) = 3. do not remove
-        //
-        // b+b : max(-1,-1) = -1. do not remove
-        // b+c : max(-1,3) = 3. do not remove
-        // b+d : max(-1,2) = 2. remove
-        //
-        // c+c : max(3,3) = 3. do not remove
-        // c+d : max(3,2) = 3. do not remove
-        //
-        // d+d : max(2,2) = 2. remove
-
-        syncTools::syncEdgeList
-        (
-            mesh_,
-            nFacesPerEdge,
-            maxEqOp<label>(),
-            labelMin                // guaranteed to be overridden by maxEqOp
-        );
-
-        // Convert to labelHashSet
-        forAll(nFacesPerEdge, edgeI)
-        {
-            if (nFacesPerEdge[edgeI] == 0)
-            {
-                // 0: edge not used anymore.
-                edgesToRemove.insert(edgeI);
-            }
-            else if (nFacesPerEdge[edgeI] == 1)
-            {
-                // 1: illegal. Tested above.
-            }
-            else if (nFacesPerEdge[edgeI] == 2)
-            {
-                // 2: merge faces.
-                edgesToRemove.insert(edgeI);
-            }
-        }
-
-        if (debug)
-        {
-            OFstream str(mesh_.time().path()/"edgesToRemove.obj");
-            Pout<< "Dumping edgesToRemove to " << str.name() << endl;
-            label vertI = 0;
-
-            for (const label edgei : edgesToRemove)
-            {
-                // Edge will get removed.
-                const edge& e = mesh_.edges()[edgei];
-
-                meshTools::writeOBJ(str, mesh_.points()[e[0]]);
-                vertI++;
-                meshTools::writeOBJ(str, mesh_.points()[e[1]]);
-                vertI++;
-                str<< "l " << vertI-1 << ' ' << vertI << nl;
-            }
-        }
-
-
-        // Walk to fill faceRegion with faces that will be connected across
-        // edges that will be removed.
-
-        label startFacei = 0;
-
-        while (true)
-        {
-            // Find unset region.
-            for (; startFacei < mesh_.nFaces(); startFacei++)
-            {
-                if (faceRegion[startFacei] == -1 && !removedFace[startFacei])
-                {
-                    break;
-                }
-            }
-
-            if (startFacei == mesh_.nFaces())
-            {
-                break;
-            }
-
-            // Start walking face-edge-face, crossing edges that will get
-            // removed. Every thus connected region will get single region
-            // number.
-            label nRegion = changeFaceRegion
-            (
-                cellRegion,
-                removedFace,
-                nFacesPerEdge,
-                startFacei,
-                nRegions,
-                mesh_.faceEdges(startFacei, fe),
-                faceRegion
-            );
-
-            if (nRegion < 1)
-            {
-                FatalErrorInFunction << "Problem" << abort(FatalError);
-            }
-            else if (nRegion == 1)
-            {
-                // Reset face to be single region.
-                faceRegion[startFacei] = -2;
-            }
-            else
-            {
-                nRegions++;
-            }
-        }
-
-
-        // Check we're deciding the same on both sides. Since the regioning
-        // is done based on nFacesPerEdge (which is synced) this should
-        // indeed be the case.
-
-        labelList nbrFaceRegion(faceRegion);
-
-        syncTools::swapFaceList
-        (
-            mesh_,
-            nbrFaceRegion
-        );
-
-        labelList toNbrRegion(nRegions, -1);
-
-        for
-        (
-            label facei = mesh_.nInternalFaces();
-            facei < mesh_.nFaces();
-            facei++
-        )
-        {
-            // Get the neighbouring region.
-            label nbrRegion = nbrFaceRegion[facei];
-            label myRegion = faceRegion[facei];
-
-            if (myRegion <= -1 || nbrRegion <= -1)
-            {
-                if (nbrRegion != myRegion)
-                {
-                    FatalErrorInFunction
-                        << "Inconsistent face region across coupled patches."
-                        << endl
-                        << "This side has for facei:" << facei
-                        << " region:" << myRegion << endl
-                        << "The other side has region:" << nbrRegion
-                        << endl
-                        << "(region -1 means face is to be deleted)"
-                        << abort(FatalError);
-                }
-            }
-            else if (toNbrRegion[myRegion] == -1)
-            {
-                // First visit of region. Store correspondence.
-                toNbrRegion[myRegion] = nbrRegion;
-            }
-            else
-            {
-                // Second visit of this region.
-                if (toNbrRegion[myRegion] != nbrRegion)
-                {
-                    FatalErrorInFunction
-                        << "Inconsistent face region across coupled patches."
-                        << endl
-                        << "This side has for facei:" << facei
-                        << " region:" << myRegion
-                        << " with coupled neighbouring regions:"
-                        << toNbrRegion[myRegion] << " and "
-                        << nbrRegion
-                        << abort(FatalError);
-                }
-            }
-        }
-    }
-
-    //if (debug)
-    //{
-    //    labelListList regionToFaces(invertOneToMany(nRegions, faceRegion));
-    //
-    //    forAll(regionToFaces, regionI)
-    //    {
-    //        Pout<< "    " << regionI << " faces:" << regionToFaces[regionI]
-    //            << endl;
-    //    }
-    //}
-
-
-    // Points to be removed
-    // ~~~~~~~~~~~~~~~~~~~~
-
-    labelHashSet pointsToRemove(4*faceLabels.size());
-
-
-    // Per point count the number of unremoved edges. Store the ones that
-    // are only used by 2 unremoved edges.
-    {
-        // Usage of points by non-removed edges.
-        labelList nEdgesPerPoint(mesh_.nPoints());
-
-        const labelListList& pointEdges = mesh_.pointEdges();
-
-        forAll(pointEdges, pointi)
-        {
-            nEdgesPerPoint[pointi] = pointEdges[pointi].size();
-        }
-
-        for (const label edgei : edgesToRemove)
-        {
-            // Edge will get removed.
-            const edge& e = mesh_.edges()[edgei];
-
-            forAll(e, i)
-            {
-                nEdgesPerPoint[e[i]]--;
-            }
-        }
-
-        // Check locally (before synchronizing) for strangeness
-        forAll(nEdgesPerPoint, pointi)
-        {
-            if (nEdgesPerPoint[pointi] == 1)
-            {
-                FatalErrorInFunction
-                    << "Problem : point would get 1 edge using it only."
-                    << " pointi:" << pointi
-                    << " coord:" << mesh_.points()[pointi]
-                    << abort(FatalError);
-            }
-        }
-
-        // Synchronize point usage. This is to make sure that both sides remove
-        // (or not remove) a point on the boundary at the same time.
-        syncTools::syncPointList
-        (
-            mesh_,
-            nEdgesPerPoint,
-            maxEqOp<label>(),
-            labelMin
-        );
-
-        forAll(nEdgesPerPoint, pointi)
-        {
-            if (nEdgesPerPoint[pointi] == 0)
-            {
-                pointsToRemove.insert(pointi);
-            }
-            else if (nEdgesPerPoint[pointi] == 1)
-            {
-                // Already checked before
-            }
-            else if (nEdgesPerPoint[pointi] == 2)
-            {
-                // Remove point and merge edges.
-                pointsToRemove.insert(pointi);
-            }
-        }
-    }
-
-
-    if (debug)
-    {
-        OFstream str(mesh_.time().path()/"pointsToRemove.obj");
-        Pout<< "Dumping pointsToRemove to " << str.name() << endl;
-
-        for (const label pointi : pointsToRemove)
-        {
-            meshTools::writeOBJ(str, mesh_.points()[pointi]);
-        }
-    }
-
-
-    // All faces affected in any way
-    // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-    // Get all faces affected in any way by removal of points/edges/faces/cells
-    boolList affectedFace
-    (
-        getFacesAffected
-        (
-            cellRegion,
-            cellRegionMaster,
-            faceLabels,
-            edgesToRemove,
-            pointsToRemove
-        )
-    );
-
-    //
-    // Now we know
-    // - faceLabels         : faces to remove (sync since no boundary faces)
-    // - cellRegion/Master  : cells to remove (sync since cells)
-    // - pointsToRemove     : points to remove (sync)
-    // - faceRegion         : connected face region of faces to be merged (sync)
-    // - affectedFace       : faces with points removed and/or owner/neighbour
-    //                        changed (non sync)
-
-
-    // Start modifying mesh and keep track of faces changed.
-
-
-    // Do all removals
-    // ~~~~~~~~~~~~~~~
-
-    // Remove split faces.
-    forAll(faceLabels, labelI)
-    {
-        label facei = faceLabels[labelI];
-
-        // Remove face if not yet uptodate (which is never; but want to be
-        // consistent with rest of face removals/modifications)
-        if (affectedFace[facei])
-        {
-            affectedFace[facei] = false;
-
-            meshMod.setAction(polyRemoveFace(facei, -1));
-        }
-    }
-
-
-    // Remove points.
-    for (const label pointi : pointsToRemove)
-    {
-        meshMod.setAction(polyRemovePoint(pointi, -1));
-    }
-
-
-    // Remove cells.
-    forAll(cellRegion, celli)
-    {
-        label region = cellRegion[celli];
-
-        if (region != -1 && (celli != cellRegionMaster[region]))
-        {
-            meshMod.setAction(polyRemoveCell(celli, cellRegionMaster[region]));
-        }
-    }
-
-
-
-    // Merge faces across edges to be merged
-    // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-    // Invert faceRegion so we get region to faces.
-    {
-        labelListList regionToFaces(invertOneToMany(nRegions, faceRegion));
-
-        forAll(regionToFaces, regionI)
-        {
-            const labelList& rFaces = regionToFaces[regionI];
-
-            if (rFaces.size() <= 1)
-            {
-                FatalErrorInFunction
-                    << "Region:" << regionI
-                    << " contains only faces " << rFaces
-                    << abort(FatalError);
-            }
-
-            // rFaces[0] is master, rest gets removed.
-            mergeFaces
-            (
-                cellRegion,
-                cellRegionMaster,
-                pointsToRemove,
-                rFaces,
-                meshMod
-            );
-
-            forAll(rFaces, i)
-            {
-                affectedFace[rFaces[i]] = false;
-            }
-        }
-    }
-
-
-    // Remaining affected faces
-    // ~~~~~~~~~~~~~~~~~~~~~~~~
-
-    // Check if any remaining faces have not been updated for new slave/master
-    // or points removed.
-    forAll(affectedFace, facei)
-    {
-        if (affectedFace[facei])
-        {
-            affectedFace[facei] = false;
-
-            face f(filterFace(pointsToRemove, facei));
-
-            label own = mesh_.faceOwner()[facei];
-
-            if (cellRegion[own] != -1)
-            {
-                own = cellRegionMaster[cellRegion[own]];
-            }
-
-            label patchID, zoneID, zoneFlip;
-
-            getFaceInfo(facei, patchID, zoneID, zoneFlip);
-
-            label nei = -1;
-
-            if (mesh_.isInternalFace(facei))
-            {
-                nei = mesh_.faceNeighbour()[facei];
-
-                if (cellRegion[nei] != -1)
-                {
-                    nei = cellRegionMaster[cellRegion[nei]];
-                }
-            }
-
-//            if (debug)
-//            {
-//                Pout<< "Modifying " << facei
-//                    << " old verts:" << mesh_.faces()[facei]
-//                    << " for new verts:" << f
-//                    << " or for new owner " << own << " or for new nei "
-//                    << nei
-//                    << endl;
-//            }
-
-            modFace
-            (
-                f,                  // modified face
-                facei,              // label of face being modified
-                own,                // owner
-                nei,                // neighbour
-                false,              // face flip
-                patchID,            // patch for face
-                false,              // remove from zone
-                zoneID,             // zone for face
-                zoneFlip,           // face flip in zone
-
-                meshMod
-            );
-        }
-    }
-}
-
-
 // ************************************************************************* //
diff --git a/src/dynamicMesh/polyTopoChange/polyTopoChange/removeFaces.H b/src/dynamicMesh/polyTopoChange/polyTopoChange/removeFaces.H
index 20652eb4db8bff1a4c5891a2abbd05c8ed9ed650..4844fc77ece5626a71f6bbaa5fff2f88ce9b35c9 100644
--- a/src/dynamicMesh/polyTopoChange/polyTopoChange/removeFaces.H
+++ b/src/dynamicMesh/polyTopoChange/polyTopoChange/removeFaces.H
@@ -86,6 +86,19 @@ class removeFaces
             labelList& cellRegion
         ) const;
 
+        //- Changes region of connected set of faces. Returns number of changed
+        //  faces. For use with batch topo change
+        label changeFaceRegion
+        (
+            const labelList& cellRegion,
+            const boolList& removedFace,
+            const labelList& nFacesPerEdge,
+            const label faceI,
+            const label newRegion,
+
+            labelList& faceRegion
+        ) const;
+
         //- Changes region of connected set of faces
         label changeFaceRegion
         (
@@ -119,13 +132,15 @@ class removeFaces
             );
 
             //- Merge faceLabels into single face.
+            template<class TopoChangeEngine>
             void mergeFaces
             (
                 const labelList& cellRegion,
                 const labelList& cellRegionMaster,
                 const labelHashSet& pointsToRemove,
                 const labelList& faceLabels,
-                polyTopoChange& meshMod
+
+                TopoChangeEngine& meshMod
             ) const;
 
             //- Get patch, zone info for facei
@@ -141,6 +156,7 @@ class removeFaces
             face filterFace(const labelHashSet&, const label) const;
 
             //- Wrapper for meshMod.modifyFace. Reverses face if own>nei.
+            template<class TopoChangeEngine>
             void modFace
             (
                 const face& f,
@@ -153,11 +169,10 @@ class removeFaces
                 const label zoneID,
                 const bool zoneFlip,
 
-                polyTopoChange& meshMod
+                TopoChangeEngine& meshMod
             ) const;
 
 
-
         //- No copy construct
         removeFaces(const removeFaces&) = delete;
 
@@ -199,12 +214,15 @@ public:
 
 
         //- Play commands into polyTopoChange to remove faces.
+        template<class TopoChangeEngine>
         void setRefinement
         (
             const labelList& piercedFaces,
             const labelList& cellRegion,
-            const labelList& cellRegionMaster,
-            polyTopoChange&
+            // const labelList& pointRegionMaster,
+            labelList& cellRegionMaster,
+
+            TopoChangeEngine&
         ) const;
 
         //- Force recalculation of locally stored data on topological change
@@ -221,6 +239,10 @@ public:
 
 } // End namespace Foam
 
+#ifdef NoRepository
+#   include "removeFacesTemplates.C"
+#endif
+
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
 
 #endif
diff --git a/src/dynamicMesh/polyTopoChange/polyTopoChange/removeFacesTemplates.C b/src/dynamicMesh/polyTopoChange/polyTopoChange/removeFacesTemplates.C
new file mode 100644
index 0000000000000000000000000000000000000000..856ab2fb416826c4b8145c54fb08afeac20d6de1
--- /dev/null
+++ b/src/dynamicMesh/polyTopoChange/polyTopoChange/removeFacesTemplates.C
@@ -0,0 +1,1112 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
+   \\    /   O peration     |
+    \\  /    A nd           | www.openfoam.com
+     \\/     M anipulation  |
+-------------------------------------------------------------------------------
+    Copyright (C) 2008 Hrvoje Jasak
+-------------------------------------------------------------------------------
+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 "removeFaces.H"
+#include "polyMesh.H"
+#include "polyTopoChange.H"
+#include "meshTools.H"
+#include "polyModifyFace.H"
+#include "polyRemoveFace.H"
+#include "polyRemoveCell.H"
+#include "polyRemovePoint.H"
+#include "syncTools.H"
+#include "OFstream.H"
+#include "indirectPrimitivePatch.H"
+#include "Time.H"
+#include "faceSet.H"
+
+// * * * * * * * * * * * * * Private Member Functions  * * * * * * * * * * * //
+
+template<class TopoChangeEngine>
+void Foam::removeFaces::mergeFaces
+(
+    const labelList& cellRegion,
+    const labelList& cellRegionMaster,
+    const labelHashSet& pointsToRemove,
+    const labelList& faceLabels,
+
+    TopoChangeEngine& ref
+) const
+{
+    // Construct addressing engine from faceLabels (in order of faceLabels as
+    // well)
+    indirectPrimitivePatch fp
+    (
+        IndirectList<face>
+        (
+            mesh_.faces(),
+            faceLabels
+        ),
+        mesh_.points()
+    );
+
+    // Get outside vertices (in local vertex numbering)
+    if (fp.edgeLoops().size() != 1)
+    {
+        writeOBJ(fp, mesh_.time().path()/"facesToBeMerged.obj");
+
+        FatalErrorInFunction
+            << "Cannot merge faces " << faceLabels
+            << " into single face since outside vertices " << fp.edgeLoops()
+            << " do not form single loop but form " << fp.edgeLoops().size()
+            << " loops instead." << abort(FatalError);
+    }
+
+    const labelList& edgeLoop = fp.edgeLoops()[0];
+
+    // Get outside vertices in order of one of the faces in faceLabels.
+    // (this becomes the master face)
+    // Find the first face that uses edgeLoop[0] and edgeLoop[1] as consecutive
+    // vertices.
+
+    label masterIndex = -1;
+    bool reverseLoop = false;
+
+    const labelList& pFaces = fp.pointFaces()[edgeLoop[0]];
+
+    // Find face among pFaces which uses edgeLoop[1]
+    forAll(pFaces, i)
+    {
+        const label& faceI = pFaces[i];
+
+        const face& f = fp.localFaces()[faceI];
+
+        label index1 = f.find(edgeLoop[1]);
+
+        if (index1 != -1)
+        {
+            // Check whether consecutive to edgeLoop[0]
+            label index0 = f.find(edgeLoop[0]);
+
+            if (index0 != -1)
+            {
+                if (index1 == f.fcIndex(index0))
+                {
+                    masterIndex = faceI;
+                    reverseLoop = false;
+                    break;
+                }
+                else if (index1 == f.rcIndex(index0))
+                {
+                    masterIndex = faceI;
+                    reverseLoop = true;
+                    break;
+                }
+            }
+        }
+    }
+
+    if (masterIndex == -1)
+    {
+        writeOBJ(fp, mesh_.time().path()/"facesToBeMerged.obj");
+
+        FatalErrorInFunction
+            << "Problem." << abort(FatalError);
+    }
+
+
+    // Modify the master face
+
+    // Modify first face
+    label faceI = faceLabels[masterIndex];
+
+    label own = mesh_.faceOwner()[faceI];
+
+    if (cellRegion[own] != -1)
+    {
+        own = cellRegionMaster[cellRegion[own]];
+    }
+
+    // Set face information
+    label patchID, zoneID, zoneFlip;
+    meshTools::setFaceInfo(mesh_, faceI, patchID, zoneID, zoneFlip);
+
+    label nei = -1;
+
+    if (mesh_.isInternalFace(faceI))
+    {
+        nei = mesh_.faceNeighbour()[faceI];
+
+        if (cellRegion[nei] != -1)
+        {
+            nei = cellRegionMaster[cellRegion[nei]];
+        }
+    }
+
+    // Collect non-removed face vertices
+    DynamicList<label> faceVerts(edgeLoop.size());
+
+    // Get mesh points
+    const labelList& meshPoints = fp.meshPoints();
+
+    forAll(edgeLoop, i)
+    {
+        const label& pointI = meshPoints[edgeLoop[i]];
+
+        if (pointsToRemove.found(pointI))
+        {
+            // Point should be removed, nothing to do
+        }
+        else
+        {
+            faceVerts.append(pointI);
+        }
+    }
+
+    face mergedFace;
+    mergedFace.transfer(faceVerts);
+    faceVerts.clear();
+
+    if (reverseLoop)
+    {
+        // Reverse the merged face
+        reverse(mergedFace);
+    }
+
+    // Finally modify merged face
+    modFace
+    (
+        mergedFace,         // modified face
+        faceI,              // label of face being modified
+        own,                // owner
+        nei,                // neighbour
+        false,              // face flip
+        patchID,            // patch for face
+        false,              // remove from zone
+        zoneID,             // zone for face
+        zoneFlip,           // face flip in zone
+
+        ref                 // topo change engine
+    );
+
+
+    // Remove all but master face
+    forAll(faceLabels, patchFaceI)
+    {
+        if (patchFaceI != masterIndex)
+        {
+            ref.setAction(polyRemoveFace(faceLabels[patchFaceI], faceI));
+        }
+    }
+}
+
+
+template<class TopoChangeEngine>
+void Foam::removeFaces::modFace
+(
+    const face& f,
+    const label masterFaceID,
+    const label own,
+    const label nei,
+    const bool flipFaceFlux,
+    const label newPatchID,
+    const bool removeFromZone,
+    const label zoneID,
+    const bool zoneFlip,
+
+    TopoChangeEngine& ref
+) const
+{
+    // Print info with deep debug level only
+    if (debug > 1)
+    {
+        if (mesh_.isInternalFace(masterFaceID))
+        {
+            Pout<< "Modifying face " << masterFaceID
+                << ", old verts: " << mesh_.faces()[masterFaceID]
+                << " for new verts:" << f
+                << nl
+                << " or for new owner " << own
+                << " (old owner: " << mesh_.faceOwner()[masterFaceID] << ")"
+                << nl
+                << " or for new nei " << nei << " (old neighbour: "
+                << mesh_.faceNeighbour()[masterFaceID] << ")"
+                << endl;
+        }
+    }
+
+    if ((nei == -1) || (own < nei))
+    {
+        // No need to revert the face, use original owner/neighbour
+        ref.setAction
+        (
+            polyModifyFace
+            (
+                f,              // modified face
+                masterFaceID,   // label of face being modified
+                own,            // owner
+                nei,            // neighbour
+                flipFaceFlux,   // face flip
+                newPatchID,     // patch for face
+                removeFromZone, // remove from zone
+                zoneID,         // zone for face
+                zoneFlip        // face flip in zone
+            )
+        );
+    }
+    else
+    {
+        // Revert the face and flip owner/neighbour
+        ref.setAction
+        (
+            polyModifyFace
+            (
+                f.reverseFace(),// modified face
+                masterFaceID,   // label of face being modified
+                nei,            // owner
+                own,            // neighbour
+                flipFaceFlux,   // face flip
+                newPatchID,     // patch for face
+                removeFromZone, // remove from zone
+                zoneID,         // zone for face
+                zoneFlip        // face flip in zone
+            )
+        );
+    }
+}
+
+
+template<class TopoChangeEngine>
+void Foam::removeFaces::setRefinement
+(
+    const labelList& faceLabels,
+    const labelList& cellRegion,
+    // const labelList& pointRegionMaster,
+    labelList& cellRegionMaster,
+
+    TopoChangeEngine& ref
+) const
+{
+    if (debug)
+    {
+        const faceSet facesToRemove
+        (
+            mesh_,
+            "facesToRemove",
+            labelHashSet(faceLabels)
+        );
+
+        Pout<< "Writing faces to remove to faceSet " << facesToRemove.name()
+            << endl;
+
+        facesToRemove.write();
+    }
+
+    // Get number of mesh faces
+    const label nFaces = mesh_.nFaces();
+
+    // Mark-up field for all faces that need to be removed
+    boolList removedFace(nFaces, false);
+    forAll(faceLabels, i)
+    {
+        const label& faceI = faceLabels[i];
+
+        if (!mesh_.isInternalFace(faceI))
+        {
+            FatalErrorInFunction
+                << "Face " << faceI << " is not an internal faces, therefore"
+                << " it cannot be removed. Check faceLabels argument."
+                << abort(FatalError);
+        }
+
+        removedFace[faceI] = true;
+    }
+
+
+    // PART 1: Collect edges to be removed
+    labelHashSet edgesToRemove(faceLabels.size());
+
+    // Region for each face:
+    // -1 = removed faces
+    // -2 = regions consisting of single face only
+    labelList faceRegion(nFaces, -1);
+
+    // Number of connected face regions
+    label nRegions = 0;
+
+    {
+        // Number of edges per non-removed face. See below regarding
+        // initialization
+        labelList nFacesPerEdge(mesh_.nEdges(), -1);
+
+        // Get necessary mesh data
+        const labelListList& meshFaceEdges = mesh_.faceEdges();
+        const labelListList& meshEdgeFaces = mesh_.edgeFaces();
+        const polyBoundaryMesh& patches = mesh_.boundaryMesh();
+
+        // Count usage of edges by non-removed faces.
+        forAll(faceLabels, i)
+        {
+            // Get face Index and edges of this face
+            const label& faceI = faceLabels[i];
+            const labelList& fEdges = meshFaceEdges[faceI];
+
+            forAll(fEdges, j)
+            {
+                // Get edge index
+                const label& edgeI = fEdges[j];
+
+                if (nFacesPerEdge[edgeI] == -1)
+                {
+                    // Number of faces for this edge is not set, set to size - 1
+                    nFacesPerEdge[edgeI] = meshEdgeFaces[edgeI].size() - 1;
+                }
+                else
+                {
+                    // Decrement number of faces for this edge
+                    --nFacesPerEdge[edgeI];
+                }
+            }
+        }
+
+        // Count usage for edges not on faces-to-be-removed.
+        // Note that this only needs to be done for possibly coupled edges
+        // so we could choose to loop only over boundary faces and use faceEdges
+        // of those.
+        forAll(meshEdgeFaces, edgeI)
+        {
+            if (nFacesPerEdge[edgeI] == -1)
+            {
+                // Edge not yet handled in loop above so is not used by any
+                // face to be removed. Get edge faces
+                const labelList& eFaces = meshEdgeFaces[edgeI];
+
+                // Number of faces for this edge is greater than 2, set size
+                if (eFaces.size() > 2)
+                {
+                    nFacesPerEdge[edgeI] = eFaces.size();
+                }
+                else if (eFaces.size() == 2)
+                {
+                    // nFacesPerEdge already -1 so do nothing
+                }
+                else
+                {
+                    // Error: edge has less than two faces. Print additional
+                    // information and issue an error
+
+                    // Get edge and all its cells
+                    const edge& e = mesh_.edges()[edgeI];
+                    const labelListList& ec = mesh_.edgeCells();
+
+                    // Get point cells
+                    const labelListList& pc = mesh_.pointCells();
+
+                    // Write mesh before termination
+                    mesh_.write();
+
+                    // Write data for debugging
+                    FatalErrorInFunction
+                        << "Problem : edge has too few face neighbours:"
+                        << eFaces << endl
+                        << "edge:" << edgeI
+                        << " vertices:" << e
+                        << " coords:" << mesh_.points()[e[0]]
+                        << mesh_.points()[e[1]]
+                        << endl
+                        << "Edge cells: " << ec[edgeI] << nl
+                        << "First point cells: " << pc[e[0]] << nl
+                        << "Second point cells: " << pc[e[1]] << nl
+                        << abort(FatalError);
+                }
+            }
+        }
+
+
+        if (debug)
+        {
+            // Write edges with two faces
+            OFstream str(mesh_.time().path()/"edgesWithTwoFaces.obj");
+
+            Pout<< "Writing edgesWithTwoFaces to " << str.name() << endl;
+
+            // Vertex counter
+            label vertI = 0;
+
+            // Get mesh points and edges
+            const pointField& meshPoints = mesh_.points();
+            const edgeList& meshEdges = mesh_.edges();
+
+            forAll(nFacesPerEdge, edgeI)
+            {
+                if (nFacesPerEdge[edgeI] == 2)
+                {
+                    // Edge will get removed, write data
+                    const edge& e = meshEdges[edgeI];
+
+                    meshTools::writeOBJ(str, meshPoints[e[0]]);
+                    ++vertI;
+                    meshTools::writeOBJ(str, meshPoints[e[1]]);
+                    ++vertI;
+                    str<< "l " << vertI - 1 << ' ' << vertI << nl;
+                }
+            }
+        }
+
+
+        // At this point, all affected edges have the number of unremoved faces
+
+        // Filter for edges inbetween two remaining boundary faces that
+        // make too big an angle.
+        forAll(nFacesPerEdge, edgeI)
+        {
+            if (nFacesPerEdge[edgeI] == 2)
+            {
+                // See if these are two boundary faces
+                label f0 = -1;
+                label f1 = -1;
+
+                const labelList& eFaces = meshEdgeFaces[edgeI];
+
+                forAll(eFaces, i)
+                {
+                    const label& faceI = eFaces[i];
+
+                    if (!removedFace[faceI] && !mesh_.isInternalFace(faceI))
+                    {
+                        if (f0 == -1)
+                        {
+                            f0 = faceI;
+                        }
+                        else
+                        {
+                            f1 = faceI;
+                            break;
+                        }
+                    }
+                }
+
+                if (f0 != -1 && f1 != -1)
+                {
+                    // Edge has two boundary faces remaining, see if they should
+                    // be merged.
+                    const label patch0 = patches.whichPatch(f0);
+                    const label patch1 = patches.whichPatch(f1);
+
+                    if (patch0 != patch1)
+                    {
+                        // Different patches. Do not merge edge.
+                        WarningIn("removeFaces::setRefinement")
+                            << "Not merging faces " << f0 << " and "
+                            << f1 << " across patch boundary edge " << edgeI
+                            << " since they are not on the same patch."
+                            << endl;
+
+                        // Set number of faces to 3 to preserve the face
+                        nFacesPerEdge[edgeI] = 3;
+                    }
+                    else if (minCos_ < 1 && minCos_ > -1)
+                    {
+                        const polyPatch& pp0 = patches[patch0];
+                        const vectorField& n0 = pp0.faceNormals();
+
+                        if
+                        (
+                            mag
+                            (
+                                n0[f0 - pp0.start()]
+                              & n0[f1 - pp0.start()]
+                            )
+                            < minCos_
+                        )
+                        {
+                            WarningIn("removeFaces::setRefinement")
+                                << "Not merging faces " << f0 << " and "
+                                << f1 << " across edge " << edgeI
+                                << " since the angle between them is too large."
+                                << endl;
+
+                            // Set number of faces to 3 to preserve the face
+                            nFacesPerEdge[edgeI] = 3;
+                        }
+                    }
+                }
+                else if (f0 != -1 || f1 != -1)
+                {
+                    const edge& e = mesh_.edges()[edgeI];
+
+                    // Only found one boundary face. Problem
+                    FatalErrorInFunction
+                        << "Problem : edge would have one boundary face"
+                        << " and one internal face using it." << endl
+                        << "Your remove pattern is probably incorrect." << endl
+                        << "edge:" << edgeI
+                        << " nFaces:" << nFacesPerEdge[edgeI]
+                        << " vertices:" << e
+                        << " coords:" << mesh_.points()[e[0]]
+                        << mesh_.points()[e[1]]
+                        << " face0:" << f0
+                        << " face1:" << f1
+                        << abort(FatalError);
+                }
+            }
+        }
+
+
+        // Check locally (before synchronizing) for strangeness
+        forAll(nFacesPerEdge, edgeI)
+        {
+            if (nFacesPerEdge[edgeI] == 1)
+            {
+                const edge& e = mesh_.edges()[edgeI];
+
+                FatalErrorInFunction
+                    << "Problem : edge would get 1 face using it only"
+                    << " edge:" << edgeI
+                    << " nFaces:" << nFacesPerEdge[edgeI]
+                    << " vertices:" << e
+                    << " coords:" << mesh_.points()[e[0]]
+                    << ' ' << mesh_.points()[e[1]]
+                    << abort(FatalError);
+            }
+
+            // Could check here for boundary edge with <= 1 faces remaining
+        }
+
+
+        // Synchronize edge usage. This is to make sure that both sides remove
+        // (or not remove) an edge on the boundary at the same time.
+        //
+        // Coupled edges (edge0, edge1 are opposite each other)
+        // a. edge not on face to be removed, edge has >= 3 faces
+        // b.  ,,                             edge has 2 faces
+        // c. edge has >= 3 remaining faces
+        // d. edge has 2 remaining faces (assume angle>minCos already handled)
+        //
+        // - a + a: do not remove edge
+        // - a + b: do not remove edge
+        // - a + c: do not remove edge
+        // - a + d: do not remove edge
+        //
+        // - b + b: do not remove edge
+        // - b + c: do not remove edge
+        // - b + d: remove edge
+        //
+        // - c + c: do not remove edge
+        // - c + d: do not remove edge
+        // - d + d: remove edge
+        //
+        //
+        // So code situation a. with >= 3
+        //                   b. with -1
+        //                   c. with >=3
+        //                   d. with 2
+        // then do max and check result.
+        //
+        // a+a : max(3,3) = 3. do not remove
+        // a+b : max(3,-1) = 3. do not remove
+        // a+c : max(3,3) = 3. do not remove
+        // a+d : max(3,2) = 3. do not remove
+        //
+        // b+b : max(-1,-1) = -1. do not remove
+        // b+c : max(-1,3) = 3. do not remove
+        // b+d : max(-1,2) = 2. remove
+        //
+        // c+c : max(3,3) = 3. do not remove
+        // c+d : max(3,2) = 3. do not remove
+        //
+        // d+d : max(2,2) = 2. remove
+
+        syncTools::syncEdgeList
+        (
+            mesh_,
+            nFacesPerEdge,
+            maxEqOp<label>(),
+            labelMin                // guaranteed to be overridden by maxEqOp
+        );
+
+        // Convert to labelHashSet
+        forAll(nFacesPerEdge, edgeI)
+        {
+            if (nFacesPerEdge[edgeI] == 0)
+            {
+                // 0: edge not used anymore.
+                edgesToRemove.insert(edgeI);
+            }
+            else if (nFacesPerEdge[edgeI] == 1)
+            {
+                // 1: illegal. Tested above.
+            }
+            else if (nFacesPerEdge[edgeI] == 2)
+            {
+                // 2: merge faces.
+                edgesToRemove.insert(edgeI);
+            }
+        }
+
+        if (debug)
+        {
+            // Write edges to remove
+            OFstream str(mesh_.time().path()/"edgesToRemove.obj");
+
+            Pout<< "Writing edgesToRemove to " << str.name() << endl;
+
+            // Vertex counter
+            label vertI = 0;
+
+            // Get mesh points
+            const pointField& meshPoints = mesh_.points();
+
+            forAllConstIter(labelHashSet, edgesToRemove, iter)
+            {
+                // Edge will get removed.
+                const edge& e = mesh_.edges()[iter.key()];
+
+                meshTools::writeOBJ(str, meshPoints[e[0]]);
+                ++vertI;
+                meshTools::writeOBJ(str, meshPoints[e[1]]);
+                ++vertI;
+                str<< "l " << vertI - 1 << ' ' << vertI << nl;
+            }
+        }
+
+        // Walk to fill faceRegion with faces that will be connected across
+        // edges that will be removed
+
+        label startFaceI = 0;
+
+        while (true)
+        {
+            // Find unset region
+            for (; startFaceI < nFaces; ++startFaceI)
+            {
+                if (faceRegion[startFaceI] == -1 && !removedFace[startFaceI])
+                {
+                    break;
+                }
+            }
+
+            if (startFaceI == nFaces)
+            {
+                break;
+            }
+
+            // Start walking face-edge-face, crossing edges that will get
+            // removed. Every thus connected region will get single region
+            // number.
+            label nRegion = changeFaceRegion
+            (
+                cellRegion,
+                removedFace,
+                nFacesPerEdge,
+                startFaceI,
+                nRegions,
+
+                faceRegion
+            );
+
+            if (nRegion < 1)
+            {
+                FatalErrorInFunction
+                    << "Problem with region number." << abort(FatalError);
+            }
+            else if (nRegion == 1)
+            {
+                // Reset face to be single region
+                faceRegion[startFaceI] = -2;
+            }
+            else
+            {
+                ++nRegions;
+            }
+        }
+
+        // Check we're deciding the same on both sides. Since the regioning
+        // is done based on nFacesPerEdge (which is synced) this should
+        // indeed be the case.
+
+        // Create a copy of face region list and swap it
+        labelList nbrFaceRegion(faceRegion);
+        syncTools::swapFaceList
+        (
+            mesh_,
+            nbrFaceRegion
+        );
+
+        // Store data from neighbouring region
+        labelList toNbrRegion(nRegions, -1);
+
+        // Get number of internal faces
+        const label nInternalFaces = mesh_.nInternalFaces();
+
+        for
+        (
+            label faceI = nInternalFaces;
+            faceI < nFaces;
+            ++faceI
+        )
+        {
+            // Get the neighbouring region
+            const label& nbrRegion = nbrFaceRegion[faceI];
+            const label& myRegion = faceRegion[faceI];
+
+            if (myRegion <= -1 || nbrRegion <= -1)
+            {
+                if (nbrRegion != myRegion)
+                {
+                    FatalErrorInFunction
+                        << "Inconsistent face region across coupled patches."
+                        << endl
+                        << "This side has for faceI:" << faceI
+                        << " region:" << myRegion << endl
+                        << "The other side has region:" << nbrRegion
+                        << endl
+                        << "(region - 1 means face is to be deleted)"
+                        << abort(FatalError);
+                }
+            }
+            else if (toNbrRegion[myRegion] == -1)
+            {
+                // First visit of region. Store correspondence.
+                toNbrRegion[myRegion] = nbrRegion;
+            }
+            else
+            {
+                // Second visit of this region
+                if (toNbrRegion[myRegion] != nbrRegion)
+                {
+                    FatalErrorInFunction
+                        << "Inconsistent face region across coupled patches."
+                        << endl
+                        << "This side has for faceI:" << faceI
+                        << " region:" << myRegion
+                        << " with coupled neighbouring regions:"
+                        << toNbrRegion[myRegion] << " and "
+                        << nbrRegion
+                        << abort(FatalError);
+                }
+            }
+        }
+    } // End memory management
+
+
+    // PART 2: Collect points to remove
+
+    // Create a hash set of points to remove, assuming that each face has 4
+    // points to prevent excessive resizing
+    labelHashSet pointsToRemove(4*faceLabels.size());
+
+    // Memory management
+    {
+        // For each point, count the number of edges that will be kept
+        // (unremoved edges). Store the ones that are only used by exactly two
+        // unremoved edges.
+
+        // Get necessary mesh data
+        const label nPoints = mesh_.nPoints();
+        const labelListList& meshPointEdges = mesh_.pointEdges();
+        const edgeList& meshEdges = mesh_.edges();
+
+        // List containing number of nonremoved edges for each point
+        labelList nEdgesPerPoint(nPoints);
+
+        // Initialise to number of edges per point
+        forAll(meshPointEdges, pointI)
+        {
+            nEdgesPerPoint[pointI] = meshPointEdges[pointI].size();
+        }
+
+        // Loop through edges to remove
+        forAllConstIter(labelHashSet, edgesToRemove, iter)
+        {
+            // Get edge to be removed
+            const edge& e = meshEdges[iter.key()];
+
+            // Loop through both points of the edge and decrement the counter of
+            // unremoved edges per point
+            forAll(e, i)
+            {
+                --nEdgesPerPoint[e[i]];
+            }
+        }
+
+        // Check locally (before synchronizing) for strangeness
+        forAll(nEdgesPerPoint, pointI)
+        {
+            if (nEdgesPerPoint[pointI] == 1)
+            {
+                FatalErrorInFunction
+                    << "Problem : point would get 1 edge using it only."
+                    << " pointI:" << pointI
+                    << " coord:" << mesh_.points()[pointI]
+                    << abort(FatalError);
+            }
+        }
+
+        // Synchronize point usage. This is to make sure that both sides remove
+        // (or don't remove) a point at the boundary at the same time.
+        syncTools::syncPointList
+        (
+            mesh_,
+            nEdgesPerPoint,
+            maxEqOp<label>(),
+            labelMin
+        );
+
+        forAll(nEdgesPerPoint, pointI)
+        {
+            if (nEdgesPerPoint[pointI] == 0)
+            {
+                pointsToRemove.insert(pointI);
+            }
+            else if (nEdgesPerPoint[pointI] == 1)
+            {
+                // Already checked before
+            }
+            else if (nEdgesPerPoint[pointI] == 2)
+            {
+                // Remove point and merge edges
+                pointsToRemove.insert(pointI);
+            }
+        }
+    }
+
+
+    if (debug)
+    {
+        // Write points to remove
+        OFstream str(mesh_.time().path()/"pointsToRemove.obj");
+        Pout<< "Writing pointsToRemove to " << str.name() << endl;
+
+        // Get mesh points
+        const pointField& meshPoints = mesh_.points();
+
+        forAllConstIter(labelHashSet, pointsToRemove, iter)
+        {
+            meshTools::writeOBJ(str, meshPoints[iter.key()]);
+        }
+    }
+
+
+    // PART 3: Collect all faces affected in any way by removal of points,
+    // edges, faces and cells
+
+    // Note: return type of affectedFaces is Xfer<boolList>, so there is no
+    // unnecessary copying
+    boolList affectedFace
+    (
+        getFacesAffected
+        (
+            cellRegion,
+            cellRegionMaster,
+            faceLabels,
+            edgesToRemove,
+            pointsToRemove
+        )
+    );
+
+    // Now the data is complete:
+    // - faceLabels         : faces to remove (sync since no boundary faces)
+    // - cellRegion/Master  : cells to remove (sync since cells)
+    // - pointsToRemove     : points to remove (sync)
+    // - faceRegion         : connected face region of faces to be merged (sync)
+    // - affectedFace       : faces with points removed and/or owner/neighbour
+    //                        changed (non sync)
+    // We can start inserting mesh modifier instructions and keep track of
+    // changed faces.
+
+
+    // PART 4: Do all removals first
+
+    // Remove split faces
+    forAll(faceLabels, labelI)
+    {
+        // Get face index
+        const label& faceI = faceLabels[labelI];
+
+        // Remove face if not yet uptodate (which can't happen here; but want to
+        // be consistent with rest of face removals/modifications)
+        if (affectedFace[faceI])
+        {
+            // Mark face as unaffected
+            affectedFace[faceI] = false;
+
+            // Insert face removal instruction into topo change engine
+            ref.setAction(polyRemoveFace(faceI, -1));
+        }
+    }
+
+    // Remove points
+    forAllConstIter(labelHashSet, pointsToRemove, iter)
+    {
+        // Get point index
+        const label& pointI = iter.key();
+
+        // Inser point removal instruction into topo change engine
+        ref.setAction(polyRemovePoint(pointI, -1));
+    }
+
+/*
+    OpenFOAM does not support adequate mapping.
+    HJ, 20/Jul/2023
+    // Add master cells for correct mapping
+    forAll (cellRegionMaster, regionI)
+    {
+        // Note: it is legal to have cellRegionMaster = -1 if the region
+        // has been created and them marged into another region.
+        // Such masters will also have pointRegionMaster = -1 and should
+        // be ignored.  HJ, 6/Sep/2019
+
+        // Additionally protect for old directTopoChangers which do not
+        // identify points for mapping.  Non-existent pointRegionMaster
+        // is rejected
+        if (cellRegionMaster[regionI] > -1 && pointRegionMaster[regionI] > -1)
+        {
+            // Add master cell from master point for correct mapping
+            cellRegionMaster[regionI] =
+                ref.setAction
+                (
+                    polyAddCell
+                    (
+                        pointRegionMaster[regionI], // masterPointID
+                        -1,                         // masterEdgeID
+                        -1,                         // masterFaceID
+                        -1,                         // masterCellID
+                        mesh_.cellZones().whichZone(cellRegionMaster[regionI])
+                    )
+                );
+        }
+    }
+    */
+
+    // Remove cells
+    forAll(cellRegion, cellI)
+    {
+        label region = cellRegion[cellI];
+
+        // Old check is acceptable: for mapping from point, the cellRegionMaster
+        // has been replaced in polyAddPoint
+        // HJ, 6/Sep/2019
+        if (region != -1 && (cellI != cellRegionMaster[region]))
+        {
+            ref.setAction(polyRemoveCell(cellI, cellRegionMaster[region]));
+        }
+    }
+
+
+    // PART 5: Merge faces across edges to be merged
+
+    // Memory management
+    {
+        // Invert faceRegion so we get region to faces
+        labelListList regionToFaces(invertOneToMany(nRegions, faceRegion));
+
+        // Loop through regions
+        forAll(regionToFaces, regionI)
+        {
+            // Get region faces
+            const labelList& rFaces = regionToFaces[regionI];
+
+            if (rFaces.size() <= 1)
+            {
+                FatalErrorInFunction
+                    << "Region: " << regionI
+                    << " contains only these faces: " << rFaces
+                    << abort(FatalError);
+            }
+
+            // rFaces[0] is master, rest get removed
+            mergeFaces
+            (
+                cellRegion,
+                cellRegionMaster,
+                pointsToRemove,
+                rFaces,
+                ref
+            );
+
+            // Mark region faces as visited (non affected anymore)
+            forAll(rFaces, i)
+            {
+                affectedFace[rFaces[i]] = false;
+            }
+        }
+    }
+
+
+    // PART 6: Remaining affected faces
+
+    // Get necessary mesh data
+    const labelList& owner = mesh_.faceOwner();
+    const labelList& neighbour = mesh_.faceNeighbour();
+
+
+    // Check any remaining faces that have not been updated for either new
+    // owner/neighbour or points removed
+    forAll(affectedFace, faceI)
+    {
+        if (affectedFace[faceI])
+        {
+            // Mark face as unaffected
+            affectedFace[faceI] = false;
+
+            // Get filtered face: without points that will be removed
+            const face f(filterFace(pointsToRemove, faceI));
+
+            // Get owner of the face and change it
+            label own = owner[faceI];
+            if (cellRegion[own] != -1)
+            {
+                own = cellRegionMaster[cellRegion[own]];
+            }
+
+            // Set face information
+            label patchID, zoneID, zoneFlip;
+            meshTools::setFaceInfo(mesh_, faceI, patchID, zoneID, zoneFlip);
+
+            // Get neighbour of the face and change it
+            label nei = -1;
+            if (mesh_.isInternalFace(faceI))
+            {
+                nei = neighbour[faceI];
+                if (cellRegion[nei] != -1)
+                {
+                    nei = cellRegionMaster[cellRegion[nei]];
+                }
+            }
+
+            // Finally modify the face
+            modFace
+            (
+                f,                  // modified face
+                faceI,              // label of face being modified
+                own,                // owner
+                nei,                // neighbour
+                false,              // face flip
+                patchID,            // patch for face
+                false,              // remove from zone
+                zoneID,             // zone for face
+                zoneFlip,           // face flip in zone
+
+                ref                 // topo change engine
+            );
+        }
+    }
+}
+
+
+// ************************************************************************* //
diff --git a/src/dynamicMesh/polyTopoChange/polyTopoChanger/polyTopoChanger.C b/src/dynamicMesh/polyTopoChange/polyTopoChanger/polyTopoChanger.C
index 022de1d90db2fa53fc1212a4ca3f59c653381be9..5006832f60f042858c55b49c0bc17ecf220428b0 100644
--- a/src/dynamicMesh/polyTopoChange/polyTopoChanger/polyTopoChanger.C
+++ b/src/dynamicMesh/polyTopoChange/polyTopoChanger/polyTopoChanger.C
@@ -28,7 +28,9 @@ License
 
 #include "polyTopoChanger.H"
 #include "polyMesh.H"
+#include "mapPolyMesh.H"
 #include "polyTopoChange.H"
+#include "batchPolyTopoChange.H"
 #include "Time.H"
 #include "PtrListOps.H"
 
@@ -192,14 +194,14 @@ bool Foam::polyTopoChanger::changeTopology() const
 }
 
 
-Foam::autoPtr<Foam::polyTopoChange>
+Foam::autoPtr<Foam::batchPolyTopoChange>
 Foam::polyTopoChanger::topoChangeRequest() const
 {
     // Collect changes from all modifiers
     const PtrList<polyMeshModifier>& topoChanges = *this;
 
-    auto ptr = autoPtr<polyTopoChange>::New(mesh());
-    polyTopoChange& ref = ptr.ref();
+    auto ptr = autoPtr<batchPolyTopoChange>::New(mesh());
+    batchPolyTopoChange& ref = ptr.ref();
 
     forAll(topoChanges, morphI)
     {
@@ -245,35 +247,35 @@ void Foam::polyTopoChanger::update(const mapPolyMesh& m)
 }
 
 
-Foam::autoPtr<Foam::mapPolyMesh> Foam::polyTopoChanger::changeMesh
-(
-    const bool inflate,
-    const bool syncParallel,
-    const bool orderCells,
-    const bool orderPoints
-)
-{
-    if (changeTopology())
-    {
-        autoPtr<polyTopoChange> ref = topoChangeRequest();
-
-        autoPtr<mapPolyMesh> topoChangeMap = ref().changeMesh
-        (
-            mesh_,
-            inflate,
-            syncParallel,
-            orderCells,
-            orderPoints
-        );
-
-        update(topoChangeMap());
-        mesh_.updateMesh(topoChangeMap());
-        return topoChangeMap;
-    }
-
-    mesh_.topoChanging(false);
-    return nullptr;
-}
+// Foam::autoPtr<Foam::mapPolyMesh> Foam::polyTopoChanger::changeMesh
+// (
+//     const bool inflate,
+//     const bool syncParallel,
+//     const bool orderCells,
+//     const bool orderPoints
+// )
+// {
+//     if (changeTopology())
+//     {
+//         autoPtr<polyTopoChange> ref = topoChangeRequest();
+
+//         autoPtr<mapPolyMesh> topoChangeMap = ref().changeMesh
+//         (
+//             mesh_,
+//             inflate,
+//             syncParallel,
+//             orderCells,
+//             orderPoints
+//         );
+
+//         update(topoChangeMap());
+//         mesh_.updateMesh(topoChangeMap());
+//         return topoChangeMap;
+//     }
+
+//     mesh_.topoChanging(false);
+//     return nullptr;
+// }
 
 
 void Foam::polyTopoChanger::addTopologyModifiers
@@ -326,6 +328,13 @@ Foam::label Foam::polyTopoChanger::findModifierID
 }
 
 
+Foam::label Foam::polyTopoChanger::morphIndex() const
+{
+    return morphIndex_;
+}
+
+
+// writeData member function required by regIOobject
 bool Foam::polyTopoChanger::writeData(Ostream& os) const
 {
     os << *this;
diff --git a/src/dynamicMesh/polyTopoChange/polyTopoChanger/polyTopoChanger.H b/src/dynamicMesh/polyTopoChange/polyTopoChanger/polyTopoChanger.H
index 8ed5efdda1bbdd29d5bb97952b663b3227ba6cc9..be9d14c98973c942f488aa0022e3c5ad5873bbff 100644
--- a/src/dynamicMesh/polyTopoChange/polyTopoChanger/polyTopoChanger.H
+++ b/src/dynamicMesh/polyTopoChange/polyTopoChanger/polyTopoChanger.H
@@ -51,6 +51,7 @@ namespace Foam
 class polyMesh;
 class mapPolyMesh;
 class polyBoundaryMesh;
+class polyTopoChange;
 class polyTopoChanger;
 
 Ostream& operator<<(Ostream&, const polyTopoChanger&);
@@ -65,6 +66,12 @@ class polyTopoChanger
     public PtrList<polyMeshModifier>,
     public regIOobject
 {
+    // Private data
+
+        //- Current time index for mesh morph
+        label morphIndex_;
+
+
     // Private Member Functions
 
         //- Read if IOobject flags set, set modifiers. Return true if read.
@@ -84,6 +91,33 @@ protected:
         //- Reference to mesh
         polyMesh& mesh_;
 
+
+    // Protected Member Functions
+
+        // Topology changes
+
+            //- Rotate a face nShift positions in anticlockwise direction
+            static face rotateFace(const face& f, const label nShift);
+
+            //- Determine ordering of faces in coupled patches.
+            //  Calculate mapping to shuffle faces inside coupled patches and
+            //  rotation to make 0th vertex in faces align.
+            static bool reorderCoupledPatches
+            (
+                const polyBoundaryMesh& boundary,
+                const labelList& patchStarts,
+                const labelList& patchSizes,
+                const faceList& faces,
+                const pointField& points,
+                labelList& faceMap,
+                labelList& rotation
+            );
+
+            //- Sync communications required for couple patch reordering when
+            //  there is no local topological change
+            void syncCoupledPatches();
+
+
 public:
 
     //- Runtime type information
@@ -125,19 +159,27 @@ public:
         bool changeTopology() const;
 
         //- Return topology change request
-        autoPtr<polyTopoChange> topoChangeRequest() const;
+        autoPtr<batchPolyTopoChange> topoChangeRequest() const;
 
         //- Modify point motion
         void modifyMotionPoints(pointField&) const;
 
-        autoPtr<mapPolyMesh> changeMesh
+        // autoPtr<mapPolyMesh> changeMesh
+        // (
+        //     const bool inflate,
+        //     const bool syncParallel = true,
+        //     const bool orderCells = false,
+        //     const bool orderPoints = false
+        // );
+
+        static autoPtr<mapPolyMesh> changeMesh
         (
-            const bool inflate,
-            const bool syncParallel = true,
-            const bool orderCells = false,
-            const bool orderPoints = false
+            polyMesh&,
+            const batchPolyTopoChange&
         );
 
+        autoPtr<mapPolyMesh> changeMesh();
+
         //- Force recalculation of locally stored data on topological change
         void update(const mapPolyMesh& m);
 
@@ -147,6 +189,9 @@ public:
         //- Find modifier given a name
         label findModifierID(const word& modName) const;
 
+        //- Return morph index
+        label morphIndex() const;
+
 
         //- writeData member function required by regIOobject
         bool writeData(Ostream&) const;
diff --git a/src/dynamicMesh/polyTopoChange/polyTopoChanger/polyTopoChangerChangeMesh.C b/src/dynamicMesh/polyTopoChange/polyTopoChanger/polyTopoChangerChangeMesh.C
new file mode 100644
index 0000000000000000000000000000000000000000..30f2bdce0e6529f065f92ac81ef1a38edc89c1dc
--- /dev/null
+++ b/src/dynamicMesh/polyTopoChange/polyTopoChanger/polyTopoChangerChangeMesh.C
@@ -0,0 +1,2431 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
+   \\    /   O peration     |
+    \\  /    A nd           | www.openfoam.com
+     \\/     M anipulation  |
+-------------------------------------------------------------------------------
+    Copyright (C) 2002 Hrvoje Jasak
+-------------------------------------------------------------------------------
+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 "polyTopoChanger.H"
+#include "polyMesh.H"
+#include "Time.H"
+#include "faceZone.H"
+#include "batchPolyTopoChange.H"
+#include "pointField.H"
+#include "polyAddPoint.H"
+#include "polyModifyPoint.H"
+#include "polyAddFace.H"
+#include "polyModifyFace.H"
+#include "Map.H"
+#include "DynamicList.H"
+#include "primitiveMesh.H"
+#include "mapPolyMesh.H"
+#include "objectMap.H"
+#include "globalMeshData.H"
+#include "ListOps.H"
+
+// * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //
+
+Foam::face Foam::polyTopoChanger::rotateFace
+(
+    const face& f,
+    const label nPos
+)
+{
+    face newF(f.size());
+
+    forAll (f, fp)
+    {
+        label fp1 = (fp + nPos) % f.size();
+
+        if (fp1 < 0)
+        {
+            fp1 += f.size();
+        }
+
+        newF[fp1] = f[fp];
+    }
+
+    return newF;
+}
+
+
+// Determine ordering of faces in coupled patches.
+// Calculate mapping to shuffle faces inside coupled patches and
+// rotation to make 0th vertex in faces align.
+bool Foam::polyTopoChanger::reorderCoupledPatches
+(
+    const polyBoundaryMesh& boundary,
+    const labelList& patchStarts,
+    const labelList& patchSizes,
+    const faceList& faces,
+    const pointField& points,
+
+    labelList& faceMap,
+    labelList& rotation
+)
+{
+    // Mapping for faces (old to new). Extends over all mesh faces for
+    // convenience (could be just the external faces)
+
+    // faceMap = identity(faces.size());
+    // faceMap for boundary faces will be forwarded to polyPatch::order(...)
+    for (label i = 0; i < patchStarts[0]; i++)
+    {
+        faceMap[i] = i;
+    }
+
+
+    // Rotation on new faces.
+    rotation.setSize(faces.size());
+    rotation = 0;
+
+    PstreamBuffers pBufs(Pstream::commsTypes::nonBlocking);
+
+    // Send ordering
+    forAll (boundary, patchI)
+    {
+        boundary[patchI].initOrder
+        (
+            pBufs,
+            primitivePatch
+            (
+                SubList<face>
+                (
+                    faces,
+                    patchSizes[patchI],
+                    patchStarts[patchI]
+                ),
+                points
+            )
+        );
+    }
+
+    pBufs.finishedSends();
+
+    // Receive and calculate ordering
+
+    bool anyChanged = false;
+
+    forAll (boundary, patchI)
+    {
+        labelList patchFaceMap(patchSizes[patchI], -1);
+        labelList patchFaceRotation(patchSizes[patchI], 0);
+
+        // Forward boundary faceMap to polyPatch::order(...)
+        label start = patchStarts[patchI];
+        forAll (patchFaceMap, patchFaceI)
+        {
+            patchFaceMap[patchFaceI] =
+                faceMap[patchFaceI + start];
+
+            faceMap[patchFaceI + start] = patchFaceI + start;
+        }
+
+        bool changed = boundary[patchI].order
+        (
+            pBufs,
+            primitivePatch
+            (
+                SubList<face>
+                (
+                    faces,
+                    patchSizes[patchI],
+                    patchStarts[patchI]
+                ),
+                points
+            ),
+            patchFaceMap,
+            patchFaceRotation
+        );
+
+        if (changed)
+        {
+            // Merge patch face reordering into mesh face reordering table
+            label start = patchStarts[patchI];
+
+            forAll (patchFaceMap, patchFaceI)
+            {
+                if (patchFaceMap[patchFaceI] == -1)
+                {
+                    SeriousErrorInFunction
+                        << "Could not determine correspondence for coupled "
+                        << " face " << start+patchFaceI
+                        << " on patch " << patchI << endl
+                        << "Continuing with incorrect ordering." << endl;
+                    return false;
+                }
+
+                faceMap[patchFaceI + start] = start + patchFaceMap[patchFaceI];
+            }
+
+            forAll (patchFaceRotation, patchFaceI)
+            {
+                rotation[patchFaceI + start] = patchFaceRotation[patchFaceI];
+            }
+
+            anyChanged = true;
+        }
+    }
+
+    return anyChanged;
+}
+
+
+// void Foam::polyTopoChanger::syncCoupledPatches()
+// {
+//     // Sync communications required for couple patch reordering when
+//     // there is no local topological change
+
+//     // Send ordering
+//     const polyPatchList& boundary = mesh_.boundaryMesh();
+
+//     PstreamBuffers pBufs(Pstream::commsTypes::nonBlocking);
+
+//     forAll (boundary, patchI)
+//     {
+//         boundary[patchI].initOrder(pBufs, boundary[patchI]);
+//     }
+
+//     pBufs.finishedSends();
+
+//     // Receive ordering
+
+//     // Note: HJ using syncOrder
+//     forAll (boundary, patchI)
+//     {
+//         boundary[patchI].order();
+//     }
+// }
+
+
+Foam::autoPtr<Foam::mapPolyMesh> Foam::polyTopoChanger::changeMesh
+(
+    polyMesh& mesh,
+    const batchPolyTopoChange& ref
+)
+{
+    // Create a new list of points
+    // Note.  Modified points are only influenced in the mesh motion stage
+    if (debug)
+    {
+        Pout<< "polyTopoChanger::changeMesh" << nl
+            << '(' << nl
+            << "    const batchPolyTopoChange& ref" << nl
+            << ") : started executing topological change." << nl << endl;
+
+        // Check topological change request for consistency and report
+        // the statistics of the refinement request
+        if (ref.check())
+        {
+            FatalErrorInFunction
+                << "Inconsistent topological change request."
+                << abort(FatalError);
+        }
+    }
+
+    const pointField& points = mesh.points();
+    const faceList& faces = mesh.faces();
+    const cellList& cells = mesh.cells();
+    const polyBoundaryMesh& boundary = mesh.boundaryMesh();
+    pointZoneMesh& pointZones = mesh.pointZones();
+    faceZoneMesh& faceZones = mesh.faceZones();
+    cellZoneMesh& cellZones = mesh.cellZones();
+
+
+    // Grab old live mesh sizes
+    const label nOldPoints = mesh.nPoints();
+    const label nOldFaces = mesh.nFaces();
+    const label nOldCells = mesh.nCells();
+
+    // Keep the old patch start labels
+    labelList oldPatchStarts(boundary.size());
+    forAll (boundary, patchi)
+    {
+        oldPatchStarts[patchi] = boundary[patchi].start();
+    }
+
+    pointField newPointsZeroVol(points.size() + ref.pointBalance());
+    pointField newPointsMotion(points.size() + ref.pointBalance());
+
+    // renumberPoints contains the new point label for every old
+    // and added point
+    labelList renumberPoints(points.size() + ref.addedPoints().size(), -1);
+
+    // pointMap contains the old point label or the master point label
+    // for all new points
+    labelList pointMap(points.size() + ref.addedPoints().size(), -1);
+    label nNewPoints = 0;
+
+    const labelHashSet& removedPoints = ref.removedPoints();
+    const labelHashSet& removedFaces = ref.removedFaces();
+    const labelHashSet& removedCells = ref.removedCells();
+
+    // Grab the untouched live points
+    for (label pointI = 0; pointI < nOldPoints; pointI++)
+    {
+        // Check if the point has been removed; if not add it to the list
+        if (!removedPoints.found(pointI))
+        {
+            // Grab a point
+            newPointsZeroVol[nNewPoints] = points[pointI];
+            newPointsMotion[nNewPoints] = points[pointI];
+
+            // Grab addressing
+            renumberPoints[pointI] = nNewPoints;
+            pointMap[nNewPoints] = pointI;
+
+            nNewPoints++;
+        }
+    }
+
+    label debugPointCounter = 0;
+
+    if (debug)
+    {
+        Pout<< "Added untouched points. Point count = "
+            << nNewPoints << endl;
+
+        debugPointCounter = nNewPoints;
+    }
+
+    // Change the modified points in two passes: first points
+    // supporting the cells and then auxiliary points
+    const DynamicList<polyModifyPoint>& mp = ref.modifiedPoints();
+
+    forAll (mp, mpI)
+    {
+        if (mp[mpI].inCell())
+        {
+            // Grab a point
+            newPointsZeroVol[nNewPoints] = points[mp[mpI].pointID()];
+            newPointsMotion[nNewPoints] = mp[mpI].newPoint();
+
+            // Grab addressing
+            renumberPoints[mp[mpI].pointID()] = nNewPoints;
+            pointMap[nNewPoints] = mp[mpI].pointID();
+            nNewPoints++;
+        }
+    }
+
+    if (debug)
+    {
+        Pout<< "Added live points: modified = "
+            << nNewPoints - debugPointCounter;
+
+        debugPointCounter = nNewPoints;
+    }
+
+    // Add the new points in two passes: first points supporting the cells
+    // and then auxiliary points
+    const DynamicList<polyAddPoint>& ap = ref.addedPoints();
+
+    // Follow the location in the renumbering list
+    const label np = points.size();
+
+    // Grab points supporting cells
+    forAll (ap, apI)
+    {
+        if (ap[apI].inCell())
+        {
+            // Grab a point
+            if (ap[apI].appended())
+            {
+                // Point appended directly; no motion
+                newPointsZeroVol[nNewPoints] = ap[apI].newPoint();
+            }
+            else
+            {
+                // Point added on top of another point
+                newPointsZeroVol[nNewPoints] = points[ap[apI].masterPointID()];
+            }
+
+            newPointsMotion[nNewPoints] = ap[apI].newPoint();
+
+            // Grab addressing
+            renumberPoints[np + apI] = nNewPoints;
+            pointMap[nNewPoints] = ap[apI].masterPointID();
+            nNewPoints++;
+        }
+    }
+
+    if (debug)
+    {
+        Pout<< " added = " << nNewPoints - debugPointCounter
+            << ".  Point count = "
+            << nNewPoints << endl;
+
+        debugPointCounter = nNewPoints;
+    }
+
+    // Grab the untouched auxiliary points
+    for (label pointI = nOldPoints; pointI < points.size(); pointI++)
+    {
+        // Check if the point has been removed; if not add it to the list
+        if (!removedPoints.found(pointI))
+        {
+            // Grab a point
+            newPointsZeroVol[nNewPoints] = points[pointI];
+            newPointsMotion[nNewPoints] = points[pointI];
+
+            // Grab addressing
+            renumberPoints[pointI] = nNewPoints;
+            pointMap[nNewPoints] = pointI;
+
+            nNewPoints++;
+        }
+    }
+
+    if (debug)
+    {
+        Pout<< "Added retired points: untouched = "
+            << nNewPoints - debugPointCounter;
+
+        debugPointCounter = nNewPoints;
+    }
+
+    // Grab auxiliary points
+    forAll (mp, mpI)
+    {
+        if (!mp[mpI].inCell())
+        {
+            // Grab a point
+            newPointsZeroVol[nNewPoints] = points[mp[mpI].pointID()];
+            newPointsMotion[nNewPoints] = mp[mpI].newPoint();
+
+            // Grab addressing
+            renumberPoints[mp[mpI].pointID()] = nNewPoints;
+            pointMap[nNewPoints] = mp[mpI].pointID();
+            nNewPoints++;
+        }
+    }
+
+    if (debug)
+    {
+        Pout<< " modified = "
+            << nNewPoints - debugPointCounter;
+
+        debugPointCounter = nNewPoints;
+    }
+
+    forAll (ap, apI)
+    {
+        if (!ap[apI].inCell())
+        {
+            // Grab a point
+            if (ap[apI].appended())
+            {
+                // Point appended directly; no motion
+                newPointsZeroVol[nNewPoints] = ap[apI].newPoint();
+            }
+            else
+            {
+                // Point added on top of another point
+                newPointsZeroVol[nNewPoints] = points[ap[apI].masterPointID()];
+            }
+
+            newPointsMotion[nNewPoints] = ap[apI].newPoint();
+
+            // Grab addressing
+            renumberPoints[np + apI] = nNewPoints;
+            pointMap[nNewPoints] = ap[apI].masterPointID();
+            nNewPoints++;
+        }
+    }
+
+    if (debug)
+    {
+        Pout<< " added = " << nNewPoints - debugPointCounter
+            << ".  Point count = "
+            << nNewPoints << endl;
+
+        debugPointCounter = nNewPoints;
+    }
+
+    // Reset the size of point map
+    pointMap.setSize(nNewPoints);
+
+    if (debug)
+    {
+        Pout<< "Added all points.  Final point count = "
+            << nNewPoints << nl << endl;
+    }
+
+    // Create a new list of faces
+    // Algorithm: go throught the list of original faces and
+    // distribute them to the cells, skipping the ones marked as
+    // removed.  Then add the new and modified faces to the cells.
+    // Renumber the faces using the point renumbering map.
+    // Gather the internal faces by looping through the cell list and
+    // collecting faces
+
+    const labelList& allOwn = mesh.faceOwner();
+    const labelList& allNei = mesh.faceNeighbour();
+
+    List<DynamicList<face> >
+        cf(cells.size() + ref.addedCells().size());
+
+    List<DynamicList<label> >
+        cfLabels(cells.size() + ref.addedCells().size());
+
+    // Insert untouched internal faces
+    for (label faceI = 0; faceI < mesh.nInternalFaces(); faceI++)
+    {
+        if (!removedFaces.found(faceI))
+        {
+            cf[allOwn[faceI]].append(faces[faceI]);
+            cfLabels[allOwn[faceI]].append(faceI);
+
+            cf[allNei[faceI]].append(faces[faceI]);
+            cfLabels[allNei[faceI]].append(faceI);
+        }
+    }
+
+    if (debug)
+    {
+        Pout<< "Inserted untouched faces into cells" << endl;
+    }
+
+    // Add the modified internal faces
+    const DynamicList<polyModifyFace>& mf = ref.modifiedFaces();
+
+    forAll (mf, mfI)
+    {
+        if (!mf[mfI].isInPatch() && !mf[mfI].onlyInZone())
+        {
+            if (debug)
+            {
+                // Check if the internal face is defined properly
+                if
+                (
+                    min(mf[mfI].owner(), mf[mfI].neighbour()) < 0
+                 || max(mf[mfI].owner(), mf[mfI].neighbour()) >= cf.size()
+                )
+                {
+                    FatalErrorInFunction
+                        << "Invalid modified face " << mf[mfI].faceID()
+                        << ".  Declared as internal but owner or neighbour "
+                        << "are invalid." << nl
+                        << "Owner: " << mf[mfI].owner()
+                        << " Neighbour: " << mf[mfI].neighbour()
+                        << " Max number of cells: " << cf.size()
+                        << abort(FatalError);
+                }
+            }
+
+            // Grab face and face label
+            cf[mf[mfI].owner()].append(mf[mfI].newFace());
+            cfLabels[mf[mfI].owner()].append(mf[mfI].faceID());
+
+            cf[mf[mfI].neighbour()].append(mf[mfI].newFace());
+            cfLabels[mf[mfI].neighbour()].append(mf[mfI].faceID());
+        }
+    }
+
+    if (debug)
+    {
+        Pout<< "Inserted modified faces into cells" << endl;
+    }
+
+    // Add the new faces
+    const DynamicList<polyAddFace>& af = ref.addedFaces();
+
+    forAll (af, afI)
+    {
+        if (!af[afI].isInPatch() && !af[afI].onlyInZone())
+        {
+            if (debug)
+            {
+                // Check if the internal face is defined properly
+                if
+                (
+                    min(af[afI].owner(), af[afI].neighbour()) < 0
+                 || max(af[afI].owner(), af[afI].neighbour()) >= cf.size()
+                )
+                {
+                    FatalErrorInFunction
+                        << "Invalid added face " << faces.size() + afI
+                        << ".  Declared as internal but owner or neighbour "
+                        << "are invalid." << nl
+                        << "Owner: " << af[afI].owner()
+                        << " Neighbour: " << af[afI].neighbour()
+                        << " Max number of cells: " << cf.size()
+                        << abort(FatalError);
+                }
+            }
+
+            // Grab face and face label
+            cf[af[afI].owner()].append(af[afI].newFace());
+            cfLabels[af[afI].owner()].append(faces.size() + afI);
+
+            cf[af[afI].neighbour()].append(af[afI].newFace());
+            cfLabels[af[afI].neighbour()].append(faces.size() + afI);
+        }
+    }
+
+    if (debug)
+    {
+        Pout<< "Inserted added faces into cells" << endl;
+    }
+
+    // All internal faces now exist on the list.  Create the new face
+    // list using upper triangular search
+
+    // Create partial point-cell addressing
+    List<DynamicList<label, primitiveMesh::facesPerPoint_> > PointCells
+    (
+        points.size() + ref.addedPoints().size()
+    );
+
+    boolListList usedCellFaces(cf.size());
+
+    forAll (cf, cellI)
+    {
+        if (!removedCells.found(cellI))
+        {
+            const DynamicList<face>&
+                curFaces = cf[cellI];
+
+            // Resize and reset the cell-face list at the same time
+            usedCellFaces[cellI].setSize(curFaces.size());
+            usedCellFaces[cellI] = false;
+
+            // Add the cell as a neighbour to all of the points
+            // of all of its faces
+            forAll (curFaces, faceI)
+            {
+                const labelList& curFacePoints = curFaces[faceI];
+
+                forAll (curFacePoints, pointI)
+                {
+                    bool found = false;
+
+                    DynamicList<label, primitiveMesh::facesPerPoint_>&
+                        curPointCells = PointCells[curFacePoints[pointI]];
+
+                    forAll (curPointCells, i)
+                    {
+                        if (curPointCells[i] == cellI)
+                        {
+                            // Cell already a neighbour of this point
+                            found = true;
+                            break;
+                        }
+                    }
+
+                    if (!found)
+                    {
+                        curPointCells.append(cellI);
+                    }
+                }
+            }
+        }
+        else
+        {
+            // Check if the removed cell has got any faces
+            if (cf[cellI].size() > 0)
+            {
+                FatalErrorInFunction
+                    << "Cell " << cellI << " is marked as removed but still "
+                    << "has faces.  Cell faces: " << cf[cellI]
+                    << abort(FatalError);
+            }
+        }
+    }
+
+    List<DynamicList<label> >
+        newCellFaces(cf.size());
+
+    faceList newFaces(faces.size() + ref.faceBalance());
+
+    // renumberFaces contains a new face label for every old and added face
+    labelList renumberFaces(faces.size() + ref.addedFaces().size(), -1);
+
+    // faceMap contains the old face label for every new face.  For
+    // inserted faces, the label will be -1.  Use with care!
+    labelList faceMap(faces.size() + ref.faceBalance(), -1);
+    label nNewFaces = 0;
+
+    // Create ordered list of faces
+
+    // Note:
+    // Insertion cannot be done in one go as the faces need to be
+    // added into the list in the increasing order of neighbour
+    // cells.  Therefore, all neighbours will be detected first
+    // and then added in the correct order.
+    // Watch out.  Subtly different from createPolyMesh!
+
+    forAll (cf, cellI)
+    {
+        const DynamicList<face>&
+            curFaces = cf[cellI];
+        labelList neiCells(curFaces.size(), -1);
+        label nNeighbours = 0;
+
+        // For all faces ...
+        forAll (curFaces, faceI)
+        {
+            // Skip faces that have already been matched
+            if (usedCellFaces[cellI][faceI]) continue;
+
+            bool found = false;
+
+            const face& curFace = curFaces[faceI];
+
+            // Get the list of labels
+            const labelList& curPoints = curFace;
+
+            // For all points
+            forAll (curPoints, pointI)
+            {
+                // Get the list of cells sharing this point
+                const DynamicList<label, primitiveMesh::facesPerPoint_>&
+                    curNeighbours = PointCells[curPoints[pointI]];
+
+                // For all neighbours
+                forAll (curNeighbours, neiI)
+                {
+                    label curNei = curNeighbours[neiI];
+
+                    // Reject neighbours with the lower label
+                    if (curNei > cellI)
+                    {
+                        // Get the list of search faces
+                        const DynamicList<face>&
+                            searchFaces = cf[curNei];
+
+                        forAll (searchFaces, neiFaceI)
+                        {
+                            if (searchFaces[neiFaceI] == curFace)
+                            {
+                                // Match!!
+                                found = true;
+
+                                // Record the neighbour cell and face
+                                neiCells[faceI] = curNei;
+                                nNeighbours++;
+
+                                break;
+                            }
+                        }
+                        if (found) break;
+                    }
+                    if (found) break;
+                }
+                if (found) break;
+            } // End of current points
+        } // End of current faces
+
+        // Add the faces in the increasing order of neighbours
+        for
+        (
+            label neiSearch = 0;
+            neiSearch < nNeighbours;
+            neiSearch++
+        )
+        {
+            // Find the lowest neighbour which is still valid
+            label nextNei = -1;
+            label minNei = cf.size();
+
+            forAll (neiCells, ncI)
+            {
+                if (neiCells[ncI] > -1 && neiCells[ncI] < minNei)
+                {
+                    nextNei = ncI;
+                    minNei = neiCells[ncI];
+                }
+            }
+
+            if (nextNei > -1)
+            {
+                // Add the face to the list of faces
+                newFaces[nNewFaces] = curFaces[nextNei];
+
+                // Set cell-face and cell-neighbour-face to current face label
+                newCellFaces[cellI].append(nNewFaces);
+                newCellFaces[minNei].append(nNewFaces);
+
+                // Grab the renumbering index
+                renumberFaces[cfLabels[cellI][nextNei]] = nNewFaces;
+
+                if (cfLabels[cellI][nextNei] < faces.size())
+                {
+                    faceMap[nNewFaces] =
+                        cfLabels[cellI][nextNei];
+                }
+
+                // Stop the neighbour from being used again
+                neiCells[nextNei] = -1;
+
+                // Increment number of faces counter
+                nNewFaces++;
+            }
+            else
+            {
+                FatalErrorInFunction
+                    << "Error in internal face insertion"
+                    << abort(FatalError);
+            }
+        }
+    }
+
+    label debugFaceCounter = 0;
+
+    if (debug)
+    {
+        Pout<< "Added internal faces.  Face count = "
+            << nNewFaces << endl;
+
+        debugFaceCounter = nNewFaces;
+    }
+
+    // Redistribute modified and newly added patch faces per patch
+    List<DynamicList<label, 10> > patchModifiedFaces(boundary.size());
+    List<DynamicList<label, 10> > patchAddedFaces(boundary.size());
+
+    forAll (mf, mfI)
+    {
+        if (mf[mfI].isInPatch())
+        {
+            patchModifiedFaces[mf[mfI].patchID()].append(mfI);
+        }
+    }
+
+    forAll (af, afI)
+    {
+        if (af[afI].isInPatch())
+        {
+            patchAddedFaces[af[afI].patchID()].append(afI);
+        }
+    }
+
+    // For every patch add original patch faces which have not been removed
+    // Remember the new patch starts and sizes
+    labelList patchSizes(boundary.size(), 0);
+    labelList patchStarts(boundary.size(), -1);
+
+    forAll (boundary, patchI)
+    {
+        // Add original patch faces
+        const label curSize = boundary[patchI].size();
+        const label curStart = boundary[patchI].start();
+
+        const UList<face>& curFaces = boundary[patchI];
+
+        // Grab patch start
+        patchStarts[patchI] = nNewFaces;
+
+        for (label faceI = curStart; faceI < curStart + curSize; faceI++)
+        {
+            if (!removedFaces.found(faceI))
+            {
+                newFaces[nNewFaces] = curFaces[faceI - curStart];
+                renumberFaces[faceI] = nNewFaces;
+
+                faceMap[nNewFaces] = faceI;
+
+                // Add the face to the owner cell
+                if (allOwn[faceI] >= 0)
+                {
+                    newCellFaces[allOwn[faceI]].append(nNewFaces);
+                }
+
+                nNewFaces++;
+            }
+        }
+
+        if (debug)
+        {
+            Pout<< "Patch " << patchI
+                << ": added faces: untouched = "
+                << nNewFaces - debugFaceCounter;
+
+            debugFaceCounter = nNewFaces;
+        }
+
+        // Add new faces belonging to this patch
+        const DynamicList<label, 10>& modPatchFaces =
+            patchModifiedFaces[patchI];
+
+        forAll (modPatchFaces, faceI)
+        {
+            newFaces[nNewFaces] = mf[modPatchFaces[faceI]].newFace();
+            renumberFaces[mf[modPatchFaces[faceI]].faceID()] = nNewFaces;
+
+            faceMap[nNewFaces]= mf[modPatchFaces[faceI]].faceID();
+
+            label newOwner = mf[modPatchFaces[faceI]].owner();
+
+            // Add the face to the owner cell
+            if (newOwner >= 0)
+            {
+                newCellFaces[newOwner].append(nNewFaces);
+            }
+
+            nNewFaces++;
+        }
+
+        if (debug)
+        {
+            Pout<< " modified = " << nNewFaces - debugFaceCounter;
+
+            debugFaceCounter = nNewFaces;
+        }
+
+        // Add new faces belonging to this patch
+        const DynamicList<label, 10>& newPatchFaces = patchAddedFaces[patchI];
+
+        forAll (newPatchFaces, faceI)
+        {
+            newFaces[nNewFaces] = af[newPatchFaces[faceI]].newFace();
+            renumberFaces[faces.size() + newPatchFaces[faceI]] = nNewFaces;
+
+            label newOwner = af[newPatchFaces[faceI]].owner();
+
+            // Add the face to the owner cell
+            if (newOwner >= 0)
+            {
+                newCellFaces[newOwner].append(nNewFaces);
+            }
+
+            nNewFaces++;
+        }
+
+        if (debug)
+        {
+            Pout<< " added = " << nNewFaces - debugFaceCounter
+                << ".  Face count = " << nNewFaces << endl;
+
+            debugFaceCounter = nNewFaces;
+        }
+
+        // Grab the new patch size
+        patchSizes[patchI] = nNewFaces - patchStarts[patchI];
+    }
+
+    // Add freely-standing faces to the back of the list
+
+    // Freely-standing faces from the original mesh
+    // Insert untouched internal faces
+    for (label faceI = nOldFaces; faceI < faces.size(); faceI++)
+    {
+        if (!removedFaces.found(faceI))
+        {
+            newFaces[nNewFaces] = faces[faceI];
+            renumberFaces[faceI] = nNewFaces;
+            faceMap[nNewFaces]= faceI;
+            nNewFaces++;
+        }
+    }
+
+    if (debug)
+    {
+        Pout<< "Added zone-only faces: untouched = "
+            << nNewFaces - debugFaceCounter;
+
+            debugFaceCounter = nNewFaces;
+    }
+
+    // Freely-standing modified faces
+    forAll (mf, mfI)
+    {
+        if (mf[mfI].onlyInZone())
+        {
+            newFaces[nNewFaces] = mf[mfI].newFace();
+            renumberFaces[mf[mfI].faceID()] = nNewFaces;
+            faceMap[nNewFaces]= mf[mfI].faceID();
+            nNewFaces++;
+        }
+    }
+
+    if (debug)
+    {
+        Pout<< " modified = " << nNewFaces - debugFaceCounter;
+
+            debugFaceCounter = nNewFaces;
+    }
+
+    // Freely-standing added faces
+    forAll (af, afI)
+    {
+        if (af[afI].onlyInZone())
+        {
+            newFaces[nNewFaces] = af[afI].newFace();
+            renumberFaces[faces.size() + afI] = nNewFaces;
+            nNewFaces++;
+        }
+    }
+
+    if (debug)
+    {
+        Pout<< " added = " << nNewFaces - debugFaceCounter
+            << ".  Final face count = "
+            << nNewFaces << nl << endl;
+
+            debugFaceCounter = nNewFaces;
+    }
+
+    if (debug)
+    {
+        if (nNewFaces != faces.size() + ref.faceBalance())
+        {
+            FatalErrorInFunction
+                << "Error in face insertion.  Number of inserted faces: "
+                << nNewFaces << ".  Expected "
+                << faces.size() + ref.faceBalance()
+                << " faces."
+                << abort(FatalError);
+        }
+    }
+
+    // Face list and cell faces completed.
+    // Renumber the faces using the point renumber list
+    forAll (newFaces, faceI)
+    {
+        face oldFace = newFaces[faceI];
+        face& renumberedFace = newFaces[faceI];
+
+        forAll (renumberedFace, pointI)
+        {
+            renumberedFace[pointI] = renumberPoints[oldFace[pointI]];
+        }
+
+        if (debug)
+        {
+            // Check if the face has been mapped correctly
+            if
+            (
+                renumberedFace.size() == 0
+             || min(renumberedFace) < 0
+             || max(renumberedFace) >= newPointsZeroVol.size()
+            )
+            {
+                FatalErrorInFunction
+                    << "Face " << faceI << " in the new mesh is not "
+                    << "mapped correctly." << nl
+                    << "It uses a removed or a non-existing vertex or "
+                    << "has been skipped." << nl
+                    << "Face before mapping: " << oldFace << " with points "
+                    << oldFace.points(newPointsZeroVol) << nl
+                    << mesh.points().size() << nl
+                    << "Face after mapping: " << renumberedFace << nl
+                    << "Max new vertex index: "
+                    << newPointsZeroVol.size() - 1 << "." << nl
+                    << "Are there extra faces in the face list that do not "
+                    << "belong to a face zone?  This is not allowed."
+                    << abort(FatalError);
+            }
+        }
+    }
+
+
+
+    // Coupled face ordering
+    // ~~~~~~~~~~~~~~~~~~~~~
+
+    // Update faceMap for added faces which have master face
+    forAll (af, afI)
+    {
+        if (af[afI].isFaceMaster()) // Face mastered by another face
+        {
+            faceMap[renumberFaces[faces.size() + afI]] =
+                af[afI].masterFaceID();
+        }
+    }
+
+    // At this point we have
+    // - newFaces: faces in upper-triangular and patch order
+    // - patchSizes, patchStarts: valid on newFaces
+    // - newCellFaces: consistent cell-face addressing
+    // - renumberFaces: from old to new face
+    // - faceMap: from new to old face
+
+    // Calculate face map and rotation so coupled faces are matched
+    // correctly
+
+    // labelList localFaceMap(newFaces.size());
+    // Forward faceMap to polyPatch::order() (needed by cohesivePolyPatch)
+    labelList localFaceMap = faceMap;
+    labelList rotation(newFaces.size(), 0);
+
+    bool anyChange = reorderCoupledPatches
+    (
+        boundary,
+        patchStarts,
+        patchSizes,
+        newFaces,           // new faces
+        newPointsMotion,    // points after inflation
+
+        localFaceMap,       // for every face the new position
+        rotation            // amount face needs to be rotated
+    );
+
+    // Reorder everything referring to faces (not patchStarts/sizes since
+    // faces only get reshuffled inside patches)
+    if (anyChange)
+    {
+        // Reorder newFaces according to localFaceMap
+        inplaceReorder(localFaceMap, newFaces);
+
+        // Renumber newCellFaces so they refer to the new face ordering
+        forAll (newCellFaces, cellI)
+        {
+            DynamicList<label>& cFaces = newCellFaces[cellI];
+
+            cFaces.shrink();
+            inplaceRenumber(localFaceMap, cFaces);
+        }
+
+        // Renumber renumberFaces so they refer to the new face ordering
+        inplaceRenumber(localFaceMap, renumberFaces);
+
+        // Reorder faceMap
+        inplaceReorder(localFaceMap, faceMap);
+
+        // Rotate faces (rotation is already in new face indices).
+        forAll (rotation, faceI)
+        {
+            label rotate = rotation[faceI];
+
+            if (rotate != 0)
+            {
+                newFaces[faceI] = rotateFace(newFaces[faceI], rotate);
+            }
+        }
+    }
+
+
+    // Build the face-from maps
+
+    List<objectMap> faceFromPoint(af.size());
+    label nFaceFromPoint = 0;
+    List<objectMap> faceFromEdge(af.size());
+    label nFaceFromEdge = 0;
+
+    forAll (af, afI)
+    {
+        if (af[afI].isPointMaster())
+        {
+            if (debug)
+            {
+                // Check that the master point index is in range
+                if
+                (
+                    af[afI].masterPointID() < 0
+                 || af[afI].masterPointID() >= mesh.nPoints()
+                )
+                {
+                    FatalErrorInFunction
+                        << "Master point for face " << faces.size() + afI
+                        << " is out of range: " << af[afI].masterPointID()
+                        << ".\n  Number of valid master points: "
+                        << mesh.nPoints()
+                        << abort(FatalError);
+                }
+            }
+
+            if (af[afI].isInPatch())
+            {
+                // Grab faces neighbouring the point which are in the
+                // same patch as the newly added face.
+                const labelList& pf =
+                   mesh.pointFaces()[af[afI].masterPointID()];
+
+                labelList facesAroundPoint(pf.size());
+                label nfap = 0;
+
+                forAll (pf, pfI)
+                {
+                    label wp = boundary.whichPatch(pf[pfI]);
+                    if (wp == af[afI].patchID())
+                    {
+                        facesAroundPoint[nfap] = pf[pfI];
+                        nfap++;
+                    }
+                }
+
+                if (debug)
+                {
+                    if (nfap == 0)
+                    {
+                        FatalErrorInFunction
+                            << "No patch face neighbours found for added "
+                            << "patch face " << afI
+                            << ".\nThere are no faces from patch "
+                            << af[afI].patchID()
+                            << " around the master point "
+                            << af[afI].masterPointID()
+                            << ".\n Bad choice of master point: "
+                            << "error in mesh mapping."
+                            << abort(FatalError);
+                    }
+                }
+
+                facesAroundPoint.setSize(nfap);
+
+                faceFromPoint[nFaceFromPoint] =
+                    objectMap
+                    (
+                        renumberFaces[faces.size() + afI],
+                        facesAroundPoint
+                    );
+
+                nFaceFromPoint++;
+            }
+            else
+            {
+                // Grab internal faces around the point
+                const labelList& pf =
+                    mesh.pointFaces()[af[afI].masterPointID()];
+
+                labelList facesAroundPoint(pf.size());
+                label nfap = 0;
+
+                forAll (pf, pfI)
+                {
+                    if (mesh.isInternalFace(pf[pfI]))
+                    {
+                        facesAroundPoint[nfap] = pf[pfI];
+                        nfap++;
+                    }
+                }
+
+                if (debug)
+                {
+                    if (nfap == 0 && mesh.nInternalFaces() > 0)
+                    {
+                        FatalErrorInFunction
+                            << "No face neighbours found for added "
+                            << "internal face " << afI
+                            << ".\nThere are no internal faces "
+                            << "around the master point "
+                            << af[afI].masterPointID()
+                            << ".\n Bad choice of master point: "
+                            << "error in mesh mapping."
+                            << abort(FatalError);
+                    }
+                }
+
+                facesAroundPoint.setSize(nfap);
+
+                faceFromPoint[nFaceFromPoint] =
+                    objectMap
+                    (
+                        renumberFaces[faces.size() + afI],
+                        facesAroundPoint
+                    );
+
+                nFaceFromPoint++;
+            }
+        }
+        else if (af[afI].isEdgeMaster())
+        {
+            if (debug)
+            {
+                // Check that the master edge index is in range
+                if
+                (
+                    af[afI].masterEdgeID() < 0
+                 || af[afI].masterEdgeID() >= mesh.nEdges()
+                )
+                {
+                    FatalErrorInFunction
+                        << "Master edge for face " << faces.size() + afI
+                        << " is out of range: " << af[afI].masterEdgeID()
+                        << ".\n  Number of valid master edges: "
+                        << mesh.nEdges()
+                        << abort(FatalError);
+                }
+            }
+
+            if (af[afI].isInPatch())
+            {
+                // Grab faces neighbouring the point which are in the
+                // same patch as the newly added face Note: the
+                // addressing is now into the patch instead of the
+                // global face list
+                const labelList& pe =
+                   mesh.edgeFaces()[af[afI].masterEdgeID()];
+
+                labelList facesAroundEdge(pe.size());
+                label nfae = 0;
+
+                forAll (pe, peI)
+                {
+                    label wp = boundary.whichPatch(pe[peI]);
+                    if (wp == af[afI].patchID())
+                    {
+                        facesAroundEdge[nfae] = pe[peI];
+                        nfae++;
+                    }
+                }
+
+                if (debug)
+                {
+                    if (nfae == 0)
+                    {
+                        FatalErrorInFunction
+                            << "No patch face neighbours found for added "
+                            << "patch face " << afI
+                            << ".  Error in mesh mapping."
+                            << abort(FatalError);
+                    }
+                }
+
+                facesAroundEdge.setSize(nfae);
+
+                faceFromEdge[nFaceFromEdge] =
+                    objectMap
+                    (
+                        renumberFaces[faces.size() + afI],
+                        facesAroundEdge
+                    );
+
+                nFaceFromEdge++;
+            }
+            else
+            {
+                // Grab internal faces around the edge
+                const labelList& pe =
+                    mesh.edgeFaces()[af[afI].masterEdgeID()];
+
+                labelList facesAroundEdge(pe.size());
+                label nfae = 0;
+
+                forAll (pe, peI)
+                {
+                    if (mesh.isInternalFace(pe[peI]))
+                    {
+                        facesAroundEdge[nfae] = pe[peI];
+                        nfae++;
+                    }
+                }
+
+                if (debug)
+                {
+                    if (nfae == 0)
+                    {
+                        FatalErrorInFunction
+                            << "No patch face neighbours found for added "
+                            << "internal face " << afI
+                            << ".  Error in mesh mapping."
+                            << abort(FatalError);
+                    }
+                }
+
+                facesAroundEdge.setSize(nfae);
+
+                faceFromEdge[nFaceFromEdge] =
+                    objectMap
+                    (
+                        renumberFaces[faces.size() + afI],
+                        facesAroundEdge
+                    );
+
+                nFaceFromEdge++;
+            }
+        }
+        else if (af[afI].isFaceMaster()) // Face mastered by another face
+        {
+            faceMap[renumberFaces[faces.size() + afI]] =
+                af[afI].masterFaceID();
+        }
+    }
+
+    // Reset the size of face mapping lists
+    faceFromPoint.setSize(nFaceFromPoint);
+    faceFromEdge.setSize(nFaceFromEdge);
+
+    // Check face maps
+    if (debug)
+    {
+        boolList mappedFaces(faceMap.size(), false);
+
+        // Fill in faces mapped from the face map
+        forAll (faceMap, faceI)
+        {
+            if (faceMap[faceI] >= 0)
+            {
+                mappedFaces[faceI] = true;
+            }
+        }
+
+        // Fill in point and edge maps
+        forAll (faceFromPoint, faceI)
+        {
+            mappedFaces[faceFromPoint[faceI].index()] = true;
+        }
+
+        forAll (faceFromEdge, faceI)
+        {
+            mappedFaces[faceFromEdge[faceI].index()] = true;
+        }
+
+        // Check if all the faces are mapped
+        label nUnmappedFaces = 0;
+
+        forAll (mappedFaces, faceI)
+        {
+            if (!mappedFaces[faceI])
+            {
+                nUnmappedFaces++;
+            }
+        }
+
+        if (nUnmappedFaces > 0)
+        {
+            Pout<< "void polyMesh::morph(const batchPolyTopoChange& ref) : "
+                << "unmapped data for " << nUnmappedFaces << " faces." << endl;
+        }
+    }
+
+    labelHashSet flipFaceFlux(mf.size() + af.size());
+
+    // Build the flip flux map
+    forAll (mf, mfI)
+    {
+        if (mf[mfI].flipFaceFlux())
+        {
+            flipFaceFlux.insert(renumberFaces[mf[mfI].faceID()]);
+        }
+    }
+
+    forAll (af, afI)
+    {
+        if (af[afI].isFaceMaster() && af[afI].flipFaceFlux())
+        {
+            flipFaceFlux.insert(renumberFaces[faces.size() + afI]);
+        }
+    }
+
+    // Renumber the cells
+
+    cellList newCells(cells.size() + ref.cellBalance());
+
+    // renumberCells holds the new cell label for all old and added cells
+    labelList renumberCells(cells.size() + ref.addedCells().size(), -1);
+
+    // cellMap holds the old cell label for every preserved cell
+    labelList cellMap(cells.size() + ref.cellBalance(), -1);
+
+    label nNewCells = 0;
+
+    forAll (newCellFaces, cellI)
+    {
+        if (!removedCells.found(cellI))
+        {
+            if (newCellFaces[cellI].size() < 4)
+            {
+                FatalErrorInFunction
+                    << "Cell " << cellI << " has got three or less faces "
+                    << "and has not been removed.  "
+                    << "This is not a valid cell." << endl
+                    << "Cell faces: " << newCellFaces[cellI]
+                    << abort(FatalError);
+            }
+
+            // Make a cell
+            newCells[nNewCells].transfer(newCellFaces[cellI].shrink());
+
+            renumberCells[cellI] = nNewCells;
+
+            // Add the cell into cell map if it is preserved
+            if (cellI < nOldCells)
+            {
+                cellMap[nNewCells] = cellI;
+            }
+
+            nNewCells++;
+        }
+    }
+
+    if (debug)
+    {
+        Pout<< "Added all cells.  Final cell count = "
+            << nNewCells << nl << endl;
+    }
+
+    label nPreservedCells = cells.size() - removedCells.size();
+
+    // Build the cell-from maps
+
+    const DynamicList<polyAddCell>& ac = ref.addedCells();
+    const DynamicList<polyModifyCell>& mc = ref.modifiedCells();
+
+    List<objectMap> cellFromPoint(ac.size());
+    label nCellFromPoint = 0;
+
+    List<objectMap> cellFromEdge(ac.size());
+    label nCellFromEdge = 0;
+
+    List<objectMap> cellFromFace(ac.size());
+    label nCellFromFace = 0;
+
+    forAll (ac, acI)
+    {
+        if (ac[acI].isPointMaster())
+        {
+            if (debug)
+            {
+                // Check that the master point index is in range
+                if
+                (
+                    ac[acI].masterPointID() < 0
+                 || ac[acI].masterPointID() >= mesh.nPoints()
+                )
+                {
+                    FatalErrorInFunction
+                        << "Master point for cell " << nPreservedCells + acI
+                        << " is out of range: " << ac[acI].masterPointID()
+                        << ".\n  Number of valid master points: "
+                        << mesh.nPoints()
+                        << abort(FatalError);
+                }
+            }
+
+            cellFromPoint[nCellFromPoint] =
+                objectMap
+                (
+                    nPreservedCells + acI,
+                    mesh.pointCells()[ac[acI].masterPointID()]
+                );
+
+            nCellFromPoint++;
+        }
+        else if (ac[acI].isEdgeMaster())
+        {
+            if (debug)
+            {
+                // Check that the master edge index is in range
+                if
+                (
+                    ac[acI].masterEdgeID() < 0
+                 || ac[acI].masterEdgeID() >= mesh.nEdges()
+                )
+                {
+                    FatalErrorInFunction
+                        << "Master edge for cell " << nPreservedCells + acI
+                        << " is out of range: " << ac[acI].masterEdgeID()
+                        << ".\n  Number of valid master edges: "
+                        << mesh.nEdges()
+                        << abort(FatalError);
+                }
+            }
+
+            cellFromEdge[nCellFromEdge] =
+                objectMap
+                (
+                    nPreservedCells + acI,
+                    mesh.edgeCells()[ac[acI].masterEdgeID()]
+                );
+
+            nCellFromEdge++;
+        }
+        else if (ac[acI].isFaceMaster()) // Cell mastered by a face
+        {
+            if (debug)
+            {
+                // Check that the master face index is in range
+                if
+                (
+                    ac[acI].masterFaceID() < 0
+                 || ac[acI].masterFaceID() >= mesh.nFaces()
+                )
+                {
+                    FatalErrorInFunction
+                        << "Master face for cell " << nPreservedCells + acI
+                        << " is out of range: " << ac[acI].masterFaceID()
+                        << ".\n  Number of valid master faces: "
+                        << mesh.nFaces()
+                        << abort(FatalError);
+                }
+            }
+
+            labelList cellsAroundFace(2, label(-1));
+
+            cellsAroundFace[0] = mesh.faceOwner()[ac[acI].masterFaceID()];
+
+            if (mesh.isInternalFace(ac[acI].masterFaceID()))
+            {
+                cellsAroundFace[1] =
+                    mesh.faceNeighbour()[ac[acI].masterFaceID()];
+            }
+            else
+            {
+                cellsAroundFace.setSize(1);
+            }
+
+            cellFromFace[nCellFromFace] =
+                objectMap
+                (
+                    nPreservedCells + acI,
+                    cellsAroundFace
+                );
+
+            nCellFromFace++;
+        }
+        else if (ac[acI].isCellMaster()) // Cell mastered by another cell
+        {
+            cellMap[renumberCells[cells.size() + acI]] =
+                ac[acI].masterCellID();
+        }
+    }
+
+    // Reset the size of cell mapping lists
+    cellFromPoint.setSize(nCellFromPoint);
+    cellFromEdge.setSize(nCellFromEdge);
+    cellFromFace.setSize(nCellFromFace);
+
+    // Check cell maps
+    if (debug)
+    {
+        boolList mappedCells(newCells.size(), false);
+
+        // Fill in cells mapped from the cell map
+        forAll (cellMap, cellI)
+        {
+            if (cellMap[cellI] >= 0)
+            {
+                mappedCells[cellI] = true;
+            }
+        }
+
+        // Fill in point and edge maps
+        forAll (cellFromPoint, cellI)
+        {
+            mappedCells[cellFromPoint[cellI].index()] = true;
+        }
+
+        forAll (cellFromEdge, cellI)
+        {
+            mappedCells[cellFromEdge[cellI].index()] = true;
+        }
+
+        forAll (cellFromFace, cellI)
+        {
+            mappedCells[cellFromFace[cellI].index()] = true;
+        }
+
+        // Check if all the cells are mapped
+        label nUnmappedCells = 0;
+
+        forAll (mappedCells, cellI)
+        {
+            if (!mappedCells[cellI])
+            {
+                nUnmappedCells++;
+            }
+        }
+
+        if (nUnmappedCells > 0)
+        {
+            Pout<< "void polyMesh::morph(const batchPolyTopoChange& ref) : "
+                << "unmapped data for " << nUnmappedCells << " cells." << endl;
+        }
+    }
+
+
+    // Rebuild the mesh
+    // ~~~~~~~~~~~~~~~~
+
+    // Grab patch mesh point maps
+
+    List<Map<label> > oldPatchMeshPointMaps(boundary.size());
+    labelList oldPatchNMeshPoints(boundary.size());
+
+    forAll (boundary, patchI)
+    {
+        // Copy old face zone mesh point maps
+        oldPatchMeshPointMaps[patchI] = boundary[patchI].meshPointMap();
+        oldPatchNMeshPoints[patchI] = boundary[patchI].meshPoints().size();
+    }
+
+    // Re-do the point zones
+
+    // Make a map of points to be removed from zones
+    labelHashSet removePointFromZone(2*ref.modifiedPoints().size());
+
+    forAll (mp, mpI)
+    {
+        if (mp[mpI].removeFromZone())
+        {
+            removePointFromZone.insert(mp[mpI].pointID());
+        }
+    }
+
+    labelListList newPointZoneAddr(pointZones.size());
+    labelList nPointsInZone(pointZones.size(), 0);
+
+    forAll (pointZones, pzI)
+    {
+        // Get the list of old points
+        const labelList& oldAddr = pointZones[pzI];
+
+        // Create new addressing, over-estimating the size
+        labelList& newAddr = newPointZoneAddr[pzI];
+        label& curNPoints = nPointsInZone[pzI];
+
+        newAddr.setSize
+        (
+            oldAddr.size()
+          + ref.modifiedPoints().size()
+          + ref.addedPoints().size()
+        );
+
+        // Add the original points that have not been removed or re-zoned
+        forAll (oldAddr, pointI)
+        {
+            if
+            (
+                !removedPoints.found(oldAddr[pointI])
+             && !removePointFromZone.found(oldAddr[pointI])
+            )
+            {
+                // The point is still alive. Add its renumbered label
+                newAddr[curNPoints] = renumberPoints[oldAddr[pointI]];
+                curNPoints++;
+            }
+        }
+    }
+
+    labelList debugPointsInZone(pointZones.size(), 0);
+
+    if (debug)
+    {
+        Pout<< "Added zone points:  untouched = " << nPointsInZone;
+
+        debugPointsInZone = nPointsInZone;
+    }
+
+    // Distribute modified zone points
+    forAll (mp, mpI)
+    {
+        if (mp[mpI].isInZone())
+        {
+            const label zoneID = mp[mpI].zoneID();
+
+            newPointZoneAddr[zoneID][nPointsInZone[zoneID]] =
+                renumberPoints[mp[mpI].pointID()];
+
+            nPointsInZone[zoneID]++;
+        }
+    }
+
+    if (debug)
+    {
+        Pout<< " modified = " << nPointsInZone - debugPointsInZone;
+    }
+
+    // Distribute added zone points
+    forAll (ap, apI)
+    {
+        if (ap[apI].isInZone())
+        {
+            const label zoneID = ap[apI].zoneID();
+
+            newPointZoneAddr[zoneID][nPointsInZone[zoneID]] =
+                renumberPoints[points.size() + apI];
+
+            nPointsInZone[zoneID]++;
+        }
+    }
+
+    if (debug)
+    {
+        Pout<< " added = " << nPointsInZone - debugPointsInZone
+            << ".  Points per zone = " << nPointsInZone << endl;
+    }
+
+    // Reset the sizes of the point zone addressing
+    forAll (newPointZoneAddr, pzI)
+    {
+        newPointZoneAddr[pzI].setSize(nPointsInZone[pzI]);
+    }
+
+    // Build the point zone renumbering
+    labelListList pzRenumber(pointZones.size());
+
+    forAll (pointZones, pzI)
+    {
+        pointZone& oldZone = pointZones[pzI];
+        const labelList& newZoneAddr = newPointZoneAddr[pzI];
+
+        labelList& curPzRnb = pzRenumber[pzI];
+        curPzRnb.setSize(newZoneAddr.size());
+
+        forAll (newZoneAddr, pointI)
+        {
+            if (newZoneAddr[pointI] < pointMap.size())
+            {
+                curPzRnb[pointI] =
+                    oldZone.whichPoint(pointMap[newZoneAddr[pointI]]);
+            }
+            else
+            {
+                curPzRnb[pointI] = -1;
+            }
+        }
+    }
+
+
+    // Re-do the face zones
+
+    // Make a map of faces to be removed from zones
+    labelHashSet removeFaceFromZone(2*ref.modifiedFaces().size());
+
+    forAll (mf, mfI)
+    {
+        if (mf[mfI].removeFromZone())
+        {
+            removeFaceFromZone.insert(mf[mfI].faceID());
+        }
+    }
+
+    labelListList newFaceZoneAddr(faceZones.size());
+    boolListList newFaceZoneFaceFlip(faceZones.size());
+    labelList nFacesInZone(faceZones.size(), 0);
+
+    forAll (faceZones, fzI)
+    {
+        // Get the list of old faces
+        const labelList& oldAddr = faceZones[fzI];
+        const boolList& oldFlip = faceZones[fzI].flipMap();
+
+        // Create new addressing, over-estimating the size
+        labelList& newAddr = newFaceZoneAddr[fzI];
+        boolList& newFlip = newFaceZoneFaceFlip[fzI];
+        label& curNFaces = nFacesInZone[fzI];
+
+        newAddr.setSize
+        (
+            oldAddr.size()
+          + ref.modifiedFaces().size()
+          + ref.addedFaces().size()
+        );
+
+        newFlip.setSize(newAddr.size());
+        newFlip = false;
+
+        // Reset the face flip to false.  None of the preserved faces will
+        // be flipped
+
+        // Add the original faces that have not been removed or re-zoned
+        forAll (oldAddr, faceI)
+        {
+            if
+            (
+                !removedFaces.found(oldAddr[faceI])
+             && !removeFaceFromZone.found(oldAddr[faceI])
+            )
+            {
+                // The face is still alive. Add its renumbered label
+                newAddr[curNFaces] = renumberFaces[oldAddr[faceI]];
+                newFlip[curNFaces] = oldFlip[faceI];
+
+                curNFaces++;
+            }
+        }
+    }
+
+    labelList debugFacesInZone(faceZones.size(), 0);
+
+    if (debug)
+    {
+        Pout<< "Added zone faces: untouched = " << nFacesInZone;
+
+        debugFacesInZone = nFacesInZone;
+    }
+
+    // Distribute modified zone faces
+    forAll (mf, mfI)
+    {
+        if (mf[mfI].isInZone())
+        {
+            const label zoneID = mf[mfI].zoneID();
+
+            // Grab the face index
+            newFaceZoneAddr[zoneID][nFacesInZone[zoneID]] =
+                renumberFaces[mf[mfI].faceID()];
+
+            // Grab the face flip
+            newFaceZoneFaceFlip[zoneID][nFacesInZone[zoneID]] =
+                mf[mfI].zoneFlip();
+
+            nFacesInZone[zoneID]++;
+        }
+    }
+
+    if (debug)
+    {
+        Pout<< " modified = " << nFacesInZone - debugFacesInZone;
+
+        debugFacesInZone = nFacesInZone;
+    }
+
+    // Distribute added zone faces
+    forAll (af, afI)
+    {
+        if (af[afI].isInZone())
+        {
+            const label zoneID = af[afI].zoneID();
+
+            // Grab the face index
+            newFaceZoneAddr[zoneID][nFacesInZone[zoneID]] =
+                renumberFaces[faces.size() + afI];
+
+            newFaceZoneFaceFlip[zoneID][nFacesInZone[zoneID]] =
+                af[afI].zoneFlip();
+
+            nFacesInZone[zoneID]++;
+        }
+    }
+
+    if (debug)
+    {
+        Pout<< " added = " << nFacesInZone - debugFacesInZone
+            << ".  Faces per zone = " << nFacesInZone << endl;
+    }
+
+    // Reset the sizes of the face zone addressing and face flip
+    forAll (newFaceZoneAddr, fzI)
+    {
+        newFaceZoneAddr[fzI].setSize(nFacesInZone[fzI]);
+        newFaceZoneFaceFlip[fzI].setSize(nFacesInZone[fzI]);
+    }
+
+    // Build the face zone renumbering
+    labelListList fzFaceRenumber(faceZones.size());
+
+    List<Map<label> > oldFaceZoneMeshPointMaps(faceZones.size());
+
+    forAll (faceZones, fzI)
+    {
+        faceZone& oldZone = faceZones[fzI];
+
+        // Point renumbering
+        // Point renumbering gives the old point location of every new
+        // point in the face zone.  If the point is new to the zone,
+        // the index will be -1.
+        // The problem is that the renumbering cannot be built at this stage
+        // as the order of points in the new face zone is not yet known:
+        // (the new zone needs to be set on the updated list of faces to
+        // be able to create the mapping).  Therefore, the old meshPoint maps
+        // will be copied here and will be used later to re-create the
+        // addressing.
+
+        // Copy old face zone mesh point maps
+        oldFaceZoneMeshPointMaps[fzI] = faceZones[fzI]().meshPointMap();
+
+        // Face renumbering
+        const labelList& newZoneAddr = newFaceZoneAddr[fzI];
+
+        labelList& curFzFaceRnb = fzFaceRenumber[fzI];
+
+        curFzFaceRnb.setSize(newZoneAddr.size());
+
+        forAll (newZoneAddr, faceI)
+        {
+            // HJ, change: can this index legally be -1?
+            // HJ, 9/Jan/2009
+            if (newZoneAddr[faceI] > -1 && newZoneAddr[faceI] < faceMap.size())
+            {
+                curFzFaceRnb[faceI] =
+                    oldZone.whichFace(faceMap[newZoneAddr[faceI]]);
+            }
+            else
+            {
+                curFzFaceRnb[faceI] = -1;
+            }
+        }
+    }
+
+
+    // Re-do the cell zones
+
+    // Make a map of cells to be removed from zones
+    labelHashSet removeCellFromZone(2*ref.modifiedCells().size());
+
+    forAll (mc, mcI)
+    {
+        if (mc[mcI].removeFromZone())
+        {
+            removeCellFromZone.insert(mc[mcI].cellID());
+        }
+    }
+
+    labelListList newCellZoneAddr(cellZones.size());
+    labelList nCellsInZone(cellZones.size(), 0);
+
+    forAll (cellZones, czI)
+    {
+        // Get the list of old cells
+        const labelList& oldAddr = cellZones[czI];
+
+        // Create new addressing, over-estimating the size
+        labelList& newAddr = newCellZoneAddr[czI];
+        label& curNCells = nCellsInZone[czI];
+
+        newAddr.setSize
+        (
+            oldAddr.size()
+          + ref.modifiedCells().size()
+          + ref.addedCells().size()
+        );
+
+        // Add the original cells that have not been removed or re-zoned
+        forAll (oldAddr, cellI)
+        {
+            if
+            (
+                !removedCells.found(oldAddr[cellI])
+             && !removeCellFromZone.found(oldAddr[cellI])
+            )
+            {
+                // The cell is still alive. Add its renumbered label
+                newAddr[curNCells] = renumberCells[oldAddr[cellI]];
+                curNCells++;
+            }
+        }
+    }
+
+    labelList debugCellsInZone(cellZones.size(), 0);
+
+    if (debug)
+    {
+        Pout<< "Added zone cells: untouched = " << nCellsInZone;
+
+        debugCellsInZone = nCellsInZone;
+    }
+
+    // Distribute modified zone cells
+    forAll (mc, mcI)
+    {
+        if (mc[mcI].isInZone())
+        {
+            const label zoneID = mc[mcI].zoneID();
+
+            newCellZoneAddr[zoneID][nCellsInZone[zoneID]] =
+                renumberCells[mc[mcI].cellID()];
+
+            nCellsInZone[zoneID]++;
+        }
+    }
+
+    if (debug)
+    {
+        Pout<< " modified = " << nCellsInZone - debugCellsInZone;
+
+        debugCellsInZone = nCellsInZone;
+    }
+
+    // Distribute added zone cells
+    forAll (ac, acI)
+    {
+        if (ac[acI].isInZone())
+        {
+            const label zoneID = ac[acI].zoneID();
+
+            newCellZoneAddr[zoneID][nCellsInZone[zoneID]] =
+                renumberCells[cells.size() + acI];
+
+            nCellsInZone[zoneID]++;
+        }
+    }
+
+    if (debug)
+    {
+        Pout<< " added = " << nCellsInZone - debugCellsInZone
+            << ".  Cells per zone = " << nCellsInZone << endl;
+    }
+
+    // Reset the sizes of the cell zone addressing
+    forAll (newCellZoneAddr, czI)
+    {
+        newCellZoneAddr[czI].setSize(nCellsInZone[czI]);
+    }
+
+    // Build the cell zone renumbering
+    labelListList czRenumber(cellZones.size());
+
+    forAll (cellZones, czI)
+    {
+        cellZone& oldZone = cellZones[czI];
+        const labelList& newZoneAddr = newCellZoneAddr[czI];
+
+        labelList& curCzRnb = czRenumber[czI];
+
+        curCzRnb.setSize(newZoneAddr.size());
+
+        forAll (newZoneAddr, cellI)
+        {
+            if (newZoneAddr[cellI] < cellMap.size())
+            {
+                curCzRnb[cellI] =
+                    oldZone.whichCell(cellMap[newZoneAddr[cellI]]);
+            }
+            else
+            {
+                curCzRnb[cellI] = -1;
+            }
+        }
+    }
+
+
+    // Calculate (all)owner and (all)neighbour and reset the mesh.
+    {
+        labelList allOwn(newFaces.size(), -1);
+        labelList allNei(newFaces.size(), -1);
+
+        boolList markedFaces(newFaces.size(), false);
+
+        forAll (newCells, cellI)
+        {
+            // get reference to face labels for current cell
+            const cell& cellfaces = newCells[cellI];
+
+            forAll (cellfaces, faceI)
+            {
+                if (!markedFaces[cellfaces[faceI]])
+                {
+                    // First visit: owner
+                    allOwn[cellfaces[faceI]] = cellI;
+
+                    markedFaces[cellfaces[faceI]] = true;
+                }
+                else
+                {
+                    // Second visit: neighbour
+                    allNei[cellfaces[faceI]] = cellI;
+                }
+            }
+        }
+
+        // Count the number of real faces.
+        // Note: if there are unused faces in the mesh, they should be
+        // clustered at the end of the list
+
+        label nUsedFaces = allOwn.size();
+
+        forAll (allOwn, faceI)
+        {
+            if (allOwn[faceI] < 0)
+            {
+                nUsedFaces = faceI;
+                break;
+            }
+        }
+
+        // Truncate owner array to the number of used faces
+        // HJ, 23/Oct/2008
+        allOwn.setSize(nUsedFaces);
+
+        // Count internal faces
+        label nInternalFaces = allOwn.size();
+
+        forAll (allNei, faceI)
+        {
+            if (allNei[faceI] < 0)
+            {
+                nInternalFaces = faceI;
+                break;
+            }
+        }
+
+        // Truncate neighbour array to the size of internal faces
+        // HJ, 23/Oct/2008
+        allNei.setSize(nInternalFaces);
+
+        if (debug)
+        {
+            Pout<< " nOldPoints: " << points.size()
+                << " nPoints: " << newPointsZeroVol.size()
+                << " nUsedFaces: " << nUsedFaces
+                << " newFaces: " << newFaces.size()
+                << " allOwn: " << allOwn.size()
+                << " allNei: " << allNei.size()
+                << " patchSizes: " << patchSizes
+                << " patchStarts: " << patchStarts << endl;
+        }
+
+        // HJ, HACKED
+        mesh.resetPrimitives
+        (
+            autoPtr<pointField>(&newPointsZeroVol),
+            autoPtr<faceList>(&newFaces),
+            autoPtr<labelList>(&allOwn),
+            autoPtr<labelList>(&allNei),
+            patchSizes,
+            patchStarts,
+            false         // The mesh is not complete: no parallel comms
+                          // HJ, 27/Nov/2009
+        );
+    }
+
+
+    // Reset the zones
+
+    forAll (pointZones, pzI)
+    {
+        pointZones[pzI] = newPointZoneAddr[pzI];
+    }
+    // // OpenFOAM does not have an updateMesh mechanism.  HJ, 1/Aug/2023
+    // pointZones.updateMesh();
+
+    forAll (faceZones, fzI)
+    {
+        faceZones[fzI].resetAddressing
+        (
+            newFaceZoneAddr[fzI],
+            newFaceZoneFaceFlip[fzI]
+        );
+    }
+    // OpenFOAM does not have an updateMesh mechanism.  HJ, 1/Aug/2023
+    // faceZones.updateMesh();
+
+    forAll (cellZones, czI)
+    {
+        cellZones[czI] = newCellZoneAddr[czI];
+    }
+    // OpenFOAM does not have an updateMesh mechanism.  HJ, 1/Aug/2023
+    // cellZones.updateMesh();
+
+
+    // Create the patch mesh point renumbering
+
+    labelListList patchPointRenumber(boundary.size());
+
+    forAll (boundary, patchI)
+    {
+        const labelList& newPatchMeshPoints = boundary[patchI].meshPoints();
+
+        const Map<label>& oldZoneMeshPointMap = oldPatchMeshPointMaps[patchI];
+        const label oldSize = oldPatchNMeshPoints[patchI];
+
+        labelList& curPatchPointRnb = patchPointRenumber[patchI];
+
+        curPatchPointRnb.setSize(newPatchMeshPoints.size());
+
+        forAll (newPatchMeshPoints, pointI)
+        {
+            if (newPatchMeshPoints[pointI] < oldSize)
+            {
+                Map<label>::const_iterator ozmpmIter =
+                    oldZoneMeshPointMap.find
+                    (
+                        pointMap[newPatchMeshPoints[pointI]]
+                    );
+
+                if (ozmpmIter != oldZoneMeshPointMap.end())
+                {
+                    curPatchPointRnb[pointI] = ozmpmIter();
+                }
+                else
+                {
+                    curPatchPointRnb[pointI] = -1;
+                }
+            }
+            else
+            {
+                curPatchPointRnb[pointI] = -1;
+            }
+        }
+    }
+
+    // Create the face zone mesh point renumbering
+
+    labelListList fzPointRenumber(faceZones.size());
+
+    forAll (faceZones, fzI)
+    {
+        const labelList& newZoneMeshPoints = faceZones[fzI]().meshPoints();
+
+        const Map<label>& oldZoneMeshPointMap = oldFaceZoneMeshPointMaps[fzI];
+
+        labelList& curFzPointRnb = fzPointRenumber[fzI];
+
+        curFzPointRnb.setSize(newZoneMeshPoints.size());
+
+        forAll (newZoneMeshPoints, pointI)
+        {
+            if (newZoneMeshPoints[pointI] < pointMap.size())
+            {
+                Map<label>::const_iterator ozmpmIter =
+                    oldZoneMeshPointMap.find
+                    (
+                        pointMap[newZoneMeshPoints[pointI]]
+                    );
+
+                if (ozmpmIter != oldZoneMeshPointMap.end())
+                {
+                    curFzPointRnb[pointI] = ozmpmIter();
+                }
+                else
+                {
+                    curFzPointRnb[pointI] = -1;
+                }
+            }
+            else
+            {
+                curFzPointRnb[pointI] = -1;
+            }
+        }
+    }
+
+
+    if (debug)
+    {
+        Pout<< "polyTopoChanger::changeMesh" << nl
+            << '(' << nl
+            << "    const batchPolyTopoChange& ref" << nl
+            << ") : completed topological change." << nl << endl;
+    }
+
+    // Comatibility with direct poly topo change:
+    // - pointsFromPoints
+    // - facesFromFaces
+    // - cellsFromCells
+    // All currently inactive in a direct map and not used
+    // HJ, 1/Sep/2007
+    List<objectMap> pointsFromPoints;
+    List<objectMap> facesFromFaces;
+    List<objectMap> cellsFromCells;
+
+    // Patch reset map is currently dummy: does not support change in number
+    // of boundary patches
+    // HJ, 23/Apr/2018
+    // boolList resetPatchFlag(boundary.size(), false);
+
+    // Dummy old cell volumes
+    autoPtr<scalarField> oldCellVolumes;
+    
+    autoPtr<mapPolyMesh> topoChangeMap
+    (
+        new mapPolyMesh
+        (
+            mesh,
+            nOldPoints,
+            nOldFaces,
+            nOldCells,
+
+            pointMap,
+            pointsFromPoints,
+
+            faceMap,
+            faceFromPoint,
+            faceFromEdge,
+            facesFromFaces,
+
+            cellMap,
+            cellFromPoint,
+            cellFromEdge,
+            cellFromFace,
+            cellsFromCells,
+
+            renumberPoints,
+            renumberFaces,
+            renumberCells,
+
+            flipFaceFlux,
+
+            patchPointRenumber,
+
+            pzRenumber,
+            fzPointRenumber,
+            fzFaceRenumber,
+            czRenumber,
+
+            // resetPatchFlag,
+
+            newPointsMotion,
+            oldPatchStarts,
+            oldPatchNMeshPoints,
+            oldCellVolumes,
+            true                   // Re-use storage
+        )
+    );
+
+    return topoChangeMap;
+}
+
+
+Foam::autoPtr<Foam::mapPolyMesh> Foam::polyTopoChanger::changeMesh()
+{
+    bool localUpdate = changeTopology();
+
+    bool globalUpdate = returnReduce(localUpdate, orOp<bool>());
+
+    if (debug)
+    {
+        Pout<< "Local mesh update: " << localUpdate
+            << " global update: " << globalUpdate << endl;
+    }
+
+    // if (localUpdate)
+    {
+        autoPtr<mapPolyMesh> topoChangeMap = changeMesh
+        (
+            mesh_,
+            topoChangeRequest()()
+        );
+
+        // Mesh data needs to be updated before the update of polyMeshModifiers
+        // because polyMeshModifiers might need all the new polyMesh data (see
+        // below for further comments). VV, 19/Feb/2019
+        mesh_.updateMesh(topoChangeMap());
+
+        // Bugfix: call to polyTopoChanger::update must happen after
+        // polyMesh::updateMesh where all the relevant mesh bits for parallel
+        // comms are updated. First noticed when the syncying of pointLevel in
+        // refinement::updateMesh was not syncying properly. VV, 19/Feb/2019
+        update(topoChangeMap());
+
+        // Increment the morph index
+        morphIndex_++;
+
+        // Mark the mesh as changing
+        mesh_.topoChanging(true);
+
+        return topoChangeMap;
+    }
+    // else
+    // {
+    //     // If other processors perform topology change, communications
+    //     // between domains need to be executed.  HJ, 27/Nov/2009
+    //     if (globalUpdate)
+    //     {
+    //         // Sync couple patch update
+    //         syncCoupledPatches();
+
+    //         // Sync mesh update
+    //         mesh_.syncUpdateMesh();
+
+    //         // Mark the mesh as changing
+    //         mesh_.changing(true);
+    //     }
+
+    //     return autoPtr<mapPolyMesh>(new mapPolyMesh(mesh_));
+    // }
+}
+
+
+// ************************************************************************* //
diff --git a/src/dynamicMesh/refinement/polyhedralRefinement/polyhedralRefinement.C b/src/dynamicMesh/refinement/polyhedralRefinement/polyhedralRefinement.C
new file mode 100644
index 0000000000000000000000000000000000000000..65021a8cbb518116e374e7eb6ac01905e0ad7659
--- /dev/null
+++ b/src/dynamicMesh/refinement/polyhedralRefinement/polyhedralRefinement.C
@@ -0,0 +1,2442 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | foam-extend: Open Source CFD
+   \\    /   O peration     | Version:     5.0
+    \\  /    A nd           | Web:         http://www.foam-extend.org
+     \\/     M anipulation  | For copyright notice see file Copyright
+-------------------------------------------------------------------------------
+License
+    This file is part of foam-extend.
+
+    foam-extend 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.
+
+    foam-extend 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 foam-extend.  If not, see <http://www.gnu.org/licenses/>.
+
+Author
+    Vuko Vukcevic, Wikki Ltd.  All rights reserved.
+
+\*---------------------------------------------------------------------------*/
+
+#include "polyhedralRefinement.H"
+#include "cellSet.H"
+#include "faceSet.H"
+#include "pointSet.H"
+#include "syncTools.H"
+#include "meshTools.H"
+#include "OFstream.H"
+#include "Time.H"
+#include "addToRunTimeSelectionTable.H"
+
+// * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * //
+
+namespace Foam
+{
+    defineTypeNameAndDebug(polyhedralRefinement, 0);
+    addToRunTimeSelectionTable
+    (
+        polyMeshModifier,
+        polyhedralRefinement,
+        dictionary
+    );
+}
+
+
+// * * * * * * * * * * * * * Private Member Functions  * * * * * * * * * * * //
+
+Foam::label Foam::polyhedralRefinement::getAnchorLevel
+(
+    const label faceI
+) const
+{
+    const face& f = mesh_.faces()[faceI];
+
+    if (f.size() <= 3)
+    {
+        return pointLevel_[f[findMaxLevel(f)]];
+    }
+    else
+    {
+        const label& ownLevel = cellLevel_[mesh_.faceOwner()[faceI]];
+
+        if (countAnchors(f, ownLevel) >= 3)
+        {
+            return ownLevel;
+        }
+        else if (countAnchors(f, ownLevel + 1) >= 3)
+        {
+            return ownLevel + 1;
+        }
+        else
+        {
+            return -1;
+        }
+    }
+}
+
+
+void Foam::polyhedralRefinement::createInternalFaces
+(
+    const labelListList& cellAnchorPoints,
+    const labelListList& cellAddedCells,
+    const labelList& cellMidPoint,
+    const labelList& faceMidPoint,
+    const labelList& faceAnchorLevel,
+    const labelList& edgeMidPoint,
+    const label cellI,
+
+    batchPolyTopoChange& ref
+) const
+{
+    // Find in every face the cellLevel + 1 points (from edge subdivision)
+    // and the anchor points
+
+    // Get current cell and its level
+    const cell& curCell = mesh_.cells()[cellI];
+    const label& cLevel = cellLevel_[cellI];
+
+    // Get mesh faces and face edges
+    const faceList& meshFaces = mesh_.faces();
+    const labelListList& meshFaceEdges = mesh_.faceEdges();
+
+    // Get mesh cell points
+    const labelListList& meshCellPoints = mesh_.cellPoints();
+
+    // Map from edge mid to anchor points
+    Map<edge> midPointToAnchors(24);
+    // Map from edge mid to face mids
+    Map<edge> midPointToFaceMids(24);
+
+    // Running count of number of internal faces added so far
+    label nFacesAdded = 0;
+
+    // Loop through faces of the cell
+    forAll(curCell, i)
+    {
+        // Get face index
+        const label& faceI = curCell[i];
+
+        // Get current face and its edges
+        const face& f = meshFaces[faceI];
+        const labelList& fEdges = meshFaceEdges[faceI];
+
+        // We are on the cellI side of face f. The face will have 1 or n
+        // cLevel points (where n is the number of points/edges of a face)
+        // and lots of higher numbered ones
+
+        // Index of face mid point
+        label faceMidPointI = -1;
+
+        // Get number of anchors for the face
+        const label nAnchors = countAnchors(f, cLevel);
+
+        if (nAnchors == 1)
+        {
+            // Only one anchor point. So the other side of the face has already
+            // been split using cLevel + 1 and cLevel + 2 points
+
+            // Find the one anchor
+            label anchorFp = -1;
+
+            // Loop through face points
+            forAll(f, fp)
+            {
+                if (pointLevel_[f[fp]] <= cLevel)
+                {
+                    // Point level is smaller than cLevel + 1, this is the
+                    // anchor point
+                    anchorFp = fp;
+                    break;
+                }
+            }
+
+            // Now the face mid point is the second cLevel + 1 point
+            label edgeMid = findLevel(f, f.fcIndex(anchorFp), true, cLevel + 1);
+            label faceMid = findLevel(f, f.fcIndex(edgeMid), true, cLevel + 1);
+
+            // Set face mid point index
+            faceMidPointI = f[faceMid];
+        }
+        else
+        {
+            // There is no face middle yet but the face will be split. Set face
+            // mid point index
+            faceMidPointI = faceMidPoint[faceI];
+        }
+
+
+        // Now loop over all the anchors (might be just one) and store
+        // the edge mids connected to it. storeMidPointInfo will collect
+        // all the info and combine it all
+        forAll(f, fp0)
+        {
+            // Get point index
+            const label& point0 = f[fp0];
+
+            if (pointLevel_[point0] <= cLevel)
+            {
+                // This is anchor point
+
+                // Walk forward to cLevel + 1 or edgeMidPoint of this level
+                label edgeMidPointI = -1;
+
+                const label fp1 = f.fcIndex(fp0);
+
+                if (pointLevel_[f[fp1]] <= cLevel)
+                {
+                    // Another anchor: edge will be split
+                    const label& edgeI = fEdges[fp0];
+
+                    edgeMidPointI = edgeMidPoint[edgeI];
+
+                    // Sanity check
+                    if (edgeMidPointI == -1)
+                    {
+                        const labelList& cPoints = meshCellPoints[cellI];
+
+                        FatalErrorInFunction
+                            << "cell:" << cellI << " cLevel:" << cLevel
+                            << " cell points:" << cPoints
+                            << " pointLevel:"
+                            << IndirectList<label>(pointLevel_, cPoints)()
+                            << " face:" << faceI
+                            << " f:" << f
+                            << " pointLevel:"
+                            << IndirectList<label>(pointLevel_, f)()
+                            << " faceAnchorLevel:" << faceAnchorLevel[faceI]
+                            << " faceMidPoint:" << faceMidPoint[faceI]
+                            << " faceMidPointI:" << faceMidPointI
+                            << " fp:" << fp0
+                            << abort(FatalError);
+                    }
+                }
+                else
+                {
+                    // Search forward in face to clevel + 1
+                    const label edgeMid = findLevel(f, fp1, true, cLevel + 1);
+
+                    edgeMidPointI = f[edgeMid];
+                }
+
+                label newFaceI = storeMidPointInfo
+                (
+                    cellAnchorPoints,
+                    cellAddedCells,
+                    cellMidPoint,
+                    edgeMidPoint,
+
+                    cellI,
+                    faceI,
+                    true,                   // mid point after anchor
+                    edgeMidPointI,          // edgemid
+                    point0,                 // anchor
+                    faceMidPointI,
+
+                    midPointToAnchors,
+                    midPointToFaceMids,
+                    ref
+                );
+
+                if (newFaceI != -1)
+                {
+                    ++nFacesAdded;
+                }
+
+
+                // Now walk backward
+
+                label fpMin1 = f.rcIndex(fp0);
+
+                if (pointLevel_[f[fpMin1]] <= cLevel)
+                {
+                    // Another anchor: edge will be split
+                    const label& edgeI = fEdges[fpMin1];
+
+                    edgeMidPointI = edgeMidPoint[edgeI];
+
+                    // Sanity check
+                    if (edgeMidPointI == -1)
+                    {
+                        const labelList& cPoints = meshCellPoints[cellI];
+
+                        FatalErrorInFunction
+                            << "cell:" << cellI << " cLevel:" << cLevel
+                            << " cell points:" << cPoints
+                            << " pointLevel:"
+                            << IndirectList<label>(pointLevel_, cPoints)()
+                            << " face:" << faceI
+                            << " f:" << f
+                            << " pointLevel:"
+                            << IndirectList<label>(pointLevel_, f)()
+                            << " faceAnchorLevel:" << faceAnchorLevel[faceI]
+                            << " faceMidPoint:" << faceMidPoint[faceI]
+                            << " faceMidPointI:" << faceMidPointI
+                            << " fp:" << fp0
+                            << abort(FatalError);
+                    }
+                }
+                else
+                {
+                    // Search back in face to clevel + 1
+                    const label edgeMid = findLevel(f, fpMin1, false, cLevel + 1);
+
+                    edgeMidPointI = f[edgeMid];
+                }
+
+                newFaceI = storeMidPointInfo
+                (
+                    cellAnchorPoints,
+                    cellAddedCells,
+                    cellMidPoint,
+                    edgeMidPoint,
+
+                    cellI,
+                    faceI,
+                    false,                  // mid point before anchor
+                    edgeMidPointI,          // edgemid
+                    point0,                 // anchor
+                    faceMidPointI,
+
+                    midPointToAnchors,
+                    midPointToFaceMids,
+                    ref
+                );
+
+                if (newFaceI != -1)
+                {
+                    ++nFacesAdded;
+                }
+            } // End for this anchor point
+        } // End for all face points
+    } // End for all cell faces
+}
+
+
+Foam::label Foam::polyhedralRefinement::getAnchorCell
+(
+    const labelListList& cellAnchorPoints,
+    const labelListList& cellAddedCells,
+    const label cellI,
+    const label faceI,
+    const label pointI
+) const
+{
+    // Check whether pointI is an anchor of cellI. If it is not, check whether
+    // any other point on the face is an anchor for the cell
+
+    if (cellAnchorPoints[cellI].size() > 0)
+    {
+        const label index = cellAnchorPoints[cellI].find(pointI);
+
+        if (index != -1)
+        {
+            return cellAddedCells[cellI][index];
+        }
+
+
+        // pointI is not an anchor for the cell. Maybe we already refined the
+        // face so check all the face vertices
+        const face& f = mesh_.faces()[faceI];
+
+        forAll(f, fp)
+        {
+            const label index = cellAnchorPoints[cellI].find(f[fp]);
+
+            if (index != -1)
+            {
+                return cellAddedCells[cellI][index];
+            }
+        }
+
+        // Problem: the point does not seem to be an anchor for cell
+
+        // Pick up points of the cell
+        const labelList& cPoints = mesh_.cellPoints()[cellI];
+
+        Perr<< "cell: " << cellI << ", points: " << endl;
+        forAll(cPoints, i)
+        {
+            const label pointI = cPoints[i];
+
+            Perr<< "    " << pointI << " coord: " << mesh_.points()[pointI]
+                << nl;
+        }
+
+        Perr<< "cell: " << cellI << " anchorPoints: " << cellAnchorPoints[cellI]
+            << endl;
+
+        FatalErrorInFunction
+            << "Could not find point " << pointI
+            << " in the anchorPoints for cell " << cellI << endl
+            << "Does your original mesh obey the 2:1 constraint and"
+            << " did you use consistentRefinement to make your cells to refine"
+            << " obey this constraint as well?"
+            << abort(FatalError);
+
+        return -1;
+    }
+    else
+    {
+        return cellI;
+    }
+}
+
+
+void Foam::polyhedralRefinement::setNewFaceNeighbours
+(
+    const labelListList& cellAnchorPoints,
+    const labelListList& cellAddedCells,
+    const label faceI,
+    const label pointI,
+
+    label& own,
+    label& nei
+) const
+{
+    // Get anchor cell for this anchor point on owner side
+    own = getAnchorCell
+    (
+        cellAnchorPoints,
+        cellAddedCells,
+        mesh_.faceOwner()[faceI],
+        faceI,
+        pointI
+    );
+
+    if (mesh_.isInternalFace(faceI))
+    {
+        // Get anchor cell for this anchor point on neighbour side
+        nei = getAnchorCell
+        (
+            cellAnchorPoints,
+            cellAddedCells,
+            mesh_.faceNeighbour()[faceI],
+            faceI,
+            pointI
+        );
+    }
+    else
+    {
+        // Boundary face: set neighbour to -1
+        nei = -1;
+    }
+}
+
+
+Foam::label Foam::polyhedralRefinement::findLevel
+(
+    const face& f,
+    const label startFp,
+    const bool searchForward,
+    const label wantedLevel
+) const
+{
+    label fp = startFp;
+
+    forAll(f, i)
+    {
+        label pointI = f[fp];
+
+        if (pointLevel_[pointI] < wantedLevel)
+        {
+            FatalErrorInFunction
+                << "face:" << f
+                << " level:" << IndirectList<label>(pointLevel_, f)()
+                << " startFp:" << startFp
+                << " wantedLevel:" << wantedLevel
+                << abort(FatalError);
+        }
+        else if (pointLevel_[pointI] == wantedLevel)
+        {
+            return fp;
+        }
+
+        if (searchForward)
+        {
+            fp = f.fcIndex(fp);
+        }
+        else
+        {
+            fp = f.rcIndex(fp);
+        }
+    }
+
+    FatalErrorInFunction
+        << "face:" << f
+        << " level:" << IndirectList<label>(pointLevel_, f)()
+        << " startFp:" << startFp
+        << " wantedLevel:" << wantedLevel
+        << abort(FatalError);
+
+    return -1;
+}
+
+
+Foam::label Foam::polyhedralRefinement::storeMidPointInfo
+(
+    const labelListList& cellAnchorPoints,
+    const labelListList& cellAddedCells,
+    const labelList& cellMidPoint,
+    const labelList& edgeMidPoint,
+    const label cellI,
+    const label faceI,
+    const bool faceOrder,
+    const label edgeMidPointI,
+    const label anchorPointI,
+    const label faceMidPointI,
+
+    Map<edge>& midPointToAnchors,
+    Map<edge>& midPointToFaceMids,
+    batchPolyTopoChange& ref
+) const
+{
+    // A single internal face is added per edge inbetween anchor points,
+    // i.e. one face per midPoint between anchor points. The information is
+    // stored on the midPoint and if we have enough information (finished
+    // collecting two anchors and two face mid points), we add the face.
+    // Note that this member function can get called anywhere from
+    // two times (two unrefined faces) to four times (two refined faces) so
+    // the first call that adds the information creates the face
+
+
+    // See if need to store anchors
+    bool changed = false;
+    bool haveTwoAnchors = false;
+
+    Map<edge>::iterator edgeMidFnd = midPointToAnchors.find(edgeMidPointI);
+
+    if (edgeMidFnd == midPointToAnchors.end())
+    {
+        midPointToAnchors.insert(edgeMidPointI, edge(anchorPointI, -1));
+    }
+    else
+    {
+        edge& e = edgeMidFnd();
+
+        if (anchorPointI != e[0])
+        {
+            if (e[1] == -1)
+            {
+                e[1] = anchorPointI;
+                changed = true;
+            }
+        }
+
+        if (e[0] != -1 && e[1] != -1)
+        {
+            haveTwoAnchors = true;
+        }
+    }
+
+    bool haveTwoFaceMids = false;
+
+    Map<edge>::iterator faceMidFnd = midPointToFaceMids.find(edgeMidPointI);
+
+    if (faceMidFnd == midPointToFaceMids.end())
+    {
+        midPointToFaceMids.insert(edgeMidPointI, edge(faceMidPointI, -1));
+    }
+    else
+    {
+        edge& e = faceMidFnd();
+
+        if (faceMidPointI != e[0])
+        {
+            if (e[1] == -1)
+            {
+                e[1] = faceMidPointI;
+                changed = true;
+            }
+        }
+
+        if (e[0] != -1 && e[1] != -1)
+        {
+            haveTwoFaceMids = true;
+        }
+    }
+
+    // Check if this call of storeMidPointInfo is the one that completed all
+    // the nessecary information
+
+    if (changed && haveTwoAnchors && haveTwoFaceMids)
+    {
+        const edge& anchors = midPointToAnchors[edgeMidPointI];
+        const edge& faceMids = midPointToFaceMids[edgeMidPointI];
+
+        label otherFaceMidPointI = faceMids.otherVertex(faceMidPointI);
+
+        // Create face consistent with anchorI being the owner.
+        // Note that the edges between the edge mid point and the face mids
+        // might be marked for splitting. Note that these edge splits cannot
+        // be between cell mid and face mids
+
+        DynamicList<label> newFaceVerts(4);
+        if (faceOrder == (mesh_.faceOwner()[faceI] == cellI))
+        {
+            newFaceVerts.append(faceMidPointI);
+
+            // Check and insert edge split if any
+            insertEdgeSplit
+            (
+                edgeMidPoint,
+                faceMidPointI,  // edge between faceMid and
+                edgeMidPointI,  // edgeMid
+                newFaceVerts
+            );
+
+            newFaceVerts.append(edgeMidPointI);
+
+            insertEdgeSplit
+            (
+                edgeMidPoint,
+                edgeMidPointI,
+                otherFaceMidPointI,
+                newFaceVerts
+            );
+
+            newFaceVerts.append(otherFaceMidPointI);
+            newFaceVerts.append(cellMidPoint[cellI]);
+        }
+        else
+        {
+            newFaceVerts.append(otherFaceMidPointI);
+
+            insertEdgeSplit
+            (
+                edgeMidPoint,
+                otherFaceMidPointI,
+                edgeMidPointI,
+                newFaceVerts
+            );
+
+            newFaceVerts.append(edgeMidPointI);
+
+            insertEdgeSplit
+            (
+                edgeMidPoint,
+                edgeMidPointI,
+                faceMidPointI,
+                newFaceVerts
+            );
+
+            newFaceVerts.append(faceMidPointI);
+            newFaceVerts.append(cellMidPoint[cellI]);
+        }
+
+        face newFace;
+        newFace.transfer(newFaceVerts.shrink());
+        newFaceVerts.clear();
+
+        label anchorCell0 = getAnchorCell
+        (
+            cellAnchorPoints,
+            cellAddedCells,
+            cellI,
+            faceI,
+            anchorPointI
+        );
+        label anchorCell1 = getAnchorCell
+        (
+            cellAnchorPoints,
+            cellAddedCells,
+            cellI,
+            faceI,
+            anchors.otherVertex(anchorPointI)
+        );
+
+        // Get mesh points
+        const pointField& meshPoints = mesh_.points();
+
+        label own, nei;
+        point ownPt, neiPt;
+
+        if (anchorCell0 < anchorCell1)
+        {
+            own = anchorCell0;
+            nei = anchorCell1;
+
+            ownPt = meshPoints[anchorPointI];
+            neiPt = meshPoints[anchors.otherVertex(anchorPointI)];
+
+        }
+        else
+        {
+            own = anchorCell1;
+            nei = anchorCell0;
+            newFace = newFace.reverseFace();
+
+            ownPt = meshPoints[anchors.otherVertex(anchorPointI)];
+            neiPt = meshPoints[anchorPointI];
+        }
+
+        if (debug)
+        {
+            point ownPt, neiPt;
+
+            if (anchorCell0 < anchorCell1)
+            {
+                ownPt = meshPoints[anchorPointI];
+                neiPt = meshPoints[anchors.otherVertex(anchorPointI)];
+            }
+            else
+            {
+                ownPt = meshPoints[anchors.otherVertex(anchorPointI)];
+                neiPt = meshPoints[anchorPointI];
+            }
+
+            checkInternalOrientation
+            (
+                ref,
+                cellI,
+                faceI,
+                ownPt,
+                neiPt,
+                newFace
+            );
+        }
+
+        return addInternalFace
+        (
+            ref,
+            faceI,
+            anchorPointI,
+            newFace,
+            own,
+            nei
+        );
+    }
+    else
+    {
+        return -1;
+    }
+}
+
+
+void Foam::polyhedralRefinement::insertEdgeSplit
+(
+    const labelList& edgeMidPoint,
+    const label p0,
+    const label p1,
+    DynamicList<label>& verts
+) const
+{
+    // Get number of points
+    const label nPoints = mesh_.nPoints();
+
+    if (p0 < nPoints && p1 < nPoints)
+    {
+        label edgeI = meshTools::findEdge(mesh_, p0, p1);
+
+        if (edgeI != -1 && edgeMidPoint[edgeI] != -1)
+        {
+            verts.append(edgeMidPoint[edgeI]);
+        }
+    }
+}
+
+
+// * * * * * * * * * * * * * Protected Member Functions  * * * * * * * * * * //
+
+void Foam::polyhedralRefinement::setRefinementInstruction
+(
+    batchPolyTopoChange& ref
+) const
+{
+    // Note: assumes that cellsToRefine_ are set prior to the function call
+
+    // Reset refinementLevelIndicator field. Note: the list is cleared in
+    // updateMesh member function after updating cell and point levels
+    if (refinementLevelIndicator_.empty())
+    {
+        // List has been resetted correctly, initialise it for this iteration
+        refinementLevelIndicator_.setSize(mesh_.nCells(), UNCHANGED);
+    }
+    else
+    {
+        // List has not been reset correctly, issue an error
+        FatalErrorInFunction
+            << "Refinement level indicator list has not been"
+            << " resetted properly." << nl
+            << "Either the call to updateMesh() after performing"
+            << " refinement has not been made or the call to"
+            << " setRefinementInstruction(...) and"
+            << " setUnrefinementInstruction(...) has not been made in"
+            << " correct order." << nl
+            << "Make sure to set refinement, set unrefinement and call"
+            << " updateMesh after performing the topo change."
+            << abort(FatalError);
+    }
+
+    if (debug)
+    {
+        Pout<< "polyhedralRefinement::setRefinementInstruction(...)" << nl
+            << "Allocating " << cellsToRefine_.size() << " cell midpoints."
+            << endl;
+    }
+
+
+    // PART 1: Mark cells for refinement and add points at their cell centres
+
+    // Get necessary mesh data
+    const faceList& meshFaces = mesh_.faces();
+    const cellList& meshCells = mesh_.cells();
+    const vectorField& meshCellCentres = mesh_.cellCentres();
+
+    // Mid point for refined cell (points at cell centres):
+    // Not refined = -1
+    // Shall be refined > -1 (label of added mid point)
+    labelList cellMidPoint(mesh_.nCells(), -1);
+
+    // Loop through cells to refine
+    forAll(cellsToRefine_, i)
+    {
+        // Get cell idnex
+        const label& cellI = cellsToRefine_[i];
+
+        cellMidPoint[cellI] = ref.setAction
+        (
+            polyAddPoint
+            (
+                meshCellCentres[cellI], // Point to add (cell centre)
+                -1,                     // Appended point: no master ID
+                -1,                     // Zone for point
+                true                    // Supports a cell
+            )
+        );
+    }
+
+    // Write out split cells as a cell set for debug
+    if (debug)
+    {
+        // Note: cellSet is actually a hash table of labels
+        cellSet splitCells(mesh_, "splitCells", cellsToRefine_.size());
+
+        forAll(cellMidPoint, cellI)
+        {
+            if (cellMidPoint[cellI] > -1)
+            {
+                // Cell is marked for refinement, insert into cellSet
+                splitCells.insert(cellI);
+            }
+        }
+
+        Pout<< "polyhedralRefinement::setRefinementInstruction(...)" << nl
+            << "Writing " << splitCells.size()
+            << " cells to split to cellSet " << splitCells.objectPath()
+            << endl;
+
+        splitCells.write();
+    }
+
+
+    // PART 2: Mark edges for refinement and add points to edge centres
+
+    if (debug)
+    {
+        Pout<< "polyhedralRefinement::setRefinementInstruction(...)" << nl
+            << "Allocating edge midpoints."
+            << endl;
+    }
+
+    // Refined edges are defined by having both their point levels lower than
+    // the cell level, i.e. if any cell that gets split uses this edge, the edge
+    // needs to be split as well
+
+    // Get necessary mesh data
+    const labelListList& meshCellEdges = mesh_.cellEdges();
+    const edgeList& meshEdges = mesh_.edges();
+
+    // Mid points for refined edge:
+    // No need to split edge = -1
+    // Label of introduced mid point > -1
+    labelList edgeMidPoint(mesh_.nEdges(), -1);
+
+    // Note: Loop over all cells instead of all edges
+    forAll(cellMidPoint, cellI)
+    {
+        // Point is marked for refinement, proceed to look at the edges
+        if (cellMidPoint[cellI] > -1)
+        {
+            // Get edges of this cell
+            const labelList& cEdges = meshCellEdges[cellI];
+
+            forAll(cEdges, i)
+            {
+                // Get edge index and edge
+                const label& edgeI = cEdges[i];
+                const edge& e = meshEdges[edgeI];
+
+                if
+                (
+                    pointLevel_[e[0]] <= cellLevel_[cellI]
+                 && pointLevel_[e[1]] <= cellLevel_[cellI]
+                )
+                {
+                    // Point levels of both edge points are <= cell level, mark
+                    // the edge for splitting
+                    edgeMidPoint[edgeI] = 12345;
+                }
+            }
+        }
+    }
+
+    // Synchronize edgeMidPoint across coupled patches. Note: use max so that
+    // any split takes precedence.
+    syncTools::syncEdgeList
+    (
+        mesh_,
+        edgeMidPoint,
+        maxEqOp<label>(),
+        labelMin
+    );
+
+    // Introduce edge points
+
+    // Get necessary mesh data
+    const pointField& meshPoints = mesh_.points();
+
+    // Memory management
+    {
+        // Phase 1: calculate midpoints and sync. This is necessary if we don't
+        // use binary format for writing and we slowly get differences.
+
+        // Allocate storage for edge points
+        pointField edgeMids(mesh_.nEdges(), point(-GREAT, -GREAT, -GREAT));
+
+        forAll(edgeMidPoint, edgeI)
+        {
+            if (edgeMidPoint[edgeI] > -1)
+            {
+                // Edge marked to be split. Get edge centre
+                edgeMids[edgeI] = meshEdges[edgeI].centre(meshPoints);
+            }
+        }
+
+        // Sync across processor boundaries
+        syncTools::syncEdgeList
+        (
+            mesh_,
+            edgeMids,
+            maxEqOp<vector>(),
+            point(-GREAT, -GREAT, -GREAT)
+        );
+
+        // Phase 2: introduce points at the synced locations.
+        forAll(edgeMidPoint, edgeI)
+        {
+            if (edgeMidPoint[edgeI] > -1)
+            {
+                edgeMidPoint[edgeI] = ref.setAction
+                (
+                    polyAddPoint
+                    (
+                        edgeMids[edgeI], // Point
+                        -1,              // Appended point, no master ID
+                        -1,              // Zone for point
+                        true             // Supports a cell
+                    )
+                );
+            }
+        }
+    } // End memory management for syncing/adding edge points
+
+    // Write out edge mid points for split edges for debugging
+    if (debug)
+    {
+        OFstream str(mesh_.time().path()/"edgeMidPoint.obj");
+
+        forAll(edgeMidPoint, edgeI)
+        {
+            if (edgeMidPoint[edgeI] > -1)
+            {
+                // Get edge and write its cell centre
+                const edge& e = meshEdges[edgeI];
+                meshTools::writeOBJ(str, e.centre(meshPoints));
+            }
+        }
+
+        Pout<< "polyhedralRefinement::setRefinementInstruction(...)" << nl
+            << "Writing centres of edges to split to file " << str.name()
+            << endl;
+    }
+
+
+    // PART 3: Calculate face level (after selected cells splitting)
+
+    if (debug)
+    {
+        Pout<< "polyhedralRefinement::setRefinementInstruction" << nl
+            << "Allocating face midpoints."
+            << endl;
+    }
+
+    // Face anchor level. There are guaranteed at least 3 points with level
+    // <= anchorLevel. These are the corner points.
+    labelList faceAnchorLevel(mesh_.nFaces());
+
+    for (label faceI = 0; faceI < mesh_.nFaces(); ++faceI)
+    {
+        faceAnchorLevel[faceI] = getAnchorLevel(faceI);
+    }
+
+    // Mid points for refined face (points at face centres):
+    // Not refined = -1
+    // Shall be refined > -1 (label of added mid point)
+    labelList faceMidPoint(mesh_.nFaces(), -1);
+
+    // Get necessary mesh data
+    const labelList& meshFaceOwner = mesh_.faceOwner();
+    const labelList& meshFaceNeighbour = mesh_.faceNeighbour();
+
+    const label nFaces = mesh_.nFaces();
+    const label nInternalFaces = mesh_.nInternalFaces();
+
+    // Internal faces: look at cells on both sides. Uniquely determined since
+    // the face itself is guaranteed to be same level as most refined neighbour
+    for (label faceI = 0; faceI < nInternalFaces; ++faceI)
+    {
+        // Note: no need to check whether the face has valid anchor level since
+        // all faces can be split
+        const label& own = meshFaceOwner[faceI];
+        const label& ownLevel = cellLevel_[own];
+        const label newOwnLevel = ownLevel + (cellMidPoint[own] > -1 ? 1 : 0);
+
+        const label& nei = meshFaceNeighbour[faceI];
+        const label& neiLevel = cellLevel_[nei];
+        const label newNeiLevel = neiLevel + (cellMidPoint[nei] > -1 ? 1 : 0);
+
+        if
+        (
+            newOwnLevel > faceAnchorLevel[faceI]
+         || newNeiLevel > faceAnchorLevel[faceI]
+        )
+        {
+            // New level is higher than the face anchor level, mark for
+            // splitting
+            faceMidPoint[faceI] = 12345;
+        }
+    }
+
+    // Coupled patches handled like internal faces except now all information
+    // from neighbour comes from across processor.
+    // Boundary faces are more complicated since the boundary face can
+    // be more refined than its owner (or neighbour for coupled patches)
+    // (does not happen if refining/unrefining only, but does e.g. when
+    // refinining and subsetting)
+
+    // Memory management
+    {
+        // Create list for swapping boundary data
+        labelList newNeiLevel(nFaces - nInternalFaces);
+
+        forAll(newNeiLevel, i)
+        {
+            const label& own = meshFaceOwner[i + nInternalFaces];
+            const label& ownLevel = cellLevel_[own];
+            const label newOwnLevel =
+                ownLevel + (cellMidPoint[own] > -1 ? 1 : 0);
+
+            newNeiLevel[i] = newOwnLevel;
+        }
+
+        // Swap the list which now contains data from the other side
+        syncTools::swapBoundaryFaceList(mesh_, newNeiLevel);
+
+        forAll(newNeiLevel, i)
+        {
+            // Get face index
+            const label faceI = i + nInternalFaces;
+
+            // Note: no need to check whether the face has valid anchor level
+            // since all faces can be split
+            const label& own = meshFaceOwner[faceI];
+            const label& ownLevel = cellLevel_[own];
+            const label newOwnLevel =
+                ownLevel + (cellMidPoint[own] > -1 ? 1 : 0);
+
+            if
+            (
+                newOwnLevel > faceAnchorLevel[faceI]
+             || newNeiLevel[i] > faceAnchorLevel[faceI]
+            )
+            {
+                // New level is higher than the face anchor level, mark for
+                // splitting
+                faceMidPoint[faceI] = 12345;
+            }
+        }
+    } // End memory management for syncing owner/neighbour face levels
+
+    // Note: synronisation of faceMidPoints across coupled patches is not
+    // necessary since we have exchanged the neighbour data above using
+    // swapBoundaryFaceList, thus the faceMidPoint has to be the same on both
+    // sides. VV, 5/Jan/2018.
+//    // Synchronize faceMidPoint across coupled patches
+//    syncTools::syncFaceList
+//    (
+//        mesh_,
+//        faceMidPoint,
+//        maxEqOp<label>(),
+//        false
+//    );
+
+
+    // Introduce face points
+
+    // Get face centres
+    const vectorField& meshFaceCentres = mesh_.faceCentres();
+
+    // Memory management
+    {
+        // Phase 1: determine mid points and sync. Note: the same procedure has
+        // been used for syncing edge mid points
+
+        // Allocate storage for boundary face points
+        pointField bFaceMids
+        (
+            nFaces - nInternalFaces,
+            point(-GREAT, -GREAT, -GREAT)
+        );
+
+        // Loop through boundary face mids
+        forAll(bFaceMids, i)
+        {
+            // Get face index
+            const label faceI = i + nInternalFaces;
+
+            if (faceMidPoint[faceI] > -1)
+            {
+                // This is a valid face mid, get the face centre
+                bFaceMids[i] = meshFaceCentres[faceI];
+            }
+        }
+
+        // Sync across coupled boundaries. Note: uses maximum of the components
+        // of the vector. This is completely arbitrary but it doesn't matter as
+        // long as we have same points on each side. VV, 5/Jan/2018.
+        syncTools::syncBoundaryFaceList
+        (
+            mesh_,
+            bFaceMids,
+            maxEqOp<vector>()
+        );
+
+        // Loop through faces
+        forAll(faceMidPoint, faceI)
+        {
+            if (faceMidPoint[faceI] > -1)
+            {
+                // Face marked to be split. Add the point at face centre and
+                // replace faceMidPoint with actual point label
+
+                faceMidPoint[faceI] = ref.setAction
+                (
+                    polyAddPoint
+                    (
+                        (
+                            faceI < nInternalFaces
+                          ? meshFaceCentres[faceI]
+                          : bFaceMids[faceI - nInternalFaces]
+                        ),    // Point
+                        -1,   // Appended point, no master ID
+                        -1,   // Zone for point
+                        true  // Supports a cell
+                    )
+                );
+            }
+        }
+    } // End memory management for syncing boundary data and adding face mids
+
+    // Write out split faces as a face set for debugging
+    if (debug)
+    {
+        // Create a faceSet with 3*cell sizes to prevent excessive resizing
+        faceSet splitFaces(mesh_, "splitFaces", 3*cellsToRefine_.size());
+
+        forAll(faceMidPoint, faceI)
+        {
+            if (faceMidPoint[faceI] > -1)
+            {
+                splitFaces.insert(faceI);
+            }
+        }
+
+        Pout<< "polyhedralRefinement::setRefinementInstruction(...)" << nl
+            << "Writing " << splitFaces.size()
+            << " faces to split to faceSet " << splitFaces.objectPath()
+            << endl;
+
+        splitFaces.write();
+    }
+
+
+    // Now we have all the information we need to perform the refinement and we
+    // no longer need to refer to cellsToRefine_. The information is:
+    // - cellMidPoint >= 0 : cell needs to be split
+    // - faceMidPoint >= 0 : face needs to be split
+    // - edgeMidPoint >= 0 : edge needs to be split
+
+
+    // PART 4: Get corner and anchor points for all cells
+
+    if (debug)
+    {
+        Pout<< "polyhedralRefinement::setRefinementInstruction(...)" << nl
+            << "Finding cell anchorPoints" << endl;
+    }
+
+    // Get anchor points for each cell: points that have the same or lower
+    // refinement level as the cell
+    List<DynamicList<label>> cellAnchorPointsDynamic(mesh_.nCells());
+
+    // Loop through all cells
+    forAll(cellMidPoint, cellI)
+    {
+        if (cellMidPoint[cellI] > -1)
+        {
+            // This cell shall be cut. Set capacity to 16 to prevent excessive
+            // resizing
+            cellAnchorPointsDynamic[cellI].setCapacity(16);
+        }
+    }
+
+    // Get necessary mesh data
+    const labelListList& meshPointCells = mesh_.pointCells();
+
+    // Loop through all points
+    forAll(pointLevel_, pointI)
+    {
+        // Get point cells
+        const labelList& pCells = meshPointCells[pointI];
+
+        // Loop through all cells sharing this point
+        forAll(pCells, pCellI)
+        {
+            // Get current cell index
+            const label& cellI = pCells[pCellI];
+
+            if
+            (
+                cellMidPoint[cellI] > -1
+             && pointLevel_[pointI] <= cellLevel_[cellI]
+            )
+            {
+                // This point cells is marked for refinement and its point level
+                // is smaller or equal to cell level, append the point
+                cellAnchorPointsDynamic[cellI].append(pointI);
+            }
+        }
+    }
+
+    // Loop through all cells and check whether at least 4 anchor points
+    // have been found (minimum requirement for a tet cell)
+
+    // Get cell points for error output
+    const labelListList& meshCellPoints = mesh_.cellPoints();
+
+    forAll(cellMidPoint, cellI)
+    {
+        if (cellMidPoint[cellI] > -1)
+        {
+            // Cell selected for refinement
+            if (cellAnchorPointsDynamic[cellI].size() < 4)
+            {
+                // Cell has less than 4 anchor points. Issue an error and report
+                // cell points
+                const labelList& cPoints = meshCellPoints[cellI];
+
+                FatalErrorInFunction
+                   << "Cell " << cellI
+                    << " of level " << cellLevel_[cellI]
+                    << " does not seem to have enough points of "
+                    << " lower level" << endl
+                    << "cellPoints:" << cPoints << endl
+                    << "pointLevels:"
+                    << IndirectList<label>(pointLevel_, cPoints)() << endl
+                    << abort(FatalError);
+            }
+        }
+    }
+
+    // Collect cellAnchorPoints into a List<labelList> instead of
+    // List<dynamicList>
+    labelListList cellAnchorPoints(mesh_.nCells());
+
+    forAll(cellAnchorPointsDynamic, cellI)
+    {
+        // Tranfer the dynamic list for each cell into an ordinary list
+        cellAnchorPoints[cellI].transfer(cellAnchorPointsDynamic[cellI]);
+    }
+
+    // PART 5: Add the cells
+
+    if (debug)
+    {
+        Pout<< "polyhedralRefinement::setRefinementInstruction(...)" << nl
+            << " Adding cells."
+            << endl;
+    }
+
+    // We should have exactly n new cells per each split cell, where n is the
+    // number of anchor points in a cell
+    labelListList cellAddedCells(mesh_.nCells());
+
+    // Get cell zone mesh
+    const cellZoneMesh& cellZones = mesh_.cellZones();
+
+    forAll(cellAnchorPoints, cellI)
+    {
+        // Check whether this is a split cell
+        if (cellMidPoint[cellI] > -1)
+        {
+            // Get cell anchors
+            const labelList& cAnchors = cellAnchorPoints[cellI];
+
+            // Set the total number of added cells to number of anchors
+            labelList& cAdded = cellAddedCells[cellI];
+            cAdded.setSize(cAnchors.size());
+
+            // Original cell has index 0
+            cAdded[0] = cellI;
+
+            // Update refinement level indicator field to 1 since this original
+            // cell will be refined
+            refinementLevelIndicator_[cellI] = REFINED;
+
+            // Add other cells
+            for (label i = 1; i < cAdded.size(); ++i)
+            {
+                cAdded[i] = ref.setAction
+                (
+                    polyAddCell
+                    (
+                        -1,                         // Master point
+                        -1,                         // Master edge
+                        -1,                         // Master face
+                        cellI,                      // Master cell
+                        cellZones.whichZone(cellI)  // Zone for cell
+                    )
+                );
+            }
+        }
+    }
+
+
+    // PART 6: Adding faces
+
+    // 6.1. Existing faces that get split (into n faces where n is the number of
+    //      points or edges)
+    // 6.2. Existing faces that do not get split but only edges get split
+    // 6.3. Existing faces that do not get split but get new owner/neighbour
+    // 6.4. New internal faces inside split cells.
+
+    if (debug)
+    {
+        Pout<< "polyhedralRefinement::setRefinementInstruction(...)" << nl
+            << " Marking faces to be handled"
+            << endl;
+    }
+
+    // Get all faces to split:
+    // a) All faces of a cell being split
+    // b) All faces that are being split
+    // c) Both faces of an edge that is being split
+    boolList facesToSplit(mesh_.nFaces(), false);
+
+    // Get edge faces
+    const labelListList& meshEdgeFaces = mesh_.edgeFaces();
+
+    // a) All faces of a cell that is being split
+    forAll(cellMidPoint, cellI)
+    {
+        if (cellMidPoint[cellI] > -1)
+        {
+            const cell& cFaces = meshCells[cellI];
+
+            forAll(cFaces, i)
+            {
+                facesToSplit[cFaces[i]] = true;
+            }
+        }
+    }
+
+    // b) All faces that are being split
+    forAll(faceMidPoint, faceI)
+    {
+        if (faceMidPoint[faceI] > -1)
+        {
+            facesToSplit[faceI] = true;
+        }
+    }
+
+    // c) Both faces of an edge that are being split
+    forAll(edgeMidPoint, edgeI)
+    {
+        if (edgeMidPoint[edgeI] > -1)
+        {
+            const labelList& eFaces = meshEdgeFaces[edgeI];
+
+            forAll(eFaces, i)
+            {
+                facesToSplit[eFaces[i]] = true;
+            }
+        }
+    }
+
+    // Note: after splitting a certain face during parts 6.1. to 6.4.,
+    // facesToSplit for that face will be set back to 0, i.e. marked as finished
+
+
+    // PART 6.1. Add/modify faces for each face being split
+
+    if (debug)
+    {
+        Pout<< "polyhedralRefinement::setRefinementInstruction(...)" << endl
+            << "Splitting faces..." << endl;
+    }
+
+    forAll(faceMidPoint, faceI)
+    {
+        if (faceMidPoint[faceI] > -1 && facesToSplit[faceI])
+        {
+            // Face has not been split.
+            // Note: although facesToSplit can't be different than 1 here and
+            // the second check can be ommitted, it is left for clarity
+
+            // Get the face
+            const face& f = meshFaces[faceI];
+
+            // Flag to control whether the original faceI has been used
+            // Note: original face gets modified, other n - 1 faces are added,
+            // where n is the number of points/edges of a face
+            bool modifiedFace = false;
+
+            // Get anchor level for the face
+            const label& anchorLevel = faceAnchorLevel[faceI];
+
+            // New face always has four points/edges for arbitrary polygon
+            face newFace(4);
+
+            // Loop through all points of original face
+            forAll(f, fp)
+            {
+                const label& pointI = f[fp];
+
+                if (pointLevel_[pointI] <= anchorLevel)
+                {
+                    // This point is anchor, start collecting face
+
+                    // Create dynamic list (because of append) for face vertices
+                    // and append the first (anchor) point
+                    DynamicList<label> faceVerts(4);
+                    faceVerts.append(pointI);
+
+                    // Walk forward to mid point.
+                    // - if next is +2 midpoint is +1
+                    // - if next is +1 it is midpoint
+                    // - if next is +0 there has to be edgeMidPoint
+
+                    // Appends all points from this point to face mid point
+                    walkFaceToMid
+                    (
+                        edgeMidPoint,
+                        anchorLevel,
+                        faceI,
+                        fp,
+                        faceVerts
+                    );
+
+                    // Append face mid point
+                    faceVerts.append(faceMidPoint[faceI]);
+
+                    // Append all points from face mid point to starting point
+                    walkFaceFromMid
+                    (
+                        edgeMidPoint,
+                        anchorLevel,
+                        faceI,
+                        fp,
+                        faceVerts
+                    );
+
+                    // Transfer dynamic list to a face (ordinary list)
+                    newFace.transfer(faceVerts);
+                    faceVerts.clear();
+
+                    // Set new owner/neighbour indices based on split cells
+                    label own, nei;
+                    setNewFaceNeighbours
+                    (
+                        cellAnchorPoints,
+                        cellAddedCells,
+                        faceI,
+                        pointI,           // Anchor point
+
+                        own,
+                        nei
+                    );
+
+
+                    if (debug)
+                    {
+                        // Get mesh cell centres
+                        const vectorField& meshCellCentres =
+                            mesh_.cellCentres();
+
+                        if (mesh_.isInternalFace(faceI))
+                        {
+                            const label oldOwn = meshFaceOwner[faceI];
+                            const label oldNei = meshFaceNeighbour[faceI];
+
+                            // Print info only with deep debug level
+                            if (debug > 1)
+                            {
+                                Pout<< "Split internal face: " << faceI
+                                    << ", verts: " << f
+                                    << ", into quad: " << newFace << nl
+                                    << "owner: " << oldOwn
+                                    << ", neighbour: " << oldNei << endl;
+                            }
+
+                            checkInternalOrientation
+                            (
+                                ref,
+                                oldOwn,
+                                faceI,
+                                meshCellCentres[oldOwn],
+                                meshCellCentres[oldNei],
+                                newFace
+                            );
+                        }
+                        else
+                        {
+                            const label oldOwn = meshFaceOwner[faceI];
+
+                            // Print info only with deep debug level
+                            if (debug > 1)
+                            {
+                                Pout<< "Split boundary face: " << faceI
+                                    << ", verts: " << f
+                                    << ", into quad: " << newFace << nl
+                                    << "owner: " << oldOwn << endl;
+                            }
+
+                            checkBoundaryOrientation
+                            (
+                                ref,
+                                oldOwn,
+                                faceI,
+                                meshCellCentres[oldOwn],
+                                meshFaceCentres[faceI],
+                                newFace
+                            );
+                        }
+                    } // End debug
+
+
+                    if (!modifiedFace)
+                    {
+                        // Modify first face
+                        modifiedFace = true;
+                        modifyFace(ref, faceI, newFace, own, nei);
+                    }
+                    else
+                    {
+                        // Add additional faces
+                        addFace(ref, faceI, newFace, own, nei);
+                    }
+                } // End point anchor check
+            } // End for all points
+
+            // Mark face as handled
+            facesToSplit[faceI] = false;
+        }
+    }
+
+
+    // PART 6.2. Modify faces that do not get split but have edges that are
+    // being split
+
+    if (debug)
+    {
+        Pout<< "polyhedralRefinement::setRefinementInstruction(...)" << nl
+            << "Modifying faces with split edges..."
+            << endl;
+    }
+
+    // Get face edges
+    const labelListList& meshFaceEdges = mesh_.faceEdges();
+
+    forAll(edgeMidPoint, edgeI)
+    {
+        if (edgeMidPoint[edgeI] > -1)
+        {
+            // This is an edge that is going to be split, get edge faces
+            const labelList& eFaces = meshEdgeFaces[edgeI];
+
+            // Loop through all faces of an edge
+            forAll(eFaces, i)
+            {
+                // Get face index
+                const label faceI = eFaces[i];
+
+                // Check whether this is not a face that has been marked for
+                // splitting and that the face has not been handled yet. The
+                // second check is necessary since we go through edge faces
+                // instead of just faces
+                if (faceMidPoint[faceI] < 0 && facesToSplit[faceI])
+                {
+                    // This is unsplit face that has not been handled
+
+                    // Get face and face edges
+                    const face& f = meshFaces[faceI];
+                    const labelList& fEdges = meshFaceEdges[faceI];
+
+                    // Create a dynamic list containing new face vertices
+                    DynamicList<label> newFaceVerts(f.size());
+
+                    // Append all original points and all edge mid points
+                    forAll(f, fp)
+                    {
+                        newFaceVerts.append(f[fp]);
+
+                        const label edgeI = fEdges[fp];
+
+                        if (edgeMidPoint[edgeI] > -1)
+                        {
+                            newFaceVerts.append(edgeMidPoint[edgeI]);
+                        }
+                    }
+
+                    face newFace;
+                    newFace.transfer(newFaceVerts.shrink());
+
+
+                    // The point with the lowest level should be an anchor
+                    // point of the neighbouring cells.
+                    const label anchorFp = findMinLevel(f);
+
+                    label own, nei;
+                    setNewFaceNeighbours
+                    (
+                        cellAnchorPoints,
+                        cellAddedCells,
+                        faceI,
+                        f[anchorFp],      // Anchor point
+
+                        own,
+                        nei
+                    );
+
+
+                    if (debug)
+                    {
+                        // Get mesh cell centres
+                        const vectorField& meshCellCentres =
+                            mesh_.cellCentres();
+
+                        if (mesh_.isInternalFace(faceI))
+                        {
+                            const label oldOwn = meshFaceOwner[faceI];
+                            const label oldNei = meshFaceNeighbour[faceI];
+
+                            checkInternalOrientation
+                            (
+                                ref,
+                                oldOwn,
+                                faceI,
+                                meshCellCentres[oldOwn],
+                                meshCellCentres[oldNei],
+                                newFace
+                            );
+                        }
+                        else
+                        {
+                            const label oldOwn = meshFaceOwner[faceI];
+
+                            checkBoundaryOrientation
+                            (
+                                ref,
+                                oldOwn,
+                                faceI,
+                                meshCellCentres[oldOwn],
+                                meshFaceCentres[faceI],
+                                newFace
+                            );
+                        }
+                    } // End debug
+
+                    // Modify the face
+                    modifyFace(ref, faceI, newFace, own, nei);
+
+                    // Mark face as handled
+                    facesToSplit[faceI] = false;
+
+                }// End if unsplit, unhandled face
+            } // End for all edge faces
+        } // End if edge has been cut
+    } // End for all edges
+
+
+    // PART 6.3.: Modify faces that do not get split but whose owner/neighbour
+    // change due to splitting
+
+    if (debug)
+    {
+        Pout<< "polyhedralRefinement::setRefinementInstruction(...)" << nl
+            << " Changing owner/neighbour for otherwise unaffected faces..."
+            << endl;
+    }
+
+    forAll(facesToSplit, faceI)
+    {
+        // All remaining unnaffected faces are the ones whose owner/neighbour
+        // changed
+        if (facesToSplit[faceI])
+        {
+            // Get the face
+            const face& f = meshFaces[faceI];
+
+            // The point with the lowest level should be an anchor point of the
+            // neighbouring cells
+            label anchorFp = findMaxLevel(f);
+
+            label own, nei;
+            setNewFaceNeighbours
+            (
+                cellAnchorPoints,
+                cellAddedCells,
+                faceI,
+                f[anchorFp],      // Anchor point
+
+                own,
+                nei
+            );
+
+            // Modify the face, changing owner and neighbour
+            modifyFace(ref, faceI, f, own, nei);
+
+            // Mark face as handled
+            facesToSplit[faceI] = false;
+        }
+    }
+
+
+    // PART 6.4. Add new internal faces inside split cells
+
+    // We have to find the splitting points between the anchor points. But the
+    // edges between the anchor points might have been split (into two, three or
+    // four edges)
+
+    if (debug)
+    {
+        Pout<< "polyhedralRefinement::setRefinementInstruction(...)" << nl
+            << " Adding new internal faces for split cells..."
+            << endl;
+    }
+
+    // Loop through cells
+    forAll(cellMidPoint, cellI)
+    {
+        if (cellMidPoint[cellI] > -1)
+        {
+            // Cell has been split, create internal faces
+            createInternalFaces
+            (
+                cellAnchorPoints,
+                cellAddedCells,
+                cellMidPoint,
+                faceMidPoint,
+                faceAnchorLevel,
+                edgeMidPoint,
+                cellI,
+                ref
+            );
+        }
+    }
+
+    // Debug: check minimum point index of added points, needs to be equal to
+    // number of points in the original mesh
+    if (debug)
+    {
+        label minPointI = labelMax;
+        label maxPointI = labelMin;
+
+        forAll(cellMidPoint, cellI)
+        {
+            if (cellMidPoint[cellI] > -1)
+            {
+                minPointI = min(minPointI, cellMidPoint[cellI]);
+                maxPointI = max(maxPointI, cellMidPoint[cellI]);
+            }
+        }
+        forAll(faceMidPoint, faceI)
+        {
+            if (faceMidPoint[faceI] > -1)
+            {
+                minPointI = min(minPointI, faceMidPoint[faceI]);
+                maxPointI = max(maxPointI, faceMidPoint[faceI]);
+            }
+        }
+        forAll(edgeMidPoint, edgeI)
+        {
+            if (edgeMidPoint[edgeI] > -1)
+            {
+                minPointI = min(minPointI, edgeMidPoint[edgeI]);
+                maxPointI = max(maxPointI, edgeMidPoint[edgeI]);
+            }
+        }
+
+        if (minPointI != labelMax && minPointI != mesh_.nPoints())
+        {
+            FatalErrorInFunction
+                << "Added point labels not consecutive to existing mesh points."
+                << nl
+                << "mesh_.nPoints():" << mesh_.nPoints()
+                << " minPointI: " << minPointI
+                << " maxPointI: " << maxPointI
+                << abort(FatalError);
+        }
+    }
+}
+
+
+void Foam::polyhedralRefinement::setUnrefinementInstruction
+(
+    batchPolyTopoChange& ref
+) const
+{
+    // Note: assumes that splitPointsToUnrefine_ are set prior to the function
+    // call
+
+    // Check whether the refinementLevelIndicator is valid
+    if (refinementLevelIndicator_.size() != mesh_.nCells())
+    {
+        FatalErrorInFunction
+            << "Refinement level indicator list has invalid size: "
+            << refinementLevelIndicator_.size()
+            << ", number of cells: " << mesh_.nCells()
+            << nl
+            << "Make sure to call setRefinementInstruction(...) before"
+            << " calling setPolyhedralRefinement(...)."
+            << abort(FatalError);
+    }
+
+    // Get point cells necessary for debug and face removal
+    const labelListList& meshPointCells = mesh_.pointCells();
+
+    if (debug)
+    {
+        Pout<< "polyhedralRefinement::setUnrefinementInstruction"
+            << "(batchPolyTopoChange& ref)"
+            << nl
+            << "Checking validity of cellLevel before setting unrefinement."
+            << endl;
+
+        forAll(cellLevel_, cellI)
+        {
+            if (cellLevel_[cellI] < 0)
+            {
+                FatalErrorInFunction
+                    << "Illegal cell level " << cellLevel_[cellI]
+                    << " for cell " << cellI
+                    << abort(FatalError);
+            }
+        }
+
+        // Write split points into a point set
+        pointSet pSet
+        (
+            mesh_,
+            "splitPoints",
+            labelHashSet(splitPointsToUnrefine_)
+        );
+        pSet.write();
+
+        // Write split point cells into a cell set
+        cellSet cSet
+        (
+            mesh_,
+            "splitPointCells",
+            splitPointsToUnrefine_.size()
+        );
+
+        forAll(splitPointsToUnrefine_, i)
+        {
+            // Get point cells and insert them into cell set
+            const labelList& pCells = meshPointCells[splitPointsToUnrefine_[i]];
+
+            forAll(pCells, j)
+            {
+                cSet.insert(pCells[j]);
+            }
+        }
+        cSet.write();
+
+        Pout<< "polyhedralRefinement::setUnrefinementInstruction"
+            << "(batchPolyTopoChange& ref)"
+            << nl
+            << "Writing " << pSet.size()
+            << " points and "
+            << cSet.size() << " cells for unrefinement to" << nl
+            << "pointSet " << pSet.objectPath() << nl
+            << "cellSet " << cSet.objectPath()
+            << endl;
+    }
+
+    // Update refinementLevelIndicator for all cells that will be unrefined
+    forAll(splitPointsToUnrefine_, i)
+    {
+        // Get point cells and mark them for unrefinement
+        const labelList& pCells = meshPointCells[splitPointsToUnrefine_[i]];
+
+        forAll(pCells, j)
+        {
+            refinementLevelIndicator_[pCells[j]] = UNREFINED;
+        }
+    }
+
+    // Create lists needed by face remover
+    labelList cellRegion;
+    labelList cellRegionMaster;
+    labelList facesToRemove;
+
+    // Memory management
+    {
+        // Collect split faces in the hash set, guess size to prevent excessive
+        // resizing
+        labelHashSet splitFaces(12*splitPointsToUnrefine_.size());
+
+        // Get point faces
+        const labelListList& meshPointFaces = mesh_.pointFaces();
+
+        forAll(splitPointsToUnrefine_, i)
+        {
+            // Loop through all faces of this point and insert face index
+            const labelList& pFaces = meshPointFaces[splitPointsToUnrefine_[i]];
+
+            forAll(pFaces, j)
+            {
+                splitFaces.insert(pFaces[j]);
+            }
+        }
+
+        // Check with faceRemover what faces will get removed. Note that this
+        // can be more (but never less) than splitFaces provided.
+        faceRemover_.compatibleRemoves
+        (
+            splitFaces.toc(),   // Pierced faces
+
+            cellRegion,         // Region merged into (-1 for no region)
+            cellRegionMaster,   // Master cell for region
+            facesToRemove       // List of faces to be removed
+        );
+
+        if (facesToRemove.size() != splitFaces.size())
+        {
+            FatalErrorInFunction
+                << "Either the initial set of split points to unrefine does not"
+                << " seem to be consistent or there are no mid points of"
+                << " refined cells."
+                << abort(FatalError);
+        }
+    }
+
+    // Find point region master for every cell region.  This is the central point
+    // from which the coarse cell will be made
+    // The property of the point region master is that all cells that touch it
+    // have the same cell region index
+    // HJ, 6/Sep/2019
+    labelList pointRegionMaster(cellRegionMaster.size(), label(-1));
+
+    // Get point-cell addressing
+    const labelListList& pc = mesh_.pointCells();
+
+    forAll (splitPointsToUnrefine_, i)
+    {
+        const labelList& curPc = pc[splitPointsToUnrefine_[i]];
+
+        label curRegion = -1;
+
+        forAll (curPc, curPcI)
+        {
+            if (curRegion == -1)
+            {
+                // First region found.  Grab it
+                curRegion = cellRegion[curPc[curPcI]];
+            }
+            else
+            {
+                // Region already found.  Check that all other cells that
+                // touch this point have the same region
+                if (curRegion != cellRegion[curPc[curPcI]])
+                {
+                    // Error: different region cells touching in split point
+                    // This is not a valid unrefinement pattern
+                    FatalErrorInFunction
+                        << "Different region cells touching in split point."
+                        << abort(FatalError);
+                }
+            }
+        }
+
+        // Record point region master
+        if (curRegion > -1)
+        {
+            pointRegionMaster[curRegion] = splitPointsToUnrefine_[i];
+        }
+        else
+        {
+            // Error: Cannot find region for point
+            FatalErrorInFunction
+                << "Different region cells touching in split point."
+                << abort(FatalError);
+        }
+    }
+
+    // Insert all commands to combine cells
+    faceRemover_.setRefinement
+    (
+        facesToRemove,
+        cellRegion,
+        // pointRegionMaster, // OpenFOAM does not support master point
+        cellRegionMaster,
+        ref
+    );
+}
+
+
+// * * * * * * * * * * * * * * * * Constructors  * * * * * * * * * * * * * * //
+
+Foam::polyhedralRefinement::polyhedralRefinement
+(
+    const word& name,
+    const dictionary& dict,
+    const label index,
+    const polyTopoChanger& mme
+)
+:
+    refinement(name, dict, index, mme)
+{}
+
+
+// * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //
+
+void Foam::polyhedralRefinement::setCellsToRefine
+(
+    const labelList& refinementCellCandidates
+)
+{
+    if (debug)
+    {
+        Info<< "polyhedralRefinement::setCellsToRefine"
+            << "(const labelList& refinementCellCandidates)" << nl
+            << "Setting cells to refine" << endl;
+    }
+
+    // Create a mark-up field for cells to refine
+    boolList refineCell(mesh_.nCells(), false);
+
+    // Roughly count how many cells we are going to end up with
+    label roughCellCountAfterRefinement = mesh_.nCells();
+
+    // Get cell points to count number of additional cells
+    const labelListList& meshCellPoints = mesh_.cellPoints();
+
+    // Mark initial refinement candidates for refinement only if the cell level
+    // is smaller than the maximum refinement level. Note: stop marking them if
+    // we exceed the rough cell count
+    forAll (refinementCellCandidates, i)
+    {
+        // Get cell index
+        const label& cellI = refinementCellCandidates[i];
+
+        if (roughCellCountAfterRefinement < maxCells_)
+        {
+            // Mark cell for refinement
+            refineCell[cellI] = true;
+
+            // Increment number of cells (nPoints - 1 new cells per cell)
+            roughCellCountAfterRefinement += meshCellPoints[cellI].size() - 1;
+        }
+    }
+
+    // Extend cells across faces using a specified number of refinement buffer
+    // layers
+    for (label i = 0; i < nRefinementBufferLayers_; ++i)
+    {
+        meshTools::extendMarkedCellsAcrossFaces(mesh_, refineCell);
+    }
+
+    // Remove all cells that would exceed the maximum refinement level
+    forAll (refineCell, cellI)
+    {
+        if (refineCell[cellI] && (cellLevel_[cellI] + 1 > maxRefinementLevel_))
+        {
+            refineCell[cellI] = false;
+        }
+    }
+
+    // Make sure that the refinement is face consistent (2:1 consistency) and
+    // point consistent (4:1 consistency) if necessary
+
+    // Counter for additional cells to refine due to consistency in each
+    // iteration and number of iterations
+    label nAddCells = 0;
+    label nIters = 0;
+    label nTotalAddCells = 0;
+
+    do
+    {
+        // Reset counter at the beginning of each iteration
+        nAddCells = 0;
+
+        if (edgeBasedConsistency_)
+        {
+            // Check for 4:1 edge based consistent refinement. Updates
+            // cellsToRefine and returns number of cells added in this iteration
+            nAddCells += edgeConsistentRefinement(refineCell);
+        }
+
+        // Check for 2:1 face based consistent refinement. Updates cellsToRefine
+        // and returns number of cells added in this iteration
+        nAddCells += faceConsistentRefinement(refineCell);
+
+        // Global reduction
+        reduce(nAddCells, sumOp<label>());
+
+        // Increment number of iterations and total number of added cells
+        ++nIters;
+        nTotalAddCells += nAddCells;
+
+	if (debug)
+        {
+            Info<< "Added " << nAddCells << " cells in iteration "
+                << nIters << nl;
+        }
+
+    } while (nAddCells > 0);
+
+    Info<< "Added " << nTotalAddCells // nTotalAddCells already reduced
+        << " cells in " << returnReduce(nIters, maxOp<label>())
+        << " iterations to obtain consistent refinement."
+        << endl;
+
+    // Collect all cells to refine in a dynamic list
+    DynamicList<label> cellsToRefineDynamic(mesh_.nCells());
+
+    forAll (refineCell, cellI)
+    {
+        if (refineCell[cellI])
+        {
+            // Cell marked for refinement, append it
+            cellsToRefineDynamic.append(cellI);
+        }
+    }
+
+    // Transfer the contents into the data member (ordinary list)
+    cellsToRefine_.transfer(cellsToRefineDynamic);
+
+    Info<< "Selected " << returnReduce(cellsToRefine_.size(), sumOp<label>())
+        << " cells to refine." << endl;
+}
+
+
+void Foam::polyhedralRefinement::setSplitPointsToUnrefine
+(
+    const labelList& unrefinementPointCandidates
+)
+{
+    if (debug)
+    {
+        Info<< "polyhedralRefinement::setSplitPointsToUnrefine"
+            << "(const labelList& unrefinementPointCandidates)" << nl
+            << "Setting split points to unrefine." << endl;
+    }
+
+    // Get necessary mesh data
+    const label nPoints = mesh_.nPoints();
+    const labelListList& meshCellPoints = mesh_.cellPoints();
+
+    // PART 1: Mark all split points in the mesh (points that can be unrefined)
+    boolList splitPointsMarkup(nPoints, false);
+
+    // Algorithm: split point is uniquely defined as a point that:
+    // 1. Has pointLevel_ > 0 (obviously),
+    // 2. A point that has the same pointLevel_ as ALL of the points of its
+    //    faces. In other words, for each point, we will look through all the
+    //    faces of the point. For each face, we will visit points and check the
+    //    point level of all of these points. All point levels must be the same
+    //    for this point candidate to be split point. This is quite useful since
+    //    there is no need to store the refinement history
+
+    // Get necessary mesh data
+    const faceList& meshFaces = mesh_.faces();
+    const labelListList& meshPointFaces = mesh_.pointFaces();
+
+    // Loop through all points
+    forAll (meshPointFaces, pointI)
+    {
+        // Get point level of this point
+        const label& centralPointLevel = pointLevel_[pointI];
+
+        if (centralPointLevel < 1)
+        {
+            // Point can't be unrefined as its level is either 0 or
+            // invalid. Continue immediately
+            continue;
+        }
+
+        // Flag to see whether this is a split point candidate
+        bool splitPointCandidate = true;
+
+        // Get face labels for this point
+        const labelList& pFaces = meshPointFaces[pointI];
+
+        // Loop through all point faces
+        forAll (pFaces, i)
+        {
+            // Get face index and the face
+            const label& faceI = pFaces[i];
+            const face& curFace = meshFaces[faceI];
+
+            // Loop through points of the face
+            forAll (curFace, j)
+            {
+                // Get point index
+                const label& pointJ = curFace[j];
+
+                if (pointLevel_[pointJ] != centralPointLevel)
+                {
+                    // Point levels are different, this can't be a split point,
+                    // set flag to false and break immediatelly
+                    splitPointCandidate = false;
+                    break;
+                }
+                // else: this is still potential split point candidate so
+                //       there's nothing to do
+            } // End for all points of this face
+
+            // Check whether this can't be a split point already and break out
+            // immediately
+            if (!splitPointCandidate)
+            {
+                break;
+            }
+        } // End for all point faces
+
+        // At this point, if the flag is still true, this is a split point
+        if (splitPointCandidate)
+        {
+            splitPointsMarkup[pointI] = true;
+        }
+    }
+
+    // Note: if there is no dynamic load balancing, points at the boundary
+    // cannot be split points by definition. However, in dynamic load balancing
+    // runs, it is possible that a split point ends on processor boundary, in
+    // which case we will simply avoid (actually delay) unrefining until this
+    // becomes internal point again. VV, 4/Jul/2018.
+    const label nInternalFaces = mesh_.nInternalFaces();
+    const label nFaces = mesh_.nFaces();
+
+    for (label faceI = nInternalFaces; faceI < nFaces; ++faceI)
+    {
+        // Get the face and make sure that the points are unmarked
+        const face& f = meshFaces[faceI];
+
+        forAll (f, fpI)
+        {
+            splitPointsMarkup[f[fpI]] = false;
+        }
+    }
+
+    // PART 2: Mark all unrefinement point candidates that are split points at
+    // the same time (basically the intersection of split points and candidates)
+
+    // Create markup field of split points to unrefine
+    // True: this is a split point which should be unrefined
+    // False: this is either not a split point or it shouldn't be unrefined
+    boolList splitPointsToUnrefine(nPoints, false);
+
+    // Loop through all unrefinement candidates
+    forAll (unrefinementPointCandidates, i)
+    {
+        // Get point index
+        const label& pointI = unrefinementPointCandidates[i];
+
+        if (splitPointsMarkup[pointI])
+        {
+            // This is a split point, mark it for unrefinement
+            splitPointsToUnrefine[pointI] = true;
+        }
+    }
+
+
+    // PART 3: Make sure that we skip unrefining around split points that
+    // possibly have cells around that will be refined
+
+    // Mark cells that need to be protected (will be refined in this iteration)
+    boolList protectedCell(mesh_.nCells(), false);
+
+    // Loop through cells to refine and mark them
+    forAll (cellsToRefine_, i)
+    {
+        protectedCell[cellsToRefine_[i]] = true;
+    }
+
+    // Extend protected cells across points using a specified number of
+    // unrefinement buffer layers
+    for (label i = 0; i < nUnrefinementBufferLayers_; ++i)
+    {
+        meshTools::extendMarkedCellsAcrossPoints(mesh_, protectedCell);
+    }
+
+    // Loop through all cells and if the cell should be protected, protect all
+    // of its points from unrefinement
+    forAll (protectedCell, cellI)
+    {
+        if (protectedCell[cellI])
+        {
+            // Get list of cell points for this protected cell
+            const labelList& cPoints = meshCellPoints[cellI];
+
+            // Loop through cell points and make sure that they are not marked
+            // for unrefinement
+            forAll (cPoints, j)
+            {
+                splitPointsToUnrefine[cPoints[j]] = false;
+            }
+        }
+    }
+
+
+    // PART 4: Ensure face consistent (2:1 constraint) and possibly point
+    // consistent (4:1 constraint) unrefinement
+
+    // Get necessary mesh data
+    const label nCells = mesh_.nCells();
+    const labelListList& meshPointCells = mesh_.pointCells();
+
+    // Count number of removed cells from unrefinement (cells that will not be
+    // unrefined) in each iteration and number of iterations
+    label nRemCells = 0;
+    label nIters = 0;
+    label nTotalRemCells = 0;
+
+    do
+    {
+        // First, create cells to unrefine (all cells sharing point to unrefine)
+        boolList cellsToUnrefine(nCells, false);
+
+        // Loop through all split points to unrefine
+        forAll (splitPointsToUnrefine, pointI)
+        {
+            if (splitPointsToUnrefine[pointI])
+            {
+                // This split point is marked for unrefinement, collect all of
+                // its cells
+                const labelList& pCells = meshPointCells[pointI];
+                forAll (pCells, i)
+                {
+                    cellsToUnrefine[pCells[i]] = true;
+                }
+            }
+        }
+
+        // Reset number of removed cells from unrefinement for this iteration
+        nRemCells = 0;
+
+        if (edgeBasedConsistency_)
+        {
+            // Check for 4:1 edge based consistent unrefinement. Updates
+            // cellsToUnrefine and returns number of removed cells from
+            // unrefinement in this iteration
+            nRemCells += edgeConsistentUnrefinement(cellsToUnrefine);
+        }
+
+        // Check for 2:1 face based consistent unrefinement. Updates
+        // cellsToUnrefine and returns number of removed cells from unrefinement
+        // in this iteration
+        nRemCells += faceConsistentUnrefinement(cellsToUnrefine);
+
+        // Global reduction
+        reduce(nRemCells, sumOp<label>());
+
+        // If we have removed at least one cell from unrefinement, we need to
+        // protect its split points as well from unrefinement
+        if (nRemCells > 0)
+        {
+            // Get point cells
+            const labelListList& meshPointCells = mesh_.pointCells();
+
+            // Loop through all split points to unrefine
+            forAll (splitPointsToUnrefine, pointI)
+            {
+                if (splitPointsToUnrefine[pointI])
+                {
+                    // This is a split point for unrefinement, get the cells
+                    const labelList& pCells = meshPointCells[pointI];
+
+                    // Loop through all point cells
+                    forAll (pCells, i)
+                    {
+                        if (!cellsToUnrefine[pCells[i]])
+                        {
+                            // Cell must not be refined, remove point from
+                            // unrefinement as well
+                            splitPointsToUnrefine[pointI] = false;
+                            break;
+                        }
+                    }
+                }
+            }
+        }
+
+        // Increment number of iterations and number of total removed cells
+        ++nIters;
+        nTotalRemCells += nRemCells;
+
+        if (debug)
+        {
+            Info<< "Removed " << nRemCells << " cells in iteration "
+                << nIters << nl;
+        }
+
+    } while (nRemCells > 0);
+
+    Info<< "Removed " << nTotalRemCells // nTotalRemCells already reduced
+        << " cells in " << returnReduce(nIters, maxOp<label>())
+        << " iterations to obtain consistent unrefinement."
+        << endl;
+
+    // Collect all split points to unrefine in a dynamic list
+    DynamicList<label> splitPointsToUnrefineDynamic(nPoints);
+
+    forAll (splitPointsToUnrefine, pointI)
+    {
+        if (splitPointsToUnrefine[pointI])
+        {
+            // Split point marked for unrefinement, append it
+            splitPointsToUnrefineDynamic.append(pointI);
+        }
+    }
+
+    // Transfer the contents into the data member (ordinary list)
+    splitPointsToUnrefine_.transfer(splitPointsToUnrefineDynamic);
+
+    Info<< "Selected "
+        << returnReduce(splitPointsToUnrefine_.size(), sumOp<label>())
+        << " split points to unrefine." << endl;
+}
+
+
+// ************************************************************************* //
diff --git a/src/dynamicMesh/refinement/polyhedralRefinement/polyhedralRefinement.H b/src/dynamicMesh/refinement/polyhedralRefinement/polyhedralRefinement.H
new file mode 100644
index 0000000000000000000000000000000000000000..3a3f8140bbf0edfe09e35e093241f65bbd04f7d3
--- /dev/null
+++ b/src/dynamicMesh/refinement/polyhedralRefinement/polyhedralRefinement.H
@@ -0,0 +1,246 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | foam-extend: Open Source CFD
+   \\    /   O peration     | Version:     5.0
+    \\  /    A nd           | Web:         http://www.foam-extend.org
+     \\/     M anipulation  | For copyright notice see file Copyright
+-------------------------------------------------------------------------------
+License
+    This file is part of foam-extend.
+
+    foam-extend 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.
+
+    foam-extend 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 foam-extend.  If not, see <http://www.gnu.org/licenses/>.
+
+Class
+    Foam::polyhedralRefinement
+
+Description
+    Isotropic refinement of polyhedral cells using the mesh modifier engine
+
+    Each polyhedral cell is split by the following procedure:
+    1. Adding points at the edge centre, face centre and cell centre,
+    2. Adding cells n cells where n is the number of points of the cell,
+    3. Splitting each face into multiple faces going from:
+       existing corner point -> new edge centre point -> new face centre
+       point -> other new edge centre point (sharing the same corner point)
+    4. Adding internal faces going from:
+       new edge centre point -> new face centre point -> new cell centre
+       point -> other new face centre point (sharing the same edge)
+
+SourceFiles
+    polyhedralRefinement.C
+
+Author
+    Vuko Vukcevic, Wikki Ltd.  All rights reserved.
+    Rewrite, porting and changes by Hrvoje Jasak, Wikki Ltd.  All rights reserved.
+
+\*---------------------------------------------------------------------------*/
+
+#ifndef polyhedralRefinement_H
+#define polyhedralRefinement_H
+
+#include "refinement.H"
+#include "polyMesh.H"
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+namespace Foam
+{
+
+/*---------------------------------------------------------------------------*\
+                    Class polyhedralRefinement Declaration
+\*---------------------------------------------------------------------------*/
+
+class polyhedralRefinement
+:
+    public refinement
+{
+private:
+
+    // Private Member Functions
+
+        // Helper functions
+
+            //- Get least cell level such that the face has at least three
+            //  points smaller than the level
+            label getAnchorLevel(const label faceI) const;
+
+
+        // Local topology modification functions (operate on cells/faces)
+
+            //- Create all internal faces of split cellI into n cells
+            //   where n is the number of cell points
+            void createInternalFaces
+            (
+                const labelListList& cellAnchorPoints,
+                const labelListList& cellAddedCells,
+                const labelList& cellMidPoint,
+                const labelList& faceMidPoint,
+                const labelList& faceAnchorLevel,
+                const labelList& edgeMidPoint,
+                const label cellI,
+                batchPolyTopoChange& ref
+            ) const;
+
+
+        // Topological change helper functions
+
+            //- Get cell added to point of cellI (if any)
+            label getAnchorCell
+            (
+                const labelListList& cellAnchorPoints,
+                const labelListList& cellAddedCells,
+                const label cellI,
+                const label faceI,
+                const label pointI
+            ) const;
+
+            //- Set new owner and neighbour (in unspecified order) of pointI
+            //  on faceI
+            void setNewFaceNeighbours
+            (
+                const labelListList& cellAnchorPoints,
+                const labelListList& cellAddedCells,
+                const label faceI,
+                const label pointI,
+
+                label& own,
+                label& nei
+            ) const;
+
+            //- Find index of point with wantedLevel, starting from fp
+            label findLevel
+            (
+                const face& f,
+                const label startFp,
+                const bool searchForward,
+                const label wantedLevel
+            ) const;
+
+            //- Store in maps correspondence from midpoint to anchors and
+            //  faces. Used when creating internal faces
+            label storeMidPointInfo
+            (
+                const labelListList& cellAnchorPoints,
+                const labelListList& cellAddedCells,
+                const labelList& cellMidPoint,
+                const labelList& edgeMidPoint,
+                const label cellI,
+                const label faceI,
+                const bool faceOrder,
+                const label midPointI,
+                const label anchorPointI,
+                const label faceMidPointI,
+
+                Map<edge>& midPointToAnchors,
+                Map<edge>& midPointToFaceMids,
+                batchPolyTopoChange& ref
+            ) const;
+
+            //- If p0 and p1 are existing vertices check if edge is split
+            //  and insert splitPoint. Used with storing mid point
+            void insertEdgeSplit
+            (
+                const labelList& edgeMidPoint,
+                const label p0,
+                const label p1,
+                DynamicList<label>& verts
+            ) const;
+
+
+        // Copy control
+
+            //- Disallow default bitwise copy construct
+            polyhedralRefinement(const polyhedralRefinement&) = delete;
+
+            //- Disallow default bitwise assignment
+            void operator=(const polyhedralRefinement&) = delete;
+
+
+protected:
+
+    // Protected Pure Virtual Member Functions
+
+        // Global topology modification functions (operate on whole polyMesh)
+
+            //- Set refinement instruction
+            virtual void setRefinementInstruction
+            (
+                batchPolyTopoChange& ref
+            ) const;
+
+            //- Set unrefinement instruction
+            virtual void setUnrefinementInstruction
+            (
+                batchPolyTopoChange& ref
+            ) const;
+
+
+public:
+
+    //- Runtime type information
+    TypeName("polyhedralRefinement");
+
+
+    // Constructors
+
+        //- Construct from dictionary
+        polyhedralRefinement
+        (
+            const word& name,
+            const dictionary& dict,
+            const label index,
+            const polyTopoChanger& mme
+        );
+
+
+    //- Destructor
+    virtual ~polyhedralRefinement() = default;
+
+
+    // Member Functions
+
+        // Edit
+
+            //- Set cells to refine given a list of refinement
+            //  candidates. Refinement candidates are extended within the
+            //  function due to possible 4:1 conflicts and specified number of
+            //  buffer layers.
+            //  Note: must be called BEFORE setSplitPointsToUnrefine
+            virtual void setCellsToRefine
+            (
+                const labelList& refinementCellCandidates
+            );
+
+            //- Set split points to unrefine given a list of all mesh points
+            //  that are candidates for unrefinement. Split points are
+            //  determined as a subset of unrefinement candidates, avoiding
+            //  splitting points of cells that are going to be refined at the
+            //  same time and ensuring consistent unrefinement.
+            //  Note: must be called AFTER setCellsToRefine
+            virtual void setSplitPointsToUnrefine
+            (
+                const labelList& unrefinementPointCandidates
+            );
+};
+
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+} // End namespace Foam
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+#endif
+
+// ************************************************************************* //
diff --git a/src/dynamicMesh/refinement/prismatic2DRefinement/prismatic2DRefinement.C b/src/dynamicMesh/refinement/prismatic2DRefinement/prismatic2DRefinement.C
new file mode 100644
index 0000000000000000000000000000000000000000..87595e58cbcfc80f4d1a3e82b4ce09b6941299e1
--- /dev/null
+++ b/src/dynamicMesh/refinement/prismatic2DRefinement/prismatic2DRefinement.C
@@ -0,0 +1,2978 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | foam-extend: Open Source CFD
+   \\    /   O peration     | Version:     5.0
+    \\  /    A nd           | Web:         http://www.foam-extend.org
+     \\/     M anipulation  | For copyright notice see file Copyright
+-------------------------------------------------------------------------------
+License
+    This file is part of foam-extend.
+
+    foam-extend 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.
+
+    foam-extend 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 foam-extend.  If not, see <http://www.gnu.org/licenses/>.
+
+Author
+    Vuko Vukcevic, Wikki Ltd.  All rights reserved.
+
+\*---------------------------------------------------------------------------*/
+
+#include "prismatic2DRefinement.H"
+#include "cellSet.H"
+#include "faceSet.H"
+#include "pointSet.H"
+#include "emptyPolyPatch.H"
+#include "wedgePolyPatch.H"
+#include "addToRunTimeSelectionTable.H"
+#include "meshTools.H"
+
+// * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * //
+
+namespace Foam
+{
+    defineTypeNameAndDebug(prismatic2DRefinement, 0);
+    addToRunTimeSelectionTable
+    (
+        polyMeshModifier,
+        prismatic2DRefinement,
+        dictionary
+    );
+}
+
+
+// * * * * * * * * * * * * * Private Member Functions  * * * * * * * * * * * //
+
+Foam::label Foam::prismatic2DRefinement::getAnchorLevel
+(
+    const label faceI,
+    const label nPoints
+) const
+{
+    // Get the face
+    const face& f = mesh_.faces()[faceI];
+
+    // Sanity check for expected number of points
+    if (nPoints != 3 && nPoints != 4)
+    {
+        FatalErrorInFunction
+            << "Trying to find anchor level with " << nPoints << " points"
+            << " smaller than anchor level. Only nPoints = 3 and 4 are"
+            << " supported."
+            << abort(FatalError);
+    }
+
+    // Sanity check: if we are expecting to find 4 points on a face which is not
+    // on special patch (empty or wedge) and we find a face with 3 points, issue
+    // an error
+    if (f.size() <= 3 && nPoints == 4)
+    {
+        FatalErrorInFunction
+            << "Expected to find at least 4 points with level lower than"
+            << " anchor level."
+            << nl
+            << "Make sure to call this function with nPoints = 4 only if"
+            << " you do not expect triangular faces."
+            << abort(FatalError);
+    }
+
+    if (f.size() <= nPoints)
+    {
+        return pointLevel_[f[findMaxLevel(f)]];
+    }
+    else
+    {
+        const label& ownLevel = cellLevel_[mesh_.faceOwner()[faceI]];
+
+        if (countAnchors(f, ownLevel) >= nPoints)
+        {
+            return ownLevel;
+        }
+        else if (countAnchors(f, ownLevel + 1) >= nPoints)
+        {
+            return ownLevel + 1;
+        }
+        else
+        {
+            return -1;
+        }
+    }
+}
+
+
+void Foam::prismatic2DRefinement::appendFaceSplitInfo
+(
+    const label& faceI,
+    const boolList& edgeOnPatchToCut,
+    const labelList& edgeMidPoint,
+    DynamicList<label>& splitFacesIntoTwo,
+    DynamicList<Pair<label> >& splitFacesEmptyEdges
+) const
+{
+    // First append the face into the list
+    splitFacesIntoTwo.append(faceI);
+
+    // Grab all edges of the face
+    const labelList& curEdges = mesh_.faceEdges()[faceI];
+
+    // Create placeholders for two edge levels. Initialise with -1
+    // for sanity checks later on
+    label specialPatchEdgeI = -1;
+    label specialPatchEdgeJ = -1;
+
+    // Count number of edges for sanity checks
+    label nEdgesOnSpecialPatch = 0;
+
+    // Collect the two edge labels found on the special patch (empty or wedge)
+    forAll (curEdges, i)
+    {
+        // Get edge index
+        const label edgeI = curEdges[i];
+
+        if (edgeOnPatchToCut[edgeI])
+        {
+            // This edge is on special patch (empty or wedge), check whether it
+            // is first, second or invalid
+            switch (nEdgesOnSpecialPatch)
+            {
+                case 0:
+                    specialPatchEdgeI = edgeI;
+                    break;
+                case 1:
+                    specialPatchEdgeJ = edgeI;
+                    break;
+                default:
+                    FatalErrorInFunction
+                        << "Found more than two edges on face " << faceI
+                        << " on the special patch (empty or wedge)."
+                        << nl
+                        << "Either this is not a valid 2D mesh or"
+                        << " we are visiting wrong faces."
+                        << abort(FatalError);
+            }
+
+            // Increment the counter
+            ++nEdgesOnSpecialPatch;
+
+        } // End if edge on special patch (empty or wedge)
+
+    } // End loop over all edges
+
+    // Debug: additional check whether the two edges are marked for
+    // refinement (they should be)
+    if (debug)
+    {
+        if (edgeMidPoint[specialPatchEdgeI] == -1)
+        {
+            FatalErrorInFunction
+                << "Empty patch edge with index: " << specialPatchEdgeI
+                << " not marked for splitting"
+                << nl
+                << "Check edgeMidPoint selection algorithm."
+                << abort(FatalError);
+        }
+
+        if (edgeMidPoint[specialPatchEdgeI] == -1)
+        {
+            FatalErrorInFunction
+                << "Empty patch edge with index: " << specialPatchEdgeJ
+                << " not marked for splitting"
+                << nl
+                << "Check edgeMidPoint selection algorithm."
+                << abort(FatalError);
+        }
+    }
+
+    // At this point, we should have the two edges we were looking
+    // for, collect them into the list with additional sanity check
+    if (specialPatchEdgeI > -1 && specialPatchEdgeJ > -1)
+    {
+        // Append the list of two edges in increasing order (just in
+        // case we end up needing this information
+        if (specialPatchEdgeI < specialPatchEdgeJ)
+        {
+            splitFacesEmptyEdges.append
+            (
+                Pair<label>(specialPatchEdgeI, specialPatchEdgeJ)
+            );
+        }
+        else
+        {
+            splitFacesEmptyEdges.append
+            (
+                Pair<label>(specialPatchEdgeJ, specialPatchEdgeI)
+            );
+        }
+    }
+    else
+    {
+        FatalErrorInFunction
+            << "Found invalid indices for edges on special patches:"
+            << nl
+            << "specialPatchEdgeI: " << specialPatchEdgeI
+            << ", specialPatchEdgeJ: " << specialPatchEdgeJ
+            << nl
+            << "Something went wrong. Check face edges."
+            << abort(FatalError);
+    }
+}
+
+
+void Foam::prismatic2DRefinement::setNewFaceNeighbours
+(
+    const HashTable
+    <
+        label,
+        Pair<label>,
+        Hash<FixedList<label, 2> >
+    >& pointCellToAddedCellMap,
+    const labelListList& cellAddedCells,
+    const label& faceI,
+    const label& pointI,
+
+    label& own,
+    label& nei
+) const
+{
+    // Get anchor cell for this anchor point on owner side
+    const label ownCellI = mesh_.faceOwner()[faceI];
+
+    // Get cell added cells for owner cell
+    const labelList& cAddedOwn = cellAddedCells[ownCellI];
+
+    // If the cell added cells list is not empty, return the necessary cell
+    // index fetched with pointCellToAddedCellMap
+    const Pair<label> pointOwnerCellPair(pointI, ownCellI);
+
+    if (cAddedOwn.empty())
+    {
+        // Cell added cells is empty for owner, fetch original owner
+        own = ownCellI;
+    }
+    else if (!pointCellToAddedCellMap.found(pointOwnerCellPair))
+    {
+        // Point-cell pair not found, meaning that we need to search for the
+        // closest point which has lower level than this point. This happens if
+        // we are splitting a face that has already been split but has different
+        // owner/neighbour levels (e.g. owner was refined, but the neighbour was
+        // not in the previous time step). It is possible that we end up with
+        // splitting this face with point levels e.g. (1 1 0 0). Therefore, we
+        // need to search for the point with minimum level on the edges sharing
+        // this point.
+
+        // Find point in the local faces addressing
+        const face& f = mesh_.faces()[faceI];
+        const label fpI = f.find(pointI);
+
+        if (fpI == -1)
+        {
+            FatalErrorInFunction
+                << "Point: " << pointI << " not found in face: " << f
+                << ", with face index: " << faceI
+                << nl
+                << "Point must belong to the face."
+                << nl
+                << "Error encounted when dealing with owner cell: "
+                << ownCellI
+                << abort(FatalError);
+        }
+
+        // Find the global point index with minimum edge connected level
+        const label anchorPointI = findMinEdgeConnectedLevel
+        (
+            fpI,                      // current face point
+            faceI,                    // face index
+            f,                        // face
+            mesh_.faceEdges()[faceI], // face edges
+            mesh_.edges()             // mesh edges
+        );
+
+        // If the point is the same as pointI, we did not find any valid point
+        if (anchorPointI == pointI)
+        {
+            FatalErrorInFunction
+                << "Could not find different adjacent anchor point."
+                << nl
+                << "pointI: " << pointI << " faceI: " << faceI
+                << "Error encounted when dealing with owner cell: "
+                << ownCellI
+                << abort(FatalError);
+        }
+
+        // Set owner cell
+        own = cAddedOwn
+        [
+            pointCellToAddedCellMap[Pair<label>(anchorPointI, ownCellI)]
+        ];
+    }
+    else
+    {
+        // Cell added cells is not empty and the mapping is found. Set owner
+        // from mapping
+        own = cAddedOwn[pointCellToAddedCellMap[pointOwnerCellPair]];
+    }
+
+    if (mesh_.isInternalFace(faceI))
+    {
+        // Get anchor cell for this anchor point on neighbour side
+        const label neiCellI = mesh_.faceNeighbour()[faceI];
+
+        // Get cell added cells for neighbour cell
+        const labelList& cAddedNei = cellAddedCells[neiCellI];
+
+        // If the cell added cells list is not empty, return the necessary cell
+        // index fetched with pointCellToAddedCellMap
+
+        const Pair<label> pointNeighbourCellPair(pointI, neiCellI);
+
+        if (cAddedNei.empty())
+        {
+            // Cell added cells is empty for neighbour, fetch original neighbour
+            nei = neiCellI;
+        }
+        else if (!pointCellToAddedCellMap.found(pointNeighbourCellPair))
+        {
+            // Point-cell pair not found, meaning that we need to search for the
+            // closest point which has lower level than this point. This happens
+            // if we are splitting a face that has already been split but has
+            // different owner/neighbour levels (e.g. owner was refined, but the
+            // neighbour was not in the previous time step). It is possible that
+            // we end up with splitting this face with point levels e.g. (1 1 0
+            // 0). Therefore, we need to search for the point with minimum level
+            // on the edges sharing this point.
+
+            // Find point in the local faces addressing
+            const face& f = mesh_.faces()[faceI];
+            const label fpI = f.find(pointI);
+
+            if (fpI == -1)
+            {
+                FatalErrorInFunction
+                    << "Point: " << pointI << " not found in face: " << f
+                    << ", with face index: " << faceI
+                    << nl
+                    << "Point must belong to the face."
+                    << nl
+                    << "Error encounted when dealing with neighbour cell: "
+                    << neiCellI
+                    << abort(FatalError);
+            }
+
+            // Find the global point index with minimum edge connected level
+            const label anchorPointI = findMinEdgeConnectedLevel
+            (
+                fpI,                      // current face point
+                faceI,                    // face index
+                f,                        // face
+                mesh_.faceEdges()[faceI], // face edges
+                mesh_.edges()             // mesh edges
+            );
+
+            // If the point is the same as pointI, we did not find any valid point
+            if (anchorPointI == pointI)
+            {
+                FatalErrorInFunction
+                    << "Could not find different adjacent anchor point."
+                    << nl
+                    << "pointI: " << pointI << " faceI: " << faceI
+                    << "Error encounted when dealing with neighbour cell: "
+                    << neiCellI
+                    << abort(FatalError);
+            }
+
+            // Set owner cell
+            nei = cAddedNei
+            [
+                pointCellToAddedCellMap[Pair<label>(anchorPointI, neiCellI)]
+            ];
+        }
+        else
+        {
+            // Cell added cells is not empty and the mapping is found. Set
+            // neighbour from mapping
+            nei = cAddedNei[pointCellToAddedCellMap[pointNeighbourCellPair]];
+        }
+    }
+    else
+    {
+        // Boundary face: set neighbour to -1
+        nei = -1;
+    }
+}
+
+
+Foam::label Foam::prismatic2DRefinement::findMinEdgeConnectedLevel
+(
+    const label& fpI,
+    const label& faceI,
+    const face& f,
+    const labelList& fEdges,
+    const edgeList& meshEdges
+) const
+{
+    // Get point index and initialize anchor point
+    const label& pointI = f[fpI];
+
+    label anchorPointI = pointI;
+
+    // Check the other point on edge starting with pointI
+    const label& edgeIndexAfterPoint = fEdges[fpI];
+    const edge& edgeAfter = meshEdges[edgeIndexAfterPoint];
+
+    // Get other point on edge after
+    const label& pointAfter = edgeAfter.otherVertex(pointI);
+
+    if (pointLevel_[pointAfter] < pointLevel_[anchorPointI])
+    {
+        anchorPointI = pointAfter;
+    }
+
+    // Check the other point on edge ending with pointI
+    const label& edgeIndexBeforePoint = fEdges[f.rcIndex(fpI)];
+    const edge& edgeBefore = meshEdges[edgeIndexBeforePoint];
+
+    // Get other point on edge before
+    const label& pointBefore = edgeBefore.otherVertex(pointI);
+
+    if (pointLevel_[pointBefore] < pointLevel_[anchorPointI])
+    {
+        anchorPointI = pointBefore;
+    }
+
+    return anchorPointI;
+}
+
+
+void Foam::prismatic2DRefinement::addFaceMids
+(
+    const labelList& faceMidPoint,
+    const boolList& faceOnPatchToCut,
+    const label& faceI,
+    const label& cellI,
+    face& newFace
+) const
+{
+    // b) faceMidPoint for this face
+    newFace[1] = faceMidPoint[faceI];
+
+    // c) faceMidPoint for the face on the other side
+    // The other face is uniquely defined as the other face of the same cell
+    // which is on special patch (empty or wedge)
+
+    // Get the cell
+    const cell& cFaces = mesh_.cells()[cellI];
+
+    // Loop through cell faces
+    forAll(cFaces, i)
+    {
+        // Get face index
+        const label& faceJ = cFaces[i];
+
+        if (faceOnPatchToCut[faceJ] && (faceI != faceJ))
+        {
+            // This is the face we're looking for, add its
+            // midpoint and double check if it is valid
+            if (faceMidPoint[faceJ] > -1)
+            {
+                newFace[2] = faceMidPoint[faceJ];
+                break;
+            }
+            else
+            {
+                FatalErrorInFunction
+                    << "Other face: "
+                    << faceJ
+                    << " has not been selected for splitting,"
+                    << " while the face on original side: "
+                    << faceI
+                    <<" has been selected."
+                    << abort(FatalError);
+            }
+        } // End if this is our face
+    } // End for all cell faces
+}
+
+
+void Foam::prismatic2DRefinement::checkNewFaceOrientation
+(
+    batchPolyTopoChange& ref,
+    const label& faceI,
+    const face& newFace
+) const
+{
+    // Get mesh cell centres
+    const vectorField& meshCellCentres = mesh_.cellCentres();
+
+    if (mesh_.isInternalFace(faceI))
+    {
+        // Get old owner/neighbour indices
+        const label oldOwn = mesh_.faceOwner()[faceI];
+        const label oldNei = mesh_.faceNeighbour()[faceI];
+
+        // Print info only with deep debug level
+        if (debug > 1)
+        {
+            Pout<< "Split infternal face: " << faceI
+                << ", into quad: " << newFace << nl
+                << "owner: " << oldOwn
+                << ", neighbour: " << oldNei << endl;
+        }
+
+        checkInternalOrientation
+        (
+            ref,
+            oldOwn,
+            faceI,
+            meshCellCentres[oldOwn],
+            meshCellCentres[oldNei],
+            newFace
+        );
+    }
+    else
+    {
+        // Get face centres and old owner
+        const vectorField& meshFaceCentres = mesh_.faceCentres();
+        const label oldOwn = mesh_.faceOwner()[faceI];
+
+        // Print info only with deep debug level
+        if (debug > 1)
+        {
+            Pout<< "Split boundary face: " << faceI
+                << ", into quad: " << newFace << nl
+                << "owner: " << oldOwn << endl;
+        }
+
+        checkBoundaryOrientation
+        (
+            ref,
+            oldOwn,
+            faceI,
+            meshCellCentres[oldOwn],
+            meshFaceCentres[faceI],
+            newFace
+        );
+    }
+}
+
+
+// * * * * * * * * * * * * * Protected Member Functions  * * * * * * * * * * //
+
+void Foam::prismatic2DRefinement::setRefinementInstruction
+(
+    batchPolyTopoChange& ref
+) const
+{
+    // Note: assumes that cellsToRefine_ are set prior to the function call
+
+    // Reset refinementLevelIndicator field. Note: the list is cleared in
+    // updateMesh member function after updating cell and point levels
+    if (refinementLevelIndicator_.empty())
+    {
+        // List has been reset correctly, initialise it for this iteration
+        refinementLevelIndicator_.setSize(mesh_.nCells(), UNCHANGED);
+    }
+    else
+    {
+        // List has not been reset correctly, issue an error
+        FatalErrorInFunction
+            << "Refinement level indicator list has not been"
+            << " reset properly." << nl
+            << "Either the call to updateMesh() after performing"
+            << " refinement has not been made or the call to"
+            << " setRefinementInstruction(...) and"
+            << " setUnrefinementInstruction(...) has not been made in"
+            << " correct order." << nl
+            << "Make sure to set refinement, set unrefinement and call"
+            << " updateMesh after performing the topo change."
+            << abort(FatalError);
+    }
+
+
+    // PART 1: Mark cells for refinement
+
+    // Bool list that marks cells which will be refined
+    boolList refineCellsMask(mesh_.nCells(), false);
+    forAll (cellsToRefine_, i)
+    {
+        // Simply mark the cell as refined, there are no additional points to
+        // add (in cell centre for example)
+        refineCellsMask[cellsToRefine_[i]] = true;
+    }
+
+    // Write out cells to refine as a cell set for debug
+    if (debug)
+    {
+        // Note: cellSet is actually a hash table of labels
+        cellSet splitCells(mesh_, "splitCells", cellsToRefine_.size());
+
+        forAll(refineCellsMask, cellI)
+        {
+            if (refineCellsMask[cellI])
+            {
+                // Cell is marked for refinement, insert into cellSet
+                splitCells.insert(cellI);
+            }
+        }
+
+        Pout<< "prismatic2DRefinement::setRefinementInstruction(...)" << nl
+            << "Writing " << splitCells.size()
+            << " cells to split to cellSet " << splitCells.objectPath()
+            << endl;
+
+        splitCells.write();
+    }
+
+
+    // PART 2: Mark edges for refinement and add points to edge centres
+
+    if (debug)
+    {
+        Pout<< "prismatic2DRefinement::setRefinementInstruction(...)" << nl
+            << "Allocating edge midpoints."
+            << endl;
+    }
+
+    // First mark faces and edges on special patches (empty or wedge). This data
+    // is used in PART 2 (collecting edges) and also PART 3 (collecting faces)
+    boolList faceOnPatchToCut(mesh_.nFaces(), false);
+    boolList edgeOnPatchToCut(mesh_.nEdges(), false);
+
+    // Get boundary
+    const polyBoundaryMesh& boundaryMesh = mesh_.boundaryMesh();
+
+    // Get face-edge addressing (for each face, a list of edges)
+    const labelListList& meshFaceEdges = mesh_.faceEdges();
+
+    // Loop through all patches
+    forAll (boundaryMesh, patchI)
+    {
+        // Get current patch
+        const polyPatch& curPatch = boundaryMesh[patchI];
+
+        // Check whether this patch is special (empty or wedge)
+        if (isA<emptyPolyPatch>(curPatch) || isA<wedgePolyPatch>(curPatch))
+        {
+            // Get start and end face labels
+            const label startFaceI = curPatch.start();
+            const label endFaceI = startFaceI + curPatch.size();
+
+            // Mark all the faces and edges on the patch
+            for (label faceI = startFaceI; faceI < endFaceI; ++faceI)
+            {
+                // Mark face
+                faceOnPatchToCut[faceI] = true;
+
+                // Get edges of this face
+                const labelList& curEdges = meshFaceEdges[faceI];
+
+                // Mark all edges
+                forAll (curEdges, i)
+                {
+                    edgeOnPatchToCut[curEdges[i]] = true;
+                }
+            }
+        }
+    }
+
+    // Now that we have marked faces and edges on special patches (empty or
+    // wedge), let's collect refined edges. Refined edges are defined by having
+    // both their point levels <= cell level, i.e. if any cell that gets split
+    // uses this edge and the edge is on special patch (empty or wedge), the
+    // edge needs to be split
+
+    // Get necessary mesh data
+    const labelListList& meshCellEdges = mesh_.cellEdges();
+    const edgeList& meshEdges = mesh_.edges();
+
+    // Mid points for refined edge:
+    // No need to split edge = -1
+    // Label of introduced mid point > -1
+    labelList edgeMidPoint(mesh_.nEdges(), -1);
+
+    // Note: Loop over refined cells
+    forAll (cellsToRefine_, i)
+    {
+        // Get cell index
+        const label& cellI = cellsToRefine_[i];
+
+        // Get edges of this cell
+        const labelList& cEdges = meshCellEdges[cellI];
+
+        forAll (cEdges, j)
+        {
+            // Get edge index and edge
+            const label& edgeI = cEdges[j];
+            const edge& e = meshEdges[edgeI];
+
+            if (edgeOnPatchToCut[edgeI])
+            {
+                // Edge is on special patch (empty or wedge), check whether it
+                // needs to be split
+                if
+                (
+                    pointLevel_[e[0]] <= cellLevel_[cellI]
+                 && pointLevel_[e[1]] <= cellLevel_[cellI]
+                )
+                {
+                    // Point levels of both edge points are <= cell level, mark
+                    // edge for splitting
+                    edgeMidPoint[edgeI] = 12345;
+                }
+            }
+            // Else nothing to do: can't split edges that are not on special
+            // patch (empty or wedge)
+        } // End for all edges of the refined cell
+    } // End for all cells
+
+    // Synchronize edgeMidPoint across coupled patches. Note: use max so that
+    // any split takes precedence.
+    syncTools::syncEdgeList
+    (
+        mesh_,
+        edgeMidPoint,
+        maxEqOp<label>(),
+        labelMin
+    );
+
+    // Now that the refinement trigger is synced, introduce edge points
+
+    // Get necessary mesh data
+    const pointField& meshPoints = mesh_.points();
+
+    // Memory management
+    {
+        // Phase 1: calculate midpoints and sync. This is necessary if we don't
+        // use binary format for writing and we slowly get differences.
+
+        // Allocate storage for edge points
+        pointField edgeMids(mesh_.nEdges(), point(-GREAT, -GREAT, -GREAT));
+
+        forAll(edgeMidPoint, edgeI)
+        {
+            if (edgeMidPoint[edgeI] > -1)
+            {
+                // Edge marked to be split. Get edge centre
+                edgeMids[edgeI] = meshEdges[edgeI].centre(meshPoints);
+            }
+        }
+
+        // Sync across processor boundaries
+        syncTools::syncEdgeList
+        (
+            mesh_,
+            edgeMids,
+            maxEqOp<vector>(),
+            point(-GREAT, -GREAT, -GREAT)
+        );
+
+        // Phase 2: introduce points at the synced locations.
+        forAll(edgeMidPoint, edgeI)
+        {
+            if (edgeMidPoint[edgeI] > -1)
+            {
+                edgeMidPoint[edgeI] = ref.setAction
+                (
+                    polyAddPoint
+                    (
+                        edgeMids[edgeI], // Point
+                        -1,              // Appended point, no master ID
+                        -1,              // Zone for point
+                        true             // Supports a cell
+                    )
+                );
+            }
+        }
+    } // End memory management for syncing/adding edge points
+
+    // Write out edge mid points for split edges for debugging
+    if (debug)
+    {
+        OFstream str(mesh_.time().path()/"edgeMidPoint.obj");
+
+        forAll(edgeMidPoint, edgeI)
+        {
+            if (edgeMidPoint[edgeI] > -1)
+            {
+                // Get edge and write its cell centre
+                const edge& e = meshEdges[edgeI];
+                meshTools::writeOBJ(str, e.centre(meshPoints));
+            }
+        }
+
+        Pout<< "prismatic2DRefinement::setRefinementInstruction(...)" << nl
+            << "Writing centres of edges to split to file " << str.name()
+            << endl;
+    }
+
+
+    // PART 3: Collect faces for refinement. Faces need to be collected in two
+    // distinct categories:
+    // 1. Faces found on special patches (empty or wedge) that will be split
+    //    into n faces (where n is the number of edges per face),
+    // 2. Faces not on special patches (empty or wedge) that will be always
+    //    split into two faces. For each of these faces, collect the two edges
+    //    found on opposing sides of the special patch (empty or wedge).
+
+    if (debug)
+    {
+        Pout<< "prismatic2DRefinement::setRefinementInstruction" << nl
+            << "Allocating face midpoints and collecting faces that are"
+            << " not on special patch (empty or wedge)."
+            << endl;
+    }
+
+    // Get face anchor level based on the face type. For split face found on
+    // special patch (empty or wedge), it is guaranteeed that we will have at
+    // least 3 points with level <= anchor level. For split face not on special
+    // (empty or wedge patch), it is guaranteed that we will have at least 4
+    // points with level <= anchor level. These are the corner points.
+    labelList faceAnchorLevel(mesh_.nFaces());
+    for (label faceI = 0; faceI < mesh_.nFaces(); ++faceI)
+    {
+        if (faceOnPatchToCut[faceI])
+        {
+            // Face on special patch, at least 3 points need to have
+            // level <= anchor level
+            faceAnchorLevel[faceI] = getAnchorLevel(faceI, 3);
+        }
+        else
+        {
+            // Face not on special patch, at least 4 points need to have
+            // level <= anchor level
+            faceAnchorLevel[faceI] = getAnchorLevel(faceI, 4);
+        }
+    }
+
+    // Split faces on special patches (empty or wedge) will be collected in
+    // faceMidPoint list:
+    // Not refined = -1
+    // Shall be refined > -1 (label of added mid point)
+    labelList faceMidPoint(mesh_.nFaces(), -1);
+
+    // Split faces not on special patches (empty or wedge) will be collected
+    // into splitFacesIntoTwo dynamic list. For each of these faces, we also
+    // need to collect its two edges that are found on special patch (empty or
+    // wedge)
+
+    // Allocate enough storage to prevent excessive resizing
+    const label nSplitFacesIntoTwo = 3*cellsToRefine_.size();
+    DynamicList<label> splitFacesIntoTwo(nSplitFacesIntoTwo);
+    DynamicList<Pair<label> > splitFacesEmptyEdges(nSplitFacesIntoTwo);
+
+    // Get necessary mesh data
+    const labelList& meshFaceOwner = mesh_.faceOwner();
+    const labelList& meshFaceNeighbour = mesh_.faceNeighbour();
+
+    const label nFaces = mesh_.nFaces();
+    const label nInternalFaces = mesh_.nInternalFaces();
+
+    // Internal faces: look at cells on both sides. Uniquely determined since
+    // the face itself is guaranteed to be same level as most refined neighbour
+    for (label faceI = 0; faceI < nInternalFaces; ++faceI)
+    {
+        // Get owner data
+        const label& own = meshFaceOwner[faceI];
+        const label& ownLevel = cellLevel_[own];
+        const label newOwnLevel = ownLevel + (refineCellsMask[own] ? 1 : 0);
+
+        // Get neighbour data
+        const label& nei = meshFaceNeighbour[faceI];
+        const label& neiLevel = cellLevel_[nei];
+        const label newNeiLevel = neiLevel + (refineCellsMask[nei] ? 1 : 0);
+
+        if
+        (
+            newOwnLevel > faceAnchorLevel[faceI]
+         || newNeiLevel > faceAnchorLevel[faceI]
+        )
+        {
+            // Note: this is internal face so we don't need to check whether the
+            // face is on special patch (empty or wedge). It can't be by
+            // definition
+
+            // Does two things:
+            // 1. Appends the face to splitFacesIntoTwo list
+            // 2. Append the two edges on special patch (empty or wedge) to
+            //    splitFaceEmptyEdges list
+            appendFaceSplitInfo
+            (
+                faceI,
+                edgeOnPatchToCut,
+                edgeMidPoint,
+                splitFacesIntoTwo,
+                splitFacesEmptyEdges
+            );
+        } // End whether the face needs to be considered (split)
+    } // End loop over all internal faces
+
+    // Coupled patches handled like internal faces except now all information
+    // from neighbour comes from across processor.
+    // Boundary faces are more complicated since the boundary face can
+    // be more refined than its owner (or neighbour for coupled patches)
+    // (does not happen if refining/unrefining only, but does e.g. when
+    // refinining and subsetting)
+
+    // Memory management
+    {
+        // Create list for swapping boundary data
+        labelList newNeiLevel(nFaces - nInternalFaces);
+
+        forAll(newNeiLevel, i)
+        {
+            const label& own = meshFaceOwner[i + nInternalFaces];
+            const label& ownLevel = cellLevel_[own];
+            const label newOwnLevel = ownLevel + (refineCellsMask[own] ? 1 : 0);
+
+            newNeiLevel[i] = newOwnLevel;
+        }
+
+        // Swap the list which now contains data from the other side
+        syncTools::swapBoundaryFaceList(mesh_, newNeiLevel);
+
+        forAll(newNeiLevel, i)
+        {
+            // Get face index
+            const label faceI = i + nInternalFaces;
+
+            // Get owner data (neighbour is available from before)
+            const label& own = meshFaceOwner[faceI];
+            const label& ownLevel = cellLevel_[own];
+            const label newOwnLevel = ownLevel + (refineCellsMask[own] ? 1 : 0);
+
+            if
+            (
+                newOwnLevel > faceAnchorLevel[faceI]
+             || newNeiLevel[i] > faceAnchorLevel[faceI]
+            )
+            {
+                if (faceOnPatchToCut[faceI])
+                {
+                    // This face is on the special patch (empty or wedge) and
+                    // will be split into n faces (n is the number of edges for
+                    // this face) and the face mid point will be added. Mark the
+                    // face for splitting
+                    faceMidPoint[faceI] = 12345;
+                }
+                else
+                {
+                    // Does two things:
+                    // 1. Appends the face to splitFacesIntoTwo list
+                    // 2. Append the two edges on special patch (empty or wedge)
+                    //    to splitFaceEmptyEdges list
+                    appendFaceSplitInfo
+                    (
+                        faceI,
+                        edgeOnPatchToCut,
+                        edgeMidPoint,
+                        splitFacesIntoTwo,
+                        splitFacesEmptyEdges
+                    );
+                } // End if the face is not on special patch (empty or wedge)
+            } // End whether the face needs to be considered
+        } // End loop over all boundary faces
+    } // End memory management for syncing owner/neighbour face levels
+
+
+    // Add face points. Note: no need to sync face mid points (as we did for
+    // edge mid points) since processor faces do not introduce new points, only
+    // faces on special patch (empty or wedge) do
+
+    // Get face centres
+    const vectorField& meshFaceCentres = mesh_.faceCentres();
+
+    // Loop through faces on special patches (empty or wedge) only
+    forAll (boundaryMesh, patchI)
+    {
+        // Get current patch
+        const polyPatch& curPatch = boundaryMesh[patchI];
+
+        // Check whether this patch is special (empty or wedge)
+        if (isA<emptyPolyPatch>(curPatch) || isA<wedgePolyPatch>(curPatch))
+        {
+            // Get start and face labels
+            const label startFaceI = curPatch.start();
+            const label endFaceI = startFaceI + curPatch.size();
+
+            // Loop through all special patch faces (global indexing)
+            for (label faceI = startFaceI; faceI < endFaceI; ++faceI)
+            {
+                if (faceMidPoint[faceI] > -1)
+                {
+                    // Face on special patch (empty or wedge) marked to be
+                    // split. Add the point at face centre and replace
+                    // faceMidPoint with new point label
+
+                    faceMidPoint[faceI] = ref.setAction
+                    (
+                        polyAddPoint
+                        (
+                            meshFaceCentres[faceI], // Point
+                            -1,                     // No master ID
+                            -1,                     // Zone for point
+                            true                    // Supports a cell
+                        )
+                    );
+                } // End if face marked for splitting
+            } // End loop over all faces on special patch (empty or wedge)
+        } // End if special patch check
+    } // End loop for all patches
+
+    // Write out all split faces as a face set for debugging
+    if (debug)
+    {
+        // Create faceSet containing all faces that need to be split into n
+        // faces (n is the number of edges on the face)
+        faceSet splitNFaces(mesh_, "splitNFaces", 3*cellsToRefine_.size());
+
+        forAll(faceMidPoint, faceI)
+        {
+            if (faceMidPoint[faceI] > -1)
+            {
+                splitNFaces.insert(faceI);
+            }
+        }
+
+        Pout<< "prismatic2DRefinement::setRefinementInstruction(...)" << nl
+            << "Writing " << splitNFaces.size()
+            << " faces to split in N to faceSet " << splitNFaces.objectPath()
+            << endl;
+
+        splitNFaces.write();
+
+        // Create faceSet containing all faces that need to be split into 2
+        // faces (faces not on special patch: empty or wedge)
+        faceSet splitTwoFaces(mesh_, "splitTwoFaces", 3*cellsToRefine_.size());
+
+        forAll (splitFacesIntoTwo, i)
+        {
+            // Insert face index into splitTwoFaces
+            splitTwoFaces.insert(splitFacesIntoTwo[i]);
+        }
+
+        Pout<< "prismatic2DRefinement::setRefinementInstruction(...)" << nl
+            << "Writing " << splitTwoFaces.size()
+            << " faces to split in two to faceSet "
+            << splitTwoFaces.objectPath() << endl;
+
+        splitTwoFaces.write();
+    }
+
+
+    // Now we have all the information we need to perform the refinement and we
+    // no longer need to refer to cellsToRefine_. The information is in:
+    // - refineCellsMask = true : cell needs to be split
+    // - edgeMidPoint >= 0     : edge on special patch that needs to be split
+    // - faceMidPoint >= 0     : face on special patch that needs to be split
+    //                           into n faces (where n is the number of edges)
+    // - splitFacesIntoTwo     : list of faces that need to be split into two
+    //                           (face not on special patch)
+    // - splitFacesEmptyEdges  : holds the two edges of the face which needs to
+    //                           be split into two
+
+
+    // PART 4: Get corner and anchor points for all cells
+
+    if (debug)
+    {
+        Pout<< "prismatic2DRefinement::setRefinementInstruction(...)" << nl
+            << "Finding cell anchorPoints" << endl;
+    }
+
+    // Get anchor points for each cell: points that have the same or lower
+    // refinement level as the cell
+    List<DynamicList<label>> cellAnchorPointsDynamic(mesh_.nCells());
+
+    // Loop through all cells
+    forAll(refineCellsMask, cellI)
+    {
+        if (refineCellsMask[cellI])
+        {
+            // The cell will be refined, set capacity to 8 to prevent excessive
+            // resizing
+            cellAnchorPointsDynamic[cellI].setCapacity(8);
+        }
+    }
+
+    // Get necessary mesh data
+    const labelListList& meshPointCells = mesh_.pointCells();
+
+    // Loop through all points
+    forAll(pointLevel_, pointI)
+    {
+        // Get point cells
+        const labelList& pCells = meshPointCells[pointI];
+
+        // Loop through all cells sharing this point
+        forAll(pCells, pCellI)
+        {
+            // Get current cell index
+            const label& cellI = pCells[pCellI];
+
+            if
+            (
+                refineCellsMask[cellI]
+             && pointLevel_[pointI] <= cellLevel_[cellI]
+            )
+            {
+                // This point cell is marked for refinement and its point level
+                // is smaller or equal to cell level, append the point
+                cellAnchorPointsDynamic[cellI].append(pointI);
+            }
+        }
+    }
+
+    // Loop through all cells and check whether at least 6 anchor points
+    // have been found (minimum requirement for a triangular prism)
+
+    // Collect cellAnchorPoint into a List<labelList> instead of
+    // List<dynamicList>
+    labelListList cellAnchorPoints(mesh_.nCells());
+
+    // Get cell points for error output
+    const labelListList& meshCellPoints = mesh_.cellPoints();
+
+    forAll(refineCellsMask, cellI)
+    {
+        // First some sanity checks
+        if (refineCellsMask[cellI])
+        {
+            // Cell selected for refinement
+            if (cellAnchorPointsDynamic[cellI].size() < 6)
+            {
+                // Cell has less than 6 anchor points. Issue an error and report
+                // cell points
+                const labelList& cPoints = meshCellPoints[cellI];
+
+                FatalErrorInFunction
+                    << "Cell " << cellI
+                    << " of level " << cellLevel_[cellI]
+                    << " does not seem to have enough points of "
+                    << " lower level" << endl
+                    << "cellPoints:" << cPoints << endl
+                    << "pointLevels:"
+                    << IndirectList<label>(pointLevel_, cPoints)() << endl
+                    << abort(FatalError);
+            }
+            else if (cellAnchorPointsDynamic[cellI].size() % 2 != 0)
+            {
+                // Cell has odd number of anchor points. This is not allowed and
+                // indicates an invalid mesh
+                const labelList& cPoints = meshCellPoints[cellI];
+
+                FatalErrorInFunction
+                    << "Cell " << cellI
+                    << " of level " << cellLevel_[cellI]
+                    << " has odd number of anchor points"
+                    << " (should be even for 2D mesh). "
+                    << "cellPoints:" << cPoints << endl
+                    << "pointLevels:"
+                    << IndirectList<label>(pointLevel_, cPoints)() << endl
+                    << abort(FatalError);
+            }
+        }
+
+        // Tranfer the dynamic list for each cell into an ordinary list
+        cellAnchorPoints[cellI].transfer(cellAnchorPointsDynamic[cellI]);
+    }
+
+
+    // PART 5: Add the cells
+
+    if (debug)
+    {
+        Pout<< "prismatic2DRefinement::setRefinementInstruction(...)" << nl
+            << " Adding cells."
+            << endl;
+    }
+
+    // We should have exactly n new cells per each split cell, where n is the
+    // number of anchor points in a cell divided by two. In order to determine
+    // owner/neighbours of new and modified faces, we need to know which cell
+    // came from which point. The mapping is not uniquely defined as in
+    // polyhedralRefinement when we had 1 point = 1 cell. Here, we have two
+    // points that correspond to a single cell, one on one side of the special
+    // patch and the other on other side. This information will be collected in
+    // a HashTable<label, Pair<label> >, where the key will be a pair of
+    // global point index and global cell index, while the value is local index
+    // into cellAddedCells list
+    labelListList cellAddedCells(mesh_.nCells());
+    HashTable<label, Pair<label>, Hash<FixedList<label, 2> > >
+        pointCellToAddedCellMap(6*cellsToRefine_.size());
+
+    // Get mesh data
+    const cellZoneMesh& cellZones = mesh_.cellZones();
+    const cellList& meshCells = mesh_.cells();
+    const faceList& meshFaces = mesh_.faces();
+    const labelListList& meshPointEdges = mesh_.pointEdges();
+
+
+    // Loop through all faces
+    forAll(faceMidPoint, faceI)
+    {
+        // Check whether this face needs to be split into n faces
+        if (faceMidPoint[faceI] > -1)
+        {
+            // This is a face that will be split and is on special patch (empty
+            // or wedge) by definition, get the cell index by looking at owner
+            // only
+            const label& cellI = meshFaceOwner[faceI];
+
+            // Get cell added cells
+            labelList& cAdded = cellAddedCells[cellI];
+
+            // Check whether the cell added cells are empty. This means that we
+            // haven't visited the first face yet. If it is not empty, we have
+            // already visited one face, which is enough
+            if (cAdded.empty())
+            {
+                // First face that hasn't been visited. Start adding cells
+                // point-by-point and keep track of mapping necessary for
+                // splitting other (not on special patch: empty or wedge) faces
+                // into two
+
+                // Set the total number of added cells to number of anchors
+                // divided by two. Note: number of anchors needs to be an even
+                // number (6 for triangular prism, 8 for hex, 10 for pentagonal
+                // prism, etc.)
+                const labelList& cAnchors = cellAnchorPoints[cellI];
+                cAdded.setSize(cAnchors.size()/2);
+
+                // Helper variable to distinguish between first and successive
+                // cells (first will have the original index)
+                label cellCounter = 0;
+
+                // Get current face
+                const face& f = meshFaces[faceI];
+
+                // Loop through face points
+                forAll (f, fpI)
+                {
+                    // Get point index
+                    const label& pointI = f[fpI];
+
+                    // Find anchor point in local list if present
+                    const label anchorI = cAnchors.find(pointI);
+
+                    if (anchorI != -1)
+                    {
+                        // This point is anchor, add the cell
+
+                        if (cellCounter == 0)
+                        {
+                            // This is first cell, simply set the existing index
+                            cAdded[cellCounter] = cellI;
+
+                            // Update refinement level indicator field to 1
+                            // since the original cell will be refined
+                            refinementLevelIndicator_[cellI] = REFINED;
+                        }
+                        else
+                        {
+                            // Other cells, need to add the cells
+                            cAdded[cellCounter] = ref.setAction
+                            (
+                                polyAddCell
+                                (
+                                    -1,                         // M. point
+                                    -1,                         // M. edge
+                                    -1,                         // M. face
+                                    cellI,                      // M. cell
+                                    cellZones.whichZone(cellI)  // M. zone
+                                )
+                            );
+                        }
+
+                        // Collect the point-cell mapping into local index
+                        // of cell added cells for point on this side
+                        pointCellToAddedCellMap.insert
+                        (
+                            Pair<label>(pointI, cellI),
+                            cellCounter
+                        );
+
+                        // This is only one side, we need to also collect
+                        // the other point on the other side. Get point edges
+                        // for this point
+                        const labelList& pEdges =
+                            meshPointEdges[pointI];
+
+                        // Loop through point edges
+                        forAll (pEdges, peI)
+                        {
+                            // Get the edge index
+                            const label& edgeI = pEdges[peI];
+
+                            if (!edgeOnPatchToCut[edgeI])
+                            {
+                                // Edge is not on special patch (empty or
+                                // wedge), therefore this is the edge we're
+                                // looking for. Collect the other point of the
+                                // edge
+                                const label pointJ =
+                                    meshEdges[edgeI].otherVertex(pointI);
+
+                                // Collect the point-cell mapping into local
+                                // index of cell added cells for this point
+                                pointCellToAddedCellMap.insert
+                                (
+                                    Pair<label>(pointJ, cellI),
+                                    cellCounter
+                                );
+                            }
+                        }
+
+                        // Now we have finished adding the cell and also
+                        // adding the necessary mapping for this added cell
+
+                        // Increment cell counter
+                        ++cellCounter;
+
+                    } // Else point is not anchor: nothing to do
+                } // End loop over all face points
+
+                // Sanity check: number of counted cells must be
+                // equal to size of cellAddedCells. This means that
+                // we have correctly marked the anchor points
+                if (cellCounter != cAdded.size())
+                {
+                    FatalErrorInFunction
+                        << "Problem while adding cells."
+                        << nl
+                        << "Going through base face on special patch"
+                        << " (empty or wedge) and adding cells, we collected: "
+                        << cellCounter << " cells."
+                        << nl
+                        << "While the number of anchor points is: "
+                        << cAnchors.size()
+                        << nl
+                        << "The number of added cells based on number of anchor"
+                        << " points is: "
+                        << cAdded.size()
+                        << nl
+                        << "Additional information: "
+                        << nl
+                        << "cellI: " << cellI << ", faceI: " << faceI
+                        << abort(FatalError);
+                }
+
+            } // End if cell added cells empty
+        } // End if face needs to be split into n
+    } // End loop over all faces
+
+
+    // PART 6: Adding faces
+
+    // 6.1. Existing faces on special patches (empty or wedge) that get split
+    //      (into n faces where n is the number of points or edges)
+    // 6.2. Existing faces not on special patches that get split into two
+    // 6.3. Existing faces that do not get split but only edges get split
+    // 6.4. Existing faces that do not get split but get new owner/neighbour
+    // 6.5. New internal faces inside split cells
+
+    if (debug)
+    {
+        Pout<< "prismatic2DRefinement::setRefinementInstruction(...)" << nl
+            << " Marking faces to be handled"
+            << endl;
+    }
+
+    // Get all faces to split:
+    // a) All faces of a cell being split
+    // b) All faces on special patch that are being split
+    // c) All faces not on special patch that are being split
+    // d) Both faces of an edge that is being split
+    // Note: although a bit redundant, loop over everything above
+    boolList facesToSplit(mesh_.nFaces(), false);
+
+    // Also collect all split faces which will be needed in 6.3
+    boolList allSplitFaces(mesh_.nFaces(), false);
+
+    // Get edge faces
+    const labelListList& meshEdgeFaces = mesh_.edgeFaces();
+
+    // a) All faces of a cell that is being split
+    forAll(refineCellsMask, cellI)
+    {
+        if (refineCellsMask[cellI])
+        {
+            const cell& cFaces = meshCells[cellI];
+
+            forAll(cFaces, i)
+            {
+                facesToSplit[cFaces[i]] = true;
+            }
+        }
+    }
+
+    // b) All faces on special patch that are being split
+    forAll(faceMidPoint, faceI)
+    {
+        if (faceMidPoint[faceI] > -1)
+        {
+            // Mark face in both lists
+            facesToSplit[faceI] = true; // Used through 6.1-6.5
+            allSplitFaces[faceI] = true; // Used in 6.3
+        }
+    }
+
+    // c) All faces not on special patch that are being split
+    forAll(splitFacesIntoTwo, i)
+    {
+        // Get face index
+        const label& faceI = splitFacesIntoTwo[i];
+
+        // Mark face in both lists
+        facesToSplit[faceI] = true;
+        allSplitFaces[faceI] = true;
+    }
+
+    // d) Both faces of an edge that is being split
+    forAll(edgeMidPoint, edgeI)
+    {
+        if (edgeMidPoint[edgeI] > -1)
+        {
+            const labelList& eFaces = meshEdgeFaces[edgeI];
+
+            forAll(eFaces, i)
+            {
+                facesToSplit[eFaces[i]] = true;
+            }
+        }
+    }
+
+    // Note: after splitting a certain face during parts 6.1. to 6.4.,
+    // facesToSplit for that face will be set back to 0, i.e. marked as finished
+
+
+    // PART 6.1. Add/modify faces for each face on special patch that is being
+    // split
+
+    if (debug)
+    {
+        Pout<< "prismatic2DRefinement::setRefinementInstruction(...)" << endl
+            << "Splitting faces on special patches (empty or wedge)..." << endl;
+    }
+
+    forAll(faceMidPoint, faceI)
+    {
+        if (faceMidPoint[faceI] > -1 && facesToSplit[faceI])
+        {
+            // Face has not been split.
+            // Note: although facesToSplit can't be different than 1 here and
+            // the second check can be ommitted, it is left for clarity
+
+            // Get the face
+            const face& f = meshFaces[faceI];
+
+            // Flag to control whether the original faceI has been used
+            // Note: original face gets modified, other n - 1 faces are added,
+            // where n is the number of points/edges of a face
+            bool modifiedFace = false;
+
+            // Get anchor level for the face
+            const label& anchorLevel = faceAnchorLevel[faceI];
+
+            // New face always has four points/edges for arbitrary polygon
+            face newFace(4);
+
+            // Loop through all points of original face
+            forAll(f, fpI)
+            {
+                const label& pointI = f[fpI];
+
+                if (pointLevel_[pointI] <= anchorLevel)
+                {
+                    // This point is anchor, start collecting face
+
+                    // Create dynamic list (because of append) for face vertices
+                    // and append the first (anchor) point
+                    DynamicList<label> faceVerts(4);
+                    faceVerts.append(pointI);
+
+                    // Walk forward to mid point.
+                    // - if next is +2 midpoint is +1
+                    // - if next is +1 it is midpoint
+                    // - if next is +0 there has to be edgeMidPoint
+
+                    // Appends all points from this point to face mid point
+                    walkFaceToMid
+                    (
+                        edgeMidPoint,
+                        anchorLevel,
+                        faceI,
+                        fpI,
+                        faceVerts
+                    );
+
+                    // Append face mid point
+                    faceVerts.append(faceMidPoint[faceI]);
+
+                    // Append all points from face mid point to starting point
+                    walkFaceFromMid
+                    (
+                        edgeMidPoint,
+                        anchorLevel,
+                        faceI,
+                        fpI,
+                        faceVerts
+                    );
+
+                    // Transfer dynamic list to a face (ordinary list)
+                    newFace.transfer(faceVerts);
+                    faceVerts.clear();
+
+                    // Set new owner/neighbour indices based on split cells
+                    label own, nei;
+                    setNewFaceNeighbours
+                    (
+                        pointCellToAddedCellMap,
+                        cellAddedCells,
+                        faceI,
+                        pointI, // Anchor point index
+
+                        own,
+                        nei
+                    );
+
+                    if (debug)
+                    {
+                        // Check orientation of the split face for debugging
+                        checkNewFaceOrientation(ref, faceI, newFace);
+                    }
+
+
+                    // Finally insert the modification/addition instruction into
+                    // the topo changer engine
+                    if (!modifiedFace)
+                    {
+                        // Modify first face
+                        modifiedFace = true;
+                        modifyFace(ref, faceI, newFace, own, nei);
+                    }
+                    else
+                    {
+                        // Add additional faces
+                        addFace(ref, faceI, newFace, own, nei);
+                    }
+                } // End point anchor check
+            } // End for all points
+
+            // Mark face as handled
+            facesToSplit[faceI] = false;
+
+        } // End if this face needs to be split
+    } // End for all faces
+
+
+    // PART 6.2. Add/modify faces for each face not on special patch that is
+    // being split into two
+
+    if (debug)
+    {
+        Pout<< "prismatic2DRefinement::setRefinementInstruction(...)" << nl
+            << "Splitting faces not on special patches (empty or wedge)..."
+            << endl;
+    }
+
+    // Loop through faces that are not on special patch. These will be split
+    // into two faces only
+    forAll (splitFacesIntoTwo, i)
+    {
+        // Get face index
+        const label& faceI = splitFacesIntoTwo[i];
+
+        // Check whether the face is marked for splitting. A bit redundant but
+        // will be left for clarity
+        if (facesToSplit[faceI])
+        {
+            // Face has not been split, grab the face
+            const face& f = meshFaces[faceI];
+
+            // Additional sanity check
+            if (f.size() != 4)
+            {
+                FatalErrorInFunction
+                    << "The original face has: " << f.size() << " points,"
+                    << " while it should have exactly 4 points in order"
+                    << " to split it in two."
+                    << " faceI: " << faceI
+                    << abort(FatalError);
+            }
+
+            // Flag to control whether the original faceI has been used
+            // Note: original face gets modified, other face gets added
+            bool modifiedFace = false;
+
+            // Get anchor level for the face
+            const label& anchorLevel = faceAnchorLevel[faceI];
+
+            // Mark visited points to avoid adding the face twice
+            boolList visitedPoint(f.size(), false);
+
+            // New face always has four points/edges for 2D face splitting of
+            // a face that is not on special patch
+            face newFace(4);
+
+            // Loop through all points of original face
+            forAll (f, fpI)
+            {
+                // Get point index
+                const label& pointI = f[fpI];
+
+                if
+                (
+                    !visitedPoint[fpI]
+                 && pointLevel_[pointI] <= anchorLevel
+                )
+                {
+                    // This point is anchor and it hasn't been visited yet,
+                    // start collecting face
+
+                    // Collect the new face in the following order:
+                    // 1. This point
+                    // 2. Edge mid points for the edge that contains this point
+                    //    and the edge that is on the other side
+                    // 3. Other point (on the other side)
+
+                    // 1. Set this point and mark it as visited
+                    newFace[0] = pointI;
+                    visitedPoint[fpI] = true;
+
+                    // 2. Get edge mid point for edge containing this point
+                    // Fetch the two edges on both sides
+                    const Pair<label>& edgesOnOppositeSides =
+                        splitFacesEmptyEdges[i];
+
+                    // Get the edge indices and edges
+                    const label& edgeIndexI = edgesOnOppositeSides.first();
+                    const label& edgeIndexJ = edgesOnOppositeSides.second();
+
+                    const edge& edgeI = meshEdges[edgeIndexI];
+                    const edge& edgeJ = meshEdges[edgeIndexJ];
+
+                    // Additional sanity check
+                    if
+                    (
+                        (edgeMidPoint[edgeIndexI] == -1)
+                     || (edgeMidPoint[edgeIndexJ] == -1)
+                    )
+                    {
+                        // Edges are not marked for refinement, issue an error
+                        FatalErrorInFunction
+                            << "Trying to split a face into two, but"
+                            << " edges on special patches (empty or wedge)"
+                            << " are not properly set."
+                            << nl
+                            << "Edge: " << edgeIndexI << " with new point: "
+                            << edgeMidPoint[edgeIndexI]
+                            << nl
+                            << "Edge: " << edgeIndexJ << " with new point: "
+                            << edgeMidPoint[edgeIndexJ]
+                            << abort(FatalError);
+                    }
+
+                    if ((edgeI.start() == pointI) || (edgeI.end() == pointI))
+                    {
+                        // Current point is on edgeI, set edgeI midpoint and
+                        // then edgeJ midpoint
+                        newFace[1] = edgeMidPoint[edgeIndexI];
+                        newFace[2] = edgeMidPoint[edgeIndexJ];
+                    }
+                    else if
+                    (
+                        (edgeJ.start() == pointI) || (edgeJ.end() == pointI)
+                    )
+                    {
+                        // Current is on edgeJ, set edgeJ midpoint and then
+                        // edgeI midpoint
+                        newFace[1] = edgeMidPoint[edgeIndexJ];
+                        newFace[2] = edgeMidPoint[edgeIndexI];
+                    }
+                    else
+                    {
+                        // Point not on either of edges, issue an error
+                        FatalErrorInFunction
+                            << "Trying to split a face into two, but"
+                            << " the point: " << pointI << " can't be found"
+                            << " on either of edges. "
+                            << nl
+                            << "Edge: " << edgeIndexI << " with new point: "
+                            << edgeMidPoint[edgeIndexI]
+                            << nl
+                            << "Edge: " << edgeIndexJ << " with new point: "
+                            << edgeMidPoint[edgeIndexJ]
+                            << abort(FatalError);
+                    }
+
+                    // At this point, we have added three points: original
+                    // point, first edge mid point and second edge mid point.
+
+                    // 3. Add the other point
+                    // Get point edges for this point
+                    const labelList& pEdges = meshPointEdges[pointI];
+
+                    // Loop through all edges
+                    forAll (pEdges, peI)
+                    {
+                        // Get the edge index
+                        const label& pointEdgeI = pEdges[peI];
+
+                        if (!edgeOnPatchToCut[pointEdgeI])
+                        {
+                            // Edge is not on special patch (empty or wedge),
+                            // therefore this is the edge we're looking for.
+                            // Collect the other point
+                            const label pointJ =
+                                meshEdges[pointEdgeI].otherVertex(pointI);
+
+                            // Insert the point into the face at the last
+                            // location
+                            newFace[3] = pointJ;
+
+                            // Mask local point index as visited by going
+                            // through the face again
+                            forAll (f, fpJ)
+                            {
+                                if (f[fpJ] == pointJ)
+                                {
+                                    // Found local index of the point, mask it
+                                    visitedPoint[fpJ] = true;
+                                }
+                            }
+                        }
+                    }
+
+                    // The face is now complete, set new owner/neighbour indices
+                    // based on split cells
+                    label own, nei;
+
+                    // Set new face owner/neighbour pair
+                    setNewFaceNeighbours
+                    (
+                        pointCellToAddedCellMap,
+                        cellAddedCells,
+                        faceI,
+                        pointI, // Anchor point index
+
+                        own,
+                        nei
+                    );
+
+                    // We need to revert the face if the edge between this point
+                    // and the next point is not split. This follows from
+                    // definition of face as ordered set of points (defining
+                    // orientation) and the splitting procedure. Note: edge
+                    // ordering in face is the same as point ordering so the
+                    // point index can be used as first face edge index
+                    if (edgeMidPoint[meshFaceEdges[faceI][fpI]] == -1)
+                    {
+                        newFace = newFace.reverseFace();
+                    }
+
+                    if (debug)
+                    {
+                        // Check orientation of the split face for debugging
+                        checkNewFaceOrientation(ref, faceI, newFace);
+                    }
+
+
+                    // Finally insert the modification/addition instruction into
+                    // the topo changer engine
+                    if (!modifiedFace)
+                    {
+                        // Modify first face
+                        modifiedFace = true;
+                        modifyFace(ref, faceI, newFace, own, nei);
+                    }
+                    else
+                    {
+                        // Add additional faces
+                        addFace(ref, faceI, newFace, own, nei);
+                    }
+                } // End if point is anchored and has not been visited
+            } // End loop over all face points
+
+            // Mark face as handled
+            facesToSplit[faceI] = false;
+
+        } // End if face is split
+    } // End for all faces that should be split into two
+
+
+    // PART 6.3. Modify faces that do not get split but have edges that are
+    // being split
+
+    if (debug)
+    {
+        Pout<< "prismatic2DRefinement::setRefinementInstruction(...)" << nl
+            << "Modifying faces with split edges..."
+            << endl;
+    }
+
+    forAll(edgeMidPoint, edgeI)
+    {
+        if (edgeMidPoint[edgeI] > -1)
+        {
+            // This is an edge that is going to be split, get edge faces
+            const labelList& eFaces = meshEdgeFaces[edgeI];
+
+            // Loop through all faces of an edge
+            forAll(eFaces, i)
+            {
+                // Get face index
+                const label faceI = eFaces[i];
+
+                // Check whether this is not a face that's been split and that
+                // the face has not been handled yet. The second check is
+                // necessary since we go through edge faces instead of just
+                // faces
+                if (!allSplitFaces[faceI] && facesToSplit[faceI])
+                {
+                    // This is unsplit face that has not been handled
+
+                    // Get face and face edges
+                    const face& f = meshFaces[faceI];
+                    const labelList& fEdges = meshFaceEdges[faceI];
+
+                    // Create a dynamic list containing new face vertices
+                    DynamicList<label> newFaceVerts(f.size());
+
+                    // Append all original points and all edge mid points
+                    forAll(f, fpI)
+                    {
+                        newFaceVerts.append(f[fpI]);
+
+                        const label edgeI = fEdges[fpI];
+
+                        if (edgeMidPoint[edgeI] > -1)
+                        {
+                            newFaceVerts.append(edgeMidPoint[edgeI]);
+                        }
+                    }
+
+                    // Create a face from dynamic list by transfer
+                    face newFace(newFaceVerts);
+
+
+                    // The point with the lowest level should be an anchor
+                    // point of the neighbouring cells.
+                    const label anchorFpI = findMinLevel(f);
+
+                    label own, nei;
+                    setNewFaceNeighbours
+                    (
+                        pointCellToAddedCellMap,
+                        cellAddedCells,
+                        faceI,
+                        f[anchorFpI], // Anchor point index
+
+                        own,
+                        nei
+                    );
+
+
+                    if (debug)
+                    {
+                        // Check orientation of the new face for debugging
+                        checkNewFaceOrientation(ref, faceI, newFace);
+                    }
+
+                    // Modify the face
+                    modifyFace(ref, faceI, newFace, own, nei);
+
+                    // Mark face as handled
+                    facesToSplit[faceI] = false;
+
+                } // End if unsplit, unhandled face
+            } // End for all edge faces
+        } // End if edge has been cut
+    } // End for all edges
+
+
+    // PART 6.4: Modify faces that do not get split but whose owner/neighbour
+    // change due to splitting
+
+    if (debug)
+    {
+        Pout<< "prismatic2DRefinement::setRefinementInstruction(...)" << nl
+            << " Changing owner/neighbour for otherwise unaffected faces..."
+            << endl;
+    }
+
+    forAll(facesToSplit, faceI)
+    {
+        // All remaining unnaffected faces are the ones whose owner/neighbour
+        // changed
+        if (facesToSplit[faceI])
+        {
+            // Get the face
+            const face& f = meshFaces[faceI];
+
+            // The point with the lowest level should be an anchor point of the
+            // neighbouring cells
+            label anchorFpI = findMinLevel(f);
+
+            label own, nei;
+            setNewFaceNeighbours
+            (
+                pointCellToAddedCellMap,
+                cellAddedCells,
+                faceI,
+                f[anchorFpI], // Anchor point
+
+                own,
+                nei
+            );
+
+            // Modify the face, changing owner and neighbour
+            modifyFace(ref, faceI, f, own, nei);
+
+            // Mark face as handled
+            facesToSplit[faceI] = false;
+
+        } // End if the face needs to be handled
+    } // End for all faces
+
+
+    // PART 6.5. Add new internal faces inside split cells
+
+    if (debug)
+    {
+        Pout<< "prismatic2DRefinement::setRefinementInstruction(...)" << nl
+            << " Adding new internal faces for split cells..."
+            << endl;
+    }
+
+    // Mark-up filed for visited cells (since we are going through faces)
+    boolList cellsToSplit(mesh_.nCells(), true);
+
+    // Loop through faces in the same way as we did when we were adding
+    // cells. This order is important since it ensures easy determination of
+    // owner/neighbour cells for new faces
+    forAll(faceMidPoint, faceI)
+    {
+        // Get owner of the face. For face on special patch (empty or wedge)
+        const label& cellI = meshFaceOwner[faceI];
+
+        // Check whether this face has been split and whether the cell has been
+        // handled (internal faces already created for this cell)
+        if (faceMidPoint[faceI] > -1 && cellsToSplit[cellI])
+        {
+            // Face is split and hasn't been visited yet. Get the face and edges
+            const face& f = meshFaces[faceI];
+            const labelList& fEdges = meshFaceEdges[faceI];
+
+            // Get anchor points for this cell and cell added cells
+            const labelList& cAnchors = cellAnchorPoints[cellI];
+            const labelList& cAdded = cellAddedCells[cellI];
+
+            // Count number of added faces (helper variable to determine
+            // owner/neighbour)
+            label nAddedFaces = 0;
+
+            // Loop through face points
+            forAll (f, fpI)
+            {
+                // Get point index
+                const label& pointI = f[fpI];
+
+                // If this point is not an anchor, it has already been handled
+                // (by going through anchors), skip
+                if (cAnchors.find(pointI) == -1)
+                {
+                    continue;
+                }
+
+                // Get corresponding edge index and edge (between fpI and
+                // fpI + 1) by definition of faceEdges
+                const label& edgeI = fEdges[fpI];
+                const edge& e = meshEdges[edgeI];
+
+                // Grab other point
+                const label& pointJ = e.otherVertex(pointI);
+
+                if (pointJ == -1)
+                {
+                    // If pointJ is equal to -1, this means that the pointI
+                    // was not found on edge, something went wrong
+                    FatalErrorInFunction
+                        << "Point: " << pointI << " not found on edge: "
+                        << edgeI << nl
+                        << "Looping through face points and face edges did"
+                        << " not ensure synchronous behaviour."
+                        << abort(FatalError);
+                }
+
+                // Create the new face
+                face newFace(4);
+
+                // Note: there are three possible variants:
+                //   i) Edge is split and the other point is anchor. Collection
+                //      of the face starts from edgeMidPoint
+                //  ii) Edge is split and the other point is not an
+                //      anchor. Collection of the face starts from other point
+                // iii) Edge is not split and the other point is not an
+                //      anchor. Collection of the face starts from other point
+                // Variants ii) and iii) can be handled together, while variant
+                // i) has to be handled separately.
+
+                // Whether the edge is split
+                const bool isEdgeSplit = edgeMidPoint[edgeI] > -1;
+
+                // Whether the other point is anchor or not
+                const bool isOtherEdgePointAnchor
+                    = cAnchors.find(pointJ) > -1;
+
+                // Check if the edge is split and whether the other edge point
+                // is an anchor
+                if (isEdgeSplit && isOtherEdgePointAnchor)
+                {
+                    // Variant i) Edge is split and other edge point is anchor
+
+                    // Create the new face and start collecting points
+                    // a) edgeMidPoint for this edge
+                    // b) faceMidPoint for this face
+                    // c) faceMidPoint for the face on the other side
+                    // d) edgeMidPoint for the edge on the other side
+
+                    // a) edgeMidPoint for this edge
+                    newFace[0] = edgeMidPoint[edgeI];
+
+                    // b) and c): adding both face mids
+                    addFaceMids
+                    (
+                        faceMidPoint,
+                        faceOnPatchToCut,
+                        faceI,
+                        cellI,
+                        newFace
+                    );
+
+                    // d) edgeMidPoint for the edge on the other side
+                    // The other edge is uniquely defined as the edge on special
+                    // patch (empty or wedge) sharing the same face as this edge
+
+                    // Get the edge faces
+                    const labelList& eFaces = meshEdgeFaces[edgeI];
+
+                    // Loop through edge faces
+                    forAll(eFaces, i)
+                    {
+                        // Get the face and check whether it is on special patch
+                        const label& faceK = eFaces[i];
+
+                        if (!faceOnPatchToCut[faceK])
+                        {
+                            // Found the face, need to search its edges
+                            const labelList& otherFaceEdges =
+                                meshFaceEdges[faceK];
+
+                            forAll(otherFaceEdges, j)
+                            {
+                                // Get the edge
+                                const label& edgeJ = otherFaceEdges[j];
+
+                                if (edgeOnPatchToCut[edgeJ] && (edgeI != edgeJ))
+                                {
+                                    // Edge is on special patch (empty or
+                                    // wedge), this must be the one we are
+                                    // looking for. Add its midpoint and double
+                                    // check if it is valid
+                                    if (edgeMidPoint[edgeJ] > -1)
+                                    {
+                                        newFace[3] = edgeMidPoint[edgeJ];
+                                        break;
+                                    }
+                                    else
+                                    {
+                                        FatalErrorInFunction
+                                            << "Other edge: "
+                                            << edgeJ
+                                            << " has not been selected for"
+                                            << " splitting, while the edge on"
+                                            << " original side: "
+                                            << edgeI
+                                            << " has been selected."
+                                            << abort(FatalError);
+                                    }
+                                } // End if this is our "other" edge
+                            } // End for all other (non special patch: empty or
+                              // wedge) face edges
+
+                            // Break out since we must have found the candidate
+                            break;
+
+                        } // End if face not on special patch (empty or wedge)
+
+                    } // End for all edge faces
+
+                } // End if this edge is split and the other point is anchor
+                else if (!isOtherEdgePointAnchor)
+                {
+                    // Variants ii) and iii). Either the edge is split and the
+                    // other point is not an anchor or the edge is not split and
+                    // the other point is not an anchor
+
+                    // Create the new face and start collecting points
+                    // a) other point of this edge
+                    // b) faceMidPoint for this face
+                    // c) faceMidPoint for the face on the other side
+                    // d) other point on the other side
+
+                    // a) other point of this edge
+                    newFace[0] = pointJ;
+
+                    // b) and c): adding both face mids
+                    addFaceMids
+                    (
+                        faceMidPoint,
+                        faceOnPatchToCut,
+                        faceI,
+                        cellI,
+                        newFace
+                    );
+
+                    // d) other point on the other side
+                    // The other point is uniquely defined as the other point of
+                    // the edge of this point which is not on special patch
+
+                    // Get point edges
+                    const labelList& pEdges = meshPointEdges[pointJ];
+
+                    // Loop through all edges
+                    forAll(pEdges, i)
+                    {
+                        // Get the edge index
+                        const label& edgeJ = pEdges[i];
+
+                        if (!edgeOnPatchToCut[edgeJ])
+                        {
+                            // Found our edge, set the point on the other side
+                            // of the edge as the last point in face
+                            newFace[3] = meshEdges[edgeJ].otherVertex(pointJ);
+                            break;
+                        }
+                    } // End loop over all point edges
+
+                } // End if the other point is not an anchor
+                else
+                {
+                    // The edge is not split and the other point is an
+                    // anchor. This should never happen
+                    FatalErrorInFunction
+                        << "Attempted to create internal face for an edge that"
+                        << " is not split and the other point that is an anchor."
+                        << nl
+                        << "Cell: " << cellI
+                        << ", point: " << pointI
+                        << ", other edge point: " << pointJ
+                        << nl
+                        << "Anchor points for cell are: " << cAnchors
+                        << abort(FatalError);
+                }
+
+                // Now we have the face defined, set owner and neighbour.
+                // Note: owner and neighbour are uniquely defined since we have
+                // gone through the face in the same way as we did while adding
+                // cells. This ensured easy definition of owner/neighbour cells
+                const label own = cAdded[nAddedFaces];
+                const label nei =
+                    nAddedFaces < cAdded.size() - 1
+                  ? cAdded[nAddedFaces + 1]
+                  : cAdded[0];
+
+                // According to the definition of adding faces, the first n - 1
+                // faces need to be reverted, while the last one is correctly
+                // oriented
+                if (nAddedFaces < cAdded.size() - 1)
+                {
+                    newFace = newFace.reverseFace();
+                }
+
+
+                // Debug: check orientation
+                if (debug)
+                {
+                    // Get owner/neighbour points
+                    point ownPt, neiPt;
+
+                    if (nAddedFaces < cAdded.size() - 1)
+                    {
+                        // Original owner/neighbour
+                        ownPt = meshPoints[pointI];
+                        neiPt = meshPoints[pointJ];
+                    }
+                    else
+                    {
+                        // Flipped owner/neighbour for last face
+                        ownPt = meshPoints[pointJ];
+                        neiPt = meshPoints[pointI];
+                    }
+
+                    checkInternalOrientation
+                    (
+                        ref,
+                        cellI,
+                        faceI,
+                        ownPt,
+                        neiPt,
+                        newFace
+                    );
+                }
+
+                // Finally, add the face. Note: ignoring return of new face
+                // index from ref.setAction(polyAddFace(...)) call
+                ref.setAction
+                (
+                    polyAddFace
+                    (
+                        newFace, // face
+                        own,     // owner
+                        nei,     // neighbour
+                        -1,      // master point
+                        -1,      // master edge
+                        0,       // master face for addition
+                        false,   // flux flip
+                        -1,      // patch for face
+                        -1,      // zone for face
+                        false    // face zone flip
+                    )
+                );
+
+                // Increment number of added faces
+                ++nAddedFaces;
+
+            } // End loop over all point (and edges) of the face
+
+            // Finished adding internal faces. Mark the cell as handled
+            cellsToSplit[cellI] = false;
+
+        } // End if face is split into n and cell has not been handled
+    } // End for all faces
+
+    // Debug: check minimum point index of added points, needs to be equal to
+    // number of points in the original mesh
+    if (debug)
+    {
+        label minPointI = labelMax;
+        label maxPointI = labelMin;
+
+        forAll(faceMidPoint, faceI)
+        {
+            if (faceMidPoint[faceI] > -1)
+            {
+                minPointI = min(minPointI, faceMidPoint[faceI]);
+                maxPointI = max(maxPointI, faceMidPoint[faceI]);
+            }
+        }
+        forAll(edgeMidPoint, edgeI)
+        {
+            if (edgeMidPoint[edgeI] > -1)
+            {
+                minPointI = min(minPointI, edgeMidPoint[edgeI]);
+                maxPointI = max(maxPointI, edgeMidPoint[edgeI]);
+            }
+        }
+
+        if (minPointI != labelMax && minPointI != mesh_.nPoints())
+        {
+            FatalErrorInFunction
+                << "Added point labels not consecutive to existing mesh points."
+                << nl
+                << "mesh_.nPoints():" << mesh_.nPoints()
+                << " minPointI: " << minPointI
+                << " maxPointI: " << maxPointI
+                << abort(FatalError);
+        }
+    }
+}
+
+
+void Foam::prismatic2DRefinement::setUnrefinementInstruction
+(
+    batchPolyTopoChange& ref
+) const
+{
+    // Note: assumes that splitPointsToUnrefine_ are set prior to the function
+    // call
+
+    // Check whether the refinementLevelIndicator is valid
+    if (refinementLevelIndicator_.size() != mesh_.nCells())
+    {
+        FatalErrorInFunction
+            << "Refinement level indicator list has invalid size: "
+            << refinementLevelIndicator_.size()
+            << ", number of cells: " << mesh_.nCells()
+            << nl
+            << "Make sure to call setRefinementInstruction(...) before"
+            << " calling setUnrefinementInstruction(...)."
+            << abort(FatalError);
+    }
+
+    // Get point cells necessary for debug and face removal
+    const labelListList& meshPointCells = mesh_.pointCells();
+
+    if (debug)
+    {
+        Pout<< "prismatic2DRefinement::setUnrefinementInstruction"
+            << "(batchPolyTopoChange& ref)"
+            << nl
+            << "Checking validity of cellLevel before setting unrefinement."
+            << endl;
+
+        forAll(cellLevel_, cellI)
+        {
+            if (cellLevel_[cellI] < 0)
+            {
+                FatalErrorInFunction
+                    << "Illegal cell level " << cellLevel_[cellI]
+                    << " for cell " << cellI
+                    << abort(FatalError);
+            }
+        }
+
+        // Write split points into a point set
+        pointSet pSet
+        (
+            mesh_,
+            "splitPoints",
+            labelHashSet(splitPointsToUnrefine_)
+        );
+        pSet.write();
+
+        // Write split point cells into a cell set
+        cellSet cSet
+        (
+            mesh_,
+            "splitPointCells",
+            splitPointsToUnrefine_.size()
+        );
+
+        forAll(splitPointsToUnrefine_, i)
+        {
+            // Get point cells and insert them into cell set
+            const labelList& pCells = meshPointCells[splitPointsToUnrefine_[i]];
+
+            forAll(pCells, j)
+            {
+                cSet.insert(pCells[j]);
+            }
+        }
+        cSet.write();
+
+        Pout<< "prismatic2DRefinement::setUnrefinementInstruction"
+            << "(batchPolyTopoChange& ref)"
+            << nl
+            << "Writing " << pSet.size()
+            << " points and "
+            << cSet.size() << " cells for unrefinement to" << nl
+            << "pointSet " << pSet.objectPath() << nl
+            << "cellSet " << cSet.objectPath()
+            << endl;
+    }
+
+    // Update refinementLevelIndicator for all cells that will be unrefined
+    forAll(splitPointsToUnrefine_, i)
+    {
+        // Get point cells and mark them for unrefinement
+        const labelList& pCells = meshPointCells[splitPointsToUnrefine_[i]];
+
+        forAll(pCells, j)
+        {
+            refinementLevelIndicator_[pCells[j]] = UNREFINED;
+        }
+    }
+
+    // Create lists needed by face remover
+    labelList cellRegion;
+    labelList cellRegionMaster;
+    labelList facesToRemove;
+
+    // Memory management
+    {
+        // Mark faces on special patches (empty or wedge) to exclude them
+        boolList faceOnPatchToCut(mesh_.nFaces(), false);
+
+        // Get boundary
+        const polyBoundaryMesh& boundaryMesh = mesh_.boundaryMesh();
+
+        // Loop through all patches
+        forAll (boundaryMesh, patchI)
+        {
+            // Get current patch
+            const polyPatch& curPatch = boundaryMesh[patchI];
+
+            // Check whether this patch is special (empty or wedge)
+            if (isA<emptyPolyPatch>(curPatch) || isA<wedgePolyPatch>(curPatch))
+            {
+                // Get start and end face labels
+                const label startFaceI = curPatch.start();
+                const label endFaceI = startFaceI + curPatch.size();
+
+                // Mark all the faces and edges on the patch
+                for (label faceI = startFaceI; faceI < endFaceI; ++faceI)
+                {
+                    // Mark face
+                    faceOnPatchToCut[faceI] = true;
+                }
+            }
+        }
+
+
+        // Collect split faces in the hash set, guess size to prevent excessive
+        // resizing
+        labelHashSet splitFaces(12*splitPointsToUnrefine_.size());
+
+        // Get point faces
+        const labelListList& meshPointFaces = mesh_.pointFaces();
+
+        forAll(splitPointsToUnrefine_, i)
+        {
+            // Loop through all faces of this point and insert face index
+            const labelList& pFaces = meshPointFaces[splitPointsToUnrefine_[i]];
+
+            forAll(pFaces, j)
+            {
+                // Get face index
+                const label& faceI = pFaces[j];
+
+                if (!faceOnPatchToCut[faceI])
+                {
+                    // Face is not on special patch, insert it into hash set
+                    splitFaces.insert(faceI);
+                }
+            }
+        }
+
+        // Check with faceRemover what faces will get removed. Note that this
+        // can be more (but never less) than splitFaces provided.
+        faceRemover_.compatibleRemoves
+        (
+            splitFaces.toc(),   // Pierced faces
+
+            cellRegion,         // Region merged into (-1 for no region)
+            cellRegionMaster,   // Master cell for region
+            facesToRemove       // List of faces to be removed
+        );
+
+        if (facesToRemove.size() != splitFaces.size())
+        {
+            FatalErrorInFunction
+                << "Either the initial set of split points to unrefine does not"
+                << " seem to be consistent or there are no mid points of"
+                << " refined cells."
+                << abort(FatalError);
+        }
+    }
+
+    // Find point region master for every cell region.  This is the central point
+    // from which the coarse cell will be made
+    // The property of the point region master is that all cells that touch it
+    // have the same cell region index
+    // HJ, 6/Sep/2019
+    labelList pointRegionMaster(cellRegionMaster.size(), label(-1));
+
+    // Get point-cell addressing
+    const labelListList& pc = mesh_.pointCells();
+
+    forAll (splitPointsToUnrefine_, i)
+    {
+        const labelList& curPc = pc[splitPointsToUnrefine_[i]];
+
+        label curRegion = -1;
+
+        forAll (curPc, curPcI)
+        {
+            if (curRegion == -1)
+            {
+                // First region found.  Grab it
+                curRegion = cellRegion[curPc[curPcI]];
+            }
+            else
+            {
+                // Region already found.  Check that all other cells that
+                // touch this point have the same region
+                if (curRegion != cellRegion[curPc[curPcI]])
+                {
+                    // Error: different region cells touching in split point
+                    // This is not a valid unrefinement pattern
+                    FatalErrorInFunction
+                        << "Different region cells touching in split point."
+                        << abort(FatalError);
+                }
+            }
+        }
+
+        // Record point region master
+        if (curRegion > -1)
+        {
+            pointRegionMaster[curRegion] = splitPointsToUnrefine_[i];
+        }
+        else
+        {
+            // Error: Cannot find region for point
+            FatalErrorInFunction
+                << "Different region cells touching in split point."
+                << abort(FatalError);
+        }
+    }
+
+    // Insert all commands to combine cells
+    faceRemover_.setRefinement
+    (
+        facesToRemove,
+        cellRegion,
+        // pointRegionMaster, // OpenFOAM does not support master point
+        cellRegionMaster,
+        ref
+    );
+}
+
+
+// * * * * * * * * * * * * * * * * Constructors  * * * * * * * * * * * * * * //
+
+Foam::prismatic2DRefinement::prismatic2DRefinement
+(
+    const word& name,
+    const dictionary& dict,
+    const label index,
+    const polyTopoChanger& mme
+)
+:
+    refinement(name, dict, index, mme)
+{}
+
+
+// * * * * * * * * * * * * * * * * Destructor  * * * * * * * * * * * * * * * //
+
+Foam::prismatic2DRefinement::~prismatic2DRefinement()
+{}
+
+
+// * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //
+
+void Foam::prismatic2DRefinement::setCellsToRefine
+(
+    const labelList& refinementCellCandidates
+)
+{
+    if (debug)
+    {
+        Info<< "prismatic2DRefinement::setCellsToRefine"
+            << "(const labelList& refinementCellCandidates)" << nl
+            << "Setting cells to refine" << endl;
+    }
+
+    // Create a mark-up field for cells to refine
+    boolList refineCell(mesh_.nCells(), false);
+
+    // Roughly count how many cells we are going to end up with
+    label roughCellCountAfterRefinement = mesh_.nCells();
+
+    // Get cell points to count number of additional cells
+    const labelListList& meshCellPoints = mesh_.cellPoints();
+
+    // Mark initial refinement candidates for refinement only if the cell level
+    // is smaller than the maximum refinement level. Note: stop marking them if
+    // we exceed the rough cell count
+    forAll (refinementCellCandidates, i)
+    {
+        // Get cell index
+        const label& cellI = refinementCellCandidates[i];
+
+        if (roughCellCountAfterRefinement < maxCells_)
+        {
+            // Mark cell for refinement
+            refineCell[cellI] = true;
+
+            // Increment number of cells (nPoints/2 - 1 new cells per cell)
+            roughCellCountAfterRefinement += meshCellPoints[cellI].size()/2 - 1;
+        }
+    }
+
+    // Extend cells across faces using a specified number of refinement buffer
+    // layers
+    for (label i = 0; i < nRefinementBufferLayers_; ++i)
+    {
+        meshTools::extendMarkedCellsAcrossFaces(mesh_, refineCell);
+    }
+
+    // Remove all cells that exceed the maximum refinement level
+    forAll (refineCell, cellI)
+    {
+        if (refineCell[cellI] && (cellLevel_[cellI] + 1 > maxRefinementLevel_))
+        {
+            refineCell[cellI] = false;
+        }
+    }
+
+    // Make sure that the refinement is face consistent (2:1 consistency) and
+    // point consistent (4:1 consistency) if necessary
+
+    // Counter for additional cells to refine due to consistency in each
+    // iteration and number of iterations
+    label nAddCells = 0;
+    label nIters = 0;
+    label nTotalAddCells = 0;
+
+    do
+    {
+        // Reset counter at the beginning of each iteration
+        nAddCells = 0;
+
+        if (edgeBasedConsistency_)
+        {
+            // Check for 4:1 edge based consistent refinement. Updates
+            // cellsToRefine and returns number of cells added in this iteration
+            nAddCells += edgeConsistentRefinement(refineCell);
+        }
+
+        // Check for 2:1 face based consistent refinement. Updates cellsToRefine
+        // and returns number of cells added in this iteration
+        nAddCells += faceConsistentRefinement(refineCell);
+
+        // Global reduction
+        reduce(nAddCells, sumOp<label>());
+
+        // Increment number of iterations and total number of added cells
+        ++nIters;
+        nTotalAddCells += nAddCells;
+
+    } while (nAddCells > 0);
+
+    Info<< "Added " << nTotalAddCells // nTotalAddCells already reduced
+        << " cells in " << returnReduce(nIters, maxOp<label>())
+        << " iterations to obtain consistent refinement."
+        << endl;
+
+    // Collect all cells to refine in a dynamic list
+    DynamicList<label> cellsToRefineDynamic(mesh_.nCells());
+
+    forAll (refineCell, cellI)
+    {
+        if (refineCell[cellI])
+        {
+            // Cell marked for refinement, append it
+            cellsToRefineDynamic.append(cellI);
+        }
+    }
+
+    // Transfer the contents into the data member (ordinary list)
+    cellsToRefine_.transfer(cellsToRefineDynamic);
+
+    Info<< "Selected " << returnReduce(cellsToRefine_.size(), sumOp<label>())
+        << " cells to refine." << endl;
+}
+
+
+void Foam::prismatic2DRefinement::setSplitPointsToUnrefine
+(
+    const labelList& unrefinementPointCandidates
+)
+{
+    if (debug)
+    {
+        Info<< "prismatic2DRefinement::setSplitPointsToUnrefine"
+            << "(const labelList& unrefinementPointCandidates)" << nl
+            << "Setting split points to unrefine." << endl;
+    }
+
+    // Get necessary mesh data
+    const label nPoints = mesh_.nPoints();
+    const labelListList& meshCellPoints = mesh_.cellPoints();
+
+    // PART 1: Mark all split points in the mesh (points that can be unrefined)
+    boolList splitPointsMarkup(nPoints, false);
+
+    // Algorithm: split point is uniquely defined as a point that:
+    // 1. Has pointLevel_ > 0 (obviously),
+    // 2. A point that has the same pointLevel_ as ALL of the points of its
+    //    edges. In other words, for each point, we will look through all the
+    //    edges of the point. For each edge, we will visit both points and
+    //    check point levels. All point levels must be the same for this point
+    //    candidate to be a split point. This is quite useful since there is no
+    //    need to store the refinement history
+
+    // Get necessary mesh data
+    const edgeList& meshEdges = mesh_.edges();
+    const labelListList& meshPointEdges = mesh_.pointEdges();
+
+    // Loop through all points
+    forAll (meshPointEdges, pointI)
+    {
+        // Get point level of this point
+        const label& centralPointLevel = pointLevel_[pointI];
+
+        if (centralPointLevel < 1)
+        {
+            // Point can't be unrefined as its level is either 0 or
+            // invalid. Continue immediately
+            continue;
+        }
+
+        // Flag to see whether this is a split point candidate
+        bool splitPointCandidate = true;
+
+        // Get all edge labels for this point
+        const labelList& pEdges = meshPointEdges[pointI];
+
+        // Loop through all point edges
+        forAll (pEdges, i)
+        {
+            // Get edge index and the edge
+            const label& edgeI = pEdges[i];
+            const edge& curEdge = meshEdges[edgeI];
+
+            // Loop through both points of the edge
+            forAll (curEdge, j)
+            {
+                // Get point index
+                const label& pointJ = curEdge[j];
+
+                if (pointLevel_[pointJ] != centralPointLevel)
+                {
+                    // Point levels are different, this can't be a split point,
+                    // set flag to false and break immediatelly
+                    splitPointCandidate = false;
+                    break;
+                }
+                // else: this is still potential split point candidate so
+                //       there's nothing to do
+            } // End for both points of this edge
+
+            // Check whether this can't be a split point already and break out
+            // immediately
+            if (!splitPointCandidate)
+            {
+                break;
+            }
+        } // End for all point faces
+
+        // At this point, if the flag is still true, this is a split point
+        if (splitPointCandidate)
+        {
+            splitPointsMarkup[pointI] = true;
+        }
+    }
+
+    // Note: if there is no dynamic load balancing, points at the processor
+    // boundary cannot be split points by definition. However, in dynamic load
+    // balancing runs, it is possible that a split point end on processor
+    // boundary, in which case we will simply avoid (actually delay) unrefining
+    // until this becomes internal point again. VV, 4/Jul/2018.
+
+    // Get boundary mesh and mesh faces
+    const polyBoundaryMesh& bMesh = mesh_.boundaryMesh();
+    const faceList& meshFaces = mesh_.faces();
+
+    // Loop through all patches
+    forAll (bMesh, patchI)
+    {
+        const polyPatch& patch = bMesh[patchI];
+
+        if (isA<processorPolyPatch>(patch))
+        {
+            // Get patch start
+            const label startIndex = patch.start();
+
+            // Loop through all the faces
+            forAll (patch, i)
+            {
+                // Get global face index and face
+                const label faceI = startIndex + i;
+                const face& f = meshFaces[faceI];
+
+                // Make sure that we don't split around point at all points of
+                // the processor patch faces
+                forAll (f, fpI)
+                {
+                    splitPointsMarkup[f[fpI]] = false;
+                }
+            }
+        }
+    }
+
+
+    // PART 2: Mark all unrefinement point candidates that are split points at
+    // the same time (basically the intersection of split points and candidates)
+
+    // Create markup field of split points to unrefine
+    // True: this is a split point which should be unrefined
+    // False: this is either not a split point or it shouldn't be unrefined
+    boolList splitPointsToUnrefine(nPoints, false);
+
+    // Loop through all unrefinement candidates
+    forAll (unrefinementPointCandidates, i)
+    {
+        // Get point index
+        const label& pointI = unrefinementPointCandidates[i];
+
+        if (splitPointsMarkup[pointI])
+        {
+            // This is a split point, mark it for unrefinement
+            splitPointsToUnrefine[pointI] = true;
+        }
+    }
+
+
+    // PART 3: Make sure that we skip unrefining around split points that
+    // possibly have cells around that will be refined
+
+    // Mark cells that need to be protected (will be refined in this iteration)
+    boolList protectedCell(mesh_.nCells(), false);
+
+    // Loop through cells to refine and mark them
+    forAll (cellsToRefine_, i)
+    {
+        protectedCell[cellsToRefine_[i]] = true;
+    }
+
+    // Extend protected cells across points using a specified number of
+    // unrefinement buffer layers
+    for (label i = 0; i < nUnrefinementBufferLayers_; ++i)
+    {
+        meshTools::extendMarkedCellsAcrossPoints(mesh_, protectedCell);
+    }
+
+    // Loop through all cells and if the cell should be protected, protect all
+    // of its points from unrefinement
+    forAll (protectedCell, cellI)
+    {
+        if (protectedCell[cellI])
+        {
+            // Get list of cell points for this protected cell
+            const labelList& cPoints = meshCellPoints[cellI];
+
+            // Loop through cell points and make sure that they are not marked
+            // for unrefinement
+            forAll (cPoints, j)
+            {
+                splitPointsToUnrefine[cPoints[j]] = false;
+            }
+        }
+    }
+
+
+    // PART 4: Ensure face consistent (2:1 constraint) and possibly edge
+    // consistent (4:1 constraint) unrefinement
+
+    // Get necessary mesh data
+    const label nCells = mesh_.nCells();
+    const labelListList& meshPointCells = mesh_.pointCells();
+
+    // Count number of removed cells from unrefinement (cells that will not be
+    // unrefined) in each iteration and number of iterations
+    label nRemCells = 0;
+    label nIters = 0;
+    label nTotalRemCells = 0;
+
+    do
+    {
+        // First, create cells to unrefine (all cells sharing point to unrefine)
+        boolList cellsToUnrefine(nCells, false);
+
+        // Loop through all split points to unrefine
+        forAll (splitPointsToUnrefine, pointI)
+        {
+            if (splitPointsToUnrefine[pointI])
+            {
+                // This split point is marked for unrefinement, collect all of
+                // its cells
+                const labelList& pCells = meshPointCells[pointI];
+                forAll (pCells, i)
+                {
+                    cellsToUnrefine[pCells[i]] = true;
+                }
+            }
+        }
+
+        // Reset number of removed cells from unrefinement for this iteration
+        nRemCells = 0;
+
+        if (edgeBasedConsistency_)
+        {
+            // Check for 4:1 edge based consistent unrefinement. Updates
+            // cellsToUnrefine and returns number of removed cells from
+            // unrefinement in this iteration
+            nRemCells += edgeConsistentUnrefinement(cellsToUnrefine);
+        }
+
+        // Check for 2:1 face based consistent unrefinement. Updates
+        // cellsToUnrefine and returns number of removed cells from unrefinement
+        // in this iteration
+        nRemCells += faceConsistentUnrefinement(cellsToUnrefine);
+
+        // Global reduction
+        reduce(nRemCells, sumOp<label>());
+
+        // If we have removed at least one cell from unrefinement, we need to
+        // protect its split points as well from unrefinement
+        if (nRemCells > 0)
+        {
+            // Get point cells
+            const labelListList& meshPointCells = mesh_.pointCells();
+
+            // Loop through all split points to unrefine
+            forAll (splitPointsToUnrefine, pointI)
+            {
+                if (splitPointsToUnrefine[pointI])
+                {
+                    // This is a split point for unrefinement, get the cells
+                    const labelList& pCells = meshPointCells[pointI];
+
+                    // Loop through all point cells
+                    forAll (pCells, i)
+                    {
+                        if (!cellsToUnrefine[pCells[i]])
+                        {
+                            // Cell must not be refined, remove point from
+                            // unrefinement as well
+                            splitPointsToUnrefine[pointI] = false;
+                            break;
+                        }
+                    }
+                }
+            }
+        }
+
+        // Increment number of iterations and number of total removed cells
+        ++nIters;
+        nTotalRemCells += nRemCells;
+
+    } while (nRemCells > 0);
+
+    Info<< "Removed " << nTotalRemCells // nTotalRemCells already reduced
+        << " cells in " << returnReduce(nIters, maxOp<label>())
+        << " iterations to obtain consistent unrefinement."
+        << endl;
+
+    // Collect all split points to unrefine in a dynamic list
+    DynamicList<label> splitPointsToUnrefineDynamic(nPoints);
+
+    forAll (splitPointsToUnrefine, pointI)
+    {
+        if (splitPointsToUnrefine[pointI])
+        {
+            // Split point marked for unrefinement, append it
+            splitPointsToUnrefineDynamic.append(pointI);
+        }
+    }
+
+    // Transfer the contents into the data member (ordinary list)
+    splitPointsToUnrefine_.transfer(splitPointsToUnrefineDynamic);
+
+    Info<< "Selected "
+        << returnReduce(splitPointsToUnrefine_.size(), sumOp<label>())
+        << " split points to unrefine." << endl;
+}
+
+
+// ************************************************************************* //
diff --git a/src/dynamicMesh/refinement/prismatic2DRefinement/prismatic2DRefinement.H b/src/dynamicMesh/refinement/prismatic2DRefinement/prismatic2DRefinement.H
new file mode 100644
index 0000000000000000000000000000000000000000..368aaad2fb5f981947c2b674785563c33296bd45
--- /dev/null
+++ b/src/dynamicMesh/refinement/prismatic2DRefinement/prismatic2DRefinement.H
@@ -0,0 +1,254 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | foam-extend: Open Source CFD
+   \\    /   O peration     | Version:     5.0
+    \\  /    A nd           | Web:         http://www.foam-extend.org
+     \\/     M anipulation  | For copyright notice see file Copyright
+-------------------------------------------------------------------------------
+License
+    This file is part of foam-extend.
+
+    foam-extend 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.
+
+    foam-extend 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 foam-extend.  If not, see <http://www.gnu.org/licenses/>.
+
+Class
+    Foam::prismatic2DRefinement
+
+Description
+    Isotropic refinement of prismatic cells in 2D using the mesh modifier
+    engine. Used for 2D cases instead of polyhedralRefinement which carries
+    unnecessary overhead in terms of number of cells since it splits the cell in
+    all directions.
+
+    Each prismatic cell is split by the following procedure:
+    1. Adding points at the face centres and edge centres of all faces found on
+       an special patch: empty or wedge.
+    2. Adding n cells per existing cell where n is the number of corner points
+       at the face on special patch (empty or wedge).
+    3. Splitting each of the faces on special patch (empty or wedge) into
+       multiple faces going from: existing corner point -> new edge centre point
+       -> new face centre point -> other new edge centre point (sharing the same
+       corner point)
+    4. Spliiting each of the faces not on an special patch (empty or wedge) into
+       two faces going from: existing corner point -> existing corner point on
+       the other side -> new edge centre point on the other side -> new edge
+       centre point on my side
+    4. Adding internal faces going from:
+       new edge centre point -> new face centre point -> new other face
+       centre point on the other side -> new other edge mid point on the other
+       side
+
+    It is an error to try and run this on anything except a 2D mesh.
+
+SourceFiles
+    prismatic2DRefinement.C
+
+Author
+    Vuko Vukcevic, Wikki Ltd.  All rights reserved.
+
+\*---------------------------------------------------------------------------*/
+
+#ifndef prismatic2DRefinement_H
+#define prismatic2DRefinement_H
+
+#include "refinement.H"
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+namespace Foam
+{
+
+/*---------------------------------------------------------------------------*\
+                    Class prismatic2DRefinement Declaration
+\*---------------------------------------------------------------------------*/
+
+class prismatic2DRefinement
+:
+    public refinement
+{
+private:
+
+    // Private Member Functions
+
+        // Helper functions
+
+            //- Get least cell level such that the face has at least nPoints
+            //  points smaller than the level
+            label getAnchorLevel
+            (
+                const label faceI,
+                const label nPoints
+            ) const;
+
+            //- Append given face into a dynamic list containing split faces
+            //  that will be split into two faces (third parameter). Additionaly
+            //  append information on which of the two edges of the face are on
+            //  special patch into a dynamic list (fourth parameter)
+            void appendFaceSplitInfo
+            (
+                const label& faceI,
+                const boolList& edgeOnEmptyPatch,
+                const labelList& edgeMidPoint,
+                DynamicList<label>& splitFacesIntoTwo,
+                DynamicList<Pair<label> >& splitFacesEmptyEdges
+            ) const;
+
+
+        // Topological change helper functions
+
+            //- Set new owner and neighbour given anchor pointI, faceI and the
+            //  necessary mapping
+            void setNewFaceNeighbours
+            (
+                const HashTable
+                <
+                    label,
+                    Pair<label>,
+                    Hash<FixedList<label, 2> >
+                >& pointCellToAddedCellMap,
+                const labelListList& cellAddedCells,
+                const label& faceI,
+                const label& pointI,
+
+                label& own,
+                label& nei
+            ) const;
+
+            //- Get index of point with minimum point level of a face across two
+            //  connected edges starting from a local point index.
+            //  Example: starting from point with level 1 in the upper left
+            //  corner, finds point index of the point with level 0 which is on
+            //  the same face, connected with edge to original point
+            //  1------1
+            //  |
+            //  |
+            //  0
+            //  Note: passing face edges and mesh edges as parameters to avoid
+            //  fetching them from mesh due to lazy evaluation
+            label findMinEdgeConnectedLevel
+            (
+                const label& fpI,
+                const label& faceI,
+                const face& f,
+                const labelList& fEdges,
+                const edgeList& meshEdges
+            ) const;
+
+            //- Store two face mids when adding internal faces
+            void addFaceMids
+            (
+                const labelList& faceMidPoint,
+                const boolList& faceOnEmptyPatch,
+                const label& faceI,
+                const label& cellI,
+                face& newFace
+            ) const;
+
+
+        // Debug functions
+
+            //- Check orientation of a split face
+            void checkNewFaceOrientation
+            (
+                batchPolyTopoChange& ref,
+                const label& faceI,
+                const face& newFace
+            ) const;
+
+
+        // Copy control
+
+            //- Disallow default bitwise copy construct
+            prismatic2DRefinement(const prismatic2DRefinement&) = delete;
+
+            //- Disallow default bitwise assignment
+            void operator=(const prismatic2DRefinement&) = delete;
+
+
+protected:
+
+    // Protected Pure Virtual Member Functions
+
+        // Global topology modification functions (operate on whole polyMesh)
+
+            //- Set refinement instruction
+            virtual void setRefinementInstruction
+            (
+                batchPolyTopoChange& ref
+            ) const;
+
+            //- Set unrefinement instruction
+            virtual void setUnrefinementInstruction
+            (
+                batchPolyTopoChange& ref
+            ) const;
+
+
+public:
+
+    //- Runtime type information
+    TypeName("prismatic2DRefinement");
+
+
+    // Constructors
+
+        //- Construct from dictionary
+        prismatic2DRefinement
+        (
+            const word& name,
+            const dictionary& dict,
+            const label index,
+            const polyTopoChanger& mme
+        );
+
+
+    //- Destructor
+    virtual ~prismatic2DRefinement();
+
+
+    // Member Functions
+
+        // Edit
+
+            //- Set cells to refine given a list of refinement
+            //  candidates. Refinement candidates are extended within the
+            //  function due to possible 4:1 conflicts and specified number of
+            //  buffer layers.
+            //  Note: must be called BEFORE setSplitPointsToUnrefine
+            virtual void setCellsToRefine
+            (
+                const labelList& refinementCellCandidates
+            );
+
+            //- Set split points to unrefine given a list of all mesh points
+            //  that are candidates for unrefinement. Split points are
+            //  determined as a subset of unrefinement candidates, avoiding
+            //  splitting points of cells that are going to be refined at the
+            //  same time and ensuring consistent unrefinement.
+            //  Note: must be called AFTER setCellsToRefine
+            void setSplitPointsToUnrefine
+            (
+                const labelList& unrefinementPointCandidates
+            );
+};
+
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+} // End namespace Foam
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+#endif
+
+// ************************************************************************* //
diff --git a/src/dynamicMesh/refinement/refinement/refinement.C b/src/dynamicMesh/refinement/refinement/refinement.C
new file mode 100644
index 0000000000000000000000000000000000000000..75f8b2eaaac4d7e1fed931ac23e20a621738aed9
--- /dev/null
+++ b/src/dynamicMesh/refinement/refinement/refinement.C
@@ -0,0 +1,1489 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | foam-extend: Open Source CFD
+   \\    /   O peration     | Version:     5.0
+    \\  /    A nd           | Web:         http://www.foam-extend.org
+     \\/     M anipulation  | For copyright notice see file Copyright
+-------------------------------------------------------------------------------
+License
+    This file is part of foam-extend.
+
+    foam-extend 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.
+
+    foam-extend 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 foam-extend.  If not, see <http://www.gnu.org/licenses/>.
+
+Author
+    Vuko Vukcevic, Wikki Ltd.  All rights reserved.
+    Hrvoje Jasak, Wikki Ltd.
+
+Notes
+    Generalisation of hexRef8 for polyhedral cells and refactorisation into mesh
+    modifier engine.
+
+\*---------------------------------------------------------------------------*/
+
+#include "refinement.H"
+#include "polyTopoChanger.H"
+#include "polyMesh.H"
+#include "batchPolyTopoChange.H"
+#include "syncTools.H"
+#include "meshTools.H"
+#include "polyAddFace.H"
+#include "polyAddPoint.H"
+#include "polyAddCell.H"
+#include "polyModifyFace.H"
+#include "mapPolyMesh.H"
+#include "addToRunTimeSelectionTable.H"
+
+// * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * //
+
+namespace Foam
+{
+    defineTypeNameAndDebug(refinement, 0);
+
+
+}
+
+const Foam::scalar Foam::refinement::nonOrthThreshold_ = 70;
+
+
+// * * * * * * * * * * * * * Protected Member Functions  * * * * * * * * * * //
+
+void Foam::refinement::setInstance(const fileName& inst) const
+{
+    if (debug)
+    {
+        Pout<< "refinement::setInstance(const fileName& inst)"
+            << nl
+            << "Resetting file instance of refinement data to " << inst
+            << endl;
+    }
+
+    cellLevel_.instance() = inst;
+    pointLevel_.instance() = inst;
+}
+
+
+Foam::label Foam::refinement::addFace
+(
+    batchPolyTopoChange& ref,
+    const label faceI,
+    const face& newFace,
+    const label own,
+    const label nei
+) const
+{
+    // Set face information
+    label patchID, zoneID, zoneFlip;
+    meshTools::setFaceInfo(mesh_, faceI, patchID, zoneID, zoneFlip);
+
+    // Set new face index to -1
+    label newFaceI = -1;
+
+    if ((nei == -1) || (own < nei))
+    {
+        // Ordering is ok, add the face
+        newFaceI = ref.setAction
+        (
+            polyAddFace
+            (
+                newFace,                    // face
+                own,                        // owner
+                nei,                        // neighbour
+                -1,                         // master point
+                -1,                         // master edge
+                faceI,                      // master face for addition
+                false,                      // flux flip
+                patchID,                    // patch for face
+                zoneID,                     // zone for face
+                zoneFlip                    // face zone flip
+            )
+        );
+    }
+    else
+    {
+        // Ordering is flipped, reverse face and flip owner/neighbour
+        newFaceI = ref.setAction
+        (
+            polyAddFace
+            (
+                newFace.reverseFace(),      // face
+                nei,                        // owner
+                own,                        // neighbour
+                -1,                         // master point
+                -1,                         // master edge
+                faceI,                      // master face for addition
+                false,                      // flux flip
+                patchID,                    // patch for face
+                zoneID,                     // zone for face
+                zoneFlip                    // face zone flip
+            )
+        );
+    }
+
+    return newFaceI;
+}
+
+
+Foam::label Foam::refinement::addInternalFace
+(
+    batchPolyTopoChange& ref,
+    const label meshFaceI,
+    const label meshPointI,
+    const face& newFace,
+    const label own,
+    const label nei
+) const
+{
+    // Check whether this is an internal face
+    if (mesh_.isInternalFace(meshFaceI))
+    {
+        return ref.setAction
+        (
+            polyAddFace
+            (
+                newFace,                    // face
+                own,                        // owner
+                nei,                        // neighbour
+                -1,                         // master point
+                -1,                         // master edge
+                meshFaceI,                  // master face for addition
+                false,                      // flux flip
+                -1,                         // patch for face
+                -1,                         // zone for face
+                false                       // face zone flip
+            )
+        );
+    }
+    else
+    {
+        // This is not an internal face. Add face out of nothing
+        return ref.setAction
+        (
+            polyAddFace
+            (
+                newFace,                    // face
+                own,                        // owner
+                nei,                        // neighbour
+                -1,                         // master point
+                -1,                         // master edge
+                0,                          // master face for addition
+                false,                      // flux flip
+                -1,                         // patch for face
+                -1,                         // zone for face
+                false                       // face zone flip
+            )
+        );
+    }
+}
+
+
+void Foam::refinement::modifyFace
+(
+    batchPolyTopoChange& ref,
+    const label faceI,
+    const face& newFace,
+    const label own,
+    const label nei
+) const
+{
+    // Set face inforomation
+    label patchID, zoneID, zoneFlip;
+    meshTools::setFaceInfo(mesh_, faceI, patchID, zoneID, zoneFlip);
+
+    // Get owner/neighbour addressing and mesh faces
+    const labelList& owner = mesh_.faceOwner();
+    const labelList& neighbour = mesh_.faceNeighbour();
+
+    const faceList& meshFaces = mesh_.faces();
+
+    if
+    (
+        (own != owner[faceI])
+     || (
+            mesh_.isInternalFace(faceI)
+         && (nei != neighbour[faceI])
+        )
+     || (newFace != meshFaces[faceI])
+    )
+    {
+        // Either:
+        // 1. Owner index does not correspond to mesh owner,
+        // 2. Neighbour index does not correspond to mesh neighbour,
+        // 3. New face does not correspond to mesh face
+        // So we need to modify this face
+        if ((nei == -1) || (own < nei))
+        {
+            // Ordering is ok, add the face
+            ref.setAction
+            (
+                polyModifyFace
+                (
+                    newFace,            // modified face
+                    faceI,              // label of face being modified
+                    own,                // owner
+                    nei,                // neighbour
+                    false,              // face flip
+                    patchID,            // patch for face
+                    false,              // remove from zone
+                    zoneID,             // zone for face
+                    zoneFlip            // face flip in zone
+                )
+            );
+        }
+        else
+        {
+            // Ordering is flipped, reverse face and flip owner/neighbour
+            ref.setAction
+            (
+                polyModifyFace
+                (
+                    newFace.reverseFace(),  // modified face
+                    faceI,                  // label of face being modified
+                    nei,                    // owner
+                    own,                    // neighbour
+                    false,                  // face flip
+                    patchID,                // patch for face
+                    false,                  // remove from zone
+                    zoneID,                 // zone for face
+                    zoneFlip                // face flip in zone
+                )
+            );
+        }
+    }
+}
+
+
+void Foam::refinement::walkFaceToMid
+(
+    const labelList& edgeMidPoint,
+    const label cLevel,
+    const label faceI,
+    const label startFp,
+    DynamicList<label>& faceVerts
+) const
+{
+    // Get the face and its edges
+    const face& f = mesh_.faces()[faceI];
+    const labelList& fEdges = mesh_.faceEdges()[faceI];
+
+    label fp = startFp;
+
+    // Starting from fp store all (1 or 2) vertices until where the face
+    // gets split
+    while (true)
+    {
+        if (edgeMidPoint[fEdges[fp]] > -1)
+        {
+            // Edge is split, append its mid point
+            faceVerts.append(edgeMidPoint[fEdges[fp]]);
+        }
+
+        fp = f.fcIndex(fp);
+
+        if (pointLevel_[f[fp]] <= cLevel)
+        {
+            // Found next anchor. Already appended the split point just above
+            return;
+        }
+        else if (pointLevel_[f[fp]] == cLevel + 1)
+        {
+            // Mid level, append and return
+            faceVerts.append(f[fp]);
+
+            return;
+        }
+        else if (pointLevel_[f[fp]] == cLevel + 2)
+        {
+            // Store and continue to cLevel + 1
+            faceVerts.append(f[fp]);
+        }
+    }
+}
+
+
+void Foam::refinement::walkFaceFromMid
+(
+    const labelList& edgeMidPoint,
+    const label cLevel,
+    const label faceI,
+    const label startFp,
+    DynamicList<label>& faceVerts
+) const
+{
+    // Get the face and its edges
+    const face& f = mesh_.faces()[faceI];
+    const labelList& fEdges = mesh_.faceEdges()[faceI];
+
+    label fp = f.rcIndex(startFp);
+
+    while (true)
+    {
+        if (pointLevel_[f[fp]] <= cLevel)
+        {
+            // Anchor point, break
+            break;
+        }
+        else if (pointLevel_[f[fp]] == cLevel + 1)
+        {
+            // Mid level, append and break
+            faceVerts.append(f[fp]);
+            break;
+        }
+        else if (pointLevel_[f[fp]] == cLevel + 2)
+        {
+            // Continue to cLevel + 1
+        }
+        fp = f.rcIndex(fp);
+    }
+
+    // Store
+    while (true)
+    {
+        if (edgeMidPoint[fEdges[fp]] > -1)
+        {
+            // Edge is split, append its mid point
+            faceVerts.append(edgeMidPoint[fEdges[fp]]);
+        }
+
+        fp = f.fcIndex(fp);
+
+        if (fp == startFp)
+        {
+            break;
+        }
+        faceVerts.append(f[fp]);
+    }
+}
+
+
+Foam::label Foam::refinement::findMinLevel(const labelList& f) const
+{
+    // Initialise minimum level to large value
+    label minLevel = labelMax;
+
+    // Initialise point label at which min level is reached to -1
+    label pointIMin = -1;
+
+    forAll(f, fp)
+    {
+        const label& level = pointLevel_[f[fp]];
+
+        if (level < minLevel)
+        {
+            minLevel = level;
+            pointIMin = fp;
+        }
+    }
+
+    return pointIMin;
+}
+
+
+Foam::label Foam::refinement::findMaxLevel(const labelList& f) const
+{
+    // Initialise  maximum level to small value
+    label maxLevel = labelMin;
+
+    // Initialise point label at which max level is reached to -1
+    label pointIMax = -1;
+
+    forAll(f, fp)
+    {
+        const label& level = pointLevel_[f[fp]];
+
+        if (level > maxLevel)
+        {
+            maxLevel = level;
+            pointIMax = fp;
+        }
+    }
+
+    return pointIMax;
+}
+
+
+Foam::label Foam::refinement::countAnchors
+(
+    const labelList& f,
+    const label anchorLevel
+) const
+{
+    label nAnchors = 0;
+
+    forAll(f, fp)
+    {
+        if (pointLevel_[f[fp]] <= anchorLevel)
+        {
+            ++nAnchors;
+        }
+    }
+    return nAnchors;
+}
+
+void Foam::refinement::adjustRefLevel
+(
+    label& curNewCellLevel,
+    const label oldCellI
+)
+{
+    if (oldCellI == -1)
+    {
+        // This cell is inflated (does not originate from other cell), set
+        // cell level to -1
+        curNewCellLevel = -1;
+    }
+    else
+    {
+        // This cell has either been added based on another cell or it
+        // hasn't changed. Update new cell level according to refinement
+        // level indicator and old cell level
+
+        // Get refinement status of the old cell
+        const label& refStatus = refinementLevelIndicator_[oldCellI];
+
+        if (refStatus == UNREFINED)
+        {
+            // New cell has been obtained by unrefining other cells - this
+            // is the remaining "master" cell. Decrement cell level
+            curNewCellLevel = cellLevel_[oldCellI] - 1;
+        }
+        else if (refStatus == UNCHANGED)
+        {
+            // Cell hasn't been changed during this refinement, copy old
+            // cell level
+            curNewCellLevel = cellLevel_[oldCellI];
+        }
+        else if (refStatus == REFINED)
+        {
+            // Cell has been refined, increment cell level
+            curNewCellLevel = cellLevel_[oldCellI] + 1;
+        }
+        else
+        {
+            FatalErrorInFunction
+                << "Invalid refinement status detected: "
+                << refStatus << nl
+                << "Old cell index: " << oldCellI << abort(FatalError);
+        }
+    }
+}
+
+
+void Foam::refinement::checkInternalOrientation
+(
+    batchPolyTopoChange& ref,
+    const label cellI,
+    const label faceI,
+    const point& ownPt,
+    const point& neiPt,
+    const face& newFace
+) const
+{
+    const face compactFace(identity(newFace.size()));
+
+    // Get list of polyAddPoint objects
+    const DynamicList<polyAddPoint>& polyAddedPoints(ref.addedPoints());
+
+    // Create a list of all points
+    const label nOrigPoints = mesh_.nPoints();
+    pointField allPoints(nOrigPoints + polyAddedPoints.size());
+
+    // Set ordinary points first
+    const pointField& meshPoints = mesh_.points();
+    forAll (meshPoints, i)
+    {
+        allPoints[i] = meshPoints[i];
+    }
+
+    // Set newly added points next
+    forAll (polyAddedPoints, i)
+    {
+        allPoints[i + nOrigPoints] = polyAddedPoints[i].newPoint();
+    }
+
+    // Get compact points
+    const pointField compactPoints
+    (
+        IndirectList<point>(allPoints, newFace)()
+    );
+
+    const vector n(compactFace.unitNormal(compactPoints));
+    const vector dir(neiPt - ownPt);
+
+    // Check orientation error
+    if ((dir & n) < 0)
+    {
+        FatalErrorInFunction
+            << "cell:" << cellI << " old face:" << faceI
+            << " newFace:" << newFace << endl
+            << " coords:" << compactPoints
+            << " ownPt:" << ownPt
+            << " neiPt:" << neiPt
+            << abort(FatalError);
+    }
+
+    // Note: report significant non-orthogonality error
+    const scalar severeNonOrthogonalityThreshold =
+      ::cos
+        (
+            nonOrthThreshold_/180.0*constant::mathematical::pi
+        );
+
+    const vector fcToOwn(compactFace.centre(compactPoints) - ownPt);
+
+    // Note: normal vector already normalised
+    const scalar dDotN = (fcToOwn & n)/(mag(fcToOwn) + VSMALL);
+
+    if (dDotN > severeNonOrthogonalityThreshold)
+    {
+        WarningIn
+        (
+            "refinement::checkInternalOrientation(...)"
+        )
+            << "Detected severely non-orthogonal face with non-orthogonality: "
+            << ::acos(dDotN)/constant::mathematical::pi*180.0
+            << "cell:" << cellI << " old face:" << faceI
+            << " newFace:" << newFace << endl
+            << " coords:" << compactPoints
+            << " ownPt:" << ownPt
+            << " neiPt:" << neiPt
+            << " dDotN:" << dDotN
+            << endl;
+    }
+}
+
+
+void Foam::refinement::checkBoundaryOrientation
+(
+    batchPolyTopoChange& ref,
+    const label cellI,
+    const label faceI,
+    const point& ownPt,
+    const point& boundaryPt,
+    const face& newFace
+) const
+{
+    const face compactFace(identity(newFace.size()));
+
+    // Get list of polyAddPoint objects
+    const DynamicList<polyAddPoint>& polyAddedPoints(ref.addedPoints());
+
+    // Create a list of all points
+    const label nOrigPoints = mesh_.nPoints();
+    pointField allPoints(nOrigPoints + polyAddedPoints.size());
+
+    // Set ordinary points first
+    const pointField& meshPoints = mesh_.points();
+    forAll (meshPoints, i)
+    {
+        allPoints[i] = meshPoints[i];
+    }
+
+    // Set newly added points next
+    forAll (polyAddedPoints, i)
+    {
+        allPoints[i + nOrigPoints] = polyAddedPoints[i].newPoint();
+    }
+
+    // Get compact points
+    const pointField compactPoints
+    (
+        IndirectList<point>(allPoints, newFace)()
+    );
+
+    const vector n(compactFace.unitNormal(compactPoints));
+    const vector dir(boundaryPt - ownPt);
+
+    // Check orientation error
+    if ((dir & n) < 0)
+    {
+        FatalErrorInFunction
+            << "cell:" << cellI << " old face:" << faceI
+            << " newFace:" << newFace
+            << " coords:" << compactPoints
+            << " ownPt:" << ownPt
+            << " boundaryPt:" << boundaryPt
+            << abort(FatalError);
+    }
+
+    // Note: report significant non-orthogonality error
+    const scalar severeNonOrthogonalityThreshold =
+      ::cos
+        (
+            nonOrthThreshold_/180.0*constant::mathematical::pi
+        );
+
+    const vector fcToOwn(compactFace.centre(compactPoints) - ownPt);
+
+    // Note: normal vector already normalised
+    const scalar dDotN = (fcToOwn & n)/(mag(fcToOwn) + VSMALL);
+
+    if (dDotN > severeNonOrthogonalityThreshold)
+    {
+        WarningInFunction
+            << "Detected severely non-orthogonal face with non-orthogonality: "
+            << ::acos(dDotN)/constant::mathematical::pi*180.0
+            << "cell:" << cellI << " old face:" << faceI
+            << " newFace:" << newFace
+            << " coords:" << compactPoints
+            << " ownPt:" << ownPt
+            << " boundaryPt:" << boundaryPt
+            << " dDotN:" << dDotN
+            << endl;
+    }
+}
+
+
+Foam::label Foam::refinement::faceConsistentRefinement
+(
+    boolList& cellsToRefine
+) const
+{
+    // Count number of cells that will be added
+    label nAddCells = 0;
+
+    // Get necessary mesh data
+    const label nFaces = mesh_.nFaces();
+    const label nInternalFaces = mesh_.nInternalFaces();
+
+    const labelList& owner = mesh_.faceOwner();
+    const labelList& neighbour = mesh_.faceNeighbour();
+
+    // Loop through internal faces and check consistency
+    for (label faceI = 0; faceI < nInternalFaces; ++faceI)
+    {
+        // Get owner and neighbour labels
+        const label& own = owner[faceI];
+        const label& nei = neighbour[faceI];
+
+        // Get owner and neighbour cell levels
+        // Note: If the cell is marked for refinement, the level is current
+        // level + 1, otherwise it is equal to the current level
+        const label ownLevel =
+            cellsToRefine[own] ? cellLevel_[own] + 1 : cellLevel_[own];
+        const label neiLevel =
+            cellsToRefine[nei] ? cellLevel_[nei] + 1 : cellLevel_[nei];
+
+        if (ownLevel > (neiLevel + 1))
+        {
+            // Owner level is higher than neighbour level + 1, neighbour must be
+            // marked for refinement
+            cellsToRefine[nei] = true;
+            ++nAddCells;
+        }
+        else if (neiLevel > (ownLevel + 1))
+        {
+            // Neighbour level is higher than owner level + 1, owner must be
+            // marked for refinement
+            cellsToRefine[own] = true;
+            ++nAddCells;
+        }
+    }
+
+    // Create owner level for boundary faces to prepare for swapping on coupled
+    // boundaries
+    labelList ownLevel(nFaces - nInternalFaces);
+    forAll (ownLevel, i)
+    {
+        // Get owner of the face and update owner cell levels
+        const label& own = owner[i + nInternalFaces];
+        ownLevel[i] =
+            cellsToRefine[own] ? cellLevel_[own] + 1 : cellLevel_[own];
+    }
+
+    // Swap boundary face lists (coupled boundary update)
+    syncTools::swapBoundaryFaceList(mesh_, ownLevel);
+
+    // Note: now the ownLevel list actually contains the neighbouring level
+    // (from the other side), use alias (reference) for clarity from now on
+    const labelList& neiLevel = ownLevel;
+
+    // Loop through boundary faces
+    forAll (neiLevel, i)
+    {
+        // Get owner of the face and owner level
+        const label& own = owner[i + nInternalFaces];
+        const label curOwnLevel =
+            cellsToRefine[own] ? cellLevel_[own] + 1 : cellLevel_[own];
+
+        // Note: we are using more stringent 1:1 consistency across coupled
+        // boundaries in order to simplify handling of edge based consistency
+        // checks for parallel runs
+        // Bugfix related to PLB: Check whether owner is already marked for
+        // refinement. Will allow 2:1 consistency across certain processor faces
+        // where we have a new processor boundary. VV, 23/Jan/2019.
+        if
+        (
+            (neiLevel[i] > curOwnLevel)
+         && !cellsToRefine[own]
+        )
+        {
+            // Neighbour level is higher than owner level, owner must be
+            // marked for refinement
+            cellsToRefine[own] = true;
+            ++nAddCells;
+        }
+
+        // Note: other possibility (that owner level is higher than neighbour
+        // level) is taken into account on the other side automatically
+    }
+
+    // Return number of added cells
+    return nAddCells;
+}
+
+
+Foam::label Foam::refinement::edgeConsistentRefinement
+(
+    boolList& cellsToRefine
+) const
+{
+    // Count number of cells that will be added
+    label nAddCells = 0;
+
+    // Algorithm: loop over all edges and visit all unique cell pairs sharing
+    // this particular edge. Then, ensure 2:1 edge consistency by marking
+    // cell with lower level for refinement
+
+    // Get edge cells
+    const labelListList& meshEdgeCells = mesh_.edgeCells();
+
+    // Loop through all mesh edges
+    forAll (meshEdgeCells, edgeI)
+    {
+        // Get current edge cells
+        const labelList& curEdgeCells = meshEdgeCells[edgeI];
+
+        // Loop through all edge cells
+        forAll (curEdgeCells, i)
+        {
+            // Get first cell index
+            const label& cellI = curEdgeCells[i];
+
+            // Loop through remaining edge cells
+            for (label j = i + 1; j < curEdgeCells.size(); ++j)
+            {
+                // Get second cell index
+                const label& cellJ = curEdgeCells[j];
+
+                // Get levels of the two cells. If the cell is marked for
+                // refinement, the level is current level + 1, otherwise it is
+                // equal to the current level
+
+                // Note: cellsToRefine flag for both cellI and cellJ might
+                // change, this is why we need to recalculate cellI level here
+                const label cellILevel =
+                    cellsToRefine[cellI]
+                  ? cellLevel_[cellI] + 1
+                  : cellLevel_[cellI];
+
+                const label cellJLevel =
+                    cellsToRefine[cellJ]
+                  ? cellLevel_[cellJ] + 1
+                  : cellLevel_[cellJ];
+
+                if (cellILevel > cellJLevel + 1)
+                {
+                    // Level of cellI is higher than level of cellJ + 1, cellJ
+                    // must be marked for refinement
+                    cellsToRefine[cellJ] = true;
+                    ++nAddCells;
+                }
+                else if (cellJLevel > cellILevel + 1)
+                {
+                    // Level of cellJ is higher than level of cellI + 1, cellI
+                    // must be marked for refinement
+                    cellsToRefine[cellI] = true;
+                    ++nAddCells;
+                }
+            }
+        }
+    }
+
+    // Note: in order to avoid very difficult and time-consuming parallelisation
+    // of edge cell connectivity and edge cell values, we enforce a more
+    // stringent face-based consistency across processor boundaries. Basically,
+    // if a face-based consistency of 1:1 (not 2:1 as for ordinary faces) is
+    // ensured, the edge-based consistency becomes a local operation (I'm not
+    // 100% sure to be honest since there are countless variants when dealing
+    // with arbitrary polyhedral cells).
+    // See faceConsistentRefinement for details. VV, 17/Apr/2018.
+
+    // Return number of added cells
+    return nAddCells;
+}
+
+
+Foam::label Foam::refinement::faceConsistentUnrefinement
+(
+    boolList& cellsToUnrefine
+) const
+{
+    // Count number of removed cells from unrefinement
+    label nRemCells = 0;
+
+    // Get necessary mesh data
+    const label nFaces = mesh_.nFaces();
+    const label nInternalFaces = mesh_.nInternalFaces();
+
+    const labelList& owner = mesh_.faceOwner();
+    const labelList& neighbour = mesh_.faceNeighbour();
+
+    // Loop through internal faces and check consistency
+    for (label faceI = 0; faceI < nInternalFaces; ++faceI)
+    {
+        // Get owner and neighbour labels
+        const label& own = owner[faceI];
+        const label& nei = neighbour[faceI];
+
+        // Get owner and neighbour cell levels
+        // Note: If the cell is marked for unrefinement, the level is current
+        // level - 1, otherwise it is equal to the current level
+        const label ownLevel =
+            cellsToUnrefine[own] ? cellLevel_[own] - 1 : cellLevel_[own];
+        const label neiLevel =
+            cellsToUnrefine[nei] ? cellLevel_[nei] - 1 : cellLevel_[nei];
+
+        if (ownLevel < (neiLevel - 1))
+        {
+            // Owner level is smaller than neighbour level - 1, we must not
+            // unrefine owner
+
+            // Check whether the cell has not been marked for unrefinement
+            if (!cellsToUnrefine[own])
+            {
+                FatalErrorInFunction
+                    << "Cell not marked for unrefinement, indicating a"
+                    << " previous unnoticed problem with unrefinement."
+                    << nl
+                    << "Owner: " << own << ", neighbour: " << nei
+                    << nl
+                    << "Owner level: " << ownLevel
+                    << ", neighbour level: " << neiLevel << nl
+                    << "This is probably because the refinement and "
+                    << "unrefinement regions are very close." << nl
+                    << "Try increasing nUnrefinementBufferLayers. "
+                    << abort(FatalError);
+            }
+            else
+            {
+                cellsToUnrefine[own] = false;
+                ++nRemCells;
+            }
+        }
+        else if (neiLevel < (ownLevel - 1))
+        {
+            // Neighbour level is smaller than owner level - 1, we must not
+            // unrefine neighbour
+
+            // Check whether the cell has not been marked for unrefinement
+            if (!cellsToUnrefine[nei])
+            {
+                FatalErrorInFunction
+                    << "Cell not marked for unrefinement, indicating a"
+                    << " previous unnoticed problem with unrefinement."
+                    << nl
+                    << "Owner: " << own << ", neighbour: " << nei
+                    << nl
+                    << "Owner level: " << ownLevel
+                    << ", neighbour level: " << neiLevel << nl
+                    << "This is probably because the refinement and "
+                    << "unrefinement regions are very close." << nl
+                    << "Try increasing nUnrefinementBufferLayers. "
+                    << abort(FatalError);
+            }
+            else
+            {
+                cellsToUnrefine[nei] = false;
+                ++nRemCells;
+            }
+        }
+    }
+
+    // Create owner level for boundary faces to prepare for swapping on coupled
+    // boundaries
+    labelList ownLevel(nFaces - nInternalFaces);
+    forAll (ownLevel, i)
+    {
+        // Get owner of the face and update owner cell levels
+        const label& own = owner[i + nInternalFaces];
+        ownLevel[i] =
+            cellsToUnrefine[own] ? cellLevel_[own] - 1 : cellLevel_[own];
+    }
+
+    // Swap boundary face lists (coupled boundary update)
+    syncTools::swapBoundaryFaceList(mesh_, ownLevel);
+
+    // Note: now the ownLevel list actually contains the neighbouring level
+    // (from the other side), use alias (reference) for clarity from now on
+    const labelList& neiLevel = ownLevel;
+
+    // Loop through boundary faces
+    forAll (neiLevel, i)
+    {
+        // Get owner of the face and owner level
+        const label& own = owner[i + nInternalFaces];
+        const label curOwnLevel =
+            cellsToUnrefine[own] ? cellLevel_[own] - 1 : cellLevel_[own];
+
+        // Note: we are using more stringent 1:1 consistency across coupled
+        // boundaries in order to simplify handling of edge based consistency
+        // checks for parallel runs
+        if (curOwnLevel < neiLevel[i])
+        {
+            // Owner level is smaller than neighbour level, we must not
+            // unrefine owner
+
+            // Check whether the cell has not been marked for unrefinement
+            // Note: this "redundancy" check should not be performed if we are
+            // running with dynamic load balancing. If an ordinary face with
+            // standard consistency (2:1) becomes a processor face with more
+            // stringent consistency (1:1), the refinement still remains valid,
+            // even though the 1:1 consistency is not achieved for this time
+            // step. Doing further refinement will make sure that this does not
+            // exceed at least 2:1 consistency and therefore 2:1 edge
+            // consistency as well. Instead of issuing a FatalError, issue a
+            // Warning and wrap it into debug
+            if (!cellsToUnrefine[own])
+            {
+                if (debug)
+                {
+                    WarningInFunction
+                        << "Boundary cell not marked for unrefinement,"
+                        << " indicating a previous unnoticed problem with"
+                        << " unrefinement."
+                        << nl
+                        << "Owner: " << own
+                        << nl
+                        << "Owner level: " << curOwnLevel
+                        << ", neighbour level: " << neiLevel[i] << nl
+                        << "This is probably because the refinement and "
+                        << "unrefinement regions are very close." << nl
+                        << "Try increasing nUnrefinementBufferLayers. "
+                        << nl
+                        << "Another possibility is that you are running "
+                        << "with Dynamic Load Balancing, in which case "
+                        << "this should be fine."
+                        << endl;
+                }
+            }
+            else
+            {
+                cellsToUnrefine[own] = false;
+                ++nRemCells;
+            }
+        }
+
+        // Note: other possibility (that neighbour level is smaller than owner
+        // level) is taken into account on the other side automatically
+    }
+
+    // Return number of local cells removed from unrefinement
+    return nRemCells;
+}
+
+
+Foam::label Foam::refinement::edgeConsistentUnrefinement
+(
+    boolList& cellsToUnrefine
+) const
+{
+    // Count number of cells that will be removed
+    label nRemCells = 0;
+
+    // Algorithm: loop over all edges and visit all unique cell pairs sharing
+    // this particular edge. Then, ensure 2:1 edge consistency by protecting the
+    // cell with lower level from unrefinement
+
+    // Get edge cells
+    const labelListList& meshEdgeCells = mesh_.edgeCells();
+
+    // Loop through all mesh edges
+    forAll (meshEdgeCells, edgeI)
+    {
+        // Get current edge cells
+        const labelList& curEdgeCells = meshEdgeCells[edgeI];
+
+        // Loop through all edge cells
+        forAll (curEdgeCells, i)
+        {
+            // Get first cell index
+            const label& cellI = curEdgeCells[i];
+
+            // Loop through remaining edge cells
+            for (label j = i + 1; j < curEdgeCells.size(); ++j)
+            {
+                // Get second cell index
+                const label& cellJ = curEdgeCells[j];
+
+                // Get levels of the two cells. If the cell is marked for
+                // unrefinement, the level is current level - 1, otherwise it is
+                // equal to the current level
+
+                // Note: cellsToUnrefine flag for both cellI and cellJ might
+                // change, this is why we need to recalculate cellI level here
+                const label cellILevel =
+                    cellsToUnrefine[cellI]
+                  ? cellLevel_[cellI] - 1
+                  : cellLevel_[cellI];
+
+                const label cellJLevel =
+                    cellsToUnrefine[cellJ]
+                  ? cellLevel_[cellJ] - 1
+                  : cellLevel_[cellJ];
+
+                if (cellILevel < cellJLevel - 1)
+                {
+                    // Level of cellI is smaller than level of cellJ - 1, cellI
+                    // must be protected from unrefinement
+
+                    // Check whether the cell has not been marked for
+                    // unrefinement
+                    if (!cellsToUnrefine[cellI])
+                    {
+                        if (debug)
+                        {
+                            WarningInFunction
+                                << "Cell not marked for unrefinement, indicating a"
+                                << " previous unnoticed problem with unrefinement."
+                                << nl
+                                << "cellI: " << cellI << ", cellJ: " << cellJ
+                                << nl
+                                << "Level of cellI: " << cellILevel
+                                << ", level of cellJ: " << cellJLevel << nl
+                                << "This is probably because the refinement and "
+                                << "unrefinement regions are very close." << nl
+                                << "Try increasing nUnrefinementBufferLayers. "
+                                << endl;
+                        }
+                    }
+                    else
+                    {
+                        cellsToUnrefine[cellI] = false;
+                        ++nRemCells;
+                    }
+                }
+                else if (cellJLevel < cellILevel - 1)
+                {
+                    // Level of cellJ is smaller than level of cellI - 1, cellJ
+                    // must be protected from unrefinement
+
+                    // Check whether the cell has not been marked for
+                    // unrefinement
+                    if (!cellsToUnrefine[cellJ])
+                    {
+                        if (debug)
+                        {
+                            WarningInFunction
+                                << "Cell not marked for unrefinement, indicating a"
+                                << " previous unnoticed problem with unrefinement."
+                                << nl
+                                << "cellI: " << cellI << ", cellJ: " << cellJ
+                                << nl
+                                << "Level of cellI: " << cellILevel
+                                << ", level of cellJ: " << cellJLevel << nl
+                                << "This is probably because the refinement and "
+                                << "unrefinement regions are very close." << nl
+                                << "Try increasing nUnrefinementBufferLayers. "
+                                << endl;
+                        }
+                    }
+                    else
+                    {
+                        cellsToUnrefine[cellJ] = false;
+                        ++nRemCells;
+                    }
+                }
+            }
+        }
+    }
+
+    // Note: in order to avoid very difficult and time-consuming parallelisation
+    // of edge cell connectivity and edge cell values, we enforce a more
+    // stringent face-based consistency across processor boundaries. Basically,
+    // if a face-based consistency of 1:1 (not 2:1 as for ordinary faces) is
+    // ensured, the edge-based consistency becomes a local operation (I'm not
+    // 100% sure to be honest whether this is true all the time since there are
+    // countless variants when dealing with arbitrary polyhedral cells).
+    // See faceConsistentRefinement for details. VV, 3/Apr/2018.
+
+    // Return number of removed cells
+    return nRemCells;
+}
+
+
+// * * * * * * * * * * * * * * * * Constructors  * * * * * * * * * * * * * * //
+
+Foam::refinement::refinement
+(
+    const word& name,
+    const dictionary& dict,
+    const label index,
+    const polyTopoChanger& mme
+)
+:
+    polyMeshModifier(name, index, mme, Switch(dict.lookup("active"))),
+    mesh_(mme.mesh()),
+    cellsToRefine_(),
+    splitPointsToUnrefine_(),
+    cellLevel_
+    (
+        IOobject
+        (
+            "cellLevel",
+            mesh_.facesInstance(),
+            polyMesh::meshSubDir,
+            mesh_,
+            IOobject::READ_IF_PRESENT,
+            IOobject::AUTO_WRITE
+        ),
+        labelField(mesh_.nCells(), 0)
+    ),
+    pointLevel_
+    (
+        IOobject
+        (
+            "pointLevel",
+            mesh_.facesInstance(),
+            polyMesh::meshSubDir,
+            mesh_,
+            IOobject::READ_IF_PRESENT,
+            IOobject::AUTO_WRITE
+        ),
+        labelField(mesh_.nPoints(), 0)
+    ),
+    refinementLevelIndicator_(0), // Must be empty before setting refinement
+    faceRemover_(mesh_, GREAT),   // Merge boundary faces wherever possible
+    maxCells_(readLabel(dict.lookup("maxCells"))),
+    maxRefinementLevel_(readLabel(dict.lookup("maxRefinementLevel"))),
+    edgeBasedConsistency_
+    (
+        dict.lookupOrDefault<Switch>("edgeBasedConsistency", true)
+    ),
+    nRefinementBufferLayers_
+    (
+        readScalar(dict.lookup("nRefinementBufferLayers"))
+    ),
+    nUnrefinementBufferLayers_
+    (
+        readScalar(dict.lookup("nUnrefinementBufferLayers"))
+    )
+{
+    Info<< "refinement::refinement: " << "Created pointLevel and cellLevel"
+        << endl;
+
+    // Check consistency between cellLevel and number of cells and pointLevel
+    // and number of points in the mesh
+    if
+    (
+        cellLevel_.size() != mesh_.nCells()
+     || pointLevel_.size() != mesh_.nPoints()
+    )
+    {
+        FatalErrorInFunction
+            << "Restarted from inconsistent cellLevel or pointLevel files."
+            << endl
+            << "Number of cells in mesh: " << mesh_.nCells()
+            << " does not equal size of cellLevel: " << cellLevel_.size() << nl
+            << "Number of points in mesh: " << mesh_.nPoints()
+            << " does not equal size of pointLevel: " << pointLevel_.size()
+            << abort(FatalError);
+    }
+
+    // Check specified number of maximum cells
+    if (maxCells_ < 1)
+    {
+        FatalErrorInFunction
+            << "Specified zero or negative maxCells."
+            << nl
+            << "This is not allowed."
+            << abort(FatalError);
+    }
+
+    // Check maximum refinement level
+    if (maxRefinementLevel_ < 0)
+    {
+        FatalErrorInFunction
+            << "Negative maxRefinementLevel specified."
+            << nl
+            << "This is not allowed."
+            << abort(FatalError);
+    }
+
+    // If the maximum refinementLevel is greater than 2 and the user insists on
+    // not using point based refinement strategy, issue a warning
+    if (!edgeBasedConsistency_ && maxRefinementLevel_ > 2)
+    {
+        WarningInFunction
+            << "You are not using point based consistency for dynamic"
+            << " refinement."
+            << nl
+            << "Since you are allowing more than two maximum refinement"
+            << " refinement levels, this might produce erroneous mesh due to"
+            << " 8:1 point conflicts."
+            << nl
+            << "In order to supress this message and use point based"
+            << " consistency checks, set edgeBasedConsistency to true."
+            << endl;
+    }
+
+    // Check number of refinement buffer layers
+    if (nRefinementBufferLayers_ < 0)
+    {
+        FatalErrorInFunction
+            << "Negative nRefinementBufferLayers specified."
+            << nl
+            << "This is not allowed."
+            << abort(FatalError);
+    }
+
+    // Check number of unrefinement buffer layers
+    if (nUnrefinementBufferLayers_ < 0)
+    {
+        FatalErrorInFunction
+            << "Negative nUnrefinementBufferLayers specified."
+            << nl
+            << "This is not allowed."
+            << abort(FatalError);
+    }
+
+    // Check whether the number of unrefinement buffer layers is smaller than
+    // number of refinement buffer layers + 2
+    if (nUnrefinementBufferLayers_ < nRefinementBufferLayers_ + 2)
+    {
+        WarningInFunction
+            << "Using " << nUnrefinementBufferLayers_
+            << " unrefinement buffer layers and " << nRefinementBufferLayers_
+            << " refinement buffer layers."
+            << nl
+            << "Make sure that the number of unrefinement buffer layers is "
+            << "at least nRefinementBufferLayers + 2" << nl
+            << "in order to avoid problems with edge level inconsistency when "
+            << "refinement and unrefinement are performed in same iteration."
+            << endl;
+    }
+}
+
+
+// * * * * * * * * * * * * * Public Member Functions * * * * * * * * * * * * //
+
+bool Foam::refinement::changeTopology() const
+{
+    if (!active())
+    {
+        // Modifier is inactive, skip topo change
+        if (debug)
+        {
+            Pout<< "bool refinement::changeTopology() const"
+                << "for object " << name() << " : "
+                << "Inactive" << endl;
+        }
+
+        return false;
+    }
+    else
+    {
+        return true;
+    }
+}
+
+
+void Foam::refinement::setRefinement(batchPolyTopoChange& ref) const
+{
+    // Set refinement and unrefinement
+    this->setRefinementInstruction(ref);
+    this->setUnrefinementInstruction(ref);
+
+    // Clear the list of cells to refine and split points to unrefine since the
+    // refinement/unrefinement instructions have been set
+    cellsToRefine_.clear();
+    splitPointsToUnrefine_.clear();
+}
+
+
+void Foam::refinement::modifyMotionPoints
+(
+    pointField& motionPoints
+) const
+{
+    if (debug)
+    {
+        Pout<< "void refinement::modifyMotionPoints("
+            << "pointField& motionPoints) const for object "
+            << name() << " : ";
+    }
+
+    if (debug)
+    {
+        Pout << "No motion point adjustment" << endl;
+    }
+}
+
+
+
+void Foam::refinement::updateMesh(const mapPolyMesh& map)
+{
+    if (debug)
+    {
+        Info<< "refinement::updateMesh(const mapPolyMesh&) "
+            << " for object " << name() << " : "
+            << "Updating cell and point levels."
+            << endl;
+    }
+
+    // Mesh has changed topologically, we need to update cell and point levels
+    // and optionally face removal object
+
+    // Get cell map: from current mesh cells to previous mesh cells
+    const labelList& cellMap = map.cellMap();
+
+    // Create new cell level
+    labelList newCellLevel(cellMap.size());
+
+    // Loop through all new cells
+    forAll (cellMap, newCellI)
+    {
+        adjustRefLevel(newCellLevel[newCellI], cellMap[newCellI]);
+    }
+
+    // Do cells from points: unrefinement
+    // Note: should other types be done as well?
+    // HJ, 9/Sep/2019
+    const List<objectMap>& cellsFromPoints =  map.cellsFromPointsMap();
+
+    forAll (cellsFromPoints, cfpI)
+    {
+        adjustRefLevel
+        (
+            newCellLevel[cellsFromPoints[cfpI].index()],
+            cellsFromPoints[cfpI].masterObjects()[0]
+        );
+    }
+
+    // Transfer the new cell level into the data member
+    cellLevel_.transfer(newCellLevel);
+
+    // Clear out refinementLevelIndicator_ field for next refinement step
+    refinementLevelIndicator_.clear();
+
+
+    // Point level will be updated based on already updated cell level. Level
+    // for newly added points has to be equal to the maximum cell level of
+    // surrounding points. At this point, mesh is fully topologically valid so
+    // it is safe to use pointCells
+
+    // Get point map: from current mesh points to previous mesh points
+    const labelList& pointMap = map.pointMap();
+
+    // Get point cell
+    const labelListList& meshPointCells = mesh_.pointCells();
+
+    // Create new point level
+    labelList newPointLevel(pointMap.size());
+
+    // Loop through all new points
+    forAll (pointMap, newPointI)
+    {
+        // Get index of the corresponding old point
+        const label& oldPointI = pointMap[newPointI];
+
+        if (oldPointI == -1)
+        {
+            // This point has been appended without any master point, use
+            // surrounding cells to determine new point level
+
+            // Get new cells surrounding this new point
+            const labelList& pCells = meshPointCells[newPointI];
+
+            // Find maximum cell level for this point
+            label maxCellLevel = 0;
+            forAll (pCells, i)
+            {
+                maxCellLevel = max(maxCellLevel, cellLevel_[pCells[i]]);
+            }
+
+            // Set new point level as the maximum of the surrounding cells
+            newPointLevel[newPointI] = maxCellLevel;
+        }
+        else
+        {
+            // This point is either old point or it has been added in terms of
+            // another point. New point level is equal to the old point level
+            newPointLevel[newPointI] = pointLevel_[oldPointI];
+        }
+    }
+
+    // Sync the new point level. Note: here, we assume that the call to
+    // updateMesh happened after polyBoundaryMesh::updateMesh where the
+    // processor data is fully rebuilt. If this is not the case, the point
+    // level will remain unsynced and will cause all kinds of trouble that
+    // are extremely difficult to spot. See the change in
+    // polyTopoChanger::changeMesh order of calling polyMesh::updateMesh and
+    // polyTopoChanger::update. VV, 19/Feb/2019.
+    syncTools::syncPointList
+    (
+        mesh_,
+        newPointLevel,
+        maxEqOp<label>(),
+        label(0)   // Null value
+    );
+
+    // Transfer the new point level into the data member
+    pointLevel_.transfer(newPointLevel);
+
+    // Mark files as changed
+    setInstance(mesh_.facesInstance());
+
+    // Update face remover
+    faceRemover_.updateMesh(map);
+}
+
+
+void Foam::refinement::write(Ostream& os) const
+{
+    os  << nl << type() << nl
+        << name() << nl
+        << maxCells_ << nl
+        << maxRefinementLevel_ << nl
+        << edgeBasedConsistency_ << nl
+        << nRefinementBufferLayers_ << nl
+        << nUnrefinementBufferLayers_ << endl;
+}
+
+
+void Foam::refinement::writeDict(Ostream& os) const
+{
+    // Write necessary data before writing dictionary
+    cellLevel_.write();
+    pointLevel_.write();
+
+    os  << nl << name() << nl << token::BEGIN_BLOCK << nl
+        << "    type " << type()
+        << token::END_STATEMENT << nl
+        << "    maxCells " << maxCells_
+        << token::END_STATEMENT << nl
+        << "    maxRefinementLevel " << maxRefinementLevel_
+        << token::END_STATEMENT << nl
+        << "    edgeBasedConsistency " << edgeBasedConsistency_
+        << token::END_STATEMENT << nl
+        << "    nRefinementBufferLayers " << nRefinementBufferLayers_
+        << token::END_STATEMENT << nl
+        << "    nUnrefinementBufferLayers " << nUnrefinementBufferLayers_
+        << token::END_STATEMENT << nl
+        << "    active " << active()
+        << token::END_STATEMENT << nl
+        << token::END_BLOCK << endl;
+}
+
+
+// ************************************************************************* //
diff --git a/src/dynamicMesh/refinement/refinement/refinement.H b/src/dynamicMesh/refinement/refinement/refinement.H
new file mode 100644
index 0000000000000000000000000000000000000000..a9754c5c851dc6bf94e4c436f7e929cdcb1cea98
--- /dev/null
+++ b/src/dynamicMesh/refinement/refinement/refinement.H
@@ -0,0 +1,392 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | foam-extend: Open Source CFD
+   \\    /   O peration     | Version:     5.0
+    \\  /    A nd           | Web:         http://www.foam-extend.org
+     \\/     M anipulation  | For copyright notice see file Copyright
+-------------------------------------------------------------------------------
+License
+    This file is part of foam-extend.
+
+    foam-extend 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.
+
+    foam-extend 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 foam-extend.  If not, see <http://www.gnu.org/licenses/>.
+
+Class
+    Foam::refinement
+
+Description
+    Abstract base class for adaptive mesh refinement using the mesh modifier
+    engine. The class provides common interface and functionalities for 3D
+    polyhedral refinement and 2D prismatic refinement.
+
+    The common interface includes (pure virtuals) following member functions:
+    - setCellsToRefine
+    - setSplitPointsToUnrefine
+
+    With a lot of ordinary protected member functions which are used by both
+    derived classes.
+
+    Note: I've written it this way in order to avoid unnecesasry code
+    duplication, but I'm 99% sure that if someone else wants to write additional
+    refinement strategy (e.g. directional refinement) as derived class, the
+    interface will need to change.
+
+SourceFiles
+    refinement.C
+
+Author
+    Vuko Vukcevic, Wikki Ltd.  All rights reserved.
+
+Notes
+    Generalisation of hexRef8 for polyhedral cells and refactorisation using
+    polyMesh modifier engine.
+
+\*---------------------------------------------------------------------------*/
+
+#ifndef refinement_H
+#define refinement_H
+
+#include "polyMeshModifier.H"
+#include "labelIOField.H"
+#include "removeFaces.H"
+#include "batchPolyTopoChange.H"
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+namespace Foam
+{
+
+/*---------------------------------------------------------------------------*\
+                            Class refinement Declaration
+\*---------------------------------------------------------------------------*/
+
+class refinement
+:
+    public polyMeshModifier
+{
+protected:
+
+    // Protected enumeration for refinement status
+    enum refinementStatus
+    {
+        UNREFINED = -1,
+        UNCHANGED = 0,
+        REFINED = 1
+    };
+
+    static const scalar nonOrthThreshold_;
+
+    // Protected data
+
+        //- Reference to polyMesh for easy access in helper functions
+        const polyMesh& mesh_;
+
+
+        // Refinement control and handling
+
+            //- List of cells to refine in this time step
+            mutable labelList cellsToRefine_;
+
+            //- List of split point labels to unrefine in this time step
+            mutable labelList splitPointsToUnrefine_;
+
+            //- Cell refinement level
+            mutable labelIOField cellLevel_;
+
+            //- Point refinement level
+            mutable labelIOField pointLevel_;
+
+            //- Helper list for original (old) cells that will be refined or
+            //  unrefined. The list is updated in setPolyhedralRefinement and
+            //  setPolyhedralUnfereinement and is used in updateMesh to update
+            //  the cellLevel on the new mesh.
+            //  Values stored in the list are:
+            //  a) UNREFINED = -1 = cell is unrefined
+            //  b) UNCHANGED =  0 = cell is untouched
+            //  c) REFINED   = +1 = cell is refined
+            mutable labelList refinementLevelIndicator_;
+
+            //- Face remover engine
+            mutable removeFaces faceRemover_;
+
+            //- Maximum number of cells in the mesh. Note: not strictly enforced
+            label maxCells_;
+
+            //- Maximum number of refinement levels for a given cell
+            label maxRefinementLevel_;
+
+            //- Switch whether to use edge based consistency on refinement
+            Switch edgeBasedConsistency_;
+
+            //- Number of buffer layers for refinement
+            label nRefinementBufferLayers_;
+
+            //- Number of buffer layers for unrefinement, controlling how far
+            //  the unrefinement region needs to be from current refinement
+            //  region
+            label nUnrefinementBufferLayers_;
+
+
+    // Protected Pure Virtual Member Functions
+
+        // Global topology modification functions (operate on whole polyMesh)
+
+            //- Set refinement instruction
+            virtual void setRefinementInstruction
+            (
+                batchPolyTopoChange& ref
+            ) const = 0;
+
+            //- Set unrefinement instruction
+            virtual void setUnrefinementInstruction
+            (
+                batchPolyTopoChange& ref
+            ) const = 0;
+
+
+    // Protected Member Functions
+
+        // Useful helper functions used by derived classes
+
+            //- Set file instance for cellLevel_ and pointLevel_
+            void setInstance(const fileName& inst) const;
+
+
+        // Local topology modification functions (operate on cells/faces)
+
+            //- Adds a face on top of existing faceI. Reverses if nessecary
+            label addFace
+            (
+                batchPolyTopoChange& ref,
+                const label faceI,
+                const face& newFace,
+                const label own,
+                const label nei
+            ) const;
+
+            //- Adds internal face from point. No checks on reversal
+            label addInternalFace
+            (
+                batchPolyTopoChange& ref,
+                const label meshFaceI,
+                const label meshPointI,
+                const face& newFace,
+                const label own,
+                const label nei
+            ) const;
+
+            //- Modifies existing faceI for either new owner/neighbour or new
+            //  face points. Reverses if nessecary
+            void modifyFace
+            (
+                batchPolyTopoChange& ref,
+                const label faceI,
+                const face& newFace,
+                const label own,
+                const label nei
+            ) const;
+
+
+        // Topological change helper functions
+
+            //- Store vertices from startFp up to face split point.
+            //  Used when splitting face into n faces where n is the number of
+            //  points in a face (or number of edges)
+            void walkFaceToMid
+            (
+                const labelList& edgeMidPoint,
+                const label cLevel,
+                const label faceI,
+                const label startFp,
+                DynamicList<label>& faceVerts
+            ) const;
+
+            //- Same as walkFaceToMid but now walk back
+            void walkFaceFromMid
+            (
+                const labelList& edgeMidPoint,
+                const label cLevel,
+                const label faceI,
+                const label startFp,
+                DynamicList<label>& faceVerts
+            ) const;
+
+            //- Get index of point with minimum point level
+            label findMinLevel(const labelList& f) const;
+
+            //- Get index of point with maximum point level
+            label findMaxLevel(const labelList& f) const;
+
+            //- Count number of vertices <= anchorLevel for a given face
+            label countAnchors
+            (
+                const labelList& f,
+                const label anchorLevel
+            ) const;
+
+            //- Adjust cell refinement level after topo change
+            void adjustRefLevel
+            (
+                label& curNewCellLevel,
+                const label oldCellI
+            );
+
+
+        // Debug functions
+
+            //- Check orientation of added internal face
+            void checkInternalOrientation
+            (
+                batchPolyTopoChange& ref,
+                const label cellI,
+                const label faceI,
+                const point& ownPt,
+                const point& neiPt,
+                const face& newFace
+            ) const;
+
+            //- Check orientation of a new boundary face
+            void checkBoundaryOrientation
+            (
+                batchPolyTopoChange& ref,
+                const label cellI,
+                const label faceI,
+                const point& ownPt,
+                const point& boundaryPt,
+                const face& newFace
+            ) const;
+
+
+        // Refinement/unrefinement consistency checks
+
+            //- Updates cellsToRefine such that a face consistent 2:1 refinement
+            //  is obtained. Returns local number of cells changed
+            label faceConsistentRefinement(boolList& cellsToRefine) const;
+
+            //- Updates cellsToRefine such that an edge consistent 4:1 refinement
+            //  is obtained. Returns local number of cells changed
+            label edgeConsistentRefinement(boolList& cellsToRefine) const;
+
+            //- Updates cellsToUnrefine such that a face consistent 2:1
+            //  unrefinement is obtained. Returns local number of cells changed
+            label faceConsistentUnrefinement(boolList& cellsToUnrefine) const;
+
+            //- Updates cellsToUnrefine such that an edge consistent 4:1
+            //  unrefinement is obtained. Returns local number of cells changed
+            label edgeConsistentUnrefinement(boolList& cellsToUnrefine) const;
+
+
+        // Copy control
+
+            //- Disallow default bitwise copy construct
+            refinement(const refinement&);
+
+            //- Disallow default bitwise assignment
+            void operator=(const refinement&);
+
+
+public:
+
+    //- Runtime type information
+    TypeName("refinement");
+
+
+    // Constructors
+
+        //- Construct from dictionary
+        refinement
+        (
+            const word& name,
+            const dictionary& dict,
+            const label index,
+            const polyTopoChanger& mme
+        );
+
+
+    //- Destructor
+    virtual ~refinement() = default;
+
+
+    // Member Functions
+
+        // Access
+
+            //- Return refinement cell level
+            const labelIOField& cellLevel() const
+            {
+                return cellLevel_;
+            }
+
+            //- Return refinement point level
+            const labelIOField& pointLevel() const
+            {
+                return pointLevel_;
+            }
+
+
+        // Edit
+
+            //- Set cells to refine given a list of refinement
+            //  candidates. Refinement candidates are extended within the
+            //  function due to possible 4:1 conflicts and specified number of
+            //  buffer layers.
+            //  Note: must be called BEFORE setSplitPointsToUnrefine
+            virtual void setCellsToRefine
+            (
+                const labelList& refinementCellCandidates
+            ) = 0;
+
+            //- Set split points to unrefine given a list of all mesh points
+            //  that are candidates for unrefinement. Split points are
+            //  determined as a subset of unrefinement candidates, avoiding
+            //  splitting points of cells that are going to be refined at the
+            //  same time and ensuring consistent unrefinement.
+            //  Note: must be called AFTER setCellsToRefine
+            virtual void setSplitPointsToUnrefine
+            (
+                const labelList& unrefinementPointCandidates
+            ) = 0;
+
+
+        // Inherited interface from polyMeshModifier
+
+            //- Check for topology change
+            virtual bool changeTopology() const;
+
+            //- Insert the polyhedral refinement/unrefinement into the
+            //  topological change
+            virtual void setRefinement(batchPolyTopoChange&) const;
+
+            //- Modify motion points to comply with the topological change
+            virtual void modifyMotionPoints(pointField& motionPoints) const;
+
+            //- Force recalculation of locally stored data on topological change
+            virtual void updateMesh(const mapPolyMesh&);
+
+            //- Write
+            virtual void write(Ostream&) const;
+
+            //- Write dictionary
+            virtual void writeDict(Ostream&) const;
+};
+
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+} // End namespace Foam
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+#endif
+
+// ************************************************************************* //
diff --git a/src/dynamicMesh/refinement/refinement/refinementTools.H b/src/dynamicMesh/refinement/refinement/refinementTools.H
new file mode 100644
index 0000000000000000000000000000000000000000..00f0a112e2a495da17117043e6a2f9e2995a0dee
--- /dev/null
+++ b/src/dynamicMesh/refinement/refinement/refinementTools.H
@@ -0,0 +1,351 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | foam-extend: Open Source CFD
+   \\    /   O peration     | Version:     5.0
+    \\  /    A nd           | Web:         http://www.foam-extend.org
+     \\/     M anipulation  | For copyright notice see file Copyright
+-------------------------------------------------------------------------------
+License
+    This file is part of foam-extend.
+
+    foam-extend 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.
+
+    foam-extend 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 foam-extend.  If not, see <http://www.gnu.org/licenses/>.
+
+Namespace
+    Foam::refinementTools
+
+Description
+    Collection of static functions to do various simple mesh related things.
+
+SourceFiles
+    refinementTools.C
+
+\*---------------------------------------------------------------------------*/
+
+#ifndef refinementTools_H
+#define refinementTools_H
+
+#include "vector.H"
+#include "label.H"
+#include "labelList.H"
+#include "pointField.H"
+#include "faceList.H"
+#include "cellList.H"
+#include "primitivePatch.H"
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+namespace Foam
+{
+
+class primitiveMesh;
+class polyMesh;
+
+/*---------------------------------------------------------------------------*\
+                    Namespace refinementTools Declaration
+\*---------------------------------------------------------------------------*/
+
+namespace refinementTools
+{
+    // Bit identifiers for octants (p=plus, m=min e.g. plusXminYminZ)
+
+    static const label mXmYmZ = 0;
+    static const label pXmYmZ = 1;
+    static const label mXpYmZ = 2;
+    static const label pXpYmZ = 3;
+
+    static const label mXmYpZ = 4;
+    static const label pXmYpZ = 5;
+    static const label mXpYpZ = 6;
+    static const label pXpYpZ = 7;
+
+    static const label mXmYmZMask = 1 << mXmYmZ;
+    static const label pXmYmZMask = 1 << pXmYmZ;
+    static const label mXpYmZMask = 1 << mXpYmZ;
+    static const label pXpYmZMask = 1 << pXpYmZ;
+
+    static const label mXmYpZMask = 1 << mXmYpZ;
+    static const label pXmYpZMask = 1 << pXmYpZ;
+    static const label mXpYpZMask = 1 << mXpYpZ;
+    static const label pXpYpZMask = 1 << pXpYpZ;
+
+
+    // Normal handling
+
+        //- Check if n is in same direction as normals of all faceLabels
+        bool visNormal
+        (
+            const vector& n,
+            const vectorField& faceNormals,
+            const labelList& faceLabels
+        );
+
+        //- Calculate point normals on a 'box' mesh (all edges aligned with
+        //  coordinate axes)
+        vectorField calcBoxPointNormals(const primitivePatch& pp);
+
+        //- Normalized edge vector
+        vector normEdgeVec(const primitiveMesh&, const label edgeI);
+
+
+    // OBJ writing
+
+        //- Write obj representation of point
+        void writeOBJ
+        (
+            Ostream& os,
+            const point& pt
+        );
+
+        //- Write obj representation of faces subset
+        void writeOBJ
+        (
+            Ostream& os,
+            const faceList&,
+            const pointField&,
+            const labelList& faceLabels
+        );
+
+        //- Write obj representation of faces
+        void writeOBJ
+        (
+            Ostream& os,
+            const faceList&,
+            const pointField&
+        );
+
+        //- Write obj representation of cell subset
+        void writeOBJ
+        (
+            Ostream& os,
+            const cellList&,
+            const faceList&,
+            const pointField&,
+            const labelList& cellLabels
+        );
+
+
+    // Cell/face/edge walking
+
+        //- Is edge used by cell
+        bool edgeOnCell
+        (
+            const primitiveMesh&,
+            const label cellI,
+            const label edgeI
+        );
+
+        //- Is edge used by face
+        bool edgeOnFace
+        (
+            const primitiveMesh&,
+            const label faceI,
+            const label edgeI
+        );
+
+        //- Is face used by cell
+        bool faceOnCell
+        (
+            const primitiveMesh&,
+            const label cellI,
+            const label faceI
+        );
+
+        //- Return edge among candidates that uses the two vertices.
+        label findEdge
+        (
+            const edgeList& edges,
+            const labelList& candidates,
+            const label v0,
+            const label v1
+        );
+
+        //- Return edge between two vertices. Returns -1 if no edge.
+        label findEdge
+        (
+            const primitiveMesh&,
+            const label v0,
+            const label v1
+        );
+
+        //- Return edge shared by two faces. Throws error if no edge found.
+        label getSharedEdge
+        (
+            const primitiveMesh&,
+            const label f0,
+            const label f1
+        );
+
+        //- Return face shared by two cells. Throws error if none found.
+        label getSharedFace
+        (
+            const primitiveMesh&,
+            const label cell0,
+            const label cell1
+        );
+
+        //- Get faces on cell using edgeI. Throws error if no two found.
+        void getEdgeFaces
+        (
+            const primitiveMesh&,
+            const label cellI,
+            const label edgeI,
+            label& face0,
+            label& face1
+        );
+
+        //- Return label of other edge (out of candidates edgeLabels)
+        //  connected to vertex but not edgeI. Throws error if none found.
+        label otherEdge
+        (
+            const primitiveMesh&,
+            const labelList& edgeLabels,
+            const label edgeI,
+            const label vertI
+        );
+
+        //- Return face on cell using edgeI but not faceI. Throws error
+        //  if none found.
+        label otherFace
+        (
+            const primitiveMesh&,
+            const label cellI,
+            const label faceI,
+            const label edgeI
+        );
+
+        //- Return cell on other side of face. Throws error
+        //  if face not internal.
+        label otherCell
+        (
+            const primitiveMesh&,
+            const label cellI,
+            const label faceI
+        );
+
+        //- Returns label of edge nEdges away from startEdge (in the direction
+        // of startVertI)
+        label walkFace
+        (
+            const primitiveMesh&,
+            const label faceI,
+            const label startEdgeI,
+            const label startVertI,
+            const label nEdges
+        );
+
+
+    // Constraints on position
+
+        //- Set the constrained components of position to mesh centre
+        void constrainToMeshCentre
+        (
+            const polyMesh& mesh,
+            point& pt
+        );
+        void constrainToMeshCentre
+        (
+            const polyMesh& mesh,
+            pointField& pt
+        );
+
+        //- Set the constrained components of directions/velocity to zero
+        void constrainDirection
+        (
+            const polyMesh& mesh,
+            const Vector<label>& dirs,
+            vector& d
+        );
+        void constrainDirection
+        (
+            const polyMesh& mesh,
+            const Vector<label>& dirs,
+            vectorField& d
+        );
+
+
+    // Hex only functionality
+
+        //- Given edge on hex find other 'parallel', non-connected edges.
+        void getParallelEdges
+        (
+            const primitiveMesh&,
+            const label cellI,
+            const label e0,
+            label&,
+            label&,
+            label&
+        );
+
+        //- Given edge on hex find all 'parallel' (i.e. non-connected)
+        //  edges and average direction of them
+        vector edgeToCutDir
+        (
+            const primitiveMesh&,
+            const label cellI,
+            const label edgeI
+        );
+
+        //- Reverse of edgeToCutDir: given direction find edge bundle and
+        //  return one of them.
+        label cutDirToEdge
+        (
+            const primitiveMesh&,
+            const label cellI,
+            const vector& cutDir
+        );
+
+
+    // Face information
+
+        //- Set face information: patch, zone and zone flip for a face
+        void setFaceInfo
+        (
+            const polyMesh& mesh,
+            const label faceI,
+            label& patchID,
+            label& zoneID,
+            label& zoneFlip
+        );
+
+
+    // Mark-up of mesh bits. Relocated from refinement polyMeshModifier
+
+        //- Extend marked cells across faces given a bool list of already marked
+        //  cells
+        void extendMarkedCellsAcrossFaces
+        (
+            const polyMesh& mesh,
+            boolList& markedCell
+        );
+
+        //- Extend marked cells across points given a bool list of already
+        //  marked cells
+        void extendMarkedCellsAcrossPoints
+        (
+            const polyMesh& mesh,
+            boolList& markedCell
+        );
+
+} // End namespace refinementTools
+
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+} // End namespace Foam
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+#endif
+
+// ************************************************************************* //
diff --git a/src/dynamicMesh/setUpdater/setUpdater.C b/src/dynamicMesh/setUpdater/setUpdater.C
index 5c93427521ca674c4498a9330a9ba4fb06678338..19ffc8db45ac94ce8dec5b099cbad352c300d6e8 100644
--- a/src/dynamicMesh/setUpdater/setUpdater.C
+++ b/src/dynamicMesh/setUpdater/setUpdater.C
@@ -27,7 +27,7 @@ License
 
 #include "setUpdater.H"
 #include "polyTopoChanger.H"
-#include "polyTopoChange.H"
+#include "batchPolyTopoChange.H"
 #include "addToRunTimeSelectionTable.H"
 #include "mapPolyMesh.H"
 #include "cellSet.H"
@@ -71,7 +71,7 @@ bool Foam::setUpdater::changeTopology() const
 }
 
 
-void Foam::setUpdater::setRefinement(polyTopoChange&) const
+void Foam::setUpdater::setRefinement(batchPolyTopoChange&) const
 {}
 
 
diff --git a/src/dynamicMesh/setUpdater/setUpdater.H b/src/dynamicMesh/setUpdater/setUpdater.H
index 3fd01c7909bf573d49a8b8311b3a06f58bd4bccd..3106403ab6b03fc45536c9c6167850bfb82c1ce0 100644
--- a/src/dynamicMesh/setUpdater/setUpdater.H
+++ b/src/dynamicMesh/setUpdater/setUpdater.H
@@ -97,7 +97,7 @@ public:
 
         //- Insert the layer addition/removal instructions
         //  into the topological change
-        virtual void setRefinement(polyTopoChange&) const;
+        virtual void setRefinement(batchPolyTopoChange&) const;
 
         //- Modify motion points to comply with the topological change
         virtual void modifyMotionPoints(pointField& motionPoints) const;
diff --git a/src/dynamicMesh/slidingInterface/coupleSlidingInterface.C b/src/dynamicMesh/slidingInterface/coupleSlidingInterface.C
index 40ca37ea7ec1cdff61e16a9bd68ef1cbfcb941eb..5274c2705b71b7d464c06f09f735e3993768b92a 100644
--- a/src/dynamicMesh/slidingInterface/coupleSlidingInterface.C
+++ b/src/dynamicMesh/slidingInterface/coupleSlidingInterface.C
@@ -27,7 +27,7 @@ License
 \*---------------------------------------------------------------------------*/
 
 #include "slidingInterface.H"
-#include "polyTopoChange.H"
+#include "batchPolyTopoChange.H"
 #include "polyMesh.H"
 #include "primitiveMesh.H"
 #include "enrichedPatch.H"
@@ -69,7 +69,7 @@ const Foam::scalar Foam::slidingInterface::edgeCoPlanarTolDefault_ = 0.8;
 // - - missed master edge in cut
 // u - edge already used in cutting
 
-void Foam::slidingInterface::coupleInterface(polyTopoChange& ref) const
+void Foam::slidingInterface::coupleInterface(batchPolyTopoChange& ref) const
 {
     if (debug)
     {
diff --git a/src/dynamicMesh/slidingInterface/decoupleSlidingInterface.C b/src/dynamicMesh/slidingInterface/decoupleSlidingInterface.C
index 64fe0f573cbb304080882cf0778e5c7c1d71c958..0c22ba2f244e3c3a7015f8b50da8d02db0cd71e8 100644
--- a/src/dynamicMesh/slidingInterface/decoupleSlidingInterface.C
+++ b/src/dynamicMesh/slidingInterface/decoupleSlidingInterface.C
@@ -29,7 +29,7 @@ License
 #include "slidingInterface.H"
 #include "polyMesh.H"
 #include "primitiveMesh.H"
-#include "polyTopoChange.H"
+#include "batchPolyTopoChange.H"
 #include "polyTopoChanger.H"
 #include "polyModifyFace.H"
 #include "polyModifyPoint.H"
@@ -38,7 +38,7 @@ License
 
 void Foam::slidingInterface::decoupleInterface
 (
-    polyTopoChange& ref
+    batchPolyTopoChange& ref
 ) const
 {
     if (debug)
diff --git a/src/dynamicMesh/slidingInterface/slidingInterface.C b/src/dynamicMesh/slidingInterface/slidingInterface.C
index cf25565ba1f5fa4e49f3331fa0364eb95af130b6..e41eeef90344dae2cd3d8d525129294d122bb8f8 100644
--- a/src/dynamicMesh/slidingInterface/slidingInterface.C
+++ b/src/dynamicMesh/slidingInterface/slidingInterface.C
@@ -29,7 +29,7 @@ License
 #include "slidingInterface.H"
 #include "polyTopoChanger.H"
 #include "polyMesh.H"
-#include "polyTopoChange.H"
+#include "batchPolyTopoChange.H"
 #include "addToRunTimeSelectionTable.H"
 #include "plane.H"
 
@@ -367,7 +367,7 @@ bool Foam::slidingInterface::changeTopology() const
 }
 
 
-void Foam::slidingInterface::setRefinement(polyTopoChange& ref) const
+void Foam::slidingInterface::setRefinement(batchPolyTopoChange& ref) const
 {
     if (coupleDecouple_)
     {
diff --git a/src/dynamicMesh/slidingInterface/slidingInterface.H b/src/dynamicMesh/slidingInterface/slidingInterface.H
index f3ac02bf527aa4cf9f4a15c9de41f639899e0d77..d5742cc904ff2a2deeca82a1556944001b1036ed 100644
--- a/src/dynamicMesh/slidingInterface/slidingInterface.H
+++ b/src/dynamicMesh/slidingInterface/slidingInterface.H
@@ -264,19 +264,19 @@ private:
             bool projectPoints() const;
 
             //- Couple sliding interface
-            void coupleInterface(polyTopoChange& ref) const;
+            void coupleInterface(batchPolyTopoChange& ref) const;
 
             //- Clear projection
             void clearPointProjection() const;
 
             //- Clear old couple
-            void clearCouple(polyTopoChange& ref) const;
+            void clearCouple(batchPolyTopoChange& ref) const;
 
             //- Decouple interface (returns it to decoupled state)
             //  Note: this should not be used in normal operation of the
             //  sliding mesh, but only to return the mesh to its
             //  original state
-            void decoupleInterface(polyTopoChange& ref) const;
+            void decoupleInterface(batchPolyTopoChange& ref) const;
 
 
     // Static data members
@@ -364,7 +364,7 @@ public:
 
         //- Insert the layer addition/removal instructions
         //  into the topological change
-        virtual void setRefinement(polyTopoChange&) const;
+        virtual void setRefinement(batchPolyTopoChange&) const;
 
         //- Modify motion points to comply with the topological change
         virtual void modifyMotionPoints(pointField& motionPoints) const;
diff --git a/src/dynamicMesh/slidingInterface/slidingInterfaceClearCouple.C b/src/dynamicMesh/slidingInterface/slidingInterfaceClearCouple.C
index 8d5318dce950f2d949c8da2c77aa1b2aecae2a09..bafd41ae652d1b746fa42abf4d051d98cb5a646c 100644
--- a/src/dynamicMesh/slidingInterface/slidingInterfaceClearCouple.C
+++ b/src/dynamicMesh/slidingInterface/slidingInterfaceClearCouple.C
@@ -27,7 +27,7 @@ License
 \*---------------------------------------------------------------------------*/
 
 #include "slidingInterface.H"
-#include "polyTopoChange.H"
+#include "batchPolyTopoChange.H"
 #include "polyMesh.H"
 #include "polyTopoChanger.H"
 #include "polyRemovePoint.H"
@@ -37,7 +37,7 @@ License
 
 void Foam::slidingInterface::clearCouple
 (
-    polyTopoChange& ref
+    batchPolyTopoChange& ref
 ) const
 {
     if (debug)
diff --git a/src/meshTools/Make/files b/src/meshTools/Make/files
index 4a571b5fc28034d5f804073cc2099b7d1a5713a2..d7125f4b4063ff38efa04fe1509bc4c635cb9f08 100644
--- a/src/meshTools/Make/files
+++ b/src/meshTools/Make/files
@@ -325,6 +325,8 @@ mappedPatches/mappedPolyPatch/mappedVariableThicknessWallPolyPatch.C
 mappedPatches/mappedPointPatch/mappedPointPatch.C
 mappedPatches/mappedPointPatch/mappedWallPointPatch.C
 
+batchPolyTopoChange/batchPolyTopoChange.C
+
 polyTopoChange/topoAction/topoActions.C
 polyTopoChange/polyTopoChange.C
 
diff --git a/src/meshTools/batchPolyTopoChange/batchPolyTopoChange.C b/src/meshTools/batchPolyTopoChange/batchPolyTopoChange.C
new file mode 100644
index 0000000000000000000000000000000000000000..3a53b6942016cf5c533891ae61cd036cfd9be2b1
--- /dev/null
+++ b/src/meshTools/batchPolyTopoChange/batchPolyTopoChange.C
@@ -0,0 +1,417 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | foam-extend: Open Source CFD
+   \\    /   O peration     | Version:     5.0
+    \\  /    A nd           | Web:         http://www.foam-extend.org
+     \\/     M anipulation  | For copyright notice see file Copyright
+-------------------------------------------------------------------------------
+License
+    This file is part of foam-extend.
+
+    foam-extend 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.
+
+    foam-extend 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 foam-extend.  If not, see <http://www.gnu.org/licenses/>.
+
+\*---------------------------------------------------------------------------*/
+
+#include "batchPolyTopoChange.H"
+#include "polyMesh.H"
+#include "primitiveMesh.H"
+
+// * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * //
+
+const Foam::label Foam::batchPolyTopoChange::minListSize = 100;
+const Foam::label Foam::batchPolyTopoChange::pointFraction = 10;
+const Foam::label Foam::batchPolyTopoChange::faceFraction = 10;
+const Foam::label Foam::batchPolyTopoChange::cellFraction = 10;
+
+namespace Foam
+{
+    defineTypeNameAndDebug(batchPolyTopoChange, 0);
+}
+
+
+// * * * * * * * * * * * * * * * * Constructors  * * * * * * * * * * * * * * //
+
+// Construct from mesh reference
+Foam::batchPolyTopoChange::batchPolyTopoChange(const polyMesh& mesh)
+:
+    mesh_(mesh),
+    addedPoints_(minListSize),
+    modifiedPoints_(minListSize),
+    removedPoints_
+    (
+        Foam::max(mesh.points().size()/pointFraction, minListSize)
+    ),
+    addedFaces_(minListSize),
+    modifiedFaces_(minListSize),
+    removedFaces_(Foam::max(mesh.faces().size()/faceFraction, minListSize)),
+    addedCells_(minListSize),
+    removedCells_(Foam::max(mesh.nCells()/cellFraction, minListSize))
+{}
+
+
+// * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //
+
+Foam::label Foam::batchPolyTopoChange::setAction(const topoAction& action)
+{
+    if (isType<polyAddPoint>(action))
+    {
+        addedPoints_.append(refCast<const polyAddPoint>(action));
+
+        return mesh_.points().size() + addedPoints_.size() - 1;
+    }
+    else if (isType<polyModifyPoint>(action))
+    {
+        const polyModifyPoint& pmp = refCast<const polyModifyPoint>(action);
+
+        if (debug)
+        {
+            if (removedPoints_.find(pmp.pointID()) != removedPoints_.end())
+            {
+                FatalErrorInFunction
+                    << "Modifying point " << pmp.pointID()
+                    << " for the second time or modifying a removed point.  "
+                    << "This is not allowed."
+                    << abort(FatalError);
+            }
+        }
+
+        modifiedPoints_.append(pmp);
+        removedPoints_.insert(pmp.pointID());
+
+        return -1;
+    }
+    else if (isType<polyRemovePoint>(action))
+    {
+        const polyRemovePoint& prp = refCast<const polyRemovePoint>(action);
+
+        if (debug)
+        {
+            if (removedPoints_.find(prp.pointID()) != removedPoints_.end())
+            {
+                FatalErrorInFunction
+                    << "Removing point " << prp.pointID()
+                    << " for the second time or removing a modified point.  "
+                    << "This is not allowed."
+                    << abort(FatalError);
+            }
+        }
+
+        removedPoints_.insert(prp.pointID());
+
+        return -1;
+    }
+    else if (isType<polyAddFace>(action))
+    {
+        addedFaces_.append(refCast<const polyAddFace>(action));
+
+        return mesh_.faces().size() + addedFaces_.size() - 1;
+    }
+    else if (isType<polyModifyFace>(action))
+    {
+        const polyModifyFace& pmf = refCast<const polyModifyFace>(action);
+
+        if (debug)
+        {
+            if (removedFaces_.find(pmf.faceID()) != removedFaces_.end())
+            {
+                FatalErrorInFunction
+                    << "Modifying face " << pmf.faceID()
+                    << " for the second time or modifying a removed face.  "
+                    << "This is not allowed."
+                    << abort(FatalError);
+            }
+        }
+
+        modifiedFaces_.append(pmf);
+        removedFaces_.insert(pmf.faceID());
+
+        return -1;
+    }
+    else if (isType<polyRemoveFace>(action))
+    {
+        const polyRemoveFace& prf = refCast<const polyRemoveFace>(action);
+
+        if (debug)
+        {
+            if (removedFaces_.find(prf.faceID()) != removedFaces_.end())
+            {
+                FatalErrorInFunction
+                    << "Removing face " << prf.faceID()
+                    << " for the second time or removing a modified face.  "
+                    << "This is not allowed."
+                    << abort(FatalError);
+            }
+        }
+
+        removedFaces_.insert(prf.faceID());
+
+        return -1;
+    }
+    else if (isType<polyAddCell>(action))
+    {
+        addedCells_.append(refCast<const polyAddCell>(action));
+
+        return mesh_.nCells() + addedCells_.size() - 1;
+    }
+    else if (isType<polyModifyCell>(action))
+    {
+        const polyModifyCell& pmc = refCast<const polyModifyCell>(action);
+
+        modifiedCells_.append(pmc);
+
+        return -1;
+    }
+    else if (isType<polyRemoveCell>(action))
+    {
+        const polyRemoveCell& prc = refCast<const polyRemoveCell>(action);
+
+        if (debug)
+        {
+            if (removedCells_.find(prc.cellID()) != removedCells_.end())
+            {
+                FatalErrorInFunction
+                    << "Removing cell " << prc.cellID()
+                    << " for the second time.  This is not allowed."
+                    << abort(FatalError);
+            }
+        }
+
+        removedCells_.insert(prc.cellID());
+
+        return -1;
+    }
+    else
+    {
+        FatalErrorInFunction
+            << "Unknown type of topoChange: " << action.type()
+            << abort(FatalError);
+
+        // Dummy return to keep compiler happy
+        return -1;
+    }
+}
+
+
+bool Foam::batchPolyTopoChange::check() const
+{
+    if (debug)
+    {
+        Info<< "Points: added = " << addedPoints_.size()
+            << " modified = " << modifiedPoints_.size()
+            << " removed = " << removedPoints_.size() - modifiedPoints_.size()
+            << " balance = " << pointBalance()
+            << endl;
+
+        Info<< "Faces: added = " << addedFaces_.size()
+            << " modified = " << modifiedFaces_.size()
+            << " removed = " << removedFaces_.size() - modifiedFaces_.size()
+            << " balance = " << faceBalance()
+            << endl;
+
+        Info<< "Cells: added = " << addedCells_.size()
+            << " modified = " << modifiedCells_.size()
+            << " removed = " << removedCells_.size() - modifiedCells_.size()
+            << " balance = " << cellBalance()
+            << endl;
+    }
+
+    label nErrors = 0;
+
+    // Check points
+
+    if (mesh_.points().size() + pointBalance() < 4)
+    {
+        WarningInFunction
+            << "Less than 4 points in the mesh.  This cannot be a valid mesh."
+            << endl;
+
+        nErrors++;
+    }
+
+    // Collect zone-only and removed points
+    labelHashSet zoneOnlyPoints(removedPoints_);
+
+    forAll (modifiedPoints_, mpI)
+    {
+        if (modifiedPoints_[mpI].inCell())
+        {
+            // Point is live; remove from lookup
+            zoneOnlyPoints.erase
+            (
+                zoneOnlyPoints.find(modifiedPoints_[mpI].pointID())
+            );
+        }
+    }
+
+    forAll (addedPoints_, apI)
+    {
+        if (!addedPoints_[apI].inCell())
+        {
+            zoneOnlyPoints.insert(mesh_.points().size() + apI);
+        }
+    }
+
+    // Check faces
+
+    label nFaceErrors = 0;
+    label nCurFaceError = 0;
+
+    // For all live modified and added faces, check that points are also live
+
+    // Collect zone-only faces
+    labelHashSet zoneOnlyFaces(removedFaces_);
+
+    // Modified faces
+    forAll (modifiedFaces_, mfI)
+    {
+        if (!modifiedFaces_[mfI].onlyInZone())
+        {
+            // Face is live; remove from lookup
+            zoneOnlyFaces.erase
+            (
+                zoneOnlyFaces.find(modifiedFaces_[mfI].faceID())
+            );
+
+            // Count errors in current face
+            nCurFaceError = 0;
+
+            const face& curFace = modifiedFaces_[mfI].newFace();
+
+            forAll (curFace, pointI)
+            {
+                if (zoneOnlyPoints.found(curFace[pointI]))
+                {
+                    nCurFaceError++;
+                }
+            }
+
+            if (nCurFaceError > 0)
+            {
+                if (debug)
+                {
+                    Info<< "Modified face no " << mfI << " is active but uses "
+                        << nCurFaceError << " zone-only or removed points.  "
+                        << "Face: " << curFace << "  Problem points:";
+
+                    forAll (curFace, pointI)
+                    {
+                        if (zoneOnlyPoints.found(curFace[pointI]))
+                        {
+                            Info  << " " << curFace[pointI];
+                        }
+                    }
+
+                    Info << endl;
+                }
+
+                nFaceErrors++;
+            }
+        }
+    }
+
+    // Added faces
+    forAll (addedFaces_, afI)
+    {
+        if (addedFaces_[afI].onlyInZone())
+        {
+            // Zone-only face; add to hash set
+            zoneOnlyFaces.insert(mesh_.faces().size() + afI);
+        }
+        else
+        {
+            // Count errors in current face
+            nCurFaceError = 0;
+
+            const face& curFace = addedFaces_[afI].newFace();
+
+            forAll (curFace, pointI)
+            {
+                if (zoneOnlyPoints.found(curFace[pointI]))
+                {
+                    nCurFaceError++;
+                }
+            }
+
+            if (nCurFaceError > 0)
+            {
+                if (debug)
+                {
+                    Info<< "Added face no " << afI << " is active but uses "
+                        << nCurFaceError << " zone-only or removed points.  "
+                        << "Face: " << curFace << "  Problem points:";
+
+                    forAll (curFace, pointI)
+                    {
+                        if (zoneOnlyPoints.found(curFace[pointI]))
+                        {
+                            Info  << " " << curFace[pointI];
+                        }
+                    }
+
+                    Info << endl;
+                }
+
+                nFaceErrors++;
+            }
+        }
+    }
+
+    // Check cells?
+
+    if (nErrors == 0)
+    {
+        if (debug)
+        {
+            Info << "batchPolyTopoChange OK" << endl;
+        }
+
+        return false;
+    }
+    else
+    {
+        if (debug)
+        {
+            Info<< "bool batchPolyTopoChange::check() const : "
+                << "Detected " << nErrors << " errors."
+                << endl;
+        }
+
+        return true;
+    }
+}
+
+
+void Foam::batchPolyTopoChange::clear()
+{
+    if (debug)
+    {
+        Info<< "void Foam::batchPolyTopoChange::clear() : "
+            << "clearing topological change request" << endl;
+    }
+
+    // Clear all contents
+    addedPoints_.clear();
+    modifiedPoints_.clear();
+    removedPoints_.clear();
+
+    addedFaces_.clear();
+    modifiedFaces_.clear();
+    removedFaces_.clear();
+
+    addedCells_.clear();
+    removedCells_.clear();
+}
+
+
+// ************************************************************************* //
diff --git a/src/meshTools/batchPolyTopoChange/batchPolyTopoChange.H b/src/meshTools/batchPolyTopoChange/batchPolyTopoChange.H
new file mode 100644
index 0000000000000000000000000000000000000000..ca8876b6a0b14762e9a28fdadeed3331469287ae
--- /dev/null
+++ b/src/meshTools/batchPolyTopoChange/batchPolyTopoChange.H
@@ -0,0 +1,256 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | foam-extend: Open Source CFD
+   \\    /   O peration     | Version:     5.0
+    \\  /    A nd           | Web:         http://www.foam-extend.org
+     \\/     M anipulation  | For copyright notice see file Copyright
+-------------------------------------------------------------------------------
+License
+    This file is part of foam-extend.
+
+    foam-extend 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.
+
+    foam-extend 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 foam-extend.  If not, see <http://www.gnu.org/licenses/>.
+
+Class
+    batchPolyTopoChange
+
+Description
+    Class accumulates information on how to perform mesh refinement.
+    Once the refinement request is completed, polyMesh modifies the mesh
+    topology.
+
+SourceFiles
+    batchPolyTopoChange.C
+
+\*---------------------------------------------------------------------------*/
+
+#ifndef batchPolyTopoChange_H
+#define batchPolyTopoChange_H
+
+#include "DynamicList.H"
+#include "HashSet.H"
+#include "polyAddPoint.H"
+#include "polyModifyPoint.H"
+#include "polyRemovePoint.H"
+#include "polyAddFace.H"
+#include "polyModifyFace.H"
+#include "polyRemoveFace.H"
+#include "polyAddCell.H"
+#include "polyModifyCell.H"
+#include "polyRemoveCell.H"
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+namespace Foam
+{
+
+class polyMesh;
+
+/*---------------------------------------------------------------------------*\
+                       Class batchPolyTopoChange Declaration
+\*---------------------------------------------------------------------------*/
+
+class batchPolyTopoChange
+{
+    // Private data
+
+        //- Reference to mesh to be refined
+        const polyMesh& mesh_;
+
+        //- Points to add
+        DynamicList<polyAddPoint> addedPoints_;
+
+        //- Points to modify
+        DynamicList<polyModifyPoint> modifiedPoints_;
+
+        //- Points to remove
+        labelHashSet removedPoints_;
+
+        //- Faces to add
+        DynamicList<polyAddFace> addedFaces_;
+
+        //- Faces to modify
+        DynamicList<polyModifyFace> modifiedFaces_;
+
+        //- Faces to remove
+        labelHashSet removedFaces_;
+
+        //- Number of cells to add
+        DynamicList<polyAddCell> addedCells_;
+
+        //- Cells to modify
+        DynamicList<polyModifyCell> modifiedCells_;
+
+        //- Cells to remove
+        labelHashSet removedCells_;
+
+
+    // Private Member Functions
+
+        //- Disallow default bitwise copy construct
+        batchPolyTopoChange(const batchPolyTopoChange&) = delete;
+
+        //- Disallow default bitwise assignment
+        void operator=(const batchPolyTopoChange&) = delete;
+
+
+    // Private static data
+
+        //- Minimum dynamic list size for object insertion
+        static const label minListSize;
+
+        //- Estimated fraction of removed points
+        static const label pointFraction;
+
+        //- Estimated fraction of removed faces
+        static const label faceFraction;
+
+        //- Estimated fraction of removed cells
+        static const label cellFraction;
+
+
+public:
+
+    //- Runtime type information
+    ClassName("batchPolyTopoChange");
+
+
+    // Constructors
+
+        //- Construct from mesh reference
+        batchPolyTopoChange(const polyMesh&);
+
+
+    //- Destructor
+    ~batchPolyTopoChange() = default;
+
+
+    // Member Functions
+
+        // Definition of topological change
+
+            //- Set topological action
+            label setAction(const topoAction& action);
+
+
+        // Topology morph data
+
+            //- Is point removed?
+            inline bool pointRemoved(const label pointI) const;
+
+            //- Is face removed?
+            inline bool faceRemoved(const label faceI) const;
+
+            //- Is cell removed?
+            inline bool cellRemoved(const label cellI) const;
+
+            //- Point balance (added - removed)
+            label pointBalance() const
+            {
+                return
+                    addedPoints_.size()
+                  + modifiedPoints_.size()
+                  - removedPoints_.size();
+            }
+
+            //- Face balance (added - removed)
+            label faceBalance() const
+            {
+                return
+                    addedFaces_.size()
+                  + modifiedFaces_.size()
+                  - removedFaces_.size();
+            }
+
+            //- Cell balance (added - removed)
+            label cellBalance() const
+            {
+                return addedCells_.size() - removedCells_.size();
+            }
+
+            //- Added points
+            const DynamicList<polyAddPoint>& addedPoints() const
+            {
+                return addedPoints_;
+            }
+
+            //- Modified points
+            const DynamicList<polyModifyPoint>& modifiedPoints() const
+            {
+                return modifiedPoints_;
+            }
+
+            //- Map of removed points
+            const labelHashSet& removedPoints() const
+            {
+                return removedPoints_;
+            }
+
+            //- Added faces
+            const DynamicList<polyAddFace>& addedFaces() const
+            {
+                return addedFaces_;
+            }
+
+            //- Modified faces
+            const DynamicList<polyModifyFace>& modifiedFaces() const
+            {
+                return modifiedFaces_;
+            }
+
+            //- Map of removed faces
+            const labelHashSet& removedFaces() const
+            {
+                return removedFaces_;
+            }
+
+            //- Added cells
+            const DynamicList<polyAddCell>& addedCells() const
+            {
+                return addedCells_;
+            }
+
+            //- Modified cells
+            const DynamicList<polyModifyCell>& modifiedCells() const
+            {
+                return modifiedCells_;
+            }
+
+            //- Map of removed cells
+            const labelHashSet& removedCells() const
+            {
+                return removedCells_;
+            }
+
+            //- Check for consistency and report status
+            //  Returns false for no error.
+            bool check() const;
+
+            //- Clear all contents
+            void clear();
+};
+
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+} // End namespace Foam
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+#include "batchPolyTopoChangeI.H"
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+#endif
+
+// ************************************************************************* //
diff --git a/src/meshTools/batchPolyTopoChange/batchPolyTopoChangeI.H b/src/meshTools/batchPolyTopoChange/batchPolyTopoChangeI.H
new file mode 100644
index 0000000000000000000000000000000000000000..79c94c025b878968a1c67347386e49ec5598b8fd
--- /dev/null
+++ b/src/meshTools/batchPolyTopoChange/batchPolyTopoChangeI.H
@@ -0,0 +1,47 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | foam-extend: Open Source CFD
+   \\    /   O peration     | Version:     5.0
+    \\  /    A nd           | Web:         http://www.foam-extend.org
+     \\/     M anipulation  | For copyright notice see file Copyright
+-------------------------------------------------------------------------------
+License
+    This file is part of foam-extend.
+
+    foam-extend 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.
+
+    foam-extend 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 foam-extend.  If not, see <http://www.gnu.org/licenses/>.
+
+\*---------------------------------------------------------------------------*/
+
+
+// * * * * * * * * * * * * * Private Member Functions  * * * * * * * * * * * //
+
+inline bool Foam::batchPolyTopoChange::pointRemoved(const label pointI) const
+{
+    return removedPoints().found(pointI);
+}
+
+
+inline bool Foam::batchPolyTopoChange::faceRemoved(const label faceI) const
+{
+    return removedFaces().found(faceI);
+}
+
+
+inline bool Foam::batchPolyTopoChange::cellRemoved(const label cellI) const
+{
+    return removedCells().found(cellI);
+}
+
+
+// ************************************************************************* //
diff --git a/src/meshTools/meshTools/meshTools.C b/src/meshTools/meshTools/meshTools.C
index 50ee9d84882449b8fab2bf83dbe8cdb6e6123710..56fd949e34788fad011f26444b9ffa88ff56af86 100644
--- a/src/meshTools/meshTools/meshTools.C
+++ b/src/meshTools/meshTools/meshTools.C
@@ -30,6 +30,7 @@ License
 #include "polyMesh.H"
 #include "hexMatcher.H"
 #include "treeBoundBox.H"
+#include "syncTools.H"
 
 // * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //
 
@@ -878,4 +879,157 @@ Foam::label Foam::meshTools::cutDirToEdge
 }
 
 
+void Foam::meshTools::setFaceInfo
+(
+    const polyMesh& mesh,
+    const label faceI,
+    label& patchID,
+    label& zoneID,
+    label& zoneFlip
+)
+{
+    patchID = -1;
+
+    if (!mesh.isInternalFace(faceI))
+    {
+        patchID = mesh.boundaryMesh().whichPatch(faceI);
+    }
+
+    zoneID = mesh.faceZones().whichZone(faceI);
+
+    zoneFlip = false;
+
+    if (zoneID > -1)
+    {
+        const faceZone& fZone = mesh.faceZones()[zoneID];
+
+        zoneFlip = fZone.flipMap()[fZone.whichFace(faceI)];
+    }
+}
+
+
+void Foam::meshTools::extendMarkedCellsAcrossFaces
+(
+    const polyMesh& mesh,
+    boolList& markedCell
+)
+{
+    // Mark all faces for all marked cells
+    const label nFaces = mesh.nFaces();
+    boolList markedFace(nFaces, false);
+
+    // Get mesh cells
+    const cellList& meshCells = mesh.cells();
+
+    // Loop through all cells
+    forAll (markedCell, cellI)
+    {
+        if (markedCell[cellI])
+        {
+            // This cell is marked, get its faces
+            const cell& cFaces = meshCells[cellI];
+
+            forAll (cFaces, i)
+            {
+                markedFace[cFaces[i]] = true;
+            }
+        }
+    }
+
+    // Snyc the face list across processor boundaries
+    syncTools::syncFaceList(mesh, markedFace, orEqOp<bool>());//HJ
+
+    // Get necessary mesh data
+    const label nInternalFaces = mesh.nInternalFaces();
+    const labelList& owner = mesh.faceOwner();
+    const labelList& neighbour = mesh.faceNeighbour();
+
+    // Internal faces
+    for (label faceI = 0; faceI < nInternalFaces; ++faceI)
+    {
+        if (markedFace[faceI])
+        {
+            // Face is marked, mark both owner and neighbour
+            const label& own = owner[faceI];
+            const label& nei = neighbour[faceI];
+
+            // Mark owner and neighbour cells
+            markedCell[own] = true;
+            markedCell[nei] = true;
+        }
+    }
+
+    // Boundary faces
+    for (label faceI = nInternalFaces; faceI < nFaces; ++faceI)
+    {
+        if (markedFace[faceI])
+        {
+            // Face is marked, mark owner
+            const label& own = owner[faceI];
+
+            // Mark owner
+            markedCell[own] = true;
+        }
+    }
+}
+
+
+void Foam::meshTools::extendMarkedCellsAcrossPoints
+(
+    const polyMesh& mesh,
+    boolList& markedCell
+)
+{
+    // Mark all points for all marked cells
+    const label nPoints = mesh.nPoints();
+    boolList markedPoint(nPoints, false);
+
+    // Get cell points
+    const labelListList& meshCellPoints = mesh.cellPoints();
+
+    // Loop through all cells
+    forAll (markedCell, cellI)
+    {
+        if (markedCell[cellI])
+        {
+            // This cell is marked, get its points
+            const labelList& cPoints = meshCellPoints[cellI];
+
+            forAll (cPoints, i)
+            {
+                markedPoint[cPoints[i]] = true;
+            }
+        }
+    }
+
+    // Snyc point list across processor boundaries
+    syncTools::syncPointList
+    (
+        mesh,
+        markedPoint,
+        orEqOp<bool>(),
+        true // Default value
+        // true  // Apply separation for parallel cyclics  //HJ
+    );
+
+    // Get point cells
+    const labelListList& meshPointCells = mesh.pointCells();
+
+    // Loop through all points
+    forAll (markedPoint, pointI)
+    {
+        if (markedPoint[pointI])
+        {
+            // This point is marked, mark all of its cells
+            const labelList& pCells = meshPointCells[pointI];
+
+            forAll (pCells, i)
+            {
+                markedCell[pCells[i]] = true;
+            }
+        }
+    }
+}
+
+
 // ************************************************************************* //
diff --git a/src/meshTools/meshTools/meshTools.H b/src/meshTools/meshTools/meshTools.H
index 007d3b519dea26a1dfbcfb6ea0e17ab4c35cd5a4..04303e78c5515b63a143cb2813a9e786d81be229 100644
--- a/src/meshTools/meshTools/meshTools.H
+++ b/src/meshTools/meshTools/meshTools.H
@@ -352,6 +352,37 @@ namespace meshTools
             const vector& cutDir
         );
 
+
+    // Face information
+
+        //- Set face information: patch, zone and zone flip for a face
+        void setFaceInfo
+        (
+            const polyMesh& mesh,
+            const label faceI,
+            label& patchID,
+            label& zoneID,
+            label& zoneFlip
+        );
+
+
+    // Mark-up of mesh bits. Relocated from refinement polyMeshModifier
+
+        //- Extend marked cells across faces given a bool list of already marked
+        //  cells
+        void extendMarkedCellsAcrossFaces
+        (
+            const polyMesh& mesh,
+            boolList& markedCell
+        );
+
+        //- Extend marked cells across points given a bool list of already
+        //  marked cells
+        void extendMarkedCellsAcrossPoints
+        (
+            const polyMesh& mesh,
+            boolList& markedCell
+        );
 } // End namespace meshTools
 
 
diff --git a/src/topoChangerFvMesh/Make/files b/src/topoChangerFvMesh/Make/files
index 8a0e7e686a3d9a747732c386884131eae3a67354..a4381293a4498e052698b6e835b1c6185c412920 100644
--- a/src/topoChangerFvMesh/Make/files
+++ b/src/topoChangerFvMesh/Make/files
@@ -11,4 +11,14 @@ movingConeTopoFvMesh/movingConeTopoFvMesh.C
 mixerFvMesh/mixerFvMesh.C
 */
 
+dynamicPolyRefinementFvMesh/dynamicPolyRefinementFvMesh.C
+dynamicPolyRefinementFvMesh/refinementSelection/refinementSelection/refinementSelection.C
+dynamicPolyRefinementFvMesh/refinementSelection/fieldBoundsRefinement/fieldBoundsRefinement.C
+dynamicPolyRefinementFvMesh/refinementSelection/minCellVolumeRefinement/minCellVolumeRefinement.C
+dynamicPolyRefinementFvMesh/refinementSelection/minCellSizeRefinement/minCellSizeRefinement.C
+dynamicPolyRefinementFvMesh/refinementSelection/minPatchDistanceRefinement/minPatchDistanceRefinement.C
+dynamicPolyRefinementFvMesh/refinementSelection/protectedInitialRefinement/protectedInitialRefinement.C
+dynamicPolyRefinementFvMesh/refinementSelection/minFaceArea2DRefinement/minFaceArea2DRefinement.C
+dynamicPolyRefinementFvMesh/refinementSelection/compositeRefinementSelection/compositeRefinementSelection.C
+
 LIB = $(FOAM_LIBBIN)/libtopoChangerFvMesh
diff --git a/src/topoChangerFvMesh/Make/options b/src/topoChangerFvMesh/Make/options
index e1f704d20e75c5434d18c1077afd31c2d906cab7..8362887c0af209ecd5d8a6584aafd3f6da18a07c 100644
--- a/src/topoChangerFvMesh/Make/options
+++ b/src/topoChangerFvMesh/Make/options
@@ -1,5 +1,6 @@
 EXE_INC = \
     -I$(LIB_SRC)/finiteVolume/lnInclude \
+    -I$(LIB_SRC)/optimisation/adjointOptimisation/adjoint/lnInclude \
     -I$(LIB_SRC)/fileFormats/lnInclude \
     -I$(LIB_SRC)/surfMesh/lnInclude \
     -I$(LIB_SRC)/meshTools/lnInclude \
@@ -8,6 +9,7 @@ EXE_INC = \
 
 LIB_LIBS = \
     -lfiniteVolume \
+    -l adjointOptimisation \
     -lfileFormats \
     -lsurfMesh \
     -lmeshTools \
diff --git a/src/topoChangerFvMesh/dynamicMotionSolverTopoFvMesh/dynamicMotionSolverTopoFvMesh.C b/src/topoChangerFvMesh/dynamicMotionSolverTopoFvMesh/dynamicMotionSolverTopoFvMesh.C
index 9f4fe646cae492a8459e2a8ae69e14d4809a3709..132349d277a5e9b07cb042c2ed35a9e8a3816ddf 100644
--- a/src/topoChangerFvMesh/dynamicMotionSolverTopoFvMesh/dynamicMotionSolverTopoFvMesh.C
+++ b/src/topoChangerFvMesh/dynamicMotionSolverTopoFvMesh/dynamicMotionSolverTopoFvMesh.C
@@ -100,7 +100,7 @@ bool Foam::dynamicMotionSolverTopoFvMesh::update()
     // (TBD: should be in changeMesh if no inflation?)
     moving(false);
     // Do mesh changes (not using inflation - points added directly into mesh)
-    autoPtr<mapPolyMesh> topoChangeMap = topoChanger_.changeMesh(false);
+    autoPtr<mapPolyMesh> topoChangeMap = topoChanger_.changeMesh();
 
     if (topoChangeMap)
     {
diff --git a/src/topoChangerFvMesh/movingConeTopoFvMesh/movingConeTopoFvMesh.C b/src/topoChangerFvMesh/movingConeTopoFvMesh/movingConeTopoFvMesh.C
index 51221b8fadfe0287f10ecd6ef0e79b3d3b5fb543..d35a3161dbcc659f71e9c482d9e63aefd9ea4e64 100644
--- a/src/topoChangerFvMesh/movingConeTopoFvMesh/movingConeTopoFvMesh.C
+++ b/src/topoChangerFvMesh/movingConeTopoFvMesh/movingConeTopoFvMesh.C
@@ -332,7 +332,7 @@ Foam::movingConeTopoFvMesh::~movingConeTopoFvMesh()
 bool Foam::movingConeTopoFvMesh::update()
 {
     // Do mesh changes (use inflation - put new points in topoChangeMap)
-    autoPtr<mapPolyMesh> topoChangeMap = topoChanger_.changeMesh(true);
+    autoPtr<mapPolyMesh> topoChangeMap = topoChanger_.changeMesh();
 
     // Calculate the new point positions depending on whether the
     // topological change has happened or not
diff --git a/src/topoChangerFvMesh/rawTopoChangerFvMesh/rawTopoChangerFvMesh.C b/src/topoChangerFvMesh/rawTopoChangerFvMesh/rawTopoChangerFvMesh.C
index 9c2dae87361e7d824e2b9bdc8f40f45583da38f9..91326a22f856457b138d9a702c822cac55cf8928 100644
--- a/src/topoChangerFvMesh/rawTopoChangerFvMesh/rawTopoChangerFvMesh.C
+++ b/src/topoChangerFvMesh/rawTopoChangerFvMesh/rawTopoChangerFvMesh.C
@@ -83,8 +83,8 @@ bool Foam::rawTopoChangerFvMesh::update()
     moving(false);
     topoChanging(false);
 
-    // Do any topology changes. Sets topoChanging (through polyTopoChange)
-    autoPtr<mapPolyMesh> topoChangeMap = topoChanger_.changeMesh(true);
+    // Do any topology changes. Sets topoChanging (through batchPolyTopoChange)
+    autoPtr<mapPolyMesh> topoChangeMap = topoChanger_.changeMesh();
 
     const bool hasChanged = bool(topoChangeMap);