From 0a561e1ebf3d7439a723792c1efceba9b474185f Mon Sep 17 00:00:00 2001
From: Christoph Wilms <christoph.wilms@ptb.de>
Date: Tue, 21 Jan 2025 10:52:47 +0100
Subject: [PATCH] [Feature] Added CloudFunctionObjects to calculate particle
 density and map Lagrangian fields to Eulerian fields

---
 .../intermediate/lnInclude/ChargingModel.C    |   1 -
 .../intermediate/lnInclude/ChargingModel.H    |   1 -
 .../intermediate/lnInclude/ChargingModelNew.C |   1 -
 .../intermediate/lnInclude/NoCharging.C       |   1 -
 .../intermediate/lnInclude/NoCharging.H       |   1 -
 .../lnInclude/NoParticleParticle.C            |   1 -
 .../lnInclude/NoParticleParticle.H            |   1 -
 .../intermediate/lnInclude/PairCharging.C     |   1 -
 .../intermediate/lnInclude/PairCharging.H     |   1 -
 .../intermediate/lnInclude/ParticleDensity.C  |   1 +
 .../intermediate/lnInclude/ParticleDensity.H  |   1 +
 .../intermediate/lnInclude/ParticleFieldMap.C |   1 +
 .../intermediate/lnInclude/ParticleFieldMap.H |   1 +
 .../lnInclude/ParticleParticleModel.C         |   1 -
 .../lnInclude/ParticleParticleModel.H         |   1 -
 .../lnInclude/ParticleParticleModelNew.C      |   1 -
 .../lnInclude/ParticleParticleRandom.C        |   1 -
 .../lnInclude/ParticleParticleRandom.H        |   1 -
 .../lnInclude/ParticleWallModel.C             |   1 -
 .../lnInclude/ParticleWallModel.H             |   1 -
 .../lnInclude/ParticleWallModelNew.C          |   1 -
 .../lnInclude/ParticleWallRandom.C            |   1 -
 .../lnInclude/ParticleWallRandom.H            |   1 -
 .../include/makeParcelCloudFunctionObjects.H  |   4 +
 .../makeReactingParcelCloudFunctionObjects.H  |   4 +
 .../makeThermoParcelCloudFunctionObjects.H    |   4 +
 .../ParticleDensity/ParticleDensity.C         | 143 ++++++++
 .../ParticleDensity/ParticleDensity.H         | 155 +++++++++
 .../ParticleFieldMap/ParticleFieldMap.C       | 327 ++++++++++++++++++
 .../ParticleFieldMap/ParticleFieldMap.H       | 168 +++++++++
 tutorials/RANS/bendPipe_2D/Allrun             |   1 +
 .../constant/kinematicCloudProperties         |  17 +-
 tutorials/RANS/bendPipe_2D/system/controlDict |   1 +
 .../RANS/bendPipe_2D/system/fieldAverage      |  49 +++
 34 files changed, 876 insertions(+), 20 deletions(-)
 delete mode 120000 src/lagrangian/intermediate/lnInclude/ChargingModel.C
 delete mode 120000 src/lagrangian/intermediate/lnInclude/ChargingModel.H
 delete mode 120000 src/lagrangian/intermediate/lnInclude/ChargingModelNew.C
 delete mode 120000 src/lagrangian/intermediate/lnInclude/NoCharging.C
 delete mode 120000 src/lagrangian/intermediate/lnInclude/NoCharging.H
 delete mode 120000 src/lagrangian/intermediate/lnInclude/NoParticleParticle.C
 delete mode 120000 src/lagrangian/intermediate/lnInclude/NoParticleParticle.H
 delete mode 120000 src/lagrangian/intermediate/lnInclude/PairCharging.C
 delete mode 120000 src/lagrangian/intermediate/lnInclude/PairCharging.H
 create mode 120000 src/lagrangian/intermediate/lnInclude/ParticleDensity.C
 create mode 120000 src/lagrangian/intermediate/lnInclude/ParticleDensity.H
 create mode 120000 src/lagrangian/intermediate/lnInclude/ParticleFieldMap.C
 create mode 120000 src/lagrangian/intermediate/lnInclude/ParticleFieldMap.H
 delete mode 120000 src/lagrangian/intermediate/lnInclude/ParticleParticleModel.C
 delete mode 120000 src/lagrangian/intermediate/lnInclude/ParticleParticleModel.H
 delete mode 120000 src/lagrangian/intermediate/lnInclude/ParticleParticleModelNew.C
 delete mode 120000 src/lagrangian/intermediate/lnInclude/ParticleParticleRandom.C
 delete mode 120000 src/lagrangian/intermediate/lnInclude/ParticleParticleRandom.H
 delete mode 120000 src/lagrangian/intermediate/lnInclude/ParticleWallModel.C
 delete mode 120000 src/lagrangian/intermediate/lnInclude/ParticleWallModel.H
 delete mode 120000 src/lagrangian/intermediate/lnInclude/ParticleWallModelNew.C
 delete mode 120000 src/lagrangian/intermediate/lnInclude/ParticleWallRandom.C
 delete mode 120000 src/lagrangian/intermediate/lnInclude/ParticleWallRandom.H
 create mode 100644 src/lagrangian/intermediate/submodels/CloudFunctionObjects/ParticleDensity/ParticleDensity.C
 create mode 100644 src/lagrangian/intermediate/submodels/CloudFunctionObjects/ParticleDensity/ParticleDensity.H
 create mode 100644 src/lagrangian/intermediate/submodels/CloudFunctionObjects/ParticleFieldMap/ParticleFieldMap.C
 create mode 100644 src/lagrangian/intermediate/submodels/CloudFunctionObjects/ParticleFieldMap/ParticleFieldMap.H
 create mode 100644 tutorials/RANS/bendPipe_2D/system/fieldAverage

