From ea12bfdb0f0d42eadccdf03badb542bfc734fe64 Mon Sep 17 00:00:00 2001
From: Andrew Heather <>
Date: Mon, 17 May 2021 21:08:38 +0100
Subject: [PATCH] ENH: new multiFieldValue function object

    Computes a selected operation between multiple \c fieldValue function
    objects.

    The operation is applied to all results of each \c fieldValue object.

Note
    Each object must generate the same number and type of results.

Usage
    Minimal example by using \c system/controlDict.functions:

    multiFieldValue1
    {
        // Mandatory entries (unmodifiable)
        type            multiFieldValue;
        libs            (fieldFunctionObjects);

        // Mandatory entries (runtime modifiable)
        operation       subtract;

        // List of fieldValue function objects as dictionaries
        functions
        {
            region1
            {
                ...
            }
            region2
            {
                ...
            }

            ...

            regionN
            {
                ...
            }
        }

        // Optional (inherited) entries
        ...
    }

    where the entries mean:

      Property     | Description                         | Type | Req'd | Dflt
      type         | Type name: multiFieldValue          | word |  yes  | -
      libs         | Library name: fieldFunctionObjects  | word |  yes  | -
      operation    | Operation type to apply to values   | word |  yes  | -
      functions    | List of fieldValue function objects | dict |  yes  | -
    \endtable

    Options for the \c operation entry:

       add           | add
       subtract      | subtract
       min           | minimum
       max           | maximum
       average       | average

Deprecated fieldValueDelta

- The fieldValueDelta function object was originally written to compute the
difference between two fieldValue-type function objects. The multiFieldValue
object name better describes its purpose whilst being able to operate on an
arbitrary number of fieldValue-type objects.
---
 .../pressure/pressureDifference.cfg           |  25 +-
 src/functionObjects/field/Make/files          |   2 +-
 .../field/fieldValues/fieldValue/fieldValue.C |  26 +-
 .../field/fieldValues/fieldValue/fieldValue.H |  10 +-
 .../fieldValues/fieldValue/fieldValueNew.C    |   9 +-
 .../fieldValueDelta/fieldValueDelta.C         | 227 -------------
 .../multiFieldValue/multiFieldValue.C         | 306 ++++++++++++++++++
 .../multiFieldValue.H}                        | 118 ++++---
 .../multiFieldValueTemplates.C}               |  44 +--
 .../surfaceFieldValue/surfaceFieldValue.C     |   2 +-
 .../fieldValues/volFieldValue/volFieldValue.C |   4 +-
 .../filter/system/controlDict                 |   3 +-
 .../filter/system/pressureDifference          |  40 +++
 13 files changed, 480 insertions(+), 336 deletions(-)
 delete mode 100644 src/functionObjects/field/fieldValues/fieldValueDelta/fieldValueDelta.C
 create mode 100644 src/functionObjects/field/fieldValues/multiFieldValue/multiFieldValue.C
 rename src/functionObjects/field/fieldValues/{fieldValueDelta/fieldValueDelta.H => multiFieldValue/multiFieldValue.H} (63%)
 rename src/functionObjects/field/fieldValues/{fieldValueDelta/fieldValueDeltaTemplates.C => multiFieldValue/multiFieldValueTemplates.C} (74%)
 create mode 100644 tutorials/lagrangian/reactingParcelFoam/filter/system/pressureDifference

diff --git a/etc/caseDicts/postProcessing/pressure/pressureDifference.cfg b/etc/caseDicts/postProcessing/pressure/pressureDifference.cfg
index b601f5b97b6..b8f8e057c52 100644
--- a/etc/caseDicts/postProcessing/pressure/pressureDifference.cfg
+++ b/etc/caseDicts/postProcessing/pressure/pressureDifference.cfg
@@ -6,7 +6,7 @@
      \\/     M anipulation  |
 \*---------------------------------------------------------------------------*/
 
-type            fieldValueDelta;
+type            multiFieldValue;
 libs            ("libfieldFunctionObjects.so");
 
 operation       subtract;
@@ -15,17 +15,20 @@ writeControl    timeStep;
 writeInterval   1;
 log             false;
 
-region1
+functions
 {
-    #includeEtc "caseDicts/postProcessing/surfaceFieldValue/surfaceRegion.cfg"
-    operation       areaAverage;
-    fields          (p);
-}
-region2
-{
-    #includeEtc "caseDicts/postProcessing/surfaceFieldValue/surfaceRegion.cfg"
-    operation       areaAverage;
-    fields          (p);
+    region1
+    {
+        #includeEtc "caseDicts/postProcessing/surfaceFieldValue/surfaceRegion.cfg"
+        operation       areaAverage;
+        fields          (p);
+    }
+    region2
+    {
+        #includeEtc "caseDicts/postProcessing/surfaceFieldValue/surfaceRegion.cfg"
+        operation       areaAverage;
+        fields          (p);
+    }
 }
 
 // ************************************************************************* //
