From 62c9a5aab7883286127bb30d8b9bbdf6fdea28a8 Mon Sep 17 00:00:00 2001 From: guille Date: Mon, 16 Sep 2024 17:34:43 +0200 Subject: [PATCH] cerc idf implementation --- hub/exports/building_energy/cerc_idf.py | 104 +++++++++++++++--- .../building_energy/idf_helper/__init__.py | 4 + tests/test_exports.py | 4 +- 3 files changed, 96 insertions(+), 16 deletions(-) diff --git a/hub/exports/building_energy/cerc_idf.py b/hub/exports/building_energy/cerc_idf.py index 54ec7559..6d253c4b 100644 --- a/hub/exports/building_energy/cerc_idf.py +++ b/hub/exports/building_energy/cerc_idf.py @@ -25,7 +25,7 @@ class CercIdf: _windows_added_to_idf = {'glazing_index': 0} _constructions_added_to_idf = {} - def __init__(self, city, output_path, idf_file_path, idd_file_path, epw_file_path, export_type="Surfaces", + def __init__(self, city, output_path, idf_file_path, idd_file_path, epw_file_path, target_buildings=None): self._city = city self._output_path = str(output_path.resolve()) @@ -41,9 +41,10 @@ class CercIdf: self._output_lighting_path = str((output_path / 'lights.idf').resolve()) self._output_appliances_path = str((output_path / 'appliances.idf').resolve()) self._output_surfaces_path = str((output_path / 'surfaces.idf').resolve()) + self._output_infiltration_path = str((output_path / 'infiltration.idf').resolve()) + self._output_ventilation_path = str((output_path / 'ventilation.idf').resolve()) self._output_file_path = str((output_path / f'{city.name}.idf').resolve()) - self._export_type = export_type self._idd_file_path = str(idd_file_path) self._idf_file_path = str(idf_file_path) self._epw_file_path = str(epw_file_path) @@ -71,7 +72,12 @@ class CercIdf: self._output_lighting = open(self._output_lighting_path, 'w') self._output_appliances = open(self._output_appliances_path, 'w') self._output_surfaces = open(self._output_surfaces_path, 'w') + self._output_infiltration = open(self._output_infiltration_path, 'w') + self._output_ventilation = open(self._output_ventilation_path, 'w') + + # Create base values self._create_geometry_rules() + with open(self._output_file_path, 'w') as self._idf_file: self._idf_file.writelines(lines) self._export() @@ -88,20 +94,21 @@ class CercIdf: self._output_lighting.close() self._output_appliances.close() self._output_surfaces.close() + self._output_infiltration.close() + self._output_ventilation.close() def _create_geometry_rules(self): - """ - GlobalGeometryRules, - UpperLeftCorner, !- Starting Vertex Position - CounterClockWise, !- Vertex Entry Direction - World; !- Coordinate System - """ file = self._output_zones 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 _create_output_control_lighting(self): + file = self._output_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 != '': @@ -235,7 +242,8 @@ class CercIdf: 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()) + file_name = str( + (Path(self._output_path) / f'{schedule_type} schedules {usage.replace("/", "_")}.csv').resolve()) with open(file_name, 'w', encoding='utf8') as file: for value in schedule.values: file.write(f'{str(value)},\n') @@ -437,11 +445,12 @@ class CercIdf: 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._output_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, f'Lighting schedules {thermal_zone.usage_name}', 'Schedule 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') @@ -453,7 +462,7 @@ class CercIdf: 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',';') + self._write_to_idf_format(file, 0, 'Return Air Fraction Function of Plenum Temperature Coefficient 2', ';') def _add_appliances(self, thermal_zone, zone_name): schedule_name = f'Appliance schedules {thermal_zone.usage_name}' @@ -476,6 +485,57 @@ class CercIdf: self._write_to_idf_format(file, 0, 'Carbon Dioxide Generation Rate') self._write_to_idf_format(file, subcategory, 'EndUse Subcategory', ';') + def _add_infiltration(self, thermal_zone, zone_name): + schedule_name = f'Infiltration schedules {thermal_zone.usage_name}' + infiltration = thermal_zone.infiltration_rate_system_off * cte.HOUR_TO_SECONDS + file = self._output_infiltration + self._write_to_idf_format(file, idf_cte.INFILTRATION) + self._write_to_idf_format(file, f'{zone_name}_infiltration', '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 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', ';') + + def _add_ventilation(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 + infiltration = thermal_zone.infiltration_rate_system_off * cte.HOUR_TO_SECONDS + file = self._output_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', ';') + def _merge_files(self): self._output_schedules.flush() with open(self._output_schedules_path, 'r') as file: @@ -517,6 +577,15 @@ class CercIdf: with open(self._output_appliances_path, 'r') as file: lines = file.readlines() self._idf_file.writelines(lines) + # todo: this should be move down instead it's here to simplify validation + self._output_infiltration.flush() + with open(self._output_infiltration_path, 'r') as file: + lines = file.readlines() + self._idf_file.writelines(lines) + self._output_ventilation.flush() + with open(self._output_ventilation_path, 'r') as file: + lines = file.readlines() + self._idf_file.writelines(lines) self._output_surfaces.flush() with open(self._output_surfaces_path, 'r') as file: lines = file.readlines() @@ -548,14 +617,18 @@ class CercIdf: self._add_idf_schedules(usage, 'Ventilation', self._create_ventilation_schedules(thermal_zone)) self._add_idf_schedules(usage, 'Occupancy', thermal_zone.occupancy.occupancy_schedules) self._add_idf_schedules(usage, 'HVAC AVAIL', thermal_zone.thermal_control.hvac_availability_schedules) - self._add_idf_schedules(usage, 'Heating thermostat', thermal_zone.thermal_control.heating_set_point_schedules) - self._add_idf_schedules(usage, 'Cooling thermostat', thermal_zone.thermal_control.cooling_set_point_schedules) + self._add_idf_schedules(usage, 'Heating thermostat', + thermal_zone.thermal_control.heating_set_point_schedules) + self._add_idf_schedules(usage, 'Cooling thermostat', + thermal_zone.thermal_control.cooling_set_point_schedules) self._add_idf_schedules(usage, 'Lighting', thermal_zone.lighting.schedules) self._add_idf_schedules(usage, 'Appliance', thermal_zone.appliances.schedules) self._add_idf_schedules(usage, 'DHW_prof', thermal_zone.domestic_hot_water.schedules) self._add_idf_schedules(usage, 'DHW_temp', self._create_constant_value_schedules(service_temperature, 24)) self._add_idf_schedules(usage, 'Activity Level', self._create_constant_value_schedules(total_heat, 24)) - self._add_file_schedules(usage, 'cold_temp', self._create_constant_value_schedules(building.cold_water_temperature[cte.HOUR], 24)) + self._add_file_schedules(usage, 'cold_temp', + self._create_constant_value_schedules(building.cold_water_temperature[cte.HOUR], + 24)) for thermal_boundary in thermal_zone.thermal_boundaries: self._add_materials(thermal_boundary) self._add_constructions(thermal_boundary) @@ -566,8 +639,11 @@ class CercIdf: self._add_occupancy(thermal_zone, building.name) self._add_lighting(thermal_zone, building.name) self._add_appliances(thermal_zone, building.name) + self._add_infiltration(thermal_zone, building.name) + self._add_ventilation(thermal_zone, building.name) if is_target: self._add_surfaces(building, building.name) + self._create_output_control_lighting() # Add lighting control to the lighting # Merge files self._merge_files() diff --git a/hub/exports/building_energy/idf_helper/__init__.py b/hub/exports/building_energy/idf_helper/__init__.py index 31cf5399..b1384c96 100644 --- a/hub/exports/building_energy/idf_helper/__init__.py +++ b/hub/exports/building_energy/idf_helper/__init__.py @@ -12,6 +12,10 @@ 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' + AUTOCALCULATE = 'autocalculate' ROUGHNESS = 'MediumRough' diff --git a/tests/test_exports.py b/tests/test_exports.py index d7b68b2b..ac881960 100644 --- a/tests/test_exports.py +++ b/tests/test_exports.py @@ -138,9 +138,9 @@ class TestExports(TestCase): function_to_hub=Dictionaries().montreal_function_to_hub_function).city self.assertIsNotNone(city, 'city is none') - EnergyBuildingsExportsFactory('idf', city, self._output_path).export() + #EnergyBuildingsExportsFactory('idf', city, self._output_path).export() ConstructionFactory('nrcan', city).enrich() - EnergyBuildingsExportsFactory('idf', city, self._output_path).export() + # EnergyBuildingsExportsFactory('idf', city, self._output_path).export() UsageFactory('nrcan', city).enrich() WeatherFactory('epw', city).enrich() try: