# 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_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 materials 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
"""
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)
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_all(self, raise_exception=True):
"""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.
Returns:
A text string with all errors that were found. This string will be empty
of no errors were found.
"""
msgs = []
# perform checks for key honeybee model schema rules
msgs.append(self.check_duplicate_construction_set_identifiers(False))
msgs.append(self.check_duplicate_program_type_identifiers(False))
msgs.append(self.check_duplicate_hvac_identifiers(False))
msgs.append(self.check_duplicate_shw_identifiers(False))
# output a final report of errors or raise an exception
full_msgs = [msg for msg in msgs if 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_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_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 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)
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.
"""
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)