From 78c984f8f40bd813d7c3d0a0dc00554117d3417e Mon Sep 17 00:00:00 2001
From: Andrew Heather <>
Date: Tue, 12 Apr 2022 16:03:14 +0100
Subject: [PATCH] ENH: binField: new field function object

The new 'binField' function object calculates binned data,
where specified patches are divided into segments according to
various input bin characteristics, so that spatially-localised
information can be output for each segment.

Co-authored-by: Kutalmis Bercin <kutalmis.bercin@esi-group.com>
---
 src/functionObjects/field/Make/files          |   6 +
 src/functionObjects/field/binField/binField.C | 128 +++++++
 src/functionObjects/field/binField/binField.H | 221 ++++++++++++
 .../binField/binModels/binModel/binModel.C    | 205 ++++++++++++
 .../binField/binModels/binModel/binModel.H    | 243 ++++++++++++++
 .../binField/binModels/binModel/binModelNew.C |  59 ++++
 .../binModels/binModel/binModelTemplates.C    | 102 ++++++
 .../singleDirectionUniformBin.C               | 186 +++++++++++
 .../singleDirectionUniformBin.H               | 186 +++++++++++
 .../singleDirectionUniformBinTemplates.C      | 195 +++++++++++
 .../binModels/uniformBin/uniformBin.C         | 315 ++++++++++++++++++
 .../binModels/uniformBin/uniformBin.H         | 222 ++++++++++++
 .../uniformBin/uniformBinTemplates.C          | 178 ++++++++++
 13 files changed, 2246 insertions(+)
 create mode 100644 src/functionObjects/field/binField/binField.C
 create mode 100644 src/functionObjects/field/binField/binField.H
 create mode 100644 src/functionObjects/field/binField/binModels/binModel/binModel.C
 create mode 100644 src/functionObjects/field/binField/binModels/binModel/binModel.H
 create mode 100644 src/functionObjects/field/binField/binModels/binModel/binModelNew.C
 create mode 100644 src/functionObjects/field/binField/binModels/binModel/binModelTemplates.C
 create mode 100644 src/functionObjects/field/binField/binModels/singleDirectionUniformBin/singleDirectionUniformBin.C
 create mode 100644 src/functionObjects/field/binField/binModels/singleDirectionUniformBin/singleDirectionUniformBin.H
 create mode 100644 src/functionObjects/field/binField/binModels/singleDirectionUniformBin/singleDirectionUniformBinTemplates.C
 create mode 100644 src/functionObjects/field/binField/binModels/uniformBin/uniformBin.C
 create mode 100644 src/functionObjects/field/binField/binModels/uniformBin/uniformBin.H
 create mode 100644 src/functionObjects/field/binField/binModels/uniformBin/uniformBinTemplates.C

diff --git a/src/functionObjects/field/Make/files b/src/functionObjects/field/Make/files
index 59fabd85cd3..b7c2550efbd 100644
--- a/src/functionObjects/field/Make/files
+++ b/src/functionObjects/field/Make/files
@@ -1,5 +1,11 @@
 AMIWeights/AMIWeights.C
 
+binField/binField.C
+binField/binModels/binModel/binModel.C
+binField/binModels/binModel/binModelNew.C
+binField/binModels/uniformBin/uniformBin.C
+binField/binModels/singleDirectionUniformBin/singleDirectionUniformBin.C
+
 columnAverage/columnAverage.C
 
 continuityError/continuityError.C
