diff --git a/src/atmosphericModels/Make/files b/src/atmosphericModels/Make/files
index 62808d2deb..0b61ec6284 100644
--- a/src/atmosphericModels/Make/files
+++ b/src/atmosphericModels/Make/files
@@ -35,6 +35,13 @@ fvOptions/atmPlantCanopyTSource/atmPlantCanopyTSource.C
fvOptions/atmNutSource/atmNutSource.C
fvOptions/treeTurbulence/treeTurbulence.C
+etht = fvOptions/evapotranspirationHeatTransfer
+ethtModels = $(etht)/evapotranspirationHeatTransferModels
+$(etht)/evapotranspirationHeatTransfer.C
+$(ethtModels)/evapotranspirationHeatTransferModel/evapotranspirationHeatTransferModel.C
+$(ethtModels)/evapotranspirationHeatTransferModel/evapotranspirationHeatTransferModelNew.C
+$(ethtModels)/tree/tree.C
+$(ethtModels)/grass/grass.C
/* functionObjects */
functionObjects/ObukhovLength/ObukhovLength.C
diff --git a/src/atmosphericModels/Make/options b/src/atmosphericModels/Make/options
index d2af502b1a..033971d120 100644
--- a/src/atmosphericModels/Make/options
+++ b/src/atmosphericModels/Make/options
@@ -8,6 +8,7 @@ EXE_INC = \
-I$(LIB_SRC)/thermophysicalModels/basic/lnInclude \
-I$(LIB_SRC)/thermophysicalModels/specie/lnInclude \
-I$(LIB_SRC)/thermophysicalModels/solidThermo/lnInclude \
+ -I$(LIB_SRC)/thermophysicalModels/radiation/lnInclude \
-I$(LIB_SRC)/TurbulenceModels/turbulenceModels/lnInclude \
-I$(LIB_SRC)/TurbulenceModels/compressible/lnInclude \
-I$(LIB_SRC)/TurbulenceModels/incompressible/lnInclude \
@@ -18,6 +19,7 @@ LIB_LIBS = \
-lfvOptions \
-lmeshTools \
-lsurfMesh \
+ -lradiationModels \
-lfluidThermophysicalModels \
-lsolidThermo \
-lturbulenceModels \
diff --git a/src/atmosphericModels/fvOptions/evapotranspirationHeatTransfer/evapotranspirationHeatTransfer.C b/src/atmosphericModels/fvOptions/evapotranspirationHeatTransfer/evapotranspirationHeatTransfer.C
new file mode 100644
index 0000000000..3c76ebe6ac
--- /dev/null
+++ b/src/atmosphericModels/fvOptions/evapotranspirationHeatTransfer/evapotranspirationHeatTransfer.C
@@ -0,0 +1,172 @@
+/*---------------------------------------------------------------------------*\
+ ========= |
+ \\ / F ield | OpenFOAM: The Open Source CFD Toolbox
+ \\ / O peration |
+ \\ / A nd | www.openfoam.com
+ \\/ M anipulation |
+-------------------------------------------------------------------------------
+ Copyright (C) 2022 OpenCFD Ltd.
+-------------------------------------------------------------------------------
+License
+ This file is part of OpenFOAM.
+
+ OpenFOAM is free software: you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ OpenFOAM is distributed in the hope that it will be useful, but WITHOUT
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with OpenFOAM. If not, see .
+
+\*---------------------------------------------------------------------------*/
+
+#include "evapotranspirationHeatTransfer.H"
+#include "evapotranspirationHeatTransferModel.H"
+#include "fvMesh.H"
+#include "fvMatrix.H"
+#include "basicThermo.H"
+#include "addToRunTimeSelectionTable.H"
+
+// * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * //
+
+namespace Foam
+{
+namespace fv
+{
+ defineTypeNameAndDebug(evapotranspirationHeatTransfer, 0);
+ addToRunTimeSelectionTable
+ (
+ option,
+ evapotranspirationHeatTransfer,
+ dictionary
+ );
+}
+}
+
+
+// * * * * * * * * * * * * * * * * Constructors * * * * * * * * * * * * * * //
+
+Foam::fv::evapotranspirationHeatTransfer::evapotranspirationHeatTransfer
+(
+ const word& sourceName,
+ const word& modelType,
+ const dictionary& dict,
+ const fvMesh& mesh
+)
+:
+ fv::cellSetOption(sourceName, modelType, dict, mesh),
+ ethtModelPtr_()
+{
+ // Set the field name to that of the energy
+ // field from which the temperature is obtained
+
+ const auto& thermo = mesh_.lookupObject(basicThermo::dictName);
+
+ fieldNames_.resize(1, thermo.he().name());
+
+ fv::option::resetApplied();
+
+ Info<< " Applying evapotranspirationHeatTransfer to: " << fieldNames_[0]
+ << endl;
+
+ read(dict);
+}
+
+
+// * * * * * * * * * * * * * * * * Destructor * * * * * * * * * * * * * * * //
+
+Foam::fv::evapotranspirationHeatTransfer::~evapotranspirationHeatTransfer()
+{} // evapotranspirationHeatTransferModel was forward declared
+
+
+// * * * * * * * * * * * * * * * Member Functions * * * * * * * * * * * * * //
+
+void Foam::fv::evapotranspirationHeatTransfer::addSup
+(
+ fvScalarMatrix& eqn,
+ const label fieldi
+)
+{
+ if (this->V() < VSMALL)
+ {
+ return;
+ }
+
+ const scalar V = this->V();
+ const scalarField& Vcells = mesh_.V();
+
+ const volScalarField& he = eqn.psi();
+ scalarField& heSource = eqn.source();
+
+ // Calculate evapotranspiration heat transfer rate per volume [J/s/m^3]
+ const scalarField Q(ethtModelPtr_->Q(cells_)/V);
+
+ if (he.dimensions() == dimEnergy/dimMass)
+ {
+ forAll(cells_, i)
+ {
+ const label celli = cells_[i];
+
+ heSource[celli] += Q[i]*Vcells[celli];
+ }
+ }
+ else if (he.dimensions() == dimTemperature)
+ {
+ const auto& thermo =
+ mesh_.lookupObject(basicThermo::dictName);
+
+ // Calculate density*heat capacity at constant pressure/volume
+ const volScalarField rhoCpv(thermo.rho()*thermo.Cpv());
+
+ // heSource [K m^3/s] = [J/s/m^3] * m^3 / [kg/m^3] / [J/kg/K]
+ forAll(cells_, i)
+ {
+ const label celli = cells_[i];
+
+ heSource[celli] += Q[i]*Vcells[celli]*rhoCpv[i];
+ }
+ }
+}
+
+
+void Foam::fv::evapotranspirationHeatTransfer::addSup
+(
+ const volScalarField& rho,
+ fvScalarMatrix& eqn,
+ const label fieldi
+)
+{
+ addSup(eqn, fieldi);
+}
+
+
+bool Foam::fv::evapotranspirationHeatTransfer::read(const dictionary& dict)
+{
+ if (!fv::cellSetOption::read(dict))
+ {
+ return false;
+ }
+
+ if (selectionMode_ != selectionModeType::smCellZone)
+ {
+ FatalIOErrorInFunction(dict)
+ << "evapotranspirationHeatTransfer requires "
+ << selectionModeTypeNames_[selectionModeType::smCellZone]
+ << exit(FatalIOError);
+ }
+
+ ethtModelPtr_.reset
+ (
+ evapotranspirationHeatTransferModel::New(dict, mesh_)
+ );
+
+ return true;
+}
+
+
+// ************************************************************************* //
diff --git a/src/atmosphericModels/fvOptions/evapotranspirationHeatTransfer/evapotranspirationHeatTransfer.H b/src/atmosphericModels/fvOptions/evapotranspirationHeatTransfer/evapotranspirationHeatTransfer.H
new file mode 100644
index 0000000000..95740bb74b
--- /dev/null
+++ b/src/atmosphericModels/fvOptions/evapotranspirationHeatTransfer/evapotranspirationHeatTransfer.H
@@ -0,0 +1,183 @@
+/*---------------------------------------------------------------------------*\
+ ========= |
+ \\ / F ield | OpenFOAM: The Open Source CFD Toolbox
+ \\ / O peration |
+ \\ / A nd | www.openfoam.com
+ \\/ M anipulation |
+-------------------------------------------------------------------------------
+ Copyright (C) 2022 OpenCFD Ltd.
+-------------------------------------------------------------------------------
+License
+ This file is part of OpenFOAM.
+
+ OpenFOAM is free software: you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ OpenFOAM is distributed in the hope that it will be useful, but WITHOUT
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with OpenFOAM. If not, see .
+
+Class
+ Foam::fv::evapotranspirationHeatTransfer
+
+Description
+ Applies sources on temperature (\c T - incompressible)
+ or energy (\c h/e - compressible) equation to incorporate
+ evapotranspiration heat-transfer effects from the specified
+ plant canopy. Heat transfer is usually calculated based on
+ empirical relations between plants and solar radiation.
+
+Usage
+ Example by using \c constant/fvOptions:
+ \verbatim
+ evapotranspirationHeatTransfer1
+ {
+ // Mandatory entries
+ type evapotranspirationHeatTransfer;
+ model ;
+
+ // Model-specific entries
+ ...
+
+ // Inherited entries
+ ...
+ }
+ \endverbatim
+
+ where the entries mean:
+ \table
+ Property | Description | Type | Reqd | Deflt
+ type | Type name: evapotranspirationHeatTransfer | word | yes | -
+ model | Name of heat-transfer model | word | yes | -
+ \endtable
+
+ Options for the \c model entry:
+ \verbatim
+ tree | Regression-based heat-transfer model for trees
+ grass | Heat-transfer model for grass
+ \endverbatim
+
+ The inherited entries are elaborated in:
+ - \link fvOption.H \endlink
+ - \link cellSetOption.H \endlink
+
+Note
+ - Evapotranspiration mass transfer is not modelled.
+
+SourceFiles
+ evapotranspirationHeatTransfer.C
+
+\*---------------------------------------------------------------------------*/
+
+#ifndef Foam_fv_evapotranspirationHeatTransfer_H
+#define Foam_fv_evapotranspirationHeatTransfer_H
+
+#include "cellSetOption.H"
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+namespace Foam
+{
+
+// Forward Declarations
+class evapotranspirationHeatTransferModel;
+
+namespace fv
+{
+
+/*---------------------------------------------------------------------------*\
+ Class evapotranspirationHeatTransfer Declaration
+\*---------------------------------------------------------------------------*/
+
+class evapotranspirationHeatTransfer
+:
+ public fv::cellSetOption
+{
+ // Private Data
+
+ //- Runtime-selectable evapotranspiration heat transfer model
+ autoPtr ethtModelPtr_;
+
+
+public:
+
+ //- Runtime type information
+ TypeName("evapotranspirationHeatTransfer");
+
+
+ // Constructors
+
+ //- Construct from explicit source name and mesh
+ evapotranspirationHeatTransfer
+ (
+ const word& sourceName,
+ const word& modelType,
+ const dictionary& dict,
+ const fvMesh& mesh
+ );
+
+ //- No copy construct
+ evapotranspirationHeatTransfer(const evapotranspirationHeatTransfer&) =
+ delete;
+
+ //- No copy assignment
+ void operator=(const evapotranspirationHeatTransfer&) = delete;
+
+
+ //- Destructor
+ virtual ~evapotranspirationHeatTransfer();
+
+
+ // Member Functions
+
+ //- Add explicit contribution to energy equation
+ //- for incompressible flow computations
+ virtual void addSup
+ (
+ fvScalarMatrix& eqn,
+ const label fieldi
+ );
+
+ //- Add explicit contribution to energy equation
+ //- for compressible flow computations
+ virtual void addSup
+ (
+ const volScalarField& rho,
+ fvScalarMatrix& eqn,
+ const label fieldi
+ );
+
+ //- Add explicit contribution to energy equation
+ //- for multiphase flow computations
+ virtual void addSup
+ (
+ const volScalarField& alpha,
+ const volScalarField& rho,
+ fvScalarMatrix& eqn,
+ const label fieldi
+ )
+ {
+ NotImplemented;
+ }
+
+ //- Read dictionary
+ virtual bool read(const dictionary& dict);
+};
+
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+} // End namespace fv
+} // End namespace Foam
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+#endif
+
+// ************************************************************************* //
diff --git a/src/atmosphericModels/fvOptions/evapotranspirationHeatTransfer/evapotranspirationHeatTransferModels/evapotranspirationHeatTransferModel/evapotranspirationHeatTransferModel.C b/src/atmosphericModels/fvOptions/evapotranspirationHeatTransfer/evapotranspirationHeatTransferModels/evapotranspirationHeatTransferModel/evapotranspirationHeatTransferModel.C
new file mode 100644
index 0000000000..5051e01f80
--- /dev/null
+++ b/src/atmosphericModels/fvOptions/evapotranspirationHeatTransfer/evapotranspirationHeatTransferModels/evapotranspirationHeatTransferModel/evapotranspirationHeatTransferModel.C
@@ -0,0 +1,196 @@
+/*---------------------------------------------------------------------------*\
+ ========= |
+ \\ / F ield | OpenFOAM: The Open Source CFD Toolbox
+ \\ / O peration |
+ \\ / A nd | www.openfoam.com
+ \\/ M anipulation |
+-------------------------------------------------------------------------------
+ Copyright (C) 2022 OpenCFD Ltd.
+-------------------------------------------------------------------------------
+License
+ This file is part of OpenFOAM.
+
+ OpenFOAM is free software: you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ OpenFOAM is distributed in the hope that it will be useful, but WITHOUT
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with OpenFOAM. If not, see .
+
+\*---------------------------------------------------------------------------*/
+
+#include "evapotranspirationHeatTransferModel.H"
+#include "radiationModel.H"
+#include "solarLoadBase.H"
+#include "fvMesh.H"
+
+// * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * //
+
+namespace Foam
+{
+ defineTypeNameAndDebug(evapotranspirationHeatTransferModel, 0);
+ defineRunTimeSelectionTable
+ (
+ evapotranspirationHeatTransferModel,
+ dictionary
+ );
+}
+
+
+// * * * * * * * * * * * * Protected Member Functions * * * * * * * * * * * //
+
+Foam::volScalarField& Foam::evapotranspirationHeatTransferModel::getOrReadField
+(
+ const word& fieldName
+) const
+{
+ auto* ptr = mesh_.getObjectPtr(fieldName);
+
+ if (!ptr)
+ {
+ ptr = new volScalarField
+ (
+ IOobject
+ (
+ fieldName,
+ mesh_.time().timeName(),
+ mesh_,
+ IOobject::MUST_READ,
+ IOobject::AUTO_WRITE
+ ),
+ mesh_
+ );
+ mesh_.objectRegistry::store(ptr);
+ }
+
+ return *ptr;
+}
+
+
+Foam::scalar Foam::evapotranspirationHeatTransferModel::q() const
+{
+ // Retrieve solar-load information
+ const auto& base =
+ mesh().lookupObject("solarLoadBase");
+ const solarCalculator& solar = base.solarCalculatorRef();
+
+ // Retrieve internal & boundary faces directly hit by solar rays
+ const faceShading& shade = base.faceShadingRef();
+ const labelList& hitFacesId = shade.rayStartFaces();
+
+ // Retrieve face zone information
+ const faceZone& zone = mesh_.faceZones()[faceZoneId_];
+ const vectorField& faceNormals = zone().faceNormals();
+ const scalarField& faceAreas = zone().magFaceAreas();
+
+ // Retrieve direct solar load [W/m^2]
+ const vector directSolarRad(solar.directSolarRad()*solar.direction());
+
+ // Identify zone faces within mesh
+ bitSet isZoneFace(mesh_.nFaces());
+ isZoneFace.set(zone);
+
+ // Calculate area-averaged incident solar load
+ // Assuming hit faces are updated by solarLoad
+ scalar q = 0;
+ scalar totalFaceArea = 0;
+
+ for (const label facei : hitFacesId)
+ {
+ if (isZoneFace[facei])
+ {
+ const label localFacei = zone.whichFace(facei);
+
+ const vector& faceNormal = faceNormals[localFacei];
+ const scalar faceArea = faceAreas[localFacei];
+
+ const scalar qIncident = directSolarRad & faceNormal;
+
+ q += qIncident*faceArea;
+ totalFaceArea += faceArea;
+ }
+ }
+ reduce(q, sumOp());
+ reduce(totalFaceArea, sumOp());
+ q /= totalFaceArea;
+
+
+ // Sum diffusive solar loads
+ switch(solar.sunLoadModel())
+ {
+ case solarCalculator::mSunLoadFairWeatherConditions:
+ case solarCalculator::mSunLoadTheoreticalMaximum:
+ {
+ // Calculate diffusive radiance
+ // contribution from sky and ground
+ tmp tdiffuseSolarRad =
+ solar.diffuseSolarRad(faceNormals);
+ const scalarField& diffuseSolarRad = tdiffuseSolarRad.cref();
+
+ // Calculate area-averaged diffusive solar load
+ scalar meanDiffuseSolarRad = 0;
+ forAll(faceAreas, i)
+ {
+ meanDiffuseSolarRad += diffuseSolarRad[i]*faceAreas[i];
+ }
+ meanDiffuseSolarRad /= totalFaceArea;
+
+ q += meanDiffuseSolarRad;
+ }
+ break;
+
+ case solarCalculator::mSunLoadConstant:
+ case solarCalculator::mSunLoadTimeDependent:
+ {
+ q += solar.diffuseSolarRad();
+ }
+ break;
+ }
+
+ return q;
+}
+
+
+// * * * * * * * * * * * * * * * * Constructors * * * * * * * * * * * * * * //
+
+Foam::evapotranspirationHeatTransferModel::evapotranspirationHeatTransferModel
+(
+ const dictionary& dict,
+ const fvMesh& mesh
+)
+:
+ mesh_(mesh)
+{}
+
+
+// * * * * * * * * * * * * * * Member Functions * * * * * * * * * * * * * * //
+
+bool Foam::evapotranspirationHeatTransferModel::read(const dictionary& dict)
+{
+ faceZoneId_ = mesh_.faceZones().findZoneID(dict.get("faceZone"));
+
+ if (faceZoneId_ < 0)
+ {
+ const word faceZoneName(mesh_.faceZones()[faceZoneId_].name());
+
+ FatalIOErrorInFunction(dict)
+ << type() << ' ' << typeName << ": "
+ << " No matching face zone: " << faceZoneName << nl
+ << " Known face zones: "
+ << flatOutput(mesh_.faceZones().names()) << nl
+ << exit(FatalIOError);
+
+ return false;
+ }
+
+ return true;
+}
+
+
+// ************************************************************************* //
diff --git a/src/atmosphericModels/fvOptions/evapotranspirationHeatTransfer/evapotranspirationHeatTransferModels/evapotranspirationHeatTransferModel/evapotranspirationHeatTransferModel.H b/src/atmosphericModels/fvOptions/evapotranspirationHeatTransfer/evapotranspirationHeatTransferModels/evapotranspirationHeatTransferModel/evapotranspirationHeatTransferModel.H
new file mode 100644
index 0000000000..1f34f91c94
--- /dev/null
+++ b/src/atmosphericModels/fvOptions/evapotranspirationHeatTransfer/evapotranspirationHeatTransferModels/evapotranspirationHeatTransferModel/evapotranspirationHeatTransferModel.H
@@ -0,0 +1,180 @@
+/*---------------------------------------------------------------------------*\
+ ========= |
+ \\ / F ield | OpenFOAM: The Open Source CFD Toolbox
+ \\ / O peration |
+ \\ / A nd | www.openfoam.com
+ \\/ M anipulation |
+-------------------------------------------------------------------------------
+ Copyright (C) 2022 OpenCFD Ltd.
+-------------------------------------------------------------------------------
+License
+ This file is part of OpenFOAM.
+
+ OpenFOAM is free software: you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ OpenFOAM is distributed in the hope that it will be useful, but WITHOUT
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with OpenFOAM. If not, see .
+
+Class
+ Foam::evapotranspirationHeatTransferModel
+
+Description
+ Base class for evapotranspiration models
+ to handle general evapotranspiration characteristics.
+
+SourceFiles
+ evapotranspirationHeatTransferModel.C
+ evapotranspirationHeatTransferModelNew.C
+ evapotranspirationHeatTransferModelTemplates.C
+
+\*---------------------------------------------------------------------------*/
+
+#ifndef Foam_evapotranspirationHeatTransferModel_H
+#define Foam_evapotranspirationHeatTransferModel_H
+
+#include "dictionary.H"
+#include "HashSet.H"
+#include "volFields.H"
+#include "runTimeSelectionTables.H"
+#include "OFstream.H"
+#include "coordinateSystem.H"
+#include "writeFile.H"
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+namespace Foam
+{
+
+// Forward Declarations
+class fvMesh;
+
+/*---------------------------------------------------------------------------*\
+ Class evapotranspirationHeatTransferModel Declaration
+\*---------------------------------------------------------------------------*/
+
+class evapotranspirationHeatTransferModel
+{
+ // Private Data
+
+ //- Reference to the mesh
+ const fvMesh& mesh_;
+
+ //- Identifier of specified face zone
+ label faceZoneId_;
+
+
+protected:
+
+ // Protected Member Functions
+
+ //- Return requested field from the object registry
+ //- or read+register the field to the object registry
+ volScalarField& getOrReadField(const word& fieldName) const;
+
+ //- Area-averaged heat flux due to short-wave
+ //- solar rays on face zone [W/m^2]
+ // Short-wave: direct and diffusive solar rays
+ scalar q() const;
+
+
+public:
+
+ //- Runtime type information
+ TypeName("evapotranspirationHeatTransferModel");
+
+
+ // Declare runtime constructor selection table
+
+ declareRunTimeSelectionTable
+ (
+ autoPtr,
+ evapotranspirationHeatTransferModel,
+ dictionary,
+ (
+ const dictionary& dict,
+ const fvMesh& mesh
+ ),
+ (dict, mesh)
+ );
+
+
+ // Selectors
+
+ //- Return a reference to the selected evapotranspiration model
+ static autoPtr New
+ (
+ const dictionary& dict,
+ const fvMesh& mesh
+ );
+
+
+ // Constructors
+
+ //- Construct from components
+ evapotranspirationHeatTransferModel
+ (
+ const dictionary& dict,
+ const fvMesh& mesh
+ );
+
+ //- No copy construct
+ evapotranspirationHeatTransferModel
+ (
+ const evapotranspirationHeatTransferModel&
+ ) = delete;
+
+ //- No copy assignment
+ void operator=(const evapotranspirationHeatTransferModel&) = delete;
+
+
+ //- Destructor
+ virtual ~evapotranspirationHeatTransferModel() = default;
+
+
+ // Member Functions
+
+ // Access
+
+ //- Return const reference to the mesh
+ const fvMesh& mesh() const noexcept
+ {
+ return mesh_;
+ }
+
+ //- Return the face-zone identifier
+ label faceZoneId() const noexcept
+ {
+ return faceZoneId_;
+ }
+
+
+ // Evaluation
+
+ //- Return heat-transfer rate for speficied cells [J/s]
+ virtual tmp Q(const labelList& cells) const = 0;
+
+
+ // I-O
+
+ //- Read the dictionary
+ virtual bool read(const dictionary& dict);
+};
+
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+} // End namespace Foam
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+#endif
+
+// ************************************************************************* //
diff --git a/src/atmosphericModels/fvOptions/evapotranspirationHeatTransfer/evapotranspirationHeatTransferModels/evapotranspirationHeatTransferModel/evapotranspirationHeatTransferModelNew.C b/src/atmosphericModels/fvOptions/evapotranspirationHeatTransfer/evapotranspirationHeatTransferModels/evapotranspirationHeatTransferModel/evapotranspirationHeatTransferModelNew.C
new file mode 100644
index 0000000000..cf8f876d9e
--- /dev/null
+++ b/src/atmosphericModels/fvOptions/evapotranspirationHeatTransfer/evapotranspirationHeatTransferModels/evapotranspirationHeatTransferModel/evapotranspirationHeatTransferModelNew.C
@@ -0,0 +1,62 @@
+/*---------------------------------------------------------------------------*\
+ ========= |
+ \\ / F ield | OpenFOAM: The Open Source CFD Toolbox
+ \\ / O peration |
+ \\ / A nd | www.openfoam.com
+ \\/ M anipulation |
+-------------------------------------------------------------------------------
+ Copyright (C) 2022 OpenCFD Ltd.
+-------------------------------------------------------------------------------
+License
+ This file is part of OpenFOAM.
+
+ OpenFOAM is free software: you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ OpenFOAM is distributed in the hope that it will be useful, but WITHOUT
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with OpenFOAM. If not, see .
+
+\*---------------------------------------------------------------------------*/
+
+#include "evapotranspirationHeatTransferModel.H"
+#include "fvMesh.H"
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+Foam::autoPtr
+Foam::evapotranspirationHeatTransferModel::New
+(
+ const dictionary& dict,
+ const fvMesh& mesh
+)
+{
+ word modelType(dict.get("model"));
+
+ auto cstrIter = dictionaryConstructorTablePtr_->cfind(modelType);
+
+ if (!cstrIter.found())
+ {
+ FatalIOErrorInLookup
+ (
+ dict,
+ "model",
+ modelType,
+ *dictionaryConstructorTablePtr_
+ ) << exit(FatalIOError);
+ }
+
+ return autoPtr
+ (
+ cstrIter()(dict, mesh)
+ );
+}
+
+
+// ************************************************************************* //
diff --git a/src/atmosphericModels/fvOptions/evapotranspirationHeatTransfer/evapotranspirationHeatTransferModels/grass/grass.C b/src/atmosphericModels/fvOptions/evapotranspirationHeatTransfer/evapotranspirationHeatTransferModels/grass/grass.C
new file mode 100644
index 0000000000..c22c1d1297
--- /dev/null
+++ b/src/atmosphericModels/fvOptions/evapotranspirationHeatTransfer/evapotranspirationHeatTransferModels/grass/grass.C
@@ -0,0 +1,418 @@
+/*---------------------------------------------------------------------------*\
+ ========= |
+ \\ / F ield | OpenFOAM: The Open Source CFD Toolbox
+ \\ / O peration |
+ \\ / A nd | www.openfoam.com
+ \\/ M anipulation |
+-------------------------------------------------------------------------------
+ Copyright (C) 2022 OpenCFD Ltd.
+-------------------------------------------------------------------------------
+License
+ This file is part of OpenFOAM.
+
+ OpenFOAM is free software: you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ OpenFOAM is distributed in the hope that it will be useful, but WITHOUT
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with OpenFOAM. If not, see .
+
+\*---------------------------------------------------------------------------*/
+
+#include "grass.H"
+#include "basicThermo.H"
+#include "fluidThermo.H"
+#include "turbulentTransportModel.H"
+#include "turbulentFluidThermoModel.H"
+#include "addToRunTimeSelectionTable.H"
+
+// * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * //
+
+namespace Foam
+{
+namespace evapotranspirationHeatTransferModels
+{
+ defineTypeNameAndDebug(grass, 0);
+ addToRunTimeSelectionTable
+ (
+ evapotranspirationHeatTransferModel,
+ grass,
+ dictionary
+ );
+}
+}
+
+
+const Foam::Enum
+<
+ Foam::evapotranspirationHeatTransferModels::grass::soilHeatFluxType
+>
+Foam::evapotranspirationHeatTransferModels::grass::soilHeatFluxTypeNames
+({
+ {
+ soilHeatFluxType::PROPORTIONAL_TO_SOLAR_RADIATION,
+ "proportionalToSolarRadiation"
+ },
+ { soilHeatFluxType::BOUNDARY, "boundary" }
+});
+
+
+// * * * * * * * * * * * * * Private Member Functions * * * * * * * * * * * //
+
+Foam::tmp
+Foam::evapotranspirationHeatTransferModels::grass::E(const labelList& cells)
+const
+{
+ const scalar q = this->q();
+ const scalar G = this->G(cells);
+ const scalar Delta = this->Delta();
+ const scalar D = this->D();
+ const scalar ra = this->ra();
+ const scalar rs = this->rs();
+ const scalar gamma = this->gamma();
+
+ // (BSG:Eq. 11)
+ const scalar E =
+ (Delta*(q - G) + rho_*Cp_*D/ra)/(Delta + gamma*(1 + rs/ra));
+
+ return tmp::New(cells.size(), E);
+}
+
+
+Foam::scalar Foam::evapotranspirationHeatTransferModels::grass::Delta() const
+{
+ // (BSG:Eq. 15), (APR:Eq. 13) - note 0.6108 -> 610.8 due to kPa -> Pa
+ const scalar Ta = Tref_ + 237.3;
+
+ return 4098*(610.8*exp(17.27*Tref_/Ta))/sqr(Ta);
+}
+
+
+Foam::scalar Foam::evapotranspirationHeatTransferModels::grass::D() const
+{
+ // (BSG:Eq. 16)
+ const scalar RHref = RHptr_->value(mesh().time().timeOutputValue());
+
+ return (1 - RHref)*pSat();
+}
+
+
+Foam::scalar Foam::evapotranspirationHeatTransferModels::grass::pSat() const
+{
+ // (BSG:Eq. 17; Arden Buck equation)
+ const scalar p1 = (1.0007 + 3.46e-8*pAtm_)*611.21;
+
+ return p1*exp((18.678 - Tref_/234.5)*Tref_/(Tref_ + 257.14));
+}
+
+
+Foam::scalar Foam::evapotranspirationHeatTransferModels::grass::gamma() const
+{
+ // (APR:Eq. 8)
+ return Cp_*pAtm_/(epsilon_*lambda());
+}
+
+
+Foam::scalar Foam::evapotranspirationHeatTransferModels::grass::lambda() const
+{
+ // (BSG:Eq. 12)
+ const scalar T1 = Tref_ + 273.15;
+ const scalar T2 = Tref_ + 239.24;
+
+ return 1.91846e6*sqr(T1/T2);
+}
+
+
+Foam::scalar Foam::evapotranspirationHeatTransferModels::grass::ra() const
+{
+ // (APR:Eq. 4), (BSG:Eq. 14)
+ const scalar log1 = log((zRefU_ - d_*h_)/(zom_*h_));
+ const scalar log2 = log((zRefH_ - d_*h_)/(zoh_*h_));
+
+ return log1*log2/(sqr(kappa_)*uRef_);
+}
+
+
+Foam::scalar Foam::evapotranspirationHeatTransferModels::grass::rs() const
+{
+ // (BSG:Eq. 13), (APR:Eq. 5; reduced form in p. 4-5)
+ return ri_/(scalar(12)*h_);
+}
+
+
+Foam::scalar Foam::evapotranspirationHeatTransferModels::grass::S
+(
+ const labelList& cells
+) const
+{
+ // Mark fvOption cells within mesh
+ bitSet isZoneCell(mesh().nCells());
+ isZoneCell.set(cells);
+
+ scalar S = 0;
+
+ // Select cells next to specified patches
+ // Sum patch area that is covered by fvOption cells
+ for (const label patchi : patchSet_)
+ {
+ const scalarField& s = mesh().magSf().boundaryField()[patchi];
+
+ const polyPatch& pp = mesh().boundaryMesh()[patchi];
+ const labelList& faceCells = pp.faceCells();
+
+ forAll(faceCells, i)
+ {
+ const bool isCovered = isZoneCell[faceCells[i]];
+
+ if (isCovered)
+ {
+ S += s[i];
+ }
+ }
+ }
+ reduce(S, sumOp());
+
+ if (mag(S) < SMALL)
+ {
+ FatalErrorInFunction
+ << "Area coverage of grass cannot be zero. "
+ << "Check whether cellZone has any boundary faces."
+ << exit(FatalError);
+ }
+
+ return S;
+}
+
+
+Foam::scalar Foam::evapotranspirationHeatTransferModels::grass::G
+(
+ const labelList& cells
+) const
+{
+ switch (soilHeatFluxMethod_)
+ {
+ case soilHeatFluxType::PROPORTIONAL_TO_SOLAR_RADIATION:
+ {
+ return Csoil_*q();
+ }
+
+ case soilHeatFluxType::BOUNDARY:
+ {
+ // Retrieve heat flux through patches
+ tmp> tqBf = this->qBf();
+ const FieldField& qBf = tqBf.cref();
+
+ // Mark fvOption cells within mesh
+ bitSet isZoneCell(mesh().nCells());
+ isZoneCell.set(cells);
+
+ scalar G = 0;
+
+ for (const label patchi : patchSet_)
+ {
+ const scalarField& s = mesh().magSf().boundaryField()[patchi];
+
+ const scalarField& qfld = qBf[patchi];
+
+ const polyPatch& pp = mesh().boundaryMesh()[patchi];
+ const labelList& faceCells = pp.faceCells();
+
+ forAll(faceCells, i)
+ {
+ const bool isCovered = isZoneCell[faceCells[i]];
+
+ if (isCovered)
+ {
+ G += qfld[i]*s[i];
+ }
+ }
+ }
+ reduce(G, sumOp());
+
+ return G/area_;
+ }
+ }
+
+ return -1;
+}
+
+
+Foam::tmp>
+Foam::evapotranspirationHeatTransferModels::grass::qBf() const
+{
+ const auto& T = mesh().lookupObject(TName_);
+ const volScalarField::Boundary& Tbf = T.boundaryField();
+
+ auto tq = tmp>::New(Tbf.size());
+ auto& q = tq.ref();
+
+ forAll(q, patchi)
+ {
+ q.set(patchi, new Field(Tbf[patchi].size(), Zero));
+ }
+
+ typedef compressible::turbulenceModel cmpTurbModel;
+
+ if (mesh().foundObject(cmpTurbModel::propertiesName))
+ {
+ const auto& turb =
+ mesh().lookupObject(cmpTurbModel::propertiesName);
+
+ // Note: calling he(p,T) instead of he()
+ const volScalarField he(turb.transport().he(turb.transport().p(), T));
+ const volScalarField::Boundary& hebf = he.boundaryField();
+
+ const volScalarField alphaEff(turb.alphaEff());
+ const volScalarField::Boundary& alphaEffbf = alphaEff.boundaryField();
+
+ for (const label patchi : patchSet_)
+ {
+ q[patchi] = alphaEffbf[patchi]*hebf[patchi].snGrad();
+ }
+ }
+ else if (mesh().foundObject(fluidThermo::dictName))
+ {
+ const auto& thermo =
+ mesh().lookupObject(fluidThermo::dictName);
+
+ // Note: calling he(p,T) instead of he()
+ const volScalarField he(thermo.he(thermo.p(), T));
+ const volScalarField::Boundary& hebf = he.boundaryField();
+
+ const volScalarField& alpha(thermo.alpha());
+ const volScalarField::Boundary& alphabf = alpha.boundaryField();
+
+ for (const label patchi : patchSet_)
+ {
+ q[patchi] = alphabf[patchi]*hebf[patchi].snGrad();
+ }
+ }
+ else
+ {
+ FatalErrorInFunction
+ << "Unable to find a valid thermo model to evaluate q. " << nl
+ << "Database contents are: " << mesh().objectRegistry::sortedToc()
+ << exit(FatalError);
+ }
+
+ // No radiative heat flux contribution is present
+
+ return tq;
+}
+
+
+// * * * * * * * * * * * * * * * * Constructors * * * * * * * * * * * * * * //
+
+Foam::evapotranspirationHeatTransferModels::grass::grass
+(
+ const dictionary& dict,
+ const fvMesh& mesh
+)
+:
+ evapotranspirationHeatTransferModel(dict, mesh),
+ soilHeatFluxMethod_
+ (
+ soilHeatFluxTypeNames.getOrDefault
+ (
+ "soilHeatFluxMethod",
+ dict,
+ soilHeatFluxType::PROPORTIONAL_TO_SOLAR_RADIATION
+ )
+ ),
+ Tptr_(nullptr),
+ RHptr_(nullptr),
+ area_(-1),
+ Csoil_(),
+ rho_(),
+ Cp_(),
+ epsilon_(),
+ h_(),
+ kappa_(),
+ uRef_(),
+ zRefU_(),
+ zRefH_(),
+ zom_(),
+ zoh_(),
+ d_(),
+ ri_(),
+ pAtm_(),
+ Tref_(0),
+ TName_(),
+ patchSet_()
+{
+ Info<< " Activating evapotranspiration heat transfer model: "
+ << typeName << endl;
+
+ read(dict);
+}
+
+
+// * * * * * * * * * * * * * * Member Functions * * * * * * * * * * * * * * //
+
+Foam::tmp
+Foam::evapotranspirationHeatTransferModels::grass::Q
+(
+ const labelList& cells
+) const
+{
+ if (area_ == -1)
+ {
+ area_ = S(cells);
+ }
+
+ Tref_ = Tptr_->value(mesh().time().timeOutputValue());
+
+ return E(cells)*area_;
+}
+
+
+bool Foam::evapotranspirationHeatTransferModels::grass::read
+(
+ const dictionary& dict
+)
+{
+ if (!evapotranspirationHeatTransferModel::read(dict))
+ {
+ return false;
+ }
+
+ Tptr_.reset(Function1::New("Tref", dict, &mesh()));
+ RHptr_.reset(Function1::New("RHref", dict, &mesh()));
+
+ area_ = -1;
+
+ const auto range = scalarMinMax::ge(SMALL);
+
+ Csoil_ = dict.getCheckOrDefault("Csoil", 0.1, range);
+ rho_ = dict.getCheckOrDefault("rho", 1.225, range);
+ Cp_ = dict.getCheckOrDefault("Cp", 1013.0, range);
+ epsilon_ = dict.getCheckOrDefault("epsilon", 0.622, range);
+ h_ = dict.getCheckOrDefault("h", 0.1, range);
+ kappa_ = dict.getCheckOrDefault("kappa", 0.41, range);
+ uRef_ = dict.getCheckOrDefault("uRef", 2, range);
+ zRefU_ = dict.getCheckOrDefault("zRefU", 10, range);
+ zRefH_ = dict.getCheckOrDefault("zRefH", 10, range);
+ zom_ = dict.getCheckOrDefault("zom", 0.123, range);
+ zoh_ = dict.getCheckOrDefault("zoh", 0.0123, range);
+ d_ = dict.getCheckOrDefault("d", 2.0/3.0, range);
+ ri_ = dict.getCheckOrDefault("ri", 100, range);
+ pAtm_ = dict.getCheckOrDefault("pAtm", 101.325, range);
+ TName_ = dict.getOrDefault("T", "T");
+
+ if (soilHeatFluxMethod_ == soilHeatFluxType::BOUNDARY)
+ {
+ patchSet_ =
+ mesh().boundaryMesh().patchSet(dict.get("patches"));
+ }
+
+ return true;
+}
+
+
+// ************************************************************************* //
diff --git a/src/atmosphericModels/fvOptions/evapotranspirationHeatTransfer/evapotranspirationHeatTransferModels/grass/grass.H b/src/atmosphericModels/fvOptions/evapotranspirationHeatTransfer/evapotranspirationHeatTransferModels/grass/grass.H
new file mode 100644
index 0000000000..b679f4e3ae
--- /dev/null
+++ b/src/atmosphericModels/fvOptions/evapotranspirationHeatTransfer/evapotranspirationHeatTransferModels/grass/grass.H
@@ -0,0 +1,342 @@
+/*---------------------------------------------------------------------------*\
+ ========= |
+ \\ / F ield | OpenFOAM: The Open Source CFD Toolbox
+ \\ / O peration |
+ \\ / A nd | www.openfoam.com
+ \\/ M anipulation |
+-------------------------------------------------------------------------------
+ Copyright (C) 2022 OpenCFD Ltd.
+-------------------------------------------------------------------------------
+License
+ This file is part of OpenFOAM.
+
+ OpenFOAM is free software: you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ OpenFOAM is distributed in the hope that it will be useful, but WITHOUT
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with OpenFOAM. If not, see .
+
+Class
+ Foam::evapotranspirationHeatTransferModels::grass
+
+Description
+ Applies sources on temperature (\c T - incompressible) or energy
+ (\c h/e - compressible) equation to incorporate evapotranspiration
+ heat-transfer effects from the specified grass canopy.
+ The model is based on Pemnan-Monteith Equation model.
+
+ Sources applied to either of the below, if exist:
+ \verbatim
+ T | Temperature [K]
+ e | Internal energy [m^2/s^2]
+ h | Enthalphy [m^2/s^2]
+ \endverbatim
+
+ Required fields:
+ \verbatim
+ T | Temperature [K]
+ e | Internal energy [m^2/s^2]
+ h | Enthalphy [m^2/s^2]
+ LAD | Leaf area density [m^2/m^3]
+ \endverbatim
+
+ References:
+ \verbatim
+ Governing equations (tag:BSG):
+ Brozovsky, J., Simonsen, A., & Gaitani, N. (2021).
+ Validation of a CFD model for the evaluation of urban
+ microclimate at high latitudes: A case study in Trondheim, Norway.
+ Building and Environment, 205, 108175.
+ DOI:10.1016/j.buildenv.2021.108175
+
+ Governing equations (tag:APR):
+ Allen, R. G., Pereira, L. S., Raes, D., & Smith, M. (1998).
+ Crop evapotranspiration-Guidelines for computing crop
+ water requirements-FAO Irrigation and drainage paper 56.
+ Fao, Rome, 300(9), D05109.
+ \endverbatim
+
+Usage
+ Example by using \c constant/fvOptions:
+ \verbatim
+ evapotranspirationHeatTransfer1
+ {
+ // Inherited entries
+ ...
+
+ // Mandatory entries
+ model grass;
+ Tref >;
+ RHref >;
+
+ // Conditional entries
+
+ // when soilHeatFluxMethod == boundary
+ patches ;
+
+ // Optional entries
+ soilHeatFluxMethod ;
+ Csoil ;
+ rho ;
+ Cp ;
+ epsilon ;
+ h ;
+ kappa ;
+ uRef ;
+ zRefU ;
+ zRefH ;
+ zom ;
+ zoh ;
+ d ;
+ ri ;
+ pAtm ;
+ T ;
+ }
+ \endverbatim
+
+ where the entries mean:
+ \table
+ Property | Description | Type | Reqd | Deflt
+ model | Model name: grass | word | yes | -
+ Tref | Reference weather station air temperature [Celsius] | Function1\ | yes | -
+ RHref | Reference weather station relative humidity [%] | Function1\ | yes | -
+ patches | Names of ground patches | wordRes | yes | -
+ soilHeatFluxMethod | Method to calculate soil heat flux - see below | word | no | -
+ Csoil | Proportionality constant of soil heat-flux wrt solar radiation | scalar | no | 0.1
+ rho | Mean air density at constant pressure [kg/m^3] | scalar | no | 1.225
+ Cp | Specific heat at constant pressure [J/kg/C] | scalar | no | 1013.0
+ epsilon | Molecular-weight ratio of water vapour/dry air [-] | scalar | no | 0.622
+ h | Height of grass layer [m] | scalar | no | 0.1
+ kappa | Von Karman constant [-] | scalar | no | 0.41
+ uRef | Reference velocity magnitude [m/s] | scalar | no | 2.0
+ zRefU | Height of wind speed measurements [m] | scalar | no | 10.0
+ zRefH | Height of humidity measurements [m] | scalar | no | 10.0
+ zom | Roughness length governing momentum transfer coefficient [-] | scalar | no | 0.123
+ zoh | Roughness length governing transfer of heat and vapour coefficient [-] | scalar | no | 0.0123
+ d | Zero plane displacement height coefficient [-] | scalar | no | 0.666
+ ri | Bulk stomata resistance of leaf [s/m] | scalar | no | 100.0
+ pAtm | Atmospheric pressure [kPa] | scalar | no | 101.325
+ T | Name of temperature field | word | no | T
+ \endtable
+
+ Options for the \c soilHeatFluxMethod entry:
+ \verbatim
+ proportionalToSolarRadiation | Estimate from solar load
+ boundary | Obtain soil heat flux from boundary
+ \endverbatim
+
+ The inherited entries are elaborated in:
+ - \link evapotranspirationHeatTransfer.H \endlink
+ - \link evapotranspirationHeatTransferModel.H \endlink
+
+SourceFiles
+ grass.C
+ grassTemplates.C
+
+\*---------------------------------------------------------------------------*/
+
+#ifndef Foam_evapotranspirationHeatTransferModels_grass_H
+#define Foam_evapotranspirationHeatTransferModels_grass_H
+
+#include "evapotranspirationHeatTransferModel.H"
+#include "Function1.H"
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+namespace Foam
+{
+namespace evapotranspirationHeatTransferModels
+{
+
+/*---------------------------------------------------------------------------*\
+ Class grass Declaration
+\*---------------------------------------------------------------------------*/
+
+class grass
+:
+ public evapotranspirationHeatTransferModel
+{
+ // Private Enumerations
+
+ //- Options for the soil heat flux
+ enum soilHeatFluxType : char
+ {
+ PROPORTIONAL_TO_SOLAR_RADIATION = 0, //!< "Estimate from solar load"
+ BOUNDARY, //!< "Obtain soil heat flux from boundary"
+ };
+
+ //- Names for soilHeatFluxType
+ static const Enum soilHeatFluxTypeNames;
+
+
+ // Private Data
+
+ //- Soil heat-flux calculation method
+ enum soilHeatFluxType soilHeatFluxMethod_;
+
+ //- Reference weather station air temperature [Celsius]
+ autoPtr> Tptr_;
+
+ //- Reference weather station relative humidity [%]
+ autoPtr> RHptr_;
+
+ //- Area coverage of grass [m^2]
+ mutable scalar area_;
+
+ //- Proportionality constant of soil heat-flux wrt solar radiation [-]
+ scalar Csoil_;
+
+ //- Mean air density at constant pressure [kg/m^3]
+ scalar rho_;
+
+ //- Specific heat at constant pressure [J/kg/C]
+ scalar Cp_;
+
+ //- Molecular-weight ratio of water vapour/dry air [-]
+ scalar epsilon_;
+
+ //- Height of grass layer [m]
+ scalar h_;
+
+ //- Von Karman constant [-]
+ scalar kappa_;
+
+ //- Reference velocity magnitude [m/s]
+ scalar uRef_;
+
+ //- Height of wind speed measurements [m]
+ scalar zRefU_;
+
+ //- Height of humidity measurements [m]
+ scalar zRefH_;
+
+ //- Roughness length governing momentum transfer coefficient [-]
+ scalar zom_;
+
+ //- Roughness length governing transfer of heat and vapour coeff [-]
+ scalar zoh_;
+
+ //- Zero plane displacement height coefficient [-]
+ scalar d_;
+
+ //- Bulk stomata resistance of leaf [s/m]
+ scalar ri_;
+
+ //- Atmospheric pressure [kPa]
+ scalar pAtm_;
+
+ //- Cached reference weather station air temperature [Celsius]
+ mutable scalar Tref_;
+
+ //- Name of temperature field
+ word TName_;
+
+ //- List of patches to calculate boundary heat flux
+ labelHashSet patchSet_;
+
+
+ // Private Member Functions
+
+ //- Return evapotranspiration heat flux through grass layer [W/m^2]
+ tmp E(const labelList& cells) const;
+
+ //- Return slope of the relation between
+ //- vapour pressure-temperature [Pa/K]
+ scalar Delta() const;
+
+ //- Return atmospheric vapour pressure deficit [Pa]
+ scalar D() const;
+
+ //- Return saturated vapour pressure over water [Pa]
+ scalar pSat() const;
+
+ //- Return psychrometric constant [kPa/K]
+ scalar gamma() const;
+
+ //- Return specific latent heat of vaporisation [MJ/kg]
+ scalar lambda() const;
+
+ //- Return bulk aerodynamic resistance [Pa]
+ scalar ra() const;
+
+ //- Return bulk surface resistance [Pa]
+ scalar rs() const;
+
+ //- Return area coverage of grass [m^2]
+ scalar S(const labelList& cells) const;
+
+ //- Return area-averaged heat flux through ground [W/m^2]
+ scalar G(const labelList& cells) const;
+
+ //- Return heat-flux boundary fields
+ tmp> qBf() const;
+
+
+public:
+
+ //- Runtime type information
+ TypeName("grass");
+
+
+ // Constructors
+
+ //- Construct from components
+ grass
+ (
+ const dictionary& dict,
+ const fvMesh& mesh
+ );
+
+ //- No copy construct
+ grass(const grass&) = delete;
+
+ //- No copy assignment
+ void operator=(const grass&) = delete;
+
+
+ //- Destructor
+ virtual ~grass() = default;
+
+
+ // Member Functions
+
+ // Evaluation
+
+ //- Return heat-transfer rate for speficied cells [J/s]
+ virtual tmp Q(const labelList& cells) const;
+
+
+ // I-O
+
+ //- Read the dictionary
+ virtual bool read(const dictionary& dict);
+};
+
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+} // End namespace evapotranspirationHeatTransferModels
+} // End namespace Foam
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+#endif
+
+// ************************************************************************* //
diff --git a/src/atmosphericModels/fvOptions/evapotranspirationHeatTransfer/evapotranspirationHeatTransferModels/tree/tree.C b/src/atmosphericModels/fvOptions/evapotranspirationHeatTransfer/evapotranspirationHeatTransferModels/tree/tree.C
new file mode 100644
index 0000000000..9c5cb5daee
--- /dev/null
+++ b/src/atmosphericModels/fvOptions/evapotranspirationHeatTransfer/evapotranspirationHeatTransferModels/tree/tree.C
@@ -0,0 +1,139 @@
+/*---------------------------------------------------------------------------*\
+ ========= |
+ \\ / F ield | OpenFOAM: The Open Source CFD Toolbox
+ \\ / O peration |
+ \\ / A nd | www.openfoam.com
+ \\/ M anipulation |
+-------------------------------------------------------------------------------
+ Copyright (C) 2022 OpenCFD Ltd.
+-------------------------------------------------------------------------------
+License
+ This file is part of OpenFOAM.
+
+ OpenFOAM is free software: you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ OpenFOAM is distributed in the hope that it will be useful, but WITHOUT
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with OpenFOAM. If not, see .
+
+\*---------------------------------------------------------------------------*/
+
+#include "tree.H"
+#include "addToRunTimeSelectionTable.H"
+
+// * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * //
+
+namespace Foam
+{
+namespace evapotranspirationHeatTransferModels
+{
+ defineTypeNameAndDebug(tree, 0);
+ addToRunTimeSelectionTable
+ (
+ evapotranspirationHeatTransferModel,
+ tree,
+ dictionary
+ );
+}
+}
+
+
+// * * * * * * * * * * * * * Private Member Functions * * * * * * * * * * * //
+
+Foam::scalar Foam::evapotranspirationHeatTransferModels::tree::Et() const
+{
+ return a_*q() + b_;
+}
+
+
+Foam::tmp
+Foam::evapotranspirationHeatTransferModels::tree::leafArea
+(
+ const labelList& cells
+) const
+{
+ const volScalarField& LAD = getOrReadField(LADname_);
+
+ const scalarField& V = mesh().V();
+
+ auto tleafArea = tmp::New(cells.size(), Zero);
+ auto& leafArea = tleafArea.ref();
+
+ forAll(cells, i)
+ {
+ const label celli = cells[i];
+
+ leafArea[i] = LAD[celli]*V[celli];
+ }
+
+ return tleafArea;
+}
+
+
+// * * * * * * * * * * * * * * * * Constructors * * * * * * * * * * * * * * //
+
+Foam::evapotranspirationHeatTransferModels::tree::tree
+(
+ const dictionary& dict,
+ const fvMesh& mesh
+)
+:
+ evapotranspirationHeatTransferModel(dict, mesh),
+ a_(),
+ b_(),
+ lambda_(),
+ LADname_()
+{
+ Info<< " Activating evapotranspiration heat transfer model: "
+ << typeName << endl;
+
+ read(dict);
+}
+
+
+// * * * * * * * * * * * * * * Member Functions * * * * * * * * * * * * * * //
+
+Foam::tmp
+Foam::evapotranspirationHeatTransferModels::tree::Q
+(
+ const labelList& cells
+) const
+{
+ // Convert units from [MJ g/hr] to [J kg/s]
+ static const scalar unitConverter = scalar(1000)/scalar(3600);
+
+ return unitConverter*lambda_*Et()*leafArea(cells);
+}
+
+
+bool Foam::evapotranspirationHeatTransferModels::tree::read
+(
+ const dictionary& dict
+)
+{
+ if (!evapotranspirationHeatTransferModel::read(dict))
+ {
+ return false;
+ }
+
+ const auto range = scalarMinMax::ge(SMALL);
+
+ a_ = dict.getOrDefault("a", 0.3622);
+ b_ = dict.getOrDefault("b", 60.758);
+ lambda_ = dict.getCheckOrDefault("lambda", 2.44, range);
+ LADname_ = dict.getOrDefault("LAD", "LAD");
+
+ (void) getOrReadField(LADname_);
+
+ return true;
+}
+
+
+// ************************************************************************* //
diff --git a/src/atmosphericModels/fvOptions/evapotranspirationHeatTransfer/evapotranspirationHeatTransferModels/tree/tree.H b/src/atmosphericModels/fvOptions/evapotranspirationHeatTransfer/evapotranspirationHeatTransferModels/tree/tree.H
new file mode 100644
index 0000000000..c8704a2cb3
--- /dev/null
+++ b/src/atmosphericModels/fvOptions/evapotranspirationHeatTransfer/evapotranspirationHeatTransferModels/tree/tree.H
@@ -0,0 +1,219 @@
+/*---------------------------------------------------------------------------*\
+ ========= |
+ \\ / F ield | OpenFOAM: The Open Source CFD Toolbox
+ \\ / O peration |
+ \\ / A nd | www.openfoam.com
+ \\/ M anipulation |
+-------------------------------------------------------------------------------
+ Copyright (C) 2022 OpenCFD Ltd.
+-------------------------------------------------------------------------------
+License
+ This file is part of OpenFOAM.
+
+ OpenFOAM is free software: you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ OpenFOAM is distributed in the hope that it will be useful, but WITHOUT
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with OpenFOAM. If not, see .
+
+Class
+ Foam::evapotranspirationHeatTransferModels::tree
+
+Description
+ Applies sources on temperature (\c T - incompressible) or energy
+ (\c h/e - compressible) equation to incorporate evapotranspiration
+ heat-transfer effects from the specified tree canopy.
+
+ The heat-transfer is calculated based on the following relations.
+ The heat transfer is given by the first equation and the
+ evapotranspiration is calculated linearly proportional to absorbed
+ solar radiation (direct and diffusive) with the empirical relation
+ shown in the second equation below.
+
+ \f[
+ Q = \lambda E_t \frac{1000}{3600} \int_{cellZone} LAD \, dV
+ \f]
+
+ where
+ \vartable
+ Q | Heat transfer from tree crown [W]
+ \lambda | Specific latent heat of vaporisation [MJ/kg]
+ E_t | Area-averaged evapotranspiration [g/m^2/hr]
+ LAD | Leaf area density [m^2/m^3]
+ V | Volume [m^3]
+ \endvartable
+
+ with
+
+ \f[
+ E_t = a R_n + b
+ \f]
+
+ where
+ \vartable
+ R_n | Heat flux through tree crown [W/m^2]
+ a | Linear-regression coefficient - slope parameter [g/W/hr]
+ b | Linear-regression coefficient - intercept parameter [g/m^2/hr]
+ \endvartable
+
+ Sources applied to either of the below, if exist:
+ \verbatim
+ T | Temperature [K]
+ e | Internal energy [m^2/s^2]
+ h | Enthalphy [m^2/s^2]
+ \endverbatim
+
+ Required fields:
+ \verbatim
+ T | Temperature [K]
+ e | Internal energy [m^2/s^2]
+ h | Enthalphy [m^2/s^2]
+ LAD | Leaf area density [m^2/m^3]
+ \endverbatim
+
+Usage
+ Example by using \c constant/fvOptions:
+ \verbatim
+ evapotranspirationHeatTransfer1
+ {
+ // Inherited entries
+ ...
+
+ // Mandatory entries
+ model tree;
+
+ // Optional entries
+ a ;
+ b ;
+ lambda ;
+ LAD ;
+ }
+ \endverbatim
+
+ where the entries mean:
+ \table
+ Property | Description | Type | Reqd | Deflt
+ model | Model name: tree | word | yes | -
+ a | Linear-regression coefficient - slope parameter [g/W/hr] | scalar | no | 0.3622
+ b | Linear-regression coefficient - intercept parameter [g/m^2/hr] | scalar | no | 60.758
+ lambda | Specific latent heat of vaporisation [MJ/kg] | scalar | no | 2.44
+ LAD | Name of leaf-area density field | word | no | LAD
+ \endtable
+
+ The inherited entries are elaborated in:
+ - \link evapotranspirationHeatTransfer.H \endlink
+ - \link evapotranspirationHeatTransferModel.H \endlink
+
+SourceFiles
+ tree.C
+ treeTemplates.C
+
+\*---------------------------------------------------------------------------*/
+
+#ifndef Foam_evapotranspirationHeatTransferModels_tree_H
+#define Foam_evapotranspirationHeatTransferModels_tree_H
+
+#include "evapotranspirationHeatTransferModel.H"
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+namespace Foam
+{
+namespace evapotranspirationHeatTransferModels
+{
+
+/*---------------------------------------------------------------------------*\
+ Class tree Declaration
+\*---------------------------------------------------------------------------*/
+
+class tree
+:
+ public evapotranspirationHeatTransferModel
+{
+ // Private Data
+
+ //- Linear-regression coefficient - slope parameter [g/W/hr]
+ scalar a_;
+
+ //- Linear-regression coefficient - intercept parameter [g/m^2/hr]
+ scalar b_;
+
+ //- Specific latent heat of vaporisation [MJ/kg]
+ scalar lambda_;
+
+ //- Name of leaf-area density field
+ word LADname_;
+
+
+ // Private Member Functions
+
+ //- Return area-averaged transpiration [g/m^2/hr]
+ // Calculated from empirical regressions with heat flux
+ scalar Et() const;
+
+ //- Return volume weighted leaf area density (LAD) [m^2]
+ // LAD [m^2/m^3]: one-sided area of leaves per unit volume
+ tmp leafArea(const labelList& cells) const;
+
+
+public:
+
+ //- Runtime type information
+ TypeName("tree");
+
+
+ // Constructors
+
+ //- Construct from components
+ tree
+ (
+ const dictionary& dict,
+ const fvMesh& mesh
+ );
+
+ //- No copy construct
+ tree(const tree&) = delete;
+
+ //- No copy assignment
+ void operator=(const tree&) = delete;
+
+
+ //- Destructor
+ virtual ~tree() = default;
+
+
+ // Member Functions
+
+ // Evaluation
+
+ //- Return heat-transfer rate for speficied cells [J/s]
+ virtual tmp Q(const labelList& cells) const;
+
+
+ // I-O
+
+ //- Read the dictionary
+ virtual bool read(const dictionary& dict);
+};
+
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+} // End namespace evapotranspirationHeatTransferModels
+} // End namespace Foam
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+#endif
+
+// ************************************************************************* //