Source code for dragonfly_energy.properties.model

# coding=utf-8
"""Model Energy Properties."""
try:
    from itertools import izip as zip  # python 2
except ImportError:
    pass   # python 3

from honeybee.extensionutil import room_extension_dicts
from honeybee.units import conversion_factor_to_meters
from honeybee.boundarycondition import Surface
from honeybee_energy.construction.opaque import OpaqueConstruction
from honeybee_energy.construction.windowshade import WindowConstructionShade
from honeybee_energy.construction.dynamic import WindowConstructionDynamic
from honeybee_energy.construction.air import AirBoundaryConstruction
import honeybee_energy.properties.model as hb_model_properties
from honeybee_energy.lib.constructions import generic_context
from honeybee_energy.lib.constructionsets import generic_construction_set
from honeybee.checkdup import check_duplicate_identifiers

from dragonfly.extensionutil import model_extension_dicts


[docs] class ModelEnergyProperties(object): """Energy Properties for Dragonfly Model. Args: host: A dragonfly_core Model object that hosts these properties. Properties: * host * materials * constructions * face_constructions * shade_constructions * construction_sets * global_construction_set * schedule_type_limits * schedules * construction_schedules * shade_schedules * program_type_schedules * hvac_schedules * misc_room_schedules * program_types * hvacs * shws """ def __init__(self, host): """Initialize Model energy properties.""" self._host = host @property def host(self): """Get the Model object hosting these properties.""" return self._host @property def materials(self): """Get a list of all unique materials contained within the model. This includes materials across all Room2Ds, Stories, and Building ConstructionSets but it does NOT include the Honeybee generic default construction set. """ materials = [] for constr in self.constructions: try: materials.extend(constr.materials) if constr.has_frame: materials.append(constr.frame) if isinstance(constr, WindowConstructionShade): if constr.is_switchable_glazing: materials.append(constr.switched_glass_material) if constr.shade_location == 'Between': materials.append(constr.window_construction.materials[-2]) except AttributeError: pass # ShadeConstruction return list(set(materials)) @property def constructions(self): """Get a list of all unique constructions in the model. This includes constructions across all Room2Ds, Stories, and Building ConstructionSets but it does NOT include the Honeybee generic default construction set. """ bldg_constrs = [] for cnstr_set in self.construction_sets: bldg_constrs.extend(cnstr_set.modified_constructions_unique) all_constrs = bldg_constrs + self.face_constructions + self.shade_constructions return list(set(all_constrs)) @property def face_constructions(self): """Get a list of all unique constructions assigned to Faces, Apertures and Doors. These objects only exist under the Building.room_3ds property and in the Building's ceiling_plenum_construction and floor_plenum_construction. """ constructions = [] for bldg in self.host.buildings: for face in bldg.room_3d_faces: self._check_and_add_obj_construction(face, constructions) for ap in face.apertures: self._check_and_add_obj_construction(ap, constructions) for dr in face.doors: self._check_and_add_obj_construction(dr, constructions) constr = bldg.properties.energy._ceiling_plenum_construction if constr is not None: if not self._instance_in_array(constr, constructions): constructions.append(constr) constr = bldg.properties.energy._floor_plenum_construction if constr is not None: if not self._instance_in_array(constr, constructions): constructions.append(constr) return list(set(constructions)) @property def shade_constructions(self): """Get a list of all unique constructions assigned to ContextShades in the model. This will also include any Shade objects assigned to the 3D Honeybee Rooms of any Model Buildings. """ constructions = [] for shade in self.host.context_shades: self._check_and_add_obj_construction(shade, constructions) for bldg in self.host.buildings: for shd in bldg.room_3d_shades: self._check_and_add_obj_construction(shd, constructions) return list(set(constructions)) @property def construction_sets(self): """Get a list of all unique ConstructionSets in the Model. Note that this includes ConstructionSets assigned to individual Stories, Room2Ds and 3D Honeybee Rooms in the Model's Buildings. """ construction_sets = [] for bldg in self.host.buildings: self._check_and_add_obj_constr_set(bldg, construction_sets) for story in bldg.unique_stories: self._check_and_add_obj_constr_set(story, construction_sets) for room in story.room_2ds: self._check_and_add_obj_constr_set(room, construction_sets) for room in bldg.room_3ds: self._check_and_add_obj_constr_set(room, construction_sets) return list(set(construction_sets)) # catch equivalent construction sets @property def global_construction_set(self): """The global energy construction set. This is what is used whenever there is no construction_set assigned to a Room2D, a parent Story, or a parent Building. """ return generic_construction_set @property def schedule_type_limits(self): """Get a list of all unique schedule type limits contained within the model. This includes schedules across all ContextShades and Room2Ds. """ type_limits = [] for sched in self.schedules: t_lim = sched.schedule_type_limit if t_lim is not None and not self._instance_in_array(t_lim, type_limits): type_limits.append(t_lim) return list(set(type_limits)) @property def schedules(self): """Get a list of all unique schedules in the model. This includes schedules across all ProgramTypes and ContextShades. """ all_scheds = self.hvac_schedules + self.program_type_schedules + \ self.misc_room_schedules + self.shade_schedules + self.construction_schedules return list(set(all_scheds)) @property def construction_schedules(self): """Get a list of all unique schedules assigned to constructions in the model. This includes schedules on al AirBoundaryConstructions, WindowConstructionShade, and WindowConstructionDynamic. """ schedules = [] for constr in self.constructions: if isinstance(constr, AirBoundaryConstruction): self._check_and_add_schedule(constr.air_mixing_schedule, schedules) elif isinstance(constr, WindowConstructionShade): if constr.schedule is not None: self._check_and_add_schedule(constr.schedule, schedules) elif isinstance(constr, WindowConstructionDynamic): self._check_and_add_schedule(constr.schedule, schedules) return list(set(schedules)) @property def shade_schedules(self): """Get a list of all unique schedules assigned to ContextShades in the model. """ schedules = [] for shade in self.host._context_shades: self._check_and_add_shade_schedule(shade, schedules) return list(set(schedules)) @property def program_type_schedules(self): """A list of all unique schedules assigned to ProgramTypes in the model.""" schedules = [] for p_type in self.program_types: for sched in p_type.schedules: self._check_and_add_schedule(sched, schedules) return list(set(schedules)) @property def hvac_schedules(self): """Get a list of all unique HVAC-assigned schedules in the model.""" schedules = [] for hvac in self.hvacs: for sched in hvac.schedules: self._check_and_add_schedule(sched, schedules) return list(set(schedules)) @property def misc_room_schedules(self): """Get a list of all unique schedules assigned directly to Rooms in the model. This includes schedules for process loads and window ventilation control that are assigned to Room2Ds. It also includes any schedules assigned directly to 3D Honeybee Rooms of the model (not through the room program). Note that this does not include schedules from ProgramTypes assigned to the rooms. For this, use the program_type_schedules property. """ scheds = [] for bldg in self.host._buildings: for story in bldg: for room in story: window_vent = room.properties.energy._window_vent_control processes = room.properties.energy._process_loads if window_vent is not None: self._check_and_add_schedule(window_vent.schedule, scheds) if len(processes) != 0: for process in processes: self._check_and_add_schedule(process.schedule, scheds) for room in bldg.room_3ds: people = room.properties.energy._people lighting = room.properties.energy._lighting electric_equipment = room.properties.energy._electric_equipment gas_equipment = room.properties.energy._gas_equipment shw = room.properties.energy._service_hot_water infiltration = room.properties.energy._infiltration ventilation = room.properties.energy._ventilation setpoint = room.properties.energy._setpoint window_vent = room.properties.energy._window_vent_control processes = room.properties.energy._process_loads fans = room.properties.energy._fans if people is not None: self._check_and_add_schedule(people.occupancy_schedule, scheds) self._check_and_add_schedule(people.activity_schedule, scheds) if lighting is not None: self._check_and_add_schedule(lighting.schedule, scheds) if electric_equipment is not None: self._check_and_add_schedule(electric_equipment.schedule, scheds) if gas_equipment is not None: self._check_and_add_schedule(gas_equipment.schedule, scheds) if shw is not None: self._check_and_add_schedule(shw.schedule, scheds) if infiltration is not None: self._check_and_add_schedule(infiltration.schedule, scheds) if ventilation is not None and ventilation.schedule is not None: self._check_and_add_schedule(ventilation.schedule, scheds) if setpoint is not None: self._check_and_add_schedule(setpoint.heating_schedule, scheds) self._check_and_add_schedule(setpoint.cooling_schedule, scheds) if setpoint.humidifying_schedule is not None: self._check_and_add_schedule( setpoint.humidifying_schedule, scheds) self._check_and_add_schedule( setpoint.dehumidifying_schedule, scheds) if window_vent is not None: self._check_and_add_schedule(window_vent.schedule, scheds) if len(processes) != 0: for process in processes: self._check_and_add_schedule(process.schedule, scheds) if len(fans) != 0: for fan in fans: self._check_and_add_schedule(fan.control.schedule, scheds) return list(set(scheds)) @property def program_types(self): """Get a list of all unique ProgramTypes in the Model. This includes ProgramTypes assigned to both Room2Ds and 3D Honeybee Rooms. """ program_types = [] for bldg in self.host._buildings: for story in bldg: for room in story: if room.properties.energy._program_type is not None: if not self._instance_in_array( room.properties.energy._program_type, program_types): program_types.append(room.properties.energy._program_type) for room in bldg.room_3ds: if room.properties.energy._program_type is not None: if not self._instance_in_array( room.properties.energy._program_type, program_types): program_types.append(room.properties.energy._program_type) return list(set(program_types)) # catch equivalent program types @property def hvacs(self): """Get a list of all unique HVAC systems in the Model.""" hvacs = [] for bldg in self.host._buildings: for story in bldg: for room in story: if room.properties.energy._hvac is not None: if not self._instance_in_array( room.properties.energy._hvac, hvacs): hvacs.append(room.properties.energy._hvac) for room in bldg.room_3ds: if room.properties.energy._hvac is not None: if not self._instance_in_array(room.properties.energy._hvac, hvacs): hvacs.append(room.properties.energy._hvac) return hvacs @property def shws(self): """Get a list of all unique Service Hot Water (SHW) systems in the Model.""" shws = [] for bldg in self.host._buildings: for story in bldg: for room in story: if room.properties.energy._shw is not None: if not self._instance_in_array( room.properties.energy._shw, shws): shws.append(room.properties.energy._shw) for room in bldg.room_3ds: if room.properties.energy._shw is not None: if not self._instance_in_array(room.properties.energy._shw, shws): shws.append(room.properties.energy._shw) return shws
[docs] def check_for_extension(self, raise_exception=True, detailed=False): """Check that the Model is valid for EnergyPlus simulation. This process includes all relevant dragonfly-core checks as well as checks that apply only for EnergyPlus. Args: raise_exception: Boolean to note whether a ValueError should be raised if any errors are found. If False, this method will simply return a text string with all errors that were found. (Default: True). detailed: Boolean for whether the returned object is a detailed list of dicts with error info or a string with a message. (Default: False). Returns: A text string with all errors that were found or a list if detailed is True. This string (or list) will be empty if no errors were found. """ # set up defaults to ensure the method runs correctly detailed = False if raise_exception else detailed msgs = [] tol = self.host.tolerance ang_tol = self.host.angle_tolerance # perform checks for duplicate identifiers, which might mess with other checks msgs.append(self.host.check_all_duplicate_identifiers(False, detailed)) # perform checks for key dragonfly model schema rules msgs.append(self.host.check_degenerate_room_2ds(tol, False, detailed)) msgs.append(self.host.check_self_intersecting_room_2ds(tol, False, detailed)) msgs.append(self.host.check_plenum_depths(tol, False, detailed)) msgs.append(self.host.check_window_parameters_valid(tol, False, detailed)) msgs.append(self.host.check_no_room2d_overlaps(tol, False, detailed)) msgs.append(self.host.check_collisions_between_stories(tol, False, detailed)) msgs.append(self.host.check_roofs_above_rooms(tol, False, detailed)) msgs.append(self.host.check_room2d_floor_heights_valid(False, detailed)) msgs.append(self.host.check_missing_adjacencies(False, detailed)) msgs.append(self.host.check_all_room3d(tol, ang_tol, False, detailed)) # perform checks for specific energy simulation rules msgs.append(self.check_all_zones_have_one_hvac(False, detailed)) msgs.append(self.check_maximum_elevation(1000, False, detailed)) # output a final report of errors or raise an exception full_msgs = [msg for msg in msgs if msg] if detailed: return [m for msg in full_msgs for m in msg] full_msg = '\n'.join(full_msgs) if raise_exception and len(full_msgs) != 0: raise ValueError(full_msg) return full_msg
[docs] def check_generic(self, raise_exception=True, detailed=False): """Check generic of the aspects of the Model energy properties. This includes checks for everything except duplicate identifiers for constructions, schedules, etc. Typically, these checks just add to the validation time without providing useful information since extension objects with duplicate IDs are lost during HBJSON serialization. Args: raise_exception: Boolean to note whether a ValueError should be raised if any errors are found. If False, this method will simply return a text string with all errors that were found. detailed: Boolean for whether the returned object is a detailed list of dicts with error info or a string with a message. (Default: False). Returns: A text string with all errors that were found or a list if detailed is True. This string (or list) will be empty if no errors were found. """ # set up defaults to ensure the method runs correctly detailed = False if raise_exception else detailed msgs = [] # perform checks for specific energy simulation rules msgs.append(self.check_all_zones_have_one_hvac(False, detailed)) msgs.append(self.check_maximum_elevation(1000, False, detailed)) # output a final report of errors or raise an exception full_msgs = [msg for msg in msgs if msg] if detailed: return [m for msg in full_msgs for m in msg] full_msg = '\n'.join(full_msgs) if raise_exception and len(full_msgs) != 0: raise ValueError(full_msg) return full_msg
[docs] def check_all(self, raise_exception=True, detailed=False): """Check all of the aspects of the Model energy properties. Args: raise_exception: Boolean to note whether a ValueError should be raised if any errors are found. If False, this method will simply return a text string with all errors that were found. detailed: Boolean for whether the returned object is a detailed list of dicts with error info or a string with a message. (Default: False). Returns: A text string with all errors that were found or a list if detailed is True. This string (or list) will be empty if no errors were found. """ # set up defaults to ensure the method runs correctly detailed = False if raise_exception else detailed msgs = [] # perform checks for duplicate identifiers msgs.append(self.check_all_duplicate_identifiers(False, detailed)) # perform checks for specific energy simulation rules msgs.append(self.check_all_zones_have_one_hvac(False, detailed)) msgs.append(self.check_maximum_elevation(1000, False, detailed)) # output a final report of errors or raise an exception full_msgs = [msg for msg in msgs if msg] if detailed: return [m for msg in full_msgs for m in msg] full_msg = '\n'.join(full_msgs) if raise_exception and len(full_msgs) != 0: raise ValueError(full_msg) return full_msg
[docs] def check_all_duplicate_identifiers(self, raise_exception=True, detailed=False): """Check that there are no duplicate identifiers for any geometry objects. This includes Materials, Constructions, ConstructionSets, Schedules, Programs, HVACs, and SHWs. Args: raise_exception: Boolean to note whether a ValueError should be raised if any Model errors are found. If False, this method will simply return a text string with all errors that were found. (Default: True). detailed: Boolean for whether the returned object is a detailed list of dicts with error info or a string with a message. (Default: False). Returns: A text string with all errors that were found or a list if detailed is True. This string (or list) will be empty if no errors were found. """ # set up defaults to ensure the method runs correctly detailed = False if raise_exception else detailed msgs = [] # perform checks for duplicate identifiers msgs.append(self.check_duplicate_material_identifiers(False, detailed)) msgs.append(self.check_duplicate_construction_identifiers(False, detailed)) msgs.append(self.check_duplicate_construction_set_identifiers(False, detailed)) stl_msgs = self.check_duplicate_schedule_type_limit_identifiers(False, detailed) msgs.append(stl_msgs) msgs.append(self.check_duplicate_schedule_identifiers(False, detailed)) msgs.append(self.check_duplicate_program_type_identifiers(False, detailed)) msgs.append(self.check_duplicate_hvac_identifiers(False, detailed)) msgs.append(self.check_duplicate_shw_identifiers(False, detailed)) # output a final report of errors or raise an exception full_msgs = [msg for msg in msgs if msg] if detailed: return [m for msg in full_msgs for m in msg] full_msg = '\n'.join(full_msgs) if raise_exception and len(full_msgs) != 0: raise ValueError(full_msg) return full_msg
[docs] def check_duplicate_material_identifiers(self, raise_exception=True, detailed=False): """Check that there are no duplicate Material identifiers in the model. Args: raise_exception: Boolean to note whether a ValueError should be raised if duplicate identifiers are found. (Default: True). detailed: Boolean for whether the returned object is a detailed list of dicts with error info or a string with a message. (Default: False). Returns: A string with the message or a list with a dictionary if detailed is True. """ return check_duplicate_identifiers( self.materials, raise_exception, 'Material', detailed, '020001', 'Energy', error_type='Duplicate Material Identifier')
[docs] def check_duplicate_construction_identifiers( self, raise_exception=True, detailed=False): """Check that there are no duplicate Construction identifiers in the model. Args: raise_exception: Boolean to note whether a ValueError should be raised if duplicate identifiers are found. (Default: True). detailed: Boolean for whether the returned object is a detailed list of dicts with error info or a string with a message. (Default: False). Returns: A string with the message or a list with a dictionary if detailed is True. """ return check_duplicate_identifiers( self.constructions, raise_exception, 'Construction', detailed, '020002', 'Energy', error_type='Duplicate Construction Identifier')
[docs] def check_duplicate_construction_set_identifiers( self, raise_exception=True, detailed=False): """Check that there are no duplicate ConstructionSet identifiers in the model. Args: raise_exception: Boolean to note whether a ValueError should be raised if duplicate identifiers are found. (Default: True). detailed: Boolean for whether the returned object is a detailed list of dicts with error info or a string with a message. (Default: False). Returns: A string with the message or a list with a dictionary if detailed is True. """ return check_duplicate_identifiers( self.construction_sets, raise_exception, 'ConstructionSet', detailed, '020003', 'Energy', error_type='Duplicate ConstructionSet Identifier')
[docs] def check_duplicate_schedule_type_limit_identifiers( self, raise_exception=True, detailed=False): """Check that there are no duplicate ScheduleTypeLimit identifiers in the model. Args: raise_exception: Boolean to note whether a ValueError should be raised if duplicate identifiers are found. (Default: True). detailed: Boolean for whether the returned object is a detailed list of dicts with error info or a string with a message. (Default: False). Returns: A string with the message or a list with a dictionary if detailed is True. """ return check_duplicate_identifiers( self.schedule_type_limits, raise_exception, 'ScheduleTypeLimit', detailed, '020004', 'Energy', error_type='Duplicate ScheduleTypeLimit Identifier')
[docs] def check_duplicate_schedule_identifiers(self, raise_exception=True, detailed=False): """Check that there are no duplicate Schedule identifiers in the model. Args: raise_exception: Boolean to note whether a ValueError should be raised if duplicate identifiers are found. (Default: True). detailed: Boolean for whether the returned object is a detailed list of dicts with error info or a string with a message. (Default: False). Returns: A string with the message or a list with a dictionary if detailed is True. """ return check_duplicate_identifiers( self.schedules, raise_exception, 'Schedule', detailed, '020005', 'Energy', error_type='Duplicate Schedule Identifier')
[docs] def check_duplicate_program_type_identifiers( self, raise_exception=True, detailed=False): """Check that there are no duplicate ProgramType identifiers in the model. Args: raise_exception: Boolean to note whether a ValueError should be raised if duplicate identifiers are found. (Default: True). detailed: Boolean for whether the returned object is a detailed list of dicts with error info or a string with a message. (Default: False). Returns: A string with the message or a list with a dictionary if detailed is True. """ return check_duplicate_identifiers( self.program_types, raise_exception, 'ProgramType', detailed, '020006', 'Energy', error_type='Duplicate ProgramType Identifier')
[docs] def check_duplicate_hvac_identifiers(self, raise_exception=True, detailed=False): """Check that there are no duplicate HVAC identifiers in the model. Args: raise_exception: Boolean to note whether a ValueError should be raised if duplicate identifiers are found. (Default: True). detailed: Boolean for whether the returned object is a detailed list of dicts with error info or a string with a message. (Default: False). Returns: A string with the message or a list with a dictionary if detailed is True. """ return check_duplicate_identifiers( self.hvacs, raise_exception, 'HVAC', detailed, '020007', 'Energy', error_type='Duplicate HVAC Identifier')
[docs] def check_duplicate_shw_identifiers(self, raise_exception=True, detailed=False): """Check that there are no duplicate SHW identifiers in the model. Args: raise_exception: Boolean to note whether a ValueError should be raised if duplicate identifiers are found. (Default: True). detailed: Boolean for whether the returned object is a detailed list of dicts with error info or a string with a message. (Default: False). Returns: A string with the message or a list with a dictionary if detailed is True. """ return check_duplicate_identifiers( self.shws, raise_exception, 'SHW', detailed, '020008', 'Energy', error_type='Duplicate SHW Identifier')
[docs] def check_all_zones_have_one_hvac(self, raise_exception=True, detailed=False): """Check that all rooms within each zone have only one HVAC assigned to them. Multiple HVAC systems serving one zone typically causes EnergyPlus simulation failures and is often a mistake that results from changing zoning strategies without changing the HVAC to be coordinated with the new zones. Note that having some Rooms in the zone referencing the HVAC and others with no HVAC is considered permissible since this just implies that the thermostat or zone equipment may be in one of the rooms but the whole zone is conditioned by this equipment. Args: raise_exception: Boolean to note whether a ValueError should be raised if there are rooms within zones that have different HVAC systems assigned to them. (Default: True). detailed: Boolean for whether the returned object is a detailed list of dicts with error info or a string with a message. (Default: False). Returns: A string with the message or a list with a dictionary if detailed is True. """ detailed = False if raise_exception else detailed # gather a list of all the missing rooms invalid_zones = {} for bldg in self.host.buildings: for zone, rooms in bldg.zone_dict.items(): if len(rooms) == 1: continue hvacs = [r.properties.energy.hvac for r in rooms if r.properties.energy.hvac is not None] if len(hvacs) > 1: invalid_zones[zone] = [rooms, hvacs] # if missing rooms were found, then report the issue if len(invalid_zones) != 0: if detailed: all_err = [] for zone, data in invalid_zones.items(): rooms, hvacs = data hvac_names = [h.display_name for h in hvacs] msg = 'Zone "{}" is served by the following different HVAC ' \ 'systems:\n{}'.format(zone, '\n'.join(hvac_names)) error_dict = { 'type': 'ValidationError', 'code': '020014', 'error_type': 'Zone with Different Room HVACs', 'extension_type': 'Energy', 'element_type': 'Room2D', 'element_id': [r.identifier for r in rooms], 'element_name': [r.display_name for r in rooms], 'message': msg } all_err.append(error_dict) return all_err else: err_zones = [] for zone, data in invalid_zones.items(): rooms, hvacs = data hvac_names = [h.display_name for h in hvacs] err_zn = ' {} - [HVACS: {}]'.format(zone, ', '.join(hvac_names)) err_zones.append(err_zn) msg = 'The model has the following invalid zones served by different ' \ 'HVAC systems:\n{}'.format('\n'.join(err_zones)) if raise_exception: raise ValueError(msg) return msg return [] if detailed else ''
[docs] def check_maximum_elevation(self, max_elevation=1000, raise_exception=True, detailed=False): """Check that no Room2Ds of the model are above a certain elevation. EnergyPlus computes wind speeds, air pressures, and adjusts outdoor temperatures to account for the height above the ground using the Z values of the geometry coordinates. This is an important consideration when modeling skyscrapers but it can be detrimental when a building has been modeled with its coordinates at the height above sea level and the location is significantly above sea level (eg. Denver, Colorado). This validation check is intended to catch such cases and make the user aware of the repercussions. Args: max_elevation: A number for the maximum elevation in Meters that the model's rooms are permitted to be at before a ValidationError is reported. While EnergyPlus technically still simulates with models that are 12 kilometers above the origin, better practice is to set this value at the maximum height above the ground that any human-made structure can reasonably obtain. For this reason, the default is set to 1000 meters. raise_exception: Boolean to note whether a ValueError should be raised if a Room composed entirely of AirBoundaries is found. (Default: True). detailed: Boolean for whether the returned object is a detailed list of dicts with error info or a string with a message. (Default: False). Returns: A string with the message or a list with a dictionary if detailed is True. """ detailed = False if raise_exception else detailed # get the maximum elevation of all the rooms conv_fac = conversion_factor_to_meters(self.host.units) max_elev_model = max_elevation / conv_fac # loop through the buildings and check whether it exceeds the maximum bldg_msgs = [] for bldg in self.host.buildings: max_bldg_elev = bldg.height # if the maximum elevation was exceeded, then report the issue if max_bldg_elev > max_elev_model: msg = 'The height of Building "{}" is currently {} meters above the ' \ 'ground given the Z values of the coordinates. This is above the ' \ 'maximum recommended height of {} meters.'.format( bldg.display_name, int(max_bldg_elev * conv_fac), max_elevation) if detailed: msg = { 'type': 'ValidationError', 'code': '020101', 'error_type': 'Building Height Exceeds Max Elevation', 'extension_type': 'Energy', 'element_type': 'Room2D', 'element_id': [r.identifier for r in bldg.unique_room_2ds], 'element_name': [r.display_name for r in bldg.unique_room_2ds], 'message': msg } bldg_msgs.append(msg) # return the validation message if requested if detailed: return bldg_msgs if bldg_msgs != []: msg = '\n'.join(bldg_msgs) if raise_exception: raise ValueError(msg) return msg return ''
[docs] def apply_properties_from_dict(self, data): """Apply the energy properties of a dictionary to the host Model of this object. Args: data: A dictionary representation of an entire dragonfly-core Model. Note that this dictionary must have ModelEnergyProperties in order for this method to successfully apply the energy properties. """ assert 'energy' in data['properties'], \ 'Dictionary possesses no ModelEnergyProperties.' _, constructions, construction_sets, _, schedules, program_types, hvacs, shws = \ hb_model_properties.ModelEnergyProperties.load_properties_from_dict(data) # collect lists of energy property dictionaries building_e_dicts, story_e_dicts, room2d_e_dicts, context_e_dicts = \ model_extension_dicts(data, 'energy', [], [], [], []) # apply energy properties to objects using the energy property dictionaries for bldg, b_dict in zip(self.host.buildings, building_e_dicts): if b_dict is not None: bldg.properties.energy.apply_properties_from_dict( b_dict, construction_sets, constructions) if bldg.has_room_3ds and b_dict is not None and 'room_3ds' in b_dict and \ b_dict['room_3ds'] is not None: room_e_dicts, face_e_dicts, shd_e_dicts, ap_e_dicts, dr_e_dicts = \ room_extension_dicts(b_dict['room_3ds'], 'energy', [], [], [], [], []) for room, r_dict in zip(bldg.room_3ds, room_e_dicts): if r_dict is not None: room.properties.energy.apply_properties_from_dict( r_dict, construction_sets, program_types, hvacs, shws, schedules, constructions) for face, f_dict in zip(bldg.room_3d_faces, face_e_dicts): if f_dict is not None: face.properties.energy.apply_properties_from_dict( f_dict, constructions) for aperture, a_dict in zip(bldg.room_3d_apertures, ap_e_dicts): if a_dict is not None: aperture.properties.energy.apply_properties_from_dict( a_dict, constructions) for door, d_dict in zip(bldg.room_3d_doors, dr_e_dicts): if d_dict is not None: door.properties.energy.apply_properties_from_dict( d_dict, constructions) for shade, s_dict in zip(bldg.room_3d_shades, shd_e_dicts): if s_dict is not None: shade.properties.energy.apply_properties_from_dict( s_dict, constructions, schedules) for story, s_dict in zip(self.host.stories, story_e_dicts): if s_dict is not None: story.properties.energy.apply_properties_from_dict( s_dict, construction_sets) for room, r_dict in zip(self.host.room_2ds, room2d_e_dicts): if r_dict is not None: room.properties.energy.apply_properties_from_dict( r_dict, construction_sets, program_types, hvacs, shws, schedules) for shade, s_dict in zip(self.host.context_shades, context_e_dicts): if s_dict is not None: shade.properties.energy.apply_properties_from_dict( s_dict, constructions, schedules)
[docs] def to_dict(self): """Return Model energy properties as a dictionary.""" base = {'energy': {'type': 'ModelEnergyProperties'}} # add all materials, constructions and construction sets to the dictionary schs = self._add_constr_type_objs_to_dict(base) # add all schedule type limits, schedules, and program types to the dictionary self._add_sched_type_objs_to_dict(base, schs) return base
[docs] def to_honeybee(self, new_host): """Get a honeybee version of this object. Args: new_host: A honeybee-core Model object that will host these properties. """ # check whether construction sets in this model can create interior conflicts can_conflict = True construction_sets = [] for room in self.host.room_2ds: c_set = room.properties.energy.construction_set if not self._instance_in_array(c_set, construction_sets): construction_sets.append(c_set) if len(construction_sets) == 1 and construction_sets[0].is_interior_symmetric: can_conflict = False # typical case of only one construction set elif all(c_set.is_interior_defaulted for c_set in construction_sets): can_conflict = False # typical case of only editing exterior constructions # if construction sets can create conflicts, loop through new_host and fix them if can_conflict: # first gather all interior faces in the model and their adjacent object adj_constr, base_objs, adj_ids = [], [], [] for face in new_host.faces: if isinstance(face.boundary_condition, Surface): const = face.properties.energy.construction if not isinstance(const, AirBoundaryConstruction): adj_constr.append(face.properties.energy.construction) base_objs.append(face) adj_ids.append(face.boundary_condition.boundary_condition_object) # next, get the adjacent objects valid_adjacencies = True try: adj_faces = new_host.faces_by_identifier(adj_ids) except ValueError: # the model has missing adjacencies valid_adjacencies = False # loop through the adjacent face pairs and report if materials are not matched if valid_adjacencies: rev_cons, reported_items = {}, set() for base_c, base_f, adj_f in zip(adj_constr, base_objs, adj_faces): if (base_f.identifier, adj_f.identifier) in reported_items: continue adj_c = adj_f.properties.energy.construction if not base_c.materials == tuple(reversed(adj_c.materials)): reported_items.add((adj_f.identifier, base_f.identifier)) # resolve the conflict by assigning the higher R-value if base_c.r_value > adj_c.r_value: if base_c.is_symmetric: rev_con = base_c else: rev_con_id = '{} Rev'.format(base_c.identifier) try: rev_con = rev_cons[rev_con_id] except KeyError: # create the reversed construction rev_con = OpaqueConstruction( rev_con_id, tuple(reversed(base_c.materials))) adj_f.properties.energy.construction = rev_con else: if adj_c.is_symmetric: rev_con = adj_c else: rev_con_id = '{} Rev'.format(adj_c.identifier) try: rev_con = rev_cons[rev_con_id] except KeyError: # create the reversed construction rev_con = OpaqueConstruction( rev_con_id, tuple(reversed(adj_c.materials))) base_f.properties.energy.construction = rev_con # create the honeybee ModelEnergyProperties return hb_model_properties.ModelEnergyProperties(new_host)
[docs] def duplicate(self, new_host=None): """Get a copy of this Model. Args: new_host: A new Model object that hosts these properties. If None, the properties will be duplicated with the same host. """ _host = new_host or self._host return ModelEnergyProperties(_host)
def _add_constr_type_objs_to_dict(self, base): """Add materials, constructions and construction sets to a base dictionary. Args: base: A base dictionary for a Dragonfly Model. """ # add the global construction set to the dictionary gs = self.global_construction_set.to_dict(abridged=True, none_for_defaults=False) gs['type'] = 'GlobalConstructionSet' del gs['identifier'] g_constr = self.global_construction_set.constructions_unique g_materials = [] for constr in g_constr: try: g_materials.extend(constr.materials) except AttributeError: pass # ShadeConstruction or AirBoundaryConstruction gs['context_construction'] = generic_context.identifier gs['constructions'] = [generic_context.to_dict()] for cnst in g_constr: try: gs['constructions'].append(cnst.to_dict(abridged=True)) except TypeError: # ShadeConstruction gs['constructions'].append(cnst.to_dict()) gs['materials'] = [mat.to_dict() for mat in set(g_materials)] base['energy']['global_construction_set'] = gs # add all ConstructionSets to the dictionary base['energy']['construction_sets'] = [] construction_sets = self.construction_sets for cnstr_set in construction_sets: base['energy']['construction_sets'].append(cnstr_set.to_dict(abridged=True)) # add all unique Constructions to the dictionary room_constrs = [] for cnstr_set in construction_sets: room_constrs.extend(cnstr_set.modified_constructions_unique) mass_constrs = [] for bldg in self.host.buildings: for room in bldg.room_3ds: for int_mass in room.properties.energy._internal_masses: constr = int_mass.construction if not self._instance_in_array(constr, mass_constrs): mass_constrs.append(constr) all_constrs = room_constrs + self.face_constructions + self.shade_constructions constructions = tuple(set(all_constrs)) base['energy']['constructions'] = [] for cnst in constructions: try: base['energy']['constructions'].append(cnst.to_dict(abridged=True)) except TypeError: # ShadeConstruction base['energy']['constructions'].append(cnst.to_dict()) # add all unique Materials to the dictionary materials = [] for cnstr in constructions: try: materials.extend(cnstr.materials) if cnstr.has_frame: materials.append(cnstr.frame) if isinstance(cnstr, WindowConstructionShade): if cnstr.is_switchable_glazing: materials.append(cnstr.switched_glass_material) if cnstr.shade_location == 'Between': materials.append(cnstr.window_construction.materials[-2]) except AttributeError: pass # ShadeConstruction base['energy']['materials'] = [mat.to_dict() for mat in set(materials)] # extract all of the schedules from the constructions schedules = [] for constr in constructions: if isinstance(constr, AirBoundaryConstruction): self._check_and_add_schedule(constr.air_mixing_schedule, schedules) elif isinstance(constr, WindowConstructionShade): if constr.schedule is not None: self._check_and_add_schedule(constr.schedule, schedules) elif isinstance(constr, WindowConstructionDynamic): self._check_and_add_schedule(constr.schedule, schedules) return schedules def _add_sched_type_objs_to_dict(self, base, schs): """Add schedule type limits, schedules, and program types to a base dictionary. Args: base: A base dictionary for a Dragonfly Model. schs: A list of additional schedules to be serialized to the base dictionary. """ # add all unique hvacs to the dictionary hvacs = self.hvacs base['energy']['hvacs'] = [] for hvac in hvacs: base['energy']['hvacs'].append(hvac.to_dict(abridged=True)) # add all unique shws to the dictionary base['energy']['shws'] = [shw.to_dict() for shw in self.shws] # add all unique ProgramTypes to the dictionary program_types = self.program_types base['energy']['program_types'] = [] for p_type in program_types: base['energy']['program_types'].append(p_type.to_dict(abridged=True)) # add all unique Schedules to the dictionary p_type_scheds = [] for p_type in program_types: for sched in p_type.schedules: self._check_and_add_schedule(sched, p_type_scheds) hvac_scheds = [] for hvac in hvacs: for sched in hvac.schedules: self._check_and_add_schedule(sched, hvac_scheds) all_scheds = hvac_scheds + p_type_scheds + self.misc_room_schedules + \ self.shade_schedules + schs schedules = tuple(set(all_scheds)) base['energy']['schedules'] = [] for sched in schedules: base['energy']['schedules'].append(sched.to_dict(abridged=True)) # add all unique ScheduleTypeLimits to the dictionary type_limits = [] for sched in schedules: t_lim = sched.schedule_type_limit if t_lim is not None and not self._instance_in_array(t_lim, type_limits): type_limits.append(t_lim) base['energy']['schedule_type_limits'] = \ [s_typ.to_dict() for s_typ in set(type_limits)] def _check_and_add_obj_constr_set(self, obj, construction_sets): """Check if a construction set is assigned to an object and add it to a list.""" c_set = obj.properties.energy._construction_set if c_set is not None: if not self._instance_in_array(c_set, construction_sets): construction_sets.append(c_set) def _check_and_add_obj_construction(self, obj, constructions): """Check if a construction is assigned to an object and add it to a list.""" constr = obj.properties.energy._construction if constr is not None: if not self._instance_in_array(constr, constructions): constructions.append(constr) def _check_and_add_shade_schedule(self, obj, schedules): """Check if a schedule is assigned to a shade and add it to a list.""" sched = obj.properties.energy._transmittance_schedule if sched is not None: if not self._instance_in_array(sched, schedules): schedules.append(sched) def _check_and_add_schedule(self, sched, schedules): """Check if a schedule is in a list and add it if not.""" if not self._instance_in_array(sched, schedules): schedules.append(sched) @staticmethod def _instance_in_array(object_instance, object_array): """Check if a specific object instance is already in an array. This can be much faster than `if object_instance in object_array` when you expect to be testing a lot of the same instance of an object for inclusion in an array since the builtin method uses an == operator to test inclusion. """ for val in object_array: if val is object_instance: return True return False
[docs] def ToString(self): return self.__repr__()
def __repr__(self): return 'Model Energy Properties: {}'.format(self.host.identifier)