From 510ffb332219fd55b158441dbfcb4509868a9b2b Mon Sep 17 00:00:00 2001
From: Mark Olesen <Mark.Olesen@esi-group.com>
Date: Tue, 7 Dec 2021 18:06:45 +0100
Subject: [PATCH] ENH: code reduction, improvements for expressions

- literal lookups only for expression strings

- code reduction for setExprFields.

- changed keyword "condition" to "fieldMask" (option -field-mask).
  This is a better description of its purpose and avoids possible
  naming ambiguities with functionObject triggers (for example)
  if we apply similar syntax elsewhere.

BUG: erroneous check in volumeExpr::parseDriver::isResultType()

- not triggered since this method is not used anywhere
  (may remove in future version)
---
 .../setExprBoundaryFields.C                   |  15 +-
 .../setExprFields/setExprFields.C             | 396 ++++++++----------
 .../setExprFields/setExprFieldsDict           |   4 +-
 etc/caseDicts/annotated/setExprFieldsDict     |   4 +-
 .../Function1/Function1Expression.C           |   4 +-
 .../expressions/exprDriver/exprDriver.H       |  13 +-
 .../expressions/exprResult/exprResult.H       |   9 +-
 .../expressions/exprString/exprString.C       |  56 ++-
 .../expressions/exprString/exprString.H       |  34 +-
 .../expressions/fields/fieldExprDriver.H      |   4 +-
 .../PatchFunction1/PatchFunction1Expression.C |   4 +-
 .../expressions/base/fvExprDriver.H           |   6 +-
 .../fields/base/patchExprFieldBase.C          |  11 +-
 .../expressions/patch/patchExprDriver.H       |   4 +-
 .../expressions/volume/volumeExprDriver.C     | 108 ++++-
 .../expressions/volume/volumeExprDriver.H     |  22 +-
 .../expressions/volume/volumeExprDriverI.H    |  14 +
 .../volume/volumeExprDriverTemplates.C        |  26 +-
 .../TJunctionAverage/system/setExprFieldsDict |   2 +-
 19 files changed, 441 insertions(+), 295 deletions(-)

diff --git a/applications/utilities/preProcessing/setExprBoundaryFields/setExprBoundaryFields.C b/applications/utilities/preProcessing/setExprBoundaryFields/setExprBoundaryFields.C
index 5cafecddffe..b421782f290 100644
--- a/applications/utilities/preProcessing/setExprBoundaryFields/setExprBoundaryFields.C
+++ b/applications/utilities/preProcessing/setExprBoundaryFields/setExprBoundaryFields.C
@@ -231,18 +231,21 @@ int main(int argc, char *argv[])
 
                 dictionary& patchDict = boundaryFieldDict.subDict(patchName);
 
-                expressions::exprString expr
+                auto valueExpr_
                 (
-                    currDict.get<string>("expression"),
-                    currDict,
-                    true  // strip comments
+                    expressions::exprString::getEntry
+                    (
+                        "expression",
+                        currDict,
+                        true  // strip comments
+                    )
                 );
 
                 Info<< "Set boundaryField/" << patchName << '/'
                     << targetName << nl
                     << "with expression" << nl
                     << "<<<<" << nl
-                    << expr.c_str() << nl
+                    << valueExpr_.c_str() << nl
                     << ">>>>" << nl;
 
                 expressions::patchExprDriver driver(currDict, mesh);
@@ -255,7 +258,7 @@ int main(int argc, char *argv[])
                 );
 
                 driver.clearVariables();
-                driver.parse(expr);
+                driver.parse(valueExpr_);
 
                 // Serializing via Field::writeEntry etc
                 OStringStream serialize;
diff --git a/applications/utilities/preProcessing/setExprFields/setExprFields.C b/applications/utilities/preProcessing/setExprFields/setExprFields.C
index 402b6679f5c..98be5699bb8 100644
--- a/applications/utilities/preProcessing/setExprFields/setExprFields.C
+++ b/applications/utilities/preProcessing/setExprFields/setExprFields.C
@@ -64,7 +64,6 @@ word fieldGeoType(const FieldAssociation geoType)
         case FieldAssociation::VOLUME_DATA : return "cells"; break;
         default: break;
     }
-
     return "unknown";
 }
 
@@ -75,7 +74,7 @@ struct setExprFieldsControl
     bool dryRun;
     bool debugParsing;
     bool cacheVariables;
-    bool useDimensions;
+    bool hasDimensions;
     bool createNew;
     bool keepPatches;
     bool correctPatches;
@@ -123,13 +122,12 @@ void doCorrectBoundaryConditions
 {}
 
 
-template<class GeoField, class Mesh>
-void setField
+template<class GeoField>
+bool setField
 (
     const word& fieldName,
-    const Mesh& mesh,
-    const GeoField& result,
-    const scalarField& cond,
+    const GeoField& evaluated,
+    const boolField& fieldMask,
     const dimensionSet& dims,
     const wordList& valuePatches,
 
@@ -139,6 +137,8 @@ void setField
     Info<< "setField(" << fieldName << "): "
         << pTraits<GeoField>::typeName << endl;
 
+    const auto& mesh = evaluated.mesh();
+
     tmp<GeoField> toutput;
 
     if (ctrl.createNew)
@@ -171,55 +171,57 @@ void setField
 
     auto& output = toutput.ref();
 
-    label setCells = 0;
+    label numValuesChanged = 0;
 
-    if (cond.empty())
+    // Internal field
+    if (fieldMask.empty())
     {
-        // No condition - set all
-        output = result;
+        // No field-mask - set entire internal field
+        numValuesChanged = output.size();
 
-        setCells = output.size();
+        output.primitiveFieldRef() = evaluated;
     }
     else
     {
-        forAll(output, celli)
+        auto& internal = output.primitiveFieldRef();
+
+        forAll(internal, idx)
         {
-            if (expressions::boolOp<scalar>()(cond[celli]))
+            if (fieldMask[idx])
             {
-                output[celli] = result[celli];
-                ++setCells;
+                internal[idx] = evaluated[idx];
+                ++numValuesChanged;
             }
         }
     }
 
-    const label totalCells = returnReduce(output.size(), plusOp<label>());
-    reduce(setCells, plusOp<label>());
-
-    forAll(result.boundaryField(), patchi)
+    // Boundary fields
+    forAll(evaluated.boundaryField(), patchi)
     {
         auto& pf = output.boundaryFieldRef()[patchi];
 
         if (pf.patch().coupled())
         {
-            pf == result.boundaryField()[patchi];
+            pf == evaluated.boundaryField()[patchi];
         }
     }
 
+    doCorrectBoundaryConditions(ctrl.correctBCs, output);
+
+    const label numTotal = returnReduce(output.size(), plusOp<label>());
+    reduce(numValuesChanged, plusOp<label>());
 
-    if (setCells == totalCells)
+    if (numValuesChanged == numTotal)
     {
         Info<< "Set all ";
     }
     else
     {
-        Info<< "Set " << setCells << " of ";
+        Info<< "Set " << numValuesChanged << " of ";
     }
-    Info<< totalCells << " cells" << endl;
-
-
-    doCorrectBoundaryConditions(ctrl.correctBCs, output);
+    Info<< numTotal << " values" << endl;
 
-    if (ctrl.useDimensions)
+    if (ctrl.hasDimensions)
     {
         Info<< "Setting dimensions to " << dims << endl;
         output.dimensions().reset(dims);
@@ -234,6 +236,8 @@ void setField
         Info<< "Writing to " << output.name() << nl;
         output.writeObject(ctrl.streamOpt, true);
     }
+
+    return true;
 }
 
 
