comfort.C
Go to the documentation of this file.
1 /*---------------------------------------------------------------------------*\
2  ========= |
3  \\ / F ield | OpenFOAM: The Open Source CFD Toolbox
4  \\ / O peration | Website: https://openfoam.org
5  \\ / A nd | Copyright (C) 2019-2026 OpenFOAM Foundation
6  \\/ M anipulation |
7 -------------------------------------------------------------------------------
8 License
9  This file is part of OpenFOAM.
10 
11  OpenFOAM is free software: you can redistribute it and/or modify it
12  under the terms of the GNU General Public License as published by
13  the Free Software Foundation, either version 3 of the License, or
14  (at your option) any later version.
15 
16  OpenFOAM is distributed in the hope that it will be useful, but WITHOUT
17  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
18  FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
19  for more details.
20 
21  You should have received a copy of the GNU General Public License
22  along with OpenFOAM. If not, see <http://www.gnu.org/licenses/>.
23 
24 \*---------------------------------------------------------------------------*/
25 
26 #include "comfort.H"
27 #include "wallFvPatch.H"
29 
30 // * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * //
31 
32 namespace Foam
33 {
34 namespace functionObjects
35 {
38 }
39 }
40 
41 
42 // * * * * * * * * * * * * * Private Member Functions * * * * * * * * * * * //
43 
44 Foam::tmp<Foam::volScalarField> Foam::functionObjects::comfort::magU() const
45 {
46  tmp<volScalarField> tmagU = mag(lookupObject<volVectorField>("U"));
47 
48  // Switch to use the averaged velocity field in the domain.
49  // Consistent with EN ISO 7730 but does not make physical sense
50  if (meanVelocity_)
51  {
52  tmagU.ref() = tmagU->weightedAverage(mesh_.V());
53  }
54 
55  return tmagU;
56 }
57 
58 
59 Foam::dimensionedScalar Foam::functionObjects::comfort::Trad() const
60 {
61  dimensionedScalar Trad(Trad_);
62 
63  // The mean radiation is calculated by the mean wall temperatures
64  // which are summed and divided by the area | only walls are taken into
65  // account. This approach might be correct for a squared room but will
66  // definitely be inconsistent for complex room geometries. The norm does
67  // not provide any information about the calculation of this quantity.
68  if (!TradSet_)
69  {
70  const volScalarField::Boundary& TBf =
71  lookupObject<volScalarField>("T").boundaryField();
72 
73  scalar areaIntegral = 0;
74  scalar TareaIntegral = 0;
75 
76  forAll(TBf, patchi)
77  {
78  const fvPatchScalarField& pT = TBf[patchi];
79  const fvPatch& pTBf = TBf[patchi].patch();
80  const scalarField& pSf = pTBf.magSf();
81 
82  if (isType<wallFvPatch>(pTBf))
83  {
84  areaIntegral += gSum(pSf);
85  TareaIntegral += gSum(pSf*pT);
86  }
87  }
88 
89  Trad.value() = TareaIntegral/areaIntegral;
90  }
91 
92  // Bounds based on EN ISO 7730
93  if ((Trad.value() < 283.15) || (Trad.value() > 313.15))
94  {
96  << "The calculated mean wall radiation temperature is out of the\n"
97  << "bounds specified in EN ISO 7730:2006\n"
98  << "Valid range is 10 degC < T < 40 degC\n"
99  << "The actual value is: " << Trad.value() - 273.15 << endl;
100  }
101 
102  return Trad;
103 }
104 
105 
106 Foam::tmp<Foam::volScalarField> Foam::functionObjects::comfort::pSat() const
107 {
108  static const dimensionedScalar kPaToPa(dimPressure, 1000);
109  static const dimensionedScalar A(dimless, 16.6563);
110  static const dimensionedScalar B(dimTemperature, 4030.183);
111  static const dimensionedScalar C(dimTemperature, -38.15);
112 
113  tmp<volScalarField> tpSat(volScalarField::New("pSat", mesh_, pSat_));
114 
115  // Calculate the saturation pressure if no user input is given
116  if (pSat_.value() == 0)
117  {
118  const volScalarField& T = lookupObject<volScalarField>("T");
119 
120  // Equation based on ISO 7730:2006
121  tpSat = kPaToPa*exp(A - B/(T + C));
122  }
123 
124  return tpSat;
125 }
126 
127 
128 Foam::tmp<Foam::volScalarField> Foam::functionObjects::comfort::Tcloth
129 (
130  volScalarField& hc,
131  const dimensionedScalar& metabolicRateSI,
132  const dimensionedScalar& extWorkSI,
133  const volScalarField& T,
134  const dimensionedScalar& Trad
135 )
136 {
137  const dimensionedScalar factor1(dimTemperature, 308.85);
138 
139  const dimensionedScalar factor2
140  (
141  dimTemperature/metabolicRateSI.dimensions(),
142  0.028
143  );
144 
145  const dimensionedScalar factor3
146  (
147  dimensionSet(1, 0, -3, -4, 0, 0, 0),
148  3.96e-8
149  );
150 
151  // Heat transfer coefficient based on forced convection [W/m^2/K]
152  const volScalarField hcForced
153  (
154  dimensionedScalar(hc.dimensions()/sqrt(dimVelocity), 12.1)
155  *sqrt(magU())
156  );
157 
158  // Tcl [K] (surface cloth temperature)
159  tmp<volScalarField> tTcl
160  (
162  (
163  "Tcl",
164  T.mesh(),
166  )
167  );
168 
169  volScalarField& Tcl = tTcl.ref();
170 
171  // Initial guess
172  Tcl = T;
173 
174  label i = 0;
175 
176  Tcl.storePrevIter();
177 
178  // Iterative solving of equation (2)
179  do
180  {
181  Tcl = (Tcl + Tcl.prevIter())/2;
182  Tcl.storePrevIter();
183 
184  // Heat transfer coefficient based on natural convection
185  volScalarField hcNatural
186  (
187  dimensionedScalar(hc.dimensions()/pow025(dimTemperature), 2.38)
188  *pow025(mag(Tcl - T))
189  );
190 
191  // Set heat transfer coefficient based on equation (3)
192  hc =
193  pos(hcForced - hcNatural)*hcForced
194  + neg0(hcForced - hcNatural)*hcNatural;
195 
196  // Calculate surface temperature based on equation (2)
197  Tcl =
198  factor1
199  - factor2*(metabolicRateSI - extWorkSI)
200  - Icl_*factor3*fcl_*(pow4(Tcl) - pow4(Trad))
201  - Icl_*fcl_*hc*(Tcl - T);
202 
203  } while (!converged(Tcl) && i++ < maxClothIter_);
204 
205  if (i == maxClothIter_)
206  {
208  << "The surface cloth temperature did not converge within " << i
209  << " iterations\n";
210  }
211 
212  return tTcl;
213 }
214 
215 
216 bool Foam::functionObjects::comfort::converged
217 (
218  const volScalarField& phi
219 ) const
220 {
221  return
222  max(mag(phi.primitiveField() - phi.prevIter().primitiveField()))
223  < tolerance_;
224 }
225 
226 
227 // * * * * * * * * * * * * * * * * Constructors * * * * * * * * * * * * * * //
228 
230 (
231  const word& name,
232  const Time& runTime,
233  const dictionary& dict
234 )
235 :
236  fvMeshFunctionObject(name, runTime, dict),
237  clothing_("clothing", dimless, 0),
238  metabolicRate_("metabolicRate", dimMass/pow3(dimTime), 0.8),
239  extWork_("extWork", dimMass/pow3(dimTime), 0),
240  TradSet_(false),
241  Trad_("Trad", dimTemperature, 0),
242  relHumidity_("relHumidity", dimless, 0.5),
243  pSat_("pSat", dimPressure, 0),
244  Icl_("Icl", dimensionSet(-1, 0, 3, 1, 0, 0, 0), 0),
245  fcl_("fcl", dimless, 0),
246  tolerance_(1e-4),
247  maxClothIter_(100),
248  meanVelocity_(false)
249 {
250  read(dict);
251 }
252 
253 
254 // * * * * * * * * * * * * * * * * Destructor * * * * * * * * * * * * * * * //
255 
257 {}
258 
259 
260 // * * * * * * * * * * * * * * * Member Functions * * * * * * * * * * * * * //
261 
263 {
264  clothing_.readIfPresent(dict);
265  metabolicRate_.readIfPresent(dict);
266  extWork_.readIfPresent(dict);
267  pSat_.readIfPresent(dict);
268  tolerance_ = dict.lookupOrDefault("tolerance", 1e-4);
269  maxClothIter_ = dict.lookupOrDefault("maxClothIter", 100);
270  meanVelocity_ = dict.lookupOrDefault<Switch>("meanVelocity", false);
271 
272  // Read relative humidity if provided and convert from % to fraction
273  if (dict.found(relHumidity_.name()))
274  {
275  relHumidity_.read(dict);
276  relHumidity_ /= 100;
277  }
278 
279  // Read radiation temperature if provided
280  if (dict.found(Trad_.name()))
281  {
282  TradSet_ = true;
283  Trad_.read(dict);
284  }
285  else
286  {
287  TradSet_ = false;
288  }
289 
290  Icl_ = dimensionedScalar(Icl_.dimensions(), 0.155)*clothing_;
291 
292  fcl_.value() =
293  Icl_.value() <= 0.078
294  ? 1.0 + 1.290*Icl_.value()
295  : 1.05 + 0.645*Icl_.value();
296 
297  return true;
298 }
299 
300 
302 {
303  return wordList{"U", "T"};
304 }
305 
306 
308 {
309  const dimensionedScalar Trad(this->Trad());
310  const volScalarField pSat(this->pSat());
311 
312  const dimensionedScalar metabolicRateSI(58.15*metabolicRate_);
313  const dimensionedScalar extWorkSI(58.15*extWork_);
314 
315  const volScalarField& T = lookupObject<volScalarField>("T");
316 
317  // Heat transfer coefficient [W/m^2/K]
318  // This field is updated in Tcloth()
319  volScalarField hc
320  (
321  IOobject
322  (
323  "hc",
324  time_.name(),
325  mesh_
326  ),
327  mesh_,
328  dimensionedScalar(dimensionSet(1, 0, -3, -1, 0, 0, 0), 0)
329  );
330 
331  // Calculate the surface temperature of the cloth by an iterative
332  // process using equation (2) from DIN EN ISO 7730 [degC]
333  const volScalarField Tcloth
334  (
335  this->Tcloth
336  (
337  hc,
338  metabolicRateSI,
339  extWorkSI,
340  T,
341  Trad
342  )
343  );
344 
345  // Calculate the PMV quantity
346  const dimensionedScalar factor1(dimensionSet(-1, 0, 3, 0, 0, 0, 0), 0.303);
347  const dimensionedScalar factor2
348  (
349  dimless/metabolicRateSI.dimensions(),
350  -0.036
351  );
352  const dimensionedScalar factor3(factor1.dimensions(), 0.028);
353  const dimensionedScalar factor4(dimLength/dimTime, 3.05e-3);
354  const dimensionedScalar factor5(dimPressure, 5733);
355  const dimensionedScalar factor6(dimTime/dimLength, 6.99);
356  const dimensionedScalar factor8(metabolicRateSI.dimensions(), 58.15);
357  const dimensionedScalar factor9(dimless/dimPressure, 1.7e-5);
358  const dimensionedScalar factor10(dimPressure, 5867);
359  const dimensionedScalar factor11(dimless/dimTemperature, 0.0014);
360  const dimensionedScalar factor12(dimTemperature, 307.15);
361  const dimensionedScalar factor13
362  (
363  dimensionSet(1, 0, -3, -4, 0, 0, 0),
364  3.96e-8
365  );
366 
367  const scalar factor7
368  (
369  // Special treatment of Term4
370  // if metaRate - extWork < factor8, set to zero
371  (metabolicRateSI - extWorkSI).value() < factor8.value() ? 0 : 0.42
372  );
373 
374  Info<< "Calculating the predicted mean vote (PMV)\n";
375 
376  // Equation (1)
378  (
380  (
381  "PMV",
382 
383  // Term1: Thermal sensation transfer coefficient
384  (factor1*exp(factor2*metabolicRateSI) + factor3)
385  *(
386  (metabolicRateSI - extWorkSI)
387 
388  // Term2: Heat loss difference through skin
389  - factor4
390  *(
391  factor5
392  - factor6*(metabolicRateSI - extWorkSI)
393  - pSat*relHumidity_
394  )
395 
396  // Term3: Heat loss through sweating
397  - factor7*(metabolicRateSI - extWorkSI - factor8)
398 
399  // Term4: Heat loss through latent respiration
400  - factor9*metabolicRateSI*(factor10 - pSat*relHumidity_)
401 
402  // Term5: Heat loss through dry respiration
403  - factor11*metabolicRateSI*(factor12 - T)
404 
405  // Term6: Heat loss through radiation
406  - factor13*fcl_*(pow4(Tcloth) - pow4(Trad))
407 
408  // Term7: Heat loss through convection
409  - fcl_*hc*(Tcloth - T)
410  )
411  )
412  );
413 
414  Info<< "Calculating the predicted percentage of dissatisfaction (PPD)\n";
415 
416  // Equation (5)
418  (
420  (
421  "PPD",
422  100 - 95*exp(-0.03353*pow4(PMV()) - 0.21790*sqr(PMV()))
423  )
424  );
425 
426  Info<< "Calculating the draught rating (DR)\n";
427 
428  const dimensionedScalar Umin("Umin", dimVelocity, 0.05);
429  const dimensionedScalar Umax("Umax", dimVelocity, 0.5);
430  const dimensionedScalar pre("preU", dimless, 0.37);
431  const dimensionedScalar C1("C1", dimVelocity, 3.14);
432 
433  // Limit the velocity field to the values given in EN ISO 7733
434  volScalarField Umag(mag(lookupObject<volVectorField>("U")));
435  Umag.maxMin(Umin, Umax);
436 
437  // Calculate the turbulent intensity if turbulent kinetic energy field k
438  // exists
439  volScalarField TI
440  (
441  IOobject
442  (
443  "TI",
444  time_.name(),
445  mesh_
446  ),
447  mesh_,
448  dimensionedScalar(dimensionSet(0, 0, 0, 0, 0, 0, 0), 0)
449  );
450 
451  if (foundObject<volScalarField>("k"))
452  {
453  const volScalarField& k = lookupObject<volScalarField>("k");
454  TI = sqrt(2/3*k)/Umag;
455  }
456 
457  // For unit correctness
458  const dimensionedScalar correctUnit
459  (
460  "correctUnit",
461  dimensionSet(0,- 1.62, 1.62, -1, 0, 0, 0),
462  1
463  );
464 
465  // Equation (6)
467  (
469  (
470  "DR",
471  correctUnit*(factor12 - T)*pow(Umag - Umin, 0.62)*(pre*Umag*TI + C1)
472  )
473  );
474 
475  store(PMV);
476  store(PPD);
477  store(DR);
478 
479  return true;
480 }
481 
482 
484 {
485  return
486  writeObject("PMV")
487  && writeObject("PPD")
488  && writeObject("DR");
489 }
490 
491 
492 // ************************************************************************* //
label k
#define forAll(list, i)
Loop across all elements in list.
Definition: UList.H:449
Macros for easy insertion into run-time selection tables.
Generic GeometricField class.
void maxMin(const dimensioned< Type > &minDt, const dimensioned< Type > &maxDt)
GeometricBoundaryField< Type, GeoMesh, PrimitiveField > Boundary
Type of the boundary field.
static tmp< GeometricField< Type, GeoMesh, PrimitiveField > > New(const word &name, const Internal &, const PtrList< Patch > &, const HashPtrTable< Source > &=HashPtrTable< Source >())
Return a temporary field constructed from name,.
IOobject defines the attributes of an object for which implicit objectRegistry management is supporte...
Definition: IOobject.H:99
A simple wrapper around bool so that it can be read as a word: true/false, on/off,...
Definition: Switch.H:61
Class to control time during OpenFOAM simulations that is also the top-level objectRegistry.
Definition: Time.H:76
A list of keywords followed by any number of values (e.g. words and numbers) or sub-dictionaries.
Definition: dictionary.H:162
Dimension set for the base types.
Definition: dimensionSet.H:125
const dimensionSet & dimensions() const
Return const reference to dimensions.
const Type & value() const
Return const reference to value.
Abstract base-class for Time/database functionObjects.
Calculates the thermal comfort quantities predicted mean vote (PMV), predicted percentage of dissatis...
Definition: comfort.H:174
virtual wordList fields() const
Return the list of fields required.
Definition: comfort.C:301
comfort(const word &name, const Time &runTime, const dictionary &dict)
Construct from Time and dictionary.
Definition: comfort.C:230
virtual bool execute()
Calculate the predicted mean vote (PMV)
Definition: comfort.C:307
virtual bool write()
Write the PPD and PMV fields.
Definition: comfort.C:483
virtual bool read(const dictionary &)
Read the data needed for the comfort calculation.
Definition: comfort.C:262
virtual ~comfort()
Destructor.
Definition: comfort.C:256
Specialisation of Foam::functionObject for an Foam::fvMesh, providing a reference to the Foam::fvMesh...
const fvMesh & mesh_
Reference to the fvMesh.
const DimensionedField< scalar, fvMesh > & V() const
Return cell volumes.
A class for managing temporary objects.
Definition: tmp.H:55
A class for handling words, derived from string.
Definition: word.H:63
label patchi
#define WarningInFunction
Report a warning using Foam::Warning.
const dimensionSet dimless
defineTypeNameAndDebug(fvMeshFunctionObject, 0)
addToRunTimeSelectionTable(functionObject, fvModel, dictionary)
static const coefficient C("C", dimTemperature, 234.5)
static const coefficient B("B", dimless, 18.678)
static const coefficient A("A", dimPressure, 611.21)
Namespace for OpenFOAM.
dimensionedScalar pos(const dimensionedScalar &ds)
const doubleScalar e
Definition: doubleScalar.H:106
dimensionedScalar exp(const dimensionedScalar &ds)
intWM_LABEL_SIZE_t label
A label is an int32_t or int64_t as specified by the pre-processor macro WM_LABEL_SIZE.
Definition: label.H:59
const dimensionSet & dimMass
Definition: dimensions.C:275
Ostream & endl(Ostream &os)
Add newline and flush stream.
Definition: Ostream.H:288
const dimensionSet & dimLength
Definition: dimensions.C:276
void pow025(LagrangianPatchField< scalar > &f, const LagrangianPatchField< scalar > &f1)
messageStream Info
void pow4(LagrangianPatchField< scalar > &f, const LagrangianPatchField< scalar > &f1)
Type gSum(const UList< Type > &f, const label comm)
Field< scalar > scalarField
Specialisation of Field<T> for scalar.
tmp< DimensionedField< typename outerProduct< Type, Type >::type, GeoMesh, Field >> sqr(const DimensionedField< Type, GeoMesh, PrimitiveField > &df)
const dimensionSet & dimVelocity
Definition: dimensions.C:286
const dimensionSet & dimTime
Definition: dimensions.C:277
VolField< scalar > volScalarField
Definition: volFieldsFwd.H:62
const dimensionSet & dimPressure
Definition: dimensions.C:294
void pow3(LagrangianPatchField< scalar > &f, const LagrangianPatchField< scalar > &f1)
word name(const LagrangianState state)
Return a string representation of a Lagrangian state enumeration.
tmp< DimensionedField< typename powProduct< Type, r >::type, GeoMesh, Field > > pow(const DimensionedField< Type, GeoMesh, PrimitiveField > &df, typename powProduct< Type, r >::type)
tmp< DimensionedField< scalar, GeoMesh, Field > > mag(const DimensionedField< Type, GeoMesh, PrimitiveField > &df)
dimensionedScalar neg0(const dimensionedScalar &ds)
void sqrt(LagrangianPatchField< scalar > &f, const LagrangianPatchField< scalar > &f1)
void T(GeometricField< Type, GeoMesh, PrimitiveField1 > &gf, const GeometricField< Type, GeoMesh, PrimitiveField2 > &gf1)
const dimensionSet & dimTemperature
Definition: dimensions.C:278
dimensioned< Type > max(const DimensionedField< Type, GeoMesh, PrimitiveField > &df)
dimensioned< scalar > dimensionedScalar
Dimensioned scalar obtained from generic dimensioned type.
fvPatchField< scalar > fvPatchScalarField
dictionary dict