diff --git a/src/lagrangian/intermediate/lnInclude/ChargingModel.C b/src/lagrangian/intermediate/lnInclude/ChargingModel.C
deleted file mode 120000
index 3055b03..0000000
--- a/src/lagrangian/intermediate/lnInclude/ChargingModel.C
+++ /dev/null
@@ -1 +0,0 @@
-../submodels/Kinematic/test_chargingModel/ChargingModel/ChargingModel.C
\ No newline at end of file
diff --git a/src/lagrangian/intermediate/lnInclude/ChargingModel.H b/src/lagrangian/intermediate/lnInclude/ChargingModel.H
deleted file mode 120000
index e293434..0000000
--- a/src/lagrangian/intermediate/lnInclude/ChargingModel.H
+++ /dev/null
@@ -1 +0,0 @@
-../submodels/Kinematic/test_chargingModel/ChargingModel/ChargingModel.H
\ No newline at end of file
diff --git a/src/lagrangian/intermediate/lnInclude/ChargingModelNew.C b/src/lagrangian/intermediate/lnInclude/ChargingModelNew.C
deleted file mode 120000
index 4c87c81..0000000
--- a/src/lagrangian/intermediate/lnInclude/ChargingModelNew.C
+++ /dev/null
@@ -1 +0,0 @@
-../submodels/Kinematic/test_chargingModel/ChargingModel/ChargingModelNew.C
\ No newline at end of file
diff --git a/src/lagrangian/intermediate/lnInclude/NoCharging.C b/src/lagrangian/intermediate/lnInclude/NoCharging.C
deleted file mode 120000
index c7e9359..0000000
--- a/src/lagrangian/intermediate/lnInclude/NoCharging.C
+++ /dev/null
@@ -1 +0,0 @@
-../submodels/Kinematic/test_chargingModel/NoCharging/NoCharging.C
\ No newline at end of file
diff --git a/src/lagrangian/intermediate/lnInclude/NoCharging.H b/src/lagrangian/intermediate/lnInclude/NoCharging.H
deleted file mode 120000
index 0c3d494..0000000
--- a/src/lagrangian/intermediate/lnInclude/NoCharging.H
+++ /dev/null
@@ -1 +0,0 @@
-../submodels/Kinematic/test_chargingModel/NoCharging/NoCharging.H
\ No newline at end of file
diff --git a/src/lagrangian/intermediate/lnInclude/NoParticleParticle.C b/src/lagrangian/intermediate/lnInclude/NoParticleParticle.C
deleted file mode 120000
index b607d32..0000000
--- a/src/lagrangian/intermediate/lnInclude/NoParticleParticle.C
+++ /dev/null
@@ -1 +0,0 @@
-../submodels/Kinematic/test_chargingModel/PairCharging/ParticleParticleModel/NoParticleParticle/NoParticleParticle.C
\ No newline at end of file
diff --git a/src/lagrangian/intermediate/lnInclude/NoParticleParticle.H b/src/lagrangian/intermediate/lnInclude/NoParticleParticle.H
deleted file mode 120000
index 14052fc..0000000
--- a/src/lagrangian/intermediate/lnInclude/NoParticleParticle.H
+++ /dev/null
@@ -1 +0,0 @@
-../submodels/Kinematic/test_chargingModel/PairCharging/ParticleParticleModel/NoParticleParticle/NoParticleParticle.H
\ No newline at end of file
diff --git a/src/lagrangian/intermediate/lnInclude/PairCharging.C b/src/lagrangian/intermediate/lnInclude/PairCharging.C
deleted file mode 120000
index e86b878..0000000
--- a/src/lagrangian/intermediate/lnInclude/PairCharging.C
+++ /dev/null
@@ -1 +0,0 @@
-../submodels/Kinematic/test_chargingModel/PairCharging/PairCharging.C
\ No newline at end of file
diff --git a/src/lagrangian/intermediate/lnInclude/PairCharging.H b/src/lagrangian/intermediate/lnInclude/PairCharging.H
deleted file mode 120000
index 61c5bfd..0000000
--- a/src/lagrangian/intermediate/lnInclude/PairCharging.H
+++ /dev/null
@@ -1 +0,0 @@
-../submodels/Kinematic/test_chargingModel/PairCharging/PairCharging.H
\ No newline at end of file
diff --git a/src/lagrangian/intermediate/lnInclude/ParticleDensity.C b/src/lagrangian/intermediate/lnInclude/ParticleDensity.C
new file mode 120000
index 0000000..e252c48
--- /dev/null
+++ b/src/lagrangian/intermediate/lnInclude/ParticleDensity.C
@@ -0,0 +1 @@
+../submodels/CloudFunctionObjects/ParticleDensity/ParticleDensity.C
\ No newline at end of file
diff --git a/src/lagrangian/intermediate/lnInclude/ParticleDensity.H b/src/lagrangian/intermediate/lnInclude/ParticleDensity.H
new file mode 120000
index 0000000..03b9432
--- /dev/null
+++ b/src/lagrangian/intermediate/lnInclude/ParticleDensity.H
@@ -0,0 +1 @@
+../submodels/CloudFunctionObjects/ParticleDensity/ParticleDensity.H
\ No newline at end of file
diff --git a/src/lagrangian/intermediate/lnInclude/ParticleFieldMap.C b/src/lagrangian/intermediate/lnInclude/ParticleFieldMap.C
new file mode 120000
index 0000000..576c1af
--- /dev/null
+++ b/src/lagrangian/intermediate/lnInclude/ParticleFieldMap.C
@@ -0,0 +1 @@
+../submodels/CloudFunctionObjects/ParticleFieldMap/ParticleFieldMap.C
\ No newline at end of file
diff --git a/src/lagrangian/intermediate/lnInclude/ParticleFieldMap.H b/src/lagrangian/intermediate/lnInclude/ParticleFieldMap.H
new file mode 120000
index 0000000..fc67919
--- /dev/null
+++ b/src/lagrangian/intermediate/lnInclude/ParticleFieldMap.H
@@ -0,0 +1 @@
+../submodels/CloudFunctionObjects/ParticleFieldMap/ParticleFieldMap.H
\ No newline at end of file
diff --git a/src/lagrangian/intermediate/lnInclude/ParticleParticleModel.C b/src/lagrangian/intermediate/lnInclude/ParticleParticleModel.C
deleted file mode 120000
index e21d3e4..0000000
--- a/src/lagrangian/intermediate/lnInclude/ParticleParticleModel.C
+++ /dev/null
@@ -1 +0,0 @@
-../submodels/Kinematic/test_chargingModel/PairCharging/ParticleParticleModel/ParticleParticleModel/ParticleParticleModel.C
\ No newline at end of file
diff --git a/src/lagrangian/intermediate/lnInclude/ParticleParticleModel.H b/src/lagrangian/intermediate/lnInclude/ParticleParticleModel.H
deleted file mode 120000
index dcd336e..0000000
--- a/src/lagrangian/intermediate/lnInclude/ParticleParticleModel.H
+++ /dev/null
@@ -1 +0,0 @@
-../submodels/Kinematic/test_chargingModel/PairCharging/ParticleParticleModel/ParticleParticleModel/ParticleParticleModel.H
\ No newline at end of file
diff --git a/src/lagrangian/intermediate/lnInclude/ParticleParticleModelNew.C b/src/lagrangian/intermediate/lnInclude/ParticleParticleModelNew.C
deleted file mode 120000
index db3ed6f..0000000
--- a/src/lagrangian/intermediate/lnInclude/ParticleParticleModelNew.C
+++ /dev/null
@@ -1 +0,0 @@
-../submodels/Kinematic/test_chargingModel/PairCharging/ParticleParticleModel/ParticleParticleModel/ParticleParticleModelNew.C
\ No newline at end of file
diff --git a/src/lagrangian/intermediate/lnInclude/ParticleParticleRandom.C b/src/lagrangian/intermediate/lnInclude/ParticleParticleRandom.C
deleted file mode 120000
index 7b345f3..0000000
--- a/src/lagrangian/intermediate/lnInclude/ParticleParticleRandom.C
+++ /dev/null
@@ -1 +0,0 @@
-../submodels/Kinematic/test_chargingModel/PairCharging/ParticleParticleModel/ParticleParticleRandom/ParticleParticleRandom.C
\ No newline at end of file
diff --git a/src/lagrangian/intermediate/lnInclude/ParticleParticleRandom.H b/src/lagrangian/intermediate/lnInclude/ParticleParticleRandom.H
deleted file mode 120000
index 23e301e..0000000
--- a/src/lagrangian/intermediate/lnInclude/ParticleParticleRandom.H
+++ /dev/null
@@ -1 +0,0 @@
-../submodels/Kinematic/test_chargingModel/PairCharging/ParticleParticleModel/ParticleParticleRandom/ParticleParticleRandom.H
\ No newline at end of file
diff --git a/src/lagrangian/intermediate/lnInclude/ParticleWallModel.C b/src/lagrangian/intermediate/lnInclude/ParticleWallModel.C
deleted file mode 120000
index c8f0a09..0000000
--- a/src/lagrangian/intermediate/lnInclude/ParticleWallModel.C
+++ /dev/null
@@ -1 +0,0 @@
-../submodels/Kinematic/test_chargingModel/PairCharging/ParticleWallModel/ParticleWallModel/ParticleWallModel.C
\ No newline at end of file
diff --git a/src/lagrangian/intermediate/lnInclude/ParticleWallModel.H b/src/lagrangian/intermediate/lnInclude/ParticleWallModel.H
deleted file mode 120000
index 0e15e11..0000000
--- a/src/lagrangian/intermediate/lnInclude/ParticleWallModel.H
+++ /dev/null
@@ -1 +0,0 @@
-../submodels/Kinematic/test_chargingModel/PairCharging/ParticleWallModel/ParticleWallModel/ParticleWallModel.H
\ No newline at end of file
diff --git a/src/lagrangian/intermediate/lnInclude/ParticleWallModelNew.C b/src/lagrangian/intermediate/lnInclude/ParticleWallModelNew.C
deleted file mode 120000
index a433520..0000000
--- a/src/lagrangian/intermediate/lnInclude/ParticleWallModelNew.C
+++ /dev/null
@@ -1 +0,0 @@
-../submodels/Kinematic/test_chargingModel/PairCharging/ParticleWallModel/ParticleWallModel/ParticleWallModelNew.C
\ No newline at end of file
diff --git a/src/lagrangian/intermediate/lnInclude/ParticleWallRandom.C b/src/lagrangian/intermediate/lnInclude/ParticleWallRandom.C
deleted file mode 120000
index 0b41ee5..0000000
--- a/src/lagrangian/intermediate/lnInclude/ParticleWallRandom.C
+++ /dev/null
@@ -1 +0,0 @@
-../submodels/Kinematic/test_chargingModel/PairCharging/ParticleWallModel/ParticleWallRandom/ParticleWallRandom.C
\ No newline at end of file
diff --git a/src/lagrangian/intermediate/lnInclude/ParticleWallRandom.H b/src/lagrangian/intermediate/lnInclude/ParticleWallRandom.H
deleted file mode 120000
index 0945b57..0000000
--- a/src/lagrangian/intermediate/lnInclude/ParticleWallRandom.H
+++ /dev/null
@@ -1 +0,0 @@
-../submodels/Kinematic/test_chargingModel/PairCharging/ParticleWallModel/ParticleWallRandom/ParticleWallRandom.H
\ No newline at end of file
diff --git a/src/lagrangian/intermediate/parcels/include/makeParcelCloudFunctionObjects.H b/src/lagrangian/intermediate/parcels/include/makeParcelCloudFunctionObjects.H
index cec8ed0..a147389 100644
--- a/src/lagrangian/intermediate/parcels/include/makeParcelCloudFunctionObjects.H
+++ b/src/lagrangian/intermediate/parcels/include/makeParcelCloudFunctionObjects.H
@@ -34,7 +34,9 @@ License
 #include "FaceInteraction.H"
 #include "FacePostProcessing.H"
 #include "ParticleCollector.H"
