from pathlib import Path from scripts.ep_workflow import energy_plus_workflow from hub.helpers.monthly_values import MonthlyValues from hub.imports.geometry_factory import GeometryFactory from hub.helpers.dictionaries import Dictionaries from hub.imports.construction_factory import ConstructionFactory from hub.imports.usage_factory import UsageFactory from hub.imports.weather_factory import WeatherFactory import hub.helpers.constants as cte from hub.imports.energy_systems_factory import EnergySystemsFactory from hub.helpers.peak_loads import PeakLoads from pathlib import Path import subprocess from hub.imports.results_factory import ResultFactory from hub.imports.energy_systems_factory import EnergySystemsFactory from scripts.energy_system_sizing_and_simulation_factory import EnergySystemsSimulationFactory from scripts.solar_angles import CitySolarAngles import hub.helpers.constants as cte from hub.exports.exports_factory import ExportsFactory from scripts.pv_sizing_and_simulation import PVSizingSimulation import pandas as pd import geopandas as gpd import json #%% # ----------------------------------------------- # Specify the GeoJSON file path #%% # ----------------------------------------------- input_files_path = (Path(__file__).parent / 'input_files') output_path = (Path(__file__).parent / 'out_files').resolve() output_path.mkdir(parents=True, exist_ok=True) energy_plus_output_path = output_path / 'energy_plus_outputs' energy_plus_output_path.mkdir(parents=True, exist_ok=True) simulation_results_path = (Path(__file__).parent / 'out_files' / 'simulation_results').resolve() simulation_results_path.mkdir(parents=True, exist_ok=True) sra_output_path = output_path / 'sra_outputs' sra_output_path.mkdir(parents=True, exist_ok=True) cost_analysis_output_path = output_path / 'cost_analysis' cost_analysis_output_path.mkdir(parents=True, exist_ok=True) #%%----------------------------------------------- #"""add geojson paths and create city for Baseline""" #%% # ----------------------------------------------- geojson_file_path_baseline = output_path / 'updated_buildings_with_all_data_baseline.geojson' geojson_file_path_2024 = output_path / 'updated_buildings_with_all_data_2024.geojson' with open(geojson_file_path_baseline , 'r') as f: building_type_data = json.load(f) with open(geojson_file_path_2024, 'r') as f: building_type_data_2024 = json.load(f) # Create city object from GeoJSON file city = GeometryFactory('geojson', path=geojson_file_path_baseline, height_field='maximum_roof_height', year_of_construction_field='year_built', function_field='building_type', function_to_hub=Dictionaries().montreal_function_to_hub_function).city #%%---------------------------------------------- # Enrich city data #%% # ---------------------------------------------- ConstructionFactory('nrcan', city).enrich() UsageFactory('nrcan', city).enrich() WeatherFactory('epw', city).enrich() # #energy plus is not going to be processed here, as demand has been obtained before # energy_plus_workflow(city) #%% # ----------------------------------------------- #"""Enrich city with geojson file data""" #%% # ----------------------------------------------- percentage_data = { 1646: {"type1_%": 1, "type2_%": 0, "type3_%": 0, "roof_area": 2672.550473, "total_floor_area": 26725.50473}, 1647: {"type1_%": 1, "type2_%": 0, "type3_%": 0, "roof_area": 2653.626087, "total_floor_area": 26536.26087}, 1648: {"type1_%": 1, "type2_%": 0, "type3_%": 0, "roof_area": 1056.787496, "total_floor_area": 10567.87496}, 1649: {"type1_%": 1, "type2_%": 0, "type3_%": 0, "roof_area": 1906.620746, "total_floor_area": 19066.20746}, 1650: {"type1_%": 1, "type2_%": 0, "type3_%": 0, "roof_area": 659.1119416, "total_floor_area": 5272.895533}, 1651: {"type1_%": 1, "type2_%": 0, "type3_%": 0, "roof_area": 1167.208109, "total_floor_area": 9337.664871}, 1652: {"type1_%": 1, "type2_%": 0, "type3_%": 0, "roof_area": 1193.251653, "total_floor_area": 9546.013222}, 1653: {"type1_%": 1, "type2_%": 0, "type3_%": 0, "roof_area": 1491.722543, "total_floor_area": 11933.78035}, 1654: {"type1_%": 1, "type2_%": 0, "type3_%": 0, "roof_area": 1168.005028, "total_floor_area": 9344.040224}, 1655: {"type1_%": 1, "type2_%": 0, "type3_%": 0, "roof_area": 1264.906961, "total_floor_area": 10119.25569}, 1656: {"type1_%": 1, "type2_%": 0, "type3_%": 0, "roof_area": 1281.768818, "total_floor_area": 10254.15054}, 1657: {"type1_%": 1, "type2_%": 0, "type3_%": 0, "roof_area": 290.3886018, "total_floor_area": 2323.108814}, 1658: {"type1_%": 1, "type2_%": 0, "type3_%": 0, "roof_area": 847.5095193, "total_floor_area": 6780.076155}, 1659: {"type1_%": 1, "type2_%": 0, "type3_%": 0, "roof_area": 1115.319153, "total_floor_area": 8922.553224}, 1660: {"type1_%": 1, "type2_%": 0, "type3_%": 0, "roof_area": 469.2918062, "total_floor_area": 3754.33445}, 1661: {"type1_%": 1, "type2_%": 0, "type3_%": 0, "roof_area": 1292.298346, "total_floor_area": 10338.38677}, 1662: {"type1_%": 1, "type2_%": 0, "type3_%": 0, "roof_area": 625.7828863, "total_floor_area": 5006.263091}, 1663: {"type1_%": 1, "type2_%": 0, "type3_%": 0, "roof_area": 1876.02897, "total_floor_area": 15008.23176}, 1664: {"type1_%": 1, "type2_%": 0, "type3_%": 0, "roof_area": 1118.224781, "total_floor_area": 22364.49562}, 1665: {"type1_%": 1, "type2_%": 0, "type3_%": 0, "roof_area": 1502.787808, "total_floor_area": 30055.75617}, 1666: {"type1_%": 0.891045711, "type2_%": 0.108954289, "type3_%": 0, "roof_area": 3038.486076, "total_floor_area": 30384.86076}, 1667: {"type1_%": 0.8, "type2_%": 0.2, "type3_%": 0, "roof_area": 1343.832818, "total_floor_area": 13438.32818}, 1668: {"type1_%": 0.7, "type2_%": 0.3, "type3_%": 0, "roof_area": 961.0996956, "total_floor_area": 4805.498478}, 1669: {"type1_%": 1, "type2_%": 0, "type3_%": 0, "roof_area": 489.1282111, "total_floor_area": 1956.512845}, 1673: {"type1_%": 0.7, "type2_%": 0.3, "type3_%": 0, "roof_area": 1693.141465, "total_floor_area": 5079.424396}, 1674: {"type1_%": 0.7, "type2_%": 0.3, "type3_%": 0, "roof_area": 3248.827576, "total_floor_area": 9746.482729}, 1675: {"type1_%": 0.7, "type2_%": 0.3, "type3_%": 0, "roof_area": 4086.842191, "total_floor_area": 12260.52657}, 1676: {"type1_%": 1, "type2_%": 0, "type3_%": 0, "roof_area": 2786.114146, "total_floor_area": 11144.45658}, 1677: {"type1_%": 1, "type2_%": 0, "type3_%": 0, "roof_area": 5142.784184, "total_floor_area": 15428.35255}, 1678: {"type1_%": 0.7, "type2_%": 0.3, "type3_%": 0, "roof_area": 6068.664574, "total_floor_area": 18205.99372}, 1679: {"type1_%": 0.7, "type2_%": 0.3, "type3_%": 0, "roof_area": 5646.751407, "total_floor_area": 16940.25422}, 1680: {"type1_%": 0.7, "type2_%": 0.3, "type3_%": 0, "roof_area": 1601.765953, "total_floor_area": 4805.297859}, 1681: {"type1_%": 0.7, "type2_%": 0.3, "type3_%": 0, "roof_area": 9728.221797, "total_floor_area": 29184.66539}, 1687: {"type1_%": 0.606611029, "type2_%": 0.28211422, "type3_%": 0.11127475, "roof_area": 4268.608743, "total_floor_area": 59760.52241}, 1688: {"type1_%": 0.92, "type2_%": 0.08, "type3_%": 0, "roof_area": 2146.654828, "total_floor_area": 38639.7869}, 1689: {"type1_%": 0.96, "type2_%": 0.04, "type3_%": 0, "roof_area": 2860.270711, "total_floor_area": 57205.41421}, 1690: {"type1_%": 0.94, "type2_%": 0.06, "type3_%": 0, "roof_area": 2189.732519, "total_floor_area": 28466.52275}, 1691: {"type1_%": 0.75, "type2_%": 0.25, "type3_%": 0, "roof_area": 3159.077523, "total_floor_area": 31590.77523}, } def enrich_buildings_with_geojson_data (building_type_data, city): for building in city.buildings: for idx, feature in enumerate(building_type_data['features']): if feature['properties']['id'] == str(building.name): building.heating_demand[cte.HOUR] = [x *1000* cte.WATTS_HOUR_TO_JULES for x in building_type_data['features'][idx]['properties'].get('heating_demand_kWh', [0])] building.cooling_demand[cte.HOUR] = [x *1000* cte.WATTS_HOUR_TO_JULES for x in building_type_data['features'][idx]['properties'].get('cooling_demand_kWh', [0])] building.domestic_hot_water_heat_demand[cte.HOUR] = [x *1000* cte.WATTS_HOUR_TO_JULES for x in building_type_data['features'][idx]['properties'].get('domestic_hot_water_heat_demand_kWh', [0])] building.appliances_electrical_demand[cte.HOUR] = [x *1000* cte.WATTS_HOUR_TO_JULES for x in building_type_data['features'][idx]['properties'].get('appliances_electrical_demand_kWh', [0])] building.lighting_electrical_demand[cte.HOUR] = [x *1000* cte.WATTS_HOUR_TO_JULES for x in building_type_data['features'][idx]['properties'].get('lighting_electrical_demand_kWh', [0])] building.heating_demand[cte.MONTH] = MonthlyValues.get_total_month(building.heating_demand[cte.HOUR]) building.cooling_demand[cte.MONTH] = MonthlyValues.get_total_month(building.cooling_demand[cte.HOUR]) building.domestic_hot_water_heat_demand[cte.MONTH] = (MonthlyValues.get_total_month(building.domestic_hot_water_heat_demand[cte.HOUR])) building.appliances_electrical_demand[cte.MONTH] = (MonthlyValues.get_total_month(building.appliances_electrical_demand[cte.HOUR])) building.lighting_electrical_demand[cte.MONTH] = (MonthlyValues.get_total_month(building.lighting_electrical_demand[cte.HOUR])) building.heating_demand[cte.YEAR] = [sum(building.heating_demand[cte.MONTH])] building.cooling_demand[cte.YEAR] = [sum(building.cooling_demand[cte.MONTH])] building.domestic_hot_water_heat_demand[cte.YEAR] = [sum(building.domestic_hot_water_heat_demand[cte.MONTH])] building.appliances_electrical_demand[cte.YEAR] = [sum(building.appliances_electrical_demand[cte.MONTH])] building.lighting_electrical_demand[cte.YEAR] = [sum(building.lighting_electrical_demand[cte.MONTH])] enrich_buildings_with_geojson_data (building_type_data, city) print('test') #%%----------------------------------------------- # """ADD energy systems""" #%% # ----------------------------------------------- for building in city.buildings: building.energy_systems_archetype_name = 'system 1 electricity' EnergySystemsFactory('montreal_custom', city).enrich() def baseline_to_dict(building): return { 'heating_consumption_kWh': [x / (cte.WATTS_HOUR_TO_JULES * 1000) for x in building.heating_consumption[cte.HOUR]], 'cooling_consumption_kWh':[x / (cte.WATTS_HOUR_TO_JULES * 1000) for x in building.cooling_consumption[cte.HOUR]], 'domestic_hot_water_consumption_kWh': [x / (cte.WATTS_HOUR_TO_JULES * 1000) for x in building.domestic_hot_water_consumption[cte.HOUR]], 'appliances_consumption_kWh':[x / (cte.WATTS_HOUR_TO_JULES * 1000) for x in building.appliances_electrical_demand[cte.HOUR]], 'lighting_consumption_kWh': [x / (cte.WATTS_HOUR_TO_JULES * 1000) for x in building.lighting_electrical_demand[cte.HOUR]] } buildings_dic={} for building in city.buildings: buildings_dic[building.name]=baseline_to_dict(building) scenario={} scenario['baseline']=buildings_dic print("Scenario 1: Baseline is performed successfully") del city del buildings_dic del building_type_data #%%----------------------------------------------- # Scenario 2 #%% # ----------------------------------------------- # Create city object from GeoJSON file city = GeometryFactory('geojson', path=geojson_file_path_2024, height_field='maximum_roof_height', year_of_construction_field='year_built', function_field='building_type', function_to_hub=Dictionaries().montreal_function_to_hub_function).city #%%----------------------------------------------- # Enrich city data #%% # ----------------------------------------------- ConstructionFactory('nrcan', city).enrich() UsageFactory('nrcan', city).enrich() WeatherFactory('epw', city).enrich() enrich_buildings_with_geojson_data (building_type_data_2024, city) def to_dict(building,hourly_pv): return { 'heating_consumption_kWh': [x / (cte.WATTS_HOUR_TO_JULES * 1000) for x in building.heating_consumption[cte.HOUR]], 'cooling_consumption_kWh':[x / (cte.WATTS_HOUR_TO_JULES * 1000) for x in building.cooling_consumption[cte.HOUR]], 'domestic_hot_water_consumption_kWh': [x / (cte.WATTS_HOUR_TO_JULES * 1000) for x in building.domestic_hot_water_consumption[cte.HOUR]], 'appliances_consumption_kWh':[x / (cte.WATTS_HOUR_TO_JULES * 1000) for x in building.appliances_electrical_demand[cte.HOUR]], 'lighting_consumption_kWh': [x / (cte.WATTS_HOUR_TO_JULES * 1000) for x in building.lighting_electrical_demand[cte.HOUR]], 'hourly_pv_kWh': [x /(cte.WATTS_HOUR_TO_JULES * 1000) for x in hourly_pv] } buildings_dic={} for building in city.buildings: building.energy_systems_archetype_name = 'system 1 electricity pv' EnergySystemsFactory('montreal_custom', city).enrich() # #%%----------------------------------------------- # # """SRA""" # #%% # ----------------------------------------------- ExportsFactory('sra', city, output_path).export() sra_path = (output_path / f'{city.name}_sra.xml').resolve() subprocess.run(['sra', str(sra_path)]) ResultFactory('sra', city, output_path).enrich() solar_angles = CitySolarAngles(city.name, city.latitude, city.longitude, tilt_angle=45, surface_azimuth_angle=180).calculate df = pd.DataFrame() df.index = ['yearly lighting (kWh)', 'yearly appliance (kWh)', 'yearly heating (kWh)', 'yearly cooling (kWh)', 'yearly dhw (kWh)', 'roof area (m2)', 'used area for pv (m2)', 'number of panels', 'pv production (kWh)'] for building in city.buildings: ghi = [x / cte.WATTS_HOUR_TO_JULES for x in building.roofs[0].global_irradiance[cte.HOUR]] pv_sizing_simulation = PVSizingSimulation(building, solar_angles, tilt_angle=45, module_height=1, module_width=2, ghi=ghi) pv_sizing_simulation.pv_output() yearly_lighting = building.lighting_electrical_demand[cte.YEAR][0] / 1000 yearly_appliance = building.appliances_electrical_demand[cte.YEAR][0] / 1000 yearly_heating = building.heating_demand[cte.YEAR][0] / (3.6e6 * 3) yearly_cooling = building.cooling_demand[cte.YEAR][0] / (3.6e6 * 4.5) yearly_dhw = building.domestic_hot_water_heat_demand[cte.YEAR][0] / 1000 roof_area = building.roofs[0].perimeter_area used_roof = pv_sizing_simulation.available_space() number_of_pv_panels = pv_sizing_simulation.total_number_of_panels yearly_pv = building.onsite_electrical_production[cte.YEAR][0] / (3.6e6) hourly_pv = building.onsite_electrical_production[cte.HOUR] df[f'{building.name}'] = [yearly_lighting, yearly_appliance, yearly_heating, yearly_cooling, yearly_dhw, roof_area, used_roof, number_of_pv_panels, yearly_pv] buildings_dic[building.name]=to_dict(building,hourly_pv) # %%----------------------------------------------- # """South facing facades""" # %% # ----------------------------------------------- # Function to convert radians to degrees import math def radians_to_degrees(radians): return radians * (180 / math.pi) # Step 1: Create the walls_id dictionary walls_id={} for building in city.buildings: ids = {} for walls in building.walls: id=walls.id azimuth_degree=radians_to_degrees(float(walls.azimuth)) if azimuth_degree>90.0 or azimuth_degree 31: sheet_name = sheet_name[:31] # Truncate to 31 characters if necessary # Write the DataFrame to a specific sheet named after the building_id df_demands.to_excel(writer, sheet_name=sheet_name, index=False) print("district balance data is exported successfully") import pandas as pd # Assuming your scenario dictionary is already defined as follows: # scenario = { # 'baseline': { ... }, # 'efficient with PV': { ... } # } def dict_to_df_col_wise(building_data): """ Converts a dictionary of building data to a DataFrame. Args: building_data (dict): Dictionary containing building data where keys are building ids and values are dictionaries with hourly data for various demand types. Returns: pd.DataFrame: DataFrame with columns for each building and demand type. """ # Create a dictionary to hold DataFrames for each demand type df_dict= {} # Loop over each building for building_id, data in building_data.items(): # Create a DataFrame for this building's data building_df = pd.DataFrame(data) # Rename columns to include building_id building_df.columns = [f"{building_id}_{col}" for col in building_df.columns] # Add this DataFrame to the dictionary df_dict[building_id] = building_df # Concatenate all building DataFrames column-wise result_df = pd.concat(df_dict.values(), axis=1) return result_df # Create DataFrames for each scenario baseline_df = dict_to_df_col_wise(scenario['baseline']) efficient_with_pv_df = dict_to_df_col_wise(scenario['efficient with PV']) efficient_with_pv_hps = dict_to_df_col_wise(scenario['efficient with PV+4Pipe+DHW']) # Write the DataFrames to an Excel file with two separate sheets with pd.ExcelWriter(r'C:\Users\a_gabald\PycharmProjects\summer_course_2024\out_files\scenario_data.xlsx') as writer: baseline_df.to_excel(writer, sheet_name='baseline', index=True) efficient_with_pv_df.to_excel(writer, sheet_name='efficient with PV', index=True) efficient_with_pv_hps.to_excel(writer, sheet_name='efficient with HPs_2', index=True) print("hourly data has been successfully exported per building to scenario_data.xlsx") import pandas as pd import matplotlib.pyplot as plt import numpy as np def convert_hourly_to_monthly(hourly_data): """ Converts hourly data to monthly data by summing up the values for each month. Args: hourly_data (list): List of hourly data (length 8760). Returns: list: List of monthly data (length 12). """ hourly_series = pd.Series(hourly_data, index=pd.date_range(start='1/1/2023', periods=8760, freq='H')) monthly_data = hourly_series.resample('M').sum() return monthly_data.tolist() import os def plot_stacked_demands_vs_pv(district_demand, demand_types, output_path, pv_type='hourly_pv_kWh'): """ Plots the stacked monthly demand for each scenario and compares it to the PV data. Args: district_demand (dict): Dictionary with scenario keys and demand data. demand_types (list): List of demand types to plot. output_path (str): Path to save the plots. pv_type (str): The PV data type to compare against. """ os.makedirs(output_path, exist_ok=True) for scenario_key, demand_data in district_demand.items(): # Convert hourly data to monthly data for each demand type monthly_data = {demand_type: convert_hourly_to_monthly(demand_data[demand_type]) for demand_type in demand_types} monthly_pv = convert_hourly_to_monthly(demand_data.get(pv_type, [0] * 8760)) # Create a DataFrame for easier plotting combined_data = pd.DataFrame(monthly_data) combined_data['Month'] = range(1, 13) combined_data['PV'] = monthly_pv # Plotting fig, ax1 = plt.subplots(figsize=(14, 8)) # Plot stacked demands combined_data.set_index('Month', inplace=True) combined_data[demand_types].plot(kind='bar', stacked=True, ax=ax1, colormap='tab20') ax1.set_xlabel('Month') ax1.set_ylabel('Energy Demand (kWh)') ax1.set_title(f'Monthly Energy Demand and PV Generation for {scenario_key}') # Plot PV data on the secondary y-axis ax2 = ax1.twinx() ax2.plot(combined_data.index, combined_data['PV'], color='black', linestyle='-', marker='o', label='PV Generation') ax2.set_ylabel('PV Generation (kWh)') # Add legends ax1.legend(loc='upper left') ax2.legend(loc='upper right') ax1.set_xticks(combined_data.index) ax1.set_xticklabels(['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']) # Save the plot plt.savefig(os.path.join(output_path, f'{scenario_key}_monthly_demand_vs_pv.png')) plt.close() # Example usage # district_demand = extract_and_sum_demand_data(scenario, demand_types) # Specify the demand types and PV type demand_types = [ 'heating_consumption_kWh', 'cooling_consumption_kWh', 'domestic_hot_water_consumption_kWh', 'appliances_consumption_kWh', 'lighting_consumption_kWh' ] # Plot the data plot_stacked_demands_vs_pv(district_demand, demand_types, output_path) # Plot the data print('test') import csv