cerc_idf basic implementation
This commit is contained in:
parent
c4636c2c3c
commit
5401064905
422
hub/exports/building_energy/cerc_idf.py
Normal file
422
hub/exports/building_energy/cerc_idf.py
Normal file
|
@ -0,0 +1,422 @@
|
||||||
|
"""
|
||||||
|
Idf exports one building to idf format
|
||||||
|
SPDX - License - Identifier: LGPL - 3.0 - or -later
|
||||||
|
Copyright © 2022 Concordia CERC group
|
||||||
|
Project Coder Guille Guillermo.GutierrezMorote@concordia.ca
|
||||||
|
Code contributors: Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca
|
||||||
|
Oriol Gavalda Torrellas oriol.gavalda@concordia.ca
|
||||||
|
"""
|
||||||
|
import copy
|
||||||
|
from datetime import datetime
|
||||||
|
from pathlib import Path
|
||||||
|
import hub.helpers.constants as cte
|
||||||
|
import hub.exports.building_energy.idf_helper as idf_cte
|
||||||
|
from hub.city_model_structure.attributes.schedule import Schedule
|
||||||
|
from hub.city_model_structure.building_demand.layer import Layer
|
||||||
|
|
||||||
|
|
||||||
|
class CercIdf:
|
||||||
|
"""
|
||||||
|
Exports city to IDF
|
||||||
|
"""
|
||||||
|
|
||||||
|
_schedules_added_to_idf = {}
|
||||||
|
_materials_added_to_idf = {}
|
||||||
|
_windows_added_to_idf = {'glazing_index': 0}
|
||||||
|
_constructions_added_to_idf = {}
|
||||||
|
|
||||||
|
def __init__(self, city, output_path, idf_file_path, idd_file_path, epw_file_path, export_type="Surfaces",
|
||||||
|
target_buildings=None):
|
||||||
|
self._city = city
|
||||||
|
self._output_path = str(output_path.resolve())
|
||||||
|
self._output_file_path = str((output_path / f'{city.name}.idf').resolve())
|
||||||
|
self._output_schedules_path = str((output_path / 'schedules.idf').resolve())
|
||||||
|
self._output_file_schedules_path = str((output_path / 'file_schedules.idf').resolve())
|
||||||
|
self._output_solid_materials_path = str((output_path / 'solid_materials.idf').resolve())
|
||||||
|
self._output_nomass_materials_path = str((output_path / 'nomass_materials.idf').resolve())
|
||||||
|
self._output_window_materials_path = str((output_path / 'window_materials.idf').resolve())
|
||||||
|
self._output_constructions_path = str((output_path / 'constructions.idf').resolve())
|
||||||
|
self._output_surfaces_path = str((output_path / 'surfaces.idf').resolve())
|
||||||
|
self._output_file_path = str((output_path / f'{city.name}.idf').resolve())
|
||||||
|
|
||||||
|
self._export_type = export_type
|
||||||
|
self._idd_file_path = str(idd_file_path)
|
||||||
|
self._idf_file_path = str(idf_file_path)
|
||||||
|
self._epw_file_path = str(epw_file_path)
|
||||||
|
self._target_buildings = target_buildings
|
||||||
|
self._adjacent_buildings = []
|
||||||
|
|
||||||
|
if target_buildings is None:
|
||||||
|
self._target_buildings = [building.name for building in self._city.buildings]
|
||||||
|
else:
|
||||||
|
for building_name in target_buildings:
|
||||||
|
building = city.city_object(building_name)
|
||||||
|
if building.neighbours is not None:
|
||||||
|
self._adjacent_buildings += building.neighbours
|
||||||
|
with open(self._idf_file_path, 'r') as base_idf:
|
||||||
|
lines = base_idf.readlines()
|
||||||
|
|
||||||
|
self._output_schedules = open(self._output_schedules_path, 'w')
|
||||||
|
self._output_file_schedules = open(self._output_file_schedules_path, 'w')
|
||||||
|
self._output_solid_materials = open(self._output_solid_materials_path, 'w')
|
||||||
|
self._output_nomass_materials = open(self._output_nomass_materials_path, 'w')
|
||||||
|
self._output_window_materials = open(self._output_window_materials_path, 'w')
|
||||||
|
self._output_constructions = open(self._output_constructions_path, 'w')
|
||||||
|
self._output_surfaces = open(self._output_surfaces_path, 'w')
|
||||||
|
|
||||||
|
with open(self._output_file_path, 'w') as self._idf_file:
|
||||||
|
self._idf_file.writelines(lines)
|
||||||
|
self._export()
|
||||||
|
|
||||||
|
def __del__(self):
|
||||||
|
self._output_schedules.close()
|
||||||
|
self._output_file_schedules.close()
|
||||||
|
self._output_solid_materials.close()
|
||||||
|
self._output_nomass_materials.close()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _write_to_idf_format(file, field, comment='', eol=','):
|
||||||
|
if comment != '':
|
||||||
|
comment = f' !- {comment}'
|
||||||
|
field = f' {field}{eol}'.ljust(26, ' ')
|
||||||
|
file.write(f'{field}{comment}\n')
|
||||||
|
else:
|
||||||
|
file.write(f'{field}{comment}')
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _matrix_to_list(points, lower_corner):
|
||||||
|
lower_x = lower_corner[0]
|
||||||
|
lower_y = lower_corner[1]
|
||||||
|
lower_z = lower_corner[2]
|
||||||
|
points_list = []
|
||||||
|
for point in points:
|
||||||
|
point_tuple = (point[0] - lower_x, point[1] - lower_y, point[2] - lower_z)
|
||||||
|
points_list.append(point_tuple)
|
||||||
|
return points_list
|
||||||
|
|
||||||
|
def _add_surfaces(self, building, zone_name):
|
||||||
|
# Verify if create building surfaces "by hand" it's faster wwr it's missing
|
||||||
|
zone_name = f'{zone_name}'
|
||||||
|
file = self._output_surfaces
|
||||||
|
for thermal_zone in building.thermal_zones_from_internal_zones:
|
||||||
|
for index, boundary in enumerate(thermal_zone.thermal_boundaries):
|
||||||
|
surface_type = idf_cte.idf_surfaces[boundary.parent_surface.type]
|
||||||
|
outside_boundary_condition = idf_cte.OUTDOORS
|
||||||
|
sun_exposure = idf_cte.SUN_EXPOSED
|
||||||
|
wind_exposure = idf_cte.WIND_EXPOSED
|
||||||
|
outside_boundary_condition_object = idf_cte.EMPTY
|
||||||
|
name = f'Building_{building.name}_surface_{index}'
|
||||||
|
construction_name = f'{boundary.construction_name} {boundary.parent_surface.type}'
|
||||||
|
space_name = idf_cte.EMPTY
|
||||||
|
if boundary.parent_surface.type == cte.GROUND:
|
||||||
|
outside_boundary_condition = idf_cte.GROUND
|
||||||
|
sun_exposure = idf_cte.NON_SUN_EXPOSED
|
||||||
|
wind_exposure = idf_cte.NON_WIND_EXPOSED
|
||||||
|
if boundary.parent_surface.percentage_shared is not None and boundary.parent_surface.percentage_shared > 0.5:
|
||||||
|
outside_boundary_condition_object = f'Building_{building.name}_surface_{index}'
|
||||||
|
outside_boundary_condition = idf_cte.SURFACE
|
||||||
|
sun_exposure = idf_cte.NON_SUN_EXPOSED
|
||||||
|
wind_exposure = idf_cte.NON_WIND_EXPOSED
|
||||||
|
self._write_to_idf_format(file, idf_cte.BUILDING_SURFACE)
|
||||||
|
self._write_to_idf_format(file, name, 'Name')
|
||||||
|
self._write_to_idf_format(file, surface_type, 'Surface Type')
|
||||||
|
self._write_to_idf_format(file, construction_name, 'Construction Name')
|
||||||
|
self._write_to_idf_format(file, zone_name, 'Zone Name')
|
||||||
|
self._write_to_idf_format(file, space_name, 'Space Name')
|
||||||
|
self._write_to_idf_format(file, outside_boundary_condition, 'Outside Boundary Condition')
|
||||||
|
self._write_to_idf_format(file, outside_boundary_condition_object, 'Outside Boundary Condition Object')
|
||||||
|
self._write_to_idf_format(file, sun_exposure, 'Sun Exposure')
|
||||||
|
self._write_to_idf_format(file, wind_exposure, 'Wind Exposure')
|
||||||
|
self._write_to_idf_format(file, idf_cte.AUTOCALCULATE, 'View Factor to Ground')
|
||||||
|
self._write_to_idf_format(file, idf_cte.AUTOCALCULATE, 'Number of Vertices')
|
||||||
|
coordinates = self._matrix_to_list(boundary.parent_surface.solid_polygon.coordinates,
|
||||||
|
self._city.lower_corner)
|
||||||
|
eol = ','
|
||||||
|
for i, coordinate in enumerate(coordinates):
|
||||||
|
if i == len(coordinates) -1:
|
||||||
|
eol = ';'
|
||||||
|
self._write_to_idf_format(file, coordinate[0], f'Vertex {i} Xcoordinate')
|
||||||
|
self._write_to_idf_format(file, coordinate[1], f'Vertex {i} Ycoordinate')
|
||||||
|
self._write_to_idf_format(file, coordinate[2], f'Vertex {i} Zcoordinate', eol)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _create_infiltration_schedules(thermal_zone):
|
||||||
|
_infiltration_schedules = []
|
||||||
|
if thermal_zone.thermal_control is None:
|
||||||
|
return []
|
||||||
|
for hvac_availability_schedule in thermal_zone.thermal_control.hvac_availability_schedules:
|
||||||
|
_schedule = Schedule()
|
||||||
|
_schedule.type = cte.INFILTRATION
|
||||||
|
_schedule.data_type = cte.FRACTION
|
||||||
|
_schedule.time_step = cte.HOUR
|
||||||
|
_schedule.time_range = cte.DAY
|
||||||
|
_schedule.day_types = copy.deepcopy(hvac_availability_schedule.day_types)
|
||||||
|
_infiltration_values = []
|
||||||
|
for hvac_value in hvac_availability_schedule.values:
|
||||||
|
if hvac_value == 0:
|
||||||
|
_infiltration_values.append(1.0)
|
||||||
|
else:
|
||||||
|
if thermal_zone.infiltration_rate_system_off == 0:
|
||||||
|
_infiltration_values.append(0.0)
|
||||||
|
else:
|
||||||
|
_infiltration_values.append(
|
||||||
|
thermal_zone.infiltration_rate_system_on / thermal_zone.infiltration_rate_system_off)
|
||||||
|
_schedule.values = _infiltration_values
|
||||||
|
_infiltration_schedules.append(_schedule)
|
||||||
|
return _infiltration_schedules
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _create_ventilation_schedules(thermal_zone):
|
||||||
|
_ventilation_schedules = []
|
||||||
|
if thermal_zone.thermal_control is None:
|
||||||
|
return []
|
||||||
|
for hvac_availability_schedule in thermal_zone.thermal_control.hvac_availability_schedules:
|
||||||
|
_schedule = Schedule()
|
||||||
|
_schedule.type = cte.VENTILATION
|
||||||
|
_schedule.data_type = cte.FRACTION
|
||||||
|
_schedule.time_step = cte.HOUR
|
||||||
|
_schedule.time_range = cte.DAY
|
||||||
|
_schedule.day_types = copy.deepcopy(hvac_availability_schedule.day_types)
|
||||||
|
_ventilation_schedules = thermal_zone.thermal_control.hvac_availability_schedules
|
||||||
|
return _ventilation_schedules
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _create_constant_value_schedules(value, amount):
|
||||||
|
_schedule = Schedule()
|
||||||
|
_schedule.type = ''
|
||||||
|
_schedule.data_type = cte.ANY_NUMBER
|
||||||
|
_schedule.time_step = cte.HOUR
|
||||||
|
_schedule.time_range = cte.DAY
|
||||||
|
_schedule.day_types = ['monday',
|
||||||
|
'tuesday',
|
||||||
|
'wednesday',
|
||||||
|
'thursday',
|
||||||
|
'friday',
|
||||||
|
'saturday',
|
||||||
|
'sunday',
|
||||||
|
'holiday',
|
||||||
|
'winter_design_day',
|
||||||
|
'summer_design_day']
|
||||||
|
_schedule.values = [value for _ in range(0, amount)]
|
||||||
|
return [_schedule]
|
||||||
|
|
||||||
|
def _add_file_schedules(self, usage, schedule_type, schedules):
|
||||||
|
schedule_name = f'{schedule_type} schedules {usage}'
|
||||||
|
for schedule in schedules:
|
||||||
|
if schedule_name not in self._schedules_added_to_idf:
|
||||||
|
self._schedules_added_to_idf[schedule_name] = True
|
||||||
|
file_name = str((Path(self._output_path) / f'{schedule_type} schedules {usage.replace("/", "_")}.csv').resolve())
|
||||||
|
with open(file_name, 'w', encoding='utf8') as file:
|
||||||
|
for value in schedule.values:
|
||||||
|
file.write(f'{str(value)},\n')
|
||||||
|
file = self._output_file_schedules
|
||||||
|
self._write_to_idf_format(file, idf_cte.FILE_SCHEDULE)
|
||||||
|
self._write_to_idf_format(file, schedule_name, 'Name')
|
||||||
|
self._write_to_idf_format(file, idf_cte.idf_type_limits[schedule.data_type], 'Schedule Type Limits Name')
|
||||||
|
self._write_to_idf_format(file, Path(file_name).name, 'File Name')
|
||||||
|
self._write_to_idf_format(file, 1, 'Column Number')
|
||||||
|
self._write_to_idf_format(file, 0, 'Rows to Skip at Top')
|
||||||
|
self._write_to_idf_format(file, 8760, 'Number of Hours of Data')
|
||||||
|
self._write_to_idf_format(file, 'Comma', 'Column Separator')
|
||||||
|
self._write_to_idf_format(file, 'No', 'Interpolate to Timestep')
|
||||||
|
self._write_to_idf_format(file, '60', 'Minutes per Item')
|
||||||
|
self._write_to_idf_format(file, 'Yes', 'Adjust Schedule for Daylight Savings', ';')
|
||||||
|
|
||||||
|
def _add_idf_schedules(self, usage, schedule_type, schedules):
|
||||||
|
if len(schedules) < 1:
|
||||||
|
return
|
||||||
|
schedule_name = f'{schedule_type} schedules {usage}'
|
||||||
|
if schedule_name not in self._schedules_added_to_idf:
|
||||||
|
self._schedules_added_to_idf[schedule_name] = True
|
||||||
|
file = self._output_schedules
|
||||||
|
self._write_to_idf_format(file, idf_cte.COMPACT_SCHEDULE)
|
||||||
|
self._write_to_idf_format(file, schedule_name, 'Name')
|
||||||
|
self._write_to_idf_format(file, idf_cte.idf_type_limits[schedules[0].data_type], 'Schedule Type Limits Name')
|
||||||
|
self._write_to_idf_format(file, 'Through: 12/31', 'Field 1')
|
||||||
|
counter = 1
|
||||||
|
for j, schedule in enumerate(schedules):
|
||||||
|
_val = schedule.values
|
||||||
|
_new_field = ''
|
||||||
|
for day_type in schedule.day_types:
|
||||||
|
_new_field += f' {idf_cte.idf_day_types[day_type]}'
|
||||||
|
self._write_to_idf_format(file, f'For:{_new_field}', f'Field {j * 25 + 2}')
|
||||||
|
counter += 1
|
||||||
|
for i, _ in enumerate(_val):
|
||||||
|
self._write_to_idf_format(file, f'Until: {i + 1:02d}:00,{_val[i]}', f'Field {j * 25 + 3 + i}')
|
||||||
|
counter += 1
|
||||||
|
self._write_to_idf_format(file, 'For AllOtherDays', f'Field {counter + 1}')
|
||||||
|
self._write_to_idf_format(file, 'Until: 24:00,0.0', f'Field {counter + 2}', ';')
|
||||||
|
|
||||||
|
def _add_solid_material(self, layer):
|
||||||
|
file = self._output_solid_materials
|
||||||
|
self._write_to_idf_format(file, idf_cte.SOLID_MATERIAL)
|
||||||
|
self._write_to_idf_format(file, layer.material_name, 'Name')
|
||||||
|
self._write_to_idf_format(file, idf_cte.ROUGHNESS, 'Roughness')
|
||||||
|
self._write_to_idf_format(file, layer.thickness, 'Thickness')
|
||||||
|
self._write_to_idf_format(file, layer.conductivity, 'Conductivity')
|
||||||
|
self._write_to_idf_format(file, layer.density, 'Density')
|
||||||
|
self._write_to_idf_format(file, layer.specific_heat, 'Specific Heat')
|
||||||
|
self._write_to_idf_format(file, layer.thermal_absorptance, 'Thermal Absorptance')
|
||||||
|
self._write_to_idf_format(file, layer.solar_absorptance, 'Solar Absorptance')
|
||||||
|
self._write_to_idf_format(file, layer.visible_absorptance, 'Visible Absorptance', ';')
|
||||||
|
|
||||||
|
def _add_nomass_material(self, layer):
|
||||||
|
file = self._output_nomass_materials
|
||||||
|
self._write_to_idf_format(file, idf_cte.NOMASS_MATERIAL)
|
||||||
|
self._write_to_idf_format(file, layer.material_name, 'Name')
|
||||||
|
self._write_to_idf_format(file, idf_cte.ROUGHNESS, 'Roughness')
|
||||||
|
self._write_to_idf_format(file, layer.thermal_resistance, 'Thermal Resistance')
|
||||||
|
self._write_to_idf_format(file, 0.9, 'Thermal Absorptance')
|
||||||
|
self._write_to_idf_format(file, 0.7, 'Solar Absorptance')
|
||||||
|
self._write_to_idf_format(file, 0.7, 'Visible Absorptance', ';')
|
||||||
|
|
||||||
|
def _add_materials(self, thermal_boundary):
|
||||||
|
for layer in thermal_boundary.layers:
|
||||||
|
if layer.material_name not in self._materials_added_to_idf:
|
||||||
|
self._materials_added_to_idf[layer.material_name] = True
|
||||||
|
if layer.no_mass:
|
||||||
|
self._add_nomass_material(layer)
|
||||||
|
else:
|
||||||
|
self._add_solid_material(layer)
|
||||||
|
|
||||||
|
def _add_default_material(self):
|
||||||
|
layer = Layer()
|
||||||
|
layer.material_name = 'DefaultMaterial'
|
||||||
|
layer.thickness = 0.1
|
||||||
|
layer.conductivity = 0.1
|
||||||
|
layer.density = 1000
|
||||||
|
layer.specific_heat = 1000
|
||||||
|
layer.thermal_absorptance = 0.9
|
||||||
|
layer.solar_absorptance = 0.9
|
||||||
|
layer.visible_absorptance = 0.7
|
||||||
|
self._add_solid_material(layer)
|
||||||
|
return layer
|
||||||
|
|
||||||
|
def _add_window_materials(self, thermal_opening):
|
||||||
|
name = f'{thermal_opening.overall_u_value}_{thermal_opening.g_value}'
|
||||||
|
if name not in self._windows_added_to_idf:
|
||||||
|
glazing_index = self._windows_added_to_idf['glazing_index'] + 1
|
||||||
|
self._windows_added_to_idf[name] = True
|
||||||
|
self._windows_added_to_idf['glazing_index'] = glazing_index # increase the count
|
||||||
|
file = self._output_window_materials
|
||||||
|
self._write_to_idf_format(file, idf_cte.WINDOW_MATERIAL)
|
||||||
|
self._write_to_idf_format(file, f'glazing_{glazing_index}', 'Name')
|
||||||
|
self._write_to_idf_format(file, thermal_opening.overall_u_value, 'UFactor')
|
||||||
|
self._write_to_idf_format(file, thermal_opening.g_value, 'Solar Heat Gain Coefficient', ';')
|
||||||
|
|
||||||
|
def _add_constructions(self, thermal_boundary):
|
||||||
|
"""
|
||||||
|
CONSTRUCTION,
|
||||||
|
1000_1900_6 Roof, !- Name
|
||||||
|
Asphalt 1, !- Outside Layer
|
||||||
|
virtual_no_mass_13, !- Layer 2
|
||||||
|
MW Glass Wool (rolls); !- Layer 3
|
||||||
|
"""
|
||||||
|
if thermal_boundary.layers is None:
|
||||||
|
thermal_boundary.layers = [self._add_default_material()]
|
||||||
|
name = f'{thermal_boundary.construction_name} {thermal_boundary.parent_surface.type}'
|
||||||
|
|
||||||
|
if name not in self._constructions_added_to_idf:
|
||||||
|
self._constructions_added_to_idf[name] = True
|
||||||
|
file = self._output_constructions
|
||||||
|
self._write_to_idf_format(file, idf_cte.CONSTRUCTION)
|
||||||
|
self._write_to_idf_format(file, name, 'Name')
|
||||||
|
eol = ','
|
||||||
|
if len(thermal_boundary.layers) == 1:
|
||||||
|
eol = ';'
|
||||||
|
self._write_to_idf_format(file, thermal_boundary.layers[0].material_name, 'Outside Layer', eol)
|
||||||
|
for i in range(1, len(thermal_boundary.layers) - 1):
|
||||||
|
comment = f'Layer {i + 1}'
|
||||||
|
material_name = thermal_boundary.layers[i].material_name
|
||||||
|
if i == len(thermal_boundary.layers) - 2:
|
||||||
|
eol = ';'
|
||||||
|
self._write_to_idf_format(file, material_name, comment, eol)
|
||||||
|
|
||||||
|
def _add_windows_constructions(self):
|
||||||
|
glazing_index = self._windows_added_to_idf['glazing_index'] + 1
|
||||||
|
for i in range(1, glazing_index):
|
||||||
|
construction_name = f'window_construction_{i}'
|
||||||
|
material_name = f'glazing_{i}'
|
||||||
|
if construction_name not in self._constructions_added_to_idf:
|
||||||
|
self._constructions_added_to_idf[construction_name] = True
|
||||||
|
file = self._output_constructions
|
||||||
|
self._write_to_idf_format(file, idf_cte.CONSTRUCTION)
|
||||||
|
self._write_to_idf_format(file, construction_name, 'Name')
|
||||||
|
self._write_to_idf_format(file, material_name, 'Outside Layer', ';')
|
||||||
|
|
||||||
|
def _merge_files(self):
|
||||||
|
self._output_schedules.flush()
|
||||||
|
with open(self._output_schedules_path, 'r') as file:
|
||||||
|
lines = file.readlines()
|
||||||
|
self._idf_file.writelines(lines)
|
||||||
|
self._output_file_schedules.flush()
|
||||||
|
with open(self._output_file_schedules_path, 'r') as file:
|
||||||
|
lines = file.readlines()
|
||||||
|
self._idf_file.writelines(lines)
|
||||||
|
self._output_solid_materials.flush()
|
||||||
|
with open(self._output_solid_materials_path, 'r') as file:
|
||||||
|
lines = file.readlines()
|
||||||
|
self._idf_file.writelines(lines)
|
||||||
|
self._output_nomass_materials.flush()
|
||||||
|
with open(self._output_nomass_materials_path, 'r') as file:
|
||||||
|
lines = file.readlines()
|
||||||
|
self._idf_file.writelines(lines)
|
||||||
|
self._output_window_materials.flush()
|
||||||
|
with open(self._output_window_materials_path, 'r') as file:
|
||||||
|
lines = file.readlines()
|
||||||
|
self._idf_file.writelines(lines)
|
||||||
|
self._output_constructions.flush()
|
||||||
|
with open(self._output_constructions_path, 'r') as file:
|
||||||
|
lines = file.readlines()
|
||||||
|
self._idf_file.writelines(lines)
|
||||||
|
self._output_surfaces.flush()
|
||||||
|
with open(self._output_surfaces_path, 'r') as file:
|
||||||
|
lines = file.readlines()
|
||||||
|
self._idf_file.writelines(lines)
|
||||||
|
|
||||||
|
def _export(self):
|
||||||
|
for building in self._city.buildings:
|
||||||
|
is_target = building.name in self._target_buildings or building.name in self._adjacent_buildings
|
||||||
|
for internal_zone in building.internal_zones:
|
||||||
|
if internal_zone.thermal_zones_from_internal_zones is None:
|
||||||
|
self._target_buildings.remove(building.name)
|
||||||
|
is_target = False
|
||||||
|
continue
|
||||||
|
for thermal_zone in internal_zone.thermal_zones_from_internal_zones:
|
||||||
|
if is_target:
|
||||||
|
start = datetime.now()
|
||||||
|
service_temperature = thermal_zone.domestic_hot_water.service_temperature
|
||||||
|
usage = thermal_zone.usage_name
|
||||||
|
occ = thermal_zone.occupancy
|
||||||
|
if occ.occupancy_density == 0:
|
||||||
|
total_heat = 0
|
||||||
|
else:
|
||||||
|
total_heat = (
|
||||||
|
occ.sensible_convective_internal_gain +
|
||||||
|
occ.sensible_radiative_internal_gain +
|
||||||
|
occ.latent_internal_gain
|
||||||
|
) / occ.occupancy_density
|
||||||
|
self._add_idf_schedules(usage, 'Infiltration', self._create_infiltration_schedules(thermal_zone))
|
||||||
|
self._add_idf_schedules(usage, 'Ventilation', self._create_ventilation_schedules(thermal_zone))
|
||||||
|
self._add_idf_schedules(usage, 'Occupancy', thermal_zone.occupancy.occupancy_schedules)
|
||||||
|
self._add_idf_schedules(usage, 'HVAC AVAIL', thermal_zone.thermal_control.hvac_availability_schedules)
|
||||||
|
self._add_idf_schedules(usage, 'Heating thermostat', thermal_zone.thermal_control.heating_set_point_schedules)
|
||||||
|
self._add_idf_schedules(usage, 'Cooling thermostat', thermal_zone.thermal_control.cooling_set_point_schedules)
|
||||||
|
self._add_idf_schedules(usage, 'Lighting', thermal_zone.lighting.schedules)
|
||||||
|
self._add_idf_schedules(usage, 'Appliance', thermal_zone.appliances.schedules)
|
||||||
|
self._add_idf_schedules(usage, 'DHW_prof', thermal_zone.domestic_hot_water.schedules)
|
||||||
|
self._add_idf_schedules(usage, 'DHW_temp', self._create_constant_value_schedules(service_temperature, 24))
|
||||||
|
self._add_idf_schedules(usage, 'Activity Level', self._create_constant_value_schedules(total_heat, 24))
|
||||||
|
self._add_file_schedules(usage, 'cold_temp', self._create_constant_value_schedules(building.cold_water_temperature[cte.HOUR], 24))
|
||||||
|
for thermal_boundary in thermal_zone.thermal_boundaries:
|
||||||
|
self._add_materials(thermal_boundary)
|
||||||
|
self._add_constructions(thermal_boundary)
|
||||||
|
for thermal_opening in thermal_boundary.thermal_openings:
|
||||||
|
self._add_window_materials(thermal_opening)
|
||||||
|
self._add_windows_constructions()
|
||||||
|
if is_target:
|
||||||
|
self._add_surfaces(building, building.name)
|
||||||
|
|
||||||
|
|
||||||
|
# Merge files
|
||||||
|
self._merge_files()
|
|
@ -107,6 +107,7 @@ class Idf:
|
||||||
else:
|
else:
|
||||||
for building_name in target_buildings:
|
for building_name in target_buildings:
|
||||||
building = city.city_object(building_name)
|
building = city.city_object(building_name)
|
||||||
|
print('Name: ', building_name)
|
||||||
if building.neighbours is not None:
|
if building.neighbours is not None:
|
||||||
self._adjacent_buildings += building.neighbours
|
self._adjacent_buildings += building.neighbours
|
||||||
self._export()
|
self._export()
|
||||||
|
@ -520,7 +521,7 @@ class Idf:
|
||||||
is_target = building.name in self._target_buildings or building.name in self._adjacent_buildings
|
is_target = building.name in self._target_buildings or building.name in self._adjacent_buildings
|
||||||
for internal_zone in building.internal_zones:
|
for internal_zone in building.internal_zones:
|
||||||
if internal_zone.thermal_zones_from_internal_zones is None:
|
if internal_zone.thermal_zones_from_internal_zones is None:
|
||||||
self._target_buildings.remoidf_surface_typeve(building.name)
|
self._target_buildings.remove(building.name)
|
||||||
is_target = False
|
is_target = False
|
||||||
continue
|
continue
|
||||||
for thermal_zone in internal_zone.thermal_zones_from_internal_zones:
|
for thermal_zone in internal_zone.thermal_zones_from_internal_zones:
|
||||||
|
@ -571,9 +572,8 @@ class Idf:
|
||||||
if self._export_type == "Surfaces":
|
if self._export_type == "Surfaces":
|
||||||
if is_target:
|
if is_target:
|
||||||
if building.thermal_zones_from_internal_zones is not None:
|
if building.thermal_zones_from_internal_zones is not None:
|
||||||
start = datetime.datetime.now()
|
pass
|
||||||
self._add_surfaces(building, building.name)
|
# self._add_surfaces(building, building.name)
|
||||||
print(f'add surfaces {datetime.datetime.now() - start}')
|
|
||||||
else:
|
else:
|
||||||
self._add_pure_geometry(building, building.name)
|
self._add_pure_geometry(building, building.name)
|
||||||
else:
|
else:
|
||||||
|
@ -624,6 +624,10 @@ class Idf:
|
||||||
self._idf.removeidfobject(window)
|
self._idf.removeidfobject(window)
|
||||||
|
|
||||||
self._idf.saveas(str(self._output_file))
|
self._idf.saveas(str(self._output_file))
|
||||||
|
for building in self._city.buildings:
|
||||||
|
if self._export_type == "Surfaces":
|
||||||
|
if is_target and building.thermal_zones_from_internal_zones is not None:
|
||||||
|
self._add_surfaces(building, building.name)
|
||||||
return self._idf
|
return self._idf
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
|
@ -703,8 +707,13 @@ class Idf:
|
||||||
self._idf.set_wwr(wwr)
|
self._idf.set_wwr(wwr)
|
||||||
|
|
||||||
def _add_surfaces(self, building, zone_name):
|
def _add_surfaces(self, building, zone_name):
|
||||||
|
start = datetime.datetime.now()
|
||||||
|
print(f'thermal_zones {len(building.thermal_zones_from_internal_zones)}')
|
||||||
for thermal_zone in building.thermal_zones_from_internal_zones:
|
for thermal_zone in building.thermal_zones_from_internal_zones:
|
||||||
|
print(f'thermal zone: {datetime.datetime.now() - start}')
|
||||||
for index, boundary in enumerate(thermal_zone.thermal_boundaries):
|
for index, boundary in enumerate(thermal_zone.thermal_boundaries):
|
||||||
|
print(f'{index} boundary: {datetime.datetime.now() - start}')
|
||||||
|
|
||||||
idf_surface_type = self.idf_surfaces[boundary.parent_surface.type]
|
idf_surface_type = self.idf_surfaces[boundary.parent_surface.type]
|
||||||
outside_boundary_condition = 'Outdoors'
|
outside_boundary_condition = 'Outdoors'
|
||||||
sun_exposure = 'SunExposed'
|
sun_exposure = 'SunExposed'
|
||||||
|
@ -731,12 +740,14 @@ class Idf:
|
||||||
else:
|
else:
|
||||||
construction_name = f'{boundary.construction_name} {boundary.parent_surface.type}'
|
construction_name = f'{boundary.construction_name} {boundary.parent_surface.type}'
|
||||||
_kwargs['Construction_Name'] = construction_name
|
_kwargs['Construction_Name'] = construction_name
|
||||||
|
start = datetime.datetime.now()
|
||||||
surface = self._idf.newidfobject(self._SURFACE, **_kwargs)
|
surface = self._idf.newidfobject(self._SURFACE, **_kwargs)
|
||||||
|
print(f'create surface: {datetime.datetime.now() - start}')
|
||||||
coordinates = self._matrix_to_list(boundary.parent_surface.solid_polygon.coordinates,
|
coordinates = self._matrix_to_list(boundary.parent_surface.solid_polygon.coordinates,
|
||||||
self._city.lower_corner)
|
self._city.lower_corner)
|
||||||
|
|
||||||
surface.setcoords(coordinates)
|
surface.setcoords(coordinates)
|
||||||
|
print(f'set coords surface: {datetime.datetime.now() - start}')
|
||||||
|
|
||||||
if self._lod >= 3:
|
if self._lod >= 3:
|
||||||
for internal_zone in building.internal_zones:
|
for internal_zone in building.internal_zones:
|
||||||
|
|
62
hub/exports/building_energy/idf_files/base.idf
Normal file
62
hub/exports/building_energy/idf_files/base.idf
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
!- Linux Line endings
|
||||||
|
|
||||||
|
Version,
|
||||||
|
24.1; !- Version Identifier
|
||||||
|
|
||||||
|
SimulationControl,
|
||||||
|
No, !- Do Zone Sizing Calculation
|
||||||
|
No, !- Do System Sizing Calculation
|
||||||
|
No, !- Do Plant Sizing Calculation
|
||||||
|
No, !- Run Simulation for Sizing Periods
|
||||||
|
Yes, !- Run Simulation for Weather File Run Periods
|
||||||
|
No, !- Do HVAC Sizing Simulation for Sizing Periods
|
||||||
|
1; !- Maximum Number of HVAC Sizing Simulation Passes
|
||||||
|
|
||||||
|
Building,
|
||||||
|
Buildings in b'Montreal', !- Name
|
||||||
|
0, !- North Axis
|
||||||
|
Suburbs, !- Terrain
|
||||||
|
0.04, !- Loads Convergence Tolerance Value
|
||||||
|
0.4, !- Temperature Convergence Tolerance Value
|
||||||
|
FullExterior, !- Solar Distribution
|
||||||
|
25, !- Maximum Number of Warmup Days
|
||||||
|
6; !- Minimum Number of Warmup Days
|
||||||
|
|
||||||
|
Timestep,
|
||||||
|
4; !- Number of Timesteps per Hour
|
||||||
|
|
||||||
|
RunPeriod,
|
||||||
|
Run Period 1, !- Name
|
||||||
|
1, !- Begin Month
|
||||||
|
1, !- Begin Day of Month
|
||||||
|
, !- Begin Year
|
||||||
|
12, !- End Month
|
||||||
|
31, !- End Day of Month
|
||||||
|
, !- End Year
|
||||||
|
Tuesday, !- Day of Week for Start Day
|
||||||
|
Yes, !- Use Weather File Holidays and Special Days
|
||||||
|
Yes, !- Use Weather File Daylight Saving Period
|
||||||
|
No, !- Apply Weekend Holiday Rule
|
||||||
|
Yes, !- Use Weather File Rain Indicators
|
||||||
|
Yes; !- Use Weather File Snow Indicators
|
||||||
|
|
||||||
|
SCHEDULETYPELIMITS,
|
||||||
|
Any Number, !- Name
|
||||||
|
, !- Lower Limit Value
|
||||||
|
, !- Upper Limit Value
|
||||||
|
, !- Numeric Type
|
||||||
|
Dimensionless; !- Unit Type
|
||||||
|
|
||||||
|
SCHEDULETYPELIMITS,
|
||||||
|
Fraction, !- Name
|
||||||
|
0, !- Lower Limit Value
|
||||||
|
1, !- Upper Limit Value
|
||||||
|
Continuous, !- Numeric Type
|
||||||
|
Dimensionless; !- Unit Type
|
||||||
|
|
||||||
|
SCHEDULETYPELIMITS,
|
||||||
|
On/Off, !- Name
|
||||||
|
0, !- Lower Limit Value
|
||||||
|
1, !- Upper Limit Value
|
||||||
|
Discrete, !- Numeric Type
|
||||||
|
Dimensionless; !- Unit Type
|
47
hub/exports/building_energy/idf_helper/__init__.py
Normal file
47
hub/exports/building_energy/idf_helper/__init__.py
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
import hub.helpers.constants as cte
|
||||||
|
|
||||||
|
BUILDING_SURFACE = '\nBUILDINGSURFACE:DETAILED,\n'
|
||||||
|
COMPACT_SCHEDULE = '\nSCHEDULE:COMPACT,\n'
|
||||||
|
FILE_SCHEDULE = '\nSCHEDULE:FILE,\n'
|
||||||
|
NOMASS_MATERIAL = '\nMATERIAL:NOMASS,\n'
|
||||||
|
SOLID_MATERIAL = '\nMATERIAL,\n'
|
||||||
|
WINDOW_MATERIAL = '\nWINDOWMATERIAL:SIMPLEGLAZINGSYSTEM,\n'
|
||||||
|
CONSTRUCTION = '\nCONSTRUCTION,\n'
|
||||||
|
|
||||||
|
AUTOCALCULATE = 'autocalculate'
|
||||||
|
ROUGHNESS = 'MediumRough'
|
||||||
|
OUTDOORS = 'Outdoors'
|
||||||
|
GROUND = 'Ground'
|
||||||
|
SURFACE = 'Surface'
|
||||||
|
SUN_EXPOSED = 'SunExposed'
|
||||||
|
WIND_EXPOSED = 'WindExposed'
|
||||||
|
NON_SUN_EXPOSED = 'NoSun'
|
||||||
|
NON_WIND_EXPOSED = 'NoWind'
|
||||||
|
EMPTY = ''
|
||||||
|
|
||||||
|
idf_surfaces = {
|
||||||
|
cte.WALL: 'wall',
|
||||||
|
cte.GROUND: 'floor',
|
||||||
|
cte.ROOF: 'roof'
|
||||||
|
}
|
||||||
|
|
||||||
|
idf_type_limits = {
|
||||||
|
cte.ON_OFF: 'on/off',
|
||||||
|
cte.FRACTION: 'Fraction',
|
||||||
|
cte.ANY_NUMBER: 'Any Number',
|
||||||
|
cte.CONTINUOUS: 'Continuous',
|
||||||
|
cte.DISCRETE: 'Discrete'
|
||||||
|
}
|
||||||
|
|
||||||
|
idf_day_types = {
|
||||||
|
cte.MONDAY: 'Monday',
|
||||||
|
cte.TUESDAY: 'Tuesday',
|
||||||
|
cte.WEDNESDAY: 'Wednesday',
|
||||||
|
cte.THURSDAY: 'Thursday',
|
||||||
|
cte.FRIDAY: 'Friday',
|
||||||
|
cte.SATURDAY: 'Saturday',
|
||||||
|
cte.SUNDAY: 'Sunday',
|
||||||
|
cte.HOLIDAY: 'Holidays',
|
||||||
|
cte.WINTER_DESIGN_DAY: 'WinterDesignDay',
|
||||||
|
cte.SUMMER_DESIGN_DAY: 'SummerDesignDay'
|
||||||
|
}
|
|
@ -11,6 +11,7 @@ import requests
|
||||||
|
|
||||||
from hub.exports.building_energy.energy_ade import EnergyAde
|
from hub.exports.building_energy.energy_ade import EnergyAde
|
||||||
from hub.exports.building_energy.idf import Idf
|
from hub.exports.building_energy.idf import Idf
|
||||||
|
from hub.exports.building_energy.cerc_idf import CercIdf
|
||||||
from hub.exports.building_energy.insel.insel_monthly_energy_balance import InselMonthlyEnergyBalance
|
from hub.exports.building_energy.insel.insel_monthly_energy_balance import InselMonthlyEnergyBalance
|
||||||
from hub.helpers.utils import validate_import_export_type
|
from hub.helpers.utils import validate_import_export_type
|
||||||
from hub.imports.weather.helpers.weather import Weather as wh
|
from hub.imports.weather.helpers.weather import Weather as wh
|
||||||
|
@ -60,6 +61,18 @@ class EnergyBuildingsExportsFactory:
|
||||||
return Idf(self._city, self._path, (idf_data_path / 'Minimal.idf'), (idf_data_path / 'Energy+.idd'), weather_path,
|
return Idf(self._city, self._path, (idf_data_path / 'Minimal.idf'), (idf_data_path / 'Energy+.idd'), weather_path,
|
||||||
target_buildings=self._target_buildings)
|
target_buildings=self._target_buildings)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def _cerc_idf(self):
|
||||||
|
idf_data_path = (Path(__file__).parent / './building_energy/idf_files/').resolve()
|
||||||
|
url = wh().epw_file(self._city.region_code)
|
||||||
|
weather_path = (Path(__file__).parent.parent / f'data/weather/epw/{url.rsplit("/", 1)[1]}').resolve()
|
||||||
|
if not weather_path.exists():
|
||||||
|
with open(weather_path, 'wb') as epw_file:
|
||||||
|
epw_file.write(requests.get(url, allow_redirects=True).content)
|
||||||
|
return CercIdf(self._city, self._path, (idf_data_path / 'base.idf'), (idf_data_path / 'Energy+.idd'), weather_path,
|
||||||
|
target_buildings=self._target_buildings)
|
||||||
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def _insel_monthly_energy_balance(self):
|
def _insel_monthly_energy_balance(self):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -144,6 +144,7 @@ class TestExports(TestCase):
|
||||||
UsageFactory('nrcan', city).enrich()
|
UsageFactory('nrcan', city).enrich()
|
||||||
WeatherFactory('epw', city).enrich()
|
WeatherFactory('epw', city).enrich()
|
||||||
try:
|
try:
|
||||||
EnergyBuildingsExportsFactory('idf', city, self._output_path, target_buildings=[1]).export()
|
EnergyBuildingsExportsFactory('cerc_idf', city, self._output_path)._cerc_idf
|
||||||
|
|
||||||
except Exception:
|
except Exception:
|
||||||
self.fail("Idf ExportsFactory raised ExceptionType unexpectedly!")
|
self.fail("Idf ExportsFactory raised ExceptionType unexpectedly!")
|
||||||
|
|
Loading…
Reference in New Issue
Block a user