+#include "ParticleDensity.H"
 #include "ParticleErosion.H"
+#include "ParticleFieldMap.H"
 #include "ParticleTracks.H"
 #include "ParticleTrap.H"
 #include "ParticleZoneInfo.H"
@@ -59,7 +61,9 @@ License
     makeCloudFunctionObjectType(FaceInteraction, CloudType);                   \
     makeCloudFunctionObjectType(FacePostProcessing, CloudType);                \
     makeCloudFunctionObjectType(ParticleCollector, CloudType);                 \
+    makeCloudFunctionObjectType(ParticleDensity, CloudType);                   \
     makeCloudFunctionObjectType(ParticleErosion, CloudType);                   \
+    makeCloudFunctionObjectType(ParticleFieldMap, CloudType);                  \
     makeCloudFunctionObjectType(ParticleTracks, CloudType);                    \
     makeCloudFunctionObjectType(ParticleTrap, CloudType);                      \
     makeCloudFunctionObjectType(ParticleZoneInfo, CloudType);                  \
diff --git a/src/lagrangian/intermediate/parcels/include/makeReactingParcelCloudFunctionObjects.H b/src/lagrangian/intermediate/parcels/include/makeReactingParcelCloudFunctionObjects.H
index 05f4088..d16e008 100644
--- a/src/lagrangian/intermediate/parcels/include/makeReactingParcelCloudFunctionObjects.H
+++ b/src/lagrangian/intermediate/parcels/include/makeReactingParcelCloudFunctionObjects.H
@@ -34,7 +34,9 @@ License
 #include "FaceInteraction.H"
 #include "FacePostProcessing.H"
 #include "ParticleCollector.H"
