Source code for ladybug_comfort.collection.adaptive

# coding=utf-8
"""Object for calculating Adaptive comfort from DataCollections."""
from __future__ import division

from ..adaptive import adaptive_comfort_ashrae55, adaptive_comfort_en15251, \
    adaptive_comfort_conditioned_function, cooling_effect_ashrae55, \
    cooling_effect_en16798, cooling_effect_en15251, t_operative, \
    weighted_running_mean_hourly, weighted_running_mean_daily
from ..parameter.adaptive import AdaptiveParameter
from .base import ComfortCollection

from ladybug._datacollectionbase import BaseCollection
from ladybug.datacollection import HourlyContinuousCollection, DailyCollection, \
    MonthlyCollection, MonthlyPerHourCollection, HourlyDiscontinuousCollection
from ladybug.analysisperiod import AnalysisPeriod

from ladybug.datatype.temperature import Temperature, OperativeTemperature, \
    PrevailingOutdoorTemperature
from ladybug.datatype.speed import Speed, AirSpeed
from ladybug.datatype.thermalcondition import ThermalComfort, ThermalCondition
from ladybug.datatype.temperaturedelta import OperativeTemperatureDelta


[docs]class Adaptive(ComfortCollection): """Adaptive comfort DataCollection object. Args: outdoor_temperature: Either one of the following inputs are acceptable: * A Data Collection of prevailing outdoor temperature values in C. Such a Data Collection must align with the operative_temperature input and bear the PrevailingOutdoorTemperature data type in its header. * A single prevailing outdoor temperature value in C to be used for all of the operative_temperature inputs below. * A Data Collection of actual outdoor temperatures recorded over the entire year. This Data Collection must be continuous and must either be an Hourly Collection or Daily Collection. In the event that the input comfort_parameter has a prevailing_temperature_method of 'Monthly', Monthly collections are also acceptable here. Note that, because an annual input is required, this input collection does not have to align with the operative_temperature input. operative_temperature: Data Collection of operative temperature (To) values in degrees Celsius. air_speed: Data Collection of air speed values in m/s or a single air_speed value to be used for the whole analysis. If None, this will default to 0.1 m/s. comfort_parameter: Optional AdaptiveParameter object to specify parameters under which conditions are considered acceptable. If None, default will assume ASHRAE-55 criteria. Properties: * prevailing_outdoor_temperature * operative_temperature * air_speed * comfort_parameter * neutral_temperature * degrees_from_neutral * is_comfortable * thermal_condition * cooling_effect * percent_comfortable * percent_uncomfortable * percent_neutral * percent_hot * percent_cold """ _model = 'Adaptive' __slots__ = ('_op_temp', '_air_speed', '_comfort_par', '_t_out', '_prevail_temp', '_neutral_temperature', '_degrees_from_neutral', '_is_comfortable', '_thermal_condition', '_cooling_effect', '_is_comfortable_coll', '_thermal_condition_coll', '_op_temp_coll', '_air_speed_coll', '_comfort_par_coll', '_t_out_coll', '_prevail_temp_coll', '_neutral_temperature_coll', '_degrees_from_neutral_coll', '_cooling_effect_coll') def __init__(self, outdoor_temperature, operative_temperature, air_speed=None, comfort_parameter=None): """Initialize an Adaptive comfort object from DataCollections of inputs. """ # set up the object using operative temperature as a base self._check_datacoll(operative_temperature, Temperature, 'C', 'operative_temperature') self._input_collections = [operative_temperature] self._calc_length = len(operative_temperature.values) self._base_collection = operative_temperature # check model inputs self._op_temp = operative_temperature.values if air_speed is not None: self._air_speed = self._check_input(air_speed, Speed, 'm/s', 'air_speed') else: self._air_speed = [0.1] * self.calc_length # check comfort parameters if comfort_parameter is None: self._comfort_par = AdaptiveParameter() else: assert isinstance(comfort_parameter, AdaptiveParameter), \ 'comfort_parameter must be an AdaptiveParameter object. '\ 'Got {}'.format(type(comfort_parameter)) self._comfort_par = comfort_parameter # check outdoor_temperature self._t_out = outdoor_temperature if isinstance(self._t_out, BaseCollection) and not \ isinstance(self._t_out.header.data_type, PrevailingOutdoorTemperature): # it is a data collection of actual recorded outdoor temperatures prev_obj = PrevailingTemperature( self._t_out, self._comfort_par.avg_month_or_running_mean) prevail_collection = prev_obj.get_aligned_prevailing(self._base_collection) self._prevail_temp = prevail_collection.values else: # it is either a data collection or single value of prevailing temperature self._prevail_temp = self._check_input( self._t_out, PrevailingOutdoorTemperature, 'C', 'outdoor_temperature') # calculate Adaptive comfort self._calculate_adaptive()
[docs] @classmethod def from_air_and_rad_temp(cls, outdoor_temperature, air_temperature, rad_temperature=None, air_speed=None, comfort_parameter=None): """Initialize an Adaptive Comfort object from air and radiant temperature.""" if rad_temperature is None: to = air_temperature else: to = BaseCollection.compute_function_aligned( t_operative, [air_temperature, rad_temperature], OperativeTemperature(), 'C') return cls(outdoor_temperature, to, air_speed, comfort_parameter)
def _calculate_adaptive(self): """Compute Adaptive comfort for each step of the Data Collection.""" # empty properties to be calculated self._neutral_temperature = [] self._degrees_from_neutral = [] self._is_comfortable = [] self._thermal_condition = [] self._cooling_effect = [] # determine the comfort function to use if self._comfort_par.conditioning != 0: comf_funct = adaptive_comfort_conditioned_function( self._comfort_par.conditioning, self._comfort_par.standard) elif self._comfort_par.ashrae_or_en: comf_funct = adaptive_comfort_ashrae55 else: comf_funct = adaptive_comfort_en15251 # determine the cooling effect function to use if not self._comfort_par.discrete_or_continuous_air_speed: cooling_funct = cooling_effect_en15251 elif self._comfort_par.ashrae_or_en: cooling_funct = cooling_effect_ashrae55 else: cooling_funct = cooling_effect_en16798 # perform the Adaptive calculation for tp, to, vel in zip(self._prevail_temp, self._op_temp, self._air_speed): result = comf_funct(tp, to) ce = cooling_funct(vel, to, tp) comf = self._comfort_par.is_comfortable(result, ce) if comf == 0: condit = 1 if result['deg_comf'] > 0 else -1 else: condit = 0 self._neutral_temperature.append(result['t_comf']) self._degrees_from_neutral.append(result['deg_comf']) self._is_comfortable.append(comf) self._thermal_condition.append(condit) self._cooling_effect.append(ce) @property def prevailing_outdoor_temperature(self): """Data Collection of prevailing outdoor temperature in degrees C.""" return self._get_coll('_prevail_temp_coll', self._prevail_temp, PrevailingOutdoorTemperature, 'C') @property def operative_temperature(self): """Data Collection of operative temperature in degrees C.""" return self._get_coll('_op_temp_coll', self._op_temp, OperativeTemperature, 'C') @property def air_speed(self): """Data Collection of air speed in m/s.""" return self._get_coll('_air_speed_coll', self._air_speed, AirSpeed, 'm/s') @property def comfort_parameter(self): """Adaptive comfort parameters that are assigned to this object.""" return self._comfort_par.duplicate() # duplicate since neutral_offset is setable @property def neutral_temperature(self): """Data Collection of the desired neutral temperature in degrees C.""" return self._get_coll('_neutral_temperature_coll', self._neutral_temperature, OperativeTemperature, 'C') @property def degrees_from_neutral(self): """Data Collection of the degrees from desired neutral temperature in C.""" return self._get_coll('_degrees_from_neutral_coll', self._degrees_from_neutral, OperativeTemperatureDelta, 'dC') @property def is_comfortable(self): """Data Collection of integers noting whether the input conditions are acceptable according to the assigned comfort_parameter. Values are one of the following: * 0 = uncomfortable * 1 = comfortable """ return self._get_coll('_is_comfortable_coll', self._is_comfortable, ThermalComfort, 'condition') @property def thermal_condition(self): """Data Collection of integers noting the thermal status of a subject according to the assigned comfort_parameter. Values are one of the following: * -1 = cold * 0 = netural * +1 = hot """ return self._get_coll('_thermal_condition_coll', self._thermal_condition, ThermalCondition, 'condition') @property def cooling_effect(self): """Data Collection of the cooling effect of the air speed in degrees Celsius. This is the difference between the air temperature and the adjusted air temperature [C]. """ return self._get_coll('_cooling_effect_coll', self._cooling_effect, OperativeTemperatureDelta, 'dC') @property def percent_comfortable(self): """The percent of time comfortable given by the assigned comfort_parameter.""" return (sum(self._is_comfortable) / self._calc_length) * 100 @property def percent_uncomfortable(self): """The percent of time uncomfortable given by the assigned comfort_parameter.""" return 100 - self.percent_comfortable @property def percent_neutral(self): """The percent of time that the thermal_condition is neutral.""" _vals = [1 for x in self._thermal_condition if x == 0] return (sum(_vals) / self._calc_length) * 100 @property def percent_cold(self): """The percent of time that the thermal_condition is cold.""" _vals = [1 for x in self._thermal_condition if x == -1] return (sum(_vals) / self._calc_length) * 100 @property def percent_hot(self): """The percent of time that the thermal_condition is hot.""" _vals = [1 for x in self._thermal_condition if x == 1] return (sum(_vals) / self._calc_length) * 100
[docs]class PrevailingTemperature(object): """Get prevailing temperature from annual DataCollections of outdoor temperature. Args: outdoor_temperature: A Data Collection of outdoor temperatures recorded over an entire year. This Data Collection must be continuous and must either be an Hourly Collection or Daily Collection. In the event that the input avg_month is True (for average monthly prevailing method), Monthly collections are also acceptable here. avg_month: A boolean to note whether the prevailing outdoor temperature is computed from the average monthly temperature (True) or a weighted running mean of the last week (False). The default is True. Properties: * avg_month_or_running_mean * hourly_prevailing_temperature * daily_prevailing_temperature * monthly_prevailing_temperature * monthly_per_hour_prevailing_temperature """ __slots__ = ('_t_out', '_head', '_avg_month', '_hourly_prevail', '_daily_prevail', '_monthly_prevail') def __init__(self, outdoor_temperature, avg_month=True): """Initialize an prevailing temperature object from DataCollections of inputs. """ # perform checks on the inputs acceptabe = (HourlyContinuousCollection, DailyCollection, MonthlyCollection) assert isinstance(outdoor_temperature, acceptabe), \ 'outdoor_temperature must be one of the following: {}.\n Got {}'.format( acceptabe, type(outdoor_temperature)) self._t_out = outdoor_temperature.duplicate() self._head = self._t_out.header self._avg_month = avg_month assert isinstance(self._head.data_type, Temperature) and self._head.unit == 'C',\ 'outdoor_temperature must be Temperature in C. Got {} in {}'.format( self._head.data_type, self._head.unit) assert self._t_out.is_continuous, 'outdoor_temperature must be continuous.' assert self._head.analysis_period.is_annual, 'outdoor_temperature must be annual' assert isinstance(self._avg_month, bool), 'avg_month' \ ' must be a boolean. Got {}.'.format(type(self._avg_month)) # defaults to be calculated self._hourly_prevail = [] self._daily_prevail = [] self._monthly_prevail = [] # calculate the base data of prevailing temperature if self._avg_month is True: if isinstance(self._t_out, (HourlyContinuousCollection, DailyCollection)): self._monthly_prevail = self._t_out.average_monthly().values elif isinstance(self._t_out, MonthlyCollection): self._monthly_prevail = self._t_out.values else: if isinstance(self._t_out, HourlyContinuousCollection): self._hourly_prevail = weighted_running_mean_hourly(self._t_out.values) elif isinstance(self._t_out, DailyCollection): self._daily_prevail = weighted_running_mean_daily(self._t_out.values) for val in self._daily_prevail: self._hourly_prevail.extend([val] * 24) else: raise TypeError('outdoor_temperature must be hourly or daily when ' 'avg_month is False.') @property def avg_month(self): """The input avg_month.""" return self._avg_month @property def hourly_prevailing_temperature(self): """HourlyContinuousCollection of prevailing outdoor temperature in C.""" if self._hourly_prevail == []: self._hourly_prevail_from_monthly() return HourlyContinuousCollection(self._get_header(), self._hourly_prevail) @property def daily_prevailing_temperature(self): """DailyCollection of prevailing outdoor temperature in C.""" if self._daily_prevail == []: if self._avg_month is True: self._daily_prevail_from_monthly() else: self._daily_prevail_from_hourly() return DailyCollection(self._get_header(), self._daily_prevail, self._head.analysis_period.doys_int) @property def monthly_prevailing_temperature(self): """MonthlyCollection of prevailing outdoor temperature in C.""" if self._monthly_prevail == []: return self.hourly_prevailing_temperature.average_monthly() else: return MonthlyCollection(self._get_header(), self._monthly_prevail, self._head.analysis_period.months_int) @property def monthly_per_hour_prevailing_temperature(self): """MonthlyPerHourCollection of prevailing outdoor temperature in C.""" mon_per_hr_vals = [] for val in self.monthly_prevailing_temperature: mon_per_hr_vals.extend([val] * 24) return MonthlyPerHourCollection(self._get_header(), mon_per_hr_vals, self._head.analysis_period.months_per_hour)
[docs] def hourly_prevailing_temperature_timestep(self, timestep): """HourlyContinuousCollection of prevailing temperature at timestep.""" if timestep != 1: hourly_coll = self.hourly_prevailing_temperature _new_values = [] for val in hourly_coll.values: _new_values.extend([val] * timestep) a_per = hourly_coll.header.analysis_period _new_a_per = AnalysisPeriod(a_per.st_month, a_per.st_day, a_per.st_hour, a_per.end_month, a_per.end_day, a_per.end_hour, timestep, a_per.is_leap_year) _new_header = hourly_coll.header.duplicate() _new_header._analysis_period = _new_a_per return HourlyContinuousCollection(_new_header, _new_values) return self.hourly_prevailing_temperature
[docs] def get_aligned_prevailing(self, collection): """"Get a Prevailing Temperature Collection aligned with input collection.""" if isinstance(collection, HourlyContinuousCollection): new_coll = self.hourly_prevailing_temperature_timestep( collection.header.analysis_period.timestep) if not collection.header.analysis_period.is_annual: new_coll = new_coll.filter_by_analysis_period( collection.header.analysis_period) return new_coll new_coll = collection.get_aligned_collection( data_type=PrevailingOutdoorTemperature(), unit='C') if isinstance(collection, HourlyDiscontinuousCollection): prevail_val_dict = self.hourly_prevailing_temperature_timestep( collection.header.analysis_period.timestep).moys_dict for i, datetime in enumerate(new_coll.datetimes): new_coll[i] = prevail_val_dict[datetime.moy] elif isinstance(collection, DailyCollection): daily_temps = self.daily_prevailing_temperature for i, datetime in enumerate(new_coll.datetimes): new_coll[i] = daily_temps[datetime - 1] elif isinstance(collection, MonthlyCollection): monthly_temps = self.monthly_prevailing_temperature for i, datetime in enumerate(new_coll.datetimes): new_coll[i] = monthly_temps[datetime - 1] elif isinstance(collection, MonthlyPerHourCollection): mon_per_hr = self.monthly_per_hour_prevailing_temperature monthly_per_hour_dict = {} for val, dt in zip(mon_per_hr.values, mon_per_hr.datetimes): monthly_per_hour_dict[dt] = val for i, datetime in enumerate(new_coll.datetimes): new_coll[i] = monthly_per_hour_dict[datetime] return new_coll
def _hourly_prevail_from_monthly(self): for i, days in enumerate(self._head.analysis_period._num_of_days_each_month): self._hourly_prevail.extend([self._monthly_prevail[i]] * days * 24) def _daily_prevail_from_monthly(self): for i, days in enumerate(self._head.analysis_period._num_of_days_each_month): self._daily_prevail.extend([self._monthly_prevail[i]] * days) def _daily_prevail_from_hourly(self): for i in range(0, len(self._hourly_prevail), 24): self._daily_prevail.append(self._hourly_prevail[i]) def _get_header(self): new_header = self._head.duplicate() new_header._data_type = PrevailingOutdoorTemperature() return new_header