diff --git a/src/functionObjects/field/Make/files b/src/functionObjects/field/Make/files
index 1ca2168217c..ddcb030299f 100644
--- a/src/functionObjects/field/Make/files
+++ b/src/functionObjects/field/Make/files
@@ -16,9 +16,9 @@ fieldMinMax/fieldMinMax.C
 
 fieldValues/fieldValue/fieldValue.C
 fieldValues/fieldValue/fieldValueNew.C
-fieldValues/fieldValueDelta/fieldValueDelta.C
 fieldValues/volFieldValue/volFieldValue.C
 fieldValues/surfaceFieldValue/surfaceFieldValue.C
+fieldValues/multiFieldValue/multiFieldValue.C
 
 heatTransferCoeff/heatTransferCoeff.C
 heatTransferCoeff/heatTransferCoeffModels/heatTransferCoeffModel/heatTransferCoeffModel.C
diff --git a/src/functionObjects/field/fieldValues/fieldValue/fieldValue.C b/src/functionObjects/field/fieldValues/fieldValue/fieldValue.C
index f981f090d00..21be1ff6cd9 100644
--- a/src/functionObjects/field/fieldValues/fieldValue/fieldValue.C
+++ b/src/functionObjects/field/fieldValues/fieldValue/fieldValue.C
@@ -37,7 +37,7 @@ namespace Foam
 namespace functionObjects
 {
     defineTypeNameAndDebug(fieldValue, 0);
-    defineRunTimeSelectionTable(fieldValue, dictionary);
+    defineRunTimeSelectionTable(fieldValue, runTime);
 }
 }
 
@@ -55,7 +55,7 @@ Foam::functionObjects::fieldValue::fieldValue
     fvMeshFunctionObject(name, runTime, dict),
     writeFile(obr_, name, valueType, dict),
     writeFields_(false),
-    regionName_(word::null),
+    regionName_(),
     scaleFactor_(1.0),
     dict_(dict),
     fields_()
@@ -75,7 +75,7 @@ Foam::functionObjects::fieldValue::fieldValue
     fvMeshFunctionObject(name, obr, dict),
     writeFile(obr_, name, valueType, dict),
     writeFields_(false),
-    regionName_(word::null),
+    regionName_(),
     scaleFactor_(1.0),
     dict_(dict),
     fields_()
@@ -88,19 +88,21 @@ Foam::functionObjects::fieldValue::fieldValue
 
 bool Foam::functionObjects::fieldValue::read(const dictionary& dict)
 {
-    if (dict != dict_)
+    if (fvMeshFunctionObject::read(dict) && writeFile::read(dict))
     {
-        dict_ = dict;
-    }
+        if (dict != dict_)
+        {
+            dict_ = dict;
+        }
 
-    fvMeshFunctionObject::read(dict);
-    writeFile::read(dict);
+        dict.readEntry("writeFields", writeFields_);
+        scaleFactor_ = dict.getOrDefault<scalar>("scaleFactor", 1.0);
+        dict.readEntry("fields", fields_);
 
-    dict.readEntry("writeFields", writeFields_);
-    scaleFactor_ = dict.getOrDefault<scalar>("scaleFactor", 1.0);
-    dict.readEntry("fields", fields_);
+        return true;
+    }
 
-    return true;
+    return false;
 }
 
 
diff --git a/src/functionObjects/field/fieldValues/fieldValue/fieldValue.H b/src/functionObjects/field/fieldValues/fieldValue/fieldValue.H
index 5e008f62289..a182e300224 100644
--- a/src/functionObjects/field/fieldValues/fieldValue/fieldValue.H
+++ b/src/functionObjects/field/fieldValues/fieldValue/fieldValue.H
@@ -98,7 +98,6 @@ class fieldValue
     public fvMeshFunctionObject,
     public writeFile
 {
-
 protected:
 
     // Protected Data
@@ -141,16 +140,15 @@ public:
         (
             autoPtr,
             fieldValue,
-            dictionary,
+            runTime,
             (
                 const word& name,
-                const objectRegistry& obr,
+                const Time& runTime,
                 const dictionary& dict
             ),
-            (name, obr, dict)
+            (name, runTime, dict)
         );
 
-
     // Constructors
 
         //- Construct from Time and dictionary
@@ -175,7 +173,7 @@ public:
         static autoPtr<fieldValue> New
         (
             const word& name,
-            const objectRegistry& obr,
+            const Time& runTime,
             const dictionary& dict,
             const bool output = true
         );
diff --git a/src/functionObjects/field/fieldValues/fieldValue/fieldValueNew.C b/src/functionObjects/field/fieldValues/fieldValue/fieldValueNew.C
index 39ebdca2539..0868e0b86fe 100644
--- a/src/functionObjects/field/fieldValues/fieldValue/fieldValueNew.C
+++ b/src/functionObjects/field/fieldValues/fieldValue/fieldValueNew.C
@@ -34,7 +34,7 @@ Foam::autoPtr<Foam::functionObjects::fieldValue>
 Foam::functionObjects::fieldValue::New
 (
     const word& name,
-    const objectRegistry& obr,
+    const Time& runTime,
     const dictionary& dict,
     const bool output
 )
@@ -46,7 +46,7 @@ Foam::functionObjects::fieldValue::New
         Info<< "Selecting " << typeName << ' ' << modelType << endl;
     }
 