@@ -241,8 +245,8 @@ void evaluate
 (
     const fvMesh& mesh,
     const word& fieldName,
-    const expressions::exprString& expression,
-    const expressions::exprString& condition,
+    const expressions::exprString& valueExpr_,
+    const expressions::exprString& maskExpr_,
     const dictionary& dict,
     const dimensionSet& dims,
     const wordList& valuePatches,
@@ -273,10 +277,8 @@ void evaluate
         if (oldFieldType == IOobject::typeName)
         {
             FatalErrorInFunction
-                << "Field " << fieldName << " is  "
-                << oldFieldType
-                << ". Seems that it does not exist. Use 'create'"
-                << nl
+                << "Field " << fieldName << "(type: " << oldFieldType
+                << ") seems to be missing. Use 'create'" << nl
                 << exit(FatalError);
         }
 
@@ -284,17 +286,21 @@ void evaluate
             << " (type " << oldFieldType << ')';
     }
 
+
     Info<< " time=" << mesh.thisDb().time().timeName() << nl
         << "Expression:" << nl
         << ">>>>" << nl
-        << expression.c_str() << nl
+        << valueExpr_.c_str() << nl
         << "<<<<" << nl;
 
-    if (condition.size() && condition != "true")
+    bool evalFieldMask =
+        (maskExpr_.size() && maskExpr_ != "true" && maskExpr_ != "1");
+
+    if (evalFieldMask)
     {
-        Info<< "Condition:" << nl
+        Info<< "field-mask:" << nl
             << ">>>>" << nl
-            << condition.c_str() << nl
+            << maskExpr_.c_str() << nl
             << "<<<<" << nl;
     }
 
@@ -318,129 +324,95 @@ void evaluate
 
     if (ctrl.debugParsing)
     {
-        Info<< "Parsing expression: " << expression << "\nand condition "
-            << condition << nl << endl;
+        Info<< "Parsing expression: " << valueExpr_ << "\nand field-mask "
+            << maskExpr_ << nl << endl;
         driver.setDebugging(true, true);
     }
 
 
     driver.clearVariables();
 
-    scalarField conditionField;
 
-    bool evaluatedCondition = false;
+    // Handle "field-mask" evaluation
 
-    FieldAssociation conditionDataType(FieldAssociation::VOLUME_DATA);
+    boolField fieldMask;
+    FieldAssociation maskFieldAssoc(FieldAssociation::NO_DATA);
 
-    if (condition.size() && condition != "true")
+    if (evalFieldMask)
     {
         if (ctrl.debugParsing)
         {
-            Info<< "Parsing condition:" << condition << endl;
+            Info<< "Parsing field-mask:" << maskExpr_ << endl;
         }
 
-        driver.parse(condition);
+        driver.parse(maskExpr_);
         if (ctrl.debugParsing)
         {
-            Info<< "Parsed condition" << endl;
+            Info<< "Parsed field-mask" << endl;
         }
 
-        // Process any/all scalar fields. May help with diagnosis
-
-        bool goodCond = true;
-        while (goodCond)
+        if (driver.isLogical())
         {
-            // volScalarField
-            {
-                const auto* ptr = driver.isResultType<volScalarField>();
-                if (ptr)
-                {
-                    conditionField = ptr->internalField();
-                    // VOLUME_DATA
-                    break;
-                }
-            }
-
-            // surfaceScalarField
-            {
-                const auto* ptr = driver.isResultType<surfaceScalarField>();
-                if (ptr)
-                {
-                    conditionField = ptr->internalField();
-                    conditionDataType = FieldAssociation::FACE_DATA;
-                    break;
-                }
-            }
-
-            // pointScalarField
+            auto& result = driver.result();
+            if (result.is_bool())
             {
-                const auto* ptr = driver.isResultType<pointScalarField>();
-                if (ptr)
-                {
-                    conditionField = ptr->internalField();
-                    conditionDataType = FieldAssociation::POINT_DATA;
-                    break;
-                }
+                fieldMask = result.getResult<bool>();
+                maskFieldAssoc = driver.fieldAssociation();
             }
-
-            // No matching field types
-            goodCond = false;
         }
 
-        // Verify that it also logical
-        goodCond = goodCond && driver.isLogical();
+        // Slightly pedantic...
+        driver.clearField();
+        driver.clearResult();
 
-        if (!goodCond)
+        evalFieldMask = (maskFieldAssoc != FieldAssociation::NO_DATA);
+
+        if (!evalFieldMask)
         {
             FatalErrorInFunction
-                << " condition: " << condition
+                << " mask: " << maskExpr_
                 << " does not evaluate to a logical expression: "
                 << driver.resultType() << nl
                 #ifdef FULLDEBUG
-                << "contents: " << conditionField
+                << "contents: " << fieldMask
                 #endif
                 << exit(FatalError);
         }
 
         if (ctrl.debugParsing)
         {
-            Info<< "Condition evaluates to "
-                << conditionField << nl;
+            Info<< "Field-mask evaluates to "
+                << fieldMask << nl;
         }
-
-        evaluatedCondition = true;
     }
 
     if (ctrl.debugParsing)
     {
-        Info<< "Parsing expression:" << expression << endl;
+        Info<< "Parsing expression:" << valueExpr_ << endl;
     }
 
