diff --git a/scripts/CityBEM_run.py b/scripts/CityBEM_run.py index 2468e37c..3dc67581 100644 --- a/scripts/CityBEM_run.py +++ b/scripts/CityBEM_run.py @@ -36,6 +36,8 @@ def CityBEM_workflow(city): export_building_info(city, CityBEM_path,geojson_data) export_weather_data(city, CityBEM_path) export_comprehensive_building_data(city, CityBEM_path) + export_indoor_temperature_setpoint_data(city, CityBEM_path) + export_internal_heat_gain_data(city, CityBEM_path) run_CityBEM(CityBEM_path) def export_geometry(city, CityBEM_path): """ @@ -65,7 +67,6 @@ def get_building_info(geojson_data, building_id): center = polygon.centroid return function_code, (center.x, center.y) - return None, None def export_building_info(city, CityBEM_path, geojson_file): """ @@ -121,13 +122,13 @@ def export_weather_data(city, CityBEM_path): def export_comprehensive_building_data(city, CityBEM_path): """ - Export all other information from buildings (both physical and thermal properties) + Extract and export detailed individual building data from the hub to replace CityBEM input archetypes, including both physical and thermal properties. :param city: City object containing necessary attributes for the workflow. :param CityBEM_path: Path where CityBEM input and output files are stored. """ - with open(CityBEM_path / 'comprehensive_building_data.csv', 'w', newline='') as textfile: + with open(CityBEM_path / 'Input_comprehensive_building_data_CityLayer.txt', 'w') as textfile: writer = csv.writer(textfile, delimiter=',') - header_row=[ + header_row="\t".join([ #building general information "buildingName", "constructionYear", @@ -144,18 +145,30 @@ def export_comprehensive_building_data(city, CityBEM_path): "roofExternalH", "roofInternalH", "roofUvalue", + "roofLongWaveEmittance", + "roofShortWaveReflectance", + "roofDensity", + "roofSpecificHeat", "roofWWR", #floor details "floorThickness", "floorExternalH", "floorInternalH", "floorUvalue", + "floorLongWaveEmittance", + "floorShortWaveReflectance", + "floorDensity", + "floorSpecificHeat", "floorWWR", #wall details "wallThickness", "wallExternalH", "wallInternalH", "wallUValue", + "wallLongWaveEmittance", + "wallShortWaveReflectance", + "wallDensity", + "wallSpecificHeat", "wallWWRNorth", "wallWWREast", "wallWWRSouth", @@ -168,9 +181,8 @@ def export_comprehensive_building_data(city, CityBEM_path): "thermalBridgesExtraLoses", "infiltrationRateOff", "infiltrationRateOn" - ] - writer.writerow(header_row) #write the header row - + ]) + textfile.write(header_row + "\n") #write the header row #extract and write comprehensive building data from the CityLayer's hub for building in city.buildings: #data should be appended based on the order of the headers. @@ -206,28 +218,72 @@ def export_comprehensive_building_data(city, CityBEM_path): for boundary in internal_zone.thermal_zones_from_internal_zones: for thermal_boundary in boundary.thermal_boundaries: if thermal_boundary.type == "Roof": + layers = thermal_boundary.layers #access the roof construction layers + non_zero_layers = [layer for layer in layers if layer.thickness > 0] #filter out layers with zero thickness + total_thickness = thermal_boundary.thickness + if total_thickness > 0: + weighted_density = sum(layer.thickness * layer.density for layer in non_zero_layers) / total_thickness #weighted average represneting the entire layer. + weighted_specific_heat = sum( + layer.thickness * layer.specific_heat for layer in non_zero_layers) / total_thickness + else: + weighted_specific_heat = 0 #handle the case where total_thickness is zero to avoid division by zero + weighted_density = 0 row_roof = [ thermal_boundary.thickness, thermal_boundary.he, thermal_boundary.hi, thermal_boundary.u_value, + thermal_boundary.external_surface.long_wave_emittance, + thermal_boundary.external_surface.short_wave_reflectance, + weighted_density, + weighted_specific_heat, thermal_boundary.window_ratio ] - elif thermal_boundary.type == "Ground": + elif thermal_boundary.type == "Ground": #ground means floor in CityBEM based on the legacy in CityBEM. + layers = thermal_boundary.layers # access the roof construction layers + non_zero_layers = [layer for layer in layers if layer.thickness > 0] # filter out layers with zero thickness + total_thickness = thermal_boundary.thickness + if total_thickness > 0: + weighted_density = sum( + layer.thickness * layer.density for layer in non_zero_layers) / total_thickness + weighted_specific_heat = sum( + layer.thickness * layer.specific_heat for layer in non_zero_layers) / total_thickness + else: + weighted_specific_heat = 0 # Handle the case where total_thickness is zero to avoid division by zero + weighted_density = 0 row_ground = [ thermal_boundary.thickness, thermal_boundary.he, thermal_boundary.hi, thermal_boundary.u_value, + thermal_boundary.external_surface.long_wave_emittance, + thermal_boundary.external_surface.short_wave_reflectance, + weighted_density, + weighted_specific_heat, thermal_boundary.window_ratio ] elif thermal_boundary.type == "Wall" and wallCount == 0: - wallCount += 1 + wallCount += 1 #wall counter. So far, it is assumed that all the walls have a similar properties to be exported to CityBEM, except the WWR + layers = thermal_boundary.layers # access the roof construction layers + non_zero_layers = [layer for layer in layers if + layer.thickness > 0] # filter out layers with zero thickness + total_thickness = thermal_boundary.thickness + if total_thickness > 0: + weighted_density = sum(layer.thickness * layer.density for layer in non_zero_layers) / total_thickness + weighted_specific_heat = sum( + layer.thickness * layer.specific_heat for layer in non_zero_layers) / total_thickness + else: + weighted_specific_heat = 0 # Handle the case where total_thickness is zero to avoid division by zero + weighted_density = 0 row_wall = [ thermal_boundary.thickness, thermal_boundary.he, thermal_boundary.hi, thermal_boundary.u_value, + thermal_boundary.external_surface.long_wave_emittance, + thermal_boundary.external_surface.short_wave_reflectance, + weighted_density, + weighted_specific_heat, northWWR, eastWWR, southWWR, @@ -244,21 +300,88 @@ def export_comprehensive_building_data(city, CityBEM_path): row.append(thermalBridgesExtraLoses) row.append(infiltrationRateOff) row.append(infiltrationRateOn) - writer.writerow(row) + #convert each item in row to string (if needed) and join with tabs (tab separated data) + row_str = "\t".join(map(str, row)) + #write the final row to the text file + textfile.write(row_str + "\n") + print("Individual building data is exported into a file named comprehensive_building_data.txt") +def export_indoor_temperature_setpoint_data(city, CityBEM_path): + """ + Extract and export individual building data on indoor temperature setpoints + :param city: City object containing necessary attributes for the workflow. + :param CityBEM_path: Path where CityBEM input and output files are stored. + """ + #open a text file in write mode (write mode removes the content if there is any) + with open(CityBEM_path /'Input_indoor_setpoint_temperature_CityLayer.txt', 'w') as textfile: + #iterate through each building + for building in city.buildings: + #write the building name + textfile.write("building"+building.name + '\t') + #iterate through each internal zone in the building + for internal_zone in building.internal_zones: + #iterate through each boundary in the internal zone + for boundary in internal_zone.thermal_zones_from_internal_zones: + #gather all indoor setpoint values for both cooling and heating + indoorSetpointValues = [] + indoorSetpointValues.extend(boundary.thermal_control.cooling_set_point_schedules[0].values)#cooling on working days + indoorSetpointValues.extend(boundary.thermal_control.cooling_set_point_schedules[1].values)#cooling on Saturday + indoorSetpointValues.extend(boundary.thermal_control.cooling_set_point_schedules[2].values)#cooling on Sunday/holidays + indoorSetpointValues.extend(boundary.thermal_control.heating_set_point_schedules[0].values)#heating on working days + indoorSetpointValues.extend(boundary.thermal_control.heating_set_point_schedules[1].values)#heating on Saturday + indoorSetpointValues.extend(boundary.thermal_control.heating_set_point_schedules[2].values)#heating on Sunday/holidays + + #convert values to a tab-separated strings + values_str = '\t'.join(map(str, indoorSetpointValues)) + + #write the values to the text file for this building + textfile.write(values_str + '\n') + + print("Indoor temperature setpoints for every building is successfully exported into a text file named Input_indoor_setpoint_temperature_CityLayer.txt") +def export_internal_heat_gain_data(city, CityBEM_path): + """ + Extract and export individual building data on internal heat gains (occupant, lighting, and equipment) + :param city: City object containing necessary attributes for the workflow. + :param CityBEM_path: Path where CityBEM input and output files are stored. + """ + # open a text file in write mode (write mode removes the content if there is any) + with open(CityBEM_path / 'Input_internal_heat_gain_CityLayer.txt', 'w') as textfile: + # iterate through each building + for building in city.buildings: + # write the building name + textfile.write("building" + building.name + '\t') # (1) building name + # gather all internal heat gains for every building + internalHeatGains = [] + # iterate through each internal zone in the building + for internal_zone in building.internal_zones: + # iterate through each internal usage in the internal zone + for usage in internal_zone.usages: + # iterate through internal heat gains + for internalGain in usage.internal_gains: # order: Occupancy, Lighting, and Appliances + internalHeatGains.append(internalGain.average_internal_gain) # (2) average_internal_gain + internalHeatGains.append(internalGain.convective_fraction) # (3) convective_fraction + internalHeatGains.append(internalGain.latent_fraction) # (4) latent_fraction + internalHeatGains.append(internalGain.radiative_fraction) # (5) radiative_fraction + internalHeatGains.extend(internalGain.schedules[0].values) # (6-29) Working day + internalHeatGains.extend(internalGain.schedules[1].values) # (30-54) Saturday + internalHeatGains.extend(internalGain.schedules[2].values) # (55-79)Sunday + # convert values to a tab-separated strings + values_str = '\t'.join(map(str, internalHeatGains)) + # write the values to the text file for this building + textfile.write(values_str + '\n') + print("Internal heat gains for every building is successfully exported into a text file named Input_internal_heat_gain_CityLayer.txt") def run_CityBEM(CityBEM_path): """ - Run the CityBEM executable after all inputs are processed. - - :param CityBEM_path: Path where CityBEM input and output files are stored. + Run the CityBEM executable after all inputs are processed. + :param CityBEM_path: Path where CityBEM input and output files are stored. """ try: print('CityBEM execution began:') - CityBEM_exe = CityBEM_path / 'CityBEM.exe' #path to the CityBEM executable - #check if the executable file exists + CityBEM_exe = CityBEM_path / 'CityBEM.exe' # path to the CityBEM executable + # check if the executable file exists if not CityBEM_exe.exists(): print(f"Error: {CityBEM_exe} does not exist.") - subprocess.run(str(CityBEM_exe), check=True, cwd=str(CityBEM_path)) #execute the CityBEM executable + subprocess.run(str(CityBEM_exe), check=True, cwd=str(CityBEM_path)) # execute the CityBEM executable print("CityBEM executable has finished successfully.") except Exception as ex: print(ex)