pyhydronic 2.0.0__py2.py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- hydronic/__init__.py +46 -0
- hydronic/comfort.py +820 -0
- hydronic/heat.py +383 -0
- hydronic/misc.py +44 -0
- hydronic/version.py +2 -0
- pyhydronic-2.0.0.dist-info/METADATA +124 -0
- pyhydronic-2.0.0.dist-info/RECORD +11 -0
- pyhydronic-2.0.0.dist-info/WHEEL +6 -0
- pyhydronic-2.0.0.dist-info/licenses/COPYING +674 -0
- pyhydronic-2.0.0.dist-info/licenses/LICENSE +22 -0
- pyhydronic-2.0.0.dist-info/top_level.txt +1 -0
hydronic/comfort.py
ADDED
|
@@ -0,0 +1,820 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Hydronic -- calculations for modeling heated structures
|
|
3
|
+
#
|
|
4
|
+
# Copyright (c) 2006-2026, Perry Kundert
|
|
5
|
+
#
|
|
6
|
+
# Hydronic is free software: you can redistribute it and/or modify it under the
|
|
7
|
+
# terms of the GNU General Public License as published by the Free Software
|
|
8
|
+
# Foundation, either version 3 of the License, or (at your option) any later
|
|
9
|
+
# version. See the LICENSE file at the top of the source tree.
|
|
10
|
+
#
|
|
11
|
+
# Hydronic is distributed in the hope that it will be useful, but WITHOUT ANY
|
|
12
|
+
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
|
13
|
+
# A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
|
14
|
+
#
|
|
15
|
+
|
|
16
|
+
"""Fanger's Comfort Equation: Predicted Mean Vote (PMV) and Percentage Dissatisfied (PPD).
|
|
17
|
+
|
|
18
|
+
All units are SI: temperatures in degrees C, air velocity in m/s, vapour pressure in Pa,
|
|
19
|
+
metabolism in met (or W/m^2) and clothing in clo (or m^2K/W).
|
|
20
|
+
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
from __future__ import absolute_import, division, print_function
|
|
24
|
+
|
|
25
|
+
import math
|
|
26
|
+
|
|
27
|
+
from .misc import Error, scale
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class fanger( object ):
|
|
31
|
+
"""Implements Fanger's Basic Comfort Equation.
|
|
32
|
+
|
|
33
|
+
A good discussion of its applicability is:
|
|
34
|
+
|
|
35
|
+
http://www.nrc-cnrc.gc.ca/obj/irc/doc/pubs/rr/rr162/rr162.pdf, now:
|
|
36
|
+
https://nrc-publications.canada.ca/eng/view/ft/?id=7525d344-a508-4fdc-9c04-d9d3a9767bdb
|
|
37
|
+
|
|
38
|
+
A derivation of this equation is detailed (somewhat incompletely) in:
|
|
39
|
+
|
|
40
|
+
http://www.lumasenseinc.com/uploads/News/Articles/English/Technical_Review_No_2-1982_-Thermal_Comfort.pdf, now:
|
|
41
|
+
https://www.bksv.com/media/doc/TechnicalReview1982-2.pdf, "Thermal Comfort", page 3
|
|
42
|
+
|
|
43
|
+
The equation depends on humidity, air temperature and velocity, average radiant temperature,
|
|
44
|
+
clothing level and metabolic level. Metabolic rate is described in either 'met', or raw W/m^2:
|
|
45
|
+
|
|
46
|
+
Metabolism, M (met): (W/m^2)
|
|
47
|
+
Activity met M
|
|
48
|
+
-------- --- -----
|
|
49
|
+
Lying down 0.8 47
|
|
50
|
+
Seated quietly 1.0 58
|
|
51
|
+
Sedentary activity (office, school) 1.2 70
|
|
52
|
+
Standing, relaxed 1.2 70
|
|
53
|
+
Light activity, standing (shopping, lab, lt. ind.) 1.6 93
|
|
54
|
+
Medium activity, standing (shop asst., dom. work) 2.0 117
|
|
55
|
+
High activity (heavy machine, garage work) 3.0 175
|
|
56
|
+
Running 9.0 522
|
|
57
|
+
|
|
58
|
+
The insulating (and radiating) value of clothing is expressed in 'clo', or raw m^2K/W:
|
|
59
|
+
|
|
60
|
+
Clothing, I_cl (clo): (m^2K/W)
|
|
61
|
+
Clothing Combination clo I_cl
|
|
62
|
+
-------------------- --- ------
|
|
63
|
+
Naked 0 0
|
|
64
|
+
Shorts 0.1 0.016
|
|
65
|
+
Tropical 0.3 0.047
|
|
66
|
+
Light Summer (short sleeves, light pants) 0.5 0.078
|
|
67
|
+
Working Clothes (cotton shirt + jeans) 0.8 0.124
|
|
68
|
+
Typical indoor winter (shirt + sweater) 1.0 0.155
|
|
69
|
+
Heavy business suit 1.5 0.233
|
|
70
|
+
|
|
71
|
+
Fanger's Comfort Equation:
|
|
72
|
+
|
|
73
|
+
(M-W)
|
|
74
|
+
- 3.05 x 10^-3 x {5733 - 6.99 x (M-W) - P_a}
|
|
75
|
+
- 0.42 { (M-W) - 58.15 }
|
|
76
|
+
- 1.7 x 10^-5 x M x (5867 - P_a)
|
|
77
|
+
- 0.0014 x M x (34 - t_a)
|
|
78
|
+
= 3.96 x 10^-8 x f_cl { (t_cl + 273.15)^4 - (t_r + 273.15)^4 }
|
|
79
|
+
+ C
|
|
80
|
+
where:
|
|
81
|
+
M: Metabolism, in met (converted to W/m^2)
|
|
82
|
+
W: External work performed (subtracted from M, usually 0.)
|
|
83
|
+
t_a: Air temperature (dec. C)
|
|
84
|
+
t_s: Temperature of skin (deg. C)
|
|
85
|
+
t_s = 35.7 - 0.028 (M-W)
|
|
86
|
+
E_sw: Sweat loss (W/m^2)
|
|
87
|
+
E_sw = 0.42 { (M-W) - 58.15 } #
|
|
88
|
+
E_res= 0.0014 M(34 - t_a) # Heat loss from respiration
|
|
89
|
+
|
|
90
|
+
C: heat exchange by convection (W/m^2)
|
|
91
|
+
C = f_cl x h_c x (t_cl - t_a)
|
|
92
|
+
t_cl =
|
|
93
|
+
t_s - 0.155 x clo [
|
|
94
|
+
(M-W)
|
|
95
|
+
- 3.05 x 10^-3 x {5733 - 6.99 x (M-W) - p_a}
|
|
96
|
+
- E_sw
|
|
97
|
+
- 1.7 x 10^-5 x M (5867 - p_a)
|
|
98
|
+
- 0.0014 x M x (34 - t_a) ]
|
|
99
|
+
|
|
100
|
+
h_c: Convective heat transfer (W/M^2 K)
|
|
101
|
+
h_c = A) 2.38 x (t_cl - t_a) ^ 0.25 # (for A > B); free convection
|
|
102
|
+
h_c = B) 12.1 x sqrt( v_ar ) # (for A < B); forced convection
|
|
103
|
+
|
|
104
|
+
f_cl: Clothing area factor
|
|
105
|
+
f_cl = A) 1.00 + 0.2 x clo # for clo <= 0.5
|
|
106
|
+
f_cl = B) 1.05 + 0.1 x clo # for clo > 0.5
|
|
107
|
+
|
|
108
|
+
From B. A. McIntyre's Bioengineering, thermal physiology, and comfort, Volume 10, pp.198, there
|
|
109
|
+
are simplifications of the Fanger equations that work within certain constraints:
|
|
110
|
+
|
|
111
|
+
For M < 150 W/m^2 (met ~2.5), and I < 0.233 m^2 K/W (clo ~1.5), this simplification
|
|
112
|
+
(for a target effective temperature) yielded error < 0.5K.
|
|
113
|
+
|
|
114
|
+
t_sub = 33.5 - 3 x I_cl - (0.08 + 0.05 x I_cl) x M
|
|
115
|
+
|
|
116
|
+
and, where t_a and t_r are different, for v < 0.1 m/s:
|
|
117
|
+
|
|
118
|
+
t_sub = 0.56 x t_a + 0.44 x t_r
|
|
119
|
+
|
|
120
|
+
for v > 0.1 m/s (high air velocity), a much more complex equation:
|
|
121
|
+
|
|
122
|
+
t_sub = 0.44 x t_r + (5 - (10 x v)^0.5 x (5 - t_a))/(0.44 + 0.56 x (10 x v)^0.5)
|
|
123
|
+
|
|
124
|
+
So for a typical residence, the lower complexity formula will suffice. Later in
|
|
125
|
+
B. A. McIntyre's Bioengineering book, on pp. 210, he also reveals that once the perceived
|
|
126
|
+
temperature is correct, changes in humidity do little to affect comfort. In fact, "...a change
|
|
127
|
+
in relative humidity from 20% to 75% would reduce the preferred temperature by 1K." So, using
|
|
128
|
+
the simplified equation (and ignoring humidity) is probably going to be adequate for an effective
|
|
129
|
+
radiant and air temperature control strategy.
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
This simplification might be useful, in concert with an "adaptive model" for thermal neutrality.
|
|
133
|
+
From:
|
|
134
|
+
|
|
135
|
+
http://nceub.commoncense.info/uploads/Saberi.pdf
|
|
136
|
+
|
|
137
|
+
it appears that the "neutral" temperature (at which a person feels thermally neutral,
|
|
138
|
+
"comfortable") is related to the average outdoor temperature, by the equation:
|
|
139
|
+
|
|
140
|
+
t_n = 11.9 + 0.534 x t_m (from Humphreys)
|
|
141
|
+
or:
|
|
142
|
+
t_n = 17.6 + 0.314 x t_m (from Auliciemes)
|
|
143
|
+
|
|
144
|
+
where:
|
|
145
|
+
t_n = neutral temp (in C)
|
|
146
|
+
t_m = Mean outdoor temp (in C)
|
|
147
|
+
|
|
148
|
+
From the data underlying these equations, it appears that Fanger et.al. computed their comfort
|
|
149
|
+
equation temperatures in an environment where the outdoor temperature was around 25 C. This
|
|
150
|
+
means that we must adjust the target temperatures using these equations. If we adjust the
|
|
151
|
+
actual t_a and t_r upward or downward by the difference between the 25 C, and the computed t_n
|
|
152
|
+
for the outdoor temperature, before computing the Fanger L,
|
|
153
|
+
|
|
154
|
+
This result is probably due to the fact that humans adjust their food intake and metabolic rate
|
|
155
|
+
upwards when it is cold outside. This suggests that it might be possible to simply add to the
|
|
156
|
+
metabolic rate input to Fanger's equation, the colder the outside temperature is...
|
|
157
|
+
|
|
158
|
+
Further neutral temperature equations from: http://www.eurojournals.com/ejsr_27_2_06.pdf
|
|
159
|
+
t_n_o = 17 + 0.38 x t_o (Humphreys MA, 1976)
|
|
160
|
+
t_n_i = 2.6 + 0.831 x t_i (Orosa JA, 2008)
|
|
161
|
+
t_n_o = 11.9 + 0.534 x t_o (Orosa JA, 2008)
|
|
162
|
+
t_n_i = 5.41 + 0.731 x t_i
|
|
163
|
+
t_n_o = 17.6 + 0.31 x t_o
|
|
164
|
+
t_n_i_o = 9.22 + 0.48 x t_i + 0.14 x t_o
|
|
165
|
+
t_c = 17.8 + 0.31 x t_o (ASHRAE)
|
|
166
|
+
where:
|
|
167
|
+
t_o = outdoor temperature
|
|
168
|
+
t_i = mean indoor temperature
|
|
169
|
+
t_c = the comfort temperature
|
|
170
|
+
t_n_o = neutral temperature based on mean outdoor temperature
|
|
171
|
+
t_n_i = neutral temperature based on mean indoor temperature
|
|
172
|
+
|
|
173
|
+
All these assume near sedentary activity, and freely adaptable clothing.
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
Another description of Fanger's equation is from:
|
|
177
|
+
|
|
178
|
+
http://ergo.human.cornell.edu/studentdownloads/DEA3500notes/Thermal/thcomnotes2.html
|
|
179
|
+
|
|
180
|
+
Fanger's Comfort Equation This equation contains terms which relate to:
|
|
181
|
+
|
|
182
|
+
functions of clothing:
|
|
183
|
+
Icl = clothing insulation in clothes
|
|
184
|
+
fcr = ratio of clothed/nude surface area
|
|
185
|
+
functions of activity:
|
|
186
|
+
H = Metabolic heat production (w/m2)
|
|
187
|
+
M = Metabolic free energy production (external work)(w/m2)
|
|
188
|
+
Environmental variables:
|
|
189
|
+
Ta = Air temperature (C)
|
|
190
|
+
Tr = Mean radiant temperature (C)
|
|
191
|
+
v = Relative air speed (m/s)
|
|
192
|
+
Pa = Vapor pressure of water vapor (mb)
|
|
193
|
+
|
|
194
|
+
Fanger's Equation
|
|
195
|
+
(hc = convective transfer coefficient w/m2 K)
|
|
196
|
+
H - 0.31(57.4 - 0.07H - Pa) - 0.42(H-58) - 0.0017M(58.7 - Pa) - 0.0014M(34 - Ta) =
|
|
197
|
+
3.9 x 10-8fcl {(Tcl + 273)4 - (Tr + 273)4} + fcl hc (Tcl - Ta)
|
|
198
|
+
|
|
199
|
+
Where the clothing surface temperature, Tcl, is given by
|
|
200
|
+
Tcl = 35.7 - 0.0275H + 0.155Iclo {H - 0.31(57.4 - 0.07H - Pa) - 0.42(H - 58) - 0.0017M(58.7 - Pa) - 0.0014M(34 - Ta)}.
|
|
201
|
+
|
|
202
|
+
In addition to this, discomfort may occur when the skin is wetted (sweat, water, etc.) and
|
|
203
|
+
Fanger has produced an equation for skin wettedness which can be used as a test to exclude
|
|
204
|
+
conditions which satisfy the comfort equation:
|
|
205
|
+
|
|
206
|
+
Predicted Mean Vote (PMV)
|
|
207
|
+
|
|
208
|
+
The problem with Fanger's equation is that when people are not satisfied, this is not a measure
|
|
209
|
+
of how uncomfortable deviation is; therefore Fanger developed PMV = mean vote on ASHRAE scale
|
|
210
|
+
(Hot / warm / slightly warm / neutral / slightly cool / cool / cold). PMV can be predicted from
|
|
211
|
+
Fanger's equation thus:
|
|
212
|
+
|
|
213
|
+
PMV = 4 + (0.303 exp(-0.036H) + 0.0275) x {6.57 + 0.46H + 0.31Pa + 0.0017HPa + 0.0014HTa - 4.13 fcl (1 + 0.01dT) (Tcl - Tr) - hcfcl (Tcl - Ta)}
|
|
214
|
+
|
|
215
|
+
where Tcl (surface temperature of clothed body) =
|
|
216
|
+
35.7 - 0.0275H + 0.155 Iclofcl (4.13 (1 + 0.Old Temp) 1 + 0.155 Iclofcl (4.13 (1 + 0.Old Temp)thc
|
|
217
|
+
|
|
218
|
+
where hc = 2.4(Tcl - Ta)0.25 or 12.1 square root of v (air speed) which is greater
|
|
219
|
+
and dT = Tr - 22.
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
From the definitive source, the ISO 7730 standard (2005-11-15):
|
|
223
|
+
|
|
224
|
+
http://www.ntm.ru/UserFiles/File/document/Microklimat/Norm/ISO_7730_2005.pdf
|
|
225
|
+
|
|
226
|
+
(with corrections (re: HL6) from http://www.naun.org/journals/mechanics/17-105.pdf)
|
|
227
|
+
|
|
228
|
+
PMV = (0.303 x e^(-0.036 x M) + 0.028) x L
|
|
229
|
+
|
|
230
|
+
L = (M-W)
|
|
231
|
+
- 3.05 x 10^-3 x [5733 - 6.99 x (M-W) - P_a]
|
|
232
|
+
- 0.42 x [(M-W) - 58.15]
|
|
233
|
+
- 1.7 x 10^-5 x M x (5867 - P_a)
|
|
234
|
+
- 1.4 x 10^-3 x M x (34 - t_a)
|
|
235
|
+
- 3.96 x 10^-8 x f_cl x [(t_cl +273)^4 - (t_r + 273)^4]
|
|
236
|
+
- f_cl x h_c x (t_cl - t_a)
|
|
237
|
+
t_cl = 35.7 - 0.028 x (M-W)
|
|
238
|
+
- I_cl
|
|
239
|
+
x (3.96 x 10^-8 x f_cl
|
|
240
|
+
x [(t_cl + 273)^4 - (t_r + 273)^4]
|
|
241
|
+
+ f_cl x h_c x (t_cl - t_a)]
|
|
242
|
+
|
|
243
|
+
h_c: Convective heat transfer (W/M^2 K)
|
|
244
|
+
h_c = A) 2.38 x (t_cl - t_a) ^ 0.25 # (for A > B); free convection
|
|
245
|
+
h_c = B) 12.1 x sqrt( v_ar ) # (for A < B); forced convection
|
|
246
|
+
|
|
247
|
+
f_cl: Clothing area factor
|
|
248
|
+
f_cl = A) 1.00 + 1.290 x I_cl # for I_cl <= 0.078 m^2K/W
|
|
249
|
+
f_cl = B) 1.05 + 0.645 x I_cl # for I_cl > 0.078 m^2K/W
|
|
250
|
+
where:
|
|
251
|
+
M is the metabolic rate, in watts per square metre (W/m2);
|
|
252
|
+
W is the effective mechanical power, in watts per square metre (W/m^2);
|
|
253
|
+
I_cl is the clothing insulation, in square metres kelvin per watt (m^2 K/W);
|
|
254
|
+
f_cl is the clothing surface area factor;
|
|
255
|
+
t_a is the air temperature, in degrees Celsius (C);
|
|
256
|
+
t_r is the mean radiant temperature, in degrees Celsius (C);
|
|
257
|
+
v is the relative air velocity, in metres per second (m/s);
|
|
258
|
+
p_a is the water vapour partial pressure, in pascals (Pa);
|
|
259
|
+
h_c is the convective heat transfer coefficient, in watts per square metre kelvin [W/(m^2K)];
|
|
260
|
+
t_cl is the clothing surface temperature, in degrees Celsius (C).
|
|
261
|
+
1 metabolic unit = 1 met = 58.2 W/m2
|
|
262
|
+
1 clothing unit = 1 clo = 0.155 m^2K/W
|
|
263
|
+
|
|
264
|
+
PMV should only be used between -2 and 2, and if the 6 main variables are in the ranges:
|
|
265
|
+
|
|
266
|
+
M 46 W/m^2 to 232 W/m^2 (0.8 met to 4 met);
|
|
267
|
+
I_cl 0 m^2K/W to 0.310 m^2K/W (0 clo to 2 clo);
|
|
268
|
+
t_a 10 C to 30 C;
|
|
269
|
+
t_r 10 C to 40 C;
|
|
270
|
+
v 0 m/s to 1 m/s;
|
|
271
|
+
p_a 0 Pa to 2700 Pa.
|
|
272
|
+
|
|
273
|
+
A BASIC program implement PMV is provided:
|
|
274
|
+
10 Computer program (BASIC) for calculation of
|
|
275
|
+
20 Predicted Mean Vote (PMV) and Predicted Percentage of Dissatisfied (PPD)
|
|
276
|
+
30 in accordance with International Standard, ISO 7730
|
|
277
|
+
40 CLS: PRINT "DATA ENTRY" data entry
|
|
278
|
+
50 INPUT " Clothing (clo) "; CLO
|
|
279
|
+
60 INPUT " Metabolic rate (met) " MET
|
|
280
|
+
70 INPUT " External work, normally around 0 (met) " WME
|
|
281
|
+
80 INPUT " Air temperature (C) " TA
|
|
282
|
+
90 INPUT " Mean radiant temperature (C) " TR
|
|
283
|
+
100 INPUT " Relative air velocity (m/s) " VEL
|
|
284
|
+
110 INPUT " ENTER EITHER RH OR WATER VAPOUR PRESSURE BUT NOT BOTH"
|
|
285
|
+
120 INPUT " Relative humidity (%) " RH
|
|
286
|
+
130 INPUT " Water vapour pressure (Pa) " PA
|
|
287
|
+
140 DEF FNPS (T) = EXP (16.6536-4030.183/T+235)) : saturated vapour pressure, kPa
|
|
288
|
+
150 IF PA = 0 THEN PA = RH * 10 * FNPS (TA) : water vapour pressure, Pa
|
|
289
|
+
160 ICL = .155 * CLO : thermal insulation of the clothing in m^2K/W
|
|
290
|
+
170 M = MET * 58.15 : metabolic rate in W/m2
|
|
291
|
+
180 W = WME * 58.15 : external work in W/m2
|
|
292
|
+
190 MW = M - W : internal heat production in the human body
|
|
293
|
+
200 IF ICL < .078
|
|
294
|
+
THEN FCL = 1 + 1.29 * ICL
|
|
295
|
+
ELSE FCL = 1.05 + 0.645 * ICL : clothing area factor
|
|
296
|
+
210 HCF = 12.1 * SQR (VEL) : heat transf. coeff. by forced convection
|
|
297
|
+
|
|
298
|
+
220 TAA = TA + 273 : air temperature in Kelvin
|
|
299
|
+
230 TRA = TR + 273 : mean radiant temperature in Kelvin
|
|
300
|
+
240 -----CALCULATE SURFACE TEMPERATURE OF CLOTHING BY ITERATION ---
|
|
301
|
+
250 TCLA = TAA + (35.5-TA) / (3.5 * ICL + .1) : first guess for surface temperature of clothing
|
|
302
|
+
260 P1 = ICL * FCL : calculation term
|
|
303
|
+
270 P2 = P1 * 3.96 : calculation term
|
|
304
|
+
280 P3 = P1 * 100 : calculation term
|
|
305
|
+
290 P4 = P1 * TAA : calculation term
|
|
306
|
+
300 P5 = 308.7 - .028 * MW + P2 * (TRA/100) * 4
|
|
307
|
+
310 XN = TLCA / 100
|
|
308
|
+
320 XF = XN
|
|
309
|
+
330 N = 0 : N: number of iterations
|
|
310
|
+
340 EPS = .00015 : stop criteria in iteration
|
|
311
|
+
350 XF = (XF + XN)/2
|
|
312
|
+
360 HCN =2.38 * ABS (100 * XF - TAA) ^ .25 : heat transf. coeff. by natural convection
|
|
313
|
+
370 IF HCF>HCN THEN HC = HCF ELSE HC = HCN
|
|
314
|
+
380 XN = (P5 + P4 * HC - P2 * XF ^ 4) / (100 + P3 * HC)
|
|
315
|
+
390 N = N + 1
|
|
316
|
+
400 IF N > 150 THEN GOTO 550
|
|
317
|
+
410 IF ABS (XN - XF) > EPS GOTO 350
|
|
318
|
+
420 TCL = 100 * XN - 273 : surface temperature of the clothing
|
|
319
|
+
430 --------------------------------HEAT LOSS COMPONENTS -----------------------------------
|
|
320
|
+
440 HL1 = 3.05 * .001 (5733-6.99 * MW-PA) : heat loss diff. through skin
|
|
321
|
+
450 IF MW > 58.15 THEN HL2 = .42 * (MW - 58.15)
|
|
322
|
+
ELSE HL2 = 0! : heat loss by sweating (comfort)
|
|
323
|
+
460 HL3 = 1.7 * .00001 * m * (5867-PA) : latent respiration heat loss
|
|
324
|
+
470 HL4 = .0014 * m * (34 - TA) : dry respiration heat loss
|
|
325
|
+
480 HL5 = 3.96 * FCL * (XN^4 - (TRA/100^4) : heat loss by radiation
|
|
326
|
+
500 --------------------------------CALCULATE PMV AND PPD -----------------------------------
|
|
327
|
+
510 TS = .303 * EXP (- .036 * m) + .028 : thermal sensation trans coeff
|
|
328
|
+
520 PMV = TS * (MW - HL1 - HL2 - HL3 - HL4 - HL5 - HL6) : predicted mean vote
|
|
329
|
+
530 PPD = 100 - 95 * EXP (- .03353 * PMV ^ 4 - .2179 * PMV^ 2) : predicted percentage dissat.
|
|
330
|
+
540 GOTO 570
|
|
331
|
+
550 PMV = 999999!
|
|
332
|
+
560 PPD = 100
|
|
333
|
+
570 PRINT:PRINT "OUTPUT" : output
|
|
334
|
+
580 PRINT " Predicted Mean Vote (PMV): "
|
|
335
|
+
:PRINT USING "# # . #": PMV
|
|
336
|
+
|
|
337
|
+
|
|
338
|
+
From:
|
|
339
|
+
|
|
340
|
+
http://listserv.repp.org/pipermail/greenbuilding_listserv.repp.org/2009-April/011178.html (gone, 20261110)
|
|
341
|
+
|
|
342
|
+
we have a seemingly corrected version:
|
|
343
|
+
|
|
344
|
+
10 CLO = 1'clothing insulation (clo)
|
|
345
|
+
20 MET=1.1'metabolic rate (met)
|
|
346
|
+
30 WME=0'external work (met)
|
|
347
|
+
40 VEL=.1'air velocity
|
|
348
|
+
50 RH=0'relative humidity (%)
|
|
349
|
+
60 DATA 70,69,.008,0.05,1
|
|
350
|
+
70 DATA 69.15,70,.008,0.05,1
|
|
351
|
+
80 FOR CASE = 1 TO 2
|
|
352
|
+
90 READ TAF,TRF,WA,VEL,CLO
|
|
353
|
+
100 TA=(TAF-32)/1.8'air temp (C)
|
|
354
|
+
110 TR=(TRF-32)/1.8'mean radiant temp (C)
|
|
355
|
+
120 PA=29.921*3377.2/(1+.62198/WA)'water vapor ressure (Pa)
|
|
356
|
+
130 DEF FNPS(T)=EXP(16.6536-4030.183/(TA+235))'sat vapor pressure, kPa
|
|
357
|
+
140 IF PA=0 THEN PA=RH*10*FNPS(TA)'water vapor pressure, Pa
|
|
358
|
+
150 ICL=.155*CLO'clothing resistance (m^2K/W)
|
|
359
|
+
160 M=MET*58.15'metabolic rate (W/m^2)
|
|
360
|
+
170 W=WME*58.15'external work in (W/m^2)
|
|
361
|
+
180 MW=M-W'internal heat production
|
|
362
|
+
190 IF ICL<.078 THEN FCL=1+1.29*ICL ELSE FCL=1.05+.645*ICL'clothing
|
|
363
|
+
factor
|
|
364
|
+
200 HCF=12.1*SQR(VEL)'forced convection conductance
|
|
365
|
+
210 TAA=TA+273'air temp (K)
|
|
366
|
+
220 TRA=TR+273'mean radiant temp (K)
|
|
367
|
+
230 TCLA=TAA+(35.5-TA)/(3.5*(6.45*ICL+.1))'est clothing temp
|
|
368
|
+
240 P1=ICL*FCL:P2=P1*3.96:P3=P1*100:P4=P1*TAA'intermediate values
|
|
369
|
+
250 P5=308.7-.028*MW+P2*(TRA/100)^4
|
|
370
|
+
260 XN=TCLA/100
|
|
371
|
+
270 XF=XN
|
|
372
|
+
280 N=0'number of iterations
|
|
373
|
+
290 EPS=.00015'stop iteration when met
|
|
374
|
+
300 XF=(XF+XN)/2'natural convection conductance
|
|
375
|
+
310 HCN=2.38*ABS(100*XF-TAA)^.25
|
|
376
|
+
320 IF HCF>HCN THEN HC=HCF ELSE HC=HCN
|
|
377
|
+
330 XN=(P5+P4*HC-P2*XF^4)/(100+P3*HC)
|
|
378
|
+
340 N=N+1
|
|
379
|
+
350 IF N>150 GOTO 470
|
|
380
|
+
360 IF ABS(XN-XF)>EPS GOTO 300
|
|
381
|
+
370 TCL=100*XN-273'clothing surface temp (C)
|
|
382
|
+
380 HL1=.00305*(5733-6.99*MW-PA)'heat loss diff through skin
|
|
383
|
+
390 IF MW>58.15 THEN HL2=.42*(MW-58.15) ELSE HL2=0'heat loss by sweating
|
|
384
|
+
400 HL3=.000017*M*(5867-PA)'latent respiration heat loss
|
|
385
|
+
410 HL4=.0014*M*(34-TA)'dry respiration heat loss
|
|
386
|
+
420 HL5=3.96*FCL*(XN^4-(TRA/100)^4)'heat loss by radiation
|
|
387
|
+
430 HL6=FCL*HC*(TCL-TA)'heat loss by convection
|
|
388
|
+
440 TS=.303*EXP(-.036*M)+.028'thermal sensation transfer coefficient
|
|
389
|
+
450 PMV=TS*(MW-HL1-HL2-HL3-HL4-HL5-HL6)'predicted mean vote
|
|
390
|
+
460 GOTO 480
|
|
391
|
+
470 PMV=99999!:PPD=100
|
|
392
|
+
480 PRINT TAF,TRF,PMV
|
|
393
|
+
490 NEXT CASE
|
|
394
|
+
500 LIST 70
|
|
395
|
+
|
|
396
|
+
|
|
397
|
+
From Truong (2010): http://hdl.handle.net/1721.1/61509, we get the following updates to Fanger's
|
|
398
|
+
equations, especially focussed on better predicting comfort in Naturally Ventilated (NV)
|
|
399
|
+
buildings:
|
|
400
|
+
|
|
401
|
+
Fanger underestimated the increase in clothing surface area, leading to too high
|
|
402
|
+
comfort temperatures with higher clo values (for all building types):
|
|
403
|
+
|
|
404
|
+
f_cl = 1 + 0.30 * clo
|
|
405
|
+
|
|
406
|
+
"""
|
|
407
|
+
MET = 58.0 # W/m^2 per met (ISO 7730 uses 58.15 or 58.2)
|
|
408
|
+
CLO = 0.155 # m^2K/W per clo
|
|
409
|
+
|
|
410
|
+
def __init__( self, met=None, M=None, clo=None, I_cl=None, v=0.0,
|
|
411
|
+
t_a=None, t_r=None, hum=None, P_w=None, W=0.0, mit=True ):
|
|
412
|
+
"""Defaults are sedentary activity or standing (met 1.2), working clothes (clo 0.8), no air
|
|
413
|
+
velocity, no net work output. Optional arguments remembered are:
|
|
414
|
+
|
|
415
|
+
met/M Metabolic rate in either met or M (W/m^2); both not allowed
|
|
416
|
+
clo/I_cl Clothing in either clo or I_cl (m^2 K/W) ; both not allowed
|
|
417
|
+
v Air velocity, in m/s
|
|
418
|
+
t_a, t_r Air and average radiant temperatures (degrees C)
|
|
419
|
+
hum/P_w Relative humidity (0,1), or vapour pressure of water in ambient air (Pa)
|
|
420
|
+
W External work performed (W/m^2), usually 0.
|
|
421
|
+
mit Use Truong (2010)'s revised clothing area factor f_cl
|
|
422
|
+
|
|
423
|
+
If you specify humidity in terms of relative humidity (hum, domain (0.0,1.0)), the vapour
|
|
424
|
+
pressure is computed from the air temperature t_a in effect at the time of each computation.
|
|
425
|
+
|
|
426
|
+
"""
|
|
427
|
+
if met is not None and M is not None:
|
|
428
|
+
raise Error( "Specify metabolism as met or M, not both" )
|
|
429
|
+
if clo is not None and I_cl is not None:
|
|
430
|
+
raise Error( "Specify clothing as clo or I_cl, not both" )
|
|
431
|
+
if hum is not None and P_w is not None:
|
|
432
|
+
raise Error( "Specify humidity as hum or P_w, not both" )
|
|
433
|
+
self._M = None
|
|
434
|
+
self._M = self.M( met=1.2 if met is None and M is None else met, M=M )
|
|
435
|
+
self._I_cl = None
|
|
436
|
+
self._I_cl = self.I_cl( clo=0.8 if clo is None and I_cl is None else clo, I_cl=I_cl )
|
|
437
|
+
self.v = v
|
|
438
|
+
self.t_a = t_a
|
|
439
|
+
self.t_r = t_r
|
|
440
|
+
self.hum = hum
|
|
441
|
+
self._P_w = P_w
|
|
442
|
+
if hum is not None and t_a is not None:
|
|
443
|
+
self._P_w = self.P_w( t_a=t_a, hum=hum )
|
|
444
|
+
self.W = W
|
|
445
|
+
self.mit = mit
|
|
446
|
+
self._t_cl = None
|
|
447
|
+
|
|
448
|
+
def metabolism( self, rate=None ):
|
|
449
|
+
"""Returns (rate, met, "description") of the current metabolic rate. Optionally, set the 'rate',
|
|
450
|
+
using a value in the domain [0,1] --> (0.8,3.0) met.
|
|
451
|
+
|
|
452
|
+
"""
|
|
453
|
+
if rate is None:
|
|
454
|
+
M = self.M()
|
|
455
|
+
else:
|
|
456
|
+
M = self._M = self.M( met=scale( rate, (0., 1.), (0.8, 3.0) ))
|
|
457
|
+
if M is None:
|
|
458
|
+
return ( float( 'nan' ), float( 'nan' ), "unknown" )
|
|
459
|
+
met = M / self.MET
|
|
460
|
+
if rate is None:
|
|
461
|
+
rate = scale( met, (0.8, 3.0), (0., 1.) )
|
|
462
|
+
|
|
463
|
+
for lim, desc in ( ( ( 0.8 + 1.0 ) / 2, "lying" ),
|
|
464
|
+
( ( 1.0 + 1.2 ) / 2, "sitting" ),
|
|
465
|
+
( ( 1.2 + 1.6 ) / 2, "standing" ),
|
|
466
|
+
( ( 1.6 + 2.0 ) / 2, "light" ),
|
|
467
|
+
( ( 2.0 + 3.0 ) / 2, "medium" ),
|
|
468
|
+
( ( 3.0 + 4.0 ) / 2, "hard" )):
|
|
469
|
+
if met < lim:
|
|
470
|
+
return ( rate, met, desc )
|
|
471
|
+
return ( rate, met, "heavy" )
|
|
472
|
+
|
|
473
|
+
def M( self, met=None, M=None ):
|
|
474
|
+
"""Support specifying M as either met or M, or from precomputed _M attribute"""
|
|
475
|
+
if met is not None:
|
|
476
|
+
if not 0.0 <= met <= 9.0:
|
|
477
|
+
raise Error( "Metabolism %r met outside (0,9)" % ( met, ))
|
|
478
|
+
return met * self.MET
|
|
479
|
+
if M is not None:
|
|
480
|
+
return M
|
|
481
|
+
return self._M
|
|
482
|
+
|
|
483
|
+
def clothing( self, amount=None ):
|
|
484
|
+
"""Returns (amount, clo, "description"), where amount is in the range [0,1] --> (0,3) clo. 1.0
|
|
485
|
+
clo is defined as the amount of clothes required for an average person to be indefinitely
|
|
486
|
+
comfortable at 20C, 50%RH, 0m/s. 4.0 is the maximum possible amount of clothing; basically
|
|
487
|
+
"Eskimo" clothing; full furs. Optionally pass amount to adjust clothing level.
|
|
488
|
+
|
|
489
|
+
"""
|
|
490
|
+
if amount is None:
|
|
491
|
+
I_cl = self.I_cl()
|
|
492
|
+
else:
|
|
493
|
+
I_cl = self._I_cl = self.I_cl( clo=scale( amount, (0., 1.), (0., 3.) ))
|
|
494
|
+
if I_cl is None:
|
|
495
|
+
return ( float( 'nan' ), float( 'nan' ), "unknown" )
|
|
496
|
+
clo = I_cl / self.CLO
|
|
497
|
+
if amount is None:
|
|
498
|
+
amount = scale( clo, (0., 3.), (0., 1.) )
|
|
499
|
+
|
|
500
|
+
for lim, desc in ( ( ( 0.0 + 0.1 ) / 2, "naked" ),
|
|
501
|
+
( ( 0.1 + 0.3 ) / 2, "shorts" ),
|
|
502
|
+
( ( 0.3 + 0.5 ) / 2, "tropical" ),
|
|
503
|
+
( ( 0.5 + 0.8 ) / 2, "summer" ),
|
|
504
|
+
( ( 0.8 + 1.0 ) / 2, "casual" ),
|
|
505
|
+
( ( 1.0 + 1.5 ) / 2, "indoor" ),
|
|
506
|
+
( ( 1.5 + 2.0 ) / 2, "winter" ),
|
|
507
|
+
( ( 2.0 + 3.0 ) / 2, "heavy" )):
|
|
508
|
+
if clo < lim:
|
|
509
|
+
return ( amount, clo, desc )
|
|
510
|
+
return ( amount, clo, "blankets" )
|
|
511
|
+
|
|
512
|
+
def I_cl( self, clo=None, I_cl=None ):
|
|
513
|
+
"""Support specifying I_cl either directly or from clo, or from a precomputed self._I_cl."""
|
|
514
|
+
if clo is not None:
|
|
515
|
+
if not 0.0 <= clo <= 3.0:
|
|
516
|
+
raise Error( "Clothing %r clo outside (0,3)" % ( clo, ))
|
|
517
|
+
return clo * self.CLO
|
|
518
|
+
if I_cl is not None:
|
|
519
|
+
return I_cl
|
|
520
|
+
return self._I_cl
|
|
521
|
+
|
|
522
|
+
def f_cl( self, I_cl ):
|
|
523
|
+
"""The clothing area factor. The updated f_cl = 1 + 0.3 x clo equation is used, if 'mit' mode is
|
|
524
|
+
true.
|
|
525
|
+
|
|
526
|
+
"""
|
|
527
|
+
if self.mit:
|
|
528
|
+
return 1.00 + 0.3 * I_cl / self.CLO
|
|
529
|
+
if I_cl < 0.078:
|
|
530
|
+
return 1.00 + 0.2 * I_cl / self.CLO
|
|
531
|
+
return 1.05 + 0.1 * I_cl / self.CLO
|
|
532
|
+
|
|
533
|
+
def t_cl_h_c( self, M, W, I_cl, f_cl, v, t_a, t_r, error=1e-6, allow=1e-4,
|
|
534
|
+
reps=150, advance=5 ):
|
|
535
|
+
"""Produce an refined estimate of t_cl and h_c to within 'err' of 1e-4. Since this algorithm
|
|
536
|
+
deals in 1/100th Kelvins, the error will be amplified by 100 in the result, so we should try
|
|
537
|
+
to solve to 1e-6. This should avoid getting too close to the limits of the underlying
|
|
538
|
+
floating point format, and usually take around 10 iterations. However, degenerate cases in
|
|
539
|
+
prior implementations have been observed; for example, the loop oscillates between two
|
|
540
|
+
values, never approaching closer than an error of 0.00000614 after 37 iterations, settling
|
|
541
|
+
into an oscillation between two values:
|
|
542
|
+
|
|
543
|
+
t_cl ( 37): 2.91945054 (err 0.00000614)
|
|
544
|
+
t_cl ( 74): 2.91944133 (err 0.00000614)
|
|
545
|
+
t_cl (111): 2.91945054 (err 0.00000614)
|
|
546
|
+
...
|
|
547
|
+
t_cl (141): 2.91945054 (err 0.00000614)
|
|
548
|
+
...
|
|
549
|
+
t_cl (146): 2.91944133 (err 0.00000614)
|
|
550
|
+
t_cl (147): 2.91945054 (err 0.00000614)
|
|
551
|
+
t_cl (148): 2.91944133 (err 0.00000614)
|
|
552
|
+
t_cl (149): 2.91945054 (err 0.00000614)
|
|
553
|
+
|
|
554
|
+
'xn': 2.9194505366000816,
|
|
555
|
+
'xf': 2.919444401018496,
|
|
556
|
+
'taa': 291.94444544017557,
|
|
557
|
+
'I_cl': 0.46499999999999997,
|
|
558
|
+
't_a': 18.79444544017561,
|
|
559
|
+
'hcf': 0.0,
|
|
560
|
+
't_r': 14.251083402047804,
|
|
561
|
+
'hcn': 0.11440058061771223,
|
|
562
|
+
'f_cl': 1.9,
|
|
563
|
+
'M': 46.400000000000006,
|
|
564
|
+
'reps': 150,
|
|
565
|
+
'W': 0.0,
|
|
566
|
+
'hc': 0.11440058061771223,
|
|
567
|
+
'p2': 3.4986599999999997,
|
|
568
|
+
'p3': 88.35,
|
|
569
|
+
'p1': 0.8835,
|
|
570
|
+
'p4': 257.9329175463951,
|
|
571
|
+
'err': 1e-07,
|
|
572
|
+
'p5': 546.1024074023035,
|
|
573
|
+
'tra': 287.4010834020478,
|
|
574
|
+
'n': 149,
|
|
575
|
+
'mw': 46.400000000000006,
|
|
576
|
+
'v': 0.0
|
|
577
|
+
|
|
578
|
+
Or, worse yet, not even within 1e-6:
|
|
579
|
+
'xf': 2.9814100885428747,
|
|
580
|
+
't_a': 24.989128226382643,
|
|
581
|
+
't_r': 22.458774905050937,
|
|
582
|
+
|
|
583
|
+
Instead of a fixed number of iterations, we'll simply iterate 'til we cease advancing the
|
|
584
|
+
convergence, or we hit our goal 'error' limit, and only failing if we hit our ultimate rep
|
|
585
|
+
limit, or cease converging without getting within our maximum 'allow' error limit...
|
|
586
|
+
|
|
587
|
+
Uses the self._t_cl as the last estimate, assuming that subsequent calls will change input
|
|
588
|
+
terms only slightly. Pretty much straight from the ISO 7730 BASIC program.
|
|
589
|
+
|
|
590
|
+
"""
|
|
591
|
+
mw = M - W
|
|
592
|
+
hcf = 12.1 * math.sqrt( v )
|
|
593
|
+
taa = t_a + 273.15 # Air temp, K
|
|
594
|
+
tra = t_r + 273.15 # Radiant temp, K
|
|
595
|
+
p1 = I_cl * f_cl
|
|
596
|
+
p2 = p1 * 3.96
|
|
597
|
+
p3 = p1 * 100
|
|
598
|
+
p4 = p1 * taa
|
|
599
|
+
p5 = 308.7 - .028 * mw + p2 * math.pow( tra / 100, 4 )
|
|
600
|
+
if self._t_cl is None:
|
|
601
|
+
self._t_cl = t_a + ( 35.5 - t_a ) / ( 3.5 * ( 6.45 * I_cl + .1 ))
|
|
602
|
+
xf = xn = ( self._t_cl + 273.15 ) / 100
|
|
603
|
+
|
|
604
|
+
converged = False
|
|
605
|
+
n = 0
|
|
606
|
+
least = {}
|
|
607
|
+
while not converged:
|
|
608
|
+
xf = ( xf + xn ) / 2
|
|
609
|
+
hcn = 2.38 * math.pow( abs( 100 * xf - taa ), .25 )
|
|
610
|
+
hc = max( hcf, hcn )
|
|
611
|
+
xn = ( p5 + p4 * hc - p2 * math.pow( xf, 4 )) / ( 100 + p3 * hc )
|
|
612
|
+
diff = abs( xf - xn )
|
|
613
|
+
converged = n > 5 and diff <= error * abs( xn )
|
|
614
|
+
n += 1
|
|
615
|
+
if not least or diff < least['diff']:
|
|
616
|
+
least['n'] = n
|
|
617
|
+
least['diff'] = diff
|
|
618
|
+
least['xn'] = xn
|
|
619
|
+
if n > least['n'] + advance or n > reps:
|
|
620
|
+
break
|
|
621
|
+
|
|
622
|
+
if not converged and diff > allow:
|
|
623
|
+
# Well, we didn't converge xf/xn within 'error'; if we didn't even get within
|
|
624
|
+
# 'allow', we'll have to raise an exception.
|
|
625
|
+
raise Error( "Couldn't refine t_cl to within %g, in %d iterations: locals:\n%r" % (
|
|
626
|
+
allow, n, locals() ))
|
|
627
|
+
|
|
628
|
+
self._t_cl = ( 100 * least['xn'] ) - 273.15
|
|
629
|
+
return self._t_cl, hc
|
|
630
|
+
|
|
631
|
+
def t_d( self, e ):
|
|
632
|
+
"""Return dewpoint temperature (degrees C), from vapour pressure e (in Pa). Equation is for
|
|
633
|
+
millibars (mb), which are Pa/100. See:
|
|
634
|
+
|
|
635
|
+
http://facstaff.unca.edu/cgodfrey/courses/advcomp/cgi.shtml
|
|
636
|
+
|
|
637
|
+
"""
|
|
638
|
+
e_ln = math.log( e / 100. / 6.112 )
|
|
639
|
+
return ( 243.5 * e_ln ) / ( 17.67 - e_ln )
|
|
640
|
+
|
|
641
|
+
def p_sat( self, t_a ):
|
|
642
|
+
"""Saturated Vapour Pressure, in Pa, for a given air temperature t_a. From ISO 7730 BASIC
|
|
643
|
+
program, and:
|
|
644
|
+
|
|
645
|
+
http://www.dtic.mil/cgi-bin/GetTRDoc?AD=ADA305730
|
|
646
|
+
|
|
647
|
+
"""
|
|
648
|
+
return math.exp( 16.6536 - ( 4030.183 / ( t_a + 235. ))) * 1000.
|
|
649
|
+
|
|
650
|
+
def P_w( self, t_a=None, hum=None, P_w=None ):
|
|
651
|
+
"""Compute actual vapour pressure P_w in Pascals (Pa), from Relative Humidity (hum, ~(0., 1.)),
|
|
652
|
+
and air temperature t_a (C). If neither hum nor P_w is supplied, returns the precomputed
|
|
653
|
+
self._P_w.
|
|
654
|
+
|
|
655
|
+
RH: Relative Humidity
|
|
656
|
+
RH = e / e_s
|
|
657
|
+
or:
|
|
658
|
+
e = RH x e_s (in mb)
|
|
659
|
+
P_w = e x 100. (in Pa)
|
|
660
|
+
where:
|
|
661
|
+
P_w: Vapour pressure (Pa)
|
|
662
|
+
e: Vapour pressure (millibars, mb)
|
|
663
|
+
|
|
664
|
+
e_s(t_a): Saturation Vapour Pressure (mb), from air temperature (degress C)
|
|
665
|
+
(From: http://facstaff.unca.edu/cgodfrey/courses/advcomp/cgi.shtml; WRONG)
|
|
666
|
+
e_s(t_a) = 6.112 x 10^((17.67 x t_a) / (t_a + 243.5))
|
|
667
|
+
or:
|
|
668
|
+
(From: http://www.theweathernetwork.com/glossary/humidex)
|
|
669
|
+
e_s(t_a) = 6.112 x 10^(7.5 x t_a/(237.7 + T))
|
|
670
|
+
1 Bar = 100,000 Pa
|
|
671
|
+
1 mb = 100 Pa
|
|
672
|
+
|
|
673
|
+
We use the ISO 7730 BASIC program's saturation vapour pressure; see p_sat.
|
|
674
|
+
"""
|
|
675
|
+
if P_w is not None:
|
|
676
|
+
return P_w
|
|
677
|
+
if hum is not None:
|
|
678
|
+
# No P_w, but we have RH%; compute vapour pressure (Pa) using air temperature (C)
|
|
679
|
+
if t_a is None or not -100. < t_a < 100.:
|
|
680
|
+
raise Error( "Relative humidity requires an air temperature in (-100,100) C; not %r" % (
|
|
681
|
+
t_a, ))
|
|
682
|
+
if not 0.0 <= hum <= 1.0:
|
|
683
|
+
raise Error( "Relative humidity %r outside (0,1)" % ( hum, ))
|
|
684
|
+
return hum * self.p_sat( t_a=t_a )
|
|
685
|
+
return self._P_w
|
|
686
|
+
|
|
687
|
+
def L( self, met=None, M=None, clo=None, I_cl=None, v=None,
|
|
688
|
+
t_a=None, t_r=None, hum=None, P_w=None, W=None ):
|
|
689
|
+
"""Fanger's Comfort Equation. Computes the net (+/-) thermal load (L) on a typical human body,
|
|
690
|
+
subject to the specified parameters, and returns it scaled to the Predicted Mean Vote (PMV)
|
|
691
|
+
thermal sensation scale (see PMV). Any terms not defined must have been specified at the
|
|
692
|
+
time of object creation, or result will be an exception.
|
|
693
|
+
|
|
694
|
+
"""
|
|
695
|
+
# Metabolism, clothing
|
|
696
|
+
M = self.M( met=met, M=M )
|
|
697
|
+
I_cl = self.I_cl( clo=clo, I_cl=I_cl )
|
|
698
|
+
f_cl = self.f_cl( I_cl=I_cl )
|
|
699
|
+
|
|
700
|
+
# External Work
|
|
701
|
+
W = W if W is not None else self.W
|
|
702
|
+
|
|
703
|
+
# Environmental conditions. A relative humidity (if specified) tracks the air temperature.
|
|
704
|
+
v = v if v is not None else self.v
|
|
705
|
+
t_a = t_a if t_a is not None else self.t_a
|
|
706
|
+
t_r = t_r if t_r is not None else self.t_r
|
|
707
|
+
if t_a is None or t_r is None:
|
|
708
|
+
raise Error( "Fanger's equation requires air and radiant temperatures t_a and t_r" )
|
|
709
|
+
P_w = self.P_w( t_a=t_a, hum=hum if hum is not None or P_w is not None else self.hum,
|
|
710
|
+
P_w=P_w )
|
|
711
|
+
if P_w is None:
|
|
712
|
+
raise Error( "Fanger's equation requires humidity hum or vapour pressure P_w" )
|
|
713
|
+
|
|
714
|
+
# Compute clothing temperature t_cl, iteratively, since the formula itself depends on
|
|
715
|
+
# t_cl. We'll iterate 'til the computed t_cl is within ~10^-4 of the last sample, and save
|
|
716
|
+
# the last computed value in self._t_cl (for the next time).
|
|
717
|
+
t_cl, h_c = self.t_cl_h_c( M=M, W=W, I_cl=I_cl, f_cl=f_cl, v=v, t_a=t_a, t_r=t_r )
|
|
718
|
+
|
|
719
|
+
# Compute all heat losses (W/m^2)
|
|
720
|
+
hl1 = .00305 * ( 5733 - 6.99 * ( M - W ) - P_w ) # Through skin
|
|
721
|
+
hl2 = max( 0.42 * ( ( M - W ) - 58.15 ), 0. ) # From sweating
|
|
722
|
+
hl3 = .000017 * M * ( 5867 - P_w ) # Latent respiration
|
|
723
|
+
hl4 = .0014 * M * ( 34 - t_a ) # Dry respiration
|
|
724
|
+
hl5 = 3.96 * f_cl * ( math.pow( ( t_cl + 273.15 ) / 100, 4 ) # Radiation
|
|
725
|
+
- math.pow( ( t_r + 273.15 ) / 100, 4 ))
|
|
726
|
+
hl6 = f_cl * h_c * ( t_cl - t_a ) # Convection
|
|
727
|
+
balance = ( M - W ) - hl1 - hl2 - hl3 - hl4 - hl5 - hl6
|
|
728
|
+
ts = .303 * math.exp( -.036 * M ) + .028 # Thermal sensation coefficient
|
|
729
|
+
return ts * balance
|
|
730
|
+
|
|
731
|
+
def L_simple( self, met=None, M=None, clo=None, I_cl=None, v=None,
|
|
732
|
+
t_a=None, t_r=None, hum=None, P_w=None, W=None ):
|
|
733
|
+
"""A simplified thermal loading calculation, that operates using an estimate of perceived
|
|
734
|
+
temperatures. It simply returns the difference between desired and perceived temperatures,
|
|
735
|
+
for different levels of clothing and metabolic rate. Result is in degrees C difference
|
|
736
|
+
between perceived and desired temperatures. Humidity and external work are ignored.
|
|
737
|
+
|
|
738
|
+
"""
|
|
739
|
+
M = self.M( met=met, M=M )
|
|
740
|
+
I_cl = self.I_cl( clo=clo, I_cl=I_cl )
|
|
741
|
+
v = v if v is not None else self.v
|
|
742
|
+
t_a = t_a if t_a is not None else self.t_a
|
|
743
|
+
t_r = t_r if t_r is not None else self.t_r
|
|
744
|
+
desired, perceived = self.t_sub( M=M, I_cl=I_cl, t_a=t_a, t_r=t_r, v=v )
|
|
745
|
+
return perceived - desired
|
|
746
|
+
|
|
747
|
+
def t_sub( self, M, I_cl, t_a, t_r, v ):
|
|
748
|
+
"""McIntyre's simplified desired and perceived temperatures (C); see class docstring."""
|
|
749
|
+
t_sub_desired = 33.5 - 3 * I_cl - ( 0.08 + 0.05 * I_cl ) * M
|
|
750
|
+
if v > 0.1:
|
|
751
|
+
t_sub_perceived = 0.44 * t_r + ( 5. - math.pow( 10 * v, 0.5 ) * ( 5. - t_a )) / (
|
|
752
|
+
0.44 + 0.56 * math.pow( 10 * v, 0.5 ))
|
|
753
|
+
else:
|
|
754
|
+
t_sub_perceived = 0.56 * t_a + 0.44 * t_r
|
|
755
|
+
return t_sub_desired, t_sub_perceived
|
|
756
|
+
|
|
757
|
+
def PMV( self, **kwargs ):
|
|
758
|
+
"""Compute Predicted Mean Vote on the ASHRAE thermal sensation scale (-3.,+3):
|
|
759
|
+
|
|
760
|
+
+3 hot
|
|
761
|
+
+2 warm
|
|
762
|
+
+1 slightly warm
|
|
763
|
+
0 neutral
|
|
764
|
+
-1 slightly cool
|
|
765
|
+
-2 cool
|
|
766
|
+
-3 cold
|
|
767
|
+
|
|
768
|
+
PMV = (0.303 x e^(-0.036 x M) + 0.028) x L
|
|
769
|
+
where:
|
|
770
|
+
M: Metabolic rate (W/m^2)
|
|
771
|
+
L: thermal load; difference between internal heat production and heat lost to environment
|
|
772
|
+
|
|
773
|
+
This is exactly what L() computes (and has always returned), so PMV is an alias for L,
|
|
774
|
+
accepting all the same keyword args.
|
|
775
|
+
|
|
776
|
+
"""
|
|
777
|
+
return self.L( **kwargs )
|
|
778
|
+
|
|
779
|
+
def PPD( self, pmv=None, **kwargs ):
|
|
780
|
+
"""Predicted Percentage of Dissatisfied (5% to 100%), from the PMV (ISO 7730, eq. 4):
|
|
781
|
+
|
|
782
|
+
PPD = 100 - 95 x e^(-0.03353 x PMV^4 - 0.2179 x PMV^2)
|
|
783
|
+
|
|
784
|
+
Even at a neutral PMV of 0, 5% of people are dissatisfied; at PMV +/-0.5, ~10%. Supply a
|
|
785
|
+
precomputed 'pmv', or the same keyword args as PMV.
|
|
786
|
+
"""
|
|
787
|
+
if pmv is None:
|
|
788
|
+
pmv = self.PMV( **kwargs )
|
|
789
|
+
return 100 - 95 * math.exp( -0.03353 * pmv ** 4 - 0.2179 * pmv ** 2 )
|
|
790
|
+
|
|
791
|
+
def setpoint( self, pmv=0.0, lo=0.0, hi=40.0, error=1e-3, **kwargs ):
|
|
792
|
+
"""Air temperature t_a (C) within (lo,hi) yielding the target 'pmv' (default: neutral), given
|
|
793
|
+
all other conditions (eg. the mean radiant temperature t_r). This is the air temperature a
|
|
794
|
+
comfort-seeking thermostat should target. PMV rises monotonically with t_a, so bisect.
|
|
795
|
+
|
|
796
|
+
"""
|
|
797
|
+
if 't_a' in kwargs:
|
|
798
|
+
raise Error( "setpoint computes t_a; do not specify it" )
|
|
799
|
+
pmv_lo, pmv_hi = self.PMV( t_a=lo, **kwargs ), self.PMV( t_a=hi, **kwargs )
|
|
800
|
+
if not pmv_lo <= pmv <= pmv_hi:
|
|
801
|
+
raise Error( "PMV %+.2f not attainable in t_a (%g,%g)C; PMV ranges (%+.2f,%+.2f)" % (
|
|
802
|
+
pmv, lo, hi, pmv_lo, pmv_hi ))
|
|
803
|
+
while hi - lo > error:
|
|
804
|
+
mid = ( lo + hi ) / 2
|
|
805
|
+
if self.PMV( t_a=mid, **kwargs ) < pmv:
|
|
806
|
+
lo = mid
|
|
807
|
+
else:
|
|
808
|
+
hi = mid
|
|
809
|
+
return ( lo + hi ) / 2
|
|
810
|
+
|
|
811
|
+
def feels( self, **kwargs ):
|
|
812
|
+
"""A description of the PMV: cold, cool, coolish, ok, warmish, warm or hot"""
|
|
813
|
+
pmv = self.PMV( **kwargs )
|
|
814
|
+
for lim, desc in ( ( -3, "cold" ), ( -2, "cool" ), ( -1, "coolish" )):
|
|
815
|
+
if pmv <= lim:
|
|
816
|
+
return desc
|
|
817
|
+
for lim, desc in ( ( +3, "hot" ), ( +2, "warm" ), ( +1, "warmish" )):
|
|
818
|
+
if pmv >= lim:
|
|
819
|
+
return desc
|
|
820
|
+
return "ok"
|