-    driver.parse(expression);
+    driver.parse(valueExpr_);
 
     if (ctrl.debugParsing)
     {
         Info<< "Parsed expression" << endl;
     }
 
-    if (evaluatedCondition)
+    if (evalFieldMask && maskFieldAssoc != driver.fieldAssociation())
     {
-        if (conditionDataType != driver.fieldAssociation())
-        {
-            FatalErrorInFunction
-                << "Mismatch between condition geometric type ("
-                << fieldGeoType(conditionDataType) << ") and" << nl
-                << "expression geometric type ("
-                << fieldGeoType(driver.fieldAssociation()) << ')' << nl
-                << nl
-                << "Expression: " << expression << nl
-                << "Condition: " << condition << nl
-                << nl
-                << exit(FatalError);
-        }
+        FatalErrorInFunction
+            << "Mismatch between field-mask geometric type ("
+            << fieldGeoType(maskFieldAssoc) << ") and" << nl
+            << "expression geometric type ("
+            << fieldGeoType(driver.fieldAssociation()) << ')' << nl
+            << nl
+            << "expression: " << valueExpr_ << nl
+            << "field-mask: " << maskExpr_ << nl
+            << nl
+            << exit(FatalError);
     }
 
-    if (!ctrl.createNew && driver.resultType() != oldFieldType)
+    if (!oldFieldType.empty() && driver.resultType() != oldFieldType)
     {
         FatalErrorInFunction
             << "Inconsistent types: " << fieldName << " is  "
@@ -452,81 +424,69 @@ void evaluate
 
     Info<< "Dispatch ... " << driver.resultType() << nl;
 
-    #undef setFieldDispatch
-    #define setFieldDispatch(FieldType)                                       \
-    {                                                                         \
-        /* FieldType */                                                       \
-        const auto* ptr = driver.isResultType<FieldType>();                   \
-        if (ptr)                                                              \
-        {                                                                     \
-            /* driver.getResult<FieldType>(correctPatches), */                \
-                                                                              \
-            setField                                                          \
-            (                                                                 \
-                fieldName,                                                    \
-                mesh,                                                         \
-                *ptr,                                                         \
-                conditionField,                                               \
-                dims,                                                         \
-                valuePatches,                                                 \
-                ctrl                                                          \
-            );                                                                \
-            return;                                                           \
-        }                                                                     \
-    }                                                                         \
-
-
-    setFieldDispatch(volScalarField);
-    setFieldDispatch(volVectorField);
-    setFieldDispatch(volTensorField);
-    setFieldDispatch(volSymmTensorField);
-    setFieldDispatch(volSphericalTensorField);
-
-    setFieldDispatch(surfaceScalarField);
-    setFieldDispatch(surfaceVectorField);
-    setFieldDispatch(surfaceTensorField);
-    setFieldDispatch(surfaceSymmTensorField);
-    setFieldDispatch(surfaceSphericalTensorField);
-
-    #undef setFieldDispatch
-    #define setFieldDispatch(FieldType)                                       \
-    {                                                                         \
-        /* FieldType */                                                       \
-        const auto* ptr = driver.isResultType<FieldType>();                   \
-                                                                              \
-        if (ptr)                                                              \
-        {                                                                     \
-            /* driver.getResult<FieldType>(correctPatches), */                \
-                                                                              \
-            setField                                                          \
-            (                                                                 \
-                fieldName,                                                    \
-                pointMesh::New(mesh),                                         \
-                *ptr,                                                         \
-                conditionField,                                               \
-                dims,                                                         \
-                valuePatches,                                                 \
-                ctrl                                                          \
-            );                                                                \
-            return;                                                           \
-        }                                                                     \
-    }                                                                         \
-
-    setFieldDispatch(pointScalarField);
-    setFieldDispatch(pointVectorField);
-    setFieldDispatch(pointTensorField);
-    setFieldDispatch(pointSymmTensorField);
-    setFieldDispatch(pointSphericalTensorField);
-
-    #undef setFieldDispatch
-
-    // Nothing dispatched?
-
-    FatalErrorInFunction
-        << "Expression evaluates to an unsupported type: "
-        << driver.resultType() << nl << nl
-        << "Expression " << expression << nl << endl
-        << exit(FatalError);
+
+    bool applied = false;
+    switch (driver.fieldAssociation())
+    {
+        #undef  doLocalCode
+        #define doLocalCode(GeoField)                                        \
+        {                                                                    \
+            const auto* ptr = driver.isResultType<GeoField>();               \
+            if (ptr)                                                         \
+            {                                                                \
+                applied = setField                                           \
+                (                                                            \
+                    fieldName,                                               \
+                    *ptr,                                                    \
+                    fieldMask,                                               \
+                    dims,                                                    \
+                    valuePatches,                                            \
+                    ctrl                                                     \
+                );                                                           \
+                break;                                                       \
+            }                                                                \
+        }
+
+        case FieldAssociation::VOLUME_DATA:
+        {
+            doLocalCode(volScalarField);
+            doLocalCode(volVectorField);
+            doLocalCode(volTensorField);
+            doLocalCode(volSymmTensorField);
+            doLocalCode(volSphericalTensorField);
+            break;
+        }
+        case FieldAssociation::FACE_DATA:
+        {
+            doLocalCode(surfaceScalarField);
+            doLocalCode(surfaceVectorField);
+            doLocalCode(surfaceTensorField);
+            doLocalCode(surfaceSymmTensorField);
+            doLocalCode(surfaceSphericalTensorField);
+            break;
+        }
+        case FieldAssociation::POINT_DATA:
+        {
+            doLocalCode(pointScalarField);
+            doLocalCode(pointVectorField);
+            doLocalCode(pointTensorField);
+            doLocalCode(pointSymmTensorField);
+            doLocalCode(pointSphericalTensorField);
+            break;
+        }
+
+        default: break;
+        #undef doLocalCode
+    }
+
+    if (!applied)
+    {
+        FatalErrorInFunction
+            << "Expression evaluates to an unsupported type: "
+            << driver.resultType() << nl << nl
+            << "Expression " << valueExpr_ << nl << endl
+            << exit(FatalError);
+    }
 }
 
 
