Compare commits

..

58 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
faa2c772ba Merge remote-tracking branch 'origin/main' 2024-11-28 22:08:47 +01:00
90353cde16 handle error in wwr 2024-11-28 22:08:34 +01:00
fb10e89248 Update hub/exports/building_energy/idf.py 2024-11-28 15:52:23 -05: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
383bcc976f Update hub/version.py 2024-11-27 12:35:23 -05:00
0d44e38985 Merge pull request 'fix: total_installed_capacity attribute added to PvGeneration class, idf modified, redundant palma file removed' (#76) from feature/pv_epw_fix into main
Reviewed-on: #76
2024-11-27 12:32:58 -05: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 1573 additions and 396 deletions

View File

@ -27,7 +27,7 @@ class Building(CityObject):
"""
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)
self._city = city
self._households = None
@ -36,6 +36,7 @@ class Building(CityObject):
self._terrains = terrains
self._year_of_construction = year_of_construction
self._function = function
self._usages = usages
self._average_storey_height = None
self._storeys_above_ground = None
self._floor_area = None
@ -257,7 +258,17 @@ class Building(CityObject):
:param value: str
"""
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
def average_storey_height(self) -> Union[None, float]:
@ -594,19 +605,6 @@ class Building(CityObject):
"""
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
def energy_systems(self) -> Union[None, List[EnergySystem]]:
"""

View File

@ -34,7 +34,7 @@ class ThermalZone:
volume,
footprint_area,
number_of_storeys,
usage_name=None):
usages=None):
self._id = None
self._parent_internal_zone = parent_internal_zone
self._footprint_area = footprint_area
@ -51,10 +51,6 @@ class ThermalZone:
self._view_factors_matrix = None
self._total_floor_area = None
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._days_year = None
self._mechanical_air_change = None
@ -64,7 +60,12 @@ class ThermalZone:
self._internal_gains = None
self._thermal_control = 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
def parent_internal_zone(self) -> InternalZone:
@ -77,24 +78,11 @@ class ThermalZone:
@property
def usages(self):
"""
Get the thermal zone usages including percentage with the format [percentage]-usage_[percentage]-usage...
Eg: 70-office_30-residential
Get the thermal zone usages
:return: str
"""
if self._usage_from_parent:
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
@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):
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:roofType'] = building.roof_type
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 datetime
import glob
import os
import shutil
import subprocess
from pathlib import Path
from geomeppy import IDF
import hub.helpers.constants as cte
from hub.city_model_structure.attributes.schedule import Schedule
from hub.city_model_structure.building_demand.thermal_zone import ThermalZone
@ -107,6 +109,7 @@ class Idf:
else:
for building_name in target_buildings:
building = city.city_object(building_name)
print('Name: ', building_name)
if building.neighbours is not None:
self._adjacent_buildings += building.neighbours
self._export()
@ -444,7 +447,7 @@ class Idf:
subcategory = f'ELECTRIC EQUIPMENT#{zone_name}#InteriorEquipment'
self._idf.newidfobject(self._APPLIANCES,
Fuel_Type=fuel_type,
Name=f'{zone_name}_appliance',
Name=zone_name,
Zone_or_ZoneList_or_Space_or_SpaceList_Name=zone_name,
Schedule_Name=f'Appliance schedules {thermal_zone.usage_name}',
Design_Level_Calculation_Method=method,
@ -501,7 +504,7 @@ class Idf:
)
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]:
building.Name = f'Buildings in {name}'
building['Solar_Distribution'] = 'FullExterior'
@ -528,11 +531,12 @@ class Idf:
self._remove_sizing_periods()
self._rename_building(self._city.name)
self._lod = self._city.level_of_detail.geometry
is_target = False
for building in self._city.buildings:
is_target = building.name in self._target_buildings or building.name in self._adjacent_buildings
for internal_zone in building.internal_zones:
if internal_zone.thermal_zones_from_internal_zones is None:
self._target_buildings.remoidf_surface_typeve(building.name)
self._target_buildings.remove(building.name)
is_target = False
continue
for thermal_zone in internal_zone.thermal_zones_from_internal_zones:
@ -586,9 +590,7 @@ class Idf:
if self._export_type == "Surfaces":
if is_target:
if building.thermal_zones_from_internal_zones is not None:
start = datetime.datetime.now()
self._add_surfaces(building, building.name)
print(f'add surfaces {datetime.datetime.now() - start}')
else:
self._add_pure_geometry(building, building.name)
else:
@ -651,14 +653,26 @@ class Idf:
self._idf.removeidfobject(window)
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
@property
def _energy_plus(self):
return shutil.which('energyplus')
def run(self):
"""
Start the energy plus simulation
"""
self._idf.run(expandobjects=False, readvars=True, output_directory=self._output_path,
output_prefix=f'{self._city.name}_')
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._idf_file_path]
subprocess.run(cmd, cwd=self._output_path)
def _add_block(self, building):
_points = self._matrix_to_2d_list(building.foot_print.coordinates)
@ -727,7 +741,10 @@ class Idf:
else:
# idf only allows setting wwr for external walls
wwr = 0
self._idf.set_wwr(wwr)
try:
self._idf.set_wwr(wwr, construction='window_construction_1')
except ValueError:
self._idf.set_wwr(0, construction='window_construction_1')
def _add_surfaces(self, building, zone_name):
for thermal_zone in building.thermal_zones_from_internal_zones:
@ -758,13 +775,11 @@ class Idf:
else:
construction_name = f'{boundary.construction_name} {boundary.parent_surface.type}'
_kwargs['Construction_Name'] = construction_name
start = datetime.datetime.now()
surface = self._idf.newidfobject(self._SURFACE, **_kwargs)
coordinates = self._matrix_to_list(boundary.parent_surface.solid_polygon.coordinates,
self._city.lower_corner)
surface.setcoords(coordinates)
if self._lod >= 3:
for internal_zone in building.internal_zones:
for thermal_zone in internal_zone.thermal_zones_from_internal_zones:
@ -776,7 +791,10 @@ class Idf:
for surface in building.surfaces:
if surface.type == cte.WALL:
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):
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.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.helpers.utils import validate_import_export_type
from hub.imports.weather.helpers.weather import Weather as wh
@ -20,6 +21,7 @@ class EnergyBuildingsExportsFactory:
"""
Energy Buildings exports factory class
"""
def __init__(self, handler, city, path, custom_insel_block='d18599', target_buildings=None, weather_file=None):
self._city = city
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'),
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
def _insel_monthly_energy_balance(self):
"""

View File

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

View File

@ -24,8 +24,7 @@ BTU_H_TO_WATTS = 0.29307107
KILO_WATTS_HOUR_TO_JULES = 3600000
WATTS_HOUR_TO_JULES = 3600
GALLONS_TO_QUBIC_METERS = 0.0037854117954011185
INFILTRATION_75PA_TO_4PA = (4/75)**0.65
INFILTRATION_75PA_TO_4PA = (4 / 75) ** 0.65
# time
SECOND = 'second'
@ -186,6 +185,19 @@ DAYS_A_MONTH = {JANUARY: 31,
NOVEMBER: 30,
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
ANY_NUMBER = 'any_number'
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
nrcan_catalog = ConstructionCatalogFactory('nrcan').catalog
for building in city.buildings:
main_function = None
functions = building.function.split('_')
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)
if building.function not in Dictionaries().hub_function_to_nrcan_construction_function:
logging.error('Building %s has an unknown building function %s', building.name, building.function)
continue
function = Dictionaries().hub_function_to_nrcan_construction_function[main_function]
function = Dictionaries().hub_function_to_nrcan_construction_function[building.function]
try:
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,
function_field=None,
function_to_hub=None,
usages_field=None,
usages_to_hub=None,
hub_crs=None
):
self._hub_crs = hub_crs
@ -52,6 +54,8 @@ class Geojson:
self._year_of_construction_field = year_of_construction_field
self._function_field = function_field
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:
self._geojson = json.loads(json_file.read())
@ -117,41 +121,30 @@ class Geojson:
lod = 0
for feature in self._geojson['features']:
extrusion_height = 0
if self._extrusion_height_field is not None:
extrusion_height = float(feature['properties'][self._extrusion_height_field])
lod = 1
self._max_z = max(self._max_z, extrusion_height)
year_of_construction = None
if self._year_of_construction_field is not None:
year_of_construction = int(feature['properties'][self._year_of_construction_field])
function = None
if self._function_field is not None:
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:
# use the transformation dictionary to retrieve the proper function
if function in self._function_to_hub:
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']
building_aliases = []
if 'id' in feature:
@ -170,6 +163,7 @@ class Geojson:
building_name,
building_aliases,
function,
usages,
year_of_construction,
extrusion_height))
@ -178,6 +172,7 @@ class Geojson:
building_name,
building_aliases,
function,
usages,
year_of_construction,
extrusion_height))
else:
@ -203,7 +198,7 @@ class Geojson:
transformed_coordinates = f'{transformed_coordinates} {transformed[self._X]} {transformed[self._Y]} 0.0'
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 = []
for polygon_coordinates in coordinates:
points = igh.points_from_string(
@ -236,7 +231,7 @@ class Geojson:
polygon = Polygon(coordinates)
polygon.area = igh.ground_area(coordinates)
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:
building.add_alias(alias)
if extrusion_height == 0:
@ -271,13 +266,13 @@ class Geojson:
polygon = Polygon(wall_coordinates)
wall = Surface(polygon, polygon)
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:
building.add_alias(alias)
building.volume = volume
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 = []
for coordinates in polygons_coordinates:
for polygon_coordinates in coordinates:
@ -310,7 +305,7 @@ class Geojson:
polygon = Polygon(coordinates)
polygon.area = igh.ground_area(coordinates)
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:
building.add_alias(alias)
if extrusion_height == 0:
@ -345,7 +340,7 @@ class Geojson:
polygon = Polygon(wall_coordinates)
wall = Surface(polygon, polygon)
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:
building.add_alias(alias)
building.volume = volume

View File

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

View File

@ -1,14 +1,12 @@
"""
Insel monthly energy balance
Cerc Idf result import
SPDX - License - Identifier: LGPL - 3.0 - or -later
Copyright © 2022 Concordia CERC group
Project Coder Saeed Ranjbar saeed.ranjbar@concordia.ca
Project collaborator Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca
Project Coder Guille Guillermo.GutierrezMorote@concordia.ca
Code contributors: Saeed Ranjbar saeed.ranjbar@concordia.ca
"""
from pathlib import Path
import csv
from hub.helpers.monthly_values import MonthlyValues
import hub.helpers.constants as cte
@ -16,62 +14,33 @@ class EnergyPlus:
"""
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._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
def _building_energy_demands(energy_plus_output_file_path):
with open(Path(energy_plus_output_file_path).resolve(), 'r', encoding='utf8') as csv_file:
with open(file_path, 'r', encoding='utf8') as csv_file:
csv_output = csv.reader(csv_file)
headers = next(csv_output)
building_energy_demands = {
'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)
self._headers = next(csv_output)
self._extract_fields_from_headers(self._headers)
for line in csv_output:
total_heating_demand = 0
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
self._lines.append(line)
def enrich(self):
"""
@ -79,27 +48,58 @@ class EnergyPlus:
:return: None
"""
for building in self._city.buildings:
file_name = f'{building.name}_out.csv'
energy_plus_output_file_path = Path(self._base_path / file_name).resolve()
if energy_plus_output_file_path.is_file():
building_energy_demands = self._building_energy_demands(energy_plus_output_file_path)
building.heating_demand[cte.HOUR] = building_energy_demands['Heating (J)']
building.cooling_demand[cte.HOUR] = building_energy_demands['Cooling (J)']
building.domestic_hot_water_heat_demand[cte.HOUR] = building_energy_demands['DHW (J)']
building.appliances_electrical_demand[cte.HOUR] = building_energy_demands['Appliances (J)']
building.lighting_electrical_demand[cte.HOUR] = building_energy_demands['Lighting (J)']
# todo: @Saeed, this a list of ONE value with the total energy of the year, exactly the same as cte.YEAR.
# You have to use the method to add hourly values from helpers/monthly_values
building.heating_demand[cte.MONTH] = MonthlyValues.get_total_month(building.heating_demand[cte.HOUR])
building.cooling_demand[cte.MONTH] = MonthlyValues.get_total_month(building.cooling_demand[cte.HOUR])
building.domestic_hot_water_heat_demand[cte.MONTH] = (
MonthlyValues.get_total_month(building.domestic_hot_water_heat_demand[cte.HOUR]))
building.appliances_electrical_demand[cte.MONTH] = (
MonthlyValues.get_total_month(building.appliances_electrical_demand[cte.HOUR]))
building.lighting_electrical_demand[cte.MONTH] = (
MonthlyValues.get_total_month(building.lighting_electrical_demand[cte.HOUR]))
building.heating_demand[cte.YEAR] = [sum(building.heating_demand[cte.MONTH])]
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])]
building.appliances_electrical_demand[cte.YEAR] = [sum(building.appliances_electrical_demand[cte.MONTH])]
building.lighting_electrical_demand[cte.YEAR] = [sum(building.lighting_electrical_demand[cte.MONTH])]
_energy_demands = {}
for header in self._building_energy_demands:
print(header)
if header == 'Zone Ideal Loads Supply Air Total Heating Energy [J](Hourly)':
field_name = f'{building.name} IDEAL LOADS AIR SYSTEM:{header}'
elif header == 'Zone Ideal Loads Supply Air Total Cooling Energy [J](Hourly)':
field_name = f'{building.name} IDEAL LOADS AIR SYSTEM:{header}'
else:
field_name = f'{building.name}:{header}'
position = -1
if field_name in self._headers:
position = self._headers.index(field_name)
if position == -1:
continue
for line in self._lines:
if header not in _energy_demands.keys():
_energy_demands[header] = []
_energy_demands[header].append(line[position])
# print(building_energy_demands['Zone Ideal Loads Supply Air Total Heating Energy [J](Hourly)'])
EnergyPlus._set_building_demands(building, _energy_demands)
@staticmethod
def _set_building_demands(building, energy_demands):
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:
csv_output = list(csv.DictReader(csv_file))
print(csv_output)
return
for building in self._city.buildings:
building_name = building.name.upper()
buildings_energy_demands[f'Building {building_name} Heating Demand (J)'] = [
float(
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 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.simplified_radiosity_algorithm import SimplifiedRadiosityAlgorithm
@ -60,6 +61,9 @@ class ResultFactory:
"""
EnergyPlusMultipleBuildings(self._city, self._base_path).enrich()
def _cerc_idf(self):
EnergyPlus(self._city, self._base_path).enrich()
def enrich(self):
"""
Enrich the city given to the class using the usage factory given handler

View File

@ -38,38 +38,36 @@ class ComnetUsageParameters:
city = self._city
comnet_catalog = UsageCatalogFactory('comnet').catalog
for building in city.buildings:
usages = []
comnet_archetype_usages = []
building_functions = building.function.split('_')
for function in building_functions:
usages.append(function.split('-'))
usages = building.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:
comnet_archetype_usage = self._search_archetypes(comnet_catalog, comnet_usage_name)
comnet_archetype_usages.append(comnet_archetype_usage)
except KeyError:
logging.error('Building %s has unknown usage archetype for usage %s', building.name, comnet_usage_name)
continue
for (i, internal_zone) in enumerate(building.internal_zones):
internal_zone_usages = []
if len(building.internal_zones) > 1:
volume_per_area = 0
if internal_zone.area is None:
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
if internal_zone.volume is None:
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
if internal_zone.area <= 0:
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
volume_per_area += internal_zone.volume / internal_zone.area
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)
usage.percentage = 1
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. '
'NRCAN construction data for the year %s is used to calculated number of storeys above '
'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
for (j, mixed_usage) in enumerate(usages):
for j, usage_type in enumerate(usages):
usage = Usage()
usage.name = mixed_usage[-1]
if len(usages) > 1:
usage.percentage = float(mixed_usage[0]) / 100
else:
usage.percentage = 1
usage.name = usage_type['usage']
usage.percentage = float(usage_type['ratio'])
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])
internal_zone_usages.append(usage)
internal_zone.usages = internal_zone_usages
@staticmethod
def _search_archetypes(comnet_catalog, usage_name):
comnet_archetypes = comnet_catalog.entries('archetypes').usages
@ -270,20 +272,11 @@ class ComnetUsageParameters:
def average_storey_height_calculator(city, building):
climate_zone = ConstructionHelper.city_to_nrcan_climate_zone(city.climate_reference_city)
nrcan_catalog = ConstructionCatalogFactory('nrcan').catalog
main_function = None
functions = building.function.split('_')
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)
function = Dictionaries().hub_function_to_nrcan_construction_function[main_function]
if building.function not in Dictionaries().hub_function_to_nrcan_construction_function:
raise ValueError('Building %s has an unknown building function %s', building.name, building.function)
function = Dictionaries().hub_function_to_nrcan_construction_function[building.function]
construction_archetype = None
average_storey_height = None
nrcan_archetypes = nrcan_catalog.entries('archetypes')
@ -294,8 +287,8 @@ class ComnetUsageParameters:
construction_archetype = building_archetype
average_storey_height = building_archetype.average_storey_height
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,
building.function, building.year_of_construction, climate_zone)
return average_storey_height
return average_storey_height

