330 lines
14 KiB
Python
330 lines
14 KiB
Python
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)] |