@@ -584,12 +544,13 @@ int main(int argc, char *argv[])
     );
     argList::addOption
     (
-        "condition",
+        "field-mask",
         "logic",
-        "The logical condition when to apply the expression"
+        "The field mask (logical condition) when to apply the expression"
         " (command-line operation)",
         true // Advanced option
     );
+    argList::addOptionCompat("field-mask", {"condition", 2106});
     argList::addOption
     (
         "dimensions",
@@ -726,7 +687,7 @@ int main(int argc, char *argv[])
         wordHashSet badOptions
         ({
             "create", "keepPatches", "value-patches",
-            "condition", "expression", "dimensions"
+            "field-mask", "expression", "dimensions"
         });
         badOptions.retain(args.options());
 
@@ -802,25 +763,24 @@ int main(int argc, char *argv[])
             ctrl.keepPatches = args.found("keepPatches");
             ctrl.correctPatches = !args.found("noCorrectPatches");
             ctrl.correctBCs = args.found("correctResultBoundaryFields");
-            ctrl.useDimensions = args.found("dimensions");
+            ctrl.hasDimensions = args.found("dimensions");
             ctrl.streamOpt.format(runTime.writeFormat());
             if (args.found("ascii"))
             {
                 ctrl.streamOpt.format(IOstream::ASCII);
             }
 
-            expressions::exprString
-                expression
-                (
-                    args["expression"],
-                    dictionary::null
-                );
+            expressions::exprString valueExpr_
+            (
+                args["expression"],
+                dictionary::null
+            );
 
-            expressions::exprString condition;
-            args.readIfPresent("condition", condition);
+            expressions::exprString maskExpr_;
+            args.readIfPresent("field-mask", maskExpr_);
 
             dimensionSet dims;
-            if (ctrl.useDimensions)
+            if (ctrl.hasDimensions)
             {
                 ITstream is(args.lookup("dimensions"));
                 is >> dims;
@@ -830,8 +790,8 @@ int main(int argc, char *argv[])
             (
                 mesh,
                 fieldName,
-                expression,
-                condition,
+                valueExpr_,
+                maskExpr_,
                 dictionary::null,
                 dims,
                 args.getList<word>("value-patches", false),
@@ -899,22 +859,28 @@ int main(int argc, char *argv[])
 
                 const word fieldName(dict.get<word>("field"));
 
-                expressions::exprString expression
+                auto valueExpr_
                 (
-                    dict.get<string>("expression"),
-                    dict
+                    expressions::exprString::getEntry
+                    (
+                        "expression",
+                        dict
+                    )
                 );
 
-                expressions::exprString condition;
-
-                if (dict.found("condition"))
+                expressions::exprString maskExpr_;
                 {
-                    condition =
-                        expressions::exprString
-                        (
-                            dict.get<string>("condition"),
-                            dict
-                        );
+                    const entry* eptr = dict.findCompat
+                    (
+                        "fieldMask", {{"condition", 2106}},
+                        keyType::LITERAL
+                    );
+
+                    if (eptr)
+                    {
+                        maskExpr_.readEntry(eptr->keyword(), dict);
+                        maskExpr_.trim();
+                    }
                 }
 
                 dimensionSet dims;
@@ -928,7 +894,7 @@ int main(int argc, char *argv[])
                     {
                         dimPtr->stream() >> dims;
                     }
-                    ctrl.useDimensions = bool(dimPtr);
+                    ctrl.hasDimensions = bool(dimPtr);
                 }
 
                 if (args.verbose() && !timei)
@@ -941,8 +907,8 @@ int main(int argc, char *argv[])
                 (
                     mesh,
                     fieldName,
-                    expression,
-                    condition,
+                    valueExpr_,
+                    maskExpr_,
                     dict,
                     dims,
                     dict.getOrDefault<wordList>("valuePatches", wordList()),
diff --git a/applications/utilities/preProcessing/setExprFields/setExprFieldsDict b/applications/utilities/preProcessing/setExprFields/setExprFieldsDict
index 834dfb55d6c..d555cc989e7 100644
--- a/applications/utilities/preProcessing/setExprFields/setExprFieldsDict
+++ b/applications/utilities/preProcessing/setExprFields/setExprFieldsDict
@@ -1,7 +1,7 @@
 /*--------------------------------*- C++ -*----------------------------------*\
 | =========                 |                                                 |
 | \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox           |
-|  \\    /   O peration     | Version:  v2106                                 |
+|  \\    /   O peration     | Version:  v2112                                 |
 |   \\  /    A nd           | Website:  www.openfoam.com                      |
 |    \\/     M anipulation  |                                                 |
 \*---------------------------------------------------------------------------*/
@@ -31,7 +31,7 @@ expressions
             "radius = 0.1"
         );
 
-        condition
+        fieldMask
         #{
             // Within the radius
             (mag(pos() - $[(vector)constants.centre]) < radius)
diff --git a/etc/caseDicts/annotated/setExprFieldsDict b/etc/caseDicts/annotated/setExprFieldsDict
index f5672ed2a62..98196eafeb7 100644
--- a/etc/caseDicts/annotated/setExprFieldsDict
+++ b/etc/caseDicts/annotated/setExprFieldsDict
@@ -1,7 +1,7 @@
 /*--------------------------------*- C++ -*----------------------------------*\
 | =========                 |                                                 |
 | \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox           |
-|  \\    /   O peration     | Version:  v2106                                 |
+|  \\    /   O peration     | Version:  v2112                                 |
 |   \\  /    A nd           | Website:  www.openfoam.com                      |
 |    \\/     M anipulation  |                                                 |
 \*---------------------------------------------------------------------------*/
@@ -34,7 +34,7 @@ expressions
             "radius = 0.1"
         );
 
-        condition
+        fieldMask
         #{
             // Within the radius
             (mag(pos() - $[(vector)constants.centre]) < radius)
diff --git a/src/OpenFOAM/expressions/Function1/Function1Expression.C b/src/OpenFOAM/expressions/Function1/Function1Expression.C
index a0461b59f66..bbd05ce3047 100644
--- a/src/OpenFOAM/expressions/Function1/Function1Expression.C
+++ b/src/OpenFOAM/expressions/Function1/Function1Expression.C
@@ -47,9 +47,7 @@ Foam::Function1Types::Function1Expression<Type>::Function1Expression
         debug |= 1;
     }
 
