From b8c3dc4e496fe51ed70e6625c98a9d85d9aa0346 Mon Sep 17 00:00:00 2001
From: Mark Olesen <Mark.Olesen@esi-group.com>
Date: Fri, 4 Mar 2022 15:46:04 +0100
Subject: [PATCH] ENH: selection mechanism for cloudInfo function object
 (#2390)

- can restrict calculation of D32 and other spray properties to a
  subset of parcels. Uses a predicate selection mechanism similar to
  vtkCloud etc.

ENH: code cleanup in scalar predicates

- pass by value not reference in predicates
- additional assign() method to refactor common code
---
 .../predicates/scalar/scalarPredicates.C      | 110 +++++------
 .../predicates/scalar/scalarPredicates.H      |  20 +-
 .../predicates/scalar/scalarPredicatesI.H     |  12 +-
 .../lagrangian/cloudInfo/cloudInfo.C          | 187 +++++++++++++++---
 .../lagrangian/cloudInfo/cloudInfo.H          |  38 +++-
 5 files changed, 266 insertions(+), 101 deletions(-)

diff --git a/src/OpenFOAM/primitives/predicates/scalar/scalarPredicates.C b/src/OpenFOAM/primitives/predicates/scalar/scalarPredicates.C
index 0be0a0400a0..04d1ba79532 100644
--- a/src/OpenFOAM/primitives/predicates/scalar/scalarPredicates.C
+++ b/src/OpenFOAM/primitives/predicates/scalar/scalarPredicates.C
@@ -5,7 +5,7 @@
     \\  /    A nd           | www.openfoam.com
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
-    Copyright (C) 2018-2020 OpenCFD Ltd.
+    Copyright (C) 2018-2022 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -182,14 +182,38 @@ Foam::predicates::scalars::scalars
 (
     std::initializer_list<std::pair<word, scalar>> entries
 )
-:
-    List<unary>(entries.size())
 {
+    assign(entries);
+}
+
+
+Foam::predicates::scalars::scalars
+(
+    const UList<Tuple2<word, scalar>>& entries
+)
+{
+    assign(entries);
+}
+
+
+Foam::predicates::scalars::scalars(Istream& is)
+{
+    is >> *this;
+}
+
+
+// * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //
+
+void Foam::predicates::scalars::assign
+(
+    std::initializer_list<std::pair<word, scalar>> entries
+)
+{
+    typedef std::pair<word, scalar> tuple_type;
+
     // Access
-    const auto get0 =
-        [](const std::pair<word,scalar>& entry) { return entry.first; };
-    const auto get1 =
-        [](const std::pair<word,scalar>& entry) { return entry.second; };
+    const auto get0 = [](const tuple_type& entry) { return entry.first; };
+    const auto get1 = [](const tuple_type& entry) { return entry.second; };
 
     // Check for bad/unknown operations
     if (hasBadEntries(entries, get0))
@@ -198,29 +222,28 @@ Foam::predicates::scalars::scalars
             << exit(FatalError);
     }
 
-    // Appears to be good
-    auto& list = *this;
-    label idx = 0;
-    for (const auto& entry : entries)
+    // Appears to be good, fill the list
+    this->resize_nocopy(entries.size());
+    auto iter = this->begin();
+
+    for (const tuple_type& entry : entries)
     {
-        list[idx] = predicates::scalars::operation(entry);
-        ++idx;
+        *iter = predicates::scalars::operation(entry);
+        ++iter;
     }
 }
 
 
-Foam::predicates::scalars::scalars
+void Foam::predicates::scalars::assign
 (
     const UList<Tuple2<word, scalar>>& entries
 )
-:
-    List<unary>(entries.size())
 {
+    typedef Tuple2<word, scalar> tuple_type;
+
     // Access
-    const auto get0 =
-        [](const Tuple2<word,scalar>& entry) { return entry.first(); };
-    const auto get1 =
-        [](const Tuple2<word,scalar>& entry) { return entry.second(); };
+    const auto get0 = [](const tuple_type& entry) { return entry.first(); };
+    const auto get1 = [](const tuple_type& entry) { return entry.second(); };
 
     // Check for bad/unknown operations
     if (hasBadEntries(entries, get0))
@@ -229,27 +252,18 @@ Foam::predicates::scalars::scalars
             << exit(FatalError);
     }
 