-    auto cstrIter = dictionaryConstructorTablePtr_->cfind(modelType);
+    auto cstrIter = runTimeConstructorTablePtr_->cfind(modelType);
 
     if (!cstrIter.found())
     {
@@ -55,12 +55,13 @@ Foam::functionObjects::fieldValue::New
             dict,
             typeName,
             modelType,
-            *dictionaryConstructorTablePtr_
+            *runTimeConstructorTablePtr_
         ) << exit(FatalIOError);
     }
 
-    return autoPtr<fieldValue>(cstrIter()(name, obr, dict));
+    return autoPtr<fieldValue>(cstrIter()(name, runTime, dict));
 }
 
 
+
 // ************************************************************************* //
diff --git a/src/functionObjects/field/fieldValues/fieldValueDelta/fieldValueDelta.C b/src/functionObjects/field/fieldValues/fieldValueDelta/fieldValueDelta.C
deleted file mode 100644
index 09f4f91833a..00000000000
--- a/src/functionObjects/field/fieldValues/fieldValueDelta/fieldValueDelta.C
+++ /dev/null
@@ -1,227 +0,0 @@
-/*---------------------------------------------------------------------------*\
-  =========                 |
-  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
-   \\    /   O peration     |
-    \\  /    A nd           | www.openfoam.com
-     \\/     M anipulation  |
--------------------------------------------------------------------------------
-    Copyright (C) 2012-2016 OpenFOAM Foundation
-    Copyright (C) 2015-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 "fieldValueDelta.H"
-#include "addToRunTimeSelectionTable.H"
-
-// * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * //
-
-namespace Foam
-{
-namespace functionObjects
-{
-namespace fieldValues
-{
-    defineTypeNameAndDebug(fieldValueDelta, 0);
-    addToRunTimeSelectionTable(functionObject, fieldValueDelta, dictionary);
-}
-}
-}
-
-
-const Foam::Enum
-<
-    Foam::functionObjects::fieldValues::fieldValueDelta::operationType
->
-Foam::functionObjects::fieldValues::fieldValueDelta::operationTypeNames_
-({
-    { operationType::opAdd, "add" },
-    { operationType::opSubtract, "subtract" },
-    { operationType::opMin, "min" },
-    { operationType::opMax, "max" },
-    { operationType::opAverage, "average" },
-});
-
-
-// * * * * * * * * * * * * * Protected Member Functions  * * * * * * * * * * //
-
-void Foam::functionObjects::fieldValues::fieldValueDelta::writeFileHeader
-(
-    Ostream& os
-) const
-{
-    const wordList& fields1 = region1Ptr_->fields();
-    const wordList& fields2 = region2Ptr_->fields();
-
-    DynamicList<word> commonFields(fields1.size());
-    forAll(fields1, fieldi)
-    {
-        label index = fields2.find(fields1[fieldi]);
-        if (index != -1)
-        {
-            commonFields.append(fields1[fieldi]);
-        }
-    }
-
-    writeHeaderValue(os, "Source1", region1Ptr_->name());
-    writeHeaderValue(os, "Source2", region2Ptr_->name());
-    writeHeaderValue(os, "Operation", operationTypeNames_[operation_]);
-    writeCommented(os, "Time");
-
-    forAll(commonFields, i)
-    {
-        os  << tab << commonFields[i];
-    }
-
-    os  << endl;
-}
-
-
-// * * * * * * * * * * * * * * * * Constructors  * * * * * * * * * * * * * * //
-
-Foam::functionObjects::fieldValues::fieldValueDelta::fieldValueDelta
-(
-    const word& name,
-    const Time& runTime,
-    const dictionary& dict
-)
-:
-    fvMeshFunctionObject(name, runTime, dict),
-    writeFile(obr_, name, typeName, dict),
-    operation_(opSubtract),
-    region1Ptr_(nullptr),
-    region2Ptr_(nullptr)
-{
-    read(dict);
-    writeFileHeader(file());
-}
-
-
-// * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //
-
-bool Foam::functionObjects::fieldValues::fieldValueDelta::read
-(
-    const dictionary& dict
-)
-{
-    fvMeshFunctionObject::read(dict);
-    writeFile::read(dict);
-
-    region1Ptr_.reset
-    (
-        fieldValue::New
-        (
-            name() + ".region1",
-            obr_,
-            dict.subDict("region1"),
-            false
-        ).ptr()
-    );
-    region2Ptr_.reset
-    (
-        fieldValue::New
-        (
-            name() + ".region2",
-            obr_,
-            dict.subDict("region2"),
-            false
-        ).ptr()
-    );
-
-    operation_ = operationTypeNames_.get("operation", dict);
-
-    return true;
-}
-
-
-bool Foam::functionObjects::fieldValues::fieldValueDelta::write()
-{
-    region1Ptr_->write();
-    region2Ptr_->write();
-
-    writeCurrentTime(file());
-
-    Log << type() << " " << name() << " write:" << endl;
-
-    const word& name1 = region1Ptr_->name();
-    const word& name2 = region2Ptr_->name();
-
-    const wordList entries1 = objectResultEntries(name1);
-    const wordList entries2 = objectResultEntries(name2);
-
-    if (entries1.size() != entries2.size())
-    {
-        FatalErrorInFunction
-            << name() << ": objects must generate the same number of results"
-            << nl
-            << "    " << name1 << " objects: " << entries1 << nl
-            << "    " << name2 << " objects: " << entries2 << nl
-            << exit(FatalError);
-    }
-
-    forAll(entries1, i)
-    {
-        const word& entry1(entries1[i]);
-        const word& entry2(entries2[i]);
-        const word type1 = objectResultType(name1, entry1);
-        const word type2 = objectResultType(name2, entry2);
-
-        if (type1 != type2)
-        {
-            FatalErrorInFunction
-                << name()
-                << ": input values for operation must be of the same type"
-                << nl
-                << "    " << entry1 << ": " << type1 << nl
-                << "    " << entry2 << ": " << type2 << nl
-                << exit(FatalError);
-        }
-
-        bool found = false;
-
-        applyOperation<scalar>(type1, name1, name2, entry1, entry2, found);
-        applyOperation<vector>(type1, name1, name2, entry1, entry2, found);
-        applyOperation<sphericalTensor>
-            (type1, name1, name2, entry1, entry2, found);
-        applyOperation<symmTensor>(type1, name1, name2, entry1, entry2, found);
-        applyOperation<tensor>(type1, name1, name2, entry1, entry2, found);
-
-        if (!found)
-        {
-            Log << "Operation between "
-                << name1 << " with result " << entry1 << " and "
-                << name2 << " with result " << entry2 << " not applied"
-                << endl;
-        }
-    }
-
-    Log << (entries1.empty() ? "    none" : "") << endl;
-
-    file()<< endl;
-
-    return true;
-}
-
-
-bool Foam::functionObjects::fieldValues::fieldValueDelta::execute()
-{
-    return true;
-}
-
-
-// ************************************************************************* //
diff --git a/src/functionObjects/field/fieldValues/multiFieldValue/multiFieldValue.C b/src/functionObjects/field/fieldValues/multiFieldValue/multiFieldValue.C
new file mode 100644
index 00000000000..251a9a47da7
--- /dev/null
+++ b/src/functionObjects/field/fieldValues/multiFieldValue/multiFieldValue.C
@@ -0,0 +1,306 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
+   \\    /   O peration     |
+    \\  /    A nd           | www.openfoam.com
+     \\/     M anipulation  |
+-------------------------------------------------------------------------------
+    Copyright (C) 2012-2016 OpenFOAM Foundation
+    Copyright (C) 2015-2021 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 "multiFieldValue.H"
+#include "addToRunTimeSelectionTable.H"
+
+// * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * //
+
+namespace Foam
+{
+namespace functionObjects
+{
+namespace fieldValues
+{
+    defineTypeNameAndDebug(multiFieldValue, 0);
+    addToRunTimeSelectionTable(functionObject, multiFieldValue, dictionary);
+}
+}
+}
+
+
+const Foam::Enum
+<
+    Foam::functionObjects::fieldValues::multiFieldValue::operationType
+>
+Foam::functionObjects::fieldValues::multiFieldValue::operationTypeNames_
+({
+    { operationType::opSum, "sum" },
+    { operationType::opAdd, "add" },
+    { operationType::opSubtract, "subtract" },
+    { operationType::opMin, "min" },
+    { operationType::opMax, "max" },
+    { operationType::opAverage, "average" },
+});
+
+
+// * * * * * * * * * * * * * Protected Member Functions  * * * * * * * * * * //
+
+void Foam::functionObjects::fieldValues::multiFieldValue::writeFileHeader
+(
+    Ostream& os
+) const
+{
+    const wordList& fields0 = functions_[0].fields();
+
+    DynamicList<word> commonFields(fields0.size());
+
+    for (const word& fieldName : fields0)
+    {
+        bool common = true;
+
+        for (label functioni=1; functioni < functions_.size(); ++functioni)
+        {
+            if (!functions_[functioni].fields().found(fieldName))
+            {
+                common = false;
+                break;
+            }
+        }
+
+        if (common)
+        {
+            commonFields.append(fieldName);
+        }
+    }
+
+    forAll(functions_, functioni)
+    {
+        writeHeaderValue
+        (
+            os,
+            "Source" + Foam::name(functioni),
+            functions_[functioni].name()
+        );
+    }
+
+    writeHeaderValue(os, "Operation", operationTypeNames_[operation_]);
+    writeCommented(os, "Time");
+
+    for (const word& fieldName : commonFields)
+    {
+        os  << tab << fieldName;
+    }
+
+    os  << endl;
+}
+
+
+// * * * * * * * * * * * * * * * * Constructors  * * * * * * * * * * * * * * //
+
+Foam::functionObjects::fieldValues::multiFieldValue::multiFieldValue
+(
+    const word& name,
+    const Time& runTime,
+    const dictionary& dict
+)
+:
+    stateFunctionObject(name, runTime),
+    writeFile(runTime, name, typeName, dict),
+    operation_(opSubtract),
+    functions_()
+{
+    if (read(dict))
+    {
+        writeFileHeader(file());
+    }
+}
+
+
+// * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //
+
+bool Foam::functionObjects::fieldValues::multiFieldValue::read
+(
+    const dictionary& dict
+)
+{
+    if (stateFunctionObject::read(dict) && writeFile::read(dict))
+    {
+        const dictionary& functionsDict = dict.subDict("functions");
+        functions_.resize(functionsDict.size());
+
+        if (functions_.empty())
+        {
+            WarningInFunction
+                << "No functions specified"
+                << endl;
+            return false;
+        }
+
+        label functioni = 0;
+        for (const entry& dEntry : functionsDict)
+        {
+            if (!dEntry.isDict())
+            {
+                FatalIOErrorInFunction(dict)
+                    << "Functions must be specified in dictionary format"
+                    << exit(FatalIOError);
+            }
+
+            const dictionary& localDict = dEntry.dict();
+
+            functions_.set
+            (
+                functioni,
+                fieldValue::New
+                (
+                    IOobject::scopedName(name(), localDict.dictName()),
+                    time(),
+                    localDict,
+                    false
+                )
+            );
+
+            ++functioni;
+        }
+
+        operation_ = operationTypeNames_.get("operation", dict);
+
+        return true;
+    }
+
+    return false;
+}
+
+
+bool Foam::functionObjects::fieldValues::multiFieldValue::write()
+{
+    if (functions_.empty())
+    {
+        return false;
+    }
+
+    Log << type() << " " << name() << " write:" << endl;
+
+    const label nFunction = functions_.size();
+    wordList entries0;
+    label nEntries = -1;
+
+    wordList names(nFunction);
+    List<wordList> entries;
+    List<wordList> types;
+
+    forAll(functions_, functioni)
+    {
+        auto& f = functions_[functioni];
+        names[functioni] = f.name();
+
+        // Note: results are not available until the call to write()
+        f.write();
+
+        const wordList e(objectResultEntries(f.name()));
+
+        if (functioni == 0)
+        {
+            entries0 = e;
+            nEntries = e.size();
+            entries.resize(nEntries);
+            types.resize(nEntries);
+
+            forAll(entries, entryi)
+            {
+                entries[entryi].resize(nFunction);
+                types[entryi].resize(nFunction);
+            }
+        }
+
+        if (e.size() != nEntries)
+        {
+            const word& f0Name = functions_[0].name();
+
+            FatalErrorInFunction
+                << "Inconsistent number of result entries" << nl
+                << "    " << f0Name << " entries:" << entries0 << nl
+                << "    " << f.name() << " entries:" << e
+                << abort(FatalError);
+        }
+
+        forAll(e, entryi)
+        {
+            entries[entryi][functioni] = e[entryi];
+            types[entryi][functioni] = objectResultType(f.name(), e[entryi]);
+        }
+    }
+
+    writeCurrentTime(file());
+
+    forAll(entries, entryi)
+    {
+        const wordList& entriesi = entries[entryi];
+        const word& t0 = types[entryi][0];
+        const wordList& typesi = types[entryi];
+        forAll(typesi, functioni)
+        {
+            const word& t = typesi[functioni];
+
+            if (t != t0)
+            {
+                FatalErrorInFunction
+                    << "Inconsistent function result types" << nl
+                    << "    " << functions_[0].name()
+                    << " result type:" << t0 << nl
+                    << "    " << functions_[functioni].name()
+                    << " result type:" << typesi[functioni]
+                    << abort(FatalError);
+            }
+        }
+
+        const bool ok
+        (
+            applyOperation<scalar>(t0, names, entriesi)
+         || applyOperation<vector>(t0, names, entriesi)
+         || applyOperation<sphericalTensor>(t0, names, entriesi)
+         || applyOperation<symmTensor>(t0, names, entriesi)
+         || applyOperation<tensor>(t0, names, entriesi)
+        );
+
+        if (!ok)
+        {
+            Log << "Operation not applied between functions:" << nl
+                << flatOutput(names, FlatOutput::BareComma{}) << nl
+                << "with result names:" << nl
+                << flatOutput(entriesi, FlatOutput::BareComma{})
+                << endl;
+        }
+    }
+
+    Log << (nEntries == 0 ? "    none" : "") << endl;
+
+    file()<< endl;
+
+    return true;
+}
+
+
+bool Foam::functionObjects::fieldValues::multiFieldValue::execute()
+{
+    return true;
+}
+
+
+// ************************************************************************* //
diff --git a/src/functionObjects/field/fieldValues/fieldValueDelta/fieldValueDelta.H b/src/functionObjects/field/fieldValues/multiFieldValue/multiFieldValue.H
similarity index 63%
rename from src/functionObjects/field/fieldValues/fieldValueDelta/fieldValueDelta.H
rename to src/functionObjects/field/fieldValues/multiFieldValue/multiFieldValue.H
index 8f6eea9cb98..d6b73499eb2 100644
--- a/src/functionObjects/field/fieldValues/fieldValueDelta/fieldValueDelta.H
+++ b/src/functionObjects/field/fieldValues/multiFieldValue/multiFieldValue.H
@@ -5,8 +5,7 @@
     \\  /    A nd           | www.openfoam.com
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
-    Copyright (C) 2012-2016 OpenFOAM Foundation
-    Copyright (C) 2015-2020 OpenCFD Ltd.
+    Copyright (C) 2021 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -25,35 +24,50 @@ License
     along with OpenFOAM.  If not, see <http://www.gnu.org/licenses/>.
 
 Class
-    Foam::functionObjects::fieldValues::fieldValueDelta
+    Foam::functionObjects::fieldValues::multiFieldValue
 
 Group
     grpFieldFunctionObjects
 
 Description
-    Computes a selected operation between two \c fieldValue function objects.
+    Computes a selected operation between multiple \c fieldValue function
+    objects.
 
     The operation is applied to all results of each \c fieldValue object.
-    Accordingly, each object must generate the same number and type of results.
+
+Note
+    Each object must generate the same number and type of results.
 
 Usage
     Minimal example by using \c system/controlDict.functions:
     \verbatim
-    fieldValueDelta1
+    multiFieldValue1
     {
         // Mandatory entries (unmodifiable)
-        type            fieldValueDelta;
-        libs            (fieldFunctionObjects);
+        type    multiFieldValue;
+        libs    (fieldFunctionObjects);
 
         // Mandatory entries (runtime modifiable)
-        operation       subtract;
-        region1
-        {
-            ...
-        }
-        region2
+        operation   average;
+
+        // List of fieldValue function objects as dictionaries
+        functions
         {
+            region1
+            {
+                ...
+            }
+            region2
+            {
+                ...
+            }
+
             ...
+
+            regionN
+            {
+                ...
+            }
         }
 
         // Optional (inherited) entries
@@ -63,12 +77,11 @@ Usage
 
     where the entries mean:
     \table
-      Property     | Description                        | Type | Req'd | Dflt
-      type         | Type name: fieldValueDelta         | word |  yes  | -
-      libs         | Library name: fieldFunctionObjects | word |  yes  | -
-      operation    | Operation type to apply to values  | word |  yes  | -
-      region1      | Region1 properties                 | dict |  yes  | -
-      region2      | Region2 properties                 | dict |  yes  | -
+      Property     | Description                         | Type | Req'd | Dflt
+      type         | Type name: multiFieldValue          | word |  yes  | -
+      libs         | Library name: fieldFunctionObjects  | word |  yes  | -
+      operation    | Operation type to apply to values   | word |  yes  | -
+      functions    | List of fieldValue function objects | dict |  yes  | -
     \endtable
 
     Options for the \c operation entry:
@@ -88,16 +101,16 @@ Usage
 See also
     - Foam::functionObject
     - Foam::functionObjects::fieldValue
-    - ExtendedCodeGuide::functionObjects::field::fieldValueDelta
+    - ExtendedCodeGuide::functionObjects::field::multiFieldValue
 
 SourceFiles
-    fieldValueDelta.C
-    fieldValueDeltaTemplates.C
+    multiFieldValue.C
+    multiFieldValueTemplates.C
 
 \*---------------------------------------------------------------------------*/
 