diff --git a/src/functionObjects/field/binField/binField.C b/src/functionObjects/field/binField/binField.C
new file mode 100644
index 00000000000..b805afcd9ae
--- /dev/null
+++ b/src/functionObjects/field/binField/binField.C
@@ -0,0 +1,128 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
+   \\    /   O peration     |
+    \\  /    A nd           | www.openfoam.com
+     \\/     M anipulation  |
+-------------------------------------------------------------------------------
+    Copyright (C) 2022 OpenCFD Ltd.
+-------------------------------------------------------------------------------
+License
+    This file is part of OpenFOAM.
+
+    OpenFOAM is free software: you can redistribute it and/or modify it
+    under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    OpenFOAM is distributed in the hope that it will be useful, but WITHOUT
+    ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+    FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+    for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with OpenFOAM.  If not, see <http://www.gnu.org/licenses/>.
+
+\*---------------------------------------------------------------------------*/
+
+#include "binField.H"
+#include "binModel.H"
+#include "addToRunTimeSelectionTable.H"
+
+// * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * //
+
+namespace Foam
+{
+namespace functionObjects
+{
+    defineTypeNameAndDebug(binField, 0);
+    addToRunTimeSelectionTable(functionObject, binField, dictionary);
+}
+}
+
+
+// * * * * * * * * * * * * * * * * Constructors  * * * * * * * * * * * * * * //
+
+Foam::functionObjects::binField::binField
+(
+    const word& name,
+    const Time& runTime,
+    const dictionary& dict,
+    bool readFields
+)
+:
+    fvMeshFunctionObject(name, runTime, dict),
+    binModelPtr_(nullptr)
+{
+    if (readFields)
+    {
+        read(dict);
+    }
+}
+
+
+Foam::functionObjects::binField::binField
+(
+    const word& name,
+    const objectRegistry& obr,
+    const dictionary& dict,
+    bool readFields
+)
+:
+    fvMeshFunctionObject(name, obr, dict),
+    binModelPtr_(nullptr)
+{
+    if (readFields)
+    {
+        read(dict);
+    }
+}
+
+
+// * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //
+
+bool Foam::functionObjects::binField::read(const dictionary& dict)
+{
+    if (!fvMeshFunctionObject::read(dict))
+    {
+        return false;
+    }
+
+    Info<< type() << " " << name() << ":" << endl;
+
+    binModelPtr_.reset(binModel::New(dict, mesh_, name()));
+
+    return true;
+}
+
+
+bool Foam::functionObjects::binField::execute()
+{
+    Log << type() << " " << name() << ":" << nl
+        << "    Calculating bins" << nl << endl;
+
+    binModelPtr_->apply();
+
+    return true;
+}
+
+
+bool Foam::functionObjects::binField::write()
+{
+    return true;
+}
+
+
+void Foam::functionObjects::binField::updateMesh(const mapPolyMesh& mpm)
+{
+    binModelPtr_->updateMesh(mpm);
+}
+
+
+void Foam::functionObjects::binField::movePoints(const polyMesh& mesh)
+{
+    binModelPtr_->movePoints(mesh);
+}
+
+
+// ************************************************************************* //
diff --git a/src/functionObjects/field/binField/binField.H b/src/functionObjects/field/binField/binField.H
new file mode 100644
index 00000000000..1fd57a1aeda
--- /dev/null
+++ b/src/functionObjects/field/binField/binField.H
@@ -0,0 +1,221 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
+   \\    /   O peration     |
+    \\  /    A nd           | www.openfoam.com
+     \\/     M anipulation  |
+-------------------------------------------------------------------------------
+    Copyright (C) 2022 OpenCFD Ltd.
+-------------------------------------------------------------------------------
+License
+    This file is part of OpenFOAM.
+
+    OpenFOAM is free software: you can redistribute it and/or modify it
+    under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    OpenFOAM is distributed in the hope that it will be useful, but WITHOUT
+    ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+    FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+    for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with OpenFOAM.  If not, see <http://www.gnu.org/licenses/>.
+
+Class
+    Foam::functionObjects::binField
+
+Group
+    grpFieldFunctionObjects
+
+Description
+    Calculates binned data, where specified patches are divided into
+    segments according to various input bin characteristics, so that
+    spatially-localised information can be output for each segment.
+
+    Operands:
+    \table
+      Operand      | Type                          | Location
+      input        | vol\<Type\>Field(s)          <!--
+               --> | \<time\>/\<inpField\>s
+      output file  | dat                           <!--
+               --> | postProcessing/\<FO\>/\<time\>/\<file\>
+      output field | -                             | -
+    \endtable
+
+    where \c \<Type\>=Scalar/Vector/SphericalTensor/SymmTensor/Tensor.
+
+Usage
+    Minimal example by using \c system/controlDict.functions:
+    \verbatim
+    binField1
+    {
+        // Mandatory entries
+        type                    binField;
+        libs                    (fieldFunctionObjects);
+        binModel                <word>;
+        fields                  (<wordHashSet>);
+        patches                 (<wordRes>);
+        binData
+        {
+            // Entries of the chosen binModel
+        }
+
+        // Optional entries
+        cellZones               (<wordRes>);
+        decomposePatchValues    <bool>;
+
+        // Conditional optional entries
+
+            // Option-1, i.e. general coordinate system specification
+            coordinateSystem
+            {
+                type            cartesian;
+                origin          (0 0 0);
+                rotation
+                {
+                    type        axes;
+                    e3          (0 0 1);
+                    e1          (1 0 0); // (e1, e2) or (e2, e3) or (e3, e1)
+                }
+            }
+
+            // Option-2, i.e. the centre of rotation
+            // by inherently using e3=(0 0 1) and e1=(1 0 0)
+            CofR                (0 0 0);
+
+        // Inherited entries
+        ...
+    }
+    \endverbatim
+
+    where the entries mean:
+    \table
+      Property  | Description                           | Type   | Reqd | Deflt
+      type      | Type name: binField                   | word   | yes  | -
+      libs      | Library name: fieldFunctionObjects    | word   | yes  | -
+      binModel  | Name of the bin model                 | word   | yes  | -
+      fields    | Names of operand fields           | wordHasSet | yes  | -
+      patches   | Names of operand patches             | wordRes | yes  | -
+      binData   | Entries of the chosen bin model       | dict   | yes  | -
+      decomposePatchValues | Flag to output normal and tangential      <!--
+                --> components                          | bool   | no   | false
+      cellZones | Names of operand cell zones          | wordRes | no   | -
+      coordinateSystem | Coordinate system specifier      | dict | cndtnl | -
+      CofR    | Centre of rotation                      | vector | cndtnl | -
+    \endtable
+
+    Options for the \c binModel entry:
+    \verbatim
+      singleDirectionUniformBin    | Segments in a single direction
+      uniformBin                   | Segments in multiple directions
+    \endverbatim
+
+    The inherited entries are elaborated in:
+      - \link fvMeshFunctionObject.H \endlink
+      - \link writeFile.H \endlink
+      - \link coordinateSystem.H \endlink
+
+SourceFiles
+    binField.C
+
+\*---------------------------------------------------------------------------*/
+
+#ifndef Foam_functionObjects_binField_H
+#define Foam_functionObjects_binField_H
+
+#include "fvMeshFunctionObject.H"
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+namespace Foam
+{
+
+class binModel;
+
+namespace functionObjects
+{
+
+/*---------------------------------------------------------------------------*\
+                          Class binField Declaration
+\*---------------------------------------------------------------------------*/
+
+class binField
+:
+    public fvMeshFunctionObject
+{
+protected:
+
+    // Protected Data
+
+        //- Runtime-selectable bin model
+        autoPtr<binModel> binModelPtr_;
+
+
+public:
+
+    //- Runtime type information
+    TypeName("binField");
+
+
+    // Constructors
+
+        //- Construct from Time and dictionary
+        binField
+        (
+            const word& name,
+            const Time& runTime,
+            const dictionary& dict,
+            const bool readFields = true
+        );
+
+        //- Construct from objectRegistry and dictionary
+        binField
+        (
+            const word& name,
+            const objectRegistry& obr,
+            const dictionary& dict,
+            const bool readFields = true
+        );
+
+        //- No copy construct
+        binField(const binField&) = delete;
+
+        //- No copy assignment
+        void operator=(const binField&) = delete;
+
+
+    //- Destructor
+    virtual ~binField() = default;
+
+
+    // Member Functions
+
+        //- Read the dictionary
+        virtual bool read(const dictionary& dict);
+
+        //- Execute the function object
+        virtual bool execute();
+
+        //- Write to data files/fields and to streams
+        virtual bool write();
+
+        //- Update for changes of mesh
+        virtual void updateMesh(const mapPolyMesh& mpm);
+
+        //- Update for changes of mesh
+        virtual void movePoints(const polyMesh& mesh);
+};
+
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+} // End namespace functionObjects
+} // End namespace Foam
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+#endif
+
+// ************************************************************************* //
diff --git a/src/functionObjects/field/binField/binModels/binModel/binModel.C b/src/functionObjects/field/binField/binModels/binModel/binModel.C
new file mode 100644
index 00000000000..5e351b07837
--- /dev/null
+++ b/src/functionObjects/field/binField/binModels/binModel/binModel.C
@@ -0,0 +1,205 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
+   \\    /   O peration     |
+    \\  /    A nd           | www.openfoam.com
+     \\/     M anipulation  |
+-------------------------------------------------------------------------------
+    Copyright (C) 2021-2022 OpenCFD Ltd.
+-------------------------------------------------------------------------------
+License
+    This file is part of OpenFOAM.
+
+    OpenFOAM is free software: you can redistribute it and/or modify it
+    under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    OpenFOAM is distributed in the hope that it will be useful, but WITHOUT
+    ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+    FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+    for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with OpenFOAM.  If not, see <http://www.gnu.org/licenses/>.
+
+\*---------------------------------------------------------------------------*/
+
+#include "binModel.H"
+#include "fvMesh.H"
+#include "cartesianCS.H"
+
+// * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * //
+
+namespace Foam
+{
+    defineTypeNameAndDebug(binModel, 0);
+    defineRunTimeSelectionTable(binModel, dictionary);
+}
+
+
+// * * * * * * * * * * * * Protected Member Functions  * * * * * * * * * * * //
+
+template<>
+bool Foam::binModel::decomposePatchValues
+(
+    List<List<vector>>& data,
+    const label bini,
+    const vector& v,
+    const vector& n
+) const
+{
+    if (!decomposePatchValues_)
+    {
+        return false;
+    }
+
+    #ifdef FULLDEBUG
+    if (data.size() != 3)
+    {
+        FatalErrorInFunction
+            << "Inconsistent data list size - expect size 3"
+            << abort(FatalError);
+    }
+    #endif
+
+    data[1][bini] += n*(v & n);
+    data[2][bini] += v - n*(v & n);
+
+    return true;
+}
+
+
+void Foam::binModel::setCoordinateSystem
+(
+    const dictionary& dict,
+    const word& e3Name,
+    const word& e1Name
+)
+{
+    coordSysPtr_.clear();
+
+    if (dict.found(coordinateSystem::typeName_()))
+    {
+        coordSysPtr_ =
+            coordinateSystem::New
+            (
+                mesh_,
+                dict,
+                coordinateSystem::typeName_()
+            );
+
+        Info<< "Setting co-ordinate system:" << nl
+            << "    - type          : " << coordSysPtr_->name() << nl
+            << "    - origin        : " << coordSysPtr_->origin() << nl
+            << "    - e3            : " << coordSysPtr_->e3() << nl
+            << "    - e1            : " << coordSysPtr_->e1() << endl;
+    }
+    else if (dict.found("CofR"))
+    {
+        const vector origin(dict.get<point>("CofR"));
+
+        const vector e3
+        (
+            e3Name == word::null
+          ? vector(0, 0, 1)
+          : dict.get<vector>(e3Name)
+        );
+
+        const vector e1
+        (
+            e1Name == word::null
+          ? vector(1, 0, 0)
+          : dict.get<vector>(e1Name)
+        );
+
+        coordSysPtr_.reset(new coordSystem::cartesian(origin, e3, e1));
+    }
+    else
+    {
+        coordSysPtr_.reset(new coordSystem::cartesian(dict));
+    }
+}
+
+
+// * * * * * * * * * * * * * * * * Constructors  * * * * * * * * * * * * * * //
+
+Foam::binModel::binModel
+(
+    const dictionary& dict,
+    const fvMesh& mesh,
+    const word& outputPrefix
+)
+:
+    writeFile(mesh, outputPrefix),
+    mesh_(mesh),
+    decomposePatchValues_(false),
+    cumulative_(false),
+    coordSysPtr_(),
+    nBin_(1),
+    patchSet_(),
+    fieldNames_(),
+    cellZoneIDs_(),
+    filePtrs_()
+{}
+
+
+// * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * * //
+
+bool Foam::binModel::read(const dictionary& dict)
+{
+    patchSet_ = mesh_.boundaryMesh().patchSet(dict.get<wordRes>("patches"));
+    fieldNames_ = dict.get<wordHashSet>("fields").sortedToc();
+
+    if (dict.found("cellZones"))
+    {
+        DynamicList<label> zoneIDs;
+        DynamicList<wordRe> czUnmatched;
+        for (const auto& cz : dict.get<wordRes>("cellZones"))
+        {
+            const labelList czi(mesh_.cellZones().indices(cz));
+
+            if (czi.empty())
+            {
+                czUnmatched.append(cz);
+            }
+            else
+            {
+                zoneIDs.append(czi);
+            }
+        }
+
+        if (czUnmatched.size())
+        {
+            WarningInFunction
+                << "Unable to find zone(s): " << czUnmatched << nl
+                << "Valid cellZones are : " << mesh_.cellZones().sortedNames()
+                << endl;
+        }
+
+        cellZoneIDs_.transfer(zoneIDs);
+    }
+
+    decomposePatchValues_ = dict.get<bool>("decomposePatchValues");
+
+    filePtrs_.setSize(fieldNames_.size());
+    forAll(filePtrs_, i)
+    {
+        filePtrs_.set(i, createFile(fieldNames_[i] + "Bin"));
+    }
+
+    setCoordinateSystem(dict);
+
+    return true;
+}
+
+
+void Foam::binModel::updateMesh(const mapPolyMesh& mpm)
+{}
+
+
+void Foam::binModel::movePoints(const polyMesh& mesh)
+{}
+
+
+// ************************************************************************* //
diff --git a/src/functionObjects/field/binField/binModels/binModel/binModel.H b/src/functionObjects/field/binField/binModels/binModel/binModel.H
new file mode 100644
index 00000000000..3583a0a9731
--- /dev/null
+++ b/src/functionObjects/field/binField/binModels/binModel/binModel.H
@@ -0,0 +1,243 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
+   \\    /   O peration     |
+    \\  /    A nd           | www.openfoam.com
+     \\/     M anipulation  |
+-------------------------------------------------------------------------------
+    Copyright (C) 2021-2022 OpenCFD Ltd.
+-------------------------------------------------------------------------------
+License
+    This file is part of OpenFOAM.
+
+    OpenFOAM is free software: you can redistribute it and/or modify it
+    under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    OpenFOAM is distributed in the hope that it will be useful, but WITHOUT
+    ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+    FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+    for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with OpenFOAM.  If not, see <http://www.gnu.org/licenses/>.
+
+Class
+    Foam::binModel
+
+Description
+    Base class for bin models to handle general bin characteristics.
+
+SourceFiles
+    binModel.C
+    binModelNew.C
+    binModelTemplates.C
+
+\*---------------------------------------------------------------------------*/
+
+#ifndef Foam_binModel_H
+#define Foam_binModel_H
+
+#include "dictionary.H"
+#include "HashSet.H"
+#include "volFields.H"
+#include "runTimeSelectionTables.H"
+#include "OFstream.H"
+#include "coordinateSystem.H"
+#include "writeFile.H"
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+namespace Foam
+{
+
+// Forward Declarations
+class fvMesh;
+
+/*---------------------------------------------------------------------------*\
+                            Class binModel Declaration
+\*---------------------------------------------------------------------------*/
+
+class binModel
+:
+    public functionObjects::writeFile
+{
+protected:
+
+    // Protected Data
+
+        //- Reference to the mesh
+        const fvMesh& mesh_;
+
+        //- Decompose patch values into normal and tangential components
+        bool decomposePatchValues_;
+
+        //- Flag to accumulate bin data with
+        //- increasing distance in binning direction
+        bool cumulative_;
+
+        //- Local coordinate system of bins
+        autoPtr<coordinateSystem> coordSysPtr_;
+
+        //- Total number of bins
+        label nBin_;
+
+        //- Indices of operand patches
+        labelHashSet patchSet_;
+
+        //- Names of operand fields
+        wordList fieldNames_;
+
+        //- Indices of operand cell zones
+        labelList cellZoneIDs_;
+
+        //- List of file pointers; 1 file per field
+        PtrList<OFstream> filePtrs_;
+
+
+    // Protected Member Functions
+
+        //- Set the co-ordinate system from dictionary and axes names
+        void setCoordinateSystem
+        (
+            const dictionary& dict,
+            const word& e3Name = word::null,
+            const word& e1Name = word::null
+        );
+
+        //- Helper function to decompose patch values
+        //- into normal and tangential components
+        template<class Type>
+        bool decomposePatchValues
+        (
+            List<List<Type>>& data,
+            const label bini,
+            const Type& v,
+            const vector& n
+        ) const;
+
+        //- Helper function to construct a string description for a given type
+        template<class Type>
+        string writeComponents(const word& stem) const;
+
+        //- Write binned data to stream
+        template<class Type>
+        void writeBinnedData
+        (
+            List<List<Type>>& data,
+            Ostream& os
+        ) const;
+
+
+public:
+
+    //- Runtime type information
+    TypeName("binModel");
+
+
+    // Declare runtime constructor selection table
+
+        declareRunTimeSelectionTable
+        (
+            autoPtr,
+            binModel,
+            dictionary,
+            (
+                const dictionary& dict,
+                const fvMesh& mesh,
+                const word& outputPrefix
+            ),
+            (dict, mesh, outputPrefix)
+        );
+
+
+    // Selectors
+
+        //- Return a reference to the selected bin model
+        static autoPtr<binModel> New
+        (
+            const dictionary& dict,
+            const fvMesh& mesh,
+            const word& outputPrefix
+        );
+
+
+    // Constructors
+
+        //- Construct from components
+        binModel
+        (
+            const dictionary& dict,
+            const fvMesh& mesh,
+            const word& outputPrefix
+        );
+
+        //- No copy construct
+        binModel(const binModel&) = delete;
+
+        //- No copy assignment
+        void operator=(const binModel&) = delete;
+
+
+    //- Destructor
+    virtual ~binModel() = default;
+
+
+    // Member Functions
+
+        //- Read the dictionary
+        virtual bool read(const dictionary& dict);
+
+
+    // Access
+
+        //- Return the total number of bins
+        label nBin() const noexcept
+        {
+            return nBin_;
+        };
+
+
+    // Evaluation
+
+        //- Initialise bin properties
+        virtual void initialise() = 0;
+
+        //- Apply bins
+        virtual void apply() = 0;
+
+        //- Update for changes of mesh
+        virtual void updateMesh(const mapPolyMesh& mpm);
+
+        //- Update for changes of mesh
+        virtual void movePoints(const polyMesh& mesh);
+};
+
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+template<>
+bool binModel::decomposePatchValues
+(
+    List<List<vector>>& data,
+    const label bini,
+    const vector& v,
+    const vector& n
+) const;
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+} // End namespace Foam
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+#ifdef NoRepository
+    #include "binModelTemplates.C"
+#endif
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+#endif
+
+// ************************************************************************* //
diff --git a/src/functionObjects/field/binField/binModels/binModel/binModelNew.C b/src/functionObjects/field/binField/binModels/binModel/binModelNew.C
new file mode 100644
index 00000000000..2447b099198
--- /dev/null
+++ b/src/functionObjects/field/binField/binModels/binModel/binModelNew.C
@@ -0,0 +1,59 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
+   \\    /   O peration     |
+    \\  /    A nd           | www.openfoam.com
+     \\/     M anipulation  |
+-------------------------------------------------------------------------------
+    Copyright (C) 2021-2022 OpenCFD Ltd.
+-------------------------------------------------------------------------------
+License
+    This file is part of OpenFOAM.
+
+    OpenFOAM is free software: you can redistribute it and/or modify it
+    under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    OpenFOAM is distributed in the hope that it will be useful, but WITHOUT
+    ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+    FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+    for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with OpenFOAM.  If not, see <http://www.gnu.org/licenses/>.
+
+\*---------------------------------------------------------------------------*/
+
+#include "binModel.H"
+#include "fvMesh.H"
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+Foam::autoPtr<Foam::binModel> Foam::binModel::New
+(
+    const dictionary& dict,
+    const fvMesh& mesh,
+    const word& outputPrefix
+)
+{
+    word modelType(dict.get<word>("binModel"));
+
+    auto cstrIter = dictionaryConstructorTablePtr_->cfind(modelType);
+
+    if (!cstrIter.found())
+    {
+        FatalIOErrorInLookup
+        (
+            dict,
+            "binModel",
+            modelType,
+            *dictionaryConstructorTablePtr_
+        ) << exit(FatalIOError);
+    }
+
+    return autoPtr<binModel>(cstrIter()(dict, mesh, outputPrefix));
+}
+
+
+// ************************************************************************* //
diff --git a/src/functionObjects/field/binField/binModels/binModel/binModelTemplates.C b/src/functionObjects/field/binField/binModels/binModel/binModelTemplates.C
new file mode 100644
index 00000000000..fdd86ffaa45
--- /dev/null
+++ b/src/functionObjects/field/binField/binModels/binModel/binModelTemplates.C
@@ -0,0 +1,102 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
+   \\    /   O peration     |
+    \\  /    A nd           | www.openfoam.com
+     \\/     M anipulation  |
+-------------------------------------------------------------------------------
+    Copyright (C) 2021-2022 OpenCFD Ltd.
+-------------------------------------------------------------------------------
+License
+    This file is part of OpenFOAM.
+
+    OpenFOAM is free software: you can redistribute it and/or modify it
+    under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    OpenFOAM is distributed in the hope that it will be useful, but WITHOUT
+    ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+    FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+    for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with OpenFOAM.  If not, see <http://www.gnu.org/licenses/>.
+
+\*---------------------------------------------------------------------------*/
+
+// * * * * * * * * * * * * Protected Member Functions  * * * * * * * * * * * //
+
+template<class Type>
+bool Foam::binModel::decomposePatchValues
+(
+    List<List<Type>>& data,
+    const label bini,
+    const Type& v,
+    const vector& n
+) const
+{
+    return decomposePatchValues_;
+}
+
+
+template<class Type>
+Foam::string Foam::binModel::writeComponents(const word& stem) const
+{
+    if (pTraits<Type>::nComponents == 1)
+    {
+        return stem;
+    }
+
+    string result = "";
+    for (label cmpt = 0; cmpt < pTraits<Type>::nComponents; ++cmpt)
+    {
+        if (cmpt) result += " ";
+        result += stem + "_" + word(pTraits<Type>::componentNames[cmpt]);
+    }
+    return result;
+};
+
+
+template<class Type>
+void Foam::binModel::writeBinnedData
+(
+    List<List<Type>>& data,
+    Ostream& os
+) const
+{
+    if (cumulative_)
+    {
+        for (auto& datai : data)
+        {
+            for (label bini = 1; bini < nBin_; ++bini)
+            {
+                datai[bini] += datai[bini-1];
+            }
+        }
+    }
+
+    writeCurrentTime(os);
+
+    for (label bini = 0; bini < nBin_; ++bini)
+    {
+        Type total = Zero;
+
+        for (label i = 0; i < data.size(); ++i)
+        {
+            total += data[i][bini];
+        }
+
+        writeValue(os, total);
+
+        for (label i = 0; i < data.size(); ++i)
+        {
+            writeValue(os, data[i][bini]);
+        }
+    }
+
+    os  << endl;
+}
+
+
+// ************************************************************************* //
diff --git a/src/functionObjects/field/binField/binModels/singleDirectionUniformBin/singleDirectionUniformBin.C b/src/functionObjects/field/binField/binModels/singleDirectionUniformBin/singleDirectionUniformBin.C
new file mode 100644
index 00000000000..2140504cc04
--- /dev/null
+++ b/src/functionObjects/field/binField/binModels/singleDirectionUniformBin/singleDirectionUniformBin.C
@@ -0,0 +1,186 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
+   \\    /   O peration     |
+    \\  /    A nd           | www.openfoam.com
+     \\/     M anipulation  |
+-------------------------------------------------------------------------------
+    Copyright (C) 2021-2022 OpenCFD Ltd.
+-------------------------------------------------------------------------------
+License
+    This file is part of OpenFOAM.
+
+    OpenFOAM is free software: you can redistribute it and/or modify it
+    under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    OpenFOAM is distributed in the hope that it will be useful, but WITHOUT
+    ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+    FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+    for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with OpenFOAM.  If not, see <http://www.gnu.org/licenses/>.
+
+\*---------------------------------------------------------------------------*/
+
+#include "singleDirectionUniformBin.H"
+#include "addToRunTimeSelectionTable.H"
+
+// * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * //
+
+namespace Foam
+{
+namespace binModels
+{
+    defineTypeNameAndDebug(singleDirectionUniformBin, 0);
+    addToRunTimeSelectionTable(binModel, singleDirectionUniformBin, dictionary);
+}
+}
+
+
+// * * * * * * * * * * * * Protected Member Functions  * * * * * * * * * * * //
+
+void Foam::binModels::singleDirectionUniformBin::initialise()
+{
+    const polyBoundaryMesh& pbm = mesh_.boundaryMesh();
+
+    // Determine extents of patches in a given direction
+    scalar geomMin = GREAT;
+    scalar geomMax = -GREAT;
+    for (const label patchi : patchSet_)
+    {
+        const polyPatch& pp = pbm[patchi];
+        const scalarField d(pp.faceCentres() & binDir_);
+        geomMin = min(min(d), geomMin);
+        geomMax = max(max(d), geomMax);
+    }
+
+    for (const label zonei : cellZoneIDs_)
+    {
+        const cellZone& cZone = mesh_.cellZones()[zonei];
+        const vectorField cz(mesh_.C(), cZone);
+        const scalarField d(cz & binDir_);
+
+        geomMin = min(min(d), geomMin);
+        geomMax = max(max(d), geomMax);
+    }
+
+    reduce(geomMin, minOp<scalar>());
+    reduce(geomMax, maxOp<scalar>());
+
+    // Slightly boost max so that region of interest is fully within bounds
+    geomMax = 1.0001*(geomMax - geomMin) + geomMin;
+
+    // Use geometry limits if not specified by the user
+    if (binMin_ == GREAT) binMin_ = geomMin;
+    if (binMax_ == GREAT) binMax_ = geomMax;
+
+    binDx_ = (binMax_ - binMin_)/scalar(nBin_);
+
+    if (binDx_ <= 0)
+    {
+        FatalErrorInFunction
+            << "Max bound must be greater than min bound" << nl
+            << "    d           = " << binDx_ << nl
+            << "    min         = " << binMin_ << nl
+            << "    max         = " << binMax_ << nl
+            << exit(FatalError);
+    }
+}
+
+
+// * * * * * * * * * * * * * * * * Constructors  * * * * * * * * * * * * * * //
+
+Foam::binModels::singleDirectionUniformBin::singleDirectionUniformBin
+(
+    const dictionary& dict,
+    const fvMesh& mesh,
+    const word& outputPrefix
+)
+:
+    binModel(dict, mesh, outputPrefix),
+    binDx_(0),
+    binMin_(GREAT),
+    binMax_(GREAT),
+    binDir_(Zero)
+{
+    read(dict);
+}
+
+
+// * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * * //
+
+bool Foam::binModels::singleDirectionUniformBin::read(const dictionary& dict)
+{
+    if (!binModel::read(dict))
+    {
+        return false;
+    }
+
+    Info<< "    Activating a set of single-direction bins" << endl;
+
+    const dictionary& binDict = dict.subDict("binData");
+
+    nBin_ = binDict.getCheck<label>("nBin", labelMinMax::ge(1));
+
+    Info<< "    Employing " << nBin_ << " bins" << endl;
+    if (binDict.readIfPresent("min", binMin_))
+    {
+        Info<< "    - min        : " << binMin_ << endl;
+    }
+    if (binDict.readIfPresent("max", binMax_))
+    {
+        Info<< "    - max        : " << binMax_ << endl;
+    }
+
+    cumulative_ = binDict.getOrDefault<bool>("cumulative", false);
+    Info<< "    - cumulative    : " << cumulative_ << endl;
+    Info<< "    - decomposePatchValues    : " << decomposePatchValues_ << endl;
+
+    binDir_ = binDict.get<vector>("direction");
+    binDir_.normalise();
+
+    if (mag(binDir_) == 0)
+    {
+        FatalIOErrorInFunction(dict)
+            << "Input direction should not be zero valued" << nl
+            << "    direction = " << binDir_ << nl
+            << exit(FatalIOError);
+    }
+
+    Info<< "    - direction     : " << binDir_ << nl << endl;
+
+    initialise();
+
+    return true;
+}
+
+
+void Foam::binModels::singleDirectionUniformBin::apply()
+{
+    forAll(fieldNames_, i)
+    {
+        const bool ok =
+            processField<scalar>(i)
+         || processField<vector>(i)
+         || processField<sphericalTensor>(i)
+         || processField<symmTensor>(i)
+         || processField<tensor>(i);
+
+        if (!ok)
+        {
+            WarningInFunction
+                << "Unable to find field " << fieldNames_[i]
+                << ". Avaliable objects are "
+                << mesh_.objectRegistry::sortedToc()
+                << endl;
+        }
+    }
+
+    writtenHeader_ = true;
+}
+
+
+// ************************************************************************* //
diff --git a/src/functionObjects/field/binField/binModels/singleDirectionUniformBin/singleDirectionUniformBin.H b/src/functionObjects/field/binField/binModels/singleDirectionUniformBin/singleDirectionUniformBin.H
new file mode 100644
index 00000000000..1e2a631b11b
--- /dev/null
+++ b/src/functionObjects/field/binField/binModels/singleDirectionUniformBin/singleDirectionUniformBin.H
@@ -0,0 +1,186 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
+   \\    /   O peration     |
+    \\  /    A nd           | www.openfoam.com
+     \\/     M anipulation  |
+-------------------------------------------------------------------------------
+    Copyright (C) 2021-2022 OpenCFD Ltd.
+-------------------------------------------------------------------------------
+License
+    This file is part of OpenFOAM.
+
+    OpenFOAM is free software: you can redistribute it and/or modify it
+    under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    OpenFOAM is distributed in the hope that it will be useful, but WITHOUT
+    ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+    FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+    for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with OpenFOAM.  If not, see <http://www.gnu.org/licenses/>.
+
+Class
+    Foam::binModels::singleDirectionUniformBin
+
+Description
+    Calculates binned data in a specified direction.
+
+    For example, a 10cm-long patch extending only in the x-direction
+    can be binned into 5 bins in the same direction, so that
+    local information can be output for each 2cm-long segment.
+
+Usage
+    Minimal example by using \c system/controlDict.functions:
+    \verbatim
+    binField1
+    {
+        // Other binField entries
+        ...
+
+        // Mandatory entries
+        binModel          singleDirectionUniformBin;
+
+        binData
+        {
+            // Mandatory entries
+            nBin          <label>;
+            direction     <vector>;
+
+            // Optional entries
+            cumulative    <bool>;
+            min           <scalar>;
+            max           <scalar>;
+        }
+    }
+    \endverbatim
+
+    where the entries mean:
+    \table
+      Property  | Description                           | Type   | Reqd | Deflt
+      binModel  | Type name: singleDirectionUniformBin  | word   | yes  | -
+      binData   | Entries of the chosen bin model       | dict   | yes  | -
+      nBin      | Number of bins in binning direction   | label  | yes  | -
+      direction | Binning direction                     | vector | yes  | -
+      cumulative | Flag to bin data accumulated with increasing distance <!--
+                --> in binning direction                | bool   | no   | false
+      min       | Min-bound in the binning direction with respect to   <!--
+                --> the global coordinate system's origin | scalar | no | GREAT
+      max       | Max-bound in the binning direction with respect to   <!--
+                --> the global coordinate system's origin | scalar | no | GREAT
+    \endtable
+
+SourceFiles
+    singleDirectionUniformBin.C
+    singleDirectionUniformBinTemplates.C
+
+\*---------------------------------------------------------------------------*/
+
+#ifndef Foam_binModels_singleDirectionUniformBin_H
+#define Foam_binModels_singleDirectionUniformBin_H
+
+#include "binModel.H"
+#include "writeFile.H"
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+namespace Foam
+{
+namespace binModels
+{
+
+/*---------------------------------------------------------------------------*\
+                   Class singleDirectionUniformBin Declaration
+\*---------------------------------------------------------------------------*/
+
+class singleDirectionUniformBin
+:
+    public binModel
+{
+protected:
+
+    // Protected Data
+
+        //- Distance between bin divisions
+        scalar binDx_;
+
+        //- Minimum bin bound
+        scalar binMin_;
+
+        //- Maximum bin bound
+        scalar binMax_;
+
+        //- Binning direction
+        vector binDir_;
+
+
+    // Protected Member Functions
+
+        //- Write header for a binned-data file
+        template<class Type>
+        void writeFileHeader(OFstream& os) const;
+
+        //- Initialise bin properties
+        virtual void initialise();
+
+        //- Apply the binning to field fieldi
+        template<class Type>
+        bool processField(const label fieldi);
+
+
+public:
+
+    //- Runtime type information
+    TypeName("singleDirectionUniformBin");
+
+
+    // Constructors
+
+        //- Construct from components
+        singleDirectionUniformBin
+        (
+            const dictionary& dict,
+            const fvMesh& mesh,
+            const word& outputPrefix
+        );
+
+        //- No copy construct
+        singleDirectionUniformBin(const singleDirectionUniformBin&) = delete;
+
+        //- No copy assignment
+        void operator=(const singleDirectionUniformBin&) = delete;
+
+
+    //- Destructor
+    virtual ~singleDirectionUniformBin() = default;
+
+
+    // Member Functions
+
+        //- Read the dictionary
+        virtual bool read(const dictionary& dict);
+
+        //- Apply bins
+        virtual void apply();
+};
+
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+} // End namespace binModels
+} // End namespace Foam
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+#ifdef NoRepository
+    #include "singleDirectionUniformBinTemplates.C"
+#endif
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+#endif
+
+// ************************************************************************* //
diff --git a/src/functionObjects/field/binField/binModels/singleDirectionUniformBin/singleDirectionUniformBinTemplates.C b/src/functionObjects/field/binField/binModels/singleDirectionUniformBin/singleDirectionUniformBinTemplates.C
new file mode 100644
index 00000000000..5fedd25a114
--- /dev/null
+++ b/src/functionObjects/field/binField/binModels/singleDirectionUniformBin/singleDirectionUniformBinTemplates.C
@@ -0,0 +1,195 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
+   \\    /   O peration     |
+    \\  /    A nd           | www.openfoam.com
+     \\/     M anipulation  |
+-------------------------------------------------------------------------------
+    Copyright (C) 2021-2022 OpenCFD Ltd.
+-------------------------------------------------------------------------------
+License
+    This file is part of OpenFOAM.
+
+    OpenFOAM is free software: you can redistribute it and/or modify it
+    under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    OpenFOAM is distributed in the hope that it will be useful, but WITHOUT
+    ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+    FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+    for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with OpenFOAM.  If not, see <http://www.gnu.org/licenses/>.
+
+\*---------------------------------------------------------------------------*/
+
+
+template<class Type>
+void Foam::binModels::singleDirectionUniformBin::writeFileHeader
+(
+    OFstream& os
+) const
+{
+    writeHeaderValue(os, "bins", nBin_);
+    writeHeaderValue(os, "start", binMin_);
+    writeHeaderValue(os, "end", binMax_);
+    writeHeaderValue(os, "delta", binDx_);
+    writeHeaderValue(os, "direction", binDir_);
+
+    // Compute and print bin end points in the binning direction
+    vectorField binPoints(nBin_);
+    writeCommented(os, "x co-ords  :");
+    forAll(binPoints, pointi)
+    {
+        binPoints[pointi] = (binMin_ + (pointi + 1)*binDx_)*binDir_;
+        os  << tab << binPoints[pointi].x();
+    }
+    os  << nl;
+
+    writeCommented(os, "y co-ords  :");
+    forAll(binPoints, pointi)
+    {
+        os  << tab << binPoints[pointi].y();
+    }
+    os  << nl;
+
+    writeCommented(os, "z co-ords  :");
+    forAll(binPoints, pointi)
+    {
+        os  << tab << binPoints[pointi].z();
+    }
+    os  << nl;
+
+    writeHeader(os, "");
+    writeCommented(os, "Time");
+
+    for (label i = 0; i < nBin_; ++i)
+    {
+        const word ibin("_" + Foam::name(i));
+        writeTabbed(os, writeComponents<Type>("total" + ibin));
+        writeTabbed(os, writeComponents<Type>("internal" + ibin));
+
+        if (decomposePatchValues_)
+        {
+            writeTabbed(os, writeComponents<Type>("normal" + ibin));
+            writeTabbed(os, writeComponents<Type>("tangenial" + ibin));
+        }
+        else
+        {
+            writeTabbed(os, writeComponents<Type>("patch" + ibin));
+        }
+
+    }
+
+    os  << endl;
+}
+
+
+template<class Type>
+bool Foam::binModels::singleDirectionUniformBin::processField
+(
+    const label fieldi
+)
+{
+    const word& fieldName = fieldNames_[fieldi];
+
+    typedef GeometricField<Type, fvPatchField, volMesh> VolFieldType;
+
+    const VolFieldType* fieldPtr = mesh_.findObject<VolFieldType>(fieldName);
+
+    if (!fieldPtr)
+    {
+        return false;
+    }
+
+    if (Pstream::master() && !writtenHeader_)
+    {
+        writeFileHeader<Type>(filePtrs_[fieldi]);
+    }
+
+    const VolFieldType& fld = *fieldPtr;
+
+    // Total number of fields
+    //
+    // 0: internal
+    // 1: patch total
+    //
+    // OR
+    //
+    // 0: internal
+    // 1: patch normal
+    // 2: patch tangential
+    label nField = 2;
+    if (decomposePatchValues_)
+    {
+        nField += 1;
+    }
+
+    List<List<Type>> data(nField);
+    for (auto& binList : data)
+    {
+        binList.setSize(nBin_, Zero);
+    }
+
+    for (const label zonei : cellZoneIDs_)
+    {
+        const cellZone& cZone = mesh_.cellZones()[zonei];
+
+        for (const label celli : cZone)
+        {
+            const scalar dd = mesh_.C()[celli] & binDir_;
+
+            if (dd < binMin_ || dd > binMax_)
+            {
+                continue;
+            }
+
+            // Find the bin division corresponding to the cell
+            const label bini =
+                min(max(floor((dd - binMin_)/binDx_), 0), nBin_ - 1);
+
+            data[0][bini] += fld[celli];
+        }
+    }
+
+    forAllIters(patchSet_, iter)
+    {
+        const label patchi = iter();
+        const polyPatch& pp = mesh_.boundaryMesh()[patchi];
+        const vectorField np(mesh_.boundary()[patchi].nf());
+
+        const scalarField dd(pp.faceCentres() & binDir_);
+
+        forAll(dd, facei)
+        {
+            // Avoid faces outside of the bin
+            if (dd[facei] < binMin_ || dd[facei] > binMax_)
+            {
+                continue;
+            }
+
+            // Find the bin division corresponding to the face
+            const label bini =
+                min(max(floor((dd[facei] - binMin_)/binDx_), 0), nBin_ - 1);
+
+            const Type& v = fld.boundaryField()[patchi][facei];
+
+            if (!decomposePatchValues(data, bini, v, np[facei]))
+            {
+                data[1][bini] += v;
+            }
+        }
+    }
+
+    if (Pstream::master())
+    {
+        writeBinnedData(data, filePtrs_[fieldi]);
+    }
+
+    return true;
+}
+
+
+// ************************************************************************* //
diff --git a/src/functionObjects/field/binField/binModels/uniformBin/uniformBin.C b/src/functionObjects/field/binField/binModels/uniformBin/uniformBin.C
new file mode 100644
index 00000000000..77836bf690b
--- /dev/null
+++ b/src/functionObjects/field/binField/binModels/uniformBin/uniformBin.C
@@ -0,0 +1,315 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
+   \\    /   O peration     |
+    \\  /    A nd           | www.openfoam.com
+     \\/     M anipulation  |
+-------------------------------------------------------------------------------
+    Copyright (C) 2021-2022 OpenCFD Ltd.
+-------------------------------------------------------------------------------
+License
+    This file is part of OpenFOAM.
+
+    OpenFOAM is free software: you can redistribute it and/or modify it
+    under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    OpenFOAM is distributed in the hope that it will be useful, but WITHOUT
+    ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+    FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+    for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with OpenFOAM.  If not, see <http://www.gnu.org/licenses/>.
+
+\*---------------------------------------------------------------------------*/
+
+#include "uniformBin.H"
+#include "addToRunTimeSelectionTable.H"
+
+// * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * //
+
+namespace Foam
+{
+namespace binModels
+{
+    defineTypeNameAndDebug(uniformBin, 0);
+    addToRunTimeSelectionTable(binModel, uniformBin, dictionary);
+}
+}
+
+// * * * * * * * * * * * * Protected Member Functions  * * * * * * * * * * * //
+
+void Foam::binModels::uniformBin::initialise()
+{
+    const polyBoundaryMesh& pbm = mesh_.boundaryMesh();
+
+    // Determine extents of patches in a given coordinate system
+    vector geomMin(GREAT, GREAT, GREAT);
+    vector geomMax(-GREAT, -GREAT, -GREAT);
+
+    for (const label patchi : patchSet_)
+    {
+        const polyPatch& pp = pbm[patchi];
+        const vectorField ppcs(coordSysPtr_->localPosition(pp.faceCentres()));
+
+        for (direction i = 0; i < vector::nComponents; ++i)
+        {
+            geomMin[i] = min(min(ppcs.component(i)), geomMin[i]);
+            geomMax[i] = max(max(ppcs.component(i)), geomMax[i]);
+        }
+    }
+
+    for (const label zonei : cellZoneIDs_)
+    {
+        const cellZone& cZone = mesh_.cellZones()[zonei];
+        const vectorField d
+        (
+            coordSysPtr_->localPosition(vectorField(mesh_.C(), cZone))
+        );
+
+        for (direction i = 0; i < vector::nComponents; ++i)
+        {
+            geomMin[i] = min(min(d.component(i)), geomMin[i]);
+            geomMax[i] = max(max(d.component(i)), geomMax[i]);
+        }
+    }
+
+    reduce(geomMin, minOp<vector>());
+    reduce(geomMax, maxOp<vector>());
+
+    for (direction i = 0; i < vector::nComponents; ++i)
+    {
+        // Slightly boost max so that region of interest is fully within bounds
+        geomMax[i] = 1.0001*(geomMax[i] - geomMin[i]) + geomMin[i];
+
+        // Use geometry limits if not specified by the user
+        if (binMinMax_[i][0] == GREAT) binMinMax_[i][0] = geomMin[i];
+        if (binMinMax_[i][1] == GREAT) binMinMax_[i][1] = geomMax[i];
+
+        if (binMinMax_[i][0] > binMinMax_[i][1])
+        {
+            FatalErrorInFunction
+                << "Max bounds must be greater than min bounds" << nl
+                << "    direction   = " << i << nl
+                << "    min         = " << binMinMax_[i][0] << nl
+                << "    max         = " << binMinMax_[i][1] << nl
+                << exit(FatalError);
+        }
+
+        //- Compute bin widths in binning directions
+        binW_[i] = (binMinMax_[i][1] - binMinMax_[i][0])/scalar(nBins_[i]);
+
+        if (binW_[i] <= 0)
+        {
+            FatalErrorInFunction
+                << "Bin widths must be greater than zero" << nl
+                << "    direction = " << i << nl
+                << "    min bound = " << binMinMax_[i][0] << nl
+                << "    max bound = " << binMinMax_[i][1] << nl
+                << "    bin width = " << binW_[i]
+                << exit(FatalError);
+        }
+    }
+
+    setBinsAddressing();
+}
+
+
+Foam::labelList Foam::binModels::uniformBin::binAddr(const vectorField& d) const
+{
+    labelList binIndices(d.size(), -1);
+
+    forAll(d, i)
+    {
+        // Avoid elements outside of the bin
+        bool faceInside = true;
+        for (direction j = 0; j < vector::nComponents; ++j)
+        {
+            if (d[i][j] < binMinMax_[j][0] || d[i][j] > binMinMax_[j][1])
+            {
+                faceInside = false;
+                break;
+            }
+        }
+
+        if (faceInside)
+        {
+            // Find the bin division corresponding to the element
+            Vector<label> n(Zero);
+            for (direction j = 0; j < vector::nComponents; ++j)
+            {
+                n[j] = floor((d[i][j] - binMinMax_[j][0])/binW_[j]);
+                n[j] = min(max(n[j], 0), nBins_[j] - 1);
+            }
+
+            // Order: (e1, e2, e3), the first varies the fastest
+            binIndices[i] = n[0] + nBins_[0]*n[1] + nBins_[0]*nBins_[1]*n[2];
+        }
+        else
+        {
+            binIndices[i] = -1;
+        }
+    }
+
+    return binIndices;
+}
+
+
+void Foam::binModels::uniformBin::setBinsAddressing()
+{
+    faceToBin_.setSize(mesh_.nBoundaryFaces());
+    faceToBin_ = -1;
+
+    forAllIters(patchSet_, iter)
+    {
+        const polyPatch& pp = mesh_.boundaryMesh()[iter()];
+        const label i0 = pp.start() - mesh_.nInternalFaces();
+
+        SubList<label>(faceToBin_, pp.size(), i0) =
+            binAddr(coordSysPtr_->localPosition(pp.faceCentres()));
+    }
+
+    cellToBin_.setSize(mesh_.nCells());
+    cellToBin_ = -1;
+
+    for (const label zonei : cellZoneIDs_)
+    {
+        const cellZone& cZone = mesh_.cellZones()[zonei];
+        labelList bins
+        (
+            binAddr(coordSysPtr_->localPosition(vectorField(mesh_.C(), cZone)))
+        );
+
+        forAll(cZone, i)
+        {
+            const label celli = cZone[i];
+            cellToBin_[celli] = bins[i];
+        }
+    }
+}
+
+
+// * * * * * * * * * * * * * * * * Constructors  * * * * * * * * * * * * * * //
+
+Foam::binModels::uniformBin::uniformBin
+(
+    const dictionary& dict,
+    const fvMesh& mesh,
+    const word& outputPrefix
+)
+:
+    binModel(dict, mesh, outputPrefix),
+    nBins_(Zero),
+    binW_(Zero),
+    binMinMax_
+    (
+        vector2D(GREAT, GREAT),
+        vector2D(GREAT, GREAT),
+        vector2D(GREAT, GREAT)
+    )
+{
+    read(dict);
+}
+
+
+// * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * * //
+
+bool Foam::binModels::uniformBin::read(const dictionary& dict)
+{
+    if (!binModel::read(dict))
+    {
+        return false;
+    }
+
+    Info<< "    Activating a set of uniform bins" << endl;
+
+    const dictionary& binDict = dict.subDict("binData");
+
+    nBins_ = binDict.get<Vector<label>>("nBin");
+
+    for (const label n : nBins_)
+    {
+        nBin_ *= n;
+    }
+
+    if (nBin_ <= 0)
+    {
+        FatalIOErrorInFunction(binDict)
+            << "Number of bins must be greater than zero" << nl
+            << "    e1 bins = " << nBins_[0] << nl
+            << "    e2 bins = " << nBins_[1] << nl
+            << "    e3 bins = " << nBins_[2]
+            << exit(FatalIOError);
+    }
+
+    Info<< "    - Employing:" << nl
+        << "        " << nBins_[0] << " e1 bins," << nl
+        << "        " << nBins_[1] << " e2 bins," << nl
+        << "        " << nBins_[2] << " e3 bins"
+        << endl;
+
+    cumulative_ = binDict.getOrDefault<bool>("cumulative", false);
+    Info<< "    - cumulative    : " << cumulative_ << endl;
+    Info<< "    - decomposePatchValues    : " << decomposePatchValues_ << endl;
+
+    if (binDict.found("minMax"))
+    {
+        const dictionary& minMaxDict = binDict.subDict("minMax");
+
+        for (direction i = 0; i < vector::nComponents; ++i)
+        {
+            const word ei("e" + Foam::name(i));
+
+            if (minMaxDict.readIfPresent(ei, binMinMax_[i]))
+            {
+                Info<< "    - " << ei << " min        : "
+                    << binMinMax_[i][0] << nl
+                    << "    - " << ei << " max        : "
+                    << binMinMax_[i][1] << endl;
+            }
+        }
+    }
+    Info<< endl;
+
+    initialise();
+
+    return true;
+}
+
+
+void Foam::binModels::uniformBin::apply()
+{
+    forAll(fieldNames_, i)
+    {
+        const bool ok =
+            processField<scalar>(i)
+         || processField<vector>(i)
+         || processField<sphericalTensor>(i)
+         || processField<symmTensor>(i)
+         || processField<tensor>(i);
+
+        if (!ok)
+        {
+            WarningInFunction
+                << "Unable to find field " << fieldNames_[i]
+                << endl;
+        }
+    }
+
+    writtenHeader_ = true;
+}
+
+
+void Foam::binModels::uniformBin::updateMesh(const mapPolyMesh& mpm)
+{}
+
+
+void Foam::binModels::uniformBin::movePoints(const polyMesh& mesh)
+{
+    setBinsAddressing();
+}
+
+
+// ************************************************************************* //
diff --git a/src/functionObjects/field/binField/binModels/uniformBin/uniformBin.H b/src/functionObjects/field/binField/binModels/uniformBin/uniformBin.H
new file mode 100644
index 00000000000..bbc043b6dae
--- /dev/null
+++ b/src/functionObjects/field/binField/binModels/uniformBin/uniformBin.H
@@ -0,0 +1,222 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
+   \\    /   O peration     |
+    \\  /    A nd           | www.openfoam.com
+     \\/     M anipulation  |
+-------------------------------------------------------------------------------
+    Copyright (C) 2021-2022 OpenCFD Ltd.
+-------------------------------------------------------------------------------
+License
+    This file is part of OpenFOAM.
+
+    OpenFOAM is free software: you can redistribute it and/or modify it
+    under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    OpenFOAM is distributed in the hope that it will be useful, but WITHOUT
+    ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+    FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+    for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with OpenFOAM.  If not, see <http://www.gnu.org/licenses/>.
+
+Class
+    Foam::binModels::uniformBin
+
+Description
+    Calculates binned data in multiple segments according to
+    a specified Cartesian or cylindrical coordinate system.
+
+Usage
+    Minimal example by using \c system/controlDict.functions:
+    \verbatim
+    binField1
+    {
+        // Other binField entries
+        ...
+
+        // Mandatory entries
+        binModel          uniformBin;
+
+        binData
+        {
+            // Mandatory entries
+            nBin          <Vector<label>>;
+
+            // Optional entries
+            cumulative    <bool>;
+            minMax
+            {
+                e1        (<scalar> <scalar>); // (min max);
+                e2        (<scalar> <scalar>);
+                e3        (<scalar> <scalar>);
+            }
+        }
+    }
+    \endverbatim
+
+    where the entries mean:
+    \table
+      Property  | Description                           | Type   | Reqd | Deflt
+      binModel  | Type name: uniformBin                 | word   | yes  | -
+      binData   | Entries of the chosen bin model       | dict   | yes  | -
+      nBin      | Numbers of bins in specified directions | Vector\<label\> <!--
+                -->                                              | yes  | -
+      cumulative | Flag to bin data accumulated with increasing distance   <!--
+                --> in binning direction                | bool   | no   | false
+      minMax     | Min-max bounds in binning directions with respect to    <!--
+                --> the coordinateSystem's origin       | dict   | no   | -
+    \endtable
+
+Note
+  - The order of bin numbering is (e1, e2, e3), where the first
+    varies the fastest. For example, for a cylindrical bin with
+    \f$ nBin = (radial, azimuth, height) = (2, 4, 2) \f$, the bin indices
+    may look like as follows - note the counterclockwise increments:
+    \verbatim
+                           |-------------------|
+                           | 12 |         | 14 |
+                                | 11 | 13 |
+                                | 9  | 15 |
+                           | 10 |         | 16 |
+                           |-------------------|
+                          /    /         /    /
+                         /    /         /    /
+                        |-------------------|
+                        | 4  |         | 6  |
+                             | 3  | 5  |
+                             | 1  | 7  |
+                        | 2  |         | 8  |
+                        |-------------------|
+    \endverbatim
+
+SourceFiles
+    uniformBin.C
+    uniformBinTemplates.C
+
+\*---------------------------------------------------------------------------*/
+
+#ifndef Foam_binModels_uniformBin_H
+#define Foam_binModels_uniformBin_H
+
+#include "binModel.H"
+#include "writeFile.H"
+#include "coordinateSystem.H"
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+namespace Foam
+{
+namespace binModels
+{
+
+/*---------------------------------------------------------------------------*\
+                   Class uniformBin Declaration
+\*---------------------------------------------------------------------------*/
+
+class uniformBin
+:
+    public binModel
+{
+protected:
+
+    // Protected Data
+
+        //- Numbers of bins in binning directions
+        Vector<label> nBins_;
+
+        //- Equidistant bin widths in binning directions
+        vector binW_;
+
+        //- Min-max bounds of bins in binning directions
+        Vector<vector2D> binMinMax_;
+
+        //- Face index to bin index addressing
+        labelList faceToBin_;
+
+        //- Cell index to bin index addressing
+        labelList cellToBin_;
+
+
+    // Protected Member Functions
+
+        //- Write header for an binned-data file
+        template<class Type>
+        void writeFileHeader(OFstream& os) const;
+
+        //- Initialise bin properties
+        virtual void initialise();
+
+        //- Return list of bin indices corresponding to positions given by d
+        virtual labelList binAddr(const vectorField& d) const;
+
+        //- Set/cache the bin addressing
+        virtual void setBinsAddressing();
+
+        //- Apply the binning to field fieldi
+        template<class Type>
+        bool processField(const label fieldi);
+
+
+public:
+
+    //- Runtime type information
+    TypeName("uniformBin");
+
+
+    // Constructors
+
+        //- Construct from components
+        uniformBin
+        (
+            const dictionary& dict,
+            const fvMesh& mesh,
+            const word& outputPrefix
+        );
+
+        //- No copy construct
+        uniformBin(const uniformBin&) = delete;
+
+        //- No copy assignment
+        void operator=(const uniformBin&) = delete;
+
+
+    //- Destructor
+    virtual ~uniformBin() = default;
+
+
+    // Member Functions
+
+        //- Read the dictionary
+        virtual bool read(const dictionary& dict);
+
+        //- Apply bins
+        virtual void apply();
+
+        //- Update for changes of mesh
+        virtual void updateMesh(const mapPolyMesh& mpm);
+
+        //- Update for changes of mesh
+        virtual void movePoints(const polyMesh& mesh);
+};
+
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+#ifdef NoRepository
+    #include "uniformBinTemplates.C"
+#endif
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+} // End namespace binModels
+} // End namespace Foam
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+#endif
+
+// ************************************************************************* //
diff --git a/src/functionObjects/field/binField/binModels/uniformBin/uniformBinTemplates.C b/src/functionObjects/field/binField/binModels/uniformBin/uniformBinTemplates.C
new file mode 100644
index 00000000000..8f7c1c103e1
--- /dev/null
+++ b/src/functionObjects/field/binField/binModels/uniformBin/uniformBinTemplates.C
@@ -0,0 +1,178 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
+   \\    /   O peration     |
+    \\  /    A nd           | www.openfoam.com
+     \\/     M anipulation  |
+-------------------------------------------------------------------------------
+    Copyright (C) 2021-2022 OpenCFD Ltd.
+-------------------------------------------------------------------------------
+License
+    This file is part of OpenFOAM.
+
+    OpenFOAM is free software: you can redistribute it and/or modify it
+    under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    OpenFOAM is distributed in the hope that it will be useful, but WITHOUT
+    ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+    FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+    for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with OpenFOAM.  If not, see <http://www.gnu.org/licenses/>.
+
+\*---------------------------------------------------------------------------*/
+
+// * * * * * * * * * * * * Protected Member Functions  * * * * * * * * * * * //
+
+template<class Type>
+void Foam::binModels::uniformBin::writeFileHeader
+(
+    OFstream& os
+) const
+{
+    writeHeader(os, "bins");
+
+    const tensor& R = coordSysPtr_->R();
+    for (direction i = 0; i < vector::nComponents; ++i)
+    {
+        writeHeaderValue(os, "e" + Foam::name(i) + " bins", nBins_[i]);
+        writeHeaderValue(os, "    start", binMinMax_[i][0]);
+        writeHeaderValue(os, "    end", binMinMax_[i][1]);
+        writeHeaderValue(os, "    delta", binW_[i]);
+        writeHeaderValue(os, "    direction", R.col(i));
+    }
+    writeCommented(os, "bin end co-ordinates:");
+    os  << nl;
+
+    // Compute and print bin end points in binning directions
+    for (direction i = 0; i < vector::nComponents; ++i)
+    {
+        scalar binEnd = binMinMax_[i][0];
+
+        writeCommented(os, "e"+Foam::name(i)+" co-ords   :");
+        for (label j = 0; j < nBins_[i]; ++j)
+        {
+            binEnd += binW_[i];
+            os  << tab << binEnd;
+        }
+        os  << nl;
+    }
+
+    writeHeader(os, "");
+    writeCommented(os, "Time");
+
+    for (label i = 0; i < nBin_; ++i)
+    {
+        const word ibin(Foam::name(i) + ':');
+        writeTabbed(os, writeComponents<Type>("total" + ibin));
+        writeTabbed(os, writeComponents<Type>("internal" + ibin));
+
+        if (decomposePatchValues_)
+        {
+            writeTabbed(os, writeComponents<Type>("normal" + ibin));
+            writeTabbed(os, writeComponents<Type>("tangential" + ibin));
+        }
+        else
+        {
+            writeTabbed(os, writeComponents<Type>("patch" + ibin));
+        }
+    }
+
+    os  << endl;
+}
+
+template<class Type>
+bool Foam::binModels::uniformBin::processField(const label fieldi)
+{
+    const word& fieldName = fieldNames_[fieldi];
+
+    typedef GeometricField<Type, fvPatchField, volMesh> VolFieldType;
+
+    const VolFieldType* fieldPtr = mesh_.findObject<VolFieldType>(fieldName);
+
+    if (!fieldPtr)
+    {
+        return false;
+    }
+
+    if (Pstream::master() && !writtenHeader_)
+    {
+        writeFileHeader<Type>(filePtrs_[fieldi]);
+    }
+
+    const VolFieldType& fld = *fieldPtr;
+
+    // Total number of fields
+    //
+    // 0: internal
+    // 1: patch total
+    //
+    // OR
+    //
+    // 0: internal
+    // 1: patch normal
+    // 2: patch tangential
+    label nField = 2;
+    if (decomposePatchValues_)
+    {
+        nField += 1;
+    }
+
+    List<List<Type>> data(nField);
+    for (auto& binList : data)
+    {
+        binList.setSize(nBin_, Zero);
+    }
+
+    for (const label zonei : cellZoneIDs_)
+    {
+        const cellZone& cZone = mesh_.cellZones()[zonei];
+
+        for (const label celli : cZone)
+        {
+            const label bini = cellToBin_[celli];
+
+            if (bini != -1)
+            {
+                data[0][bini] += fld[celli];
+            }
+        }
+    }
+
+    forAllIters(patchSet_, iter)
+    {
+        const label patchi = iter();
+        const polyPatch& pp = mesh_.boundaryMesh()[patchi];
+        const vectorField np(mesh_.boundary()[patchi].nf());
+
+        forAll(pp, facei)
+        {
+            const label localFacei =
+                pp.start() - mesh_.nInternalFaces() + facei;
+            const label bini = faceToBin_[localFacei];
+
+            if (bini != -1)
+            {
+                const Type& v = fld.boundaryField()[patchi][facei];
+
+                if (!decomposePatchValues(data, bini, v, np[facei]))
+                {
+                    data[1][bini] += v;
+                }
+            }
+        }
+    }
+
+    if (Pstream::master())
+    {
+        writeBinnedData(data, filePtrs_[fieldi]);
+    }
+
+    return true;
+}
+
+
+// ************************************************************************* //
-- 
GitLab