-    string expr;
-    dict_.readEntry("expression", expr);
-    valueExpr_ = expressions::exprString(std::move(expr), dict_);
+    valueExpr_.readEntry("expression", dict_);
 
     // Basic sanity
     if (valueExpr_.empty())
diff --git a/src/OpenFOAM/expressions/exprDriver/exprDriver.H b/src/OpenFOAM/expressions/exprDriver/exprDriver.H
index 11590e51a3b..3db249aff80 100644
--- a/src/OpenFOAM/expressions/exprDriver/exprDriver.H
+++ b/src/OpenFOAM/expressions/exprDriver/exprDriver.H
@@ -314,13 +314,13 @@ public:
 
     // Public Member Functions
 
-        //- The underlying field size for the expression
+        //- The natural field size for the expression
         virtual label size() const
         {
             return 1;
         }
 
-        //- The underlying point field size for the expression
+        //- The point field size for the expression
         virtual label pointSize() const
         {
             return 1;
@@ -358,6 +358,7 @@ public:
         void clearResult();
 
         //- Return the expression result as a tmp field
+        //  This also clears the result and associated memory.
         template<class Type>
         tmp<Field<Type>> getResult(bool wantPointData=false);
 
@@ -426,7 +427,7 @@ public:
 
     // Variables
 
-        //- Clear temporary variables and resets from expression strings
+        //- Clear temporary variables, reset from expression strings
         virtual void clearVariables();
 
         //- Set special-purpose scalar reference argument.
@@ -555,7 +556,8 @@ public:
             size_t len = std::string::npos
         ) = 0;
 
-        //- Evaluate the expression and return the field
+        //- Evaluate the expression and return the field.
+        //  This also clears the result and associated memory.
         template<class Type>
         inline tmp<Field<Type>>
         evaluate
@@ -564,7 +566,8 @@ public:
             bool wantPointData = false
         );
 