-#ifndef functionObjects_fieldValueDelta_H
-#define functionObjects_fieldValueDelta_H
+#ifndef functionObjects_multiFieldValue_H
+#define functionObjects_multiFieldValue_H
 
 #include "stateFunctionObject.H"
 #include "writeFile.H"
@@ -114,23 +127,27 @@ namespace fieldValues
 {
 
 /*---------------------------------------------------------------------------*\
-                       Class fieldValueDelta Declaration
+                       Class multiFieldValue Declaration
 \*---------------------------------------------------------------------------*/
 
-class fieldValueDelta
+class multiFieldValue
 :
-    public fvMeshFunctionObject,
-    public writeFile
+    public functionObjects::stateFunctionObject,
+    public functionObjects::writeFile
 {
 public:
+
+    // Public Data Types
+
         //- Operation type enumeration
         enum operationType
         {
-            opAdd,              //!< Add
-            opSubtract,         //!< Subtract
-            opMin,              //!< Minimum
-            opMax,              //!< Maximum
-            opAverage           //!< Average
+            opSum,              //!< Sum of values
+            opAdd,              //!< Add values (same as sum)
+            opSubtract,         //!< Subtract values from first entry
+            opMin,              //!< Minimum value
+            opMax,              //!< Maximum value
+            opAverage           //!< Average value
         };
 
         //- Operation type names
@@ -144,25 +161,20 @@ private:
         //- Operation to apply to values
         operationType operation_;
 
-        //- Field value region object 1
-        autoPtr<fieldValue> region1Ptr_;
-
-        //- Field value region object 2
-        autoPtr<fieldValue> region2Ptr_;
+        //- List of fieldValue function objects
+        PtrList<fieldValue> functions_;
 
 
     // Private Member Functions
 
-        //- Templated function to apply the operation
+        //- Templated function to apply the operation.
+        //  \return true if Type and resultType are correct
         template<class Type>
-        void applyOperation
+        bool applyOperation
         (
             const word& resultType,
-            const word& name1,
-            const word& name2,
-            const word& entryName1,
-            const word& entryName2,
-            bool& found
+            const wordList& names,
+            const wordList& entryNames
         );
 
 
@@ -177,13 +189,13 @@ protected:
 public:
 
     //- Run-time type information
-    TypeName("fieldValueDelta");
+    TypeName("multiFieldValue");
 
 
     // Constructors
 
         //- Construct from Time and dictionary
-        fieldValueDelta
+        multiFieldValue
         (
             const word& name,
             const Time& runTime,
@@ -191,20 +203,20 @@ public:
         );
 
         //- No copy construct
-        fieldValueDelta(const fieldValueDelta&) = delete;
+        multiFieldValue(const multiFieldValue&) = delete;
 
         //- No copy assignment
-        void operator=(const fieldValueDelta&) = delete;
+        void operator=(const multiFieldValue&) = delete;
 
 
     //- Destructor
-    virtual ~fieldValueDelta() = default;
+    virtual ~multiFieldValue() = default;
 
 
     // Public Member Functions
 
         //- Read from dictionary
-        virtual bool read(const dictionary&);
+        virtual bool read(const dictionary& dict);
 
         //- Do nothing
         virtual bool execute();
@@ -223,7 +235,7 @@ public:
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
 
 #ifdef NoRepository
-    #include "fieldValueDeltaTemplates.C"
+    #include "multiFieldValueTemplates.C"
 #endif
 
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
diff --git a/src/functionObjects/field/fieldValues/fieldValueDelta/fieldValueDeltaTemplates.C b/src/functionObjects/field/fieldValues/multiFieldValue/multiFieldValueTemplates.C
similarity index 74%
rename from src/functionObjects/field/fieldValues/fieldValueDelta/fieldValueDeltaTemplates.C
rename to src/functionObjects/field/fieldValues/multiFieldValue/multiFieldValueTemplates.C
index 3ef57feddb1..714cb1c48b6 100644
--- a/src/functionObjects/field/fieldValues/fieldValueDelta/fieldValueDeltaTemplates.C
+++ b/src/functionObjects/field/fieldValues/multiFieldValue/multiFieldValueTemplates.C
@@ -6,7 +6,7 @@
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
     Copyright (C) 2012-2016 OpenFOAM Foundation
-    Copyright (C) 2015-2016 OpenCFD Ltd.
+    Copyright (C) 2015-2021 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -26,56 +26,63 @@ License
 
 \*---------------------------------------------------------------------------*/
 
+#include "FlatOutput.H"
+
 // * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //
 
 template<class Type>
-void Foam::functionObjects::fieldValues::fieldValueDelta::applyOperation
+bool Foam::functionObjects::fieldValues::multiFieldValue::applyOperation
 (
     const word& resultType,
-    const word& name1,
-    const word& name2,
-    const word& entryName1,
-    const word& entryName2,
-    bool& found
+    const wordList& names,
+    const wordList& entryNames
 )
 {
     if (pTraits<Type>::typeName != resultType)
     {
-        return;
+        return false;
     }
 
     Type result = Zero;
 
-    Type value1 = this->getObjectResult<Type>(name1, entryName1);
-    Type value2 = this->getObjectResult<Type>(name2, entryName2);
+    Field<Type> values(names.size());
+    forAll(values, i)
+    {
+        values[i] = this->getObjectResult<Type>(names[i], entryNames[i]);
+    }
 
     const word& opName = operationTypeNames_[operation_];
 
     switch (operation_)
     {
+        case opSum:
         case opAdd:
         {
-            result = value1 + value2;
+            result = sum(values);
             break;
         }
         case opSubtract:
         {
-            result = value1 - value2;
+            result = values[0];
+            for (label i = 1; i < values.size(); ++i)
+            {
+                result -= values[i];
+            }
             break;
         }
         case opMin:
         {
-            result = min(value1, value2);
+            result = min(values);
             break;
         }
         case opMax:
         {
-            result = max(value1, value2);
+            result = max(values);
             break;
         }
         case opAverage:
         {
-            result = 0.5*(value1 + value2);
+            result = average(values);
             break;
         }
         default:
@@ -87,8 +94,9 @@ void Foam::functionObjects::fieldValues::fieldValueDelta::applyOperation
         }
     }
 
-    const word resultName(opName + '(' + entryName1 + ',' + entryName2 + ')');
-
+    OStringStream os;
+    os << opName << flatOutput(entryNames, FlatOutput::ParenComma{});
+    const word resultName(os.str());
     Log << "    " << resultName << " = " << result << endl;
 
     this->file()<< tab << result;
@@ -96,7 +104,7 @@ void Foam::functionObjects::fieldValues::fieldValueDelta::applyOperation
     // Write state/results information
     this->setResult(resultName, result);
 
-    found = true;
+    return true;
 }
 
 
diff --git a/src/functionObjects/field/fieldValues/surfaceFieldValue/surfaceFieldValue.C b/src/functionObjects/field/fieldValues/surfaceFieldValue/surfaceFieldValue.C
index 64713a635ab..4d9ff3ab055 100644
--- a/src/functionObjects/field/fieldValues/surfaceFieldValue/surfaceFieldValue.C
+++ b/src/functionObjects/field/fieldValues/surfaceFieldValue/surfaceFieldValue.C
@@ -45,7 +45,7 @@ namespace functionObjects
 namespace fieldValues
 {
     defineTypeNameAndDebug(surfaceFieldValue, 0);
-    addToRunTimeSelectionTable(fieldValue, surfaceFieldValue, dictionary);
+    addToRunTimeSelectionTable(fieldValue, surfaceFieldValue, runTime);
     addToRunTimeSelectionTable(functionObject, surfaceFieldValue, dictionary);
 }
 }