+#include "ParticleDensity.H"
 #include "ParticleErosion.H"
+#include "ParticleFieldMap.H"
 #include "ParticleTracks.H"
 #include "ParticleTrap.H"
 #include "ParticleZoneInfo.H"
@@ -61,7 +63,9 @@ License
     makeCloudFunctionObjectType(FaceInteraction, CloudType);                   \
     makeCloudFunctionObjectType(FacePostProcessing, CloudType);                \
     makeCloudFunctionObjectType(ParticleCollector, CloudType);                 \
+    makeCloudFunctionObjectType(ParticleDensity, CloudType);                   \
     makeCloudFunctionObjectType(ParticleErosion, CloudType);                   \
+    makeCloudFunctionObjectType(ParticleFieldMap, CloudType);                  \
     makeCloudFunctionObjectType(ParticleTracks, CloudType);                    \
     makeCloudFunctionObjectType(ParticleTrap, CloudType);                      \
     makeCloudFunctionObjectType(ParticleZoneInfo, CloudType);                  \
diff --git a/src/lagrangian/intermediate/parcels/include/makeThermoParcelCloudFunctionObjects.H b/src/lagrangian/intermediate/parcels/include/makeThermoParcelCloudFunctionObjects.H
index 5f5d5c0..967b902 100644
--- a/src/lagrangian/intermediate/parcels/include/makeThermoParcelCloudFunctionObjects.H
+++ b/src/lagrangian/intermediate/parcels/include/makeThermoParcelCloudFunctionObjects.H
@@ -33,7 +33,9 @@ License
 #include "FaceInteraction.H"
 #include "FacePostProcessing.H"
 #include "ParticleCollector.H"
+#include "ParticleDensity.H"
 #include "ParticleErosion.H"
+#include "ParticleFieldMap.H"
 #include "ParticleTracks.H"
 #include "ParticleTrap.H"
 #include "ParticleZoneInfo.H"
@@ -59,7 +61,9 @@ License
     makeCloudFunctionObjectType(FaceInteraction, CloudType);                   \
     makeCloudFunctionObjectType(FacePostProcessing, CloudType);                \
     makeCloudFunctionObjectType(ParticleCollector, CloudType);                 \
+    makeCloudFunctionObjectType(ParticleDensity, CloudType);                   \
     makeCloudFunctionObjectType(ParticleErosion, CloudType);                   \
+    makeCloudFunctionObjectType(ParticleFieldMap, CloudType);                  \
     makeCloudFunctionObjectType(ParticleTracks, CloudType);                    \
     makeCloudFunctionObjectType(ParticleTrap, CloudType);                      \
     makeCloudFunctionObjectType(ParticleZoneInfo, CloudType);                  \