View File

@ -37,21 +37,18 @@ class NrcanUsageParameters:
nrcan_catalog = UsageCatalogFactory('nrcan').catalog
comnet_catalog = UsageCatalogFactory('comnet').catalog
for building in city.buildings:
usages = []
nrcan_archetype_usages = []
comnet_archetype_usages = []
building_functions = building.function.split('_')
for function in building_functions:
usages.append(function.split('-'))
usages = building.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:
archetype_usage = self._search_archetypes(nrcan_catalog, usage_name)
nrcan_archetype_usages.append(archetype_usage)
except KeyError:
logging.error('Building %s has unknown usage archetype for usage %s', building.name, usage_name)
continue
comnet_usage_name = Dictionaries().hub_usage_to_comnet_usage[usage[-1]]
comnet_usage_name = Dictionaries().hub_usage_to_comnet_usage[usage['usage']]
try:
comnet_archetype_usage = self._search_archetypes(comnet_catalog, comnet_usage_name)
comnet_archetype_usages.append(comnet_archetype_usage)
@ -65,19 +62,19 @@ class NrcanUsageParameters:
volume_per_area = 0
if internal_zone.area is None:
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
if internal_zone.volume is None:
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
if internal_zone.area <= 0:
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
volume_per_area += internal_zone.volume / internal_zone.area
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_comnet_extra_values(usage, comnet_archetype_usages[i], nrcan_archetype_usages[i].occupancy.occupancy_density)
usage.percentage = 1
@ -86,19 +83,21 @@ class NrcanUsageParameters:
else:
storeys_above_ground = building.storeys_above_ground
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 '
'ground', building.name, usages, building.year_of_construction)
storeys_above_ground = self.average_storey_height_calculator(self._city, building)
continue
'ground', building.name, building.function, building.year_of_construction)
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
for (j, mixed_usage) in enumerate(usages):
for j, usage_type in enumerate(usages):
usage = Usage()
usage.name = mixed_usage[-1]
if len(usages) > 1:
usage.percentage = float(mixed_usage[0]) / 100
else:
usage.percentage = 1
usage.name = usage_type['usage']
usage.percentage = float(usage_type['ratio'])
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._calculate_reduced_values_from_extended_library(usage, nrcan_archetype_usages[j])
@ -227,20 +226,11 @@ class NrcanUsageParameters:
def average_storey_height_calculator(city, building):
climate_zone = ConstructionHelper.city_to_nrcan_climate_zone(city.climate_reference_city)
nrcan_catalog = ConstructionCatalogFactory('nrcan').catalog
main_function = None
functions = building.function.split('_')
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)
function = Dictionaries().hub_function_to_nrcan_construction_function[main_function]
if building.function not in Dictionaries().hub_function_to_nrcan_construction_function:
raise ValueError('Building %s has an unknown building function %s', building.name, building.function)
function = Dictionaries().hub_function_to_nrcan_construction_function[building.function]
construction_archetype = None
average_storey_height = None
nrcan_archetypes = nrcan_catalog.entries('archetypes')
@ -251,7 +241,7 @@ class NrcanUsageParameters:
construction_archetype = building_archetype
average_storey_height = building_archetype.average_storey_height
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,
building.function, building.year_of_construction, climate_zone)

