API reference¶
The thermal-index functions are available at the top level of the package, for
example thermofeel.calculate_utci(...) or
thermofeel.calculate_excess_heat_factor(...). The supporting functions for the
excess heat/cold factors (daily mean temperature, significance and
acclimatisation indices, heatwave severity) live in the thermofeel.excess_heat
submodule. This page is generated from the source docstrings.
All functions are vectorised over NumPy and take SI inputs; pass arrays and
wrap scalars in an array (e.g. np.array([300.0])). See
Calling convention for details.
Thermal indices and supporting quantities¶
thermofeel is a library to calculate human thermal comfort indexes.
Currently calculates the thermal indexes: * Universal Thermal Climate Index * Apparent Temperature * Apparent Temperature (radiation) * Heat Index Adjusted * Heat Index Simplified * Humidex * Discomfort Index * Summer Simmer Index * Normal Effective Temperature * Relative Strain Index * Wet Bulb Globe Temperature * Wet Bulb Globe Temperature Simple * Wet Bulb Globe Temperature (Liljegren method) * Heat Force (KNMI 0-10 heat-stress scale) * Excess Heat Factor and Excess Cold Factor * Predicted Mean Vote and Predicted Percentage of Dissatisfied (PMV/PPD) * Wind Chill
In support of the above indexes, it also calculates: * Globe Temperature * Mean Radiant Temperature * Mean Radiant Temperature from Globe Temperature * Relative Humidity Percentage * Saturation vapour pressure * Wet Bulb Temperature
The thermofeel.approximations namespace additionally provides clearly-labelled estimators (currently the direct solar radiation fdir from global radiation) for datasets that lack an input that a radiation index needs.
To calculate the cos of the solar zenith angle, we suggest to use the earthkit-meteo library (github.com:ecmwf/earthkit-meteo.git)
calculate_relative_humidity_percent ¶
Relative Humidity in percent :param t2_k: (float array) 2m temperature [K] :param td_k: (float array) dew point temperature [K] returns relative humidity [%]
Uses the Magnus-Tetens saturation vapour pressure (coefficients over water) -
a different empirical form to calculate_saturation_vapour_pressure
(Hardy 1998). The result is not clamped: when td_k > t2_k (supersaturation)
it exceeds 100%.
Reference: Tetens (1930); coefficients per Murray (1967) https://doi.org/10.1175/1520-0450(1967)006<0203:OTCOSV>2.0.CO;2
Source code in thermofeel/thermofeel.py
calculate_saturation_vapour_pressure ¶
Saturation vapour pressure over water :param t2_k: (float array) 2m temperature [K] returns saturation vapor pressure over water in the pure phase [hPa] == [mBar] Reference: Hardy (1998) https://www.decatur.de/javascript/dew/resources/its90formulas.pdf
Source code in thermofeel/thermofeel.py
calculate_saturation_vapour_pressure_multiphase ¶
Saturation vapour pressure over liquid water and ice :param t2_k: (float array) 2m temperature [K] :param phase: (int array) 0 over liquid water, 1 over ice (same shape as t2_k) returns pressure of water vapor over a surface of liquid water or ice [hPa] == [mBar]
t2_k and phase are array-like (wrap a scalar in an array). Only
elements with phase equal to 0 (liquid) or 1 (ice) are computed; any
other phase value leaves that element at 0 hPa.
Reference: ECMWF IFS Documentation CY45R1 - Part IV : Physical processes (2018) pp. 116 https://doi.org/10.21957/4whwo8jw0 https://metview.readthedocs.io/en/latest/api/functions/saturation_vapour_pressure.html
Source code in thermofeel/thermofeel.py
calculate_nonsaturation_vapour_pressure ¶
Non saturated vapour pressure :param t2_k: (float array) 2m temperature [K] :param rh: (float array) relative humidity percentage [%] returns non saturated vapor pressure [hPa] == [mBar] Reference: Bureau of Meteorology (2010) http://www.bom.gov.au/info/thermal_stress/#approximation
Source code in thermofeel/thermofeel.py
scale_windspeed ¶
Scaling wind speed from 10 metres to height h :param va: (float array) 10m wind speed [m/s] :param h: (float array) height at which wind speed needs to be scaled [m] returns wind speed at height h Reference: Bröde et al. (2012) https://doi.org/10.1007/s00484-011-0454-1
Source code in thermofeel/thermofeel.py
approximate_dsrp ¶
Helper function to approximate dsrp from fdir and cossza
By geometry the direct solar radiation perpendicular to the beam is
dsrp = fdir / cos(sza) = fdir / cossza; this is applied where
cossza > threshold and left as fdir below it. The approximation
introduces large errors as cossza approaches zero, so use it only if dsrp is
not available in your dataset. fdir and cossza are array-like (wrap a
scalar in an array). To compute cossza consider using
earthkit-meteo.solar.cos_solar_zenith_angle.
:param fdir: (float array) total sky direct solar radiation at surface [W m-2]
:param cossza: (float array) cosine of solar zenith angle [dimentionless]
:param threshold: (float) minimum cossza for which fdir is divided (default 0.1)
returns direct radiation from the Sun [W m-2]
Source code in thermofeel/thermofeel.py
calculate_dew_point_from_relative_humidity ¶
Dew point temperature at 2m from relative humidity in percent :param rh: (float array) relative humidity [%] :param t2_k: (float array) 2m temperature [K] returns dew point temperature [K] Reference: Alduchov and Eskridge (1996) https://doi.org/10.1175/1520-0450(1996)035<0601:IMFAOS>2.0.CO;2
Source code in thermofeel/thermofeel.py
calculate_mean_radiant_temperature ¶
calculate_mean_radiant_temperature(
ssrd: ArrayLike,
ssr: ArrayLike,
dsrp: ArrayLike,
strd: ArrayLike,
fdir: ArrayLike,
strr: ArrayLike,
cossza: ArrayLike,
) -> np.ndarray
MRT - Mean Radiant Temperature To compute cossza consider using earhkit-meteo.solar.calculate_cos_solar_zenith_angle :param ssrd: (float array) surface solar radiation downwards [W m-2] :param ssr: (float array) surface net solar radiation [W m-2] :param dsrp: (float array) direct solar radiation [W m-2] :param strd: (float array) surface thermal radiation downwards [W m-2] :param fdir: (float array) total sky direct solar radiation at surface [W m-2] :param strr: (float array) surface net thermal radiation [W m-2] :param cossza: (float array) cosine of solar zenith angle [dimentionless] returns mean radiant temperature [K] Reference: Di Napoli et al. (2020) https://link.springer.com/article/10.1007/s00484-020-01900-5
Source code in thermofeel/thermofeel.py
calculate_utci_polynomial ¶
calculate_utci_polynomial(
t2m: ArrayLike,
mrt: ArrayLike,
va: ArrayLike,
wvp: ArrayLike,
) -> np.ndarray
Helper function to calculate the UTCI polynomial approximation :param t2m: (float array) is 2m temperature [C] :param mrt: (float array) is mean radiant temperature [C] :param va: (float array) is wind speed at 10 meters [m/s] :param wvp: (float array) is water vapour pressure [kPa] returns UTCI [K] Reference: Brode et al. (2012) https://doi.org/10.1007/s00484-011-0454-1
Source code in thermofeel/thermofeel.py
284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 | |
calculate_utci ¶
calculate_utci(
t2_k: ArrayLike,
va: ArrayLike,
mrt: ArrayLike,
td_k: ArrayLike | None = None,
ehPa: ArrayLike | None = None,
) -> np.ndarray
UTCI - Universal Thermal Climate Index :param t2_k: (float array) is 2m temperature [K] :param va: (float array) is wind speed at 10 meters [m/s] :param mrt: (float array) is mean radiant temperature [K] :param td_k: (float array) is 2m dew point temperature [K] :param ehPa: (float array) is water vapour pressure [hPa] returns UTCI [K]
Validity: the polynomial is fitted for air temperature -50...+50 degC, 10 m wind speed 0.5...17 m/s, mean radiant temperature from 30 degC below to 70 degC above air temperature, and water vapour pressure up to 50 hPa. Inputs are not clamped; outside this range the approximation extrapolates.
Reference: Brode et al. (2012) https://doi.org/10.1007/s00484-011-0454-1
Source code in thermofeel/thermofeel.py
calculate_wbgt_simple ¶
WBGT - Wet Bulb Globe Temperature computed by a the simpler algorithm :param t2_k: (float array) 2m temperature [K] :param rh: (float array) relative humidity percentage [%] returns Wet Bulb Globe Temperature [K]
Validity: an empirical screening estimate (no radiation/wind term), intended
for moderate-to-warm outdoor conditions; it is not a substitute for the
physically based calculate_wbgt_liljegren where accuracy matters.
Reference: ACSM (1984) https://doi.org/10.1080/00913847.1984.11701899 See also: http://www.bom.gov.au/info/thermal_stress/#approximation https://www.jstage.jst.go.jp/article/indhealth/50/4/50_MS1352/_pdf
Source code in thermofeel/thermofeel.py
calculate_wbt ¶
Wet Bulb Temperature :param t2_k: (float array) 2m temperature [K] :param rh: (float array) relative humidity percentage [%] returns wet bulb temperature [K]
Validity: Stull's regression is fitted for relative humidity 5...99% and air temperature -20...+50 degC at standard sea-level pressure (1013.25 hPa).
Reference: Stull (2011) https://doi.org/10.1175/JAMC-D-11-0143.1
Source code in thermofeel/thermofeel.py
calculate_bgt ¶
Globe temperature :param t2_k: (float array) 2m temperature [K] :param mrt: (float array) mean radiant temperature [K] :param va: (float array) wind speed at 10 meters [m/s] returns globe temperature [K]
Solves the globe energy balance with a closed-form quartic root.
Edge case — calm air: the closed form is a 0/0 indeterminate at exactly
zero wind speed. The limit as va -> 0 is the mean radiant temperature
(with no convection the globe reaches radiative equilibrium, bgt -> mrt),
so mrt is returned where va == 0. Invalid negative wind still yields
NaN.
Reference: Guo et al. 2018 https://doi.org/10.1016/j.enbuild.2018.08.029
Source code in thermofeel/thermofeel.py
calculate_wbgt ¶
WBGT - Wet Bulb Globe Temperature :param t2_k: (float array) 2m temperature [K] :param mrt: (float array) mean radiant temperature [K] :param va: (float array) wind speed at 10 meters [m/s] :param td_k: (float array) dew point temperature [K] returns wet bulb globe temperature [K] Reference: Stull (2011) https://doi.org/10.1175/JAMC-D-11-0143.1 See also: http://www.bom.gov.au/info/thermal_stress/
Source code in thermofeel/thermofeel.py
calculate_wind_speed_2m_liljegren ¶
calculate_wind_speed_2m_liljegren(
va: ArrayLike, cossza: ArrayLike, ssrd: ArrayLike
) -> np.ndarray
Scale 10m wind speed to 2m using the Liljegren stability-dependent profile :param va: (float array) wind speed at 10 metres [m/s] :param cossza: (float array) cosine of the solar zenith angle [dimensionless] :param ssrd: (float array) instantaneous downward shortwave radiation at the surface (SSRD) [W/m2] returns wind speed at 2 metres [m/s]
This is the 10m -> 2m conversion used operationally by KNMI within the
Liljegren WBGT (va * (2/10)**p), where the power-law exponent p comes
from a Pasquill-Gifford stability class derived from the solar elevation,
incoming radiation and wind speed. The result is floored at 0.13 m/s.
Reference: Liljegren et al. (2008) https://doi.org/10.1080/15459620802310770 See also: Kong and Huber (2022) https://doi.org/10.1029/2021EF002334
Source code in thermofeel/thermofeel.py
calculate_wbgt_liljegren ¶
calculate_wbgt_liljegren(
t2_k: ArrayLike,
rh: ArrayLike,
pressure: ArrayLike,
va: ArrayLike,
ssrd: ArrayLike,
fdir: ArrayLike,
cossza: ArrayLike,
wind_scaling: str = "liljegren",
) -> np.ndarray
WBGT - Wet Bulb Globe Temperature, Liljegren method :param t2_k: (float array) 2m temperature [K] :param rh: (float array) relative humidity [%] :param pressure: (float array) surface air pressure [hPa] :param va: (float array) wind speed at 10 metres [m/s] :param ssrd: (float array) instantaneous downward shortwave radiation at the surface (SSRD) [W/m2] :param fdir: (float array) fraction of ssrd that is direct beam [dimensionless, 0-1] :param cossza: (float array) cosine of the solar zenith angle [dimensionless] :param wind_scaling: (str) how to convert the 10m wind to the 2m sensor height: "liljegren" (default, the KNMI stability-dependent profile, see calculate_wind_speed_2m_liljegren) or "brode" (the generic scale_windspeed log profile) returns wet bulb globe temperature [K]
Physically based WBGT after Liljegren et al. (2008), the method used
operationally by KNMI. The globe and natural-wet-bulb temperatures are each
solved from their steady-state energy balance by fixed-point iteration and
combined with the air temperature as WBGT = 0.7*Tnw + 0.2*Tg + 0.1*Ta.
The KNMI operational guards are applied: wind below 0.62 m/s at 10 m is
raised to that floor (it is then scaled to 2 m for the sensor model); the
direct-beam fraction is clamped to [0, 0.9] and set to 0 when the sun is at
or below 89.5 degrees zenith. Instantaneous ssrd and cossza are
supplied by the caller (e.g. via earthkit-meteo). NaN is returned where the
iteration does not converge.
The Liljegren energy-balance solvers and physical constants live in the
thermofeel.liljegren submodule.
Reference: Liljegren et al. (2008) https://doi.org/10.1080/15459620802310770 See also: Kong and Huber (2022) https://doi.org/10.1029/2021EF002334
Source code in thermofeel/thermofeel.py
calculate_heat_force ¶
Heat Force - KNMI 0-10 heat-stress communication scale (hittekracht) :param wbgt_k: (float array) wet bulb globe temperature [K] returns heat force on a 0-10 scale [dimensionless]
Translates WBGT onto the integer 0-10 "heat force" scale used by KNMI for public communication of heat stress, analogous to wind force and the UV index. The bands are fixed 2 degC intervals of WBGT (lower-closed): heat force 0 is WBGT < 14 degC, 1 is [14, 16) degC, ..., 9 is [30, 32) degC, and 10 is WBGT >= 32 degC. Values are returned as whole numbers (as a float array, so NaN inputs propagate).
Reference: KNMI Technical Report TR-26-04 (2026), Table 1.
Source code in thermofeel/thermofeel.py
calculate_excess_heat_factor ¶
calculate_excess_heat_factor(
ehi_sig: ArrayLike,
ehi_accl: ArrayLike,
clip: bool = False,
) -> np.ndarray
EXHF - Excess Heat Factor :param ehi_sig: (float array) significance index, e.g. relative to the 95th percentile of daily mean temperature over a reference period. :param ehi_accl: (float array) acclimatisation index. :param clip: (bool) clip the significance index at zero (default False). returns excess heat factor [input unit squared, e.g. K^2]
Top-level convenience wrapper for
thermofeel.excess_heat.excess_heat_factor. The supporting daily mean
temperature, significance and acclimatisation indices, and heatwave
severity, live in the thermofeel.excess_heat submodule.
Reference: Nairn and Fawcett (2014), equation (3) https://doi.org/10.3390/ijerph120100227
Source code in thermofeel/thermofeel.py
calculate_excess_cold_factor ¶
calculate_excess_cold_factor(
ehi_sig: ArrayLike,
ehi_accl: ArrayLike,
clip: bool = False,
) -> np.ndarray
EXCF - Excess Cold Factor :param ehi_sig: (float array) significance index, e.g. relative to the 5th percentile of daily mean temperature over a reference period. :param ehi_accl: (float array) acclimatisation index. :param clip: (bool) clip the significance index at zero (default False). returns excess cold factor [input unit squared, e.g. K^2]
Top-level convenience wrapper for
thermofeel.excess_heat.excess_cold_factor. The supporting daily mean
temperature, significance and acclimatisation indices live in the
thermofeel.excess_heat submodule.
Reference: Nairn (2013), equation (7) https://www.cawcr.gov.au/technical-reports/CTR_060.pdf
Source code in thermofeel/thermofeel.py
calculate_mrt_from_bgt ¶
Mean radiant temperature from globe temperature :param t2_k: (float array) 2m temperature [K] :param bgt_k: (float array) globe temperature [K] :param va: (float array) wind speed at 10 meters [m/s] returns mean radiant temperature [K] Reference: Brimicombe et al. (2023) https://doi.org/10.1029/2022GH000701
Source code in thermofeel/thermofeel.py
calculate_humidex ¶
Humidex
:param t2_k: (float array) 2m temperature [K]
:param td_k: (float array) dew point temperature [K]
returns humidex [K]
Validity: Environment Canada's index; it is most meaningful in warm, humid conditions and is not reported by Environment Canada below ~20 degC.
Reference: Environment Canada https://climate.weather.gc.ca/glossary_e.html#humidex
Source code in thermofeel/thermofeel.py
calculate_discomfort_index ¶
Discomfort Index (Thom) :param t2_k: (float array) 2m temperature [K] :param rh: (float array) relative humidity [%] returns discomfort index [K]
Thom's Discomfort Index (also called the Temperature-Humidity Index) estimates heat discomfort from air temperature and relative humidity. This is the Celsius/relative-humidity formulation of Thom's index given by Giles et al. (1990), DI = T - 0.55 (1 - 0.01 RH)(T - 14.5), with T in degC and RH in %; at 100% RH the index equals the air temperature, and in warm conditions (above 14.5 degC) drier air lowers it below the air temperature. It is a warm-season heat-stress indicator and is not clamped - out-of-range inputs return the raw value (the caller masks).
Reference (this temperature/relative-humidity formulation): Giles et al. (1990) https://doi.org/10.1007/BF01093455 Index origin (Thom's Discomfort Index / Temperature-Humidity Index): Thom (1959) https://doi.org/10.1080/00431672.1959.9926960 See also (modern review and discomfort categories; a different wet-bulb formulation): Epstein and Moran (2006) https://doi.org/10.2486/indhealth.44.388
Source code in thermofeel/thermofeel.py
calculate_summer_simmer_index ¶
Summer Simmer Index (Pepi) :param t2_k: (float array) 2m temperature [K] :param rh: (float array) relative humidity [%] returns summer simmer index [K]
The Summer Simmer Index (SSI) estimates warm-season heat discomfort from air temperature and relative humidity. This is the common 1987 closed form, evaluated in Fahrenheit, SSI = 1.98 (Tf - (0.55 - 0.0055 RH)(Tf - 58)) - 56.83, with Tf the air temperature in degF and RH in %, the result converted back to Kelvin. The inner bracket is Thom's Temperature-Humidity Index in Fahrenheit, so the SSI is an affine image of that index (SSI_F = 1.98 THI_F - 56.83), the near-exact Fahrenheit sibling of calculate_discomfort_index (which uses 14.5 degC where 58 degF = 14.44... degC). It is a warm-season heat-stress indicator and is not clamped - out-of-range inputs return the raw value (the caller masks).
Reference: Pepi, J.W. (1987) The Summer Simmer Index, Weatherwise 40(3): 143-145 https://doi.org/10.1080/00431672.1987.9933356
Provenance caveat: the 1987 article is not openly available, so the equation above is reproduced from secondary sources. It is the common 1987 closed form - an affine transform of Thom's Fahrenheit Temperature-Humidity Index - NOT the author's later tabulated "New Summer Simmer Index", which is a different relationship and is not implemented here.
Source code in thermofeel/thermofeel.py
calculate_normal_effective_temperature ¶
calculate_normal_effective_temperature(
t2_k: ArrayLike, va: ArrayLike, rh: ArrayLike
) -> np.ndarray
NET - Normal Effective Temperature :param t2_k: (float array) 2m temperature [K] :param va: (float array) wind speed at 10 meters [m/s] :param rh: (float array) relative humidity percentage [%] returns normal effective temperature [K]
Validity: an empirical index derived for subtropical (Hong Kong) conditions (Li and Chan 2000); it has no sharply defined input range.
Reference: Li and Chan (2006) https://doi.org/10.1017/S1350482700001602
Source code in thermofeel/thermofeel.py
calculate_relative_strain_index ¶
Relative Strain Index (RSI) :param t2_k: (float array) 2m temperature [K] :param rh: (float array) relative humidity percentage [%] returns relative strain index [dimensionless]
The Relative Strain Index expresses warm-environment heat strain on a young,
healthy adult from air temperature and ambient water-vapour pressure. This is
the peer-reviewed hectopascal closed form stated with units by Asghari et al.
(2020), RSI = (Ta - 21) / (58 - e), with Ta the air temperature in degC and e
the ambient water-vapour pressure in hPa (from
calculate_nonsaturation_vapour_pressure). It is dimensionless and not
clamped - out-of-range inputs return the raw value (the caller masks).
Domain edge: the denominator 58 - e shrinks as e approaches 58 hPa (near
saturation around 35-36 degC), so RSI grows without bound (diverging to
+/-inf), and for e > 58 hPa (very hot and near-saturated, beyond the ~35 degC
validity range) the denominator turns negative and RSI becomes negative - a
spurious value for a heat-strain index. These out-of-domain elements are
returned raw and are deliberately not clamped; the caller masks inputs above
the validity range.
Variant caveat: a different literature form, (10.7 + 0.74 (Ta - 35)) / (44 - Pa), appears in secondary sources with Pa in other units (likely mmHg). It could not be verified against the primary text and is deliberately NOT implemented here; only this hPa closed form is provided.
Reference (this hPa closed form and the five-level assessment bands): Asghari et al. (2020) https://doi.org/10.2174/1874213002013010011 Index origin (Relative Strain Index): Lee and Henschel (1966) https://doi.org/10.1111/j.1749-6632.1966.tb43059.x
Source code in thermofeel/thermofeel.py
calculate_apparent_temperature ¶
Apparent Temperature - version without radiation :param t2_k: (float array) 2m temperature [K] :param va: (float array) wind speed at 10 meters [m/s] :param rh: (float array) relative humidity percentage [%] returns apparent temperature [K]
Validity: the Bureau of Meteorology non-radiation form of Steadman's apparent temperature; an empirical estimate without sharply defined input bounds.
Reference: Steadman (1984) https://doi.org/10.1175/1520-0450(1984)023%3C1674:AUSOAT%3E2.0.CO;2 See also: http://www.bom.gov.au/info/thermal_stress/#atapproximation
Source code in thermofeel/thermofeel.py
calculate_apparent_temperature_radiation ¶
calculate_apparent_temperature_radiation(
t2_k: ArrayLike,
va: ArrayLike,
rh: ArrayLike,
q: ArrayLike,
) -> np.ndarray
Apparent Temperature - version including radiation :param t2_k: (float array) 2m temperature [K] :param va: (float array) wind speed at 10 meters [m/s] :param rh: (float array) relative humidity percentage [%] :param q: (float array) net radiation absorbed per unit body-surface area [W m-2] returns apparent temperature [K]
The radiation-inclusive form of Steadman's apparent temperature, as
published operationally by the Australian Bureau of Meteorology:
AT = Ta + 0.348*e - 0.70*va + 0.70*q/(va + 10) - 4.25 (Ta in degC),
with e the ambient water-vapour pressure in hPa. It shares the vapour
path of calculate_apparent_temperature via
calculate_nonsaturation_vapour_pressure (whose constants match BoM's
e = (rh/100)*6.105*exp(17.27*Ta/(237.7+Ta))).
q is the net radiation absorbed per unit area of body surface. It is a
caller-supplied input (like cossza), NOT an NWP surface flux and NOT the
mean radiant temperature; callers must supply their own q.
Validity: an empirical estimate without sharply defined input bounds; the result is not clamped.
Reference: Steadman, R.G. (1994) Norms of apparent temperature in Australia, Aust. Met. Mag. 43:1-16. https://doi.org/10.1071/es94001 See also: http://www.bom.gov.au/info/thermal_stress/#atapproximation
Source code in thermofeel/thermofeel.py
calculate_wind_chill ¶
Wind Chill :param t2_k: (float array) 2m Temperature [K] :param va: (float array) wind speed at 10 meters [m/s] returns wind chill [K] Computation is only valid for temperatures between -50°C and 5°C and wind speeds between 5km/h and 80km/h. For input values outside those ranges, computed results not be considered valid. Reference: Blazejczyk et al. (2012) https://doi.org/10.1007/s00484-011-0453-2 See also: https://web.archive.org/web/20130627223738/http://climate.weatheroffice.gc.ca/prods_servs/normals_documentation_e.html # noqa
Source code in thermofeel/thermofeel.py
calculate_heat_index_simplified ¶
Heat Index :param t2_k: (float array) 2m temperature [K] :param rh: (float array) relative humidity [%] returns heat index [K]
The regression is applied only where air temperature exceeds 20 degC (below that the air temperature is returned unchanged). Inputs are array-like (wrap a scalar in an array), as the function masks elementwise internally.
Reference: Blazejczyk et al. (2012) https://doi.org/10.1007/s00484-011-0453-2
Source code in thermofeel/thermofeel.py
calculate_heat_index_adjusted ¶
Heat Index adjusted :param t2_k: (float array) 2m temperature [K] :param td_k: (float array) 2m dewpoint temperature [K] returns heat index [K] Reference: https://www.wpc.ncep.noaa.gov/html/heatindex_equation.shtml
Source code in thermofeel/thermofeel.py
calculate_pmv ¶
calculate_pmv(
t2_k: ArrayLike,
mrt_k: ArrayLike,
var: ArrayLike,
rh: ArrayLike | None = None,
vapour_pressure_hpa: ArrayLike | None = None,
met: ArrayLike = 1.2,
clo: ArrayLike = 0.5,
wme: ArrayLike = 0.0,
) -> np.ndarray
PMV - Predicted Mean Vote (Fanger's thermal comfort model)
:param t2_k: (float array) 2m air temperature [K]
:param mrt_k: (float array) mean radiant temperature [K]
:param var: (float array) relative air velocity at the body [m/s].
This is the air movement felt at the body (the still-air velocity
plus any movement the occupant's own activity induces), NOT the
10 m meteorological wind speed - do not pass the model wind field.
:param rh: (float array) relative humidity [%]. Provide exactly one of
rh or vapour_pressure_hpa.
:param vapour_pressure_hpa: (float array) water vapour pressure [hPa].
Provide exactly one of rh or vapour_pressure_hpa.
:param met: (float array) metabolic rate [met] (1 met = 58.15 W m-2);
default 1.2 met (ISO 8996 sedentary/office activity).
:param clo: (float array) clothing insulation [clo] (1 clo = 0.155
m2 K W-1); default 0.5 clo (light indoor clothing).
:param wme: (float array) external (mechanical) work [met]; default 0.0.
returns predicted mean vote [dimensionless]. The thermal-sensation
scale runs from -3 (cold) through 0 (neutral) to +3 (hot); the index is
not clamped, so inputs beyond the ISO 7730 validity range can yield
values outside +/-3 (ISO recommends interpreting PMV only within
about +/-2).
Fanger's steady-state heat-balance comfort equation as standardised in ISO 7730:2005. The clothing-surface temperature is obtained by the Annex D fixed-point iteration (tolerance 0.00015, capped at 150 sweeps). The whole array is iterated together (mirroring the Liljegren energy-balance solvers) and any element that has not converged within the cap is returned as NaN.
met and clo are exposed as parameters because PMV is only defined for
a stated activity and clothing level; the defaults (1.2 met, 0.5 clo) are
common office conventions rather than fixed constants of the standard.
Reference: ISO 7730:2005 (Annex D); Fanger, P.O. (1970) Thermal Comfort: Analysis and Applications in Environmental Engineering, McGraw-Hill.
Source code in thermofeel/thermofeel.py
1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266 1267 1268 1269 1270 1271 1272 1273 1274 1275 1276 1277 1278 1279 1280 1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291 1292 1293 1294 1295 1296 1297 1298 1299 1300 1301 1302 1303 1304 1305 1306 1307 1308 1309 1310 1311 1312 1313 1314 1315 1316 1317 1318 1319 1320 1321 1322 1323 1324 1325 1326 1327 1328 1329 1330 1331 1332 1333 1334 1335 1336 1337 1338 1339 1340 1341 1342 1343 1344 1345 1346 1347 1348 1349 1350 1351 1352 1353 1354 1355 1356 1357 1358 1359 1360 1361 1362 1363 1364 1365 1366 1367 1368 1369 1370 | |
calculate_ppd ¶
PPD - Predicted Percentage of Dissatisfied :param pmv: (float array) predicted mean vote [dimensionless] returns predicted percentage of dissatisfied [%]
ISO 7730:2005 / Fanger relation mapping the Predicted Mean Vote onto the
percentage of occupants likely to be thermally dissatisfied. It is symmetric
in PMV (warm and cold deviations of equal magnitude give the same PPD) with a
minimum of 5% at PMV = 0 (thermal neutrality). A NaN PMV (e.g. a non-converged
calculate_pmv element) propagates to NaN.
Reference: ISO 7730:2005 (Annex D); Fanger, P.O. (1970) Thermal Comfort: Analysis and Applications in Environmental Engineering, McGraw-Hill.
Source code in thermofeel/thermofeel.py
Excess heat and cold factors¶
The Excess Heat Factor and Excess Cold Factor are available at the top level as
thermofeel.calculate_excess_heat_factor(...) and
thermofeel.calculate_excess_cold_factor(...) (listed above). The
thermofeel.excess_heat submodule below additionally provides the supporting
daily mean temperature, significance and acclimatisation indices, and heatwave
severity.
daily_mean_temperature ¶
Daily mean temperature computed from min and max
:param t2_min: 2-metre daily minimum temperature.
:param t2_max: 2-metre daily maximum temperature.
Reference: Nairn and Fawcett (2014) https://doi.org/10.3390/ijerph120100227
Source code in thermofeel/excess_heat.py
significance_index ¶
Significance index
:param dmt: 3-day running mean daily mean temperature (day i, i+1, i+2).
:param threshold: climatological threshold, e.g., 95th (heat extremes)
or 5th (cold extremes) percentile of daily mean temperature over a
reference period.
Reference: Nairn and Fawcett (2014), equation (1) https://doi.org/10.3390/ijerph120100227
Source code in thermofeel/excess_heat.py
acclimatisation_index ¶
Acclimatisation index
:param dmt: 3-day running mean daily mean temperature (days i, i+1, i+2).
:param threshold: 30-day running mean daily mean temperature (days i-30, ..., i-1).
Reference: Nairn and Fawcett (2014), equation (2) https://doi.org/10.3390/ijerph120100227
Source code in thermofeel/excess_heat.py
excess_heat_factor ¶
Excess heat factor
:param ehi_sig: Significance index, e.g., computed with respect to 95th
percentile of daily mean temperature over a reference period.
:param ehi_accl: Acclimatisation index.
:param clip: Whether to clip the lower value range at zero. Disabled by default.
Reference: Nairn and Fawcett (2014), equation (3) https://doi.org/10.3390/ijerph120100227
Source code in thermofeel/excess_heat.py
heatwave_severity ¶
Heatwave severity index
:param exhf: Excess heat factor.
:param threshold: Excess heat factor threshold, the 85th percentile
of all positive excess heat factor values in a reference period.
Reference: Nairn (2018), equation (4) https://doi.org/10.3390/ijerph15112494
Source code in thermofeel/excess_heat.py
excess_cold_factor ¶
Excess cold factor
:param ehi_sig: Significance index, e.g., computed with respect to 5th
percentile of daily mean temperature over a reference period..
:param ehi_accl: Acclimatisation index.
:param clip: Whether to clip the upper value range at zero. Disabled by default.
Reference: Nairn (2013), equation (7) https://www.cawcr.gov.au/technical-reports/CTR_060.pdf
Source code in thermofeel/excess_heat.py
Approximations¶
The thermofeel.approximations submodule provides clearly-labelled estimators
(not validated index formulas), kept out of the top-level namespace so calling
code always shows the .approximations. marker. It currently estimates the
direct solar radiation fdir (needed for the mean radiant temperature, and hence
UTCI / WBGT / PMV) from the global horizontal radiation ssrd and solar geometry,
for datasets that lack fdir. These are demonstration/estimation grade — see the
Approximations guide.
Approximations used to estimate an input that a thermal index needs but that a dataset does not provide.
These are clearly-labelled empirical estimates, deliberately kept separate
from the validated index formulas in thermofeel.thermofeel and NOT
re-exported at the top level: calling code carries the .approximations.
marker so it is always visible that an approximation is in play. Their outputs
are demonstration / estimation grade, not observations; use a dataset that
provides the real field for quantitative work.
Currently provides estimators of the direct (beam) solar radiation on a
horizontal surface -- ECMWF fdir -- from the global horizontal radiation
ssrd and the solar geometry, for datasets (e.g. ECMWF open data) that carry
ssrd but not fdir. fdir is required by
calculate_mean_radiant_temperature and hence by UTCI / WBGT / PMV. The
direct/diffuse split cannot be reconstructed exactly from ssrd (two skies
with the same ssrd can have very different beam fractions), so these use an
empirical decomposition driven by the clearness index; expect errors of order
0.1-0.25 of the global radiation, larger in broken cloud and at low sun.
References¶
- Erbs, D.G., Klein, S.A. & Duffie, J.A. (1982). Estimation of the diffuse radiation fraction for hourly, daily and monthly-average global radiation. Solar Energy 28(4), 293-302. https://doi.org/10.1016/0038-092X(82)90302-4
- Maxwell, E.L. (1987). A Quasi-Physical Model for Converting Hourly Global Horizontal to Direct Normal Insolation. SERI/TR-215-3087, Solar Energy Research Institute, Golden, CO.
- Spencer, J.W. (1971). Fourier series representation of the position of the Sun. Search 2(5), 172. (Earth-Sun distance factor.)
- Kasten, F. (1966). A new table and approximation formula for the relative optical air mass. Arch. Meteorol. Geophys. Bioklimatol. B 14, 206-223.
earth_sun_distance_factor ¶
Earth-Sun distance (eccentricity) factor (mean-distance normal irradiance scaling), via the Spencer (1971) Fourier series.
:param doy: (float array) day of the year (1..365/366)
returns the dimensionless factor (a0/a)^2, ranging ~0.967 (aphelion,
early July) to ~1.035 (perihelion, early January)
Multiply the solar constant by this factor to obtain the actual extraterrestrial normal irradiance for the day.
Reference: Spencer (1971); see also Iqbal, An Introduction to Solar Radiation (1983).
Source code in thermofeel/approximations.py
approximate_fdir_erbs ¶
approximate_fdir_erbs(
ssrd: ArrayLike,
cossza: ArrayLike,
*,
doy: ArrayLike | None = None,
solar_constant: float = SOLAR_CONSTANT,
min_cossza: float = 0.065
) -> np.ndarray
Estimate direct horizontal solar radiation (ECMWF fdir) from global
horizontal ssrd and cos(solar zenith angle) via the Erbs et al. (1982)
diffuse-fraction decomposition.
:param ssrd: (float array) surface global horizontal solar radiation
(ECMWF ssrd), a mean flux [W m-2] (or any energy unit; the output
follows the input unit)
:param cossza: (float array) cosine of the solar zenith angle
:param doy: (optional, float array) day of the year; if given, the
extraterrestrial irradiance is corrected for the Earth-Sun distance
(``earth_sun_distance_factor``); if omitted the mean-distance solar
constant is used
:param solar_constant: (float) solar constant [W m-2] (default 1361)
:param min_cossza: (float) below this cos(zenith) the Sun is treated as
below the horizon; ``fdir`` returns 0 there (default 0.065)
returns the estimated direct horizontal radiation, in the same unit as
``ssrd``, clipped to [0, ssrd] (and to 0 where ``ssrd`` is negative)
The Erbs correlation splits global horizontal radiation into its direct and
diffuse parts from the clearness index kt = ssrd / TOA. It is an
empirical hourly fit; applying it to longer step-mean fluxes is a further
approximation. NaN inputs propagate to NaN. Estimation grade only -- use a
real fdir field for quantitative work.
Reference: Erbs, Klein & Duffie (1982), Solar Energy 28(4):293-302, https://doi.org/10.1016/0038-092X(82)90302-4
Source code in thermofeel/approximations.py
approximate_fdir_disc ¶
approximate_fdir_disc(
ssrd: ArrayLike,
cossza: ArrayLike,
doy: ArrayLike,
*,
pressure_hpa: ArrayLike = 1013.25,
solar_constant: float = SOLAR_CONSTANT,
min_cossza: float = 0.065,
max_airmass: float = 12.0
) -> np.ndarray
Estimate direct horizontal solar radiation (ECMWF fdir) from global
horizontal ssrd and the solar geometry via the DISC model (Maxwell,
1987) -- a quasi-physical direct-normal estimator that generally improves on
the simpler Erbs split.
:param ssrd: (float array) surface global horizontal solar radiation
(ECMWF ssrd), a mean flux [W m-2]
:param cossza: (float array) cosine of the solar zenith angle
:param doy: (float array) day of the year (required; sets the
Earth-Sun distance correction of the extraterrestrial irradiance)
:param pressure_hpa: (float array) surface pressure [hPa] for the
pressure-corrected air mass (default 1013.25)
:param solar_constant: (float) solar constant [W m-2] (default 1361).
Maxwell derived the DISC coefficients with a solar constant of
~1367-1370 W m-2, so pass ``solar_constant=1370`` to reproduce the
original model (and pvlib) exactly.
:param min_cossza: (float) below this cos(zenith) the Sun is treated as
below the horizon; ``fdir`` returns 0 there (default 0.065)
:param max_airmass: (float) air mass is capped at this value in the Kn
fit, per the original model (default 12)
returns the estimated direct horizontal radiation [W m-2], clipped to
[0, ssrd] (and to 0 where ``ssrd`` is negative)
DISC converts the global clearness index to a direct-beam clearness index
Kn using empirical polynomials in kt and the (pressure-corrected
Kasten 1966) air mass, then dni = Kn * I0 and fdir = dni * cossza.
NaN inputs propagate to NaN. Estimation grade only.
Reference: Maxwell, E.L. (1987), A Quasi-Physical Model for Converting Hourly
Global Horizontal to Direct Normal Insolation, SERI/TR-215-3087. The
coefficients are those of the DISC model as implemented in pvlib
(pvlib.irradiance.disc).
Source code in thermofeel/approximations.py
147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 | |