diff --git a/src/lagrangian/intermediate/submodels/CloudFunctionObjects/ParticleDensity/ParticleDensity.C b/src/lagrangian/intermediate/submodels/CloudFunctionObjects/ParticleDensity/ParticleDensity.C
new file mode 100644
index 0000000..78aaa22
--- /dev/null
+++ b/src/lagrangian/intermediate/submodels/CloudFunctionObjects/ParticleDensity/ParticleDensity.C
@@ -0,0 +1,143 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
+   \\    /   O peration     |
+    \\  /    A nd           | www.openfoam.com
+     \\/     M anipulation  |
+-------------------------------------------------------------------------------
+    Copyright (C) 2011-2017 OpenFOAM Foundation
+    Copyright (C) 2020 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 "ParticleDensity.H"
+
+// * * * * * * * * * * * * * Protected Member Functions  * * * * * * * * * * //
+
+template<class CloudType>
+void Foam::ParticleDensity<CloudType>::write()
+{
+    if (thetaPtr_)
+    {
+        thetaPtr_->write();
+    }
+    else
+    {
+        FatalErrorInFunction
+            << "thetaPtr not valid" << abort(FatalError);
+    }
+}
+
+
+// * * * * * * * * * * * * * * * * Constructors  * * * * * * * * * * * * * * //
+
+template<class CloudType>
+Foam::ParticleDensity<CloudType>::ParticleDensity
+(
+    const dictionary& dict,
+    CloudType& owner,
+    const word& modelName
+)
+:
+    CloudFunctionObject<CloudType>(dict, owner, modelName, typeName),
+    thetaPtr_(nullptr)
+{}
+
+
+template<class CloudType>
+Foam::ParticleDensity<CloudType>::ParticleDensity
+(
+    const ParticleDensity<CloudType>& vf
+)
+:
+    CloudFunctionObject<CloudType>(vf),
+    thetaPtr_(nullptr)
+{}
+
+
+// * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //
+
+template<class CloudType>
+void Foam::ParticleDensity<CloudType>::preEvolve
+(
+    const typename parcelType::trackingData& td
+)
+{
+    if (thetaPtr_)
+    {
+        thetaPtr_->primitiveFieldRef() = 0.0;
+    }
+    else
+    {
+        const fvMesh& mesh = this->owner().mesh();
+
+        thetaPtr_.reset
+        (
+            new volScalarField
+            (
+                IOobject
+                (
+                    this->owner().name() + "ParticleDensity",
+                    mesh.time().timeName(),
+                    mesh,
+                    IOobject::NO_READ,
+                    IOobject::NO_WRITE
+                ),
+                mesh,
+                dimensionedScalar(dimless, Zero)
+            )
+        );
+    }
+}
+
+
+template<class CloudType>
+void Foam::ParticleDensity<CloudType>::postEvolve
+(
+    const typename parcelType::trackingData& td
+)
+{
+    volScalarField& theta = thetaPtr_();
+
+    const fvMesh& mesh = this->owner().mesh();
+
+    theta.primitiveFieldRef() /= mesh.time().deltaTValue()*mesh.V();
+
+    CloudFunctionObject<CloudType>::postEvolve(td);
+}
+
+
+template<class CloudType>
+bool Foam::ParticleDensity<CloudType>::postMove
+(
+    parcelType& p,
+    const scalar dt,
+    const point&,
+    const typename parcelType::trackingData& td
+)
+{
+    volScalarField& theta = thetaPtr_();
+
+    theta[p.cell()] += dt*p.nParticle();
+
+    return true;
+}
+
+
+// ************************************************************************* //
diff --git a/src/lagrangian/intermediate/submodels/CloudFunctionObjects/ParticleDensity/ParticleDensity.H b/src/lagrangian/intermediate/submodels/CloudFunctionObjects/ParticleDensity/ParticleDensity.H
new file mode 100644
index 0000000..d745c47
--- /dev/null
+++ b/src/lagrangian/intermediate/submodels/CloudFunctionObjects/ParticleDensity/ParticleDensity.H
@@ -0,0 +1,155 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
+   \\    /   O peration     |
+    \\  /    A nd           | www.openfoam.com
+     \\/     M anipulation  |
+-------------------------------------------------------------------------------
+    Copyright (C) 2011-2017 OpenFOAM Foundation
+    Copyright (C) 2020 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::ParticleDensity
+
+Group
+    grpLagrangianIntermediateFunctionObjects
+
+Description
+    Creates particle density field on carrier phase
+
+SourceFiles
+    ParticleDensity.C
+
+\*---------------------------------------------------------------------------*/
+
+#ifndef ParticleDensity_H
+#define ParticleDensity_H
+
+#include "CloudFunctionObject.H"
+#include "volFields.H"
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+namespace Foam
+{
+
+/*---------------------------------------------------------------------------*\
+                       Class ParticleDensity Declaration
+\*---------------------------------------------------------------------------*/
+
+template<class CloudType>
+class ParticleDensity
+:
+    public CloudFunctionObject<CloudType>
+{
+    // Private Data
+
+        // Typedefs
+
+            //- Convenience typedef for parcel type
+            typedef typename CloudType::parcelType parcelType;
+
+
+        //- Void fraction field
+        autoPtr<volScalarField> thetaPtr_;
+
+
+protected:
+
+    // Protected Member Functions
+
+        //- Write post-processing info
+        virtual void write();
+
+
+public:
+
+    //- Runtime type information
+    TypeName("particleDensity");
+
+
+    // Constructors
+
+        //- Construct from dictionary
+        ParticleDensity
+        (
+            const dictionary& dict,
+            CloudType& owner,
+            const word& modelName
+        );
+
+        //- Construct copy
+        ParticleDensity(const ParticleDensity<CloudType>& vf);
+
+        //- Construct and return a clone
+        virtual autoPtr<CloudFunctionObject<CloudType>> clone() const
+        {
+            return autoPtr<CloudFunctionObject<CloudType>>
+            (
+                new ParticleDensity<CloudType>(*this)
+            );
+        }
+
+
+    //- Destructor
+    virtual ~ParticleDensity() = default;
+
+
+    // Member Functions
+
+        // Evaluation
+
+            //- Pre-evolve hook
+            virtual void preEvolve
+            (
+                const typename parcelType::trackingData& td
+            );
+
+            //- Post-evolve hook
+            virtual void postEvolve
+            (
+                const typename parcelType::trackingData& td
+            );
+
+            //- Post-move hook
+            virtual bool postMove
+            (
+                parcelType& p,
+                const scalar dt,
+                const point& position0,
+                const typename parcelType::trackingData& td
+            );
+};
+
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+} // End namespace Foam
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+#ifdef NoRepository
+    #include "ParticleDensity.C"
+#endif
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+#endif
+
+// ************************************************************************* //
diff --git a/src/lagrangian/intermediate/submodels/CloudFunctionObjects/ParticleFieldMap/ParticleFieldMap.C b/src/lagrangian/intermediate/submodels/CloudFunctionObjects/ParticleFieldMap/ParticleFieldMap.C
new file mode 100644
index 0000000..77ca751
--- /dev/null
+++ b/src/lagrangian/intermediate/submodels/CloudFunctionObjects/ParticleFieldMap/ParticleFieldMap.C
@@ -0,0 +1,327 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
+   \\    /   O peration     |
+    \\  /    A nd           | www.openfoam.com
+     \\/     M anipulation  |
+-------------------------------------------------------------------------------
+    Copyright (C) 2011-2017 OpenFOAM Foundation
+    Copyright (C) 2020 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 "ParticleFieldMap.H"
+#include <unordered_map>
+#include <functional>
+
+// * * * * * * * * * * * * * Protected Member Functions  * * * * * * * * * * //
+
+template<class CloudType>
+void Foam::ParticleFieldMap<CloudType>::write()
+{
+    if (thetaScalarPtr_)
+    {
+        thetaScalarPtr_->write();
+    }
+    else if (thetaVectorPtr_)
+    {
+        thetaVectorPtr_->write();
+    }
+    else
+    {
+        FatalErrorInFunction
+            << "thetaPtr not valid" << abort(FatalError);
+    }
+
+    if (particleCountPtr_)
+    {
+        particleCountPtr_->write();
+    }
+}
+
+
+// * * * * * * * * * * * * * * * * Constructors  * * * * * * * * * * * * * * //
+
+template<class CloudType>
+Foam::ParticleFieldMap<CloudType>::ParticleFieldMap
+(
+    const dictionary& dict,
+    CloudType& owner,
+    const word& modelName
+)
+:
+    CloudFunctionObject<CloudType>(dict, owner, modelName, typeName),
+    thetaScalarPtr_(nullptr),
+    thetaVectorPtr_(nullptr),
+    particleCountPtr_(nullptr),
+    fieldName_(dict.getString("field")),
+    statisticOperator_(dict.getString("statisticOperator")),
+    normaliseByCellVolume_(dict.getOrDefault<bool>("normaliseByCellVolume", false))
+{}
+
+
+template<class CloudType>
+Foam::ParticleFieldMap<CloudType>::ParticleFieldMap
+(
+    const ParticleFieldMap<CloudType>& vf
+)
+:
+    CloudFunctionObject<CloudType>(vf),
+    thetaScalarPtr_(nullptr),
+    thetaVectorPtr_(nullptr),
+    particleCountPtr_(nullptr)
+{}
+
+
+// * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //
+
+template<class CloudType>
+void Foam::ParticleFieldMap<CloudType>::preEvolve
+(
+    const typename parcelType::trackingData& td
+)
+{
+    if (thetaScalarPtr_)
+    {
+        thetaScalarPtr_->primitiveFieldRef() = 0.0;
+    }
+    else if (thetaVectorPtr_)
+    {
+        thetaVectorPtr_->primitiveFieldRef() = vector::zero;
+    }
+    else
+    {
+        const fvMesh& mesh = this->owner().mesh();
+
+        string statisticOperator = statisticOperator_;
+        statisticOperator[0] = toupper(statisticOperator[0]);
+
+        Info << "Map particle field: " << fieldName_ << endl;
+        Info << "Map particle field, statistical operation: " << statisticOperator << endl;
+
+        if (fieldName_ == "U" || fieldName_ == "UCorrect" || fieldName_ == "UTurb" || fieldName_ == "dragForce" || fieldName_ == "liftForce" || fieldName_ == "electricForce")
+        {
+            thetaVectorPtr_.reset
+            (
+                new volVectorField
+                (
+                    IOobject
+                    (
+                        this->owner().name() + "ParticleField" + statisticOperator + "." + fieldName_,
+                        mesh.time().timeName(),
+                        mesh,
+                        IOobject::NO_READ,
+                        IOobject::NO_WRITE
+                    ),
+                    mesh,
+                    dimensionedVector(dimless, vector::zero)
+                )
+            );
+        }
+        else
+        {
+            thetaScalarPtr_.reset
+            (
+                new volScalarField
+                (
+                    IOobject
+                    (
+                        this->owner().name() + "ParticleField" + statisticOperator + "." + fieldName_,
+                        mesh.time().timeName(),
+                        mesh,
+                        IOobject::NO_READ,
+                        IOobject::NO_WRITE
+                    ),
+                    mesh,
+                    dimensionedScalar(dimless, Zero)
+                )
+            );
+        }
+    }
+
+    // Initialize the particle count field
+    const fvMesh& mesh = this->owner().mesh();
+    particleCountPtr_.reset
+    (
+        new volScalarField
+        (
+            IOobject
+            (
+                this->owner().name() + "ParticleCount",
+                mesh.time().timeName(),
+                mesh,
+                IOobject::NO_READ,
+                IOobject::NO_WRITE
+            ),
+            mesh,
+            dimensionedScalar(dimless, Zero)
+        )
+    );
+}
+
+
+template<class CloudType>
+void Foam::ParticleFieldMap<CloudType>::postEvolve
+(
+    const typename parcelType::trackingData& td
+)
+{
+    if (thetaScalarPtr_)
+    {
+        volScalarField& theta = thetaScalarPtr_();
+        volScalarField& particleCount = particleCountPtr_();
+        const fvMesh& mesh = this->owner().mesh();
+
+        theta.primitiveFieldRef() /= mesh.time().deltaTValue();
+        particleCount.primitiveFieldRef() /= mesh.time().deltaTValue();
+        
+        if (statisticOperator_ == "average")
+        {
+            theta.primitiveFieldRef() /= (particleCount.primitiveFieldRef() + VSMALL);
+        }
+        else if (statisticOperator_ == "sum")
+        {
+            // Do nothing
+        }
+        else
+        {
+            FatalErrorInFunction
+                << "Unknown statistic operator: " << statisticOperator_
+                << abort(FatalError);
+        }
+
+	if (normaliseByCellVolume_)
+        {
+            theta.primitiveFieldRef() /= mesh.V();
+        }
+    }
+    else if (thetaVectorPtr_)
+    {
+        volVectorField& theta = thetaVectorPtr_();
+        volScalarField& particleCount = particleCountPtr_();
+        const fvMesh& mesh = this->owner().mesh();
+
+        theta.primitiveFieldRef() /= mesh.time().deltaTValue();
+        particleCount.primitiveFieldRef() /= mesh.time().deltaTValue();
+
+        if (statisticOperator_ == "average")
+        {
+            theta.primitiveFieldRef() /= (particleCount.primitiveFieldRef() + VSMALL);
+        }
+        else if (statisticOperator_ == "sum")
+        {
+            // Do nothing
+        }
+        else
+        {
+            FatalErrorInFunction
+                << "Unknown statistic operator: " << statisticOperator_
+                << abort(FatalError);
+        }
+
+	if (normaliseByCellVolume_)
+        {
+            theta.primitiveFieldRef() /= mesh.V();
+        }
+    }
+
+    CloudFunctionObject<CloudType>::postEvolve(td);
+}
+
+
+template<class CloudType>
+bool Foam::ParticleFieldMap<CloudType>::postMove
+(
+    parcelType& p,
+    const scalar dt,
+    const point&,
+    const typename parcelType::trackingData& td
+)
+{
+    volScalarField& particleCount = particleCountPtr_();
+    particleCount[p.cell()] += dt * p.nParticle();
+
+    if (thetaScalarPtr_)
+    {
+        volScalarField& theta = thetaScalarPtr_();
+        
+        scalar field = 0.0;
+
+        static const std::unordered_map<std::string, std::function<scalar(parcelType&)>> fieldAccessors{
+            {"nParticle", [](parcelType& p) { return p.nParticle(); }},
+            {"d", [](parcelType& p) { return p.d(); }},
+            {"rho", [](parcelType& p) { return p.rho(); }},
+            {"age", [](parcelType& p) { return p.age(); }},
+            {"charge", [](parcelType& p) { return p.charge(); }},
+            {"tTurb", [](parcelType& p) { return p.tTurb(); }}
+        };
+
+        // Find the field accessor
+        auto it = fieldAccessors.find(fieldName_);
+        if (it != fieldAccessors.end())
+        {
+            field = it->second(p);
+            theta[p.cell()] += dt * p.nParticle() * field;
+        }
+        else
+        {
+            FatalErrorInFunction
+                << "Unsupported field type or unknown field: " << fieldName_
+                << abort(FatalError);
+        }
+    }
+    else if (thetaVectorPtr_)
+    {
+        volVectorField& theta = thetaVectorPtr_();
+        
+        vector field = vector::zero;
+
+        static const std::unordered_map<std::string, std::function<vector(parcelType&)>> fieldAccessors{
+            {"U", [](parcelType& p) { return p.U(); }},
+            {"UCorrect", [](parcelType& p) { return p.UCorrect(); }},
+            {"UTurb", [](parcelType& p) { return p.UTurb(); }}
+        };
+
+        // Find the field accessor
+        auto it = fieldAccessors.find(fieldName_);
+        if (it != fieldAccessors.end())
+        {
+            field = it->second(p);
+            theta[p.cell()] += dt * p.nParticle() * field;
+        }
+        else
+        {
+            FatalErrorInFunction
+                << "Unsupported field type or unknown field: " << fieldName_
+                << abort(FatalError);
+        }
+    }
+    else
+    {
+        FatalErrorInFunction
+            << "Unsupported field type or unknown field: " << fieldName_
+            << abort(FatalError);
+    }
+
+    return true;
+}
+
+
+
+// ************************************************************************* //
diff --git a/src/lagrangian/intermediate/submodels/CloudFunctionObjects/ParticleFieldMap/ParticleFieldMap.H b/src/lagrangian/intermediate/submodels/CloudFunctionObjects/ParticleFieldMap/ParticleFieldMap.H
new file mode 100644
index 0000000..23159bb
--- /dev/null
+++ b/src/lagrangian/intermediate/submodels/CloudFunctionObjects/ParticleFieldMap/ParticleFieldMap.H
@@ -0,0 +1,168 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
+   \\    /   O peration     |
+    \\  /    A nd           | www.openfoam.com
+     \\/     M anipulation  |
+-------------------------------------------------------------------------------
+    Copyright (C) 2011-2017 OpenFOAM Foundation
+    Copyright (C) 2020 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::ParticleFieldMap
+
+Group
+    grpLagrangianIntermediateFunctionObjects
+
+Description
+    Maps specified particle field on carrier phase and calculates the sum or average
+
+SourceFiles
+    ParticleFieldMap.C
+
+\*---------------------------------------------------------------------------*/
+
+#ifndef ParticleFieldMap_H
+#define ParticleFieldMap_H
+
+#include "CloudFunctionObject.H"
+#include "volFields.H"
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+namespace Foam
+{
+
+/*---------------------------------------------------------------------------*\
+                       Class ParticleFieldMap Declaration
+\*---------------------------------------------------------------------------*/
+
+template<class CloudType>
+class ParticleFieldMap
+:
+    public CloudFunctionObject<CloudType>
+{
+    // Private Data
+
+        // Typedefs
+
+            //- Convenience typedef for parcel type
+            typedef typename CloudType::parcelType parcelType;
+
+
+        //- Void fraction field
+        autoPtr<volScalarField> thetaScalarPtr_;
+        autoPtr<volVectorField> thetaVectorPtr_;
+
+        //- Field to store the count of particles in each cell
+        autoPtr<volScalarField> particleCountPtr_;
+
+        //- Field name
+        string fieldName_;
+
+        //- Static operator
+        string statisticOperator_;
+
+        //- Normalise by cell volume
+        bool normaliseByCellVolume_;
+
+
+protected:
+
+    // Protected Member Functions
+
+        //- Write post-processing info
+        virtual void write();
+
+
+public:
+
+    //- Runtime type information
+    TypeName("particleFieldMap");
+
+
+    // Constructors
+
+        //- Construct from dictionary
+        ParticleFieldMap
+        (
+            const dictionary& dict,
+            CloudType& owner,
+            const word& modelName
+        );
+
+        //- Construct copy
+        ParticleFieldMap(const ParticleFieldMap<CloudType>& vf);
+
+        //- Construct and return a clone
+        virtual autoPtr<CloudFunctionObject<CloudType>> clone() const
+        {
+            return autoPtr<CloudFunctionObject<CloudType>>
+            (
+                new ParticleFieldMap<CloudType>(*this)
+            );
+        }
+
+
+    //- Destructor
+    virtual ~ParticleFieldMap() = default;
+
+
+    // Member Functions
+
+        // Evaluation
+
+            //- Pre-evolve hook
+            virtual void preEvolve
+            (
+                const typename parcelType::trackingData& td
+            );
+
+            //- Post-evolve hook
+            virtual void postEvolve
+            (
+                const typename parcelType::trackingData& td
+            );
+
+            //- Post-move hook
+            virtual bool postMove
+            (
+                parcelType& p,
+                const scalar dt,
+                const point& position0,
+                const typename parcelType::trackingData& td
+            );
+};
+
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+} // End namespace Foam
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+#ifdef NoRepository
+    #include "ParticleFieldMap.C"
+#endif
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+#endif
+
+// ************************************************************************* //
diff --git a/tutorials/RANS/bendPipe_2D/Allrun b/tutorials/RANS/bendPipe_2D/Allrun
index 14fe091..0253061 100755
--- a/tutorials/RANS/bendPipe_2D/Allrun
+++ b/tutorials/RANS/bendPipe_2D/Allrun
@@ -30,6 +30,7 @@ runApplication blockMesh
 runApplication topoSet
 runApplication createPatch -overwrite
 
