diff --git a/hub/exports/building_energy/cerc_idf.py b/hub/exports/building_energy/cerc_idf.py index 6d253c4b..001be16d 100644 --- a/hub/exports/building_energy/cerc_idf.py +++ b/hub/exports/building_energy/cerc_idf.py @@ -24,26 +24,34 @@ class CercIdf: _materials_added_to_idf = {} _windows_added_to_idf = {'glazing_index': 0} _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): self._city = city self._output_path = str(output_path.resolve()) - self._output_file_path = str((output_path / f'{city.name}.idf').resolve()) - self._output_schedules_path = str((output_path / 'schedules.idf').resolve()) - self._output_file_schedules_path = str((output_path / 'file_schedules.idf').resolve()) - self._output_solid_materials_path = str((output_path / 'solid_materials.idf').resolve()) - self._output_nomass_materials_path = str((output_path / 'nomass_materials.idf').resolve()) - self._output_window_materials_path = str((output_path / 'window_materials.idf').resolve()) - self._output_constructions_path = str((output_path / 'constructions.idf').resolve()) - self._output_zones_path = str((output_path / 'zones.idf').resolve()) - self._output_occupancy_path = str((output_path / 'occupancy.idf').resolve()) - 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._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()), + 'occupancy': str((output_path / 'occupancy.idf').resolve()), + 'lighting': str((output_path / 'lights.idf').resolve()), + 'appliances': str((output_path / 'appliances.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()), + 'surfaces': str((output_path / 'surfaces.idf').resolve()) # todo: move surface to the right position after appliances + } + self._files = {} + for key, value in self._file_paths.items(): + self._files[key] = open(value, 'w') self._idd_file_path = str(idd_file_path) self._idf_file_path = str(idf_file_path) @@ -61,20 +69,6 @@ class CercIdf: with open(self._idf_file_path, 'r') as base_idf: lines = base_idf.readlines() - self._output_schedules = open(self._output_schedules_path, 'w') - self._output_file_schedules = open(self._output_file_schedules_path, 'w') - self._output_solid_materials = open(self._output_solid_materials_path, 'w') - self._output_nomass_materials = open(self._output_nomass_materials_path, 'w') - self._output_window_materials = open(self._output_window_materials_path, 'w') - self._output_constructions = open(self._output_constructions_path, 'w') - self._output_zones = open(self._output_zones_path, 'w') - self._output_occupancy = open(self._output_occupancy_path, 'w') - 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() @@ -82,30 +76,23 @@ class CercIdf: self._idf_file.writelines(lines) self._export() - def __del__(self): - self._output_schedules.close() - self._output_file_schedules.close() - self._output_solid_materials.close() - self._output_nomass_materials.close() - self._output_window_materials.close() - self._output_constructions.close() - self._output_zones.close() - self._output_occupancy.close() - self._output_lighting.close() - self._output_appliances.close() - self._output_surfaces.close() - self._output_infiltration.close() - self._output_ventilation.close() + def _merge_files(self): + for file in self._files.values(): + file.close() + for path in self._file_paths.values(): + with open(path, 'r') as file: + lines = file.readlines() + self._idf_file.writelines(lines) def _create_geometry_rules(self): - file = self._output_zones + file = self._files['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 + file = self._files['appliances'] self._write_to_idf_format(file, idf_cte.OUTPUT_CONTROL) self._write_to_idf_format(file, 'Comma', 'Column Separator', ';') @@ -132,7 +119,7 @@ class CercIdf: def _add_surfaces(self, building, zone_name): # Verify if create building surfaces "by hand" it's faster wwr it's missing zone_name = f'{zone_name}' - file = self._output_surfaces + 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[boundary.parent_surface.type] @@ -247,7 +234,7 @@ class CercIdf: with open(file_name, 'w', encoding='utf8') as file: for value in schedule.values: file.write(f'{str(value)},\n') - file = self._output_file_schedules + 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') @@ -266,7 +253,7 @@ class CercIdf: schedule_name = f'{schedule_type} schedules {usage}' if schedule_name not in self._schedules_added_to_idf: self._schedules_added_to_idf[schedule_name] = True - file = self._output_schedules + 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') @@ -286,7 +273,7 @@ class CercIdf: self._write_to_idf_format(file, 'Until: 24:00,0.0', f'Field {counter + 2}', ';') def _add_solid_material(self, layer): - file = self._output_solid_materials + 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') @@ -299,7 +286,7 @@ class CercIdf: self._write_to_idf_format(file, layer.visible_absorptance, 'Visible Absorptance', ';') def _add_nomass_material(self, layer): - file = self._output_nomass_materials + 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') @@ -336,7 +323,7 @@ class CercIdf: glazing_index = self._windows_added_to_idf['glazing_index'] + 1 self._windows_added_to_idf[name] = True self._windows_added_to_idf['glazing_index'] = glazing_index # increase the count - file = self._output_window_materials + file = self._files['window_materials'] self._write_to_idf_format(file, idf_cte.WINDOW_MATERIAL) self._write_to_idf_format(file, f'glazing_{glazing_index}', 'Name') self._write_to_idf_format(file, thermal_opening.overall_u_value, 'UFactor') @@ -356,7 +343,7 @@ class CercIdf: if name not in self._constructions_added_to_idf: self._constructions_added_to_idf[name] = True - file = self._output_constructions + file = self._files['constructions'] self._write_to_idf_format(file, idf_cte.CONSTRUCTION) self._write_to_idf_format(file, name, 'Name') eol = ',' @@ -377,13 +364,13 @@ class CercIdf: material_name = f'glazing_{i}' if construction_name not in self._constructions_added_to_idf: self._constructions_added_to_idf[construction_name] = True - file = self._output_constructions + 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, material_name, 'Outside Layer', ';') def _add_zone(self, thermal_zone, zone_name): - file = self._output_zones + 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') @@ -409,7 +396,7 @@ class CercIdf: 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._output_occupancy + 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') @@ -446,7 +433,7 @@ class CercIdf: 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 + 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') @@ -469,7 +456,7 @@ class CercIdf: 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._output_appliances + file = self._files['appliances'] self._write_to_idf_format(file, idf_cte.APPLIANCES) self._write_to_idf_format(file, f'{zone_name}_appliance', 'Name') self._write_to_idf_format(file, 'Electricity', 'Fuel Type') @@ -488,7 +475,7 @@ class CercIdf: 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 + file = self._files['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') @@ -507,7 +494,7 @@ class CercIdf: 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 + 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') @@ -536,60 +523,54 @@ class CercIdf: 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: - lines = file.readlines() - self._idf_file.writelines(lines) - self._output_file_schedules.flush() - with open(self._output_file_schedules_path, 'r') as file: - lines = file.readlines() - self._idf_file.writelines(lines) - self._output_solid_materials.flush() - with open(self._output_solid_materials_path, 'r') as file: - lines = file.readlines() - self._idf_file.writelines(lines) - self._output_nomass_materials.flush() - with open(self._output_nomass_materials_path, 'r') as file: - lines = file.readlines() - self._idf_file.writelines(lines) - self._output_window_materials.flush() - with open(self._output_window_materials_path, 'r') as file: - lines = file.readlines() - self._idf_file.writelines(lines) - self._output_constructions.flush() - with open(self._output_constructions_path, 'r') as file: - lines = file.readlines() - self._idf_file.writelines(lines) - self._output_zones.flush() - with open(self._output_zones_path, 'r') as file: - lines = file.readlines() - self._idf_file.writelines(lines) - self._output_occupancy.flush() - with open(self._output_occupancy_path, 'r') as file: - lines = file.readlines() - self._idf_file.writelines(lines) - self._output_lighting.flush() - with open(self._output_lighting_path, 'r') as file: - lines = file.readlines() - self._idf_file.writelines(lines) - self._output_appliances.flush() - 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() - self._idf_file.writelines(lines) + def _add_thermostat(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', ';') + + def _add_heating_system(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', ';') def _export(self): for building in self._city.buildings: @@ -641,6 +622,8 @@ class CercIdf: self._add_appliances(thermal_zone, building.name) self._add_infiltration(thermal_zone, building.name) self._add_ventilation(thermal_zone, building.name) + self._add_thermostat(thermal_zone) + self._add_heating_system(thermal_zone, building.name) if is_target: self._add_surfaces(building, building.name) diff --git a/hub/exports/building_energy/idf_helper/__init__.py b/hub/exports/building_energy/idf_helper/__init__.py index b1384c96..ff4b64ae 100644 --- a/hub/exports/building_energy/idf_helper/__init__.py +++ b/hub/exports/building_energy/idf_helper/__init__.py @@ -15,6 +15,8 @@ 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' AUTOCALCULATE = 'autocalculate'