Loading...
Searching...
No Matches
comfort.C
Go to the documentation of this file.
1/*---------------------------------------------------------------------------*\
2 ========= |
3 \\ / F ield | OpenFOAM: The Open Source CFD Toolbox
4 \\ / O peration |
5 \\ / A nd | www.openfoam.com
6 \\/ M anipulation |
7-------------------------------------------------------------------------------
8 Copyright (C) 2019 OpenFOAM Foundation
9 Copyright (C) 2021-2025 OpenCFD Ltd.
10-------------------------------------------------------------------------------
11License
12 This file is part of OpenFOAM.
13
14 OpenFOAM is free software: you can redistribute it and/or modify it
15 under the terms of the GNU General Public License as published by
16 the Free Software Foundation, either version 3 of the License, or
17 (at your option) any later version.
18
19 OpenFOAM is distributed in the hope that it will be useful, but WITHOUT
20 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
21 FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
22 for more details.
23
24 You should have received a copy of the GNU General Public License
25 along with OpenFOAM. If not, see <http://www.gnu.org/licenses/>.
26
27\*---------------------------------------------------------------------------*/
28
29#include "comfort.H"
30#include "wallFvPatch.H"
33// * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * //
34
35namespace Foam
36{
37namespace functionObjects
38{
40 addToRunTimeSelectionTable(functionObject, comfort, dictionary);
41}
42}
43
44
45// Temperature bounds based on EN ISO 7730 (10 - 40 degC)
46static const Foam::scalarMinMax Tbounds(283.15, 313.15);
47
48
49// * * * * * * * * * * * * * Private Member Functions * * * * * * * * * * * //
50
51Foam::tmp<Foam::volScalarField> Foam::functionObjects::comfort::magU() const
52{
53 tmp<volScalarField> tmagU = mag(lookupObject<volVectorField>("U"));
54 volScalarField& magU = tmagU.ref();
55
56 // Flag to use the averaged velocity field in the domain.
57 // Consistent with EN ISO 7730 but does not make physical sense
58 if (meanVelocity_)
59 {
60 magU = magU.weightedAverage(mesh_.V());
61 }
62
63 return tmagU;
64}
65
66
67void Foam::functionObjects::comfort::initWallInfo() const
68{
69 if (wallInfoInit_) return;
70
71 const fvBoundaryMesh& patches = mesh_.boundary();
72 label wallCount = 0;
73 forAll(patches, patchi)
74 {
75 if (isType<wallFvPatch>(patches[patchi]))
76 {
77 ++wallCount;
78 }
79 }
80
81 wallArea_ = 0;
82 wallPatchIDs_.setSize(wallCount);
83
84 scalar localWallArea = 0;
85 label wallIndex = 0;
86 forAll(patches, patchi)
87 {
88 const fvPatch& p = patches[patchi];
90 {
91 wallPatchIDs_[wallIndex++] = patchi;
92 localWallArea += sum(p.magSf());
93 }
94 }
95
96 wallArea_ = localWallArea;
97 reduce(wallArea_, sumOp<scalar>());
98
99 wallInfoInit_ = true;
100}
101
102
103Foam::dimensionedScalar Foam::functionObjects::comfort::Trad() const
104{
105 if (TradSet_)
106 {
107 if (!Tbounds.contains(Trad_.value()))
108 {
110 << "The calculated mean wall radiation temperature is out of\n"
111 << "the bounds specified in EN ISO 7730:2005\n"
112 << "Valid range is 10 degC < T < 40 degC\n"
113 << "The actual value is: " << (Trad_.value() - 273.15) << nl
114 << endl;
115 }
116 return Trad_;
117 }
118
119
120 // The mean radiation is calculated by the mean wall temperatures
121 // which are summed and divided by the area | only walls are taken into
122 // account. This approach might be correct for a squared room but will
123 // definitely be inconsistent for complex room geometries. The norm does
124 // not provide any information about the calculation of this quantity.
125 const volScalarField::Boundary& TBf =
126 lookupObject<volScalarField>("T").boundaryField();
127
128
129 scalar TareaIntegral = 0;
130
131 forAll(wallPatchIDs_, wi)
132 {
133 const label patchi = wallPatchIDs_[wi];
134 const fvPatchScalarField& Tp = TBf[patchi];
135 const fvPatch& p = Tp.patch();
136 TareaIntegral += weightedSum(p.magSf(), Tp);
137 }
138
139 reduce(TareaIntegral, sumOp<scalar>());
140
141 dimensionedScalar TradMean
142 (
144 TareaIntegral/(wallArea_ + VSMALL)
145 );
146
147
148 if (!Tbounds.contains(TradMean.value()))
149 {
151 << "The calculated mean wall radiation temperature is out of\n"
152 << "the bounds specified in EN ISO 7730:2005\n"
153 << "Valid range is 10 degC < T < 40 degC\n"
154 << "The actual value is: " << (TradMean.value() - 273.15) << nl
155 << endl;
156 }
157
158 return TradMean;
159}
160
161
162Foam::tmp<Foam::volScalarField> Foam::functionObjects::comfort::pSat() const
163{
164 static const dimensionedScalar kPaToPa(dimPressure, 1000);
165 static const dimensionedScalar A(dimless, 16.6563);
166 static const dimensionedScalar B(dimTemperature, 4030.183);
167 static const dimensionedScalar C(dimTemperature, -38.15);
168
169 tmp<volScalarField> tpSat;
170
171 // Calculate the saturation pressure if no user input is given
172 if (pSat_.value() == 0)
173 {
174 const auto& T = lookupObject<volScalarField>("T");
175
176 // Equation based on ISO 7730:2006
177 tpSat = kPaToPa*exp(A - B/(T + C));
178 }
179 else
180 {
181 tpSat = volScalarField::New("pSat", mesh_, pSat_);
182 }
183
184 return tpSat;
185}
186
187
188Foam::tmp<Foam::volScalarField> Foam::functionObjects::comfort::Tcloth
189(
190 volScalarField& hc,
191 const dimensionedScalar& metabolicRateSI,
192 const dimensionedScalar& extWorkSI,
193 const volScalarField& T,
194 const dimensionedScalar& Trad
195)
196{
197 static const dimensionedScalar factor1(dimTemperature, 308.85);
198 static const dimensionedScalar factor2
199 (
200 dimTemperature/metabolicRateSI.dimensions(),
201 0.028
202 );
203 static const dimensionedScalar factor3
204 (
206 3.96e-8
207 );
208 static const dimensionedScalar coeffHCf
209 (
210 hc.dimensions()/sqrt(dimVelocity), 12.1
211 );
212 static const dimensionedScalar coeffHCn
213 (
214 hc.dimensions()/pow025(dimTemperature), 2.38
215 );
216 const dimensionedScalar MminusW(metabolicRateSI - extWorkSI);
217 const dimensionedScalar Trad4(pow4(Trad));
218
219 // Heat transfer coefficient based on forced convection [W/m^2/K]
220 const volScalarField hcForced(coeffHCf*sqrt(magU()));
221
222 // Tcl [K] (surface cloth temperature)
223 auto tTcl = volScalarField::New("Tcl", mesh_, dimTemperature);
224 auto& Tcl = tTcl.ref();
225 Tcl = T; // initial guess
226 Tcl.storePrevIter();
227
228 // Heat transfer coefficient based on natural convection
229 auto thcNatural = volScalarField::New("hcNatural", mesh_, hc.dimensions());
230 auto& hcNatural = thcNatural.ref();
231
232 label iter = 0;
233 // Iterative solving of equation (2)
234 do
235 {
236 Tcl = 0.5*(Tcl + Tcl.prevIter());
237 Tcl.storePrevIter();
238
239 // Heat transfer coefficient based on natural convection
240 hcNatural = coeffHCn*pow025(mag(Tcl - T));
241
242 // Set heat transfer coefficient based on equation (3)
243 hc =
244 pos(hcForced - hcNatural)*hcForced
245 + neg0(hcForced - hcNatural)*hcNatural;
246
247 // Calculate surface temperature based on equation (2)
248 Tcl =
249 factor1
250 - factor2*MminusW
251 - Icl_*factor3*fcl_*(pow4(Tcl) - Trad4)
252 - Icl_*fcl_*hc*(Tcl - T);
253
254 // Make sure that Tcl is in some physical limit (same range as we used
255 // for the radiative estimation - based on ISO EN 7730:2005)
256 Tcl.clamp_range(Tbounds);
257
258 } while (!converged(Tcl) && ++iter < maxClothIter_);
259
260 if (iter == maxClothIter_)
261 {
263 << "The surface cloth temperature 'Tcl' did not converge within "
264 << iter << " iterations" << nl;
265 }
266
267 return tTcl;
268}
269
270
271bool Foam::functionObjects::comfort::converged
272(
273 const volScalarField& phi
274) const
275{
276 const auto& curr = phi.primitiveField();
277 const auto& prev = phi.prevIter().primitiveField();
278 forAll(curr, i)
279 {
280 if (mag(curr[i] - prev[i]) >= tolerance_)
281 {
282 return false;
283 }
285 return true;
286}
287
288
289// * * * * * * * * * * * * * * * * Constructors * * * * * * * * * * * * * * //
290
292(
293 const word& name,
294 const Time& runTime,
295 const dictionary& dict
296)
297:
298 fvMeshFunctionObject(name, runTime, dict),
299 clothing_("clothing", dimless, 0),
300 metabolicRate_("metabolicRate", dimMass/pow3(dimTime), 0.8),
301 extWork_("extWork", dimMass/pow3(dimTime), 0),
302 Trad_("Trad", dimTemperature, 0),
303 relHumidity_("relHumidity", dimless, 0.5),
304 pSat_("pSat", dimPressure, 0),
305 Icl_("Icl", pow3(dimTime)*dimTemperature/dimMass, 0),
306 fcl_("fcl", dimless, 0),
307 tolerance_(1e-4),
308 wallArea_(0),
309 maxClothIter_(100),
310 wallInfoInit_(false),
311 TradSet_(false),
312 meanVelocity_(false)
314 read(dict);
315}
316
317
318// * * * * * * * * * * * * * * * Member Functions * * * * * * * * * * * * * //
319
321{
323 {
324 return false;
325 }
326
327 clothing_.readIfPresent(dict);
328 metabolicRate_.readIfPresent(dict);
329 extWork_.readIfPresent(dict);
330 pSat_.readIfPresent(dict);
331 tolerance_ = dict.getOrDefault("tolerance", 1e-4);
332 maxClothIter_ = dict.getOrDefault("maxClothIter", 100);
333 meanVelocity_ = dict.getOrDefault("meanVelocity", false);
334
335 // Read relative humidity if provided and convert from % to fraction
336 if (dict.found(relHumidity_.name()))
337 {
338 relHumidity_.read(dict);
339 relHumidity_ /= scalar(100);
340 }
341
342 // Read radiation temperature if provided
343 if (dict.found(Trad_.name()))
344 {
345 TradSet_ = true;
346 Trad_.read(dict);
347 }
348
349 Icl_ = dimensionedScalar(Icl_.dimensions(), 0.155)*clothing_;
350
351 fcl_.value() =
352 Icl_.value() <= 0.078
353 ? 1.0 + 1.290*Icl_.value()
354 : 1.05 + 0.645*Icl_.value();
356 initWallInfo();
357
358 return true;
359}
360
361
363{
364 // Assign and build fields
365 const dimensionedScalar Trad(this->Trad());
366 const volScalarField pSatRH(this->pSat()*relHumidity_);
367
368 const dimensionedScalar metabolicRateSI(58.15*metabolicRate_);
369 const dimensionedScalar extWorkSI(58.15*extWork_);
370 const dimensionedScalar metaDiff(metabolicRateSI - extWorkSI);
371
372 const auto& T = lookupObject<volScalarField>("T");
373
374 // Heat transfer coefficient [W/m^2/K]
375 // This field is updated in Tcloth()
377 (
379 (
380 "hc",
381 mesh_.time().timeName(),
382 mesh_,
384 ),
385 mesh_,
387 );
388
389 // Calculate the surface temperature of the cloth by an iterative
390 // process using equation (2) from DIN EN ISO 7730 [degC]
391 const volScalarField TclothFld
392 (
393 this->Tcloth
394 (
395 hc,
396 metabolicRateSI,
397 extWorkSI,
398 T,
399 Trad
400 )
401 );
402
403 // Calculate the PMV quantity
404 static const dimensionedScalar factor1(pow3(dimTime)/dimMass, 0.303);
405 static const dimensionedScalar factor2
406 (
407 dimless/metabolicRateSI.dimensions(),
408 -0.036
409 );
410 static const dimensionedScalar factor3(factor1.dimensions(), 0.028);
411 static const dimensionedScalar factor4(dimLength/dimTime, 3.05e-3);
412 static const dimensionedScalar factor5(dimPressure, 5733);
413 static const dimensionedScalar factor6(dimTime/dimLength, 6.99);
414 static const dimensionedScalar factor8(metabolicRateSI.dimensions(), 58.15);
415 static const dimensionedScalar factor9(dimless/dimPressure, 1.7e-5);
416 static const dimensionedScalar factor10(dimPressure, 5867);
417 static const dimensionedScalar factor11(dimless/dimTemperature, 0.0014);
418 static const dimensionedScalar factor12(dimTemperature, 307.15);
419 static const dimensionedScalar factor13
420 (
422 3.96e-8
423 );
424
425 const scalar factor7
426 (
427 // Special treatment of Term4
428 // if metaRate - extWork < factor8, set to zero
429 metaDiff.value() < factor8.value() ? 0 : 0.42
430 );
431
432 const dimensionedScalar Trad4(pow4(Trad));
433
434 Info<< "Calculating the predicted mean vote (PMV)" << endl;
435
436 // Equation (1)
438 (
439 // Term1: Thermal sensation transfer coefficient
440 (factor1*exp(factor2*metabolicRateSI) + factor3)
441 *(
442 metaDiff
443
444 // Term2: Heat loss difference through skin
445 - factor4
446 *(
447 factor5
448 - factor6*metaDiff
449 - pSatRH
450 )
451
452 // Term3: Heat loss through sweating
453 - factor7*(metabolicRateSI - extWorkSI - factor8)
454
455 // Term4: Heat loss through latent respiration
456 - factor9*metabolicRateSI*(factor10 - pSatRH)
457
458 // Term5: Heat loss through dry respiration
459 - factor11*metabolicRateSI*(factor12 - T)
460
461 // Term6: Heat loss through radiation
462 - factor13*fcl_*(pow4(TclothFld) - Trad4)
463
464 // Term7: Heat loss through convection
465 - fcl_*hc*(TclothFld - T)
466 )
467 );
468
469 Info<< "Calculating the predicted percentage of dissatisfaction (PPD)"
470 << endl;
471
472 // Equation (5)
474 100 - 95*exp(-0.03353*pow4(PMV()) - 0.21790*sqr(PMV()));
475
476 Info<< "Calculating the draught rating (DR)\n";
477
478 static const dimensionedScalar Umin(dimVelocity, 0.05);
479 static const dimensionedScalar Umax(dimVelocity, 0.5);
480 static const dimensionedScalar pre(dimless, 0.37);
481 static const dimensionedScalar C1(dimVelocity, 3.14);
482
483 // Limit the velocity field to the values given in EN ISO 7733
484 volScalarField Umag(mag(lookupObject<volVectorField>("U")));
485 Umag.clamp_range(Umin, Umax);
486
487 // Calculate the turbulent intensity if turbulent kinetic energy field k
488 // exists
490 (
492 (
493 "TI",
494 mesh_.time().timeName(),
495 mesh_,
497 ),
498 mesh_,
500 );
501
502 if (foundObject<volScalarField>("k"))
503 {
504 const auto& k = lookupObject<volScalarField>("k");
505 TI = sqrt(2/3*k)/Umag;
506 }
507
508 // For unit correctness
509 static const dimensionedScalar correctUnit
510 (
511 dimensionSet(0, -1.62, 1.62, -1, 0, 0, 0),
512 1
513 );
514
515 // Equation (6)
517 correctUnit*(factor12 - T)*pow(Umag - Umin, 0.62)*(pre*Umag*TI + C1);
518
519 // Calculate the operative temperature
520 tmp<volScalarField> Top = 0.5*(T + Trad);
521
522 // Need modifiable field names:
523 word fieldNamePMV = "PMV";
524 word fieldNamePPD = "PPD";
525 word fieldNameDR = "DR";
526 word fieldNameTop = "Top";
527
528 return
529 (
530 store(fieldNamePMV, PMV)
531 && store(fieldNamePPD, PPD)
532 && store(fieldNameDR, DR)
533 && store(fieldNameTop, Top)
534 );
535}
536
537
539{
540 return
541 (
542 writeObject("PMV")
543 && writeObject("PPD")
544 && writeObject("DR")
545 && writeObject("Top")
546 );
547}
548
549
550// ************************************************************************* //
static const Foam::dimensionedScalar C("", Foam::dimTemperature, 234.5)
static const Foam::dimensionedScalar A("", Foam::dimPressure, 611.21)
static const Foam::dimensionedScalar B("", Foam::dimless, 18.678)
label k
Macros for easy insertion into run-time selection tables.
#define addToRunTimeSelectionTable(baseType, thisType, argNames)
Add to construction table with typeName as the key.
static tmp< GeometricField< scalar, fvPatchField, volMesh > > New(const word &name, IOobjectOption::registerOption regOpt, const Mesh &mesh, const dimensionSet &dims, const word &patchFieldType=fvPatchField< scalar >::calculatedType())
void clamp_range(const dimensioned< MinMax< Type > > &range)
Clamp field values (in-place) to the specified range.
Internal & ref(const bool updateAccessTime=true)
Same as internalFieldRef().
GeometricBoundaryField< scalar, fvPatchField, volMesh > Boundary
@ NO_REGISTER
Do not request registration (bool: false).
Defines the attributes of an object for which implicit objectRegistry management is supported,...
Definition IOobject.H:191
bool contains(const T &val) const
True if the value is within the range (inclusive check).
Definition MinMaxI.H:220
Class to control time during OpenFOAM simulations that is also the top-level objectRegistry.
Definition Time.H:75
A list of keyword definitions, which are a keyword followed by a number of values (eg,...
Definition dictionary.H:133
Dimension set for the base types, which can be used to implement rigorous dimension checking for alge...
const dimensionSet & dimensions() const noexcept
Return const reference to dimensions.
const Type & value() const noexcept
Return const reference to value.
Abstract base-class for Time/database function objects.
virtual bool read(const dictionary &dict)
Read and set the function object if its data have changed.
Calculates the thermal comfort quantities predicted mean vote (PMV), predicted percentage of dissatis...
Definition comfort.H:243
virtual bool read(const dictionary &dict)
Read the function-object dictionary.
Definition comfort.C:313
comfort(const word &name, const Time &runTime, const dictionary &dict)
Construct from name, Time and dictionary.
Definition comfort.C:285
virtual bool execute()
Execute the function-object operations.
Definition comfort.C:355
virtual bool write()
Write the function-object results.
Definition comfort.C:531
const fvMesh & mesh_
Reference to the fvMesh.
fvMeshFunctionObject(const fvMeshFunctionObject &)=delete
No copy construct.
const ObjectType & lookupObject(const word &fieldName) const
Lookup and return object (eg, a field) from the (sub) objectRegistry.
bool foundObject(const word &fieldName) const
Find object (eg, a field) in the (sub) objectRegistry.
bool store(word &fieldName, const tmp< ObjectType > &tfield, bool cacheable=false)
Store the field in the (sub) objectRegistry under the given name.
bool writeObject(const word &fieldName)
Write field if present in the (sub) objectRegistry.
const DimensionedField< scalar, volMesh > & V() const
Return cell volumes.
const fvPatch & patch() const noexcept
Return the patch.
A class for managing temporary objects.
Definition tmp.H:75
A class for handling words, derived from Foam::string.
Definition word.H:66
#define defineTypeNameAndDebug(Type, DebugSwitch)
Define the typeName and debug information.
Definition className.H:142
volScalarField & p
const volScalarField & T
static const Foam::scalarMinMax Tbounds(283.15, 313.15)
const polyBoundaryMesh & patches
engineTime & runTime
#define WarningInFunction
Report a warning using Foam::Warning.
Function objects are OpenFOAM utilities to ease workflow configurations and enhance workflows.
Namespace for OpenFOAM.
const dimensionSet dimPressure
Type weightedSum(const UList< scalar > &weights, const UList< Type > &fld)
The local weighted sum (integral) of a field, using the mag() of the weights.
dimensionedScalar pos(const dimensionedScalar &ds)
dimensionedScalar exp(const dimensionedScalar &ds)
const dimensionSet dimless
Dimensionless.
bool read(const char *buf, int32_t &val)
Same as readInt32.
Definition int32.H:127
dimensionedSymmTensor sqr(const dimensionedVector &dv)
const dimensionSet dimLength(0, 1, 0, 0, 0, 0, 0)
dimensionedScalar pow3(const dimensionedScalar &ds)
const dimensionSet dimTime(0, 0, 1, 0, 0, 0, 0)
GeometricField< scalar, fvPatchField, volMesh > volScalarField
const dimensionSet dimVelocity
messageStream Info
Information stream (stdout output on master, null elsewhere).
MinMax< scalar > scalarMinMax
A scalar min/max range.
Definition MinMax.H:97
dimensionedScalar pow(const dimensionedScalar &ds, const dimensionedScalar &expt)
Ostream & endl(Ostream &os)
Add newline and flush stream.
Definition Ostream.H:519
const dimensionSet dimTemperature(0, 0, 0, 1, 0, 0, 0)
dimensionedScalar sqrt(const dimensionedScalar &ds)
dimensioned< typename typeOfMag< Type >::type > mag(const dimensioned< Type > &dt)
void reduce(T &value, BinaryOp bop, const int tag=UPstream::msgType(), const int communicator=UPstream::worldComm)
Reduce inplace (cf. MPI Allreduce).
dimensionedScalar pow4(const dimensionedScalar &ds)
dimensioned< Type > sum(const DimensionedField< Type, GeoMesh > &f1, const label comm)
static constexpr const zero Zero
Global zero (0).
Definition zero.H:127
bool isType(const U &obj)
Check if typeid of the object and Type are identical.
Definition typeInfo.H:112
dimensionedScalar neg0(const dimensionedScalar &ds)
word name(const expressions::valueTypeCode typeCode)
A word representation of a valueTypeCode. Empty for expressions::valueTypeCode::INVALID.
Definition exprTraits.C:127
dimensioned< scalar > dimensionedScalar
Dimensioned scalar obtained from generic dimensioned type.
void T(FieldField< Field, Type > &f1, const FieldField< Field, Type > &f2)
const dimensionSet dimMass(1, 0, 0, 0, 0, 0, 0)
fvPatchField< scalar > fvPatchScalarField
dimensionedScalar pow025(const dimensionedScalar &ds)
constexpr char nl
The newline '\n' character (0x0a).
Definition Ostream.H:50
dictionary dict
volScalarField & e
#define forAll(list, i)
Loop across all elements in list.
Definition stdFoam.H:299