-        //- Evaluate the expression and return a single value
+        //- Evaluate the expression and return a single value.
+        //  Does not clear the result.
         template<class Type>
         inline Type evaluateUniform
         (
diff --git a/src/OpenFOAM/expressions/exprResult/exprResult.H b/src/OpenFOAM/expressions/exprResult/exprResult.H
index 3e9d19f6bc8..793381351fd 100644
--- a/src/OpenFOAM/expressions/exprResult/exprResult.H
+++ b/src/OpenFOAM/expressions/exprResult/exprResult.H
@@ -415,10 +415,10 @@ public:
         void clear();
 
         //- Change reset behaviour
-        void noReset() { noReset_ = true; }
+        void noReset() noexcept { noReset_ = true; }
 
         //- Change reset behaviour
-        void allowReset() { noReset_ = false; }
+        void allowReset() noexcept { noReset_ = false; }
 
         //- Test if field corresponds to a single-value and thus uniform.
         //  Uses field min/max to establish uniformity.
@@ -468,11 +468,6 @@ public:
         template<class Type>
         inline tmp<Field<Type>> getResult(bool cacheCopy=false);
 
-        //- Get object result (Caution - potentially fragile)
-        //- optionally keeping a copy in cache
-        template<class Type>
-        inline tmp<Type> getObjectResult(bool cacheCopy=false);
-
         //- Construct a uniform field from the current results
         //  Uses the field average. Optionally warning if the min/max
         //  deviation is larger than SMALL.
diff --git a/src/OpenFOAM/expressions/exprString/exprString.C b/src/OpenFOAM/expressions/exprString/exprString.C
index 43cbbb36084..5fd48fb4e3b 100644
--- a/src/OpenFOAM/expressions/exprString/exprString.C
+++ b/src/OpenFOAM/expressions/exprString/exprString.C
@@ -48,20 +48,30 @@ void Foam::expressions::exprString::inplaceExpand
 
 
 Foam::expressions::exprString
-Foam::expressions::exprString::getExpression
+Foam::expressions::exprString::getEntry
 (
-    const word& name,
+    const word& key,
     const dictionary& dict,
     const bool stripComments
 )
 {
-    string orig(dict.get<string>(name));
+    exprString expr;
+    expr.readEntry(key, dict, true, stripComments);  // mandatory
 
-    // No validation
-    expressions::exprString expr;
-    expr.assign(std::move(orig));
+    return expr;
+}
 
-    inplaceExpand(expr, dict, stripComments);
+
+Foam::expressions::exprString
+Foam::expressions::exprString::getOptional
+(
+    const word& key,
+    const dictionary& dict,
+    const bool stripComments
+)
+{
+    exprString expr;
+    expr.readEntry(key, dict, false, stripComments);  // optional
 
     return expr;
 }
@@ -69,8 +79,7 @@ Foam::expressions::exprString::getExpression
 
 // * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //
 
-Foam::expressions::exprString&
-Foam::expressions::exprString::expand
+void Foam::expressions::exprString::expand
 (
     const dictionary& dict,
     const bool stripComments
@@ -81,8 +90,35 @@ Foam::expressions::exprString::expand
     #ifdef FULLDEBUG
     (void)valid();
     #endif
+}
+
+
+void Foam::expressions::exprString::trim()
+{
+    stringOps::inplaceTrim(*this);
+}
 
-    return *this;
+
+bool Foam::expressions::exprString::readEntry
+(
+    const word& keyword,
+    const dictionary& dict,
+    bool mandatory,
+    const bool stripComments
+)
+{
+    const bool ok = dict.readEntry(keyword, *this, keyType::LITERAL, mandatory);
+
+    if (ok && !empty())
+    {
+        this->expand(dict, stripComments);  // strip comments
+    }
+    else
+    {
+        clear();
+    }
+
+    return ok;
 }
 
 
diff --git a/src/OpenFOAM/expressions/exprString/exprString.H b/src/OpenFOAM/expressions/exprString/exprString.H
index f1d5bef94fb..84ac3eccc3e 100644
--- a/src/OpenFOAM/expressions/exprString/exprString.H
+++ b/src/OpenFOAM/expressions/exprString/exprString.H
@@ -135,13 +135,22 @@ public:
 
         //- Get and expand expression with dictionary entries,
         //- optionally strip C/C++ comments from the input.
-        //
         // Expansion behaviour as per inplaceExpand
-        static exprString getExpression
+        static exprString getEntry
+        (
+            const word& keyword,  //!< Lookup key. Uses LITERAL (not REGEX)
+            const dictionary& dict,
+            const bool stripComments = true
+        );
+
+        //- Get and expand expression with dictionary entries,
+        //- optionally strip C/C++ comments from the input.
+        // Expansion behaviour as per inplaceExpand
+        static exprString getOptional
         (
-            const word& name,
+            const word& keyword,  //!< Lookup key. Uses LITERAL (not REGEX)
             const dictionary& dict,
-            const bool stripComments = false
+            const bool stripComments = true
         );
 
         //- Copy convert string to exprString.
@@ -155,17 +164,26 @@ public:
 
     // Member Functions
 
+        //- Check for unexpanded '$' entries. Fatal if any exist.
+        inline bool valid() const;
+
         //- Inplace expansion with dictionary variables,
         //- and strip C/C++ comments from the input
-        exprString& expand
+        void expand(const dictionary& dict, const bool stripComments = true);
+
+        //- Inplace trim leading and trailing whitespace
+        void trim();
+
+        //- Read/expand entry with dictionary variables,
+        //- and strip C/C++ comments from the input
+        bool readEntry
         (
+            const word& keyword,  //!< Lookup key. Uses LITERAL (not REGEX)
             const dictionary& dict,
+            bool mandatory = true,
             const bool stripComments = true
         );
 
-        //- Check for unexpanded '$' entries. Fatal if any exist.
-        inline bool valid() const;
-
 
     // Member Operators
 
diff --git a/src/OpenFOAM/expressions/fields/fieldExprDriver.H b/src/OpenFOAM/expressions/fields/fieldExprDriver.H
index 70ae0d2d91b..657bcb5ee4a 100644
--- a/src/OpenFOAM/expressions/fields/fieldExprDriver.H
+++ b/src/OpenFOAM/expressions/fields/fieldExprDriver.H
@@ -118,13 +118,13 @@ public:
 
     // Public Member Functions
 
-        //- The underlying field size for the expression
+        //- The natural field size for the expression
         virtual label size() const
         {
             return size_;
         }
 
-        //- The underlying point field size for the expression
+        //- The point field size for the expression
         virtual label pointSize() const
         {
             return size_;
diff --git a/src/finiteVolume/expressions/PatchFunction1/PatchFunction1Expression.C b/src/finiteVolume/expressions/PatchFunction1/PatchFunction1Expression.C
index 72e19ce8a22..0c200969a04 100644
--- a/src/finiteVolume/expressions/PatchFunction1/PatchFunction1Expression.C
+++ b/src/finiteVolume/expressions/PatchFunction1/PatchFunction1Expression.C
@@ -50,9 +50,7 @@ Foam::PatchFunction1Types::PatchExprField<Type>::PatchExprField
         debug |= 1;
     }
 
-    string expr;
-    dict_.readEntry("expression", expr);
-    valueExpr_ = expressions::exprString(std::move(expr), dict_);
+    valueExpr_.readEntry("expression", dict_);
 
     // Basic sanity
     if (valueExpr_.empty())
diff --git a/src/finiteVolume/expressions/base/fvExprDriver.H b/src/finiteVolume/expressions/base/fvExprDriver.H
index 5c4f78f04ea..1ecd04caa54 100644
--- a/src/finiteVolume/expressions/base/fvExprDriver.H
+++ b/src/finiteVolume/expressions/base/fvExprDriver.H
@@ -410,10 +410,10 @@ public:
 
     // Public Member Functions
 
-        //- The underlying field size for the expression
+        //- The natural field size for the expression
         virtual label size() const = 0;
 
-        //- The underlying point field size for the expression
+        //- The point field size for the expression
         virtual label pointSize() const = 0;
 
 
@@ -425,7 +425,7 @@ public:
 
     // Variables
 
-        //- Clear temporary variables and resets from expression strings
+        //- Clear temporary variables, reset from expression strings
         virtual void clearVariables();
 
         //- True if named variable exists
diff --git a/src/finiteVolume/expressions/fields/base/patchExprFieldBase.C b/src/finiteVolume/expressions/fields/base/patchExprFieldBase.C
index f304e5e5658..d8f77bccb50 100644
--- a/src/finiteVolume/expressions/fields/base/patchExprFieldBase.C
+++ b/src/finiteVolume/expressions/fields/base/patchExprFieldBase.C
@@ -57,18 +57,21 @@ void Foam::expressions::patchExprFieldBase::readExpressions
     if (expectedTypes::VALUE_TYPE == expectedType)
     {
         // Mandatory
-        evalValue = dict.readEntry("valueExpr", exprValue);
+        evalValue = dict.readEntry("valueExpr", exprValue, keyType::LITERAL);
     }
     else if (expectedTypes::GRADIENT_TYPE == expectedType)
     {
         // Mandatory
-        evalGrad = dict.readEntry("gradientExpr", exprGrad);
+        evalGrad = dict.readEntry("gradientExpr", exprGrad, keyType::LITERAL);
     }
     else
     {
         // MIXED_TYPE
-        evalValue = dict.readIfPresent("valueExpr", exprValue);
-        evalGrad = dict.readIfPresent("gradientExpr", exprGrad);
+        evalValue =
+            dict.readIfPresent("valueExpr", exprValue, keyType::LITERAL);
+
+        evalGrad =
+            dict.readIfPresent("gradientExpr", exprGrad, keyType::LITERAL);
 
         if (!evalValue && !evalGrad)
         {
diff --git a/src/finiteVolume/expressions/patch/patchExprDriver.H b/src/finiteVolume/expressions/patch/patchExprDriver.H
index e864b5dd66b..51a2c5fb235 100644
--- a/src/finiteVolume/expressions/patch/patchExprDriver.H
+++ b/src/finiteVolume/expressions/patch/patchExprDriver.H
@@ -179,13 +179,13 @@ public:
             return patch_.boundaryMesh().mesh();
         }
 
-        //- The underlying field size for the expression
+        //- The natural field size for the expression
         virtual label size() const
         {
             return patch_.patch().size();
         }
 
-        //- The underlying point field size for the expression
+        //- The point field size for the expression
         virtual label pointSize() const
         {
             return patch_.patch().nPoints();
diff --git a/src/finiteVolume/expressions/volume/volumeExprDriver.C b/src/finiteVolume/expressions/volume/volumeExprDriver.C
index f41b2b467d1..35ce03da28b 100644
--- a/src/finiteVolume/expressions/volume/volumeExprDriver.C
+++ b/src/finiteVolume/expressions/volume/volumeExprDriver.C
@@ -94,8 +94,9 @@ Foam::expressions::volumeExpr::parseDriver::parseDriver
     mesh_(mesh),
     resultType_(),
     isLogical_(false),
+    hasDimensions_(false),
     fieldGeoType_(NO_DATA),
-    resultDimension_()
+    resultDimensions_()
 {
     resetTimeReference(nullptr);
     resetDb(mesh_.thisDb());
@@ -105,17 +106,18 @@ Foam::expressions::volumeExpr::parseDriver::parseDriver
 Foam::expressions::volumeExpr::parseDriver::parseDriver
 (
     const fvMesh& mesh,
-    const parseDriver& driver,
+    const parseDriver& rhs,
     const dictionary& dict
 )
 :
     parsing::genericRagelLemonDriver(),
-    expressions::fvExprDriver(driver, dict),
+    expressions::fvExprDriver(rhs, dict),
     mesh_(mesh),
     resultType_(),
     isLogical_(false),
+    hasDimensions_(false),
     fieldGeoType_(NO_DATA),
-    resultDimension_()
+    resultDimensions_()
 {
     resetTimeReference(nullptr);
     resetDb(mesh_.thisDb());
@@ -146,7 +148,7 @@ Foam::expressions::volumeExpr::parseDriver::parseDriver
     resultType_(),
     isLogical_(false),
     fieldGeoType_(NO_DATA),
-    resultDimension_()
+    resultDimensions_()
 {
     resetTimeReference(nullptr);
     resetDb(mesh_.thisDb());
@@ -161,7 +163,15 @@ bool Foam::expressions::volumeExpr::parseDriver::readDict
 )
 {
     expressions::fvExprDriver::readDict(dict);
-    dict.readIfPresent("dimensions", resultDimension_);
+
+    resultDimensions_.clear();  // Avoid stickiness
+
+    hasDimensions_ = resultDimensions_.readEntry
+    (
+        "dimensions",
+        dict,
+        false  // mandatory=false
+    );
 
     return true;
 }
@@ -184,4 +194,90 @@ unsigned Foam::expressions::volumeExpr::parseDriver::parse
 }
 
 
+void Foam::expressions::volumeExpr::parseDriver::clearField()
+{
+    resultField_.reset(nullptr);
+
+    // Characteristics
+    resultType_.clear();
+    isLogical_ = false;
+    fieldGeoType_ = NO_DATA;
+}
+
+
+Foam::autoPtr<Foam::regIOobject>
+Foam::expressions::volumeExpr::parseDriver::dupZeroField() const
+{
+    const auto* regIOobjectPtr = resultField_.get();
+
+    if (!regIOobjectPtr)
+    {
+        return nullptr;
+    }
+
+    autoPtr<regIOobject> zField;
+
+    switch (fieldGeoType_)
+    {
+        #undef  doLocalCode
+        #define doLocalCode(GeoField)                                         \
+        {                                                                     \
+            const auto* ptr = dynamic_cast<const GeoField*>(regIOobjectPtr);  \
+            typedef typename GeoField::value_type Type;                       \
+                                                                              \
+            if (ptr)                                                          \
+            {                                                                 \
+                zField.reset                                                  \
+                (                                                             \
+                    GeoField::New                                             \
+                    (                                                         \
+                        word(pTraits<Type>::typeName) + word("(zero)"),       \
+                        (*ptr).mesh(),                                        \
+                        dimensioned<Type>(Zero)                               \
+                    ).ptr()                                                   \
+                );                                                            \
+                break;                                                        \
+            }                                                                 \
+        }
+
+        case FieldAssociation::VOLUME_DATA:
+        {
+            doLocalCode(volScalarField);
+            doLocalCode(volVectorField);
+            doLocalCode(volTensorField);
+            doLocalCode(volSymmTensorField);
+            doLocalCode(volSphericalTensorField);
+            break;
+        }
+        case FieldAssociation::FACE_DATA:
+        {
+            doLocalCode(surfaceScalarField);
+            doLocalCode(surfaceVectorField);
+            doLocalCode(surfaceTensorField);
+            doLocalCode(surfaceSymmTensorField);
+            doLocalCode(surfaceSphericalTensorField);
+            break;
+        }
+        case FieldAssociation::POINT_DATA:
+        {
+            doLocalCode(pointScalarField);
+            doLocalCode(pointVectorField);
+            doLocalCode(pointTensorField);
+            doLocalCode(pointSymmTensorField);
+            doLocalCode(pointSphericalTensorField);
+            break;
+        }
+        default: break;
+        #undef doLocalCode
+    }
+
+    // if (!zField)
+    // {
+    //     // Report
+    // }
+
+    return zField;
+}
+
+
 // ************************************************************************* //
diff --git a/src/finiteVolume/expressions/volume/volumeExprDriver.H b/src/finiteVolume/expressions/volume/volumeExprDriver.H
index 47042f2ad08..b4c182ab629 100644
--- a/src/finiteVolume/expressions/volume/volumeExprDriver.H
+++ b/src/finiteVolume/expressions/volume/volumeExprDriver.H
@@ -126,11 +126,14 @@ protected:
         //- A logical (bool-like) field (but actually a scalar)
         bool isLogical_;
 
+        //- Requested use of dimensions
+        bool hasDimensions_;
+
         //- A volume/surface/point field
         expressions::FieldAssociation fieldGeoType_;
 
         //- The result dimensions
-        dimensionSet resultDimension_;
+        dimensionSet resultDimensions_;
 
 
     // Protected Member Functions
@@ -217,13 +220,13 @@ public:
             return mesh_;
         }
 
-        //- The underlying field size for the expression
+        //- The natural field size for the expression
         virtual label size() const
         {
             return mesh_.nCells();
         }
 
-        //- The underlying point field size for the expression
+        //- The point field size for the expression
         virtual label pointSize() const
         {
             return mesh_.nPoints();
@@ -232,6 +235,16 @@ public:
         //- Field size associated with different geometric field types
         inline label size(const FieldAssociation geoType) const;
 
+        //- Apply dimensions() to geometric fields
+        inline bool hasDimensions() const noexcept;
+
+        //- The preferred result dimensions (if any)
+        inline const dimensionSet& dimensions() const noexcept;
+
+
+        //- Clear out local copies of the field
+        void clearField();
+
 
     // Reading
 
@@ -304,6 +317,9 @@ public:
         template<class GeoField>
         const GeoField* isResultType(bool logical, bool dieOnNull=false) const;
 
+        //- A zero-initialized field with the same type as the result field.
+        autoPtr<regIOobject> dupZeroField() const;
+
 
     // Set Fields
 
diff --git a/src/finiteVolume/expressions/volume/volumeExprDriverI.H b/src/finiteVolume/expressions/volume/volumeExprDriverI.H
index 8c6d00bb50b..0c1383e69fc 100644
--- a/src/finiteVolume/expressions/volume/volumeExprDriverI.H
+++ b/src/finiteVolume/expressions/volume/volumeExprDriverI.H
@@ -50,6 +50,20 @@ inline Foam::label Foam::expressions::volumeExpr::parseDriver::size
 }
 
 
+inline bool Foam::expressions::volumeExpr::parseDriver::hasDimensions()
+const noexcept
+{
+    return hasDimensions_;
+}
+
+
+inline const Foam::dimensionSet&
+Foam::expressions::volumeExpr::parseDriver::dimensions() const noexcept
+{
+    return resultDimensions_;
+}
+
+
 inline Foam::tmp<Foam::volScalarField>
 Foam::expressions::volumeExpr::parseDriver::parseDriver::field_cellSet
 (
diff --git a/src/finiteVolume/expressions/volume/volumeExprDriverTemplates.C b/src/finiteVolume/expressions/volume/volumeExprDriverTemplates.C
index 60c54f2d40d..ba39f568b58 100644
--- a/src/finiteVolume/expressions/volume/volumeExprDriverTemplates.C
+++ b/src/finiteVolume/expressions/volume/volumeExprDriverTemplates.C
@@ -68,17 +68,17 @@ void Foam::expressions::volumeExpr::parseDriver::setResult
 {
     typedef GeometricField<Type, fvPatchField, volMesh> fieldType;
 
-    resultField_.clear();
+    resultField_.reset(nullptr);
 
     // Characteristics
     resultType_ = pTraits<fieldType>::typeName;
     isLogical_ = logical;
     fieldGeoType_ = VOLUME_DATA;
 
-    // Always strip out dimensions?
-    if (!resultDimension_.dimensionless())
+    // Assign dimensions
+    if (hasDimensions_ && !logical)
     {
-        ptr->dimensions().reset(resultDimension_);
+        ptr->dimensions().reset(resultDimensions_);
     }
 
     setInternalFieldResult(ptr->primitiveField());
@@ -97,17 +97,17 @@ void Foam::expressions::volumeExpr::parseDriver::setResult
 {
     typedef GeometricField<Type, fvsPatchField, surfaceMesh> fieldType;
 
-    resultField_.clear();
+    resultField_.reset(nullptr);
 
     // Characteristics
     resultType_ = pTraits<fieldType>::typeName;
     isLogical_ = logical;
     fieldGeoType_ = FACE_DATA;
 
-    // Always strip out dimensions?
-    if (!resultDimension_.dimensionless())
+    // Assign dimensions
+    if (hasDimensions_ && !logical)
     {
-        ptr->dimensions().reset(resultDimension_);
+        ptr->dimensions().reset(resultDimensions_);
     }
 
     setInternalFieldResult(ptr->primitiveField());
@@ -126,17 +126,17 @@ void Foam::expressions::volumeExpr::parseDriver::setResult
 {
     typedef GeometricField<Type, pointPatchField, pointMesh> fieldType;
 
-    resultField_.clear();
+    resultField_.reset(nullptr);
 
     // Characteristics
     resultType_ = pTraits<fieldType>::typeName;
     isLogical_ = logical;
     fieldGeoType_ = POINT_DATA;
 
-    // Always strip out dimensions?
-    if (!resultDimension_.dimensionless())
+    // Assign dimensions
+    if (hasDimensions_ && !logical)
     {
-        ptr->dimensions().reset(resultDimension_);
+        ptr->dimensions().reset(resultDimensions_);
     }
 
     setInternalFieldResult(ptr->primitiveField());
@@ -164,7 +164,7 @@ Foam::expressions::volumeExpr::parseDriver::isResultType
 {
     const regIOobject* ptr = resultField_.get();
 
-    if (dieOnNull && ptr != nullptr)
+    if (dieOnNull && !ptr)
     {
         FatalErrorInFunction
             << "No result available. Requested "
diff --git a/tutorials/compressible/rhoPimpleFoam/RAS/TJunctionAverage/system/setExprFieldsDict b/tutorials/compressible/rhoPimpleFoam/RAS/TJunctionAverage/system/setExprFieldsDict
index 834dfb55d6c..f9c23f4f18c 100644
--- a/tutorials/compressible/rhoPimpleFoam/RAS/TJunctionAverage/system/setExprFieldsDict
+++ b/tutorials/compressible/rhoPimpleFoam/RAS/TJunctionAverage/system/setExprFieldsDict
@@ -31,7 +31,7 @@ expressions
             "radius = 0.1"
         );
 
-        condition
+        fieldMask
         #{
             // Within the radius
             (mag(pos() - $[(vector)constants.centre]) < radius)
-- 
GitLab