-    // Appears to be good
-    auto& list = *this;
-    label idx = 0;
-    for (const auto& entry : entries)
+    // Appears to be good, fill the list
+    this->resize_nocopy(entries.size());
+    auto iter = this->begin();
+
+    for (const tuple_type& entry : entries)
     {
-        list[idx] = predicates::scalars::operation(entry);
-        ++idx;
+        *iter = predicates::scalars::operation(entry);
+        ++iter;
     }
 }
 
 
-Foam::predicates::scalars::scalars(Istream& is)
-:
-    List<unary>()
-{
-    is >> *this;
-}
-
-
-// * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //
-
 Foam::label Foam::predicates::scalars::find
 (
     const scalar value,
@@ -310,29 +324,7 @@ Foam::Istream& Foam::operator>>(Istream& is, Foam::predicates::scalars& list)
     // Read tuples
     List<Tuple2<word, scalar>> entries(is);
 
-    // Access
-    const auto get0 =
-        [](const Tuple2<word,scalar>& entry) { return entry.first(); };
-    const auto get1 =
-        [](const Tuple2<word,scalar>& entry) { return entry.second(); };
-
-
-    // Check for bad/unknown operations
-    if (hasBadEntries(entries, get0))
-    {
-        printBadEntries(FatalIOErrorInFunction(is), entries, get0, get1)
-            << exit(FatalIOError);
-    }
-
-    // Appears to be good
-    list.resize(entries.size());
-
-    label idx = 0;
-    for (const Tuple2<word, scalar>& entry : entries)
-    {
-        list[idx] = predicates::scalars::operation(entry);
-        ++idx;
-    }
+    list.assign(entries);
 
     return is;
 }
diff --git a/src/OpenFOAM/primitives/predicates/scalar/scalarPredicates.H b/src/OpenFOAM/primitives/predicates/scalar/scalarPredicates.H
index 4a135f05e01..15e8ed5b4b4 100644
--- a/src/OpenFOAM/primitives/predicates/scalar/scalarPredicates.H
+++ b/src/OpenFOAM/primitives/predicates/scalar/scalarPredicates.H
@@ -5,7 +5,7 @@
     \\  /    A nd           | www.openfoam.com
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
-    Copyright (C) 2018-2020 OpenCFD Ltd.
+    Copyright (C) 2018-2022 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -256,6 +256,14 @@ public:
 
     // Member Functions
 
+        //- Assign new predicates from an initializer list of
+        //- (opName opValue) tuples
+        void assign(std::initializer_list<std::pair<word, scalar>> entries);
+
+        //- Assign new predicates from a list of (opName opValue) tuples
+        void assign(const UList<Tuple2<word, scalar>>& entries);
+
+
     // Search
 
         //- Index of the first match for the value.
@@ -279,23 +287,23 @@ public:
         //- Match any condition in the list.
         //
         //  \return True if the value matches any condition in the list.
-        inline bool match(const scalar& value) const;
+        inline bool match(const scalar value) const;
 
         //- Match any condition in the list.
         //
         //  \return True if the value matches any condition in the list.
-        inline bool matchAny(const scalar& value) const;
+        inline bool matchAny(const scalar value) const;
 
         //- Match all conditions in the list.
         //
         //  \return True if the value matches all conditions in the list.
-        inline bool matchAll(const scalar& value) const;
+        inline bool matchAll(const scalar value) const;
 
         //- Extract list indices for all matches.
         //
         //  \return The locations (indices) in the input list where match()
         //      is true
-        inline labelList matching(const scalar& value) const;
+        inline labelList matching(const scalar value) const;
 
         //- Extract list indices for all matches.
         //
@@ -313,7 +321,7 @@ public:
     // Member Operators
 
         //- Identical to found(), match(), for use as a predicate.
-        inline bool operator()(const scalar& value) const;
+        inline bool operator()(const scalar value) const;
 };
 
 
diff --git a/src/OpenFOAM/primitives/predicates/scalar/scalarPredicatesI.H b/src/OpenFOAM/primitives/predicates/scalar/scalarPredicatesI.H
index 207fe59de5e..2275d29cdce 100644
--- a/src/OpenFOAM/primitives/predicates/scalar/scalarPredicatesI.H
+++ b/src/OpenFOAM/primitives/predicates/scalar/scalarPredicatesI.H
@@ -5,7 +5,7 @@
     \\  /    A nd           | www.openfoam.com
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
-    Copyright (C) 2018-2020 OpenCFD Ltd.
+    Copyright (C) 2018-2022 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -70,13 +70,13 @@ inline bool Foam::predicates::scalars::found
 }
 
 
