diff --git a/src/lagrangian/intermediate/parcels/include/makeParcelCloudFunctionObjects.H b/src/lagrangian/intermediate/parcels/include/makeParcelCloudFunctionObjects.H
index e11442604b..f95d5d0ef0 100644
--- a/src/lagrangian/intermediate/parcels/include/makeParcelCloudFunctionObjects.H
+++ b/src/lagrangian/intermediate/parcels/include/makeParcelCloudFunctionObjects.H
@@ -6,7 +6,7 @@
\\/ M anipulation |
-------------------------------------------------------------------------------
Copyright (C) 2011-2018 OpenFOAM Foundation
- Copyright (C) 2020-2021 OpenCFD Ltd.
+ Copyright (C) 2020-2022 OpenCFD Ltd.
-------------------------------------------------------------------------------
License
This file is part of OpenFOAM.
@@ -37,6 +37,7 @@ License
#include "ParticleErosion.H"
#include "ParticleTracks.H"
#include "ParticleTrap.H"
+#include "ParticleZoneInfo.H"
#include "PatchCollisionDensity.H"
#include "PatchInteractionFields.H"
#include "PatchPostProcessing.H"
@@ -57,6 +58,7 @@ License
makeCloudFunctionObjectType(ParticleErosion, CloudType); \
makeCloudFunctionObjectType(ParticleTracks, CloudType); \
makeCloudFunctionObjectType(ParticleTrap, CloudType); \
+ makeCloudFunctionObjectType(ParticleZoneInfo, CloudType); \
makeCloudFunctionObjectType(PatchCollisionDensity, CloudType); \
makeCloudFunctionObjectType(PatchInteractionFields, CloudType); \
makeCloudFunctionObjectType(PatchPostProcessing, CloudType); \
diff --git a/src/lagrangian/intermediate/parcels/include/makeReactingParcelCloudFunctionObjects.H b/src/lagrangian/intermediate/parcels/include/makeReactingParcelCloudFunctionObjects.H
index 61e0e413cd..16258ff40c 100644
--- a/src/lagrangian/intermediate/parcels/include/makeReactingParcelCloudFunctionObjects.H
+++ b/src/lagrangian/intermediate/parcels/include/makeReactingParcelCloudFunctionObjects.H
@@ -6,7 +6,7 @@
\\/ M anipulation |
-------------------------------------------------------------------------------
Copyright (C) 2011-2018 OpenFOAM Foundation
- Copyright (C) 2020-2021 OpenCFD Ltd.
+ Copyright (C) 2020-2022 OpenCFD Ltd.
-------------------------------------------------------------------------------
License
This file is part of OpenFOAM.
@@ -37,6 +37,7 @@ License
#include "ParticleErosion.H"
#include "ParticleTracks.H"
#include "ParticleTrap.H"
+#include "ParticleZoneInfo.H"
#include "PatchCollisionDensity.H"
#include "PatchInteractionFields.H"
#include "PatchPostProcessing.H"
@@ -60,6 +61,7 @@ License
makeCloudFunctionObjectType(ParticleErosion, CloudType); \
makeCloudFunctionObjectType(ParticleTracks, CloudType); \
makeCloudFunctionObjectType(ParticleTrap, CloudType); \
+ makeCloudFunctionObjectType(ParticleZoneInfo, CloudType); \
makeCloudFunctionObjectType(PatchCollisionDensity, CloudType); \
makeCloudFunctionObjectType(PatchInteractionFields, CloudType); \
makeCloudFunctionObjectType(PatchPostProcessing, CloudType); \
diff --git a/src/lagrangian/intermediate/parcels/include/makeThermoParcelCloudFunctionObjects.H b/src/lagrangian/intermediate/parcels/include/makeThermoParcelCloudFunctionObjects.H
index 4b641e275b..a703eb8040 100644
--- a/src/lagrangian/intermediate/parcels/include/makeThermoParcelCloudFunctionObjects.H
+++ b/src/lagrangian/intermediate/parcels/include/makeThermoParcelCloudFunctionObjects.H
@@ -5,7 +5,7 @@
\\ / A nd | www.openfoam.com
\\/ M anipulation |
-------------------------------------------------------------------------------
- Copyright (C) 2021 OpenCFD Ltd.
+ Copyright (C) 2021-2022 OpenCFD Ltd.
-------------------------------------------------------------------------------
License
This file is part of OpenFOAM.
@@ -36,6 +36,7 @@ License
#include "ParticleErosion.H"
#include "ParticleTracks.H"
#include "ParticleTrap.H"
+#include "ParticleZoneInfo.H"
#include "PatchCollisionDensity.H"
#include "PatchInteractionFields.H"
#include "PatchPostProcessing.H"
@@ -58,6 +59,7 @@ License
makeCloudFunctionObjectType(ParticleErosion, CloudType); \
makeCloudFunctionObjectType(ParticleTracks, CloudType); \
makeCloudFunctionObjectType(ParticleTrap, CloudType); \
+ makeCloudFunctionObjectType(ParticleZoneInfo, CloudType); \
makeCloudFunctionObjectType(PatchCollisionDensity, CloudType); \
makeCloudFunctionObjectType(PatchInteractionFields, CloudType); \
makeCloudFunctionObjectType(PatchPostProcessing, CloudType); \
diff --git a/src/lagrangian/intermediate/submodels/CloudFunctionObjects/ParticleZoneInfo/ParticleZoneInfo.C b/src/lagrangian/intermediate/submodels/CloudFunctionObjects/ParticleZoneInfo/ParticleZoneInfo.C
new file mode 100644
index 0000000000..86bacdfb8f
--- /dev/null
+++ b/src/lagrangian/intermediate/submodels/CloudFunctionObjects/ParticleZoneInfo/ParticleZoneInfo.C
@@ -0,0 +1,458 @@
+/*---------------------------------------------------------------------------*\
+ ========= |
+ \\ / 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 .
+
+\*---------------------------------------------------------------------------*/
+
+#include "ParticleZoneInfo.H"
+#include "DynamicField.H"
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+namespace Foam
+{
+struct particleInfoCombineOp
+{
+ void operator()(particleInfo& p1, const particleInfo& p2) const
+ {
+ // p2 not set
+ if (p2.origID == -1)
+ {
+ return;
+ }
+
+ // p1 not set - initialise with p2
+ if (p1.origID == -1)
+ {
+ p1 = p2;
+ return;
+ }
+
+ // Set initial values
+ if (p2.time0 < p1.time0)
+ {
+ p1.time0 = p2.time0;
+ p1.d0 = p2.d0;
+ p1.mass0 = p2.mass0;
+ }
+
+ // Accumulate age
+ p1.age += p2.age;
+
+ // Set latest available values
+ if (p2.isOlderThan(p1))
+ {
+ p1.position = p2.position;
+ p1.d = p2.d;
+ p1.mass = p2.mass;
+ }
+ }
+};
+} // End namespace Foam
+
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+template
+Foam::Field getData
+(
+ const Foam::UList& data,
+ Type Foam::particleInfo::* field
+)
+{
+ Field result(data.size());
+
+ forAll(data, i)
+ {
+ result[i] = data[i].*field;
+ }
+
+ return result;
+}
+
+
+template
+Foam::Field getParData
+(
+ const Foam::List>& parData,
+ Type Foam::particleInfo::* field
+)
+{
+ DynamicField result;
+
+ for (const auto& particles : parData)
+ {
+ for (const auto& p : particles)
+ {
+ if (p.origID != -1)
+ {
+ result.append(p.*field);
+ }
+ }
+ }
+
+ return std::move(result);
+}
+
+
+template
+void Foam::ParticleZoneInfo::writeWriter
+(
+ const DynamicList& data
+)
+{
+ coordSet coords
+ (
+ "zoneParticles",
+ "xyz",
+ getData(data_, &particleInfo::position),
+ scalarList(data.size(), Zero)
+ );
+
+ writerPtr_->open(coords, this->baseTimeDir() / "zoneParticles");
+ writerPtr_->beginTime(this->owner().time());
+
+#undef writeLocal
+#define writeLocal(field) \
+ writerPtr_->write(#field, getData(data, &particleInfo::field));
+
+ writeLocal(origID);
+ writeLocal(origProc);
+ writeLocal(time0);
+ writeLocal(age);
+ writeLocal(d0);
+ writeLocal(d);
+ writeLocal(mass0);
+ writeLocal(mass);
+#undef writeLocal
+
+ writerPtr_->endTime();
+ writerPtr_->close();
+}
+
+
+template
+void Foam::ParticleZoneInfo::writeWriter
+(
+ const List>& procData
+)
+{
+ vectorField points(getParData(procData, &particleInfo::position));
+
+ coordSet coords
+ (
+ "zoneParticles",
+ "xyz",
+ std::move(points),
+ scalarList(points.size(), Zero)
+ );
+
+ writerPtr_->open(coords, this->baseTimeDir() / "zoneParticles");
+ writerPtr_->beginTime(this->owner().time());
+
+#undef writeLocal
+#define writeLocal(field) \
+ writerPtr_->write(#field, getParData(procData, &particleInfo::field));
+
+ writeLocal(origID);
+ writeLocal(origProc);
+ writeLocal(time0);
+ writeLocal(age);
+ writeLocal(d0);
+ writeLocal(d);
+ writeLocal(mass0);
+ writeLocal(mass);
+#undef writeLocal
+
+ writerPtr_->endTime();
+ writerPtr_->close();
+}
+
+
+template
+void Foam::ParticleZoneInfo::writeFileHeader(Ostream& os) const
+{
+ this->writeHeaderValue(os, "cellZone", cellZoneName_);
+ this->writeHeaderValue(os, "time", this->owner().time().timeOutputValue());
+ this->writeHeader(os, "");
+ this->writeCommented(os, "origID");
+ os << tab << "origProc"
+ << tab << "(x y z)"
+ << tab << "time0"
+ << tab << "age"
+ << tab << "d0"
+ << tab << "d"
+ << tab << "mass0"
+ << tab << "mass"
+ << endl;
+}
+
+
+template
+bool Foam::ParticleZoneInfo::inZone(const label celli) const
+{
+ return this->owner().mesh().cellZones()[cellZoneId_].whichCell(celli) != -1;
+}
+
+
+template
+Foam::label Foam::ParticleZoneInfo::getParticleID
+(
+ const particleInfo& p
+) const
+{
+ forAll(data_, i)
+ {
+ const auto& d = data_[i];
+ if ((d.origProc == p.origProc) && (d.origID == p.origID))
+ {
+ return i;
+ }
+ }
+
+ return -1;
+}
+
+
+// * * * * * * * * * * * * * * * * Constructors * * * * * * * * * * * * * * //
+
+template
+Foam::ParticleZoneInfo::ParticleZoneInfo
+(
+ const dictionary& dict,
+ CloudType& owner,
+ const word& modelName
+)
+:
+ CloudFunctionObject(dict, owner, modelName, typeName),
+ functionObjects::writeFile
+ (
+ owner,
+ this->localPath(),
+ typeName,
+ this->coeffDict()
+ ),
+ cellZoneName_(this->coeffDict().getWord("cellZone")),
+ cellZoneId_(-1),
+ data_(),
+ movedParticles_(),
+ maxIDs_(Pstream::nProcs(), Zero),
+ writerPtr_
+ (
+ Pstream::master()
+ ? coordSetWriter::New
+ (
+ this->coeffDict().getWord("writer"),
+ this->coeffDict().subOrEmptyDict("formatOptions")
+ )
+ : nullptr
+ )
+{
+ const auto& cellZones = owner.mesh().cellZones();
+
+ cellZoneId_ = cellZones.findZoneID(cellZoneName_);
+ if (cellZoneId_ == -1)
+ {
+ FatalIOErrorInFunction(this->coeffDict())
+ << "Unable to find cellZone " << cellZoneName_
+ << ". Available cellZones are:" << cellZones.names()
+ << exit(FatalIOError);
+ }
+
+ Info<< " Processing cellZone" << cellZoneName_ << " with id "
+ << cellZoneId_ << endl;
+}
+
+
+template
+Foam::ParticleZoneInfo::ParticleZoneInfo
+(
+ const ParticleZoneInfo& pzi
+)
+:
+ CloudFunctionObject(pzi),
+ writeFile(pzi),
+ cellZoneName_(pzi.cellZoneName_),
+ cellZoneId_(pzi.cellZoneId_),
+ data_(pzi.data_),
+ movedParticles_(pzi.movedParticles_),
+ maxIDs_(Pstream::nProcs()),
+ writerPtr_(nullptr)
+{}
+
+
+// * * * * * * * * * * * * * * * Member Functions * * * * * * * * * * * * * //
+
+template
+void Foam::ParticleZoneInfo::preEvolve
+(
+ const typename parcelType::trackingData& td
+)
+{}
+
+
+template
+void Foam::ParticleZoneInfo::postEvolve
+(
+ const typename parcelType::trackingData& td
+)
+{
+ Info<< this->type() << ":" << nl
+ << " Cell zone = " << cellZoneName_ << nl
+ << " Contributions = "
+ << returnReduce(movedParticles_.size(), sumOp