+touch bendPipe_2D.foam
 runApplication $(getApplication)
 
 
diff --git a/tutorials/RANS/bendPipe_2D/constant/kinematicCloudProperties b/tutorials/RANS/bendPipe_2D/constant/kinematicCloudProperties
index 543dd59..dd14786 100644
--- a/tutorials/RANS/bendPipe_2D/constant/kinematicCloudProperties
+++ b/tutorials/RANS/bendPipe_2D/constant/kinematicCloudProperties
@@ -201,7 +201,22 @@ subModels
 
 
 cloudFunctions
-{}
+{
+        particleFieldAverageCharge
+        {
+                type particleFieldMap;
+                field "charge";
+                statisticOperator "average";
+                normaliseByCellVolume true;
+        }
+
+        particleFieldAverageVelocity
+        {
+                type particleFieldMap;
+                field "U";
+                statisticOperator "average";
+        }
+}
 
 
 // ************************************************************************* //
diff --git a/tutorials/RANS/bendPipe_2D/system/controlDict b/tutorials/RANS/bendPipe_2D/system/controlDict
index e03da35..207ece6 100644
--- a/tutorials/RANS/bendPipe_2D/system/controlDict
+++ b/tutorials/RANS/bendPipe_2D/system/controlDict
@@ -52,6 +52,7 @@ runTimeModifiable true;
 functions
 {
     #include "lineSampling"
+    #include "fieldAverage"
 
     sTransport
     {
diff --git a/tutorials/RANS/bendPipe_2D/system/fieldAverage b/tutorials/RANS/bendPipe_2D/system/fieldAverage
new file mode 100644
index 0000000..573c9d7
--- /dev/null
+++ b/tutorials/RANS/bendPipe_2D/system/fieldAverage
@@ -0,0 +1,49 @@
+/*--------------------------------*- C++ -*----------------------------------*\
+| =========                 |                                                 |
+| \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox           |
+|  \\    /   O peration     | Version:  v2312                                 |
+|   \\  /    A nd           | Website:  www.openfoam.com                      |
+|    \\/     M anipulation  |                                                 |
+\*---------------------------------------------------------------------------*/
+
+fieldAverage
+{
+    type            fieldAverage;
+    libs            (fieldFunctionObjects);
+
+    enabled         true;
+    writeControl    writeTime;
+
+    timeStart       0;
+
+    fields
+    (
+        U.air
+        {
+            mean            on;
+            prime2Mean      on;
+            base            time;
+        }
+        p
+        {
+            mean            on;
+            prime2Mean      off;
+            base            time;
+        }
+        kinematicCloudParticleFieldAverage.charge
+        {
+            mean            on;
+            prime2Mean      on;
+            base            time;
+        }
+        kinematicCloudParticleFieldAverage.U
+        {
+            mean            on;
+            prime2Mean      on;
+            base            time;
+        }
+    );
+}
+
+
+// ************************************************************************* //
-- 
GitLab