View File

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

View File

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

View File

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

View File

@ -59,12 +59,14 @@ setup(
'hub.exports',
'hub.exports.building_energy',
'hub.exports.building_energy.idf_files',
'hub.exports.building_energy.idf_helper',
'hub.exports.building_energy.insel',
'hub.exports.energy_systems',
'hub.exports.formats',
'hub.helpers',
'hub.helpers.peak_calculation',
'hub.helpers.data',
'hub.helpers.parsers',
'hub.imports',
'hub.imports.construction',
'hub.imports.construction.helpers',
@ -109,4 +111,4 @@ setup(
('hub/exports/building_energy/idf_files', glob.glob('hub/exports/building_energy/idf_files/*.idd'))
],
)
)

View File

@ -17,6 +17,7 @@ from hub.exports.exports_factory import ExportsFactory
from hub.helpers.dictionaries import Dictionaries
from hub.imports.construction_factory import ConstructionFactory
from hub.imports.geometry_factory import GeometryFactory
from hub.imports.results_factory import ResultFactory
from hub.imports.usage_factory import UsageFactory
from hub.imports.weather_factory import WeatherFactory
@ -136,7 +137,6 @@ class TestExports(TestCase):
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')
EnergyBuildingsExportsFactory('idf', city, self._output_path).export()
ConstructionFactory('nrcan', city).enrich()
@ -144,6 +144,42 @@ class TestExports(TestCase):
UsageFactory('nrcan', city).enrich()
WeatherFactory('epw', city).enrich()
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:
self.fail("Idf ExportsFactory raised ExceptionType unexpectedly!")

View File

@ -92,42 +92,3 @@ class TestResultsImport(TestCase):
building.cooling_demand[cte.HOUR] = values
self.assertIsNotNone(building.heating_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.usage_factory import UsageFactory
from hub.helpers.dictionaries import Dictionaries
from hub.helpers.usage_parsers import UsageParsers
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.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):
"""
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')
for usage in internal_zone.usages:
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.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):
"""
@ -148,40 +150,6 @@ class TestUsageFactory(TestCase):
self.assertIsNot(len(internal_zone.usages), 0, 'no building usage defined')
for usage in internal_zone.usages:
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):
"""
@ -205,38 +173,35 @@ class TestUsageFactory(TestCase):
self.assertIsNot(len(internal_zone.usages), 0, 'no building usage defined')
for usage in internal_zone.usages:
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_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