Source code for honeybee_energy.construction.dynamic

# coding=utf-8
"""Window Construction with any number of dynamic states."""
from __future__ import division

import re

from honeybee._lockable import lockable
from honeybee.typing import valid_ep_string

from .window import WindowConstruction
from ..material.glazing import EnergyWindowMaterialSimpleGlazSys
from ..schedule.dictutil import dict_to_schedule
from ..schedule.ruleset import ScheduleRuleset
from ..schedule.fixedinterval import ScheduleFixedInterval
from ..writer import generate_idf_string


[docs] @lockable class WindowConstructionDynamic(object): """Window Construction with any number of dynamic states. Args: identifier: Text string for a unique Construction ID. Must be < 100 characters and not contain any EnergyPlus special characters. This will be used to identify the object across a model and in the exported IDF. constructions: A list of WindowConstruction objects that define the various states that the dynamic window can assume. schedule: A ScheduleRuleset or ScheduleFixedInterval composed of integers that correspond to the indices of the constructions that are active at given times throughout the simulation. Properties: * identifier * display_name * constructions * schedule * materials * layers * unique_materials * frame * r_value * u_value * u_factor * r_factor * is_symmetric * has_frame * has_shade * is_dynamic * inside_emissivity * outside_emissivity * solar_transmittance * visible_transmittance * shgc * thickness * glazing_count * gap_count * user_data """ __slots__ = ('_identifier', '_display_name', '_constructions', '_schedule', '_locked', '_user_data') def __init__(self, identifier, constructions, schedule): """Initialize dynamic window construction.""" self._locked = False # unlocked by default self.identifier = identifier self._display_name = None self.constructions = constructions self.schedule = schedule self._user_data = None @property def identifier(self): """Get or set the text string for construction identifier.""" return self._identifier @identifier.setter def identifier(self, identifier): self._identifier = valid_ep_string(identifier, 'construction identifier') @property def display_name(self): """Get or set a string for the object name without any character restrictions. If not set, this will be equal to the identifier. """ if self._display_name is None: return self._identifier return self._display_name @display_name.setter def display_name(self, value): if value is not None: try: value = str(value) except UnicodeEncodeError: # Python 2 machine lacking the character set pass # keep it as unicode self._display_name = value @property def constructions(self): """Get or set a list of WindowConstructions that define the dynamic states.""" return self._constructions @constructions.setter def constructions(self, cons): try: if not isinstance(cons, tuple): cons = tuple(cons) except TypeError: raise TypeError('Expected list or tuple for WindowConstructionDynamic ' 'constructions. Got {}'.format(type(cons))) for construct in cons: assert isinstance(construct, WindowConstruction), \ 'Expected WindowConstruction for WindowConstructionDynamic. ' \ 'Got {}.'.format(type(construct)) assert len(cons) > 1, 'There must be at least two constructions ' \ 'for a WindowConstructionDynamic.' self._constructions = cons @property def schedule(self): """Get or set a control schedule for the constructions active at given time. The values of the schedule should be integers and range from 0 to one less then the number of constructions. Zero indicates that the first construction is active, one indicates that the second on is active, etc. The schedule type limits of this schedule should be "Discrete" and the unit type should be "Mode," "Control," or some other fractional unit. """ return self._schedule @schedule.setter def schedule(self, value): assert isinstance(value, (ScheduleRuleset, ScheduleFixedInterval)), \ 'Expected schedule for window construction shaded schedule. ' \ 'Got {}.'.format(type(value)) if value.schedule_type_limit is not None: assert value.schedule_type_limit.numeric_type == 'Discrete', 'Dynamic ' \ 'window construction schedule should have a Discrete type limit. ' \ 'Got a schedule of numeric_type [{}].'.format( value.schedule_type_limit.numeric_type) assert value.schedule_type_limit.unit == 'fraction', 'Dynamic window ' \ 'construction schedule should have Mode or Control unit types. ' \ 'Got a schedule of unit_type [{}].'.format( value.schedule_type_limit.unit_type) value.lock() # lock editing in case schedule has multiple references self._schedule = value @property def materials(self): """Get a list of materials for the constituent constructions. Materials will be listed from outside to inside and will move from the first construction to the last. """ return [m for con in self._constructions for m in con.materials] @property def layers(self): """Get a list of material identifiers for the constituent constructions. Materials will be listed from outside to inside and will move from the first construction to the last. """ return [mat.identifier for mat in self.materials] @property def unique_materials(self): """A list of only unique material objects in the construction. This will include the materials across all dynamic states of the construction. """ return list(set([m for con in self._constructions for m in con.materials])) @property def frame(self): """Get a window frame for the frame material surrounding the construction.""" return self._constructions[0].frame @property def r_value(self): """R-value of the first window construction [m2-K/W] (excluding air films).""" return self._constructions[0].r_value @property def u_value(self): """U-value of the first window construction [W/m2-K] (excluding air films).""" return self._constructions[0].u_value @property def r_factor(self): """First window construction R-factor [m2-K/W] (with standard air films). Formulas for film coefficients come from EN673 / ISO10292. """ return self._constructions[0].r_factor @property def u_factor(self): """First window construction U-factor [W/m2-K] (with standard air films). Formulas for film coefficients come from EN673 / ISO10292. """ return self._constructions[0].u_factor @property def solar_transmittance(self): """The solar transmittance of the first window construction at normal incidence. """ return self._constructions[0].solar_transmittance @property def visible_transmittance(self): """Visible transmittance of the first window construction at normal incidence. """ return self._constructions[0].visible_transmittance @property def shgc(self): """The solar heat gain coefficient (SHGC) of the first window construction.""" return self._constructions[0].shgc @property def is_symmetric(self): """Get a boolean for whether all of the construction layers are symmetric. Symmetric means that the materials in reversed order are equal to those in the current order (eg. 'Glass', 'Air Gap', 'Glass'). This is particularly helpful for interior constructions, which need to have matching materials in reversed order between adjacent Faces. """ for con in self._constructions: mats = con.materials half_mat = int(len(mats) / 2) for i in range(half_mat): if mats[i] != mats[-(i + 1)]: return False return True @property def has_frame(self): """Get a boolean noting whether the construction has a frame assigned to it.""" return self._constructions[0].has_frame @property def has_shade(self): """Get a boolean noting whether dynamic shade materials are in the construction. """ # This is False for all construction types except WindowConstructionShade. return False @property def is_dynamic(self): """Get a boolean noting whether the construction is dynamic. This will always be True for this class. """ return True @property def inside_emissivity(self): """"The emissivity of the inside face of the first construction.""" mats = self._constructions[0].materials if isinstance(mats[-1], EnergyWindowMaterialSimpleGlazSys): return 0.84 try: return mats[-1].emissivity_back except AttributeError: return mats[-1].emissivity @property def outside_emissivity(self): """"The emissivity of the outside face of the first construction.""" mats = self._constructions[0].materials if isinstance(mats[0], EnergyWindowMaterialSimpleGlazSys): return 0.84 return mats[0].emissivity @property def thickness(self): """Thickness of the first construction [m].""" return self._constructions[0].thickness @property def glazing_count(self): """The number of glazing materials contained within the first construction.""" return self._constructions[0].glazing_count @property def gap_count(self): """The number of gas gaps contained within the first construction.""" return self._constructions[0].gap_count @property def inside_material(self): """The the inside material layer of the first construction. Useful for checking that an asymmetric construction is correctly assigned. """ mats = self._constructions[0].materials return mats[-1] @property def outside_material(self): """The the outside material layer of the first construction. Useful for checking that an asymmetric construction is correctly assigned. """ mats = self._constructions[0].materials return mats[0] @property def user_data(self): """Get or set an optional dictionary for additional meta data for this object. This will be None until it has been set. All keys and values of this dictionary should be of a standard Python type to ensure correct serialization of the object to/from JSON (eg. str, float, int, list, dict) """ if self._user_data is not None: return self._user_data @user_data.setter def user_data(self, value): if value is not None: assert isinstance(value, dict), 'Expected dictionary for honeybee_energy' \ 'object user_data. Got {}.'.format(type(value)) self._user_data = value
[docs] @classmethod def from_dict(cls, data): """Create a WindowConstructionDynamic from a dictionary. Note that the dictionary must be a non-abridged version for this classmethod to work. Args: data: A python dictionary in the following format .. code-block:: python { "type": 'WindowConstructionDynamic', "identifier": 'Double Pane Electrochromic 0.4-0.12', "display_name": 'Electrochromic Window', "constructions": [], # a list of WindowConstruction dictionaries "schedule": {} # a ScheduleRuleset or ScheduleFixedInterval dictionary } """ # check the type assert data['type'] == 'WindowConstructionDynamic', \ 'Expected WindowConstructionDynamic. Got {}.'.format(data['type']) # re-serialize the inputs constrs = [WindowConstruction.from_dict(c_dict) for c_dict in data['constructions']] schedule = dict_to_schedule(data['schedule']) # create the object new_obj = cls(data['identifier'], constrs, schedule) if 'display_name' in data and data['display_name'] is not None: new_obj.display_name = data['display_name'] if 'user_data' in data and data['user_data'] is not None: new_obj.user_data = data['user_data'] return new_obj
[docs] @classmethod def from_dict_abridged(cls, data, materials, schedules): """Create a WindowConstructionDynamic from an abridged dictionary. Args: data: An WindowConstructionDynamic dictionary with the format below. materials: A dictionary with identifiers of materials as keys and Python material objects as values. schedules: A dictionary with schedule identifiers as keys and honeybee schedule objects as values. .. code-block:: python { "type": 'WindowConstructionDynamicAbridged', "identifier": 'Double Pane Electrochromic 0.4-0.12', "display_name": 'Electrochromic Window', "constructions": [], # a list of WindowConstructionAbridged dictionaries "schedule": 'DayNight_Schedule' # a schedule identifier } """ # check the type assert data['type'] == 'WindowConstructionDynamicAbridged', \ 'Expected WindowConstructionDynamicAbridged. Got {}.'.format(data['type']) # re-serialize the inputs constrs = [WindowConstruction.from_dict_abridged(c_dict, materials) for c_dict in data['constructions']] schedule = schedules[data['schedule']] # create the object new_obj = cls(data['identifier'], constrs, schedule) if 'display_name' in data and data['display_name'] is not None: new_obj.display_name = data['display_name'] if 'user_data' in data and data['user_data'] is not None: new_obj.user_data = data['user_data'] return new_obj
[docs] def to_idf(self): """Get an IDF string representation of this construction object. Note that writing this string is not enough to add everything needed for the construction to the IDF. The construction's materials also have to be added as well as the schedule. The to_program_idf method must also be called in order to add the EMS program that controls the constructions. Returns: Text string that includes the following EnergyPlus objects. - Construction definitions for each state - The EMS Construction Index Variable object for each state - The EMS Sensor linked to the schedule .. code-block:: shell Construction, TCwindow_25, !- Name Clear3PPG, !- Outside Layer AIR 3MM, !- Layer 2 WO18RT25, !- Layer 3 AIR 8MM, !- Layer 4 SB60Clear3PPG; !- Layer 5 EnergyManagementSystem:ConstructionIndexVariable, TCwindow_25_obj, TCwindow_25; """ idf_strs = [] # add all of the construction definitions and EMS states state_com = ('name', 'construction name') for i, con in enumerate(self.constructions): con_dup = con.duplicate() con_dup.identifier = '{}State{}'.format(con.identifier, i) idf_strs.append(con_dup.to_idf()) state_id = 'State{}{}'.format(i, re.sub('[^A-Za-z0-9]', '', con.identifier)) vals = [state_id, con_dup.identifier] state_str = generate_idf_string( 'EnergyManagementSystem:ConstructionIndexVariable', vals, state_com) idf_strs.append(state_str) # add the EMS Sensor definition sensor_com = ('name', 'variable key name', 'variable name') sensor_id = 'Sensor{}'.format(re.sub('[^A-Za-z0-9]', '', self.identifier)) sen_vals = [sensor_id, self.schedule.identifier, 'Schedule Value'] sensor = generate_idf_string( 'EnergyManagementSystem:Sensor', sen_vals, sensor_com) idf_strs.append(sensor) return '\n\n'.join(idf_strs)
[docs] def to_program_idf(self, aperture_identifiers): """Get an IDF string representation of the EMS program. Args: aperture_identifiers: A list of Aperture identifiers to which this construction is assigned. Returns: Text string that includes the following EnergyPlus objects. - The EMS Actuators linked to the apertures - The EMS Program definition """ idf_strs = [] # add all of the actuators linked to the apertures act_com = ('name', 'component name', 'component type', 'component control') actuator_ids = [] for i, ap_id in enumerate(aperture_identifiers): act_id = 'Actuator{}{}'.format(i, re.sub('[^A-Za-z0-9]', '', ap_id)) act_vals = [act_id, ap_id, 'Surface', 'Construction State'] actuator = generate_idf_string( 'EnergyManagementSystem:Actuator', act_vals, act_com) actuator_ids.append(act_id) idf_strs.append(actuator) # add each construction state to the program pid = 'StateChange{}'.format(re.sub('[^A-Za-z0-9]', '', self.identifier)) ems_program = [pid] sensor_id = 'Sensor{}'.format(re.sub('[^A-Za-z0-9]', '', self.identifier)) max_state_count = len(self.constructions) - 1 for i, con in enumerate(self.constructions): # determine which conditional operator to use cond_op = 'IF' if i == 0 else 'ELSEIF' # add the conditional statement state_count = i + 1 if i == max_state_count: cond_stmt = 'ELSE' else: cond_stmt = '{} ({} < {})'.format(cond_op, sensor_id, state_count) ems_program.append(cond_stmt) # loop through the actuators and set the appropriate window state state_id = 'State{}{}'.format(i, re.sub('[^A-Za-z0-9]', '', con.identifier)) for act_name in actuator_ids: ems_program.append('SET {} = {}'.format(act_name, state_id)) ems_program.append('ENDIF') ems_prog_str = generate_idf_string('EnergyManagementSystem:Program', ems_program) idf_strs.append(ems_prog_str) return '\n\n'.join(idf_strs)
[docs] @staticmethod def idf_program_manager(constructions): """Get an IDF string representation of the EMS program calling manager. Args: constructions: A list of WindowConstructionDynamic objects to be written into a program manager. """ man_com = ['name', 'calling point'] man_vals = ['Dynamic_Window_Constructions', 'BeginTimestepBeforePredictor'] for i, con in enumerate(constructions): pid = 'StateChange{}'.format(re.sub('[^A-Za-z0-9]', '', con.identifier)) man_vals.append(pid) man_com.append('program name{}'.format(i)) manager = generate_idf_string( 'EnergyManagementSystem:ProgramCallingManager', man_vals, man_com) return manager
[docs] def to_radiance_solar(self): """Honeybee Radiance material for the first construction.""" # TODO: add methods that can represent the dynamic window behavior return self._constructions[0].to_radiance_solar()
[docs] def to_radiance_visible(self): """Honeybee Radiance material for the first construction.""" # TODO: add methods that can represent the dynamic window behavior return self._constructions[0].to_radiance_visible()
[docs] def to_dict(self, abridged=False): """Window construction dictionary representation. Args: abridged: Boolean to note whether the full dictionary describing the object should be returned (False) or just an abridged version (True), which only specifies the identifiers of material layers and schedules. (Default: False). """ base = {'type': 'WindowConstructionDynamic'} if not \ abridged else {'type': 'WindowConstructionDynamicAbridged'} base['identifier'] = self.identifier base['constructions'] = [con.to_dict(abridged) for con in self.constructions] base['schedule'] = self.schedule.identifier if abridged \ else self.schedule.to_dict() if self._display_name is not None: base['display_name'] = self.display_name if self._user_data is not None: base['user_data'] = self.user_data return base
[docs] def lock(self): """The lock() method will also lock the constructions.""" self._locked = True for mat in self.constructions: mat.lock()
[docs] def unlock(self): """The unlock() method will also unlock the constructions.""" self._locked = False for mat in self.constructions: mat.unlock()
[docs] def duplicate(self): """Get a copy of this construction.""" return self.__copy__()
def __copy__(self): new_con = WindowConstructionDynamic( self.identifier, self.constructions, self.schedule) new_con._display_name = self._display_name new_con.user_data = None if self._user_data is None else self._user_data.copy() return new_con def __len__(self): return len(self._constructions) def __getitem__(self, key): return self._constructions[key] def __iter__(self): return iter(self._constructions) def __key(self): """A tuple based on the object properties, useful for hashing.""" return (self._identifier, hash(self.schedule)) + \ tuple(hash(con) for con in self.constructions) def __hash__(self): return hash(self.__key()) def __eq__(self, other): return isinstance(other, WindowConstructionDynamic) and \ self.__key() == other.__key() def __ne__(self, other): return not self.__eq__(other)
[docs] def ToString(self): """Overwrite .NET ToString.""" return self.__repr__()
def __repr__(self): return 'WindowConstructionDynamic: [{} states]'.format(len(self.constructions))