import json import random import datetime from pathlib import Path import pandas as pd import platform import os import xlsxwriter from subprocess import SubprocessError, TimeoutExpired, CalledProcessError from hub.imports.geometry_factory import GeometryFactory from hub.imports.weather_factory import WeatherFactory from hub.imports.construction_factory import ConstructionFactory from hub.imports.usage_factory import UsageFactory from hub.imports.results_factory import ResultFactory from hub.exports.energy_building_exports_factory import EnergyBuildingsExportsFactory from hub.exports.exports_factory import ExportsFactory from hub.helpers.data.montreal_function_to_hub_function import MontrealFunctionToHubFunction from sra import Sra from meb import Meb from meb_results import Results as MEBResults class EnergyValidation: def __init__(self, debug=False): self.debug = debug self.weather_file = Path('./data/CAN_PQ_Montreal.Intl.AP.716270_CWEC.epw').resolve() self.climate_file_name = 'Montreal' self.tmp_folder = Path('./tmp').resolve() self.storage_path = Path('./storage').resolve() self.climate_file = Path(f'{self.storage_path}/{self.climate_file_name}.cli').resolve() self.meb_folder = Path('./results/meb').resolve() self.ep_folder = Path('./results/ep').resolve() self.result_file = Path(f'./results/{datetime.datetime.now().strftime("%m-%d-%Y_%H-%M-%S")}' f'_energy_validation_results.xlsx').resolve() results_xlsx = xlsxwriter.Workbook(self.result_file) simulation_data_worksheet = results_xlsx.add_worksheet('Simulation data') metadata_worksheet = results_xlsx.add_worksheet('Metadata') bold = results_xlsx.add_format({'bold': True}) simulation_data_worksheet.write('A1', 'Building', bold) simulation_data_worksheet.write('B1', 'Month', bold) simulation_data_worksheet.write('C1', 'Heating demand (kWh/m2)', bold) simulation_data_worksheet.write('D1', 'Cooling demand (kWh/m2)', bold) simulation_data_worksheet.write('E1', 'Appliances demand (kWh/m2)', bold) simulation_data_worksheet.write('F1', 'Lighting demand (kWh/m2)', bold) simulation_data_worksheet.write('G1', 'Domestic hot water demand (kWh/m2)', bold) simulation_data_worksheet.write('H1', 'Source of simulation (kWh/m2)', bold) metadata_worksheet.write('A1', 'Building id', bold) metadata_worksheet.write('B1', 'Storeys', bold) metadata_worksheet.write('C1', 'M2 per storey', bold) metadata_worksheet.write('D1', 'Total m2', bold) metadata_worksheet.write('E1', 'Total m3', bold) metadata_worksheet.write('F1', 'Year building', bold) metadata_worksheet.write('G1', 'Type of building', bold) results_xlsx.close() if platform.system() == 'Windows': self.encoding = 'windows-1252' else: self.encoding = 'utf-8' @staticmethod def _sort_buildings(buildings_to_simulate): sorted_buildings = {} for building in buildings_to_simulate: code_utili = building['properties']['CODE_UTILI'] if not sorted_buildings.get(code_utili): sorted_buildings[code_utili] = [] sorted_buildings[code_utili].append(building) return sorted_buildings def _save_meb_results(self, demand, metadata, building_area): results = [] building_name = metadata.iloc[0,0].split(': ')[1] # start by formatting the meb results # convert from Wh to kWh/m^2 demand *= 0.001/building_area # replace indexes with month/day/year format months = { 'month': [ '1/1/2023', '2/1/2023', '3/1/2023', '4/1/2023', '5/1/2023', '6/1/2023', '7/1/2023', '8/1/2023', '9/1/2023', '10/1/2023', '11/1/2023', '12/1/2023' ]} demand.iloc[:, 0] = pd.DataFrame(months) # insert building_name to first column demand.insert(0, 'building_name', building_name) # swap lighting and appliances columns demand[f'{building_name} lighting electrical demand Wh'], \ demand[f'{building_name} appliances electrical demand Wh'] = \ demand[f'{building_name} appliances electrical demand Wh'], \ demand[f'{building_name} lighting electrical demand Wh'] # insert simulation source to last column demand['source'] = 'meb' # format building metadata ''' metadata format: building_id number_of_storeys m2_per_storey total_m2 total_m3 year_of_construction building_usage TODO: number_of_adjacent_walls ''' formatted_metadata = pd.DataFrame({ 'metadata': [ metadata.iloc[0, 0].split(': ')[1], metadata.iloc[4, 0].split(': ')[1], metadata.iloc[3, 0].split(': ')[1], building_area, metadata.iloc[6, 0].split(': ')[1], metadata.iloc[1, 0].split(': ')[1], metadata.iloc[2, 0].split(': ')[1] ]}).transpose() # last, but not least, append our lovely reformatted data to the results spreadsheet with pd.ExcelWriter(self.result_file, engine='openpyxl', if_sheet_exists='overlay', mode='a') as writer: demand.to_excel( writer, startrow=writer.sheets['Simulation data'].max_row, sheet_name='Simulation data', index=False, header=False, ) formatted_metadata.to_excel( writer, startrow=writer.sheets['Metadata'].max_row, sheet_name='Metadata', index=False, header=False, ) def _save_ep_results(self, demand, building_area, building_name): demand.drop('Date/Time', axis=1, inplace=True) # convert from J to kWh/m^2 demand *= (2.77778e-7/building_area) # insert date in month/day/year format months = [ '1/1/2023', '2/1/2023', '3/1/2023', '4/1/2023', '5/1/2023', '6/1/2023', '7/1/2023', '8/1/2023', '9/1/2023', '10/1/2023', '11/1/2023', '12/1/2023' ] demand.insert(0, 'month', months) # insert building_name demand.insert(0, 'building_name', building_name) # insert hot water demand demand['water_usage'] = demand.iloc[:,5] - demand.iloc[:,2] # add simulation source as ep demand['source'] = 'ep' # drop unneeded columns demand.drop(demand.columns[3], axis=1, inplace=True) demand.drop(demand.columns[3], axis=1, inplace=True) demand.drop(demand.columns[3], axis=1, inplace=True) # last, but not least, append our lovely reformatted data to the results spreadsheet with pd.ExcelWriter(self.result_file, engine='openpyxl', if_sheet_exists='overlay', mode='a') as writer: demand.to_excel( writer, startrow=writer.sheets['Simulation data'].max_row, sheet_name='Simulation data', index=False, header=False, ) def run(self, building_set, building_quantities, cleanup=True): sorted_buildings = self._sort_buildings(building_set) min_m2_satisfied = False for code_utili in building_quantities: if code_utili not in sorted_buildings: print(f'CODE_UTILI:{code_utili} is not found in the provided dataset.') else: for building in range(building_quantities[code_utili]): building_to_simulate = [] min_m2_satisfied = False # only select buildings with an area of 500 m^2 or more while not min_m2_satisfied: building_to_simulate.append(sorted_buildings[code_utili][random.randrange( len(sorted_buildings[code_utili]))]) if building_to_simulate[0]['properties']['bldgarea'] < 500: building_to_simulate.clear() else: min_m2_satisfied = True building_id = building_to_simulate[0]['id'] geojson = { "type": "FeatureCollection", "features": building_to_simulate } geojson_file = open(f'tmp/{building_id}_energy_validation.geojson', 'w') geojson_file.write(json.dumps(geojson, indent=2)) geojson_file.close() # run enrichment factories city = GeometryFactory('geojson', path=f'tmp/{building_id}_energy_validation.geojson', height_field='building_height', year_of_construction_field='ANNEE_CONS', function_field='CODE_UTILI', function_to_hub=MontrealFunctionToHubFunction().dictionary).city WeatherFactory('epw', city, file_name='./CAN_PQ_Montreal.Intl.AP.716270_CWEC.epw').enrich() ConstructionFactory('nrcan', city).enrich() UsageFactory('nrcan', city).enrich() if city.climate_reference_city is None: city.name = f'{building_id}_energy_validation' city.climate_reference_city = city.location self.climate_file_name = city.location city.climate_file = self.climate_file city.name = f'{building_id}_energy_validation' try: # starting sra print(f'{building_id} starting sra') ExportsFactory('sra', city, self.tmp_folder, weather_file=self.weather_file, weather_format='epw').export() sra_file = (self.tmp_folder / f'{city.name}_sra.xml').resolve() sra_start = datetime.datetime.now() Sra(sra_file, self.tmp_folder).run() sra_end = datetime.datetime.now() - sra_start ResultFactory('sra', city, self.tmp_folder).enrich() # run meb print(f'{building_id} starting meb') for building in city.buildings: building.attic_heated = 0 building.basement_heated = 1 EnergyBuildingsExportsFactory('insel_monthly_energy_balance', city, self.tmp_folder).export() meb_start = datetime.datetime.now() Meb(self.tmp_folder).run() meb_end = datetime.datetime.now() - meb_start ResultFactory('insel_meb', city, self.tmp_folder).enrich() results = MEBResults(city, Path('./results/meb/').resolve()) results.print() # run energyplus print(f'{building_id} starting energy plus') idf_file = EnergyBuildingsExportsFactory('idf', city, self.ep_folder).export() ep_start = datetime.datetime.now() idf_file.run() ep_end = datetime.datetime.now() - ep_start # save meb results to energy_validation_results total_m2 = city.buildings[0].internal_zones[0].thermal_zones[0].total_floor_area meb_results = pd.read_csv(Path(f'{self.meb_folder}/demand.csv').resolve(), encoding=self.encoding) meb_metadata = pd.read_csv(Path(f'{self.meb_folder}/metadata.csv').resolve(), encoding=self.encoding) self._save_meb_results(meb_results, meb_metadata, total_m2) # save ep results to energy_validation_results ep_results = pd.read_csv(Path(f'{self.ep_folder}/{building_id}_energy_validation_out.csv').resolve(), encoding=self.encoding) self._save_ep_results(ep_results, total_m2, city.buildings[0].name) print(f"{building_id} sra time: {sra_end}") print(f"{building_id} meb time: {meb_end}") print(f"{building_id} ep time: {ep_end}") except (SubprocessError, TimeoutExpired, CalledProcessError) as error: if self.debug is True: raise Exception(error) else: print(building_id, " failed SRA, MEB, or EP") building_with_error = pd.DataFrame([ building_id, 'NA', 'NA', 'NA', 'NA', 'NA', 'NA', 'NA', ]).transpose() with pd.ExcelWriter(self.result_file, engine='openpyxl', if_sheet_exists='overlay', mode='a') as writer: building_with_error.to_excel( writer, startrow=writer.sheets['Simulation data'].max_row, sheet_name='Simulation data', index=False, header=False, ) if cleanup is True: [os.remove(os.path.join(self.tmp_folder, file)) for file in os.listdir(self.tmp_folder)]