diff --git a/src/functionObjects/field/fieldValues/volFieldValue/volFieldValue.C b/src/functionObjects/field/fieldValues/volFieldValue/volFieldValue.C
index f18041e68a9..9f355a25bb4 100644
--- a/src/functionObjects/field/fieldValues/volFieldValue/volFieldValue.C
+++ b/src/functionObjects/field/fieldValues/volFieldValue/volFieldValue.C
@@ -6,7 +6,7 @@
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
     Copyright (C) 2011-2017 OpenFOAM Foundation
-    Copyright (C) 2017-2020 OpenCFD Ltd.
+    Copyright (C) 2017-2021 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -40,7 +40,7 @@ namespace functionObjects
 namespace fieldValues
 {
     defineTypeNameAndDebug(volFieldValue, 0);
-    addToRunTimeSelectionTable(fieldValue, volFieldValue, dictionary);
+    addToRunTimeSelectionTable(fieldValue, volFieldValue, runTime);
     addToRunTimeSelectionTable(functionObject, volFieldValue, dictionary);
 }
 }
diff --git a/tutorials/lagrangian/reactingParcelFoam/filter/system/controlDict b/tutorials/lagrangian/reactingParcelFoam/filter/system/controlDict
index 735b83ad32f..c196a53845c 100644
--- a/tutorials/lagrangian/reactingParcelFoam/filter/system/controlDict
+++ b/tutorials/lagrangian/reactingParcelFoam/filter/system/controlDict
@@ -1,7 +1,7 @@
 /*--------------------------------*- C++ -*----------------------------------*\
 | =========                 |                                                 |
 | \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox           |
-|  \\    /   O peration     | Version:  v2012                                 |
+|  \\    /   O peration     | Version:  v2106                                 |
 |   \\  /    A nd           | Website:  www.openfoam.com                      |
 |    \\/     M anipulation  |                                                 |
 \*---------------------------------------------------------------------------*/
