diff --git a/src/functionObjects/field/Make/files b/src/functionObjects/field/Make/files
index ddcb030299..e11d10f36c 100644
--- a/src/functionObjects/field/Make/files
+++ b/src/functionObjects/field/Make/files
@@ -6,6 +6,8 @@ continuityError/continuityError.C
derivedFields/derivedFields.C
+expressions/fvExpressionField.C
+
fieldAverage/fieldAverage.C
fieldAverage/fieldAverageItem/fieldAverageItem.C
fieldAverage/fieldAverageItem/fieldAverageItemIO.C
diff --git a/src/functionObjects/field/expressions/fvExpressionField.C b/src/functionObjects/field/expressions/fvExpressionField.C
new file mode 100644
index 0000000000..fb395e7538
--- /dev/null
+++ b/src/functionObjects/field/expressions/fvExpressionField.C
@@ -0,0 +1,696 @@
+/*---------------------------------------------------------------------------*\
+ ========= |
+ \\ / F ield | OpenFOAM: The Open Source CFD Toolbox
+ \\ / O peration |
+ \\ / A nd | www.openfoam.com
+ \\/ M anipulation |
+-------------------------------------------------------------------------------
+ Copyright (C) 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 .
+
+\*---------------------------------------------------------------------------*/
+
+#include "fvExpressionField.H"
+#include "volFields.H"
+#include "surfaceFields.H"
+#include "pointMesh.H"
+#include "pointFields.H"
+#include "volumeExprDriver.H"
+#include "calculatedFvPatchField.H"
+#include "addToRunTimeSelectionTable.H"
+
+// * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * //
+
+namespace Foam
+{
+namespace functionObjects
+{
+ defineTypeNameAndDebug(fvExpressionField, 0);
+ addToRunTimeSelectionTable(functionObject, fvExpressionField, dictionary);
+}
+}
+
+
+const Foam::Enum
+<
+ Foam::functionObjects::fvExpressionField::actionType
+>
+Foam::functionObjects::fvExpressionField::actionNames_
+({
+ { actionType::opNone, "none" },
+ { actionType::opNew, "new" },
+ { actionType::opModify, "modify" },
+});
+
+
+// * * * * * * * * * * * * * * * Local Functions * * * * * * * * * * * * * * //
+
+namespace Foam
+{
+
+word fieldGeoType(const expressions::FieldAssociation geoType)
+{
+ switch (geoType)
+ {
+ case expressions::FieldAssociation::POINT_DATA : return "points"; break;
+ case expressions::FieldAssociation::FACE_DATA : return "faces"; break;
+ case expressions::FieldAssociation::VOLUME_DATA : return "cells"; break;
+ default: break;
+ }
+ return "unknown";
+}
+
+
+template
+static void doCorrectBoundaryConditions
+(
+ bool correctBCs,
+ GeometricField& field
+)
+{
+ if (correctBCs)
+ {
+ // Info<< "Correcting boundary conditions: " << field.name() << nl;
+ field.correctBoundaryConditions();
+
+ // Ensure that calculated patches are updated
+ for (auto& pf : field.boundaryFieldRef())
+ {
+ if (isA>(pf))
+ {
+ pf = pf.patchInternalField();
+ }
+ }
+ }
+}
+
+
+template
+void doCorrectBoundaryConditions
+(
+ bool correctBCs,
+ GeometricField& field
+)
+{
+ if (correctBCs)
+ {
+ // Info<< "Correcting boundary conditions: " << field.name() << nl;
+ field.correctBoundaryConditions();
+ }
+}
+
+
+template
+void doCorrectBoundaryConditions
+(
+ bool correctBCs,
+ GeometricField& field
+)
+{}
+
+} // End namespace Foam
+
+
+// * * * * * * * * * * * * * Private Member Functions * * * * * * * * * * * //
+
+template
+bool Foam::functionObjects::fvExpressionField::loadAndStore(const IOobject& io)
+{
+ if (FieldType::typeName == io.headerClassName())
+ {
+ // Store field on mesh database
+ Log << " Reading " << io.name()
+ << " (" << FieldType::typeName << ')' << endl;
+
+ mesh_.objectRegistry::store(new FieldType(io, mesh_));
+ return true;
+ }
+
+ return false;
+}
+
+
+template
+bool Foam::functionObjects::fvExpressionField::loadField(const IOobject& io)
+{
+ typedef GeometricField VolFieldType;
+ // typedef typename VolFieldType::Internal IntVolFieldType;
+ typedef GeometricField SurfaceFieldType;
+
+ return
+ (
+ loadAndStore(io)
+ /// || loadAndStore(io)
+ || loadAndStore(io)
+ );
+}
+
+
+Foam::label Foam::functionObjects::fvExpressionField::loadFields
+(
+ const UList& fieldSet_
+)
+{
+ label nLoaded = 0;
+
+ for (const word& fieldName : fieldSet_)
+ {
+ // Already loaded?
+ const auto* ptr = mesh_.cfindObject(fieldName);
+
+ if (ptr)
+ {
+ ++nLoaded;
+ DebugInfo
+ << "readFields : "
+ << ptr->name() << " (" << ptr->type()
+ << ") already in database" << endl;
+ continue;
+ }
+
+ // Load field as necessary
+ IOobject io
+ (
+ fieldName,
+ mesh_.time().timeName(),
+ mesh_,
+ IOobject::MUST_READ,
+ IOobject::NO_WRITE
+ );
+
+ const bool ok =
+ (
+ io.typeHeaderOk(false) // Preload header info
+ && !io.headerClassName().empty() // Extra safety
+ &&
+ (
+ loadField(io)
+ || loadField(io)
+ || loadField(io)
+ || loadField(io)
+ || loadField(io)
+ )
+ );
+
+ if (ok)
+ {
+ ++nLoaded;
+ }
+ else
+ {
+ DebugInfo
+ << "readFields : failed to load " << fieldName << endl;
+ }
+ }
+
+ return nLoaded;
+}
+
+
+template
+bool Foam::functionObjects::fvExpressionField::setField
+(
+ GeoField& output,
+ const GeoField& evaluated,
+ const boolField& fieldMask
+)
+{
+ label numValuesChanged = 0;
+
+ // Internal field
+ if (fieldMask.empty())
+ {
+ // No field-mask - set all
+ numValuesChanged = output.size();
+
+ output.primitiveFieldRef() = evaluated;
+ }
+ else
+ {
+ auto& internal = output.primitiveFieldRef();
+
+ forAll(internal, idx)
+ {
+ if (fieldMask[idx])
+ {
+ internal[idx] = evaluated[idx];
+ ++numValuesChanged;
+ }
+ }
+ }
+
+ // Boundary fields
+ forAll(evaluated.boundaryField(), patchi)
+ {
+ auto& pf = output.boundaryFieldRef()[patchi];
+
+ if (pf.patch().coupled())
+ {
+ pf == evaluated.boundaryField()[patchi];
+ }
+ }
+
+ doCorrectBoundaryConditions(true, output);
+
+ if (action_ == actionType::opModify && log)
+ {
+ const label numTotal = returnReduce(output.size(), plusOp