from typing import Literal
import numpy as np
from pythermalcomfort.classes_input import ASHRAEInputs
from pythermalcomfort.classes_return import AdaptiveASHRAE
from pythermalcomfort.shared_functions import valid_range
from pythermalcomfort.utilities import (
Units,
_check_ashrae55_compliance,
operative_tmp,
units_converter,
)
[docs]
def adaptive_ashrae(
tdb: float | list[float],
tr: float | list[float],
t_running_mean: float | list[float],
v: float | list[float],
units: Literal["SI", "IP"] = Units.SI.value,
limit_inputs: bool = True,
round_output: bool = True,
) -> AdaptiveASHRAE:
"""Calculate the adaptive thermal comfort based on ASHRAE 55.
The adaptive
model relates indoor design temperatures or acceptable temperature ranges
to outdoor meteorological or climatological parameters. The adaptive model
can only be used in occupant-controlled naturally conditioned spaces that
meet all the following criteria:
* There is no mechanical cooling or heating system in operation.
* Occupants have a metabolic rate between 1.0 and 1.5 met.
* Occupants are free to adapt their clothing within a range as wide as 0.5 and 1.0 clo.
* The prevailing mean (running mean) outdoor temperature is between 10 and 33.5 °C.
Parameters
----------
tdb : float or list of floats
Dry bulb air temperature, default in [°C] or [°F] if `units` = 'IP'.
tr : float or list of floats
Mean radiant temperature, default in [°C] or [°F] if `units` = 'IP'.
t_running_mean : float or list of floats
Running mean temperature, default in [°C] or [°F] if `units` = 'IP'.
.. note::
The running mean temperature can be calculated using the function :py:meth:`pythermalcomfort.utilities.running_mean_outdoor_temperature`.
v : float or list of floats
Air speed, default in [m/s] or [fps] if `units` = 'IP'.
units : str, optional
Units system, 'SI' or 'IP'. Defaults to 'SI'.
limit_inputs : bool, optional
If True, returns nan for inputs outside standard limits. Defaults to True.
.. note::
ASHRAE 55 2020 limits: 10 < tdb [°C] < 40, 10 < tr [°C] < 40, 0 < vr [m/s] < 2, 10 < t_running_mean [°C] < 33.5.
round_output : bool, optional
If True, rounds ``t_cmf`` to one decimal place in SI before the comfort bounds are
derived, so ``tmp_cmf_80_low``, ``tmp_cmf_80_up``, ``tmp_cmf_90_low`` and
``tmp_cmf_90_up`` inherit that rounding. If False, ``t_cmf`` and the derived bounds
are returned at full precision. Under ``units='IP'`` the rounded SI value is then
converted to °F, so IP outputs carry the extra decimals from the °C-to-°F conversion.
Defaults to True.
Returns
-------
AdaptiveASHRAE
A dataclass containing the results. See :py:class:`~pythermalcomfort.classes_return.AdaptiveASHRAE` for more details.
Examples
--------
.. code-block:: python
from pythermalcomfort.models import adaptive_ashrae
results = adaptive_ashrae(tdb=25, tr=25, t_running_mean=20, v=0.1)
print(results)
# AdaptiveASHRAE(tmp_cmf=np.float64(24.0), tmp_cmf_80_low=np.float64(20.5), tmp_cmf_80_up=np.float64(27.5), tmp_cmf_90_low=np.float64(21.5), tmp_cmf_90_up=np.float64(26.5), acceptability_80=array(True), acceptability_90=array(True))
print(results.acceptability_80) # or use print(results["acceptability_80"])
# True
# The conditions you entered are considered to be comfortable for 80% of the occupants.
# You can also pass arrays as input to the function
results = adaptive_ashrae(tdb=[25, 26], tr=25, t_running_mean=20, v=0.1)
print(results)
# AdaptiveASHRAE(tmp_cmf=array([24., 24.]), tmp_cmf_80_low=array([20.5, 20.5]), tmp_cmf_80_up=array([27.5, 27.5]), tmp_cmf_90_low=array([21.5, 21.5]), tmp_cmf_90_up=array([26.5, 26.5]), acceptability_80=array([ True, True]), acceptability_90=array([ True, True]))
# For users who want to use the IP system
results = adaptive_ashrae(tdb=77, tr=77, t_running_mean=68, v=0.3, units="IP")
print(results)
# AdaptiveASHRAE(tmp_cmf=np.float64(75.2), tmp_cmf_80_low=np.float64(68.9), tmp_cmf_80_up=np.float64(81.5), tmp_cmf_90_low=np.float64(70.7), tmp_cmf_90_up=np.float64(79.7), acceptability_80=array(True), acceptability_90=array(True))
adaptive_ashrae(tdb=25, tr=25, t_running_mean=9, v=0.1)
# AdaptiveASHRAE(tmp_cmf=np.float64(nan), ... acceptability_90=array(False))
# The adaptive thermal comfort model can only be used if the running mean temperature is higher than 10°C.
"""
# Validate inputs using the ASHRAEInputs class
ASHRAEInputs(
tdb=tdb,
tr=tr,
t_running_mean=t_running_mean,
v=v,
units=units,
round_output=round_output,
)
tdb = np.asarray(tdb)
tr = np.asarray(tr)
t_running_mean = np.asarray(t_running_mean)
v = np.asarray(v)
standard = "ashrae"
if units.upper() == Units.IP.value:
tdb, tr, t_running_mean, v = units_converter(
tdb=tdb,
tr=tr,
tmp_running_mean=t_running_mean,
v=v,
)
to = operative_tmp(tdb, tr, v, standard=standard)
# Calculate cooling effect (ce) of elevated air speed when top > 25 degC.
ce = np.where((v >= 0.6) & (to >= 25.0), 999, 0)
ce = np.where((v < 0.9) & (ce == 999), 1.2, ce)
ce = np.where((v < 1.2) & (ce == 999), 1.8, ce)
ce = np.where(ce == 999, 2.2, ce)
# Relation between comfort and outdoor temperature
t_cmf = 0.31 * t_running_mean + 17.8
if limit_inputs:
tdb_valid, tr_valid, v_valid = _check_ashrae55_compliance(
tdb=tdb,
tr=tr,
v=v,
)
trm_valid = valid_range(t_running_mean, (10.0, 33.5))
all_valid = ~(
np.isnan(tdb_valid)
| np.isnan(tr_valid)
| np.isnan(v_valid)
| np.isnan(trm_valid)
)
t_cmf = np.where(all_valid, t_cmf, np.nan)
if round_output:
t_cmf = np.around(t_cmf, 1)
tmp_cmf_80_low = t_cmf - 3.5
tmp_cmf_90_low = t_cmf - 2.5
tmp_cmf_80_up = t_cmf + 3.5 + ce
tmp_cmf_90_up = t_cmf + 2.5 + ce
acceptability_80 = (tmp_cmf_80_low <= to) & (to <= tmp_cmf_80_up)
acceptability_90 = (tmp_cmf_90_low <= to) & (to <= tmp_cmf_90_up)
if units.upper() == Units.IP.value:
(
t_cmf,
tmp_cmf_80_low,
tmp_cmf_80_up,
tmp_cmf_90_low,
tmp_cmf_90_up,
) = units_converter(
from_units=Units.SI.value,
tmp_cmf=t_cmf,
tmp_cmf_80_low=tmp_cmf_80_low,
tmp_cmf_80_up=tmp_cmf_80_up,
tmp_cmf_90_low=tmp_cmf_90_low,
tmp_cmf_90_up=tmp_cmf_90_up,
)
return AdaptiveASHRAE(
tmp_cmf=t_cmf,
tmp_cmf_80_low=tmp_cmf_80_low,
tmp_cmf_80_up=tmp_cmf_80_up,
tmp_cmf_90_low=tmp_cmf_90_low,
tmp_cmf_90_up=tmp_cmf_90_up,
acceptability_80=acceptability_80,
acceptability_90=acceptability_90,
)