diff --git a/src/functionObjects/field/cellDecomposer/cellDecomposer.C b/src/functionObjects/field/cellDecomposer/cellDecomposer.C
new file mode 100644
index 0000000000000000000000000000000000000000..446c3d88813f13fbd4f888194aaa7d4686b7d29c
--- /dev/null
+++ b/src/functionObjects/field/cellDecomposer/cellDecomposer.C
@@ -0,0 +1,415 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
+   \\    /   O peration     |
+    \\  /    A nd           | www.openfoam.com
+     \\/     M anipulation  |
+-------------------------------------------------------------------------------
+    Copyright (C) 2024 OpenCFD Ltd.
+-------------------------------------------------------------------------------
+License
+    This file is part of OpenFOAM.
+
+    OpenFOAM is free software: you can redistribute it and/or modify it
+    under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    OpenFOAM is distributed in the hope that it will be useful, but WITHOUT
+    ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+    FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+    for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with OpenFOAM.  If not, see <http://www.gnu.org/licenses/>.
+
+\*---------------------------------------------------------------------------*/
+
+#include "cellDecomposer.H"
+#include "addToRunTimeSelectionTable.H"
+#include "polyTopoChange.H"
+#include "mapPolyMesh.H"
+#include "tetDecomposer.H"
+#include "syncTools.H"
+#include "dummyTransform.H"
+#include "ReadFields.H"
+#include "surfaceFields.H"
+#include "PackedBoolList.H"
+#include "fvMeshTools.H"
+#include "cellSetOption.H"
+#include "cellBitSet.H"
+#include "cellSet.H"
+#include "hexMatcher.H"
+#include "prismMatcher.H"
+#include "pyrMatcher.H"
+#include "tetMatcher.H"
+
+#include "OBJstream.H"
+
+// * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * //
+
+namespace Foam
+{
+namespace functionObjects
+{
+    defineTypeNameAndDebug(cellDecomposer, 0);
+    addToRunTimeSelectionTable(functionObject, cellDecomposer, dictionary);
+}
+}
+
+
+// * * * * * * * * * * * * * Private Member Functions  * * * * * * * * * * * //
+
+void Foam::functionObjects::cellDecomposer::makeMesh
+(
+    const dictionary& dict,
+    const word& regionName
+)
+{
+    Info<< name() << ':' << nl
+        << "    Decomposing cells to region " << regionName << endl;
+
+    Info<< incrIndent;
+
+    const fv::cellSetOption::selectionModeType selectionMode
+    (
+        fv::cellSetOption::selectionModeTypeNames_.get
+        (
+            "selectionMode",
+            dict
+        )
+    );
+
+
+    // Start off from existing mesh
+    polyTopoChange meshMod(mesh_);
+
+    tetDecompPtr_.reset(new Foam::tetDecomposer(mesh_));
+
+    // Default values
+    bitSet decomposeCell(mesh_.nCells());
+    autoPtr<bitSet> decomposeFacePtr;
+    tetDecomposer::decompositionType decompType
+    (
+        tetDecomposer::FACE_CENTRE_TRIS
+    );
+
+
+    switch (selectionMode)
+    {
+        case fv::cellSetOption::smAll:
+        {
+            Info<< indent << "- selecting all cells" << endl;
+
+            decomposeCell = true;
+            break;
+        }
+        case fv::cellSetOption::smGeometric:
+        {
+            Info<< indent << "- selecting cells geometrically" << endl;
+
+            decomposeCell =
+                cellBitSet::select(mesh_, dict.subDict("selection"), true);
+            break;
+        }
+        case fv::cellSetOption::smCellSet:
+        {
+            const word selectionName(dict.get<word>("cellSet"));
+
+            Info<< indent
+                << "- selecting cells using cellSet "
+                << selectionName << endl;
+
+            decomposeCell.set
+            (
+                cellSet(mesh_, selectionName, IOobject::MUST_READ).toc()
+            );
+            break;
+        }
+        case fv::cellSetOption::smCellZone:
+        {
+            wordRes selectionNames;
+            if
+            (
+                !dict.readIfPresent("cellZones", selectionNames)
+             || selectionNames.empty()
+            )
+            {
+                selectionNames.resize(1);
+                dict.readEntry("cellZone", selectionNames.first());
+            }
+
+            Info<< indent
+                << "- selecting cells using cellZones "
+                << flatOutput(selectionNames) << nl;
+
+            const auto& zones = mesh_.cellZones();
+
+            // Also handles groups, multiple zones etc ...
+            labelList zoneIDs = zones.indices(selectionNames);
+
+            if (zoneIDs.empty())
+            {
+                FatalErrorInFunction
+                    << "No matching cellZones: "
+                    << flatOutput(selectionNames) << nl
+                    << "Valid zones : "
+                    << flatOutput(zones.names()) << nl
+                    << "Valid groups: "
+                    << flatOutput(zones.groupNames())
+                    << nl
+                    << exit(FatalError);
+            }
+
+            if (zoneIDs.size() == 1)
+            {
+                decomposeCell.set(zones[zoneIDs.first()]);
+                // TBD: Foam::sort(cells_);
+            }
+            else
+            {
+                decomposeCell.set(zones.selection(zoneIDs).sortedToc());
+            }
+            break;
+        }
+        default:
+        {
+            FatalErrorInFunction
+                << "Unsupported selectionMode "
+                << fv::cellSetOption::selectionModeTypeNames_[selectionMode]
+                << ". Valid selectionMode types are "
+                << fv::cellSetOption::selectionModeTypeNames_
+                << exit(FatalError);
+        }
+    }
+
+    word decompTypeName;
+    if (dict.readIfPresent("decomposeType", decompTypeName))
+    {
+        if (decompTypeName == "polyhedral")
+        {
+            // Automatic selection to generate hex/prism/tet only
+            //  - subset decomposeCell to exclude hex/prism/tet
+            //  - set faces to FACE_DIAG_QUADS
+            // Usually start with cellSetOption::smAll
+            decomposeFacePtr.reset(new bitSet(mesh_.nFaces()));
+            auto& decomposeFace = decomposeFacePtr();
+
+            decompType = tetDecomposer::FACE_DIAG_QUADS;
+            bitSet oldDecomposeCell(decomposeCell);
+            decomposeCell = false;
+
+            // Construct shape recognizers
+            prismMatcher prism;
+
+            for (const label celli : oldDecomposeCell)
+            {
+                if
+                (
+                   !hexMatcher::test(mesh_, celli)
+                && !tetMatcher::test(mesh_, celli)
+                && !pyrMatcher::test(mesh_, celli)
+                && !prism.isA(mesh_, celli)
+                )
+                {
+                    decomposeCell.set(celli);
+                    decomposeFace.set(mesh_.cells()[celli]);
+                }
+            }
+
+            // Sync boundary info
+            syncTools::syncFaceList
+            (
+                mesh_,
+                decomposeFace,
+                orEqOp<unsigned int>()
+            );
+        }
+        else
+        {
+            decompType = tetDecomposer::decompositionTypeNames[decompTypeName];
+        }
+    }
+
+
+    // Insert all changes to create tets
+    if (decomposeFacePtr)
+    {
+        if (debug)
+        {
+            OBJstream os(mesh_.time().path()/"orig_faces.obj");
+            os.write
+            (
+                UIndirectList<face>
+                (
+                    mesh_.faces(),
+                    decomposeFacePtr().sortedToc()
+                )(),
+                mesh_.points(),
+                false
+            );
+            Pout<< "Written " << meshMod.faces().size()
+                << " faces to " << os.name() << endl;
+        }
+
+        tetDecompPtr_().setRefinement
+        (
+            decompType, //Foam::tetDecomposer::FACE_CENTRE_TRIS,
+            decomposeCell,
+            decomposeFacePtr(),
+            meshMod
+        );
+    }
+    else
+    {
+        tetDecompPtr_().setRefinement
+        (
+            decompType, //Foam::tetDecomposer::FACE_CENTRE_TRIS,
+            decomposeCell,
+            meshMod
+        );
+    }
+
+
+    if (debug)
+    {
+        OBJstream os(mesh_.time().path()/"faces.obj");
+        os.write(meshMod.faces(), pointField(meshMod.points()), false);
+        Pout<< "Written " << meshMod.faces().size()
+            << " faces to " << os.name() << endl;
+    }
+
+
+    autoPtr<fvMesh> tetMeshPtr;
+
+    mapPtr_ = meshMod.makeMesh
+    (
+        tetMeshPtr,
+        IOobject
+        (
+            regionName,
+            mesh_.facesInstance(),      // same instance as original mesh
+            mesh_.time(),               //? why same database? TBD.
+            IOobject::READ_IF_PRESENT,  // Read fv* if present
+            IOobject::AUTO_WRITE,
+            IOobject::REGISTER
+        ),
+        mesh_
+    );
+
+
+    //- Update numbering on tet-decomposition engine
+    tetDecompPtr_().updateMesh(mapPtr_());
+
+    Info<< indent << "Writing decomposed mesh to "
+        << tetMeshPtr().objectRegistry::objectRelPath()
+        << endl;
+    tetMeshPtr().write();
+
+    fvMeshTools::createDummyFvMeshFiles(mesh_.time(), regionName, true);
+
+    // Store new mesh on object registry
+    tetMeshPtr.ptr()->polyMesh::store();
+
+    Info<< decrIndent;
+}
+
+
+// * * * * * * * * * * * * * * * * Constructors  * * * * * * * * * * * * * * //
+
+Foam::functionObjects::cellDecomposer::cellDecomposer
+(
+    const word& name,
+    const Time& runTime,
+    const dictionary& dict
+)
+:
+    fvMeshFunctionObject(name, runTime, dict)
+{
+    read(dict);
+}
+
+
+// * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //
+
+bool Foam::functionObjects::cellDecomposer::read(const dictionary& dict)
+{
+    if (fvMeshFunctionObject::read(dict))
+    {
+        // Generate new tet equivalent mesh. TBD: why meshObject?
+        //meshObjects::tetDecomposition::New(mesh, dict);
+
+        dict_ = dict.optionalSubDict(typeName + "Coeffs");
+        dict_.readEntry("mapRegion", mapRegion_);
+        dict_.readEntry("fields", fieldNames_);
+        makeMesh(dict_, mapRegion_);
+    }
+
+    return true;
+}
+
+
+bool Foam::functionObjects::cellDecomposer::execute()
+{
+    Log << type() << " " << name() << " execute:" << nl;
+
+    bool ok = false;
+
+    if (mesh_.changing())
+    {
+        // Re-do mesh. Currently rebuilds whole mesh. Could move points only
+        // for mesh motion.
+        tetDecompPtr_.clear();
+        mapPtr_.clear();
+        const_cast<Time&>(this->mesh_.time()).erase(mapRegion_);
+        makeMesh(dict_, mapRegion_);
+    }
+
+
+    // Look up
+    ok = mapFieldType<scalar>() || ok;
+    ok = mapFieldType<vector>() || ok;
+    ok = mapFieldType<sphericalTensor>() || ok;
+    ok = mapFieldType<symmTensor>() || ok;
+    ok = mapFieldType<tensor>() || ok;
+
+    if (log)
+    {
+        if (!ok)
+        {
+            Info<< "    none" << nl;
+        }
+
+        Info<< endl;
+    }
+    return true;
+}
+
+
+bool Foam::functionObjects::cellDecomposer::write()
+{
+    Log << type() << " " << name() << " write:" << nl;
+
+    bool ok = false;
+
+    ok = writeFieldType<scalar>() || ok;
+    ok = writeFieldType<vector>() || ok;
+    ok = writeFieldType<sphericalTensor>() || ok;
+    ok = writeFieldType<symmTensor>() || ok;
+    ok = writeFieldType<tensor>() || ok;
+
+    if (log)
+    {
+        if (!ok)
+        {
+            Info<< "    none" << nl;
+        }
+
+        Info<< endl;
+    }
+
+    return true;
+}
+
+
+// ************************************************************************* //
diff --git a/src/functionObjects/field/cellDecomposer/cellDecomposer.H b/src/functionObjects/field/cellDecomposer/cellDecomposer.H
new file mode 100644
index 0000000000000000000000000000000000000000..0e3363bfc0f8e1485a363b4c29b9c8ccece772b8
--- /dev/null
+++ b/src/functionObjects/field/cellDecomposer/cellDecomposer.H
@@ -0,0 +1,226 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
+   \\    /   O peration     |
+    \\  /    A nd           | www.openfoam.com
+     \\/     M anipulation  |
+-------------------------------------------------------------------------------
+    Copyright (C) 2024 OpenCFD Ltd.
+-------------------------------------------------------------------------------
+License
+    This file is part of OpenFOAM.
+
+    OpenFOAM is free software: you can redistribute it and/or modify it
+    under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    OpenFOAM is distributed in the hope that it will be useful, but WITHOUT
+    ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+    FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+    for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with OpenFOAM.  If not, see <http://www.gnu.org/licenses/>.
+
+Class
+    Foam::functionObjects::cellDecomposer
+
+Group
+    grpFieldFunctionObjects
+
+Description
+    Maps input fields from local mesh to a secondary mesh at runtime.
+
+    The secondary mesh gets created on-the-fly by decomposing the current mesh.
+
+
+    Operands:
+    \table
+      Operand      | Type                       | Location
+      input        | {vol,surface}\<Type\>Field <!--
+                                    --> | $FOAM_CASE/\<time\>/\<inpField\>
+      output file  | -                          | -
+      output field | {vol,surface}\<Type\>Field <!--
+                                    --> | $FOAM_CASE/\<time\>/\<outField\>
+    \endtable
+
+    where \c \<Type\>=Scalar/Vector/SphericalTensor/SymmTensor/Tensor.
+
+Usage
+    Minimal example by using \c system/controlDict.functions:
+    \verbatim
+    cellDecomposer1
+    {
+        // Mandatory entries (unmodifiable)
+        type            cellDecomposer;
+        libs            (fieldFunctionObjects);
+
+        // Mandatory (inherited) entries (runtime modifiable)
+        fields          (<field1> <field2> ... <fieldN>);
+        mapRegion       myTetMesh;
+        ...
+        // Mandatory entries
+        // Decompose type: 
+        decomposeType   polyhedral;
+        // Cell set to decompose
+        selectionMode   all;
+    }
+    \endverbatim
+
+    where the entries mean:
+    \table
+      Property     | Description                        | Type | Req'd | Dflt
+      type         | Type name: cellDecomposer          | word |  yes  | -
+      libs         | Library name: fieldFunctionObjects | word |  yes  | -
+      fields       | Names of operand fields            | wordList |  yes  | -
+      mapRegion    | Name of region to map to           | word |  yes  | -
+      decomposeType | How to decompose cells            | word |  yes  | -
+      selectionMode | How to select cells (see fvOption)| word |  yes  | -
+    \endtable
+
+    decomposeType:
+    - faceCentre : decompose cells into tets using face centre and cell centre.
+                   (hex becomes 6*4 tets)
+    - faceDiagonal : decompose cells into tets using face diagonal, similar
+                     to implicit decomposition inside lagrangian tracking.
+                     (hex becomes 6*2 tets)
+    - pyramid : keep faces intact but create (polygonal-base) pyramids using
+                cell centre (hex becomes 6 pyramids)
+    - faceDiagonalQuads : like faceDiagonal but split faces into quads and
+                          triangles instead of just triangles
+    - polyhedral : like faceDiagonalQuads but only decompose non-hex/prism/tet
+                   cells in selected set. Used to convert polyhedral mesh into
+                   'simple' mesh.
+
+    The inherited entries are elaborated in:
+     - \link functionObject.H \endlink
+
+See also
+    - Foam::functionObjects::mapFields
+
+SourceFiles
+    cellDecomposer.C
+
+\*---------------------------------------------------------------------------*/
+
+#ifndef functionObjects_cellDecomposer_H
+#define functionObjects_cellDecomposer_H
+
+#include "fvMeshFunctionObject.H"
+#include "volFieldsFwd.H"
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+namespace Foam
+{
+
+class tetDecomposer;
+class mapPolyMesh;
+
+namespace functionObjects
+{
+
+/*---------------------------------------------------------------------------*\
+                       Class cellDecomposer Declaration
+\*---------------------------------------------------------------------------*/
+
+class cellDecomposer
+:
+    public fvMeshFunctionObject
+{
+    // Private Data
+
+        //- Parameter dictionary
+        dictionary dict_;
+
+        //- Name of new mesh
+        word mapRegion_;
+
+        //- List of field names to interpolate
+        wordRes fieldNames_;
+
+        //- Tet decomposer
+        autoPtr<tetDecomposer> tetDecompPtr_;
+
+        //- Map from polyMesh to tet-mesh
+        autoPtr<mapPolyMesh> mapPtr_;
+
+
+    // Private Member Functions
+
+        //- Generate mesh
+        void makeMesh(const dictionary& dict, const word& name);
+
+        //- Helper function to map the \<Type\> fields
+        template<class Type>
+        bool mapFieldType() const;
+
+        //- Helper function to write the \<Type\> fields
+        template<class Type>
+        bool writeFieldType() const;
+
+        template<class Type>
+        tmp<GeometricField<Type, fvPatchField, volMesh>>
+        interpolate
+        (
+            const GeometricField<Type, fvPatchField, volMesh>& vf,
+            const fvMesh& sMesh,
+            const labelUList& patchMap,
+            const labelUList& cellMap,
+            const labelUList& faceMap,
+            const bool allowUnmapped
+        ) const;
+
+
+public:
+
+
+    //- Runtime type information
+    TypeName("cellDecomposer");
+
+
+    // Constructors
+
+        //- Construct from Time and dictionary
+        cellDecomposer
+        (
+            const word& name,
+            const Time& runTime,
+            const dictionary& dict
+        );
+
+
+    //- Destructor
+    virtual ~cellDecomposer() = default;
+
+
+    // Member Functions
+
+        //- Read the cellDecomposer data
+        virtual bool read(const dictionary& dict);
+
+        //- Execute
+        virtual bool execute();
+
+        //- Write
+        virtual bool write();
+};
+
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+} // End namespace functionObjects
+} // End namespace Foam
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+#ifdef NoRepository
+    #include "cellDecomposerTemplates.C"
+#endif
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+#endif
+
+// ************************************************************************* //
diff --git a/src/functionObjects/field/cellDecomposer/cellDecomposerTemplates.C b/src/functionObjects/field/cellDecomposer/cellDecomposerTemplates.C
new file mode 100644
index 0000000000000000000000000000000000000000..e982aeff5b58f6bf317f24bca54fb8fe1d0d3018
--- /dev/null
+++ b/src/functionObjects/field/cellDecomposer/cellDecomposerTemplates.C
@@ -0,0 +1,364 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
+   \\    /   O peration     |
+    \\  /    A nd           | www.openfoam.com
+     \\/     M anipulation  |
+-------------------------------------------------------------------------------
+    Copyright (C) 2024 OpenCFD Ltd.
+-------------------------------------------------------------------------------
+License
+    This file is part of OpenFOAM.
+
+    OpenFOAM is free software: you can redistribute it and/or modify it
+    under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    OpenFOAM is distributed in the hope that it will be useful, but WITHOUT
+    ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+    FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+    for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with OpenFOAM.  If not, see <http://www.gnu.org/licenses/>.
+
+\*---------------------------------------------------------------------------*/
+
+#include "fvMesh.H"
+#include "emptyFvPatchFields.H"
+#include "directFvPatchFieldMapper.H"
+#include "mapPolyMesh.H"
+//#include "polyPatch.H"
+//#include "lduSchedule.H"
+//#include "meshToMesh.H"
+
+//template<class Type>
+//void Foam::functionObjects::cellDecomposer::evaluateConstraintTypes
+//(
+//    GeometricField<Type, fvPatchField, volMesh>& fld
+//) const
+//{
+//    auto& fldBf = fld.boundaryFieldRef();
+//
+//    const UPstream::commsTypes commsType = UPstream::defaultCommsType;
+//    const label startOfRequests = UPstream::nRequests();
+//
+//    if
+//    (
+//        commsType == UPstream::commsTypes::blocking
+//     || commsType == UPstream::commsTypes::nonBlocking
+//    )
+//    {
+//        forAll(fldBf, patchi)
+//        {
+//            fvPatchField<Type>& tgtField = fldBf[patchi];
+//
+//            if
+//            (
+//                tgtField.type() == tgtField.patch().patch().type()
+//             && polyPatch::constraintType(tgtField.patch().patch().type())
+//            )
+//            {
+//                tgtField.initEvaluate(commsType);
+//            }
+//        }
+//
+//        // Wait for outstanding requests
+//        if (commsType == UPstream::commsTypes::nonBlocking)
+//        {
+//            UPstream::waitRequests(startOfRequests);
+//        }
+//
+//        forAll(fldBf, patchi)
+//        {
+//            fvPatchField<Type>& tgtField = fldBf[patchi];
+//
+//            if
+//            (
+//                tgtField.type() == tgtField.patch().patch().type()
+//             && polyPatch::constraintType(tgtField.patch().patch().type())
+//            )
+//            {
+//                tgtField.evaluate(commsType);
+//            }
+//        }
+//    }
+//    else if (commsType == UPstream::commsTypes::scheduled)
+//    {
+//        const lduSchedule& patchSchedule =
+//            fld.mesh().globalData().patchSchedule();
+//
+//        for (const auto& schedEval : patchSchedule)
+//        {
+//            const label patchi = schedEval.patch;
+//
+//            fvPatchField<Type>& tgtField = fldBf[patchi];
+//
+//            if
+//            (
+//                tgtField.type() == tgtField.patch().patch().type()
+//             && polyPatch::constraintType(tgtField.patch().patch().type())
+//            )
+//            {
+//                if (schedEval.init)
+//                {
+//                    tgtField.initEvaluate(commsType);
+//                }
+//                else
+//                {
+//                    tgtField.evaluate(commsType);
+//                }
+//            }
+//        }
+//    }
+//}
+
+template<class Type>
+Foam::tmp
+<
+    Foam::GeometricField<Type, Foam::fvPatchField, Foam::volMesh>
+>
+Foam::functionObjects::cellDecomposer::interpolate
+(
+    const GeometricField<Type, fvPatchField, volMesh>& vf,
+    const fvMesh& sMesh,
+    const labelUList& patchMap,
+    const labelUList& cellMap,
+    const labelUList& faceMap,
+    const bool allowUnmapped
+) const
+{
+    // 1. Create the complete field with dummy patch fields
+    PtrList<fvPatchField<Type>> patchFields(patchMap.size());
+
+    forAll(patchFields, patchi)
+    {
+        // Set the first one by hand as it corresponds to the
+        // exposed internal faces. Additional interpolation can be put here
+        // as necessary.
+        if (patchMap[patchi] == -1)
+        {
+            patchFields.set
+            (
+                patchi,
+                new emptyFvPatchField<Type>
+                (
+                    sMesh.boundary()[patchi],
+                    fvPatchField<Type>::Internal::null()
+                )
+            );
+        }
+        else
+        {
+            patchFields.set
+            (
+                patchi,
+                fvPatchField<Type>::New
+                (
+                    fvPatchFieldBase::calculatedType(),
+                    sMesh.boundary()[patchi],
+                    fvPatchField<Type>::Internal::null()
+                )
+            );
+        }
+    }
+
+    auto tresult = tmp<GeometricField<Type, fvPatchField, volMesh>>::New
+    (
+        IOobject
+        (
+            "subset"+vf.name(),
+            sMesh.time().timeName(),
+            sMesh,
+            IOobject::NO_READ,
+            IOobject::NO_WRITE
+        ),
+        sMesh,
+        vf.dimensions(),
+        Field<Type>(vf.primitiveField(), cellMap),
+        patchFields
+    );
+    auto& result = tresult.ref();
+    result.oriented() = vf.oriented();
+
+
+    // 2. Change the fvPatchFields to the correct type using a mapper
+    //  constructor (with reference to the now correct internal field)
+
+    auto& bf = result.boundaryFieldRef();
+
+    forAll(bf, patchi)
+    {
+        const label basePatchId = patchMap[patchi];
+
+        if (basePatchId != -1)
+        {
+            // Construct addressing
+            const fvPatch& subPatch = sMesh.boundary()[patchi];
+            const fvPatch& basePatch = vf.mesh().boundary()[basePatchId];
+            const label baseStart = basePatch.start();
+            const label baseSize = basePatch.size();
+
+            labelList directAddressing(subPatch.size());
+
+            forAll(directAddressing, i)
+            {
+                const label baseFacei = faceMap[subPatch.start()+i];
+
+                if (baseFacei >= baseStart && baseFacei < baseStart+baseSize)
+                {
+                    directAddressing[i] = baseFacei-baseStart;
+                }
+                else
+                {
+                    // Mapped from internal face. Do what? Leave up to
+                    // fvPatchField
+                    directAddressing[i] = -1;
+                }
+            }
+
+
+            directFvPatchFieldMapper mapper(directAddressing);
+
+            // allowUnmapped : special mode for if we do not want to be
+            // warned for unmapped faces (e.g. from fvMeshDistribute).
+
+            const bool hasUnmapped = mapper.hasUnmapped();
+            if (allowUnmapped)
+            {
+                mapper.hasUnmapped() = false;
+            }
+
+            bf.set
+            (
+                patchi,
+                fvPatchField<Type>::New
+                (
+                    vf.boundaryField()[basePatchId],
+                    subPatch,
+                    result.internalField(),
+                    mapper
+                )
+            );
+
+            if (allowUnmapped && hasUnmapped)
+            {
+                // Set unmapped values to zeroGradient. This is the default
+                // action for unmapped fvPatchFields. Note that this bypasses
+                // any special logic for handling unmapped fvPatchFields but
+                // since this is only used inside fvMeshDistribute ...
+
+                tmp<Field<Type>> tfld(bf[patchi].patchInternalField());
+                const Field<Type>& fld = tfld();
+
+                Field<Type> value(bf[patchi]);
+                forAll(directAddressing, i)
+                {
+                    if (directAddressing[i] == -1)
+                    {
+                        value[i] = fld[i];
+                    }
+                }
+                bf[patchi].fvPatchField<Type>::operator=(value);
+            }
+        }
+    }
+
+    return tresult;
+}
+
+
+template<class Type>
+bool Foam::functionObjects::cellDecomposer::mapFieldType() const
+{
+    typedef GeometricField<Type, fvPatchField, volMesh> VolFieldType;
+
+    const fvMesh& mapRegion =
+        this->mesh_.time().lookupObject<fvMesh>(mapRegion_);
+
+    const labelList patchMap(identity(mapRegion.boundaryMesh().size()));
+
+    const wordList fieldNames
+    (
+        this->mesh_.sortedNames<VolFieldType>(fieldNames_)
+    );
+
+    const bool processed = !fieldNames.empty();
+
+    for (const word& fieldName : fieldNames)
+    {
+        const VolFieldType& field = lookupObject<VolFieldType>(fieldName);
+
+        auto* mapFieldPtr = mapRegion.getObjectPtr<VolFieldType>(fieldName);
+
+        if (!mapFieldPtr)
+        {
+            mapFieldPtr = new VolFieldType
+            (
+                IOobject
+                (
+                    fieldName,
+                    time_.timeName(),
+                    mapRegion,
+                    IOobject::NO_READ,
+                    IOobject::NO_WRITE,
+                    IOobject::REGISTER
+                ),
+                mapRegion,
+                dimensioned<Type>(field.dimensions(), Zero)
+            );
+
+            mapFieldPtr->store();
+        }
+
+        auto& mappedField = *mapFieldPtr;
+
+        mappedField = interpolate
+        (
+            field,
+            mapRegion,
+            patchMap,
+            mapPtr_().cellMap(),
+            mapPtr_().faceMap(),
+            false             //allowUnmapped
+        );
+        Log << "    " << fieldName << ": interpolated";
+
+        //evaluateConstraintTypes(mappedField);
+    }
+
+    return processed;
+}
+
+
+template<class Type>
+bool Foam::functionObjects::cellDecomposer::writeFieldType() const
+{
+    typedef GeometricField<Type, fvPatchField, volMesh> VolFieldType;
+
+    const fvMesh& mapRegion =
+        this->mesh_.time().lookupObject<fvMesh>(mapRegion_);
+
+    const wordList fieldNames
+    (
+        this->mesh_.sortedNames<VolFieldType>(fieldNames_)
+    );
+
+    const bool processed = !fieldNames.empty();
+
+    for (const word& fieldName : fieldNames)
+    {
+        const VolFieldType& mappedField =
+            mapRegion.template lookupObject<VolFieldType>(fieldName);
+
+        mappedField.write();
+
+        Log << "    " << fieldName << ": written";
+    }
+
+    return processed;
+}
+
+
+// ************************************************************************* //