-inline bool Foam::predicates::scalars::match(const scalar& value) const
+inline bool Foam::predicates::scalars::match(const scalar value) const
 {
     return this->matchAny(value);
 }
 
 
-inline bool Foam::predicates::scalars::matchAny(const scalar& value) const
+inline bool Foam::predicates::scalars::matchAny(const scalar value) const
 {
     for (const unary& test : *this)
     {
@@ -90,7 +90,7 @@ inline bool Foam::predicates::scalars::matchAny(const scalar& value) const
 }
 
 
-inline bool Foam::predicates::scalars::matchAll(const scalar& value) const
+inline bool Foam::predicates::scalars::matchAll(const scalar value) const
 {
     for (const unary& test : *this)
     {
@@ -106,7 +106,7 @@ inline bool Foam::predicates::scalars::matchAll(const scalar& value) const
 
 inline Foam::labelList Foam::predicates::scalars::matching
 (
-    const scalar& value
+    const scalar value
 ) const
 {
     labelList indices(this->size());
@@ -154,7 +154,7 @@ inline Foam::labelList Foam::predicates::scalars::matching
 
 // * * * * * * * * * * * * * * * Member Operators  * * * * * * * * * * * * * //
 
-inline bool Foam::predicates::scalars::operator()(const scalar& value) const
+inline bool Foam::predicates::scalars::operator()(const scalar value) const
 {
     return this->found(value);
 }
diff --git a/src/functionObjects/lagrangian/cloudInfo/cloudInfo.C b/src/functionObjects/lagrangian/cloudInfo/cloudInfo.C
index 72e25248784..ae81784e44f 100644
--- a/src/functionObjects/lagrangian/cloudInfo/cloudInfo.C
+++ b/src/functionObjects/lagrangian/cloudInfo/cloudInfo.C
@@ -6,7 +6,7 @@
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
     Copyright (C) 2012-2016 OpenFOAM Foundation
-    Copyright (C) 2015 OpenCFD Ltd.
+    Copyright (C) 2015-2022 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -27,8 +27,10 @@ License
 \*---------------------------------------------------------------------------*/
 
 #include "cloudInfo.H"
+#include "cloud.H"
 #include "kinematicCloud.H"
 #include "dictionary.H"
+#include "mathematicalConstants.H"
 #include "PstreamReduceOps.H"
 #include "addToRunTimeSelectionTable.H"
 
@@ -74,8 +76,10 @@ Foam::functionObjects::cloudInfo::cloudInfo
     const dictionary& dict
 )
 :
-    regionFunctionObject(name, runTime, dict),
-    logFiles(obr_, name, dict)
+    functionObjects::regionFunctionObject(name, runTime, dict),
+    functionObjects::logFiles(obr_, name, dict),
+    verbose_(false),
+    onExecute_(false)
 {
     read(dict);
 }
@@ -85,55 +89,175 @@ Foam::functionObjects::cloudInfo::cloudInfo
 
 bool Foam::functionObjects::cloudInfo::read(const dictionary& dict)
 {
+    parcelSelect_.clear();
+    verbose_ = false;
+    onExecute_ = false;
+
     if (regionFunctionObject::read(dict) && logFiles::read(dict))
     {
         logFiles::resetNames(dict.get<wordList>("clouds"));
 
         Info<< type() << " " << name() << ": ";
-        if (writeToFile() && names().size())
+        if (names().size())
         {
             Info<< "applying to clouds:" << nl;
-            forAll(names(), cloudi)
+            for (const word& cldName : names())
             {
-                Info<< "    " << names()[cloudi] << nl;
-                writeFileHeader(files(cloudi));
+                Info<< "    " << cldName << nl;
             }
             Info<< endl;
+
+            // Actions to define selection
+            parcelSelect_ = dict.subOrEmptyDict("selection");
+
+            verbose_ = dict.getOrDefault("verbose", false);
+            onExecute_ = dict.getOrDefault("sampleOnExecute", false);
         }
         else
         {
             Info<< "no clouds to be processed" << nl << endl;
         }
+
+        if (writeToFile())
+        {
+            forAll(names(), cloudi)
+            {
+                writeFileHeader(files(cloudi));
+            }
+        }
     }
 
     return true;
 }
 
 
-bool Foam::functionObjects::cloudInfo::execute()
+bool Foam::functionObjects::cloudInfo::performAction(unsigned request)
 {
-    return true;
-}
-
+    if (!request || names().empty())
+    {
+        return true;
+    }
 
-bool Foam::functionObjects::cloudInfo::write()
-{
     forAll(names(), cloudi)
     {
+        // The reported quantities
+        label nTotParcels = 0;
+        scalar totMass = 0, Dmax = 0, D10 = 0, D32 = 0;
+        bool applyFilter = false;
+
         const word& cloudName = names()[cloudi];
 
-        const kinematicCloud& cloud =
-            obr_.lookupObject<kinematicCloud>(cloudName);
+        const auto* kinCloudPtr = obr_.cfindObject<kinematicCloud>(cloudName);
 
-        const label nTotParcels =
-            returnReduce(cloud.nParcels(), sumOp<label>());
+        if (!kinCloudPtr)
+        {
+            // Safety
+            continue;
+        }
+
+        const auto& kinCloud = *kinCloudPtr;
+        const auto* plainCloudPtr = isA<cloud>(kinCloud);
 
-        const scalar totMass =
-            returnReduce(cloud.massInSystem(), sumOp<scalar>());
+        if (!parcelSelect_.empty() && plainCloudPtr)
+        {
+            const auto& plainCloud = *plainCloudPtr;
+
+            // Filtering - simply use cloud methods
+
+            objectRegistry obrTmp
+            (
+                IOobject
+                (
+                    "tmp::cloudInfo::" + cloudName,
+                    obr_.time().constant(),
+                    obr_,
+                    IOobject::NO_READ,
+                    IOobject::NO_WRITE,
+                    false
+                )
+            );
+
+            plainCloud.writeObjects(obrTmp);
+
+            // Apply output filter (for the current cloud)
+            applyFilter = calculateFilter(obrTmp, log);
+
+            // Expected/required fields
+            const auto* diamFldPtr = obrTmp.cfindObject<IOField<scalar>>("d");
+            const auto* rhoFldPtr = obrTmp.cfindObject<IOField<scalar>>("rho");
+            const auto* nParticleFldPtr =
+                obrTmp.cfindObject<IOField<scalar>>("nParticle");
+
+            do
+            {
+                #undef  doLocalCode
+                #define doLocalCode(FldPtr, FldName)                          \
+                if (applyFilter && !FldPtr)                                   \
+                {                                                             \
+                    WarningInFunction                                         \
+                        << "Missing \"" << #FldName                           \
+                        << "\" field - disabling filter" << nl;               \
+                    applyFilter = false;                                      \
+                    break;                                                    \
+                }
+
+                doLocalCode(diamFldPtr, d);
+                doLocalCode(rhoFldPtr, rho);
+                doLocalCode(nParticleFldPtr, nParticle);
+                #undef doLocalCode
+            }
+            while (false);
+
+            if (applyFilter)
+            {
+                // Filtered. Need to do everything by hand!
 
-        const scalar Dmax = cloud.Dmax();
-        const scalar D10 = cloud.Dij(1, 0);
-        const scalar D32 = cloud.Dij(3, 2);
+                const auto& diams = *diamFldPtr;
+                const auto& rhos = *rhoFldPtr;
+                const auto& nParts = *nParticleFldPtr;
+
+                FixedList<scalar, 4> Dsums(Zero);
+
+                for (const label particlei : parcelAddr_)
+                {
+                    ++nTotParcels;
+
+                    const scalar d = diams[particlei];
+                    const scalar rho = rhos[particlei];
+                    const scalar np = nParts[particlei];
+
+                    totMass += np*rho*pow3(d);
+                    Dmax = max(Dmax, d);
+
+                    Dsums[0] += np;
+                    Dsums[1] += np*(d);
+                    Dsums[2] += np*(sqr(d));
+                    Dsums[3] += np*(pow3(d));
+                }
+
+                reduce(nTotParcels, sumOp<label>());
+                reduce(totMass, sumOp<scalar>());
+                reduce(Dmax, maxOp<scalar>());
+                reduce(Dsums, sumOp<scalar>());
+
+                totMass *= (constant::mathematical::pi/6.0);
+
+                Dmax = max(0, Dmax);
+                D10 = Dsums[1]/(max(Dsums[0], VSMALL));
+                D32 = Dsums[3]/(max(Dsums[2], VSMALL));
+            }
+        }
+
+        if (!applyFilter)
+        {
+            // No filter - use regular cloud methods
+            nTotParcels = returnReduce(kinCloud.nParcels(), sumOp<label>());
+            totMass = returnReduce(kinCloud.massInSystem(), sumOp<scalar>());
+
+            Dmax = kinCloud.Dmax();
+            D10 = kinCloud.Dij(1, 0);
+            D32 = kinCloud.Dij(3, 2);
+        }
 
         Log << type() << " " << name() <<  " write:" << nl
             << "    number of parcels : " << nTotParcels << nl
@@ -143,7 +267,7 @@ bool Foam::functionObjects::cloudInfo::write()
             << "    D32 diameter      : " << D32 << nl
             << endl;
 
-        if (writeToFile())
+        if ((request & ACTION_WRITE) && writeToFile())
         {
             auto& os = files(cloudi);
 
@@ -162,4 +286,21 @@ bool Foam::functionObjects::cloudInfo::write()
 }
 
 
+bool Foam::functionObjects::cloudInfo::execute()
+{
+    if (onExecute_)
+    {
+        return performAction(ACTION_ALL & ~ACTION_WRITE);
+    }
+
+    return true;
+}
+
+
+bool Foam::functionObjects::cloudInfo::write()
+{
+    return performAction(ACTION_ALL);
+}
+
+
 // ************************************************************************* //
diff --git a/src/functionObjects/lagrangian/cloudInfo/cloudInfo.H b/src/functionObjects/lagrangian/cloudInfo/cloudInfo.H
index da19031243e..4fb4ae680b5 100644
--- a/src/functionObjects/lagrangian/cloudInfo/cloudInfo.H
+++ b/src/functionObjects/lagrangian/cloudInfo/cloudInfo.H
@@ -6,7 +6,7 @@
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
     Copyright (C) 2012-2016 OpenFOAM Foundation
-    Copyright (C) 2015-2020 OpenCFD Ltd.
+    Copyright (C) 2015-2022 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -58,6 +58,8 @@ Usage
         Property     | Description                          | Required | Default
         type         | type name: cloudInfo                 | yes |
         clouds       | list of clouds names to process      | yes |
+        selection    | Parcel selection control         | no  | empty-dict
+        sampleOnExecute| Sample/report (on execute) without writing | no | false
     \endtable
 
     The output data of each cloud is written to a file named \<cloudName\>.dat
@@ -72,11 +74,12 @@ SourceFiles
 
 \*---------------------------------------------------------------------------*/
 
-#ifndef functionObjects_cloudInfo_H
-#define functionObjects_cloudInfo_H
+#ifndef Foam_functionObjects_cloudInfo_H
+#define Foam_functionObjects_cloudInfo_H
 
 #include "regionFunctionObject.H"
 #include "logFiles.H"
+#include "parcelSelectionDetail.H"
 
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
 
@@ -91,13 +94,31 @@ namespace functionObjects
 
 class cloudInfo
 :
-    public regionFunctionObject,
-    public logFiles
+    public functionObjects::regionFunctionObject,
+    public functionObjects::logFiles,
+    public Foam::Detail::parcelSelection
 {
 protected:
 
-        //- Switch to send output to Info as well
-        Switch log_;
+    // Data Types
+
+        //- Local control for sampling actions
+        enum sampleActionType : unsigned
+        {
+            ACTION_NONE  = 0,
+            ACTION_WRITE = 0x1,
+            ACTION_STORE = 0x2,
+            ACTION_ALL = 0xF
+        };
+
+
+    // Protected Data
+
+        //- Additional verbosity
+        bool verbose_;
+
+        //- Perform sample actions on execute as well
+        bool onExecute_;
 
         //- List of cloud names
         wordList cloudNames_;
@@ -105,11 +126,14 @@ protected:
         //- Output file per cloud
         PtrList<OFstream> filePtrs_;
 
+
     // Protected Member Functions
 
         //- File header information
         virtual void writeFileHeader(Ostream& os) const;
 
+        //- Perform operation report/write
+        bool performAction(unsigned request);
 
         //- No copy construct
         cloudInfo(const cloudInfo&) = delete;
-- 
GitLab