Compare commits

...

53 Commits

Author SHA1 Message Date
7bd7b680b3 Update hub/version.py 2024-12-12 14:14:34 -05:00
Guille
765784135d Correct old ep export and optimize code 2024-12-12 20:09:58 +01:00
08e7f68adf Update hub/version.py 2024-12-07 02:48:32 -05:00
Guille
464abea93a Correct package 2024-12-07 08:46:05 +01:00
7d057ece81 Update hub/version.py 2024-12-04 09:45:28 -05:00
04f24e9d91 Merge pull request 'main' (#79) from main into feature/cerc_idf
Reviewed-on: #79
2024-12-04 02:09:57 -05:00
4da206761a Update hub/version.py 2024-12-04 01:10:53 -05:00
0f6a2a5b8f Update hub/version.py 2024-12-04 00:26:16 -05:00
766eba2cb7 Merge pull request 'Remove usages_percentage in favor of usages' (#78) from fix/remove-usages_percentage into main
Reviewed-on: #78
2024-12-04 00:11:05 -05:00
Connor Brackley
103923b272 Changed sql usage type to JSON 2024-12-03 18:41:54 -05:00
Connor Brackley
a492a9eb0a Remove usages_percentage in favor of usages 2024-12-03 17:28:00 -05:00
5ec1708a2c Update hub/version.py 2024-12-03 00:42:54 -05:00
1bac29118e bug fix in result reading 2024-12-03 06:41:13 +01:00
246e3442a6 Merge branch 'main' into feature/cerc_idf 2024-12-03 05:35:09 +01:00
7831af9144 Update hub/version.py 2024-12-02 14:57:29 -05:00
06532adbb9 bug fix 2024-12-02 20:56:06 +01:00
fba7effd52 bug fix 2024-12-02 20:53:45 +01:00
5ca4a802cd Update hub/version.py 2024-12-02 14:14:28 -05:00
f4598ac946 bug fix 2024-12-02 20:13:56 +01:00
1f3d981ace Merge branch 'main' into feature/cerc_idf 2024-11-30 07:33:26 +01:00
f3454bbb72 bug fix 2024-11-30 07:32:46 +01:00
20b7929519 Update hub/version.py 2024-11-29 00:24:27 -05:00
d6032b06a4 Merge pull request 'fix/multi-useage' (#77) from fix/multi-useage into main
Reviewed-on: #77
2024-11-29 00:23:59 -05:00
Connor Brackley
f0a72919ff Fix typos 2024-11-28 22:30:25 +00:00
b9cb69ec05 Try to correct the importer 2024-11-28 21:46:21 +01:00
Connor Brackley
da819ad9d0 Minor bug fixes 2024-11-27 22:56:39 +00:00
Connor Brackley
44e6820ce6 Improve documentation and error handling 2024-11-27 22:06:22 +00:00
Connor Brackley
66dbda5525 Update usage handling in thermal zones 2024-11-27 22:06:03 +00:00
Connor Brackley
e0d1f1f8fb Added multi-usage to tests 2024-11-25 22:53:07 +00:00
Connor Brackley
2c6f602a2e Bug fixes 2024-11-25 22:42:54 +00:00
Vagrant
1449298a25 Update multi-usage methods to work with usage geojson input and parsers 2024-11-24 06:48:35 +00:00
c2a5cc2d5c Correct output names 2024-11-22 06:51:54 +01:00
f94ce25394 Partial correction of output names and result imports 2024-11-12 07:04:57 +01:00
8552b7cbd1 Bug fix 2024-11-08 06:58:23 +01:00
14404fbf04 Include oriol's infiltration changes into cerc_idf and remove empty file 2024-11-08 06:41:15 +01:00
c804c5ee6a Merge branch 'main' into feature/cerc_idf 2024-10-29 22:02:08 +01:00
ddf4631c59 test 2024-10-29 21:52:38 +01:00
6020964899 Validation in progress 2024-10-17 06:13:23 +02:00
841a6136bb Validation in progress 2024-10-15 06:12:11 +02:00
68d2bef9ec Validation in progress 2024-10-15 05:24:33 +02:00
afe5e433ea complete refactor 2024-10-03 15:40:02 +02:00
16b0726db7 correct refactor 2024-10-03 13:56:01 +02:00
b915dbdead Merge branch 'main' into feature/cerc_idf 2024-10-03 13:29:29 +02:00
cd7ac9378e Merge branch 'main' into feature/cerc_idf 2024-10-03 13:16:38 +02:00
0157f47bdc Refactor completed 2024-09-30 16:26:19 +02:00
8687b1257d Merge branch 'main' into feature/cerc_idf 2024-09-30 15:17:31 +02:00
78aa84c338 Partial refactor 2024-09-30 15:15:57 +02:00
dc98b634e8 add weather file to the EnergyBuildingsExportsFactory 2024-09-27 14:33:51 +02:00
27514d4d77 cerc idf implementation refactoring and added systems 2024-09-23 17:52:52 +02:00
5e384c8185 cerc idf implementation refactoring and added systems 2024-09-18 06:56:04 +02:00
62c9a5aab7 cerc idf implementation 2024-09-16 17:34:43 +02:00
cc2ee61ada inputs completed 2024-09-13 06:55:12 +02:00
5401064905 cerc_idf basic implementation 2024-09-12 06:57:15 +02:00
50 changed files with 1569 additions and 395 deletions

View File

@ -27,7 +27,7 @@ class Building(CityObject):
""" """
Building(CityObject) class Building(CityObject) class
""" """
def __init__(self, name, surfaces, year_of_construction, function, terrains=None, city=None): def __init__(self, name, surfaces, year_of_construction, function, usages=None, terrains=None, city=None):
super().__init__(name, surfaces) super().__init__(name, surfaces)
self._city = city self._city = city
self._households = None self._households = None
@ -36,6 +36,7 @@ class Building(CityObject):
self._terrains = terrains self._terrains = terrains
self._year_of_construction = year_of_construction self._year_of_construction = year_of_construction
self._function = function self._function = function
self._usages = usages
self._average_storey_height = None self._average_storey_height = None
self._storeys_above_ground = None self._storeys_above_ground = None
self._floor_area = None self._floor_area = None
@ -257,7 +258,17 @@ class Building(CityObject):
:param value: str :param value: str
""" """
if value is not None: if value is not None:
self._function = str(value) self._function = value
@property
def usages(self) -> Union[None, list]:
"""
Get building usages, if none, assume usage is function
:return: None or list of functions
"""
if self._usages is None and self._function is not None:
self._usages = [{'usage': self._function, 'ratio': 1 }]
return self._usages
@property @property
def average_storey_height(self) -> Union[None, float]: def average_storey_height(self) -> Union[None, float]:
@ -594,19 +605,6 @@ class Building(CityObject):
""" """
self._city = value self._city = value
@property
def usages_percentage(self):
"""
Get the usages and percentages for the building
"""
_usage = ''
for internal_zone in self.internal_zones:
if internal_zone.usages is None:
continue
for usage in internal_zone.usages:
_usage = f'{_usage}{usage.name}_{usage.percentage} '
return _usage.rstrip()
@property @property
def energy_systems(self) -> Union[None, List[EnergySystem]]: def energy_systems(self) -> Union[None, List[EnergySystem]]:
""" """

View File

@ -34,7 +34,7 @@ class ThermalZone:
volume, volume,
footprint_area, footprint_area,
number_of_storeys, number_of_storeys,
usage_name=None): usages=None):
self._id = None self._id = None
self._parent_internal_zone = parent_internal_zone self._parent_internal_zone = parent_internal_zone
self._footprint_area = footprint_area self._footprint_area = footprint_area
@ -51,10 +51,6 @@ class ThermalZone:
self._view_factors_matrix = None self._view_factors_matrix = None
self._total_floor_area = None self._total_floor_area = None
self._number_of_storeys = number_of_storeys self._number_of_storeys = number_of_storeys
self._usage_name = usage_name
self._usage_from_parent = False
if usage_name is None:
self._usage_from_parent = True
self._hours_day = None self._hours_day = None
self._days_year = None self._days_year = None
self._mechanical_air_change = None self._mechanical_air_change = None
@ -64,7 +60,12 @@ class ThermalZone:
self._internal_gains = None self._internal_gains = None
self._thermal_control = None self._thermal_control = None
self._domestic_hot_water = None self._domestic_hot_water = None
self._usages = None self._usage_name = None
self._usages = usages
self._usage_from_parent = False
if usages is None:
self._usage_from_parent = True
@property @property
def parent_internal_zone(self) -> InternalZone: def parent_internal_zone(self) -> InternalZone:
@ -77,24 +78,11 @@ class ThermalZone:
@property @property
def usages(self): def usages(self):
""" """
Get the thermal zone usages including percentage with the format [percentage]-usage_[percentage]-usage... Get the thermal zone usages
Eg: 70-office_30-residential
:return: str :return: str
""" """
if self._usage_from_parent: if self._usage_from_parent:
self._usages = copy.deepcopy(self._parent_internal_zone.usages) self._usages = copy.deepcopy(self._parent_internal_zone.usages)
else:
values = self._usage_name.split('_')
usages = []
for value in values:
usages.append(value.split('-'))
self._usages = []
for parent_usage in self._parent_internal_zone.usages:
for value in usages:
if parent_usage.name == value[1]:
new_usage = copy.deepcopy(parent_usage)
new_usage.percentage = float(value[0]) / 100
self._usages.append(new_usage)
return self._usages return self._usages
@property @property

View File

@ -0,0 +1,248 @@
"""
Cerc Idf exports one city or some buildings 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
import os
import shutil
import subprocess
import hub.exports.building_energy.idf_helper as idf_cte
import hub.helpers.constants as cte
from hub.city_model_structure.attributes.schedule import Schedule
from hub.exports.building_energy.idf_helper.idf_appliance import IdfAppliance
from hub.exports.building_energy.idf_helper.idf_base import IdfBase
from hub.exports.building_energy.idf_helper.idf_construction import IdfConstruction
from hub.exports.building_energy.idf_helper.idf_dhw import IdfDhw
from hub.exports.building_energy.idf_helper.idf_file_schedule import IdfFileSchedule
from hub.exports.building_energy.idf_helper.idf_heating_system import IdfHeatingSystem
from hub.exports.building_energy.idf_helper.idf_infiltration import IdfInfiltration
from hub.exports.building_energy.idf_helper.idf_lighting import IdfLighting
from hub.exports.building_energy.idf_helper.idf_material import IdfMaterial
from hub.exports.building_energy.idf_helper.idf_occupancy import IdfOccupancy
from hub.exports.building_energy.idf_helper.idf_schedule import IdfSchedule
from hub.exports.building_energy.idf_helper.idf_shading import IdfShading
from hub.exports.building_energy.idf_helper.idf_surfaces import IdfSurfaces
from hub.exports.building_energy.idf_helper.idf_thermostat import IdfThermostat
from hub.exports.building_energy.idf_helper.idf_ventilation import IdfVentilation
from hub.exports.building_energy.idf_helper.idf_window import IdfWindow
from hub.exports.building_energy.idf_helper.idf_windows_constructions import IdfWindowsConstructions
from hub.exports.building_energy.idf_helper.idf_windows_material import IdfWindowsMaterial
from hub.exports.building_energy.idf_helper.idf_zone import IdfZone
class CercIdf(IdfBase):
"""
Exports city to IDF
"""
_schedules_added_to_idf = {}
_materials_added_to_idf = {}
_windows_added_to_idf = {}
_constructions_added_to_idf = {}
_thermostat_added_to_idf = {}
def __init__(self, city, output_path, idf_file_path, idd_file_path, epw_file_path, target_buildings=None):
super().__init__(city, output_path, idf_file_path, idd_file_path, epw_file_path, target_buildings)
self._add_surfaces = IdfSurfaces.add
self._add_file_schedule = IdfFileSchedule.add
self._add_idf_schedule = IdfSchedule.add
self._add_construction = IdfConstruction.add
self._add_material = IdfMaterial.add
self._add_windows_material = IdfWindowsMaterial.add
self._add_windows_constructions = IdfWindowsConstructions.add
self._add_occupancy = IdfOccupancy.add
self._add_lighting = IdfLighting.add
self._add_appliance = IdfAppliance.add
self._add_infiltration = IdfInfiltration.add
self._add_infiltration_surface = IdfInfiltration.add_surface
self._add_ventilation = IdfVentilation.add
self._add_zone = IdfZone.add
self._add_thermostat = IdfThermostat.add
self._add_heating_system = IdfHeatingSystem.add
self._add_dhw = IdfDhw.add
self._add_shading = IdfShading.add
self._add_windows = IdfWindow.add
with open(self._idf_file_path, 'r', encoding='UTF-8') as base_idf:
lines = base_idf.readlines()
# Change city name
comment = f' !- Name'
field = f' Buildings in {self._city.name},'.ljust(26, ' ')
lines[15] = f'{field}{comment}\n'
with open(self._output_file_path, 'w', encoding='UTF-8') as self._idf_file:
self._idf_file.writelines(lines)
self._export()
def _create_geometry_rules(self):
file = self._files['constructions']
self._write_to_idf_format(file, idf_cte.GLOBAL_GEOMETRY_RULES)
self._write_to_idf_format(file, 'UpperLeftCorner', 'Starting Vertex Position')
self._write_to_idf_format(file, 'CounterClockWise', 'Vertex Entry Direction')
self._write_to_idf_format(file, 'World', 'Coordinate System', ';')
def _merge_files(self):
for file in self._files.values():
file.close()
for path in self._file_paths.values():
with open(path, 'r', encoding='UTF-8') as file:
lines = file.readlines()
self._idf_file.writelines(lines)
for path in self._file_paths.values():
os.unlink(path)
def _add_outputs(self):
with open(self._outputs_file_path, 'r', encoding='UTF-8') as base_idf:
lines = base_idf.readlines()
self._idf_file.writelines(lines)
@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 _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:
is_target = False
continue
for thermal_zone in internal_zone.thermal_zones_from_internal_zones:
if is_target:
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_schedule(self, usage, 'Infiltration', self._create_infiltration_schedules(thermal_zone))
self._add_idf_schedule(self, usage, 'Ventilation', self._create_ventilation_schedules(thermal_zone))
self._add_idf_schedule(self, usage, 'Occupancy', thermal_zone.occupancy.occupancy_schedules)
self._add_idf_schedule(self, usage, 'HVAC AVAIL', thermal_zone.thermal_control.hvac_availability_schedules)
self._add_idf_schedule(self, usage, 'Heating thermostat',
thermal_zone.thermal_control.heating_set_point_schedules)
self._add_idf_schedule(self, usage, 'Cooling thermostat',
thermal_zone.thermal_control.cooling_set_point_schedules)
self._add_idf_schedule(self, usage, 'Lighting', thermal_zone.lighting.schedules)
self._add_idf_schedule(self, usage, 'Appliance', thermal_zone.appliances.schedules)
self._add_idf_schedule(self, usage, 'DHW_prof', thermal_zone.domestic_hot_water.schedules)
self._add_idf_schedule(self, usage, 'DHW_temp',
self._create_constant_value_schedules(service_temperature, 24))
self._add_idf_schedule(self, usage, 'Activity Level', self._create_constant_value_schedules(total_heat, 24))
self._add_file_schedule(self, 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_material(self, thermal_boundary)
self._add_construction(self, thermal_boundary)
for thermal_opening in thermal_boundary.thermal_openings:
self._add_windows_material(self, thermal_boundary, thermal_opening)
self._add_windows_constructions(self, thermal_boundary)
self._add_zone(self, thermal_zone, building.name)
self._add_occupancy(self, thermal_zone, building.name)
self._add_lighting(self, thermal_zone, building.name)
self._add_appliance(self, thermal_zone, building.name)
if self._calculate_with_new_infiltration: # ToDo: Check with oriol if we want to keep the old method too
self._add_infiltration_surface(self, thermal_zone, building.name)
else:
self._add_infiltration(self, thermal_zone, building.name)
self._add_ventilation(self, thermal_zone, building.name)
self._add_thermostat(self, thermal_zone)
self._add_heating_system(self, thermal_zone, building.name)
self._add_dhw(self, thermal_zone, building.name)
if is_target:
self._add_surfaces(self, building, building.name)
self._add_windows(self, building)
else:
self._add_shading(self, building)
self._create_output_control_lighting() # Add lighting control to the lighting
# Create base values
self._create_geometry_rules()
# Merge files
self._merge_files()
self._add_outputs()
@property
def _energy_plus(self):
return shutil.which('energyplus')
def run(self):
cmd = [self._energy_plus,
'--weather', self._epw_file_path,
'--output-directory', self._output_path,
'--idd', self._idd_file_path,
'--expandobjects',
'--readvars',
'--output-prefix', f'{self._city.name}_',
self._output_file_path]
subprocess.run(cmd, cwd=self._output_path)

View File

@ -169,7 +169,7 @@ class EnergyAde:
def _building_geometry(self, building, building_dic, city): def _building_geometry(self, building, building_dic, city):
building_dic['bldg:Building']['bldg:function'] = building.function building_dic['bldg:Building']['bldg:function'] = building.function
building_dic['bldg:Building']['bldg:usage'] = building.usages_percentage building_dic['bldg:Building']['bldg:usage'] = building.usages
building_dic['bldg:Building']['bldg:yearOfConstruction'] = building.year_of_construction building_dic['bldg:Building']['bldg:yearOfConstruction'] = building.year_of_construction
building_dic['bldg:Building']['bldg:roofType'] = building.roof_type building_dic['bldg:Building']['bldg:roofType'] = building.roof_type
building_dic['bldg:Building']['bldg:measuredHeight'] = { building_dic['bldg:Building']['bldg:measuredHeight'] = {

View File

@ -8,10 +8,12 @@ Code contributors: Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concord
""" """
import copy import copy
import datetime import datetime
import glob import shutil
import os import subprocess
from pathlib import Path from pathlib import Path
from geomeppy import IDF from geomeppy import IDF
import hub.helpers.constants as cte import hub.helpers.constants as cte
from hub.city_model_structure.attributes.schedule import Schedule from hub.city_model_structure.attributes.schedule import Schedule
from hub.city_model_structure.building_demand.thermal_zone import ThermalZone from hub.city_model_structure.building_demand.thermal_zone import ThermalZone
@ -107,6 +109,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()
@ -444,7 +447,7 @@ class Idf:
subcategory = f'ELECTRIC EQUIPMENT#{zone_name}#InteriorEquipment' subcategory = f'ELECTRIC EQUIPMENT#{zone_name}#InteriorEquipment'
self._idf.newidfobject(self._APPLIANCES, self._idf.newidfobject(self._APPLIANCES,
Fuel_Type=fuel_type, Fuel_Type=fuel_type,
Name=f'{zone_name}_appliance', Name=zone_name,
Zone_or_ZoneList_or_Space_or_SpaceList_Name=zone_name, Zone_or_ZoneList_or_Space_or_SpaceList_Name=zone_name,
Schedule_Name=f'Appliance schedules {thermal_zone.usage_name}', Schedule_Name=f'Appliance schedules {thermal_zone.usage_name}',
Design_Level_Calculation_Method=method, Design_Level_Calculation_Method=method,
@ -501,7 +504,7 @@ class Idf:
) )
def _rename_building(self, city_name): def _rename_building(self, city_name):
name = str(str(city_name.encode("utf-8"))) name = str(city_name.encode("utf-8"))
for building in self._idf.idfobjects[self._BUILDING]: for building in self._idf.idfobjects[self._BUILDING]:
building.Name = f'Buildings in {name}' building.Name = f'Buildings in {name}'
building['Solar_Distribution'] = 'FullExterior' building['Solar_Distribution'] = 'FullExterior'
@ -528,11 +531,12 @@ class Idf:
self._remove_sizing_periods() self._remove_sizing_periods()
self._rename_building(self._city.name) self._rename_building(self._city.name)
self._lod = self._city.level_of_detail.geometry self._lod = self._city.level_of_detail.geometry
is_target = False
for building in self._city.buildings: for building in self._city.buildings:
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:
@ -586,9 +590,7 @@ 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()
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:
@ -651,14 +653,26 @@ 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
@property
def _energy_plus(self):
return shutil.which('energyplus')
def run(self): def run(self):
""" cmd = [self._energy_plus,
Start the energy plus simulation '--weather', self._epw_file_path,
""" '--output-directory', self._output_path,
self._idf.run(expandobjects=False, readvars=True, output_directory=self._output_path, '--idd', self._idd_file_path,
output_prefix=f'{self._city.name}_') '--expandobjects',
'--readvars',
'--output-prefix', f'{self._city.name}_',
self._idf_file_path]
subprocess.run(cmd, cwd=self._output_path)
def _add_block(self, building): def _add_block(self, building):
_points = self._matrix_to_2d_list(building.foot_print.coordinates) _points = self._matrix_to_2d_list(building.foot_print.coordinates)
@ -761,13 +775,11 @@ 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)
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)
if self._lod >= 3: if self._lod >= 3:
for internal_zone in building.internal_zones: for internal_zone in building.internal_zones:
for thermal_zone in internal_zone.thermal_zones_from_internal_zones: for thermal_zone in internal_zone.thermal_zones_from_internal_zones:
@ -779,7 +791,10 @@ class Idf:
for surface in building.surfaces: for surface in building.surfaces:
if surface.type == cte.WALL: if surface.type == cte.WALL:
wwr = surface.associated_thermal_boundaries[0].window_ratio wwr = surface.associated_thermal_boundaries[0].window_ratio
self._idf.set_wwr(wwr, construction='window_construction_1') try:
self._idf.set_wwr(wwr, construction='window_construction_1')
except ValueError:
self._idf.set_wwr(0, construction='window_construction_1')
def _add_windows_by_vertices(self, boundary): def _add_windows_by_vertices(self, boundary):
raise NotImplementedError raise NotImplementedError

View 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 #CITY#, !- 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

View File

@ -0,0 +1,74 @@
Output:Table:SummaryReports,
AnnualBuildingUtilityPerformanceSummary, !- Report 1 Name
DemandEndUseComponentsSummary, !- Report 2 Name
SensibleHeatGainSummary, !- Report 3 Name
InputVerificationandResultsSummary, !- Report 4 Name
AdaptiveComfortSummary, !- Report 5 Name
Standard62.1Summary, !- Report 6 Name
ClimaticDataSummary, !- Report 7 Name
EquipmentSummary, !- Report 8 Name
EnvelopeSummary, !- Report 9 Name
LightingSummary, !- Report 10 Name
HVACSizingSummary, !- Report 11 Name
SystemSummary, !- Report 12 Name
ComponentSizingSummary, !- Report 13 Name
OutdoorAirSummary, !- Report 14 Name
ObjectCountSummary, !- Report 15 Name
EndUseEnergyConsumptionOtherFuelsMonthly, !- Report 16 Name
PeakEnergyEndUseOtherFuelsMonthly; !- Report 17 Name
OutputControl:Table:Style,
CommaAndHTML, !- Column Separator
JtoKWH; !- Unit Conversion
OUTPUT:VARIABLE,
*, !- Key Value
Zone Ideal Loads Supply Air Total Heating Energy, !- Variable Name
Hourly; !- Reporting Frequency
OUTPUT:VARIABLE,
*, !- Key Value
Zone Ideal Loads Supply Air Total Cooling Energy, !- Variable Name
Hourly; !- Reporting Frequency
OUTPUT:VARIABLE,
*, !- Key Value
Water Use Equipment Heating Rate, !- Variable Name
Hourly; !- Reporting Frequency
OUTPUT:VARIABLE,
*, !- Key Value
Zone Lights Electricity Rate, !- Variable Name
Hourly; !- Reporting Frequency
OUTPUT:VARIABLE,
*, !- Key Value
Other Equipment Electricity Rate, !- Variable Name
Hourly; !- Reporting Frequency
OUTPUT:VARIABLE,
*, !- Key Value
Zone Air Temperature, !- Variable Name
Hourly; !- Reporting Frequency
OUTPUT:VARIABLE,
*, !- Key Value
Zone Air Relative Humidity, !- Variable Name
Hourly; !- Reporting Frequency
Output:Meter,
DISTRICTHEATING:Facility, !- Key Name
hourly; !- Reporting Frequency
Output:Meter,
DISTRICTCOOLING:Facility, !- Key Name
hourly; !- Reporting Frequency
Output:Meter,
InteriorEquipment:Electricity, !- Key Name
hourly; !- Reporting Frequency
Output:Meter,
InteriorLights:Electricity, !- Key Name
hourly; !- Reporting Frequency

View File

@ -0,0 +1,60 @@
import hub.helpers.constants as cte
BUILDING_SURFACE = '\nBUILDINGSURFACE:DETAILED,\n'
WINDOW_SURFACE = '\nFENESTRATIONSURFACE: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'
ZONE = '\nZONE,\n'
GLOBAL_GEOMETRY_RULES = '\nGlobalGeometryRules,\n'
PEOPLE = '\nPEOPLE,\n'
LIGHTS = '\nLIGHTS,\n'
APPLIANCES = '\nOTHEREQUIPMENT,\n'
OUTPUT_CONTROL = '\nOutputControl:IlluminanceMap:Style,\n'
INFILTRATION = '\nZONEINFILTRATION:DESIGNFLOWRATE,\n'
VENTILATION = '\nZONEVENTILATION:DESIGNFLOWRATE,\n'
THERMOSTAT = '\nHVACTEMPLATE:THERMOSTAT,\n'
IDEAL_LOAD_SYSTEM = '\nHVACTEMPLATE:ZONE:IDEALLOADSAIRSYSTEM,\n'
DHW = '\nWATERUSE:EQUIPMENT,\n'
SHADING = '\nSHADING:BUILDING:DETAILED,\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_dictionary = {
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'
}

View File

@ -0,0 +1,26 @@
import hub.exports.building_energy.idf_helper as idf_cte
from hub.exports.building_energy.idf_helper.idf_base import IdfBase
class IdfAppliance(IdfBase):
@staticmethod
def add(self, thermal_zone, zone_name):
schedule_name = f'Appliance schedules {thermal_zone.usage_name}'
storeys_number = int(thermal_zone.total_floor_area / thermal_zone.footprint_area)
watts_per_zone_floor_area = thermal_zone.appliances.density * storeys_number
subcategory = f'ELECTRIC EQUIPMENT#{zone_name}#InteriorEquipment'
file = self._files['appliances']
self._write_to_idf_format(file, idf_cte.APPLIANCES)
self._write_to_idf_format(file, zone_name, 'Name')
self._write_to_idf_format(file, 'Electricity', 'Fuel Type')
self._write_to_idf_format(file, zone_name, 'Zone or ZoneList or Space or SpaceList Name')
self._write_to_idf_format(file, schedule_name, 'Schedule Name')
self._write_to_idf_format(file, 'Watts/Area', 'Design Level Calculation Method')
self._write_to_idf_format(file, idf_cte.EMPTY, 'Design Level')
self._write_to_idf_format(file, watts_per_zone_floor_area, 'Power per Zone Floor Area')
self._write_to_idf_format(file, idf_cte.EMPTY, 'Power per Person')
self._write_to_idf_format(file, thermal_zone.appliances.latent_fraction, 'Fraction Latent')
self._write_to_idf_format(file, thermal_zone.appliances.radiative_fraction, 'Fraction Radiant')
self._write_to_idf_format(file, 0, 'Fraction Lost')
self._write_to_idf_format(file, 0, 'Carbon Dioxide Generation Rate')
self._write_to_idf_format(file, subcategory, 'EndUse Subcategory', ';')

View File

@ -0,0 +1,78 @@
import os
from pathlib import Path
import hub.exports.building_energy.idf_helper as idf_cte
class IdfBase:
def __init__(self, city, output_path, idf_file_path, idd_file_path, epw_file_path, target_buildings=None,
_calculate_with_new_infiltration=True):
self._city = city
self._output_path = str(output_path.resolve())
self._output_file_path = str((output_path / f'{city.name}.idf').resolve())
self._file_paths = {
'schedules': str((output_path / 'schedules.idf').resolve()),
'file_schedules': str((output_path / 'file_schedules.idf').resolve()),
'solid_materials': str((output_path / 'solid_materials.idf').resolve()),
'nomass_materials': str((output_path / 'nomass_materials.idf').resolve()),
'window_materials': str((output_path / 'window_materials.idf').resolve()),
'constructions': str((output_path / 'constructions.idf').resolve()),
'zones': str((output_path / 'zones.idf').resolve()),
'surfaces': str((output_path / 'surfaces.idf').resolve()),
'fenestration': str((output_path / 'fenestration.idf').resolve()),
'occupancy': str((output_path / 'occupancy.idf').resolve()),
'lighting': str((output_path / 'lights.idf').resolve()),
'appliances': str((output_path / 'appliances.idf').resolve()),
'shading': str((output_path / 'shading.idf').resolve()),
'infiltration': str((output_path / 'infiltration.idf').resolve()),
'ventilation': str((output_path / 'ventilation.idf').resolve()),
'thermostat': str((output_path / 'thermostat.idf').resolve()),
'ideal_load_system': str((output_path / 'ideal_load_system.idf').resolve()),
'dhw': str((output_path / 'dhw.idf').resolve()),
}
self._files = {}
for key, value in self._file_paths.items():
self._files[key] = open(value, 'w', encoding='UTF-8')
self._idd_file_path = str(idd_file_path)
self._idf_file_path = str(idf_file_path)
self._outputs_file_path = str(Path(idf_file_path).parent / 'outputs.idf')
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
self._calculate_with_new_infiltration = _calculate_with_new_infiltration
def _create_output_control_lighting(self):
file = self._files['appliances']
self._write_to_idf_format(file, idf_cte.OUTPUT_CONTROL)
self._write_to_idf_format(file, 'Comma', 'Column Separator', ';')
@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

View File

@ -0,0 +1,56 @@
import hub.exports.building_energy.idf_helper as idf_cte
from hub.city_model_structure.building_demand.layer import Layer
from hub.exports.building_energy.idf_helper.idf_base import IdfBase
class IdfConstruction(IdfBase):
@staticmethod
def _add_solid_material(self, layer):
file = self._files['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', ';')
@staticmethod
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
IdfConstruction._add_solid_material(self, layer)
return layer
@staticmethod
def add(self, thermal_boundary):
if thermal_boundary.layers is None:
thermal_boundary.layers = [IdfConstruction._add_default_material(self)]
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._files['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)

View File

@ -0,0 +1,21 @@
import hub.exports.building_energy.idf_helper as idf_cte
from hub.exports.building_energy.idf_helper.idf_base import IdfBase
class IdfDhw(IdfBase):
@staticmethod
def add(self, thermal_zone, zone_name):
peak_flow_rate = thermal_zone.domestic_hot_water.peak_flow * thermal_zone.total_floor_area
flow_rate_schedule = f'DHW_prof schedules {thermal_zone.usage_name}'
dhw_schedule = f'DHW_temp schedules {thermal_zone.usage_name}'
cold_temp_schedule = f'cold_temp schedules {thermal_zone.usage_name}'
file = self._files['dhw']
self._write_to_idf_format(file, idf_cte.DHW)
self._write_to_idf_format(file, zone_name, 'Name')
self._write_to_idf_format(file, zone_name, 'EndUse Subcategory')
self._write_to_idf_format(file, peak_flow_rate, 'Peak Flow Rate')
self._write_to_idf_format(file, flow_rate_schedule, 'Flow Rate Fraction Schedule Name')
self._write_to_idf_format(file, dhw_schedule, 'Target Temperature Schedule Name')
self._write_to_idf_format(file, dhw_schedule, 'Hot Water Supply Temperature Schedule Name')
self._write_to_idf_format(file, cold_temp_schedule, 'Cold Water Supply Temperature Schedule Name')
self._write_to_idf_format(file, zone_name, 'Zone Name', ';')

View File

@ -0,0 +1,30 @@
from pathlib import Path
import hub.helpers.constants as cte
import hub.exports.building_energy.idf_helper as idf_cte
from hub.exports.building_energy.idf_helper.idf_base import IdfBase
class IdfFileSchedule(IdfBase):
@staticmethod
def add(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[0]:
file.write(f'{value},\n')
file = self._files['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', ';')

View File

@ -0,0 +1,41 @@
import hub.exports.building_energy.idf_helper as idf_cte
from hub.exports.building_energy.idf_helper.idf_base import IdfBase
class IdfHeatingSystem(IdfBase):
@staticmethod
def add(self, thermal_zone, zone_name):
availability_schedule = f'HVAC AVAIL SCHEDULES {thermal_zone.usage_name}'
thermostat_name = f'Thermostat {thermal_zone.usage_name}'
file = self._files['ideal_load_system']
self._write_to_idf_format(file, idf_cte.IDEAL_LOAD_SYSTEM)
self._write_to_idf_format(file, zone_name, 'Zone Name')
self._write_to_idf_format(file, thermostat_name, 'Template Thermostat Name')
self._write_to_idf_format(file, availability_schedule, 'System Availability Schedule Name')
self._write_to_idf_format(file, 50, 'Maximum Heating Supply Air Temperature')
self._write_to_idf_format(file, 13, 'Minimum Cooling Supply Air Temperature')
self._write_to_idf_format(file, 0.0156, 'Maximum Heating Supply Air Humidity Ratio')
self._write_to_idf_format(file, 0.0077, 'Minimum Cooling Supply Air Humidity Ratio')
self._write_to_idf_format(file, 'NoLimit', 'Heating Limit')
self._write_to_idf_format(file, idf_cte.EMPTY, 'Maximum Heating Air Flow Rate')
self._write_to_idf_format(file, idf_cte.EMPTY, 'Maximum Sensible Heating Capacity')
self._write_to_idf_format(file, 'NoLimit', 'Cooling Limit')
self._write_to_idf_format(file, idf_cte.EMPTY, 'Maximum Cooling Air Flow Rate')
self._write_to_idf_format(file, idf_cte.EMPTY, 'Maximum Total Cooling Capacity')
self._write_to_idf_format(file, availability_schedule, 'Heating Availability Schedule Name')
self._write_to_idf_format(file, availability_schedule, 'Cooling Availability Schedule Name')
self._write_to_idf_format(file, 'ConstantSensibleHeatRatio', 'Dehumidification Control Type')
self._write_to_idf_format(file, 0.7, 'Cooling Sensible Heat Ratio')
self._write_to_idf_format(file, 60, 'Dehumidification Setpoint')
self._write_to_idf_format(file, 'None', 'Humidification Control Type')
self._write_to_idf_format(file, 30, 'Humidification Setpoint')
self._write_to_idf_format(file, 'None', 'Outdoor Air Method')
self._write_to_idf_format(file, 0.00944, 'Outdoor Air Flow Rate per Person')
self._write_to_idf_format(file, 0.0, 'Outdoor Air Flow Rate per Zone Floor Area')
self._write_to_idf_format(file, 0, 'Outdoor Air Flow Rate per Zone')
self._write_to_idf_format(file, idf_cte.EMPTY, 'Design Specification Outdoor Air Object Name')
self._write_to_idf_format(file, 'None', 'Demand Controlled Ventilation Type')
self._write_to_idf_format(file, 'NoEconomizer', 'Outdoor Air Economizer Type')
self._write_to_idf_format(file, 'None', 'Heat Recovery Type')
self._write_to_idf_format(file, 0.70, 'Sensible Heat Recovery Effectiveness')
self._write_to_idf_format(file, 0.65, 'Latent Heat Recovery Effectiveness', ';')

View File

@ -0,0 +1,32 @@
import hub.exports.building_energy.idf_helper as idf_cte
import hub.helpers.constants as cte
from hub.exports.building_energy.idf_helper.idf_base import IdfBase
class IdfInfiltration(IdfBase):
@staticmethod
def add(self, thermal_zone, zone_name):
IdfInfiltration._add_infiltration(self, thermal_zone, zone_name, 'AirChanges/Hour', cte.HOUR_TO_SECONDS)
@staticmethod
def add_surface(self, thermal_zone, zone_name):
IdfInfiltration._add_infiltration(self, thermal_zone, zone_name, 'Flow/ExteriorWallArea', cte.INFILTRATION_75PA_TO_4PA)
@staticmethod
def _add_infiltration(self, thermal_zone, zone_name, calculation_method, multiplier):
schedule_name = f'Infiltration schedules {thermal_zone.usage_name}'
infiltration = thermal_zone.infiltration_rate_system_off * multiplier
file = self._files['infiltration']
self._write_to_idf_format(file, idf_cte.INFILTRATION)
self._write_to_idf_format(file, zone_name, 'Name')
self._write_to_idf_format(file, zone_name, 'Zone or ZoneList or Space or SpaceList Name')
self._write_to_idf_format(file, schedule_name, 'Schedule Name')
self._write_to_idf_format(file, calculation_method, 'Design Flow Rate Calculation Method')
self._write_to_idf_format(file, idf_cte.EMPTY, 'Design Flow Rate')
self._write_to_idf_format(file, idf_cte.EMPTY, 'Flow Rate per Floor Area')
self._write_to_idf_format(file, infiltration, 'Flow Rate per Exterior Surface Area')
self._write_to_idf_format(file, infiltration, 'Air Changes per Hour')
self._write_to_idf_format(file, 1, 'Constant Term Coefficient')
self._write_to_idf_format(file, 0, 'Temperature Term Coefficient')
self._write_to_idf_format(file, 0, 'Velocity Term Coefficient')
self._write_to_idf_format(file, 0, 'Velocity Squared Term Coefficient', ';')

View File

@ -0,0 +1,28 @@
import hub.exports.building_energy.idf_helper as idf_cte
from hub.exports.building_energy.idf_helper.idf_base import IdfBase
class IdfLighting(IdfBase):
@staticmethod
def add(self, thermal_zone, zone_name):
storeys_number = int(thermal_zone.total_floor_area / thermal_zone.footprint_area)
watts_per_zone_floor_area = thermal_zone.lighting.density * storeys_number
subcategory = f'ELECTRIC EQUIPMENT#{zone_name}#GeneralLights'
schedule_name = f'Lighting schedules {thermal_zone.usage_name}'
file = self._files['lighting']
self._write_to_idf_format(file, idf_cte.LIGHTS)
self._write_to_idf_format(file, f'{zone_name}_lights', 'Name')
self._write_to_idf_format(file, zone_name, 'Zone or ZoneList or Space or SpaceList Name')
self._write_to_idf_format(file, schedule_name, 'Schedule Name')
self._write_to_idf_format(file, 'Watts/Area', 'Design Level Calculation Method')
self._write_to_idf_format(file, idf_cte.EMPTY, 'Lighting Level')
self._write_to_idf_format(file, watts_per_zone_floor_area, 'Watts per Zone Floor Area')
self._write_to_idf_format(file, idf_cte.EMPTY, 'Watts per Person')
self._write_to_idf_format(file, 0, 'Return Air Fraction')
self._write_to_idf_format(file, thermal_zone.lighting.radiative_fraction, 'Fraction Radiant')
self._write_to_idf_format(file, 0, 'Fraction Visible')
self._write_to_idf_format(file, 1, 'Fraction Replaceable')
self._write_to_idf_format(file, subcategory, 'EndUse Subcategory')
self._write_to_idf_format(file, 'No', 'Return Air Fraction Calculated from Plenum Temperature')
self._write_to_idf_format(file, 0, 'Return Air Fraction Function of Plenum Temperature Coefficient 1')
self._write_to_idf_format(file, 0, 'Return Air Fraction Function of Plenum Temperature Coefficient 2', ';')

View File

@ -0,0 +1,39 @@
import hub.exports.building_energy.idf_helper as idf_cte
from hub.exports.building_energy.idf_helper.idf_base import IdfBase
class IdfMaterial(IdfBase):
@staticmethod
def _add_solid_material(self, layer):
file = self._files['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', ';')
@staticmethod
def _add_nomass_material(self, layer):
file = self._files['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', ';')
@staticmethod
def add(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:
IdfMaterial._add_nomass_material(self, layer)
else:
IdfMaterial._add_solid_material(self, layer)

View File

@ -0,0 +1,47 @@
import hub.exports.building_energy.idf_helper as idf_cte
from hub.exports.building_energy.idf_helper.idf_base import IdfBase
class IdfOccupancy(IdfBase):
@staticmethod
def add(self, thermal_zone, zone_name):
number_of_people = thermal_zone.occupancy.occupancy_density * thermal_zone.total_floor_area
fraction_radiant = 0
total_sensible = (
thermal_zone.occupancy.sensible_radiative_internal_gain + thermal_zone.occupancy.sensible_convective_internal_gain
)
if total_sensible != 0:
fraction_radiant = thermal_zone.occupancy.sensible_radiative_internal_gain / total_sensible
occupancy_schedule = f'Occupancy schedules {thermal_zone.usage_name}'
activity_level_schedule = f'Activity Level schedules {thermal_zone.usage_name}'
file = self._files['occupancy']
self._write_to_idf_format(file, idf_cte.PEOPLE)
self._write_to_idf_format(file, f'{zone_name}_occupancy', 'Name')
self._write_to_idf_format(file, zone_name, 'Zone or ZoneList or Space or SpaceList Name')
self._write_to_idf_format(file, occupancy_schedule, 'Number of People Schedule Name')
self._write_to_idf_format(file, 'People', 'Number of People Calculation Method')
self._write_to_idf_format(file, number_of_people, 'Number of People')
self._write_to_idf_format(file, idf_cte.EMPTY, 'People per Floor Area')
self._write_to_idf_format(file, idf_cte.EMPTY, 'Floor Area per Person')
self._write_to_idf_format(file, fraction_radiant, 'Fraction Radiant')
self._write_to_idf_format(file, idf_cte.AUTOCALCULATE, 'Sensible Heat Fraction')
self._write_to_idf_format(file, activity_level_schedule, 'Activity Level Schedule Name')
self._write_to_idf_format(file, '3.82e-08', 'Carbon Dioxide Generation Rate')
self._write_to_idf_format(file, 'No', 'Enable ASHRAE 55 Comfort Warnings')
self._write_to_idf_format(file, 'EnclosureAveraged', 'Mean Radiant Temperature Calculation Type')
self._write_to_idf_format(file, idf_cte.EMPTY, 'Surface NameAngle Factor List Name')
self._write_to_idf_format(file, idf_cte.EMPTY, 'Work Efficiency Schedule Name')
self._write_to_idf_format(file, 'ClothingInsulationSchedule', 'Clothing Insulation Calculation Method')
self._write_to_idf_format(file, idf_cte.EMPTY, 'Clothing Insulation Calculation Method Schedule Name')
self._write_to_idf_format(file, idf_cte.EMPTY, 'Clothing Insulation Schedule Name')
self._write_to_idf_format(file, idf_cte.EMPTY, 'Air Velocity Schedule Name')
self._write_to_idf_format(file, idf_cte.EMPTY, 'Thermal Comfort Model 1 Type')
self._write_to_idf_format(file, idf_cte.EMPTY, 'Thermal Comfort Model 2 Type')
self._write_to_idf_format(file, idf_cte.EMPTY, 'Thermal Comfort Model 3 Type')
self._write_to_idf_format(file, idf_cte.EMPTY, 'Thermal Comfort Model 4 Type')
self._write_to_idf_format(file, idf_cte.EMPTY, 'Thermal Comfort Model 5 Type')
self._write_to_idf_format(file, idf_cte.EMPTY, 'Thermal Comfort Model 6 Type')
self._write_to_idf_format(file, idf_cte.EMPTY, 'Thermal Comfort Model 7 Type')
self._write_to_idf_format(file, idf_cte.EMPTY, 'Ankle Level Air Velocity Schedule Name')
self._write_to_idf_format(file, '15.56', 'Cold Stress Temperature Threshold')
self._write_to_idf_format(file, '30', 'Heat Stress Temperature Threshold', ';')

View File

@ -0,0 +1,30 @@
import hub.exports.building_energy.idf_helper as idf_cte
from hub.exports.building_energy.idf_helper.idf_base import IdfBase
class IdfSchedule(IdfBase):
@staticmethod
def add(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._files['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}', ';')

View File

@ -0,0 +1,25 @@
import hub.exports.building_energy.idf_helper as idf_cte
from hub.exports.building_energy.idf_helper.idf_base import IdfBase
class IdfShading(IdfBase):
@staticmethod
def add(self, building):
name = building.name
file = self._files['shading']
for s, surface in enumerate(building.surfaces):
self._write_to_idf_format(file, idf_cte.SHADING)
self._write_to_idf_format(file, f'{name}_{s}', 'Name')
self._write_to_idf_format(file, idf_cte.EMPTY, 'Transmittance Schedule Name')
self._write_to_idf_format(file, idf_cte.AUTOCALCULATE, 'Number of Vertices')
eol = ','
coordinates = self._matrix_to_list(surface.solid_polygon.coordinates, self._city.lower_corner)
coordinates_length = len(coordinates)
for i, coordinate in enumerate(coordinates):
vertex = i + 1
if vertex == coordinates_length:
eol = ';'
self._write_to_idf_format(file, coordinate[0], f'Vertex {vertex} Xcoordinate')
self._write_to_idf_format(file, coordinate[1], f'Vertex {vertex} Ycoordinate')
self._write_to_idf_format(file, coordinate[2], f'Vertex {vertex} Zcoordinate', eol)

View File

@ -0,0 +1,52 @@
import hub.exports.building_energy.idf_helper as idf_cte
import hub.helpers.constants as cte
from hub.exports.building_energy.idf_helper.idf_base import IdfBase
class IdfSurfaces(IdfBase):
@staticmethod
def add(self, building, zone_name):
zone_name = f'{zone_name}'
file = self._files['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_dictionary[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 = ','
coordinates_length = len(coordinates)
for i, coordinate in enumerate(coordinates):
vertex = i + 1
if vertex == coordinates_length:
eol = ';'
self._write_to_idf_format(file, coordinate[0], f'Vertex {vertex} Xcoordinate')
self._write_to_idf_format(file, coordinate[1], f'Vertex {vertex} Ycoordinate')
self._write_to_idf_format(file, coordinate[2], f'Vertex {vertex} Zcoordinate', eol)

View File

@ -0,0 +1,18 @@
import hub.exports.building_energy.idf_helper as idf_cte
from hub.exports.building_energy.idf_helper.idf_base import IdfBase
class IdfThermostat(IdfBase):
@staticmethod
def add(self, thermal_zone):
thermostat_name = f'Thermostat {thermal_zone.usage_name}'
heating_schedule = f'Heating thermostat schedules {thermal_zone.usage_name}'
cooling_schedule = f'Cooling thermostat schedules {thermal_zone.usage_name}'
if thermostat_name not in self._thermostat_added_to_idf:
self._thermostat_added_to_idf[thermostat_name] = True
file = self._files['thermostat']
self._write_to_idf_format(file, idf_cte.THERMOSTAT)
self._write_to_idf_format(file, thermostat_name, 'Name')
self._write_to_idf_format(file, heating_schedule, 'Heating Setpoint Schedule Name')
self._write_to_idf_format(file, idf_cte.EMPTY, 'Constant Heating Setpoint')
self._write_to_idf_format(file, cooling_schedule, 'Cooling Setpoint Schedule Name', ';')

View File

@ -0,0 +1,38 @@
import hub.exports.building_energy.idf_helper as idf_cte
import hub.helpers.constants as cte
from hub.exports.building_energy.idf_helper.idf_base import IdfBase
class IdfVentilation(IdfBase):
@staticmethod
def add(self, thermal_zone, zone_name):
schedule_name = f'Ventilation schedules {thermal_zone.usage_name}'
air_change = thermal_zone.mechanical_air_change * cte.HOUR_TO_SECONDS
file = self._files['ventilation']
self._write_to_idf_format(file, idf_cte.VENTILATION)
self._write_to_idf_format(file, f'{zone_name}_ventilation', 'Name')
self._write_to_idf_format(file, zone_name, 'Zone or ZoneList or Space or SpaceList Name')
self._write_to_idf_format(file, schedule_name, 'Schedule Name')
self._write_to_idf_format(file, 'AirChanges/Hour', 'Design Flow Rate Calculation Method')
self._write_to_idf_format(file, idf_cte.EMPTY, 'Design Flow Rate')
self._write_to_idf_format(file, idf_cte.EMPTY, 'Flow Rate per Floor Area')
self._write_to_idf_format(file, idf_cte.EMPTY, 'Flow Rate per Person')
self._write_to_idf_format(file, air_change, 'Air Changes per Hour')
self._write_to_idf_format(file, 'Natural', 'Ventilation Type')
self._write_to_idf_format(file, 0, 'Fan Pressure Rise')
self._write_to_idf_format(file, 1, 'Fan Total Efficiency')
self._write_to_idf_format(file, 1, 'Constant Term Coefficient')
self._write_to_idf_format(file, 0, 'Temperature Term Coefficient')
self._write_to_idf_format(file, 0, 'Velocity Term Coefficient')
self._write_to_idf_format(file, 0, 'Velocity Squared Term Coefficient')
self._write_to_idf_format(file, -100, 'Minimum Indoor Temperature')
self._write_to_idf_format(file, idf_cte.EMPTY, 'Minimum Indoor Temperature Schedule Name')
self._write_to_idf_format(file, 100, 'Maximum Indoor Temperature')
self._write_to_idf_format(file, idf_cte.EMPTY, 'Maximum Indoor Temperature Schedule Name')
self._write_to_idf_format(file, -100, 'Delta Temperature')
self._write_to_idf_format(file, idf_cte.EMPTY, 'Delta Temperature Schedule Name')
self._write_to_idf_format(file, -100, 'Minimum Outdoor Temperature')
self._write_to_idf_format(file, idf_cte.EMPTY, 'Minimum Outdoor Temperature Schedule Name')
self._write_to_idf_format(file, 100, 'Maximum Outdoor Temperature')
self._write_to_idf_format(file, idf_cte.EMPTY, 'Maximum Outdoor Temperature Schedule Name')
self._write_to_idf_format(file, 40, 'Maximum Wind Speed', ';')

View File

@ -0,0 +1,64 @@
import logging
import hub.exports.building_energy.idf_helper as idf_cte
import hub.helpers.constants as cte
from hub.exports.building_energy.idf_helper.idf_base import IdfBase
class IdfWindow(IdfBase):
@staticmethod
def _to_window_surface(self, surface):
window_ratio = surface.associated_thermal_boundaries[0].window_ratio
x = 0
y = 1
z = 2
coordinates = self._matrix_to_list(surface.solid_polygon.coordinates, self._city.lower_corner)
min_z = surface.lower_corner[z]
max_z = surface.upper_corner[z]
middle = (max_z - min_z) / 2
distance = (max_z - min_z) * window_ratio
new_max_z = middle + distance / 2
new_min_z = middle - distance / 2
for index, coordinate in enumerate(coordinates):
if coordinate[z] == max_z:
coordinates[index] = (coordinate[x], coordinate[y], new_max_z)
elif coordinate[z] == min_z:
coordinates[index] = (coordinate[x], coordinate[y], new_min_z)
else:
logging.warning('Z coordinate not in top or bottom during window creation')
return coordinates
@staticmethod
def add(self, building):
file = self._files['fenestration']
for thermal_zone in building.thermal_zones_from_internal_zones:
for index, boundary in enumerate(thermal_zone.thermal_boundaries):
building_surface_name = f'Building_{building.name}_surface_{index}'
is_exposed = boundary.parent_surface.type == cte.WALL
if boundary.parent_surface.percentage_shared is not None and boundary.parent_surface.percentage_shared > 0.5 or boundary.window_ratio == 0:
is_exposed = False
if not is_exposed:
continue
name = f'Building_{building.name}_window_{index}'
construction_name = f'{boundary.construction_name}_window_construction'
self._write_to_idf_format(file, idf_cte.WINDOW_SURFACE)
self._write_to_idf_format(file, name, 'Name')
self._write_to_idf_format(file, 'Window', 'Surface Type')
self._write_to_idf_format(file, construction_name, 'Construction Name')
self._write_to_idf_format(file, building_surface_name, 'Building Surface Name')
self._write_to_idf_format(file, idf_cte.EMPTY, 'Outside Boundary Condition Object')
self._write_to_idf_format(file, idf_cte.AUTOCALCULATE, 'View Factor to Ground')
self._write_to_idf_format(file, idf_cte.EMPTY, 'Frame and Divider Name')
self._write_to_idf_format(file, '1.0', 'Multiplier')
self._write_to_idf_format(file, idf_cte.AUTOCALCULATE, 'Number of Vertices')
coordinates = IdfWindow._to_window_surface(self, boundary.parent_surface)
eol = ','
coordinates_length = len(coordinates)
for i, coordinate in enumerate(coordinates):
vertex = i + 1
if vertex == coordinates_length:
eol = ';'
self._write_to_idf_format(file, coordinate[0], f'Vertex {vertex} Xcoordinate')
self._write_to_idf_format(file, coordinate[1], f'Vertex {vertex} Ycoordinate')
self._write_to_idf_format(file, coordinate[2], f'Vertex {vertex} Zcoordinate', eol)

View File

@ -0,0 +1,17 @@
import hub.exports.building_energy.idf_helper as idf_cte
from hub.exports.building_energy.idf_helper.idf_base import IdfBase
class IdfWindowsConstructions(IdfBase):
@staticmethod
def add(self, thermal_boundary):
name = f'{thermal_boundary.construction_name}_window'
if name not in self._windows_added_to_idf:
return # Material not added or already assigned to construction
construction_name = f'{thermal_boundary.construction_name}_window_construction'
if construction_name not in self._constructions_added_to_idf:
self._constructions_added_to_idf[construction_name] = True
file = self._files['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, name, 'Outside Layer', ';')

View File

@ -0,0 +1,15 @@
import hub.exports.building_energy.idf_helper as idf_cte
from hub.exports.building_energy.idf_helper.idf_base import IdfBase
class IdfWindowsMaterial(IdfBase):
@staticmethod
def add(self, thermal_boundary, thermal_opening):
name = f'{thermal_boundary.construction_name}_window'
if name not in self._windows_added_to_idf:
self._windows_added_to_idf[name] = True
file = self._files['window_materials']
self._write_to_idf_format(file, idf_cte.WINDOW_MATERIAL)
self._write_to_idf_format(file, name, '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', ';')

View File

@ -0,0 +1,22 @@
import hub.exports.building_energy.idf_helper as idf_cte
from hub.exports.building_energy.idf_helper.idf_base import IdfBase
class IdfZone(IdfBase):
@staticmethod
def add(self, thermal_zone, zone_name):
file = self._files['zones']
self._write_to_idf_format(file, idf_cte.ZONE)
self._write_to_idf_format(file, zone_name, 'Name')
self._write_to_idf_format(file, 0, 'Direction of Relative North')
self._write_to_idf_format(file, 0, 'X Origin')
self._write_to_idf_format(file, 0, 'Y Origin')
self._write_to_idf_format(file, 0, 'Z Origin')
self._write_to_idf_format(file, 1, 'Type')
self._write_to_idf_format(file, 1, 'Multiplier')
self._write_to_idf_format(file, idf_cte.AUTOCALCULATE, 'Ceiling Height')
self._write_to_idf_format(file, thermal_zone.volume, 'Volume')
self._write_to_idf_format(file, idf_cte.AUTOCALCULATE, 'Floor Area')
self._write_to_idf_format(file, idf_cte.EMPTY, 'Zone Inside Convection Algorithm')
self._write_to_idf_format(file, idf_cte.EMPTY, 'Zone Outside Convection Algorithm')
self._write_to_idf_format(file, 'Yes', 'Part of Total Floor Area', ';')

View File

@ -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
@ -20,6 +21,7 @@ class EnergyBuildingsExportsFactory:
""" """
Energy Buildings exports factory class Energy Buildings exports factory class
""" """
def __init__(self, handler, city, path, custom_insel_block='d18599', target_buildings=None, weather_file=None): def __init__(self, handler, city, path, custom_insel_block='d18599', target_buildings=None, weather_file=None):
self._city = city self._city = city
self._export_type = '_' + handler.lower() self._export_type = '_' + handler.lower()
@ -62,6 +64,17 @@ class EnergyBuildingsExportsFactory:
return Idf(self._city, self._path, (idf_data_path / 'Minimal.idf'), (idf_data_path / 'Energy+.idd'), return Idf(self._city, self._path, (idf_data_path / 'Minimal.idf'), (idf_data_path / 'Energy+.idd'),
self._weather_file, target_buildings=self._target_buildings) self._weather_file, 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):
""" """

View File

@ -77,8 +77,8 @@ class CesiumjsTileset:
'function': { 'function': {
'type': 'STRING' 'type': 'STRING'
}, },
'usages_percentage': { 'usages': {
'type': 'STRING' 'type': 'LIST'
} }
} }
} }
@ -146,7 +146,7 @@ class CesiumjsTileset:
'max_height': building.max_height, 'max_height': building.max_height,
'year_of_construction': building.year_of_construction, 'year_of_construction': building.year_of_construction,
'function': building.function, 'function': building.function,
'usages_percentage': building.usages_percentage 'usages': building.usages
} }
}, },
'content': { 'content': {

View File

@ -24,8 +24,7 @@ BTU_H_TO_WATTS = 0.29307107
KILO_WATTS_HOUR_TO_JULES = 3600000 KILO_WATTS_HOUR_TO_JULES = 3600000
WATTS_HOUR_TO_JULES = 3600 WATTS_HOUR_TO_JULES = 3600
GALLONS_TO_QUBIC_METERS = 0.0037854117954011185 GALLONS_TO_QUBIC_METERS = 0.0037854117954011185
INFILTRATION_75PA_TO_4PA = (4/75)**0.65 INFILTRATION_75PA_TO_4PA = (4 / 75) ** 0.65
# time # time
SECOND = 'second' SECOND = 'second'
@ -186,6 +185,19 @@ DAYS_A_MONTH = {JANUARY: 31,
NOVEMBER: 30, NOVEMBER: 30,
DECEMBER: 31} DECEMBER: 31}
HOURS_A_MONTH = {JANUARY: 744,
FEBRUARY: 672,
MARCH: 744,
APRIL: 720,
MAY: 744,
JUNE: 720,
JULY: 744,
AUGUST: 744,
SEPTEMBER: 720,
OCTOBER: 744,
NOVEMBER: 720,
DECEMBER: 744}
# data types # data types
ANY_NUMBER = 'any_number' ANY_NUMBER = 'any_number'
FRACTION = 'fraction' FRACTION = 'fraction'

View File

@ -0,0 +1,31 @@
class ListUsageToHub:
"""
Eilat function to hub function class
"""
def __init__(self, function_dictionary=None):
self._function_dictionary = function_dictionary
def _apply_function_dictionary(self, usages):
function_dictionary = self._function_dictionary
if function_dictionary is not None:
for usage in usages:
if usage['usage'] in function_dictionary:
usage['usage'] = function_dictionary[usage['usage']]
return usages
def parse(self, usages) -> list[dict]:
"""
Get the dictionary
:return: {}
"""
usages = [{"usage": str(i["usage"]), "ratio": float(i["ratio"])} for i in usages]
usages = self._apply_function_dictionary(usages)
return usages

View File

@ -0,0 +1,19 @@
class StringUsageToHub:
"""
Eilat function to hub function class
"""
def parse(self, usages) -> list[dict]:
"""
Parse usage string in form residential-80_commercial-20
:usages: str
:return: {}
"""
parsed_usages = []
for usage in usages.split('_'):
usage_dict = {"usage": str(usage.split('-')[0]), "ratio": float(usage.split('-')[1])/100}
parsed_usages.append(usage_dict)
return parsed_usages

View File

@ -0,0 +1,31 @@
"""
Dictionaries module saves all transformations of functions and usages to access the catalogs
SPDX - License - Identifier: LGPL - 3.0 - or -later
Copyright © 2023 Concordia CERC group
Project Coder Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca
"""
from hub.helpers.parsers.list_usage_to_hub import ListUsageToHub
from hub.helpers.parsers.string_usage_to_hub import StringUsageToHub
class UsageParsers:
"""
Dictionaries class
"""
@staticmethod
def string_usage_to_hub() -> object:
"""
Hub usage to HfT usage, transformation dictionary
:return: dict
"""
return StringUsageToHub().parse
@staticmethod
def list_usage_to_hub(function_dictionary=None) -> object:
"""
Hub usage to HfT usage, transformation dictionary
:return: dict
"""
return ListUsageToHub(function_dictionary).parse

View File

@ -33,21 +33,10 @@ class NrcanPhysicsParameters:
city = self._city city = self._city
nrcan_catalog = ConstructionCatalogFactory('nrcan').catalog nrcan_catalog = ConstructionCatalogFactory('nrcan').catalog
for building in city.buildings: for building in city.buildings:
main_function = None if building.function not in Dictionaries().hub_function_to_nrcan_construction_function:
functions = building.function.split('_') logging.error('Building %s has an unknown building function %s', building.name, building.function)
if len(functions) > 1:
maximum_percentage = 0
for function in functions:
percentage_and_function = function.split('-')
if float(percentage_and_function[0]) > maximum_percentage:
maximum_percentage = float(percentage_and_function[0])
main_function = percentage_and_function[-1]
else:
main_function = functions[-1]
if main_function not in Dictionaries().hub_function_to_nrcan_construction_function:
logging.error('Building %s has an unknown building function %s', building.name, main_function)
continue continue
function = Dictionaries().hub_function_to_nrcan_construction_function[main_function] function = Dictionaries().hub_function_to_nrcan_construction_function[building.function]
try: try:
archetype = self._search_archetype(nrcan_catalog, function, building.year_of_construction, self._climate_zone) archetype = self._search_archetype(nrcan_catalog, function, building.year_of_construction, self._climate_zone)

View File

@ -35,6 +35,8 @@ class Geojson:
year_of_construction_field=None, year_of_construction_field=None,
function_field=None, function_field=None,
function_to_hub=None, function_to_hub=None,
usages_field=None,
usages_to_hub=None,
hub_crs=None hub_crs=None
): ):
self._hub_crs = hub_crs self._hub_crs = hub_crs
@ -52,6 +54,8 @@ class Geojson:
self._year_of_construction_field = year_of_construction_field self._year_of_construction_field = year_of_construction_field
self._function_field = function_field self._function_field = function_field
self._function_to_hub = function_to_hub self._function_to_hub = function_to_hub
self._usages_field = usages_field
self._usages_to_hub = usages_to_hub
with open(path, 'r', encoding='utf8') as json_file: with open(path, 'r', encoding='utf8') as json_file:
self._geojson = json.loads(json_file.read()) self._geojson = json.loads(json_file.read())
@ -117,41 +121,30 @@ class Geojson:
lod = 0 lod = 0
for feature in self._geojson['features']: for feature in self._geojson['features']:
extrusion_height = 0 extrusion_height = 0
if self._extrusion_height_field is not None: if self._extrusion_height_field is not None:
extrusion_height = float(feature['properties'][self._extrusion_height_field]) extrusion_height = float(feature['properties'][self._extrusion_height_field])
lod = 1 lod = 1
self._max_z = max(self._max_z, extrusion_height) self._max_z = max(self._max_z, extrusion_height)
year_of_construction = None year_of_construction = None
if self._year_of_construction_field is not None: if self._year_of_construction_field is not None:
year_of_construction = int(feature['properties'][self._year_of_construction_field]) year_of_construction = int(feature['properties'][self._year_of_construction_field])
function = None function = None
if self._function_field is not None: if self._function_field is not None:
function = str(feature['properties'][self._function_field]) function = str(feature['properties'][self._function_field])
if function == 'Mixed use' or function == 'mixed use':
function_parts = []
if 'usages' in feature['properties']:
usages = feature['properties']['usages']
for usage in usages:
if self._function_to_hub is not None and usage['usage'] in self._function_to_hub:
function_parts.append(f"{usage['percentage']}-{self._function_to_hub[usage['usage']]}")
else:
function_parts.append(f"{usage['percentage']}-{usage['usage']}")
else:
for key, value in feature['properties'].items():
if key.startswith("mixed_type_") and not key.endswith("_percentage"):
type_key = key
percentage_key = f"{key}_percentage"
if percentage_key in feature['properties']:
if self._function_to_hub is not None and feature['properties'][type_key] in self._function_to_hub:
usage_function = self._function_to_hub[feature['properties'][type_key]]
function_parts.append(f"{feature['properties'][percentage_key]}-{usage_function}")
else:
function_parts.append(f"{feature['properties'][percentage_key]}-{feature['properties'][type_key]}")
function = "_".join(function_parts)
if self._function_to_hub is not None: if self._function_to_hub is not None:
# use the transformation dictionary to retrieve the proper function
if function in self._function_to_hub: if function in self._function_to_hub:
function = self._function_to_hub[function] function = self._function_to_hub[function]
usages = None
if self._usages_field is not None:
if self._usages_field in feature['properties']:
usages = feature['properties'][self._usages_field]
if self._usages_to_hub is not None:
usages = self._usages_to_hub(usages)
geometry = feature['geometry'] geometry = feature['geometry']
building_aliases = [] building_aliases = []
if 'id' in feature: if 'id' in feature:
@ -170,6 +163,7 @@ class Geojson:
building_name, building_name,
building_aliases, building_aliases,
function, function,
usages,
year_of_construction, year_of_construction,
extrusion_height)) extrusion_height))
@ -178,6 +172,7 @@ class Geojson:
building_name, building_name,
building_aliases, building_aliases,
function, function,
usages,
year_of_construction, year_of_construction,
extrusion_height)) extrusion_height))
else: else:
@ -203,7 +198,7 @@ class Geojson:
transformed_coordinates = f'{transformed_coordinates} {transformed[self._X]} {transformed[self._Y]} 0.0' transformed_coordinates = f'{transformed_coordinates} {transformed[self._X]} {transformed[self._Y]} 0.0'
return transformed_coordinates.lstrip(' ') return transformed_coordinates.lstrip(' ')
def _parse_polygon(self, coordinates, building_name, building_aliases, function, year_of_construction, extrusion_height): def _parse_polygon(self, coordinates, building_name, building_aliases, function, usages, year_of_construction, extrusion_height):
surfaces = [] surfaces = []
for polygon_coordinates in coordinates: for polygon_coordinates in coordinates:
points = igh.points_from_string( points = igh.points_from_string(
@ -236,7 +231,7 @@ class Geojson:
polygon = Polygon(coordinates) polygon = Polygon(coordinates)
polygon.area = igh.ground_area(coordinates) polygon.area = igh.ground_area(coordinates)
surfaces[-1] = Surface(polygon, polygon) surfaces[-1] = Surface(polygon, polygon)
building = Building(f'{building_name}', surfaces, year_of_construction, function) building = Building(f'{building_name}', surfaces, year_of_construction, function, usages=usages)
for alias in building_aliases: for alias in building_aliases:
building.add_alias(alias) building.add_alias(alias)
if extrusion_height == 0: if extrusion_height == 0:
@ -271,13 +266,13 @@ class Geojson:
polygon = Polygon(wall_coordinates) polygon = Polygon(wall_coordinates)
wall = Surface(polygon, polygon) wall = Surface(polygon, polygon)
surfaces.append(wall) surfaces.append(wall)
building = Building(f'{building_name}', surfaces, year_of_construction, function) building = Building(f'{building_name}', surfaces, year_of_construction, function, usages=usages)
for alias in building_aliases: for alias in building_aliases:
building.add_alias(alias) building.add_alias(alias)
building.volume = volume building.volume = volume
return building return building
def _parse_multi_polygon(self, polygons_coordinates, building_name, building_aliases, function, year_of_construction, extrusion_height): def _parse_multi_polygon(self, polygons_coordinates, building_name, building_aliases, function, usages, year_of_construction, extrusion_height):
surfaces = [] surfaces = []
for coordinates in polygons_coordinates: for coordinates in polygons_coordinates:
for polygon_coordinates in coordinates: for polygon_coordinates in coordinates:
@ -310,7 +305,7 @@ class Geojson:
polygon = Polygon(coordinates) polygon = Polygon(coordinates)
polygon.area = igh.ground_area(coordinates) polygon.area = igh.ground_area(coordinates)
surfaces[-1] = Surface(polygon, polygon) surfaces[-1] = Surface(polygon, polygon)
building = Building(f'{building_name}', surfaces, year_of_construction, function) building = Building(f'{building_name}', surfaces, year_of_construction, function, usages=usages)
for alias in building_aliases: for alias in building_aliases:
building.add_alias(alias) building.add_alias(alias)
if extrusion_height == 0: if extrusion_height == 0:
@ -345,7 +340,7 @@ class Geojson:
polygon = Polygon(wall_coordinates) polygon = Polygon(wall_coordinates)
wall = Surface(polygon, polygon) wall = Surface(polygon, polygon)
surfaces.append(wall) surfaces.append(wall)
building = Building(f'{building_name}', surfaces, year_of_construction, function) building = Building(f'{building_name}', surfaces, year_of_construction, function, usages=usages)
for alias in building_aliases: for alias in building_aliases:
building.add_alias(alias) building.add_alias(alias)
building.volume = volume building.volume = volume

View File

@ -23,6 +23,8 @@ class GeometryFactory:
year_of_construction_field=None, year_of_construction_field=None,
function_field=None, function_field=None,
function_to_hub=None, function_to_hub=None,
usages_field=None,
usages_to_hub=None,
hub_crs=None): hub_crs=None):
self._file_type = '_' + file_type.lower() self._file_type = '_' + file_type.lower()
validate_import_export_type(GeometryFactory, file_type) validate_import_export_type(GeometryFactory, file_type)
@ -32,6 +34,8 @@ class GeometryFactory:
self._year_of_construction_field = year_of_construction_field self._year_of_construction_field = year_of_construction_field
self._function_field = function_field self._function_field = function_field
self._function_to_hub = function_to_hub self._function_to_hub = function_to_hub
self._usages_field = usages_field
self._usages_to_hub = usages_to_hub
self._hub_crs = hub_crs self._hub_crs = hub_crs
@property @property
@ -66,6 +70,8 @@ class GeometryFactory:
self._year_of_construction_field, self._year_of_construction_field,
self._function_field, self._function_field,
self._function_to_hub, self._function_to_hub,
self._usages_field,
self._usages_to_hub,
self._hub_crs).city self._hub_crs).city
@property @property

View File

@ -1,14 +1,12 @@
""" """
Insel monthly energy balance Cerc Idf result import
SPDX - License - Identifier: LGPL - 3.0 - or -later SPDX - License - Identifier: LGPL - 3.0 - or -later
Copyright © 2022 Concordia CERC group Copyright © 2022 Concordia CERC group
Project Coder Saeed Ranjbar saeed.ranjbar@concordia.ca Project Coder Guille Guillermo.GutierrezMorote@concordia.ca
Project collaborator Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca Code contributors: Saeed Ranjbar saeed.ranjbar@concordia.ca
""" """
from pathlib import Path
import csv import csv
from hub.helpers.monthly_values import MonthlyValues
import hub.helpers.constants as cte import hub.helpers.constants as cte
@ -16,62 +14,33 @@ class EnergyPlus:
""" """
Energy plus class Energy plus class
""" """
def __init__(self, city, base_path):
def _extract_fields_from_headers(self, headers):
for header in headers:
header_parts = header.split(':')
building_name = header_parts[0]
variable = ':'.join(header_parts[1:]).strip() # concat the rest and ensure that : it's reintroduced just in case
if variable == '':
continue
if building_name not in self._summary_variables:
self._building_energy_demands[variable] = [] # initialize the list of variables
else:
self._building_energy_demands[header] = []
def __init__(self, city, file_path):
self._city = city self._city = city
self._base_path = base_path self._building_energy_demands = {}
self._lines = []
self._summary_variables = ['DistrictCooling:Facility [J](Hourly)',
'InteriorEquipment:Electricity [J](Hourly)',
'InteriorLights:Electricity [J](Hourly) ']
@staticmethod with open(file_path, 'r', encoding='utf8') as csv_file:
def _building_energy_demands(energy_plus_output_file_path):
with open(Path(energy_plus_output_file_path).resolve(), 'r', encoding='utf8') as csv_file:
csv_output = csv.reader(csv_file) csv_output = csv.reader(csv_file)
headers = next(csv_output) self._headers = next(csv_output)
building_energy_demands = { self._extract_fields_from_headers(self._headers)
'Heating (J)': [],
'Cooling (J)': [],
'DHW (J)': [],
'Appliances (J)': [],
'Lighting (J)': []
}
heating_column_index = []
cooling_column_index = []
dhw_column_index = []
appliance_column_index = []
lighting_column_index = []
for index, header in enumerate(headers):
if "Total Heating" in header:
heating_column_index.append(index)
elif "Total Cooling" in header:
cooling_column_index.append(index)
elif "DHW" in header:
dhw_column_index.append(index)
elif "InteriorEquipment" in header:
appliance_column_index.append(index)
elif "InteriorLights" in header:
lighting_column_index.append(index)
for line in csv_output: for line in csv_output:
total_heating_demand = 0 self._lines.append(line)
total_cooling_demand = 0
total_dhw_demand = 0
total_appliance_demand = 0
total_lighting_demand = 0
for heating_index in heating_column_index:
total_heating_demand += float(line[heating_index])
building_energy_demands['Heating (J)'].append(total_heating_demand)
for cooling_index in cooling_column_index:
total_cooling_demand += float(line[cooling_index])
building_energy_demands['Cooling (J)'].append(total_cooling_demand)
for dhw_index in dhw_column_index:
total_dhw_demand += float(line[dhw_index]) * 3600
building_energy_demands['DHW (J)'].append(total_dhw_demand)
for appliance_index in appliance_column_index:
total_appliance_demand += float(line[appliance_index])
building_energy_demands['Appliances (J)'].append(total_appliance_demand)
for lighting_index in lighting_column_index:
total_lighting_demand += float(line[lighting_index])
building_energy_demands['Lighting (J)'].append(total_lighting_demand)
return building_energy_demands
def enrich(self): def enrich(self):
""" """
@ -79,27 +48,58 @@ class EnergyPlus:
:return: None :return: None
""" """
for building in self._city.buildings: for building in self._city.buildings:
file_name = f'{building.name}_out.csv' _energy_demands = {}
energy_plus_output_file_path = Path(self._base_path / file_name).resolve() for header in self._building_energy_demands:
if energy_plus_output_file_path.is_file(): print(header)
building_energy_demands = self._building_energy_demands(energy_plus_output_file_path) if header == 'Zone Ideal Loads Supply Air Total Heating Energy [J](Hourly)':
building.heating_demand[cte.HOUR] = building_energy_demands['Heating (J)'] field_name = f'{building.name} IDEAL LOADS AIR SYSTEM:{header}'
building.cooling_demand[cte.HOUR] = building_energy_demands['Cooling (J)'] elif header == 'Zone Ideal Loads Supply Air Total Cooling Energy [J](Hourly)':
building.domestic_hot_water_heat_demand[cte.HOUR] = building_energy_demands['DHW (J)'] field_name = f'{building.name} IDEAL LOADS AIR SYSTEM:{header}'
building.appliances_electrical_demand[cte.HOUR] = building_energy_demands['Appliances (J)'] else:
building.lighting_electrical_demand[cte.HOUR] = building_energy_demands['Lighting (J)'] field_name = f'{building.name}:{header}'
# todo: @Saeed, this a list of ONE value with the total energy of the year, exactly the same as cte.YEAR. position = -1
# You have to use the method to add hourly values from helpers/monthly_values if field_name in self._headers:
building.heating_demand[cte.MONTH] = MonthlyValues.get_total_month(building.heating_demand[cte.HOUR]) position = self._headers.index(field_name)
building.cooling_demand[cte.MONTH] = MonthlyValues.get_total_month(building.cooling_demand[cte.HOUR]) if position == -1:
building.domestic_hot_water_heat_demand[cte.MONTH] = ( continue
MonthlyValues.get_total_month(building.domestic_hot_water_heat_demand[cte.HOUR])) for line in self._lines:
building.appliances_electrical_demand[cte.MONTH] = ( if header not in _energy_demands.keys():
MonthlyValues.get_total_month(building.appliances_electrical_demand[cte.HOUR])) _energy_demands[header] = []
building.lighting_electrical_demand[cte.MONTH] = ( _energy_demands[header].append(line[position])
MonthlyValues.get_total_month(building.lighting_electrical_demand[cte.HOUR])) # print(building_energy_demands['Zone Ideal Loads Supply Air Total Heating Energy [J](Hourly)'])
building.heating_demand[cte.YEAR] = [sum(building.heating_demand[cte.MONTH])] EnergyPlus._set_building_demands(building, _energy_demands)
building.cooling_demand[cte.YEAR] = [sum(building.cooling_demand[cte.MONTH])]
building.domestic_hot_water_heat_demand[cte.YEAR] = [sum(building.domestic_hot_water_heat_demand[cte.MONTH])] @staticmethod
building.appliances_electrical_demand[cte.YEAR] = [sum(building.appliances_electrical_demand[cte.MONTH])] def _set_building_demands(building, energy_demands):
building.lighting_electrical_demand[cte.YEAR] = [sum(building.lighting_electrical_demand[cte.MONTH])] print(energy_demands.keys())
heating = [float(x) for x in energy_demands['Zone Ideal Loads Supply Air Total Heating Energy [J](Hourly)']]
cooling = [float(x) for x in energy_demands['Zone Ideal Loads Supply Air Total Cooling Energy [J](Hourly)']]
dhw = [float(x) * cte.WATTS_HOUR_TO_JULES for x in energy_demands['Water Use Equipment Heating Rate [W](Hourly)']]
appliances = [float(x) * cte.WATTS_HOUR_TO_JULES for x in energy_demands['Other Equipment Electricity Rate [W](Hourly)']]
lighting = [float(x) * cte.WATTS_HOUR_TO_JULES for x in energy_demands['Zone Lights Electricity Rate [W](Hourly)']]
building.heating_demand[cte.HOUR] = heating
building.cooling_demand[cte.HOUR] = cooling
building.domestic_hot_water_heat_demand[cte.HOUR] = dhw
building.appliances_electrical_demand[cte.HOUR] = appliances
building.lighting_electrical_demand[cte.HOUR] = lighting
building.heating_demand[cte.MONTH] = []
building.cooling_demand[cte.MONTH] = []
building.domestic_hot_water_heat_demand[cte.MONTH] = []
building.appliances_electrical_demand[cte.MONTH] = []
building.lighting_electrical_demand[cte.MONTH] = []
start = 0
for hours in cte.HOURS_A_MONTH.values():
end = hours + start
building.heating_demand[cte.MONTH].append(sum(building.heating_demand[cte.HOUR][start: end]))
building.cooling_demand[cte.MONTH].append(sum(building.cooling_demand[cte.HOUR][start: end]))
building.domestic_hot_water_heat_demand[cte.MONTH].append(sum(dhw[start: end]))
building.appliances_electrical_demand[cte.MONTH].append(sum(appliances[start: end]))
building.lighting_electrical_demand[cte.MONTH].append(sum(lighting[start: end]))
start = end
building.heating_demand[cte.YEAR] = [sum(building.heating_demand[cte.HOUR])]
building.cooling_demand[cte.YEAR] = [sum(building.cooling_demand[cte.HOUR])]
building.domestic_hot_water_heat_demand[cte.YEAR] = [sum(building.domestic_hot_water_heat_demand[cte.HOUR])]
building.appliances_electrical_demand[cte.YEAR] = [sum(building.appliances_electrical_demand[cte.HOUR])]
building.lighting_electrical_demand[cte.YEAR] = [sum(building.lighting_electrical_demand[cte.HOUR])]

View File

@ -22,9 +22,11 @@ class EnergyPlusMultipleBuildings:
with open(Path(energy_plus_output_file_path).resolve(), 'r', encoding='utf8') as csv_file: with open(Path(energy_plus_output_file_path).resolve(), 'r', encoding='utf8') as csv_file:
csv_output = list(csv.DictReader(csv_file)) csv_output = list(csv.DictReader(csv_file))
print(csv_output)
return
for building in self._city.buildings: for building in self._city.buildings:
building_name = building.name.upper() building_name = building.name.upper()
buildings_energy_demands[f'Building {building_name} Heating Demand (J)'] = [ buildings_energy_demands[f'Building {building_name} Heating Demand (J)'] = [
float( float(
row[f"{building_name} IDEAL LOADS AIR SYSTEM:Zone Ideal Loads Supply Air Total Heating Energy [J](Hourly)"]) row[f"{building_name} IDEAL LOADS AIR SYSTEM:Zone Ideal Loads Supply Air Total Heating Energy [J](Hourly)"])

View File

@ -8,6 +8,7 @@ Code contributors: Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concord
from pathlib import Path from pathlib import Path
from hub.helpers.utils import validate_import_export_type from hub.helpers.utils import validate_import_export_type
from hub.imports.results.energy_plus import EnergyPlus
from hub.imports.results.insel_monthly_energry_balance import InselMonthlyEnergyBalance from hub.imports.results.insel_monthly_energry_balance import InselMonthlyEnergyBalance
from hub.imports.results.simplified_radiosity_algorithm import SimplifiedRadiosityAlgorithm from hub.imports.results.simplified_radiosity_algorithm import SimplifiedRadiosityAlgorithm
@ -60,6 +61,9 @@ class ResultFactory:
""" """
EnergyPlusMultipleBuildings(self._city, self._base_path).enrich() EnergyPlusMultipleBuildings(self._city, self._base_path).enrich()
def _cerc_idf(self):
EnergyPlus(self._city, self._base_path).enrich()
def enrich(self): def enrich(self):
""" """
Enrich the city given to the class using the usage factory given handler Enrich the city given to the class using the usage factory given handler

View File

@ -38,38 +38,36 @@ class ComnetUsageParameters:
city = self._city city = self._city
comnet_catalog = UsageCatalogFactory('comnet').catalog comnet_catalog = UsageCatalogFactory('comnet').catalog
for building in city.buildings: for building in city.buildings:
usages = []
comnet_archetype_usages = [] comnet_archetype_usages = []
building_functions = building.function.split('_') usages = building.usages
for function in building_functions:
usages.append(function.split('-'))
for usage in usages: for usage in usages:
comnet_usage_name = Dictionaries().hub_usage_to_comnet_usage[usage[-1]] comnet_usage_name = Dictionaries().hub_usage_to_comnet_usage[usage['usage']]
try: try:
comnet_archetype_usage = self._search_archetypes(comnet_catalog, comnet_usage_name) comnet_archetype_usage = self._search_archetypes(comnet_catalog, comnet_usage_name)
comnet_archetype_usages.append(comnet_archetype_usage) comnet_archetype_usages.append(comnet_archetype_usage)
except KeyError: except KeyError:
logging.error('Building %s has unknown usage archetype for usage %s', building.name, comnet_usage_name) logging.error('Building %s has unknown usage archetype for usage %s', building.name, comnet_usage_name)
continue continue
for (i, internal_zone) in enumerate(building.internal_zones): for (i, internal_zone) in enumerate(building.internal_zones):
internal_zone_usages = [] internal_zone_usages = []
if len(building.internal_zones) > 1: if len(building.internal_zones) > 1:
volume_per_area = 0 volume_per_area = 0
if internal_zone.area is None: if internal_zone.area is None:
logging.error('Building %s has internal zone area not defined, ACH cannot be calculated for usage %s', logging.error('Building %s has internal zone area not defined, ACH cannot be calculated for usage %s',
building.name, usages[i][-1]) building.name, usages[i]['usage'])
continue continue
if internal_zone.volume is None: if internal_zone.volume is None:
logging.error('Building %s has internal zone volume not defined, ACH cannot be calculated for usage %s', logging.error('Building %s has internal zone volume not defined, ACH cannot be calculated for usage %s',
building.name, usages[i][-1]) building.name, usages[i]['usage'])
continue continue
if internal_zone.area <= 0: if internal_zone.area <= 0:
logging.error('Building %s has internal zone area equal to 0, ACH cannot be calculated for usage %s', logging.error('Building %s has internal zone area equal to 0, ACH cannot be calculated for usage %s',
building.name, usages[i][-1]) building.name, usages[i]['usage'])
continue continue
volume_per_area += internal_zone.volume / internal_zone.area volume_per_area += internal_zone.volume / internal_zone.area
usage = Usage() usage = Usage()
usage.name = usages[i][-1] usage.name = usages[i]['usage']
self._assign_values(usage, comnet_archetype_usages[i], volume_per_area, building.cold_water_temperature) self._assign_values(usage, comnet_archetype_usages[i], volume_per_area, building.cold_water_temperature)
usage.percentage = 1 usage.percentage = 1
self._calculate_reduced_values_from_extended_library(usage, comnet_archetype_usages[i]) self._calculate_reduced_values_from_extended_library(usage, comnet_archetype_usages[i])
@ -80,20 +78,24 @@ class ComnetUsageParameters:
logging.error('Building %s no number of storeys assigned, ACH cannot be calculated for usage %s. ' logging.error('Building %s no number of storeys assigned, ACH cannot be calculated for usage %s. '
'NRCAN construction data for the year %s is used to calculated number of storeys above ' 'NRCAN construction data for the year %s is used to calculated number of storeys above '
'ground', building.name, usages, building.year_of_construction) 'ground', building.name, usages, building.year_of_construction)
storeys_above_ground = self.average_storey_height_calculator(self._city, building) try:
storeys_above_ground = self.average_storey_height_calculator(self._city, building)
except ValueError as e:
logging.error(e)
continue
volume_per_area = building.volume / building.floor_area / storeys_above_ground volume_per_area = building.volume / building.floor_area / storeys_above_ground
for (j, mixed_usage) in enumerate(usages): for j, usage_type in enumerate(usages):
usage = Usage() usage = Usage()
usage.name = mixed_usage[-1] usage.name = usage_type['usage']
if len(usages) > 1: usage.percentage = float(usage_type['ratio'])
usage.percentage = float(mixed_usage[0]) / 100
else:
usage.percentage = 1
self._assign_values(usage, comnet_archetype_usages[j], volume_per_area, building.cold_water_temperature) self._assign_values(usage, comnet_archetype_usages[j], volume_per_area, building.cold_water_temperature)
self._calculate_reduced_values_from_extended_library(usage, comnet_archetype_usages[j]) self._calculate_reduced_values_from_extended_library(usage, comnet_archetype_usages[j])
internal_zone_usages.append(usage) internal_zone_usages.append(usage)
internal_zone.usages = internal_zone_usages internal_zone.usages = internal_zone_usages
@staticmethod @staticmethod
def _search_archetypes(comnet_catalog, usage_name): def _search_archetypes(comnet_catalog, usage_name):
comnet_archetypes = comnet_catalog.entries('archetypes').usages comnet_archetypes = comnet_catalog.entries('archetypes').usages
@ -270,20 +272,11 @@ class ComnetUsageParameters:
def average_storey_height_calculator(city, building): def average_storey_height_calculator(city, building):
climate_zone = ConstructionHelper.city_to_nrcan_climate_zone(city.climate_reference_city) climate_zone = ConstructionHelper.city_to_nrcan_climate_zone(city.climate_reference_city)
nrcan_catalog = ConstructionCatalogFactory('nrcan').catalog nrcan_catalog = ConstructionCatalogFactory('nrcan').catalog
main_function = None
functions = building.function.split('_') if building.function not in Dictionaries().hub_function_to_nrcan_construction_function:
if len(functions) > 1: raise ValueError('Building %s has an unknown building function %s', building.name, building.function)
maximum_percentage = 0
for function in functions: function = Dictionaries().hub_function_to_nrcan_construction_function[building.function]
percentage_and_function = function.split('-')
if float(percentage_and_function[0]) > maximum_percentage:
maximum_percentage = float(percentage_and_function[0])
main_function = percentage_and_function[-1]
else:
main_function = functions[-1]
if main_function not in Dictionaries().hub_function_to_nrcan_construction_function:
logging.error('Building %s has an unknown building function %s', building.name, main_function)
function = Dictionaries().hub_function_to_nrcan_construction_function[main_function]
construction_archetype = None construction_archetype = None
average_storey_height = None average_storey_height = None
nrcan_archetypes = nrcan_catalog.entries('archetypes') nrcan_archetypes = nrcan_catalog.entries('archetypes')
@ -294,7 +287,7 @@ class ComnetUsageParameters:
construction_archetype = building_archetype construction_archetype = building_archetype
average_storey_height = building_archetype.average_storey_height average_storey_height = building_archetype.average_storey_height
if construction_archetype is None: if construction_archetype is None:
logging.error('Building %s has unknown construction archetype for building function: %s ' raise ValueError('Building %s has unknown construction archetype for building function: %s '
'[%s], building year of construction: %s and climate zone %s', building.name, function, '[%s], building year of construction: %s and climate zone %s', building.name, function,
building.function, building.year_of_construction, climate_zone) building.function, building.year_of_construction, climate_zone)

View File

@ -37,21 +37,18 @@ class NrcanUsageParameters:
nrcan_catalog = UsageCatalogFactory('nrcan').catalog nrcan_catalog = UsageCatalogFactory('nrcan').catalog
comnet_catalog = UsageCatalogFactory('comnet').catalog comnet_catalog = UsageCatalogFactory('comnet').catalog
for building in city.buildings: for building in city.buildings:
usages = []
nrcan_archetype_usages = [] nrcan_archetype_usages = []
comnet_archetype_usages = [] comnet_archetype_usages = []
building_functions = building.function.split('_') usages = building.usages
for function in building_functions:
usages.append(function.split('-'))
for usage in usages: for usage in usages:
usage_name = Dictionaries().hub_usage_to_nrcan_usage[usage[-1]] usage_name = Dictionaries().hub_usage_to_nrcan_usage[usage['usage']]
try: try:
archetype_usage = self._search_archetypes(nrcan_catalog, usage_name) archetype_usage = self._search_archetypes(nrcan_catalog, usage_name)
nrcan_archetype_usages.append(archetype_usage) nrcan_archetype_usages.append(archetype_usage)
except KeyError: except KeyError:
logging.error('Building %s has unknown usage archetype for usage %s', building.name, usage_name) logging.error('Building %s has unknown usage archetype for usage %s', building.name, usage_name)
continue continue
comnet_usage_name = Dictionaries().hub_usage_to_comnet_usage[usage[-1]] comnet_usage_name = Dictionaries().hub_usage_to_comnet_usage[usage['usage']]
try: try:
comnet_archetype_usage = self._search_archetypes(comnet_catalog, comnet_usage_name) comnet_archetype_usage = self._search_archetypes(comnet_catalog, comnet_usage_name)
comnet_archetype_usages.append(comnet_archetype_usage) comnet_archetype_usages.append(comnet_archetype_usage)
@ -65,19 +62,19 @@ class NrcanUsageParameters:
volume_per_area = 0 volume_per_area = 0
if internal_zone.area is None: if internal_zone.area is None:
logging.error('Building %s has internal zone area not defined, ACH cannot be calculated for usage %s', logging.error('Building %s has internal zone area not defined, ACH cannot be calculated for usage %s',
building.name, usages[i][-1]) building.name, usages[i]['usage'])
continue continue
if internal_zone.volume is None: if internal_zone.volume is None:
logging.error('Building %s has internal zone volume not defined, ACH cannot be calculated for usage %s', logging.error('Building %s has internal zone volume not defined, ACH cannot be calculated for usage %s',
building.name, usages[i][-1]) building.name, usages[i]['usage'])
continue continue
if internal_zone.area <= 0: if internal_zone.area <= 0:
logging.error('Building %s has internal zone area equal to 0, ACH cannot be calculated for usage %s', logging.error('Building %s has internal zone area equal to 0, ACH cannot be calculated for usage %s',
building.name, usages[i][-1]) building.name, usages[i]['usage'])
continue continue
volume_per_area += internal_zone.volume / internal_zone.area volume_per_area += internal_zone.volume / internal_zone.area
usage = Usage() usage = Usage()
usage.name = usages[i][-1] usage.name = usages[i]['usage']
self._assign_values(usage, nrcan_archetype_usages[i], volume_per_area, building.cold_water_temperature) self._assign_values(usage, nrcan_archetype_usages[i], volume_per_area, building.cold_water_temperature)
self._assign_comnet_extra_values(usage, comnet_archetype_usages[i], nrcan_archetype_usages[i].occupancy.occupancy_density) self._assign_comnet_extra_values(usage, comnet_archetype_usages[i], nrcan_archetype_usages[i].occupancy.occupancy_density)
usage.percentage = 1 usage.percentage = 1
@ -86,19 +83,21 @@ class NrcanUsageParameters:
else: else:
storeys_above_ground = building.storeys_above_ground storeys_above_ground = building.storeys_above_ground
if storeys_above_ground is None: if storeys_above_ground is None:
logging.error('Building %s no number of storeys assigned, ACH cannot be calculated for usage %s. ' logging.error('Building %s no number of storeys assigned, ACH cannot be calculated for function %s. '
'NRCAN construction data for the year %s is used to calculated number of storeys above ' 'NRCAN construction data for the year %s is used to calculated number of storeys above '
'ground', building.name, usages, building.year_of_construction) 'ground', building.name, building.function, building.year_of_construction)
storeys_above_ground = self.average_storey_height_calculator(self._city, building) try:
continue storeys_above_ground = self.average_storey_height_calculator(self._city, building)
except ValueError as e:
logging.error(e)
continue
volume_per_area = building.volume / building.floor_area / storeys_above_ground volume_per_area = building.volume / building.floor_area / storeys_above_ground
for (j, mixed_usage) in enumerate(usages): for j, usage_type in enumerate(usages):
usage = Usage() usage = Usage()
usage.name = mixed_usage[-1] usage.name = usage_type['usage']
if len(usages) > 1: usage.percentage = float(usage_type['ratio'])
usage.percentage = float(mixed_usage[0]) / 100
else:
usage.percentage = 1
self._assign_values(usage, nrcan_archetype_usages[j], volume_per_area, building.cold_water_temperature) self._assign_values(usage, nrcan_archetype_usages[j], volume_per_area, building.cold_water_temperature)
self._assign_comnet_extra_values(usage, comnet_archetype_usages[j], nrcan_archetype_usages[j].occupancy.occupancy_density) self._assign_comnet_extra_values(usage, comnet_archetype_usages[j], nrcan_archetype_usages[j].occupancy.occupancy_density)
self._calculate_reduced_values_from_extended_library(usage, nrcan_archetype_usages[j]) self._calculate_reduced_values_from_extended_library(usage, nrcan_archetype_usages[j])
@ -227,20 +226,11 @@ class NrcanUsageParameters:
def average_storey_height_calculator(city, building): def average_storey_height_calculator(city, building):
climate_zone = ConstructionHelper.city_to_nrcan_climate_zone(city.climate_reference_city) climate_zone = ConstructionHelper.city_to_nrcan_climate_zone(city.climate_reference_city)
nrcan_catalog = ConstructionCatalogFactory('nrcan').catalog nrcan_catalog = ConstructionCatalogFactory('nrcan').catalog
main_function = None
functions = building.function.split('_') if building.function not in Dictionaries().hub_function_to_nrcan_construction_function:
if len(functions) > 1: raise ValueError('Building %s has an unknown building function %s', building.name, building.function)
maximum_percentage = 0
for function in functions: function = Dictionaries().hub_function_to_nrcan_construction_function[building.function]
percentage_and_function = function.split('-')
if float(percentage_and_function[0]) > maximum_percentage:
maximum_percentage = float(percentage_and_function[0])
main_function = percentage_and_function[-1]
else:
main_function = functions[-1]
if main_function not in Dictionaries().hub_function_to_nrcan_construction_function:
logging.error('Building %s has an unknown building function %s', building.name, main_function)
function = Dictionaries().hub_function_to_nrcan_construction_function[main_function]
construction_archetype = None construction_archetype = None
average_storey_height = None average_storey_height = None
nrcan_archetypes = nrcan_catalog.entries('archetypes') nrcan_archetypes = nrcan_catalog.entries('archetypes')
@ -251,7 +241,7 @@ class NrcanUsageParameters:
construction_archetype = building_archetype construction_archetype = building_archetype
average_storey_height = building_archetype.average_storey_height average_storey_height = building_archetype.average_storey_height
if construction_archetype is None: if construction_archetype is None:
logging.error('Building %s has unknown construction archetype for building function: %s ' raise ValueError('Building %s has unknown construction archetype for building function: %s '
'[%s], building year of construction: %s and climate zone %s', building.name, function, '[%s], building year of construction: %s and climate zone %s', building.name, function,
building.function, building.year_of_construction, climate_zone) building.function, building.year_of_construction, climate_zone)

View File

@ -9,6 +9,7 @@ import datetime
import logging import logging
from sqlalchemy import Column, Integer, String, Sequence, ForeignKey, Float from sqlalchemy import Column, Integer, String, Sequence, ForeignKey, Float
from sqlalchemy.dialects.postgresql import JSON
from sqlalchemy import DateTime from sqlalchemy import DateTime
from hub.city_model_structure.building import Building from hub.city_model_structure.building import Building
@ -27,7 +28,7 @@ class CityObject(Models):
type = Column(String, nullable=False) type = Column(String, nullable=False)
year_of_construction = Column(Integer, nullable=True) year_of_construction = Column(Integer, nullable=True)
function = Column(String, nullable=True) function = Column(String, nullable=True)
usage = Column(String, nullable=True) usage = Column(JSON, nullable=True)
volume = Column(Float, nullable=False) volume = Column(Float, nullable=False)
area = Column(Float, nullable=False) area = Column(Float, nullable=False)
total_heating_area = Column(Float, nullable=False) total_heating_area = Column(Float, nullable=False)
@ -46,7 +47,7 @@ class CityObject(Models):
self.type = building.type self.type = building.type
self.year_of_construction = building.year_of_construction self.year_of_construction = building.year_of_construction
self.function = building.function self.function = building.function
self.usage = building.usages_percentage self.usage = building.usages
self.volume = building.volume self.volume = building.volume
self.area = building.floor_area self.area = building.floor_area
self.roof_area = sum(roof.solid_polygon.area for roof in building.roofs) self.roof_area = sum(roof.solid_polygon.area for roof in building.roofs)

View File

@ -1,4 +1,4 @@
""" """
Hub version number Hub version number
""" """
__version__ = '0.2.0.16' __version__ = '0.3.0.5'

View File

@ -1,5 +1,5 @@
xmltodict xmltodict
numpy==1.26.4 numpy
trimesh[all] trimesh[all]
pyproj pyproj
pandas pandas

View File

@ -59,12 +59,14 @@ setup(
'hub.exports', 'hub.exports',
'hub.exports.building_energy', 'hub.exports.building_energy',
'hub.exports.building_energy.idf_files', 'hub.exports.building_energy.idf_files',
'hub.exports.building_energy.idf_helper',
'hub.exports.building_energy.insel', 'hub.exports.building_energy.insel',
'hub.exports.energy_systems', 'hub.exports.energy_systems',
'hub.exports.formats', 'hub.exports.formats',
'hub.helpers', 'hub.helpers',
'hub.helpers.peak_calculation', 'hub.helpers.peak_calculation',
'hub.helpers.data', 'hub.helpers.data',
'hub.helpers.parsers',
'hub.imports', 'hub.imports',
'hub.imports.construction', 'hub.imports.construction',
'hub.imports.construction.helpers', 'hub.imports.construction.helpers',

View File

@ -17,6 +17,7 @@ from hub.exports.exports_factory import ExportsFactory
from hub.helpers.dictionaries import Dictionaries from hub.helpers.dictionaries import Dictionaries
from hub.imports.construction_factory import ConstructionFactory from hub.imports.construction_factory import ConstructionFactory
from hub.imports.geometry_factory import GeometryFactory from hub.imports.geometry_factory import GeometryFactory
from hub.imports.results_factory import ResultFactory
from hub.imports.usage_factory import UsageFactory from hub.imports.usage_factory import UsageFactory
from hub.imports.weather_factory import WeatherFactory from hub.imports.weather_factory import WeatherFactory
@ -136,7 +137,6 @@ class TestExports(TestCase):
year_of_construction_field='ANNEE_CONS', year_of_construction_field='ANNEE_CONS',
function_field='CODE_UTILI', function_field='CODE_UTILI',
function_to_hub=Dictionaries().montreal_function_to_hub_function).city function_to_hub=Dictionaries().montreal_function_to_hub_function).city
self.assertIsNotNone(city, 'city is none') self.assertIsNotNone(city, 'city is none')
EnergyBuildingsExportsFactory('idf', city, self._output_path).export() EnergyBuildingsExportsFactory('idf', city, self._output_path).export()
ConstructionFactory('nrcan', city).enrich() ConstructionFactory('nrcan', city).enrich()
@ -144,6 +144,42 @@ 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() _idf = EnergyBuildingsExportsFactory('idf', city, self._output_path).export()
_idf.run()
except Exception:
self.fail("Idf ExportsFactory raised ExceptionType unexpectedly!")
def test_cerc_idf_export(self):
"""
export to IDF
"""
file = 'test.geojson'
file_path = (self._example_path / file).resolve()
city = GeometryFactory('geojson',
path=file_path,
height_field='citygml_me',
year_of_construction_field='ANNEE_CONS',
function_field='CODE_UTILI',
function_to_hub=Dictionaries().montreal_function_to_hub_function).city
self.assertIsNotNone(city, 'city is none')
ConstructionFactory('nrcan', city).enrich()
UsageFactory('nrcan', city).enrich()
WeatherFactory('epw', city).enrich()
try:
idf = EnergyBuildingsExportsFactory('cerc_idf', city, self._output_path).export()
idf.run()
csv_output_path = (self._output_path / f'{city.name}_out.csv').resolve()
ResultFactory('cerc_idf', city, csv_output_path).enrich()
self.assertTrue(csv_output_path.is_file())
for building in city.buildings:
self.assertIsNotNone(building.heating_demand)
self.assertIsNotNone(building.cooling_demand)
self.assertIsNotNone(building.domestic_hot_water_heat_demand)
self.assertIsNotNone(building.lighting_electrical_demand)
self.assertIsNotNone(building.appliances_electrical_demand)
total_demand = sum(building.heating_demand[cte.HOUR])
total_demand_month = sum(building.heating_demand[cte.MONTH])
self.assertAlmostEqual(total_demand, building.heating_demand[cte.YEAR][0], 2)
self.assertAlmostEqual(total_demand_month, building.heating_demand[cte.YEAR][0], 2)
except Exception: except Exception:
self.fail("Idf ExportsFactory raised ExceptionType unexpectedly!") self.fail("Idf ExportsFactory raised ExceptionType unexpectedly!")

View File

@ -92,42 +92,3 @@ class TestResultsImport(TestCase):
building.cooling_demand[cte.HOUR] = values building.cooling_demand[cte.HOUR] = values
self.assertIsNotNone(building.heating_peak_load) self.assertIsNotNone(building.heating_peak_load)
self.assertIsNotNone(building.cooling_peak_load) self.assertIsNotNone(building.cooling_peak_load)
def test_energy_plus_results_import(self):
ResultFactory('energy_plus_single_building', self._city, self._example_path).enrich()
for building in self._city.buildings:
csv_output_name = f'{building.name}_out.csv'
csv_output_path = (self._example_path / csv_output_name).resolve()
if csv_output_path.is_file():
self.assertEqual(building.name, '12')
self.assertIsNotNone(building.heating_demand)
self.assertIsNotNone(building.cooling_demand)
self.assertIsNotNone(building.domestic_hot_water_heat_demand)
self.assertIsNotNone(building.lighting_electrical_demand)
self.assertIsNotNone(building.appliances_electrical_demand)
total_demand = sum(building.heating_demand[cte.HOUR])
self.assertAlmostEqual(total_demand, building.heating_demand[cte.YEAR][0], 3)
total_demand = sum(building.heating_demand[cte.MONTH])
self.assertEqual(total_demand, building.heating_demand[cte.YEAR][0], 3)
if building.name != '12':
self.assertDictEqual(building.heating_demand, {})
self.assertDictEqual(building.cooling_demand, {})
self.assertDictEqual(building.domestic_hot_water_heat_demand, {})
self.assertDictEqual(building.lighting_electrical_demand, {})
self.assertDictEqual(building.appliances_electrical_demand, {})
def test_energy_plus_multiple_buildings_results_import(self):
ResultFactory('energy_plus_multiple_buildings', self._city, self._example_path).enrich()
csv_output_name = f'{self._city.name}_out.csv'
csv_output_path = (self._example_path / csv_output_name).resolve()
if csv_output_path.is_file():
for building in self._city.buildings:
self.assertIsNotNone(building.heating_demand)
self.assertIsNotNone(building.cooling_demand)
self.assertIsNotNone(building.domestic_hot_water_heat_demand)
self.assertIsNotNone(building.lighting_electrical_demand)
self.assertIsNotNone(building.appliances_electrical_demand)
total_demand = sum(building.heating_demand[cte.HOUR])
self.assertAlmostEqual(total_demand, building.heating_demand[cte.YEAR][0], 2)
total_demand = sum(building.heating_demand[cte.MONTH])
self.assertEqual(total_demand, building.heating_demand[cte.YEAR][0], 2)

View File

@ -11,6 +11,7 @@ from hub.imports.geometry_factory import GeometryFactory
from hub.imports.construction_factory import ConstructionFactory from hub.imports.construction_factory import ConstructionFactory
from hub.imports.usage_factory import UsageFactory from hub.imports.usage_factory import UsageFactory
from hub.helpers.dictionaries import Dictionaries from hub.helpers.dictionaries import Dictionaries
from hub.helpers.usage_parsers import UsageParsers
class TestUsageFactory(TestCase): class TestUsageFactory(TestCase):
@ -75,6 +76,40 @@ class TestUsageFactory(TestCase):
self.assertIsNotNone(usage.thermal_control.heating_set_back, 'control heating set back is none') self.assertIsNotNone(usage.thermal_control.heating_set_back, 'control heating set back is none')
self.assertIsNotNone(usage.thermal_control.mean_cooling_set_point, 'control cooling set point is none') self.assertIsNotNone(usage.thermal_control.mean_cooling_set_point, 'control cooling set point is none')
self.assertIsNotNone(usage.mechanical_air_change, 'mechanical air change is none')
self.assertIsNotNone(usage.thermal_control.heating_set_point_schedules,
'control heating set point schedule is none')
self.assertIsNotNone(usage.thermal_control.cooling_set_point_schedules,
'control cooling set point schedule is none')
self.assertIsNotNone(usage.occupancy, 'occupancy is none')
occupancy = usage.occupancy
self.assertIsNotNone(occupancy.occupancy_density, 'occupancy density is none')
self.assertIsNotNone(occupancy.latent_internal_gain, 'occupancy latent internal gain is none')
self.assertIsNotNone(occupancy.sensible_convective_internal_gain,
'occupancy sensible convective internal gain is none')
self.assertIsNotNone(occupancy.sensible_radiative_internal_gain,
'occupancy sensible radiant internal gain is none')
self.assertIsNotNone(occupancy.occupancy_schedules, 'occupancy schedule is none')
self.assertIsNotNone(usage.lighting, 'lighting is none')
lighting = usage.lighting
self.assertIsNotNone(lighting.density, 'lighting density is none')
self.assertIsNotNone(lighting.latent_fraction, 'lighting latent fraction is none')
self.assertIsNotNone(lighting.convective_fraction, 'lighting convective fraction is none')
self.assertIsNotNone(lighting.radiative_fraction, 'lighting radiant fraction is none')
self.assertIsNotNone(lighting.schedules, 'lighting schedule is none')
self.assertIsNotNone(usage.appliances, 'appliances is none')
appliances = usage.appliances
self.assertIsNotNone(appliances.density, 'appliances density is none')
self.assertIsNotNone(appliances.latent_fraction, 'appliances latent fraction is none')
self.assertIsNotNone(appliances.convective_fraction, 'appliances convective fraction is none')
self.assertIsNotNone(appliances.radiative_fraction, 'appliances radiant fraction is none')
self.assertIsNotNone(appliances.schedules, 'appliances schedule is none')
self.assertIsNotNone(usage.thermal_control.hvac_availability_schedules,
'control hvac availability is none')
self.assertIsNotNone(usage.domestic_hot_water.service_temperature,
'domestic hot water service temperature is none')
self.assertIsNotNone(usage.domestic_hot_water.schedules, 'domestic hot water schedules is none')
def test_import_comnet(self): def test_import_comnet(self):
""" """
Enrich the city with the usage information from comnet and verify it Enrich the city with the usage information from comnet and verify it
@ -91,40 +126,7 @@ class TestUsageFactory(TestCase):
self.assertIsNot(len(internal_zone.usages), 0, 'no building usage defined') self.assertIsNot(len(internal_zone.usages), 0, 'no building usage defined')
for usage in internal_zone.usages: for usage in internal_zone.usages:
self._check_usage(usage) self._check_usage(usage)
self.assertIsNotNone(usage.mechanical_air_change, 'mechanical air change is none')
self.assertIsNotNone(usage.thermal_control.heating_set_point_schedules,
'control heating set point schedule is none')
self.assertIsNotNone(usage.thermal_control.cooling_set_point_schedules,
'control cooling set point schedule is none')
self.assertIsNotNone(usage.occupancy, 'occupancy is none')
occupancy = usage.occupancy
self.assertIsNotNone(occupancy.occupancy_density, 'occupancy density is none')
self.assertIsNotNone(occupancy.latent_internal_gain, 'occupancy latent internal gain is none')
self.assertIsNotNone(occupancy.sensible_convective_internal_gain,
'occupancy sensible convective internal gain is none')
self.assertIsNotNone(occupancy.sensible_radiative_internal_gain,
'occupancy sensible radiant internal gain is none')
self.assertIsNotNone(occupancy.occupancy_schedules, 'occupancy schedule is none')
self.assertIsNotNone(usage.lighting, 'lighting is none')
lighting = usage.lighting
self.assertIsNotNone(lighting.density, 'lighting density is none')
self.assertIsNotNone(lighting.latent_fraction, 'lighting latent fraction is none')
self.assertIsNotNone(lighting.convective_fraction, 'lighting convective fraction is none')
self.assertIsNotNone(lighting.radiative_fraction, 'lighting radiant fraction is none')
self.assertIsNotNone(lighting.schedules, 'lighting schedule is none')
self.assertIsNotNone(usage.appliances, 'appliances is none')
appliances = usage.appliances
self.assertIsNotNone(appliances.density, 'appliances density is none')
self.assertIsNotNone(appliances.latent_fraction, 'appliances latent fraction is none')
self.assertIsNotNone(appliances.convective_fraction, 'appliances convective fraction is none')
self.assertIsNotNone(appliances.radiative_fraction, 'appliances radiant fraction is none')
self.assertIsNotNone(appliances.schedules, 'appliances schedule is none')
self.assertIsNotNone(usage.thermal_control.hvac_availability_schedules,
'control hvac availability is none')
self.assertIsNotNone(usage.domestic_hot_water.density, 'domestic hot water density is none') self.assertIsNotNone(usage.domestic_hot_water.density, 'domestic hot water density is none')
self.assertIsNotNone(usage.domestic_hot_water.service_temperature,
'domestic hot water service temperature is none')
self.assertIsNotNone(usage.domestic_hot_water.schedules, 'domestic hot water schedules is none')
def test_import_nrcan(self): def test_import_nrcan(self):
""" """
@ -148,40 +150,6 @@ class TestUsageFactory(TestCase):
self.assertIsNot(len(internal_zone.usages), 0, 'no building usage defined') self.assertIsNot(len(internal_zone.usages), 0, 'no building usage defined')
for usage in internal_zone.usages: for usage in internal_zone.usages:
self._check_usage(usage) self._check_usage(usage)
self.assertIsNotNone(usage.mechanical_air_change, 'mechanical air change is none')
self.assertIsNotNone(usage.thermal_control.heating_set_point_schedules,
'control heating set point schedule is none')
self.assertIsNotNone(usage.thermal_control.cooling_set_point_schedules,
'control cooling set point schedule is none')
self.assertIsNotNone(usage.occupancy, 'occupancy is none')
occupancy = usage.occupancy
self.assertIsNotNone(occupancy.occupancy_density, 'occupancy density is none')
self.assertIsNotNone(occupancy.latent_internal_gain, 'occupancy latent internal gain is none')
self.assertIsNotNone(occupancy.sensible_convective_internal_gain,
'occupancy sensible convective internal gain is none')
self.assertIsNotNone(occupancy.sensible_radiative_internal_gain,
'occupancy sensible radiant internal gain is none')
self.assertIsNotNone(occupancy.occupancy_schedules, 'occupancy schedule is none')
self.assertIsNotNone(usage.lighting, 'lighting is none')
lighting = usage.lighting
self.assertIsNotNone(lighting.density, 'lighting density is none')
self.assertIsNotNone(lighting.latent_fraction, 'lighting latent fraction is none')
self.assertIsNotNone(lighting.convective_fraction, 'lighting convective fraction is none')
self.assertIsNotNone(lighting.radiative_fraction, 'lighting radiant fraction is none')
self.assertIsNotNone(lighting.schedules, 'lighting schedule is none')
self.assertIsNotNone(usage.appliances, 'appliances is none')
appliances = usage.appliances
self.assertIsNotNone(appliances.density, 'appliances density is none')
self.assertIsNotNone(appliances.latent_fraction, 'appliances latent fraction is none')
self.assertIsNotNone(appliances.convective_fraction, 'appliances convective fraction is none')
self.assertIsNotNone(appliances.radiative_fraction, 'appliances radiant fraction is none')
self.assertIsNotNone(appliances.schedules, 'appliances schedule is none')
self.assertIsNotNone(usage.thermal_control.hvac_availability_schedules,
'control hvac availability is none')
self.assertIsNotNone(usage.domestic_hot_water.peak_flow, 'domestic hot water peak flow is none')
self.assertIsNotNone(usage.domestic_hot_water.service_temperature,
'domestic hot water service temperature is none')
self.assertIsNotNone(usage.domestic_hot_water.schedules, 'domestic hot water schedules is none')
def test_import_palma(self): def test_import_palma(self):
""" """
@ -205,38 +173,35 @@ class TestUsageFactory(TestCase):
self.assertIsNot(len(internal_zone.usages), 0, 'no building usage defined') self.assertIsNot(len(internal_zone.usages), 0, 'no building usage defined')
for usage in internal_zone.usages: for usage in internal_zone.usages:
self._check_usage(usage) self._check_usage(usage)
self.assertIsNotNone(usage.mechanical_air_change, 'mechanical air change is none')
self.assertIsNotNone(usage.thermal_control.heating_set_point_schedules,
'control heating set point schedule is none')
self.assertIsNotNone(usage.thermal_control.cooling_set_point_schedules,
'control cooling set point schedule is none')
self.assertIsNotNone(usage.occupancy, 'occupancy is none')
occupancy = usage.occupancy
self.assertIsNotNone(occupancy.occupancy_density, 'occupancy density is none')
self.assertIsNotNone(occupancy.latent_internal_gain, 'occupancy latent internal gain is none')
self.assertIsNotNone(occupancy.sensible_convective_internal_gain,
'occupancy sensible convective internal gain is none')
self.assertIsNotNone(occupancy.sensible_radiative_internal_gain,
'occupancy sensible radiant internal gain is none')
self.assertIsNotNone(occupancy.occupancy_schedules, 'occupancy schedule is none')
self.assertIsNotNone(usage.lighting, 'lighting is none')
lighting = usage.lighting
self.assertIsNotNone(lighting.density, 'lighting density is none')
self.assertIsNotNone(lighting.latent_fraction, 'lighting latent fraction is none')
self.assertIsNotNone(lighting.convective_fraction, 'lighting convective fraction is none')
self.assertIsNotNone(lighting.radiative_fraction, 'lighting radiant fraction is none')
self.assertIsNotNone(lighting.schedules, 'lighting schedule is none')
self.assertIsNotNone(usage.appliances, 'appliances is none')
appliances = usage.appliances
self.assertIsNotNone(appliances.density, 'appliances density is none')
self.assertIsNotNone(appliances.latent_fraction, 'appliances latent fraction is none')
self.assertIsNotNone(appliances.convective_fraction, 'appliances convective fraction is none')
self.assertIsNotNone(appliances.radiative_fraction, 'appliances radiant fraction is none')
self.assertIsNotNone(appliances.schedules, 'appliances schedule is none')
self.assertIsNotNone(usage.thermal_control.hvac_availability_schedules,
'control hvac availability is none')
self.assertIsNotNone(usage.domestic_hot_water.peak_flow, 'domestic hot water peak flow is none') self.assertIsNotNone(usage.domestic_hot_water.peak_flow, 'domestic hot water peak flow is none')
self.assertIsNotNone(usage.domestic_hot_water.service_temperature,
'domestic hot water service temperature is none')
self.assertIsNotNone(usage.domestic_hot_water.schedules, 'domestic hot water schedules is none')
def test_import_nrcan_multiusage(self):
"""
Enrich the city with the usage information from nrcan and verify it
"""
file = 'test.geojson'
file_path = (self._example_path / file).resolve()
function_dictionary = Dictionaries().montreal_function_to_hub_function
usage_parser = UsageParsers().list_usage_to_hub(function_dictionary=function_dictionary)
city = GeometryFactory('geojson',
path=file_path,
height_field='citygml_me',
year_of_construction_field='ANNEE_CONS',
function_field='CODE_UTILI',
function_to_hub=function_dictionary,
usages_field='usages',
usages_to_hub=usage_parser).city
ConstructionFactory('nrcan', city).enrich()
UsageFactory('nrcan', city).enrich()
self._check_buildings(city)
for building in city.buildings:
for internal_zone in building.internal_zones:
if internal_zone.usages is not None:
self.assertIsNot(len(internal_zone.usages), 0, 'no building usage defined')
for usage in internal_zone.usages:
self._check_usage(usage)

File diff suppressed because one or more lines are too long