@@ -55,6 +55,7 @@ functions
     #include "dataCloud"
     #include "vtkCloud"
     #include "vtkWrite"
+    #include "pressureDifference"
 }
 
 
diff --git a/tutorials/lagrangian/reactingParcelFoam/filter/system/pressureDifference b/tutorials/lagrangian/reactingParcelFoam/filter/system/pressureDifference
new file mode 100644
index 00000000000..64332980a9b
--- /dev/null
+++ b/tutorials/lagrangian/reactingParcelFoam/filter/system/pressureDifference
@@ -0,0 +1,40 @@
+// -*- C++ -*-
+
+pressureDifference
+{
+    type    multiFieldValue;
+    libs    (fieldFunctionObjects);
+
+    operation   subtract;
+
+    functions
+    {
+        inlet
+        {
+            type            surfaceFieldValue;
+            operation       areaAverage;
+            regionType      patch;
+            name            inlet;
+            fields          (p);
+
+            writeFields     no;
+            writeToFile     no;
+            log             no;
+        }
+        outlet
+        {
+            type            surfaceFieldValue;
+            operation       areaAverage;
+            regionType      patch;
+            name            outlet;
+            fields          (p);
+
+            writeFields     no;
+            writeToFile     no;
+            log             no;
+        }
+    }
+}
+
+
+// ************************************************************************* //
-- 
GitLab