From 7369bc65a4c7c6de39750499fdb68a849c275540 Mon Sep 17 00:00:00 2001 From: s_ranjbar Date: Fri, 28 Jun 2024 16:40:37 -0400 Subject: [PATCH 1/3] fix: first stages of report creation are started --- main.py | 2 +- report_test.py | 41 +++++ scripts/energy_system_retrofit_report.py | 196 +++++++++++++++++++++++ scripts/report_creation.py | 61 ++++--- 4 files changed, 275 insertions(+), 25 deletions(-) create mode 100644 report_test.py create mode 100644 scripts/energy_system_retrofit_report.py diff --git a/main.py b/main.py index a0383a6d..5140022c 100644 --- a/main.py +++ b/main.py @@ -65,4 +65,4 @@ for building in city.buildings: costs.loc['global_capital_costs', f'Scenario {SYSTEM_RETROFIT}'].to_csv( output_path / f'{building.name}_cc.csv') costs.loc['global_maintenance_costs', f'Scenario {SYSTEM_RETROFIT}'].to_csv( - output_path / f'{building.name}_m.csv') \ No newline at end of file + output_path / f'{building.name}_m.csv') diff --git a/report_test.py b/report_test.py new file mode 100644 index 00000000..638b9213 --- /dev/null +++ b/report_test.py @@ -0,0 +1,41 @@ +from pathlib import Path +import subprocess +from scripts.ep_run_enrich import energy_plus_workflow +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 +from hub.imports.results_factory import ResultFactory +from scripts.energy_system_retrofit_report import EnergySystemRetrofitReport +from scripts.geojson_creator import process_geojson +from scripts import random_assignation +from hub.imports.energy_systems_factory import EnergySystemsFactory +from scripts.energy_system_sizing import SystemSizing +from scripts.energy_system_retrofit_results import system_results, new_system_results +from scripts.energy_system_sizing_and_simulation_factory import EnergySystemsSimulationFactory +from scripts.costs.cost import Cost +from scripts.costs.constants import SKIN_RETROFIT_AND_SYSTEM_RETROFIT_AND_PV, SYSTEM_RETROFIT_AND_PV +import hub.helpers.constants as cte +from hub.exports.exports_factory import ExportsFactory + +geojson_file = process_geojson(x=-73.5681295982132, y=45.49218262677643, diff=0.0001) +file_path = (Path(__file__).parent / 'input_files' / 'output_buildings.geojson') +output_path = (Path(__file__).parent / 'out_files').resolve() +city = GeometryFactory('geojson', + path=file_path, + height_field='height', + year_of_construction_field='year_of_construction', + function_field='function', + function_to_hub=Dictionaries().montreal_function_to_hub_function).city +ConstructionFactory('nrcan', city).enrich() +UsageFactory('nrcan', city).enrich() +WeatherFactory('epw', city).enrich() +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() +energy_plus_workflow(city) + +(EnergySystemRetrofitReport(city, output_path, 'PV Implementation and HVAC Retrofit'). + create_report(current_system=None, new_system=None)) diff --git a/scripts/energy_system_retrofit_report.py b/scripts/energy_system_retrofit_report.py new file mode 100644 index 00000000..6209c5bc --- /dev/null +++ b/scripts/energy_system_retrofit_report.py @@ -0,0 +1,196 @@ +import os +import hub.helpers.constants as cte +import matplotlib.pyplot as plt +import random +import matplotlib.colors as mcolors +from matplotlib import cm +from scripts.report_creation import LatexReport +import matplotlib as mpl +from matplotlib.ticker import MaxNLocator +import numpy as np +from pathlib import Path + +class EnergySystemRetrofitReport: + def __init__(self, city, output_path, retrofit_scenario): + self.city = city + self.output_path = output_path + self.content = [] + self.report = LatexReport('energy_system_retrofit_report', + 'Energy System Retrofit Report', retrofit_scenario, output_path) + self.charts_path = Path(output_path) / 'charts' + self.charts_path.mkdir(parents=True, exist_ok=True) + + def building_energy_info(self): + table_data = [ + ["Building Name", "Year of Construction", "function", "Yearly Heating Demand (MWh)", + "Yearly Cooling Demand (MWh)", "Yearly DHW Demand (MWh)", "Yearly Electricity Demand (MWh)"] + ] + intensity_table_data = [["Building Name", "Total Floor Area m2", "Heating Demand Intensity kWh/m2", + "Cooling Demand Intensity kWh/m2", "Electricity Intensity kWh/m2"]] + + for building in self.city.buildings: + total_floor_area = 0 + for zone in building.thermal_zones_from_internal_zones: + total_floor_area += zone.total_floor_area + building_data = [ + building.name, + str(building.year_of_construction), + building.function, + str(format(building.heating_demand[cte.YEAR][0] / 3.6e9, '.2f')), + str(format(building.cooling_demand[cte.YEAR][0] / 3.6e9, '.2f')), + str(format(building.domestic_hot_water_heat_demand[cte.YEAR][0] / 3.6e9, '.2f')), + str(format( + (building.lighting_electrical_demand[cte.YEAR][0] + building.appliances_electrical_demand[cte.YEAR][0]) + / 3.6e9, '.2f')), + ] + intensity_data = [ + building.name, + str(format(total_floor_area, '.2f')), + str(format(building.heating_demand[cte.YEAR][0] / (3.6e6 * total_floor_area), '.2f')), + str(format(building.cooling_demand[cte.YEAR][0] / (3.6e6 * total_floor_area), '.2f')), + str(format( + (building.lighting_electrical_demand[cte.YEAR][0] + building.appliances_electrical_demand[cte.YEAR][0]) / + (3.6e6 * total_floor_area), '.2f')) + ] + table_data.append(building_data) + intensity_table_data.append(intensity_data) + + self.report.add_table(table_data, caption='Buildings Energy Consumption Data') + self.report.add_table(intensity_table_data, caption='Buildings Energy Use Intensity Data') + + def monthly_demands(self): + heating = [] + cooling = [] + dhw = [] + lighting_appliance = [] + for i in range(12): + heating_demand = 0 + cooling_demand = 0 + dhw_demand = 0 + lighting_appliance_demand = 0 + for building in self.city.buildings: + heating_demand += building.heating_demand[cte.MONTH][i] / 3.6e6 + cooling_demand += building.cooling_demand[cte.MONTH][i] / 3.6e6 + dhw_demand += building.domestic_hot_water_heat_demand[cte.MONTH][i] / 3.6e6 + lighting_appliance_demand += building.lighting_electrical_demand[cte.MONTH][i] / 3.6e6 + heating.append(heating_demand) + cooling.append(cooling_demand) + dhw.append(dhw_demand) + lighting_appliance.append(lighting_appliance_demand) + + monthly_demands = {'heating': heating, + 'cooling': cooling, + 'dhw': dhw, + 'lighting_appliance': lighting_appliance} + return monthly_demands + + def plot_monthly_energy_demands(self, demands, file_name): + # Data preparation + months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'] + heating = demands['heating'] + cooling = demands['cooling'] + dhw = demands['dhw'] + electricity = demands['lighting_appliance'] + + # Plotting + fig, axs = plt.subplots(2, 2, figsize=(15, 10), dpi=96) + fig.suptitle('Monthly Energy Demands', fontsize=16, weight='bold', alpha=.8) + + # Heating bar chart + axs[0, 0].bar(months, heating, color='#2196f3', width=0.6, zorder=2) + axs[0, 0].grid(which="major", axis='x', color='#DAD8D7', alpha=0.5, zorder=1) + axs[0, 0].grid(which="major", axis='y', color='#DAD8D7', alpha=0.5, zorder=1) + axs[0, 0].set_xlabel('Month', fontsize=12, labelpad=10) + axs[0, 0].set_ylabel('Heating Demand (kWh)', fontsize=12, labelpad=10) + axs[0, 0].set_title('Monthly Heating Demands', fontsize=14, weight='bold', alpha=.8) + axs[0, 0].xaxis.set_major_locator(MaxNLocator(integer=True)) + axs[0, 0].yaxis.set_major_locator(MaxNLocator(integer=True)) + axs[0, 0].set_xticks(np.arange(len(months))) + axs[0, 0].set_xticklabels(months, rotation=45, ha='right') + axs[0, 0].bar_label(axs[0, 0].containers[0], padding=3, color='black', fontsize=8) + axs[0, 0].spines[['top', 'left', 'bottom']].set_visible(False) + axs[0, 0].spines['right'].set_linewidth(1.1) + average_heating = np.mean(heating) + axs[0, 0].axhline(y=average_heating, color='grey', linewidth=2, linestyle='--') + axs[0, 0].text(len(months)-1, average_heating, f'Average = {average_heating:.1f} kWh', ha='right', va='bottom', color='grey') + + # Cooling bar chart + axs[0, 1].bar(months, cooling, color='#ff5a5f', width=0.6, zorder=2) + axs[0, 1].grid(which="major", axis='x', color='#DAD8D7', alpha=0.5, zorder=1) + axs[0, 1].grid(which="major", axis='y', color='#DAD8D7', alpha=0.5, zorder=1) + axs[0, 1].set_xlabel('Month', fontsize=12, labelpad=10) + axs[0, 1].set_ylabel('Cooling Demand (kWh)', fontsize=12, labelpad=10) + axs[0, 1].set_title('Monthly Cooling Demands', fontsize=14, weight='bold', alpha=.8) + axs[0, 1].xaxis.set_major_locator(MaxNLocator(integer=True)) + axs[0, 1].yaxis.set_major_locator(MaxNLocator(integer=True)) + axs[0, 1].set_xticks(np.arange(len(months))) + axs[0, 1].set_xticklabels(months, rotation=45, ha='right') + axs[0, 1].bar_label(axs[0, 1].containers[0], padding=3, color='black', fontsize=8) + axs[0, 1].spines[['top', 'left', 'bottom']].set_visible(False) + axs[0, 1].spines['right'].set_linewidth(1.1) + average_cooling = np.mean(cooling) + axs[0, 1].axhline(y=average_cooling, color='grey', linewidth=2, linestyle='--') + axs[0, 1].text(len(months)-1, average_cooling, f'Average = {average_cooling:.1f} kWh', ha='right', va='bottom', color='grey') + + # DHW bar chart + axs[1, 0].bar(months, dhw, color='#4caf50', width=0.6, zorder=2) + axs[1, 0].grid(which="major", axis='x', color='#DAD8D7', alpha=0.5, zorder=1) + axs[1, 0].grid(which="major", axis='y', color='#DAD8D7', alpha=0.5, zorder=1) + axs[1, 0].set_xlabel('Month', fontsize=12, labelpad=10) + axs[1, 0].set_ylabel('DHW Demand (kWh)', fontsize=12, labelpad=10) + axs[1, 0].set_title('Monthly DHW Demands', fontsize=14, weight='bold', alpha=.8) + axs[1, 0].xaxis.set_major_locator(MaxNLocator(integer=True)) + axs[1, 0].yaxis.set_major_locator(MaxNLocator(integer=True)) + axs[1, 0].set_xticks(np.arange(len(months))) + axs[1, 0].set_xticklabels(months, rotation=45, ha='right') + axs[1, 0].bar_label(axs[1, 0].containers[0], padding=3, color='black', fontsize=8) + axs[1, 0].spines[['top', 'left', 'bottom']].set_visible(False) + axs[1, 0].spines['right'].set_linewidth(1.1) + average_dhw = np.mean(dhw) + axs[1, 0].axhline(y=average_dhw, color='grey', linewidth=2, linestyle='--') + axs[1, 0].text(len(months)-1, average_dhw, f'Average = {average_dhw:.1f} kWh', ha='right', va='bottom', color='grey') + + # Electricity bar chart + axs[1, 1].bar(months, electricity, color='#ffc107', width=0.6, zorder=2) + axs[1, 1].grid(which="major", axis='x', color='#DAD8D7', alpha=0.5, zorder=1) + axs[1, 1].grid(which="major", axis='y', color='#DAD8D7', alpha=0.5, zorder=1) + axs[1, 1].set_xlabel('Month', fontsize=12, labelpad=10) + axs[1, 1].set_ylabel('Electricity Demand (kWh)', fontsize=12, labelpad=10) + axs[1, 1].set_title('Monthly Electricity Demands', fontsize=14, weight='bold', alpha=.8) + axs[1, 1].xaxis.set_major_locator(MaxNLocator(integer=True)) + axs[1, 1].yaxis.set_major_locator(MaxNLocator(integer=True)) + axs[1, 1].set_xticks(np.arange(len(months))) + axs[1, 1].set_xticklabels(months, rotation=45, ha='right') + axs[1, 1].bar_label(axs[1, 1].containers[0], padding=3, color='black', fontsize=8) + axs[1, 1].spines[['top', 'left', 'bottom']].set_visible(False) + axs[1, 1].spines['right'].set_linewidth(1.1) + average_electricity = np.mean(electricity) + axs[1, 1].axhline(y=average_electricity, color='grey', linewidth=2, linestyle='--') + axs[1, 1].text(len(months)-1, average_electricity * 0.95, f'Average = {average_electricity:.1f} kWh', ha='right', va='top', color='grey') + + # Set a white background + fig.patch.set_facecolor('white') + + # Adjust the margins around the plot area + plt.subplots_adjust(left=0.05, right=0.95, top=0.9, bottom=0.1, wspace=0.3, hspace=0.5) + + + # Save the plot + chart_path = self.charts_path / f'{file_name}.png' + plt.savefig(chart_path, bbox_inches='tight') + plt.close() + + return chart_path + + def create_report(self, current_system, new_system): + os.chdir(self.charts_path) + self.report.add_section('Current Status') + self.report.add_subsection('City Buildings Characteristics') + self.building_energy_info() + monthly_demands = self.monthly_demands() + monthly_demands_path = str(Path(self.charts_path / 'monthly_demands.png')) + self.plot_monthly_energy_demands(demands=monthly_demands, + file_name='monthly_demands') + self.report.add_image('monthly_demands.png', 'Total Monthly Energy Demands in City' ) + self.report.save_report() + self.report.compile_to_pdf() diff --git a/scripts/report_creation.py b/scripts/report_creation.py index cca587f4..e1e88927 100644 --- a/scripts/report_creation.py +++ b/scripts/report_creation.py @@ -1,38 +1,50 @@ import subprocess import datetime +import os +from pathlib import Path + class LatexReport: - def __init__(self, file_name): - self.file_name = file_name - self.content = [] - self.content.append(r'\documentclass{article}') - self.content.append(r'\usepackage[margin=2.5cm]{geometry}') # Adjust page margins - self.content.append(r'\usepackage{graphicx}') - self.content.append(r'\usepackage{tabularx}') - self.content.append(r'\begin{document}') - # Get current date and time - current_datetime = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") - self.content.append(r'\title{Energy System Analysis Report - ' + current_datetime + r'}') - self.content.append(r'\author{Next-Generation Cities Institute}') - self.content.append(r'\date{}') # Remove the date field, as it's included in the title now - self.content.append(r'\maketitle') + def __init__(self, file_name, title, subtitle, output_path): + self.file_name = file_name + self.output_path = Path(output_path) / 'report' + self.output_path.mkdir(parents=True, exist_ok=True) + self.file_path = self.output_path / f"{file_name}.tex" + self.content = [] + self.content.append(r'\documentclass{article}') + self.content.append(r'\usepackage[margin=2.5cm]{geometry}') + self.content.append(r'\usepackage{graphicx}') + self.content.append(r'\usepackage{tabularx}') + self.content.append(r'\begin{document}') + + current_datetime = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") + + self.content.append(r'\title{' + title + '}') + self.content.append(r'\author{Next-Generation Cities Institute}') + self.content.append(r'\date{}') + self.content.append(r'\maketitle') + + self.content.append(r'\begin{center}') + self.content.append(r'\large ' + subtitle + r'\\') + self.content.append(r'\large ' + current_datetime) + self.content.append(r'\end{center}') def add_section(self, section_title): - self.content.append(r'\section{' + section_title + r'}') + self.content.append(r'\section{' + section_title + r'}') def add_subsection(self, subsection_title): - self.content.append(r'\subsection{' + subsection_title + r'}') + self.content.append(r'\subsection{' + subsection_title + r'}') def add_text(self, text): - self.content.append(text) + self.content.append(text) def add_table(self, table_data, caption=None, first_column_width=None): num_columns = len(table_data[0]) - total_width = 0.9 # Default total width + total_width = 0.9 if first_column_width is not None: first_column_width_str = str(first_column_width) + 'cm' - total_width -= first_column_width / 16.0 # Adjust total width for the first column + total_width -= first_column_width / 16.0 if caption: self.content.append(r'\begin{table}[htbp]') @@ -55,19 +67,20 @@ class LatexReport: if caption: self.content.append(r'\begin{figure}[htbp]') self.content.append(r'\centering') - self.content.append(r'\includegraphics[width=0.8\textwidth]{' + image_path + r'}') + self.content.append(r'\includegraphics[width=\textwidth]{' + image_path + r'}') self.content.append(r'\caption{' + caption + r'}') self.content.append(r'\end{figure}') else: self.content.append(r'\begin{figure}[htbp]') self.content.append(r'\centering') - self.content.append(r'\includegraphics[width=0.8\textwidth]{' + image_path + r'}') + self.content.append(r'\includegraphics[width=\textwidth]{' + image_path + r'}') self.content.append(r'\end{figure}') def save_report(self): - self.content.append(r'\end{document}') # Add this line to close the document - with open(self.file_name, 'w') as f: + self.content.append(r'\end{document}') + with open(self.file_path, 'w') as f: f.write('\n'.join(self.content)) def compile_to_pdf(self): - subprocess.run(['pdflatex', self.file_name]) + subprocess.run(['pdflatex', '-output-directory', str(self.output_path), str(self.file_path)]) + -- 2.39.2 From c4f98a30c1f81525eec086669ec45d3c91f114e6 Mon Sep 17 00:00:00 2001 From: s_ranjbar Date: Mon, 15 Jul 2024 08:51:15 -0400 Subject: [PATCH 2/3] fix: current and retrofitted status energy consumption analysis and system schematic added to the report --- .../energy_systems/emission_system.py | 2 +- .../energy_systems/montreal_custom_catalog.py | 2 +- .../montreal_future_system_catalogue.py | 2 +- .../energy_systems/emission_system.py | 2 +- .../energy_systems/schemas/PV+4Pipe+DHW.jpg | Bin 0 -> 79991 bytes ...ontreal_custom_energy_system_parameters.py | 10 +- ...ntreal_future_energy_systems_parameters.py | 10 +- input_files/output_buildings_expanded.geojson | 863 ++++++++++++++++++ report_test.py | 38 +- scripts/energy_system_retrofit_report.py | 535 ++++++++--- scripts/energy_system_retrofit_results.py | 123 ++- scripts/geojson_creator.py | 7 +- scripts/pv_feasibility.py | 34 + scripts/random_assignation.py | 8 +- scripts/report_creation.py | 51 +- .../system_simulation_models/archetype13.py | 18 +- 16 files changed, 1486 insertions(+), 219 deletions(-) create mode 100644 hub/data/energy_systems/schemas/PV+4Pipe+DHW.jpg create mode 100644 input_files/output_buildings_expanded.geojson create mode 100644 scripts/pv_feasibility.py diff --git a/hub/catalog_factories/data_models/energy_systems/emission_system.py b/hub/catalog_factories/data_models/energy_systems/emission_system.py index a8ac91b6..538954d3 100644 --- a/hub/catalog_factories/data_models/energy_systems/emission_system.py +++ b/hub/catalog_factories/data_models/energy_systems/emission_system.py @@ -10,7 +10,7 @@ class EmissionSystem: """ Emission system class """ - def __init__(self, system_id, model_name=None, system_type=None, parasitic_energy_consumption=None): + def __init__(self, system_id, model_name=None, system_type=None, parasitic_energy_consumption=0): self._system_id = system_id self._model_name = model_name diff --git a/hub/catalog_factories/energy_systems/montreal_custom_catalog.py b/hub/catalog_factories/energy_systems/montreal_custom_catalog.py index d3e37e36..cace9278 100644 --- a/hub/catalog_factories/energy_systems/montreal_custom_catalog.py +++ b/hub/catalog_factories/energy_systems/montreal_custom_catalog.py @@ -135,7 +135,7 @@ class MontrealCustomCatalog(Catalog): equipment_id = float(equipment['@id']) equipment_type = equipment['@type'] model_name = equipment['name'] - parasitic_consumption = None + parasitic_consumption = 0 if 'parasitic_consumption' in equipment: parasitic_consumption = float(equipment['parasitic_consumption']['#text']) / 100 diff --git a/hub/catalog_factories/energy_systems/montreal_future_system_catalogue.py b/hub/catalog_factories/energy_systems/montreal_future_system_catalogue.py index 625e362c..a4477ba2 100644 --- a/hub/catalog_factories/energy_systems/montreal_future_system_catalogue.py +++ b/hub/catalog_factories/energy_systems/montreal_future_system_catalogue.py @@ -262,7 +262,7 @@ class MontrealFutureSystemCatalogue(Catalog): system_id = None model_name = None system_type = None - parasitic_energy_consumption = None + parasitic_energy_consumption = 0 emission_system = EmissionSystem(system_id=system_id, model_name=model_name, system_type=system_type, diff --git a/hub/city_model_structure/energy_systems/emission_system.py b/hub/city_model_structure/energy_systems/emission_system.py index 32bf7c17..e8773013 100644 --- a/hub/city_model_structure/energy_systems/emission_system.py +++ b/hub/city_model_structure/energy_systems/emission_system.py @@ -13,7 +13,7 @@ class EmissionSystem: def __init__(self): self._model_name = None self._type = None - self._parasitic_energy_consumption = None + self._parasitic_energy_consumption = 0 @property def model_name(self): diff --git a/hub/data/energy_systems/schemas/PV+4Pipe+DHW.jpg b/hub/data/energy_systems/schemas/PV+4Pipe+DHW.jpg new file mode 100644 index 0000000000000000000000000000000000000000..7daad9875de9fc55791c58b97f36bbcef48aa9a8 GIT binary patch literal 79991 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%0AYW(ILm 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 literal 0 HcmV?d00001 diff --git a/hub/imports/energy_systems/montreal_custom_energy_system_parameters.py b/hub/imports/energy_systems/montreal_custom_energy_system_parameters.py index 15833c9b..eb1b9e92 100644 --- a/hub/imports/energy_systems/montreal_custom_energy_system_parameters.py +++ b/hub/imports/energy_systems/montreal_custom_energy_system_parameters.py @@ -136,10 +136,14 @@ class MontrealCustomEnergySystemParameters: _distribution_system.distribution_consumption_variable_flow = \ archetype_distribution_system.distribution_consumption_variable_flow _distribution_system.heat_losses = archetype_distribution_system.heat_losses - _emission_system = None + _generic_emission_system = None if archetype_distribution_system.emission_systems is not None: - _emission_system = EmissionSystem() - _distribution_system.emission_systems = [_emission_system] + _emission_systems = [] + for emission_system in archetype_distribution_system.emission_systems: + _generic_emission_system = EmissionSystem() + _generic_emission_system.parasitic_energy_consumption = emission_system.parasitic_energy_consumption + _emission_systems.append(_generic_emission_system) + _distribution_system.emission_systems = _emission_systems _distribution_systems.append(_distribution_system) return _distribution_systems diff --git a/hub/imports/energy_systems/montreal_future_energy_systems_parameters.py b/hub/imports/energy_systems/montreal_future_energy_systems_parameters.py index 1bcde834..b5628d9f 100644 --- a/hub/imports/energy_systems/montreal_future_energy_systems_parameters.py +++ b/hub/imports/energy_systems/montreal_future_energy_systems_parameters.py @@ -185,10 +185,14 @@ class MontrealFutureEnergySystemParameters: _distribution_system.distribution_consumption_variable_flow = \ archetype_distribution_system.distribution_consumption_variable_flow _distribution_system.heat_losses = archetype_distribution_system.heat_losses - _emission_system = None + _generic_emission_system = None if archetype_distribution_system.emission_systems is not None: - _emission_system = EmissionSystem() - _distribution_system.emission_systems = [_emission_system] + _emission_systems = [] + for emission_system in archetype_distribution_system.emission_systems: + _generic_emission_system = EmissionSystem() + _generic_emission_system.parasitic_energy_consumption = emission_system.parasitic_energy_consumption + _emission_systems.append(_generic_emission_system) + _distribution_system.emission_systems = _emission_systems _distribution_systems.append(_distribution_system) return _distribution_systems diff --git a/input_files/output_buildings_expanded.geojson b/input_files/output_buildings_expanded.geojson new file mode 100644 index 00000000..43fd4d3f --- /dev/null +++ b/input_files/output_buildings_expanded.geojson @@ -0,0 +1,863 @@ +{ + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + -73.56769087843276, + 45.49251875903776 + ], + [ + -73.56765050367694, + 45.492560280202284 + ], + [ + -73.5677794213865, + 45.49262188364245 + ], + [ + -73.56781916241786, + 45.49258006136105 + ], + [ + -73.56769087843276, + 45.49251875903776 + ] + ] + ] + }, + "id": 173347, + "properties": { + "name": "01044617", + "address": "rue Victor-Hugo (MTL) 1666", + "function": "1000", + "height": 9, + "year_of_construction": 1986 + } + }, + { + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + -73.56765050367694, + 45.492560280202284 + ], + [ + -73.56761436875776, + 45.49259744179384 + ], + [ + -73.5676075694645, + 45.49260454199484 + ], + [ + -73.56773226889548, + 45.49266394156485 + ], + [ + -73.56773726906921, + 45.49266624130272 + ], + [ + -73.5677794213865, + 45.49262188364245 + ], + [ + -73.56765050367694, + 45.492560280202284 + ] + ] + ] + }, + "id": 173348, + "properties": { + "name": "01044619", + "address": "rue Victor-Hugo (MTL) 1670", + "function": "1000", + "height": 9, + "year_of_construction": 1986 + } + }, + { + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + -73.56829026835214, + 45.492524742569145 + ], + [ + -73.56849646900322, + 45.49262354174874 + ], + [ + -73.56861067001111, + 45.492505541343576 + ], + [ + -73.56864076915663, + 45.492519941474434 + ], + [ + -73.56866246900178, + 45.49249754209202 + ], + [ + -73.56867696946317, + 45.49250454136644 + ], + [ + -73.56867726964143, + 45.49250414255471 + ], + [ + -73.56881486931461, + 45.492362042624144 + ], + [ + -73.56881686903772, + 45.492359941181455 + ], + [ + -73.5688004699483, + 45.49235084193039 + ], + [ + -73.56882097012145, + 45.4923320417195 + ], + [ + -73.56879846891101, + 45.49232034109352 + ], + [ + -73.56883736970825, + 45.492284841271946 + ], + [ + -73.56886806888434, + 45.492256240993704 + ], + [ + -73.56885337003277, + 45.49224914198001 + ], + [ + -73.56890226932418, + 45.49219894164121 + ], + [ + -73.56851866897392, + 45.49201434154299 + ], + [ + -73.56837326884313, + 45.492163841620254 + ], + [ + -73.56864696910176, + 45.49229554163243 + ], + [ + -73.5685268682051, + 45.49241904187041 + ], + [ + -73.56825396962694, + 45.49228824183907 + ], + [ + -73.56810906858335, + 45.49243794104013 + ], + [ + -73.56829026835214, + 45.492524742569145 + ] + ] + ] + }, + "id": 173403, + "properties": { + "name": "01044334", + "address": "rue Saint-Jacques (MTL) 1460", + "function": "1000", + "height": 15, + "year_of_construction": 1985 + } + }, + { + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + -73.5683896684674, + 45.491800342137736 + ], + [ + -73.56838616878639, + 45.49180414157881 + ], + [ + -73.56850686988925, + 45.49185994152571 + ], + [ + -73.56851286844197, + 45.4918626410622 + ], + [ + -73.56855549071014, + 45.49181750806087 + ], + [ + -73.56842962331187, + 45.49175738300567 + ], + [ + -73.5683896684674, + 45.491800342137736 + ] + ] + ] + }, + "id": 174898, + "properties": { + "name": "01044590", + "address": "rue Victor-Hugo (MTL) 1600", + "function": "1000", + "height": 9, + "year_of_construction": 1986 + } + }, + { + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + -73.5680637695714, + 45.49212884162544 + ], + [ + -73.56802228176146, + 45.49217205619571 + ], + [ + -73.56815668696326, + 45.49223626189717 + ], + [ + -73.56815766959974, + 45.49223524178655 + ], + [ + -73.56818746886172, + 45.49224944155107 + ], + [ + -73.56822816806918, + 45.49220694186927 + ], + [ + -73.5680637695714, + 45.49212884162544 + ] + ] + ] + }, + "id": 175785, + "properties": { + "name": "01044602", + "address": "rue Victor-Hugo (MTL) 1630", + "function": "1000", + "height": 12, + "year_of_construction": 1986 + } + }, + { + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + -73.56850793693103, + 45.49167318076048 + ], + [ + -73.56846877951091, + 45.4917152818903 + ], + [ + -73.56859506290321, + 45.491775605518725 + ], + [ + -73.56863463503653, + 45.491733702062774 + ], + [ + -73.56850793693103, + 45.49167318076048 + ] + ] + ] + }, + "id": 175910, + "properties": { + "name": "01044586", + "address": "rue Victor-Hugo (MTL) 1590", + "function": "1000", + "height": 9, + "year_of_construction": 1986 + } + }, + { + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + -73.56817543449134, + 45.49201384773851 + ], + [ + -73.56813497596143, + 45.49205532773507 + ], + [ + -73.56826745951075, + 45.492118613912375 + ], + [ + -73.56830763251781, + 45.49207699906335 + ], + [ + -73.56817543449134, + 45.49201384773851 + ] + ] + ] + }, + "id": 176056, + "properties": { + "name": "01044599", + "address": "rue Victor-Hugo (MTL) 1620", + "function": "1000", + "height": 8, + "year_of_construction": 1986 + } + }, + { + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + -73.56772876855176, + 45.49247194194522 + ], + [ + -73.56773406949068, + 45.492474341387755 + ], + [ + -73.56773125185198, + 45.492477239659124 + ], + [ + -73.56785890467093, + 45.492538239964624 + ], + [ + -73.56789966910456, + 45.49249534173201 + ], + [ + -73.56776616865103, + 45.49243264153464 + ], + [ + -73.56772876855176, + 45.49247194194522 + ] + ] + ] + }, + "id": 176261, + "properties": { + "name": "01044613", + "address": "rue Victor-Hugo (MTL) 1656", + "function": "1000", + "height": 10, + "year_of_construction": 1986 + } + }, + { + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + -73.56802228176146, + 45.49217205619571 + ], + [ + -73.56798225825526, + 45.492213743742184 + ], + [ + -73.56811660206223, + 45.49227791893211 + ], + [ + -73.56815668696326, + 45.49223626189717 + ], + [ + -73.56802228176146, + 45.49217205619571 + ] + ] + ] + }, + "id": 176293, + "properties": { + "name": "01044604", + "address": "rue Victor-Hugo (MTL) 1636", + "function": "1000", + "height": 12, + "year_of_construction": 1986 + } + }, + { + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + -73.56790222258577, + 45.49229712328457 + ], + [ + -73.56785996900595, + 45.49234104192853 + ], + [ + -73.56799446861396, + 45.49240484193282 + ], + [ + -73.56803643080562, + 45.49236123475947 + ], + [ + -73.56790222258577, + 45.49229712328457 + ] + ] + ] + }, + "id": 176296, + "properties": { + "name": "01044611", + "address": "rue Victor-Hugo (MTL) 1650", + "function": "1000", + "height": 10, + "year_of_construction": 1986 + } + }, + { + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + -73.56798225825526, + 45.492213743742184 + ], + [ + -73.56794223597048, + 45.4922554321734 + ], + [ + -73.56807651582375, + 45.49231957685336 + ], + [ + -73.56811660206223, + 45.49227791893211 + ], + [ + -73.56798225825526, + 45.492213743742184 + ] + ] + ] + }, + "id": 176298, + "properties": { + "name": "01044607", + "address": "rue Victor-Hugo (MTL) 1640", + "function": "1000", + "height": 12, + "year_of_construction": 1986 + } + }, + { + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + -73.56742736898599, + 45.49184704208998 + ], + [ + -73.56761256873325, + 45.491896142437554 + ], + [ + -73.56766926915839, + 45.4917902412014 + ], + [ + -73.56766956853903, + 45.49179024192391 + ], + [ + -73.56792966911675, + 45.49183254222432 + ], + [ + -73.56793006788594, + 45.491831141828406 + ], + [ + -73.56794526884076, + 45.49174634219527 + ], + [ + -73.56794516904765, + 45.49174634225465 + ], + [ + -73.56753896905731, + 45.491638642248425 + ], + [ + -73.56742736898599, + 45.49184704208998 + ] + ] + ] + }, + "id": 176918, + "properties": { + "name": "01097185", + "address": "rue Victor-Hugo (MTL) 1591", + "function": "1000", + "height": 10, + "year_of_construction": 1987 + } + }, + { + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + -73.56773125185198, + 45.492477239659124 + ], + [ + -73.56769087843276, + 45.49251875903776 + ], + [ + -73.56781916241786, + 45.49258006136105 + ], + [ + -73.56785890467093, + 45.492538239964624 + ], + [ + -73.56773125185198, + 45.492477239659124 + ] + ] + ] + }, + "id": 178164, + "properties": { + "name": "01044615", + "address": "rue Victor-Hugo (MTL) 1660", + "function": "1000", + "height": 9, + "year_of_construction": 1986 + } + }, + { + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + -73.56846877951091, + 45.4917152818903 + ], + [ + -73.56842962331187, + 45.49175738300567 + ], + [ + -73.56855549071014, + 45.49181750806087 + ], + [ + -73.56859506290321, + 45.491775605518725 + ], + [ + -73.56846877951091, + 45.4917152818903 + ] + ] + ] + }, + "id": 179679, + "properties": { + "name": "01044588", + "address": "rue Victor-Hugo (MTL) 1596", + "function": "1000", + "height": 9, + "year_of_construction": 1986 + } + }, + { + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + -73.56825635009473, + 45.49193088860213 + ], + [ + -73.56821589168355, + 45.491972368627906 + ], + [ + -73.5683477837006, + 45.4920353716151 + ], + [ + -73.56838787594006, + 45.49199371809223 + ], + [ + -73.56825635009473, + 45.49193088860213 + ] + ] + ] + }, + "id": 179789, + "properties": { + "name": "01044595", + "address": "rue Victor-Hugo (MTL) 1610", + "function": "1000", + "height": 8, + "year_of_construction": 1986 + } + }, + { + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + -73.56821589168355, + 45.491972368627906 + ], + [ + -73.56817543449134, + 45.49201384773851 + ], + [ + -73.56830763251781, + 45.49207699906335 + ], + [ + -73.5683477837006, + 45.4920353716151 + ], + [ + -73.56821589168355, + 45.491972368627906 + ] + ] + ] + }, + "id": 181310, + "properties": { + "name": "01044597", + "address": "rue Victor-Hugo (MTL) 1616", + "function": "1000", + "height": 8, + "year_of_construction": 1986 + } + }, + { + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + -73.56809506939487, + 45.49209624228538 + ], + [ + -73.56809246893268, + 45.4920988416879 + ], + [ + -73.56821287000538, + 45.49216124158406 + ], + [ + -73.56822186852654, + 45.49216584161625 + ], + [ + -73.56826745951075, + 45.492118613912375 + ], + [ + -73.56813497596143, + 45.49205532773507 + ], + [ + -73.56809506939487, + 45.49209624228538 + ] + ] + ] + }, + "id": 182393, + "properties": { + "name": "01044601", + "address": "rue Victor-Hugo (MTL) 1626", + "function": "1000", + "height": 8, + "year_of_construction": 1986 + } + }, + { + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + -73.56790756893894, + 45.492291541967774 + ], + [ + -73.56790222258577, + 45.49229712328457 + ], + [ + -73.56803643080562, + 45.49236123475947 + ], + [ + -73.56807651582375, + 45.49231957685336 + ], + [ + -73.56794223597048, + 45.4922554321734 + ], + [ + -73.56790756893894, + 45.492291541967774 + ] + ] + ] + }, + "id": 182442, + "properties": { + "name": "01044609", + "address": "rue Victor-Hugo (MTL) 1646", + "function": "1000", + "height": 11, + "year_of_construction": 1986 + } + }, + { + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + -73.56829706912258, + 45.49188914205178 + ], + [ + -73.56825635009473, + 45.49193088860213 + ], + [ + -73.56838787594006, + 45.49199371809223 + ], + [ + -73.56842846901456, + 45.49195154234486 + ], + [ + -73.56829706912258, + 45.49188914205178 + ] + ] + ] + }, + "id": 182546, + "properties": { + "name": "01044592", + "address": "rue Victor-Hugo (MTL) 1606", + "function": "1000", + "height": 8, + "year_of_construction": 1986 + } + } + ] +} \ No newline at end of file diff --git a/report_test.py b/report_test.py index 638b9213..aa67926b 100644 --- a/report_test.py +++ b/report_test.py @@ -12,17 +12,22 @@ from scripts.geojson_creator import process_geojson from scripts import random_assignation from hub.imports.energy_systems_factory import EnergySystemsFactory from scripts.energy_system_sizing import SystemSizing -from scripts.energy_system_retrofit_results import system_results, new_system_results +from scripts.solar_angles import CitySolarAngles +from scripts.pv_sizing_and_simulation import PVSizingSimulation +from scripts.energy_system_retrofit_results import consumption_data from scripts.energy_system_sizing_and_simulation_factory import EnergySystemsSimulationFactory from scripts.costs.cost import Cost from scripts.costs.constants import SKIN_RETROFIT_AND_SYSTEM_RETROFIT_AND_PV, SYSTEM_RETROFIT_AND_PV import hub.helpers.constants as cte from hub.exports.exports_factory import ExportsFactory +from scripts.pv_feasibility import pv_feasibility geojson_file = process_geojson(x=-73.5681295982132, y=45.49218262677643, diff=0.0001) file_path = (Path(__file__).parent / 'input_files' / 'output_buildings.geojson') output_path = (Path(__file__).parent / 'out_files').resolve() -city = GeometryFactory('geojson', +simulation_results_path = (Path(__file__).parent / 'out_files' / 'simulation_results').resolve() +simulation_results_path.mkdir(parents=True, exist_ok=True) +city = GeometryFactory(file_type='geojson', path=file_path, height_field='height', year_of_construction_field='year_of_construction', @@ -35,7 +40,32 @@ 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() +pv_feasibility(-73.5681295982132, 45.49218262677643, 0.0001, selected_buildings=city.buildings) energy_plus_workflow(city) +solar_angles = CitySolarAngles(city.name, + city.latitude, + city.longitude, + tilt_angle=45, + surface_azimuth_angle=180).calculate +random_assignation.call_random(city.buildings, random_assignation.residential_systems_percentage) +EnergySystemsFactory('montreal_custom', city).enrich() +SystemSizing(city.buildings).montreal_custom() +current_status_energy_consumption = consumption_data(city) +random_assignation.call_random(city.buildings, random_assignation.residential_new_systems_percentage) +EnergySystemsFactory('montreal_future', city).enrich() +for building in city.buildings: + if 'PV' in building.energy_systems_archetype_name: + 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() + if building.energy_systems_archetype_name == 'PV+4Pipe+DHW': + EnergySystemsSimulationFactory('archetype13', building=building, output_path=simulation_results_path).enrich() +retrofitted_energy_consumption = consumption_data(city) +(EnergySystemRetrofitReport(city, output_path, 'PV Implementation and System Retrofit', + current_status_energy_consumption, retrofitted_energy_consumption).create_report()) -(EnergySystemRetrofitReport(city, output_path, 'PV Implementation and HVAC Retrofit'). - create_report(current_system=None, new_system=None)) diff --git a/scripts/energy_system_retrofit_report.py b/scripts/energy_system_retrofit_report.py index 6209c5bc..60e82ece 100644 --- a/scripts/energy_system_retrofit_report.py +++ b/scripts/energy_system_retrofit_report.py @@ -9,24 +9,36 @@ import matplotlib as mpl from matplotlib.ticker import MaxNLocator import numpy as np from pathlib import Path +import glob + class EnergySystemRetrofitReport: - def __init__(self, city, output_path, retrofit_scenario): + def __init__(self, city, output_path, retrofit_scenario, current_status_energy_consumption_data, + retrofitted_energy_consumption_data): self.city = city + self.current_status_data = current_status_energy_consumption_data + self.retrofitted_data = retrofitted_energy_consumption_data self.output_path = output_path self.content = [] + self.retrofit_scenario = retrofit_scenario self.report = LatexReport('energy_system_retrofit_report', - 'Energy System Retrofit Report', retrofit_scenario, output_path) + 'Energy System Retrofit Report', self.retrofit_scenario, output_path) + self.system_schemas_path = (Path(__file__).parent.parent / 'hub' / 'data' / 'energy_systems' / 'schemas') self.charts_path = Path(output_path) / 'charts' self.charts_path.mkdir(parents=True, exist_ok=True) + files = glob.glob(f'{self.charts_path}/*') + for file in files: + os.remove(file) def building_energy_info(self): table_data = [ ["Building Name", "Year of Construction", "function", "Yearly Heating Demand (MWh)", "Yearly Cooling Demand (MWh)", "Yearly DHW Demand (MWh)", "Yearly Electricity Demand (MWh)"] ] - intensity_table_data = [["Building Name", "Total Floor Area m2", "Heating Demand Intensity kWh/m2", - "Cooling Demand Intensity kWh/m2", "Electricity Intensity kWh/m2"]] + intensity_table_data = [["Building Name", "Total Floor Area $m^2$", "Heating Demand Intensity kWh/ $m^2$", + "Cooling Demand Intensity kWh/ $m^2$", "Electricity Intensity kWh/ $m^2$"]] + peak_load_data = [["Building Name", "Heating Peak Load (kW)", "Cooling Peak Load (kW)", + "Domestic Hot Water Peak Load (kW)"]] for building in self.city.buildings: total_floor_area = 0 @@ -52,128 +64,68 @@ class EnergySystemRetrofitReport: (building.lighting_electrical_demand[cte.YEAR][0] + building.appliances_electrical_demand[cte.YEAR][0]) / (3.6e6 * total_floor_area), '.2f')) ] + peak_data = [ + building.name, + str(format(building.heating_peak_load[cte.YEAR][0] / 1000, '.2f')), + str(format(building.cooling_peak_load[cte.YEAR][0] / 1000, '.2f')), + str(format( + (building.lighting_electrical_demand[cte.YEAR][0] + building.appliances_electrical_demand[cte.YEAR][0]) / + (3.6e6 * total_floor_area), '.2f')) + ] table_data.append(building_data) intensity_table_data.append(intensity_data) + peak_load_data.append(peak_data) self.report.add_table(table_data, caption='Buildings Energy Consumption Data') self.report.add_table(intensity_table_data, caption='Buildings Energy Use Intensity Data') + self.report.add_table(peak_load_data, caption='Buildings Peak Load Data') - def monthly_demands(self): - heating = [] - cooling = [] - dhw = [] - lighting_appliance = [] - for i in range(12): - heating_demand = 0 - cooling_demand = 0 - dhw_demand = 0 - lighting_appliance_demand = 0 - for building in self.city.buildings: - heating_demand += building.heating_demand[cte.MONTH][i] / 3.6e6 - cooling_demand += building.cooling_demand[cte.MONTH][i] / 3.6e6 - dhw_demand += building.domestic_hot_water_heat_demand[cte.MONTH][i] / 3.6e6 - lighting_appliance_demand += building.lighting_electrical_demand[cte.MONTH][i] / 3.6e6 - heating.append(heating_demand) - cooling.append(cooling_demand) - dhw.append(dhw_demand) - lighting_appliance.append(lighting_appliance_demand) - - monthly_demands = {'heating': heating, - 'cooling': cooling, - 'dhw': dhw, - 'lighting_appliance': lighting_appliance} - return monthly_demands - - def plot_monthly_energy_demands(self, demands, file_name): + def plot_monthly_energy_demands(self, data, file_name, title): # Data preparation months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'] - heating = demands['heating'] - cooling = demands['cooling'] - dhw = demands['dhw'] - electricity = demands['lighting_appliance'] + demands = { + 'Heating': ('heating', '#2196f3'), + 'Cooling': ('cooling', '#ff5a5f'), + 'DHW': ('dhw', '#4caf50'), + 'Electricity': ('lighting_appliance', '#ffc107') + } + + # Helper function for plotting + def plot_bar_chart(ax, demand_type, color, ylabel, title): + values = data[demand_type] + ax.bar(months, values, color=color, width=0.6, zorder=2) + ax.grid(which="major", axis='x', color='#DAD8D7', alpha=0.5, zorder=1) + ax.grid(which="major", axis='y', color='#DAD8D7', alpha=0.5, zorder=1) + ax.set_xlabel('Month', fontsize=12, labelpad=10) + ax.set_ylabel(ylabel, fontsize=14, labelpad=10) + ax.set_title(title, fontsize=14, weight='bold', alpha=.8, pad=40) + ax.xaxis.set_major_locator(MaxNLocator(integer=True)) + ax.yaxis.set_major_locator(MaxNLocator(integer=True)) + ax.set_xticks(np.arange(len(months))) + ax.set_xticklabels(months, rotation=45, ha='right') + ax.bar_label(ax.containers[0], padding=3, color='black', fontsize=12, rotation=90) + ax.spines[['top', 'left', 'bottom']].set_visible(False) + ax.spines['right'].set_linewidth(1.1) + average_value = np.mean(values) + ax.axhline(y=average_value, color='grey', linewidth=2, linestyle='--') + ax.text(len(months) - 1, average_value, f'Average = {average_value:.1f} kWh', ha='right', va='bottom', + color='grey') # Plotting - fig, axs = plt.subplots(2, 2, figsize=(15, 10), dpi=96) - fig.suptitle('Monthly Energy Demands', fontsize=16, weight='bold', alpha=.8) + fig, axs = plt.subplots(4, 1, figsize=(20, 16), dpi=96) + fig.suptitle(title, fontsize=16, weight='bold', alpha=.8) - # Heating bar chart - axs[0, 0].bar(months, heating, color='#2196f3', width=0.6, zorder=2) - axs[0, 0].grid(which="major", axis='x', color='#DAD8D7', alpha=0.5, zorder=1) - axs[0, 0].grid(which="major", axis='y', color='#DAD8D7', alpha=0.5, zorder=1) - axs[0, 0].set_xlabel('Month', fontsize=12, labelpad=10) - axs[0, 0].set_ylabel('Heating Demand (kWh)', fontsize=12, labelpad=10) - axs[0, 0].set_title('Monthly Heating Demands', fontsize=14, weight='bold', alpha=.8) - axs[0, 0].xaxis.set_major_locator(MaxNLocator(integer=True)) - axs[0, 0].yaxis.set_major_locator(MaxNLocator(integer=True)) - axs[0, 0].set_xticks(np.arange(len(months))) - axs[0, 0].set_xticklabels(months, rotation=45, ha='right') - axs[0, 0].bar_label(axs[0, 0].containers[0], padding=3, color='black', fontsize=8) - axs[0, 0].spines[['top', 'left', 'bottom']].set_visible(False) - axs[0, 0].spines['right'].set_linewidth(1.1) - average_heating = np.mean(heating) - axs[0, 0].axhline(y=average_heating, color='grey', linewidth=2, linestyle='--') - axs[0, 0].text(len(months)-1, average_heating, f'Average = {average_heating:.1f} kWh', ha='right', va='bottom', color='grey') - - # Cooling bar chart - axs[0, 1].bar(months, cooling, color='#ff5a5f', width=0.6, zorder=2) - axs[0, 1].grid(which="major", axis='x', color='#DAD8D7', alpha=0.5, zorder=1) - axs[0, 1].grid(which="major", axis='y', color='#DAD8D7', alpha=0.5, zorder=1) - axs[0, 1].set_xlabel('Month', fontsize=12, labelpad=10) - axs[0, 1].set_ylabel('Cooling Demand (kWh)', fontsize=12, labelpad=10) - axs[0, 1].set_title('Monthly Cooling Demands', fontsize=14, weight='bold', alpha=.8) - axs[0, 1].xaxis.set_major_locator(MaxNLocator(integer=True)) - axs[0, 1].yaxis.set_major_locator(MaxNLocator(integer=True)) - axs[0, 1].set_xticks(np.arange(len(months))) - axs[0, 1].set_xticklabels(months, rotation=45, ha='right') - axs[0, 1].bar_label(axs[0, 1].containers[0], padding=3, color='black', fontsize=8) - axs[0, 1].spines[['top', 'left', 'bottom']].set_visible(False) - axs[0, 1].spines['right'].set_linewidth(1.1) - average_cooling = np.mean(cooling) - axs[0, 1].axhline(y=average_cooling, color='grey', linewidth=2, linestyle='--') - axs[0, 1].text(len(months)-1, average_cooling, f'Average = {average_cooling:.1f} kWh', ha='right', va='bottom', color='grey') - - # DHW bar chart - axs[1, 0].bar(months, dhw, color='#4caf50', width=0.6, zorder=2) - axs[1, 0].grid(which="major", axis='x', color='#DAD8D7', alpha=0.5, zorder=1) - axs[1, 0].grid(which="major", axis='y', color='#DAD8D7', alpha=0.5, zorder=1) - axs[1, 0].set_xlabel('Month', fontsize=12, labelpad=10) - axs[1, 0].set_ylabel('DHW Demand (kWh)', fontsize=12, labelpad=10) - axs[1, 0].set_title('Monthly DHW Demands', fontsize=14, weight='bold', alpha=.8) - axs[1, 0].xaxis.set_major_locator(MaxNLocator(integer=True)) - axs[1, 0].yaxis.set_major_locator(MaxNLocator(integer=True)) - axs[1, 0].set_xticks(np.arange(len(months))) - axs[1, 0].set_xticklabels(months, rotation=45, ha='right') - axs[1, 0].bar_label(axs[1, 0].containers[0], padding=3, color='black', fontsize=8) - axs[1, 0].spines[['top', 'left', 'bottom']].set_visible(False) - axs[1, 0].spines['right'].set_linewidth(1.1) - average_dhw = np.mean(dhw) - axs[1, 0].axhline(y=average_dhw, color='grey', linewidth=2, linestyle='--') - axs[1, 0].text(len(months)-1, average_dhw, f'Average = {average_dhw:.1f} kWh', ha='right', va='bottom', color='grey') - - # Electricity bar chart - axs[1, 1].bar(months, electricity, color='#ffc107', width=0.6, zorder=2) - axs[1, 1].grid(which="major", axis='x', color='#DAD8D7', alpha=0.5, zorder=1) - axs[1, 1].grid(which="major", axis='y', color='#DAD8D7', alpha=0.5, zorder=1) - axs[1, 1].set_xlabel('Month', fontsize=12, labelpad=10) - axs[1, 1].set_ylabel('Electricity Demand (kWh)', fontsize=12, labelpad=10) - axs[1, 1].set_title('Monthly Electricity Demands', fontsize=14, weight='bold', alpha=.8) - axs[1, 1].xaxis.set_major_locator(MaxNLocator(integer=True)) - axs[1, 1].yaxis.set_major_locator(MaxNLocator(integer=True)) - axs[1, 1].set_xticks(np.arange(len(months))) - axs[1, 1].set_xticklabels(months, rotation=45, ha='right') - axs[1, 1].bar_label(axs[1, 1].containers[0], padding=3, color='black', fontsize=8) - axs[1, 1].spines[['top', 'left', 'bottom']].set_visible(False) - axs[1, 1].spines['right'].set_linewidth(1.1) - average_electricity = np.mean(electricity) - axs[1, 1].axhline(y=average_electricity, color='grey', linewidth=2, linestyle='--') - axs[1, 1].text(len(months)-1, average_electricity * 0.95, f'Average = {average_electricity:.1f} kWh', ha='right', va='top', color='grey') + plot_bar_chart(axs[0], 'heating', demands['Heating'][1], 'Heating Demand (kWh)', 'Monthly Heating Demand') + plot_bar_chart(axs[1], 'cooling', demands['Cooling'][1], 'Cooling Demand (kWh)', 'Monthly Cooling Demand') + plot_bar_chart(axs[2], 'dhw', demands['DHW'][1], 'DHW Demand (kWh)', 'Monthly DHW Demand') + plot_bar_chart(axs[3], 'lighting_appliance', demands['Electricity'][1], 'Electricity Demand (kWh)', + 'Monthly Electricity Demand') # Set a white background fig.patch.set_facecolor('white') # Adjust the margins around the plot area - plt.subplots_adjust(left=0.05, right=0.95, top=0.9, bottom=0.1, wspace=0.3, hspace=0.5) - + plt.subplots_adjust(left=0.05, right=0.95, top=0.9, bottom=0.1, hspace=0.5) # Save the plot chart_path = self.charts_path / f'{file_name}.png' @@ -182,15 +134,360 @@ class EnergySystemRetrofitReport: return chart_path - def create_report(self, current_system, new_system): - os.chdir(self.charts_path) - self.report.add_section('Current Status') - self.report.add_subsection('City Buildings Characteristics') + def plot_monthly_energy_consumption(self, data, file_name, title): + # Data preparation + months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'] + consumptions = { + 'Heating': ('heating', '#2196f3', 'Heating Consumption (kWh)', 'Monthly Energy Consumption for Heating'), + 'Cooling': ('cooling', '#ff5a5f', 'Cooling Consumption (kWh)', 'Monthly Energy Consumption for Cooling'), + 'DHW': ('dhw', '#4caf50', 'DHW Consumption (kWh)', 'Monthly DHW Consumption') + } + + # Helper function for plotting + def plot_bar_chart(ax, consumption_type, color, ylabel, title): + values = data[consumption_type] + ax.bar(months, values, color=color, width=0.6, zorder=2) + ax.grid(which="major", axis='x', color='#DAD8D7', alpha=0.5, zorder=1) + ax.grid(which="major", axis='y', color='#DAD8D7', alpha=0.5, zorder=1) + ax.set_xlabel('Month', fontsize=12, labelpad=10) + ax.set_ylabel(ylabel, fontsize=14, labelpad=10) + ax.set_title(title, fontsize=14, weight='bold', alpha=.8, pad=40) + ax.xaxis.set_major_locator(MaxNLocator(integer=True)) + ax.yaxis.set_major_locator(MaxNLocator(integer=True)) + ax.set_xticks(np.arange(len(months))) + ax.set_xticklabels(months, rotation=45, ha='right') + ax.bar_label(ax.containers[0], padding=3, color='black', fontsize=12, rotation=90) + ax.spines[['top', 'left', 'bottom']].set_visible(False) + ax.spines['right'].set_linewidth(1.1) + average_value = np.mean(values) + ax.axhline(y=average_value, color='grey', linewidth=2, linestyle='--') + ax.text(len(months) - 1, average_value, f'Average = {average_value:.1f} kWh', ha='right', va='bottom', + color='grey') + + # Plotting + fig, axs = plt.subplots(3, 1, figsize=(20, 15), dpi=96) + fig.suptitle(title, fontsize=16, weight='bold', alpha=.8) + + plot_bar_chart(axs[0], 'heating', consumptions['Heating'][1], consumptions['Heating'][2], + consumptions['Heating'][3]) + plot_bar_chart(axs[1], 'cooling', consumptions['Cooling'][1], consumptions['Cooling'][2], + consumptions['Cooling'][3]) + plot_bar_chart(axs[2], 'dhw', consumptions['DHW'][1], consumptions['DHW'][2], consumptions['DHW'][3]) + + # Set a white background + fig.patch.set_facecolor('white') + + # Adjust the margins around the plot area + plt.subplots_adjust(left=0.05, right=0.95, top=0.9, bottom=0.1, wspace=0.3, hspace=0.5) + + # Save the plot + chart_path = self.charts_path / f'{file_name}.png' + plt.savefig(chart_path, bbox_inches='tight') + plt.close() + + return chart_path + + def fuel_consumption_breakdown(self, file_name, data): + fuel_consumption_breakdown = {} + for building in self.city.buildings: + for key, breakdown in data[f'{building.name}']['energy_consumption_breakdown'].items(): + if key not in fuel_consumption_breakdown: + fuel_consumption_breakdown[key] = {sector: 0 for sector in breakdown} + for sector, value in breakdown.items(): + if sector in fuel_consumption_breakdown[key]: + fuel_consumption_breakdown[key][sector] += value / 3.6e6 + else: + fuel_consumption_breakdown[key][sector] = value / 3.6e6 + + plt.style.use('ggplot') + num_keys = len(fuel_consumption_breakdown) + fig, axs = plt.subplots(1 if num_keys <= 2 else num_keys, min(num_keys, 2), figsize=(12, 5)) + axs = axs if num_keys > 1 else [axs] # Ensure axs is always iterable + + for i, (fuel, breakdown) in enumerate(fuel_consumption_breakdown.items()): + labels = breakdown.keys() + values = breakdown.values() + colors = cm.get_cmap('tab20c', len(labels)) + ax = axs[i] if num_keys > 1 else axs[0] + ax.pie(values, labels=labels, + autopct=lambda pct: f"{pct:.1f}%\n({pct / 100 * sum(values):.2f})", + startangle=90, colors=[colors(j) for j in range(len(labels))]) + ax.set_title(f'{fuel} Consumption Breakdown') + + plt.suptitle('City Energy Consumption Breakdown', fontsize=16, fontweight='bold') + plt.tight_layout(rect=[0, 0, 1, 0.95]) # Adjust layout to fit the suptitle + + chart_path = self.charts_path / f'{file_name}.png' + plt.savefig(chart_path, dpi=300) + plt.close() + return chart_path + + def energy_system_archetype_schematic(self): + energy_system_archetypes = {} + for building in self.city.buildings: + if building.energy_systems_archetype_name not in energy_system_archetypes: + energy_system_archetypes[building.energy_systems_archetype_name] = [building.name] + else: + energy_system_archetypes[building.energy_systems_archetype_name].append(building.name) + + text = "" + items = "" + for archetype, buildings in energy_system_archetypes.items(): + buildings_str = ", ".join(buildings) + text += f"Figure 4 shows the schematic of the proposed energy system for buildings {buildings_str}.\n" + if archetype in ['PV+4Pipe+DHW', 'PV+ASHP+GasBoiler+TES']: + text += "This energy system archetype is formed of the following systems: \par" + items = ['Rooftop Photovoltaic System: The rooftop PV system is tied to the grid and in case there is surplus ' + 'energy, it is sold to Hydro-Quebec through their Net-Meterin program.', + '4-Pipe HVAC System: This systems includes a 4-pipe heat pump capable of generating heat and cooling ' + 'at the same time, a natural gas boiler as the auxiliary heating system, and a hot water storage tank.' + 'The temperature inside the tank is kept between 40-55 C. The cooling demand is totally supplied by ' + 'the heat pump unit.', + 'Domestic Hot Water Heat Pump System: This system is in charge of supplying domestic hot water demand.' + 'The heat pump is connected to a thermal storage tank with electric resistance heating coil inside it.' + ' The temperature inside the tank should always remain above 60 C.'] + + self.report.add_text(text) + self.report.add_itemize(items=items) + schema_path = self.system_schemas_path / f'{archetype}.jpg' + self.report.add_image(str(schema_path).replace('\\', '/'), + f'Proposed energy system for buildings {buildings_str}') + + def plot_monthly_radiation(self): + months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'] + monthly_roof_radiation = [] + for i in range(len(months)): + tilted_radiation = 0 + for building in self.city.buildings: + tilted_radiation += (building.roofs[0].global_irradiance_tilted[cte.MONTH][i] / + (cte.WATTS_HOUR_TO_JULES * 1000)) + monthly_roof_radiation.append(tilted_radiation) + + def plot_bar_chart(ax, months, values, color, ylabel, title): + ax.bar(months, values, color=color, width=0.6, zorder=2) + ax.grid(which="major", axis='x', color='#DAD8D7', alpha=0.5, zorder=1) + ax.grid(which="major", axis='y', color='#DAD8D7', alpha=0.5, zorder=1) + ax.set_xlabel('Month', fontsize=12, labelpad=10) + ax.set_ylabel(ylabel, fontsize=14, labelpad=10) + ax.set_title(title, fontsize=14, weight='bold', alpha=.8, pad=40) + ax.xaxis.set_major_locator(MaxNLocator(integer=True)) + ax.yaxis.set_major_locator(MaxNLocator(integer=True)) + ax.set_xticks(np.arange(len(months))) + ax.set_xticklabels(months, rotation=45, ha='right') + ax.bar_label(ax.containers[0], padding=3, color='black', fontsize=12, rotation=90) + ax.spines[['top', 'left', 'bottom']].set_visible(False) + ax.spines['right'].set_linewidth(1.1) + average_value = np.mean(values) + ax.axhline(y=average_value, color='grey', linewidth=2, linestyle='--') + ax.text(len(months) - 1, average_value, f'Average = {average_value:.1f} kWh', ha='right', va='bottom', + color='grey') + + # Plotting the bar chart + fig, ax = plt.subplots(figsize=(15, 8), dpi=96) + plot_bar_chart(ax, months, monthly_roof_radiation, '#ffc107', 'Tilted Roof Radiation (kWh / m2)', + 'Monthly Tilted Roof Radiation') + + # Set a white background + fig.patch.set_facecolor('white') + + # Adjust the margins around the plot area + plt.subplots_adjust(left=0.1, right=0.95, top=0.9, bottom=0.1) + + # Save the plot + chart_path = self.charts_path / 'monthly_tilted_roof_radiation.png' + plt.savefig(chart_path, bbox_inches='tight') + plt.close() + return chart_path + + def energy_consumption_comparison(self, current_status_data, retrofitted_data, file_name, title): + months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'] + consumptions = { + 'Heating': ('heating', '#2196f3', 'Heating Consumption (kWh)', 'Monthly Energy Consumption for Heating'), + 'Cooling': ('cooling', '#ff5a5f', 'Cooling Consumption (kWh)', 'Monthly Energy Consumption for Cooling'), + 'DHW': ('dhw', '#4caf50', 'DHW Consumption (kWh)', 'Monthly DHW Consumption') + } + + # Helper function for plotting + def plot_double_bar_chart(ax, consumption_type, color, ylabel, title): + current_values = current_status_data[consumption_type] + retrofitted_values = retrofitted_data[consumption_type] + bar_width = 0.35 + index = np.arange(len(months)) + + ax.bar(index, current_values, bar_width, label='Current Status', color=color, alpha=0.7, zorder=2) + ax.bar(index + bar_width, retrofitted_values, bar_width, label='Retrofitted', color=color, hatch='/', zorder=2) + + ax.grid(which="major", axis='x', color='#DAD8D7', alpha=0.5, zorder=1) + ax.grid(which="major", axis='y', color='#DAD8D7', alpha=0.5, zorder=1) + ax.set_xlabel('Month', fontsize=12, labelpad=10) + ax.set_ylabel(ylabel, fontsize=14, labelpad=10) + ax.set_title(title, fontsize=14, weight='bold', alpha=.8, pad=40) + ax.set_xticks(index + bar_width / 2) + ax.set_xticklabels(months, rotation=45, ha='right') + ax.legend() + + # Adding bar labels + ax.bar_label(ax.containers[0], padding=3, color='black', fontsize=12, rotation=90) + ax.bar_label(ax.containers[1], padding=3, color='black', fontsize=12, rotation=90) + + ax.spines[['top', 'left', 'bottom']].set_visible(False) + ax.spines['right'].set_linewidth(1.1) + + # Plotting + fig, axs = plt.subplots(3, 1, figsize=(20, 15), dpi=96) + fig.suptitle(title, fontsize=16, weight='bold', alpha=.8) + + plot_double_bar_chart(axs[0], 'heating', consumptions['Heating'][1], consumptions['Heating'][2], + consumptions['Heating'][3]) + plot_double_bar_chart(axs[1], 'cooling', consumptions['Cooling'][1], consumptions['Cooling'][2], + consumptions['Cooling'][3]) + plot_double_bar_chart(axs[2], 'dhw', consumptions['DHW'][1], consumptions['DHW'][2], consumptions['DHW'][3]) + + # Set a white background + fig.patch.set_facecolor('white') + + # Adjust the margins around the plot area + plt.subplots_adjust(left=0.05, right=0.95, top=0.9, bottom=0.1, wspace=0.3, hspace=0.5) + + # Save the plot + chart_path = self.charts_path / f'{file_name}.png' + plt.savefig(chart_path, bbox_inches='tight') + plt.close() + + return chart_path + + + def yearly_consumption_comparison(self): + current_total_consumption = self.current_status_data['total_consumption'] + retrofitted_total_consumption = self.retrofitted_data['total_consumption'] + + + def pv_system(self): + self.report.add_text('The first step in PV assessments is evaluating the potential of buildings for installing ' + 'rooftop PV system. The benchmark value used for this evaluation is the mean yearly solar ' + 'incident in Montreal. According to Hydro-Quebec, the mean annual incident in Montreal is 1350' + 'kWh/m2. Therefore, any building with rooftop annual global horizontal radiation of less than ' + '1080 kWh/m2 is considered to be infeasible. Table 4 shows the yearly horizontal radiation on ' + 'buildings roofs. \par') + radiation_data = [ + ["Building Name", "Roof Area $m^2$", "Function", "Rooftop Annual Global Horizontal Radiation kWh/ $m^2$"] + ] + pv_feasible_buildings = [] + for building in self.city.buildings: + if building.roofs[0].global_irradiance[cte.YEAR][0] > 1080: + pv_feasible_buildings.append(building.name) + data = [building.name, str(format(building.roofs[0].perimeter_area, '.2f')), building.function, + str(format(building.roofs[0].global_irradiance[cte.YEAR][0] / (cte.WATTS_HOUR_TO_JULES * 1000), '.2f'))] + radiation_data.append(data) + + self.report.add_table(radiation_data, + caption='Buildings Roof Characteristics') + + if len(pv_feasible_buildings) == len(self.city.buildings): + buildings_str = 'all' + else: + buildings_str = ", ".join(pv_feasible_buildings) + self.report.add_text(f"From the table it can be seen that {buildings_str} buildings are good candidates to have " + f"rooftop PV system. The next step is calculating the amount of solar radiation on a tilted " + f"surface. Figure 5 shows the total monthly solar radiation on a surface with the tilt angle " + f"of 45 degrees on the roofs of those buildings that are identified to have rooftop PV system." + f"\par") + tilted_radiation = self.plot_monthly_radiation() + self.report.add_image(str(tilted_radiation).replace('\\', '/'), + caption='Total Monthly Solar Radiation on Buildings Roofs on a 45 Degrees Tilted Surface', + placement='H') + self.report.add_text('The first step in sizing the PV system is to find the available roof area. ' + 'Few considerations need to be made here. The considerations include space for maintenance ' + 'crew, space for mechanical equipment, and orientation correction factor to make sure all ' + 'the panel are truly facing south. After all these considerations, the minimum distance ' + 'between the panels to avoid shading throughout the year is found. Table 5 shows the number of' + 'panles on each buildings roof, yearly PV production, total electricity consumption, and self ' + 'consumption. \par') + + pv_output_table = [['Building Name', 'Total Surface Area of PV Panels ($m^2$)', + 'Total Solar Incident on PV Modules (MWh)', 'Yearly PV production (MWh)']] + + for building in self.city.buildings: + if building.name in pv_feasible_buildings: + pv_data = [] + pv_data.append(building.name) + yearly_solar_incident = (building.roofs[0].global_irradiance_tilted[cte.YEAR][0] * + building.roofs[0].installed_solar_collector_area) / (cte.WATTS_HOUR_TO_JULES * 1e6) + yearly_solar_incident_str = format(yearly_solar_incident, '.2f') + yearly_pv_output = building.onsite_electrical_production[cte.YEAR][0] / (cte.WATTS_HOUR_TO_JULES * 1e6) + yearly_pv_output_str = format(yearly_pv_output, '.2f') + + pv_data.append(format(building.roofs[0].installed_solar_collector_area, '.2f')) + pv_data.append(yearly_solar_incident_str) + pv_data.append(yearly_pv_output_str) + + pv_output_table.append(pv_data) + + self.report.add_table(pv_output_table, caption='PV System Simulation Results', first_column_width=3) + + def create_report(self): + # Add sections and text to the report + self.report.add_section('Overview of the Current Status in Buildings') + self.report.add_text('In this section, an overview of the current status of buildings characteristics, ' + 'energy demand and consumptions are provided') + self.report.add_subsection('Buildings Characteristics') + self.building_energy_info() - monthly_demands = self.monthly_demands() - monthly_demands_path = str(Path(self.charts_path / 'monthly_demands.png')) - self.plot_monthly_energy_demands(demands=monthly_demands, - file_name='monthly_demands') - self.report.add_image('monthly_demands.png', 'Total Monthly Energy Demands in City' ) + + # current monthly demands and consumptions + current_monthly_demands = self.current_status_data['monthly_demands'] + current_monthly_consumptions = self.current_status_data['monthly_consumptions'] + + # Plot and save demand chart + current_demand_chart_path = self.plot_monthly_energy_demands(data=current_monthly_demands, + file_name='current_monthly_demands', + title='Current Status Monthly Energy Demands') + # Plot and save consumption chart + current_consumption_chart_path = self.plot_monthly_energy_consumption(data=current_monthly_consumptions, + file_name='monthly_consumptions', + title='Monthly Energy Consumptions') + current_consumption_breakdown_path = self.fuel_consumption_breakdown('City_Energy_Consumption_Breakdown', + self.current_status_data) + # Add current state of energy demands in the city + self.report.add_subsection('Current State of Energy Demands in the City') + self.report.add_text('The total monthly energy demands in the city are shown in Figure 1. It should be noted ' + 'that the electricity demand refers to total lighting and appliance electricity demands') + self.report.add_image(str(current_demand_chart_path).replace('\\', '/'), + 'Total Monthly Energy Demands in City', + placement='h') + + # Add current state of energy consumption in the city + self.report.add_subsection('Current State of Energy Consumption in the City') + self.report.add_text('The following figure shows the amount of energy consumed to supply heating, cooling, and ' + 'domestic hot water needs in the city. The details of the systems in each building before ' + 'and after retrofit are provided in Section 4. \par') + self.report.add_image(str(current_consumption_chart_path).replace('\\', '/'), + 'Total Monthly Energy Consumptions in City', + placement='H') + self.report.add_text('Figure 3 shows the yearly energy supplied to the city by fuel in different sectors. ' + 'All the values are in kWh.') + self.report.add_image(str(current_consumption_breakdown_path).replace('\\', '/'), + 'Current Energy Consumption Breakdown in the City by Fuel', + placement='H') + self.report.add_section(f'{self.retrofit_scenario}') + self.report.add_subsection('Proposed Systems') + self.energy_system_archetype_schematic() + if 'PV' in self.retrofit_scenario: + self.report.add_subsection('Rooftop Photovoltaic System Implementation') + self.pv_system() + if 'System' in self.retrofit_scenario: + self.report.add_subsection('Retrofitted HVAC and DHW Systems') + self.report.add_text('Figure 6 shows a comparison between total monthly energy consumption in the selected ' + 'buildings before and after retrofitting.') + consumption_comparison = self.energy_consumption_comparison(self.current_status_data['monthly_consumptions'], + self.retrofitted_data['monthly_consumptions'], + 'energy_consumption_comparison_in_city', + 'Total Monthly Energy Consumption Comparison in ' + 'Buildings') + self.report.add_image(str(consumption_comparison).replace('\\', '/'), + caption='Comparison of Total Monthly Energy Consumption in City Buildings', + placement='H') + + # Save and compile the report self.report.save_report() self.report.compile_to_pdf() diff --git a/scripts/energy_system_retrofit_results.py b/scripts/energy_system_retrofit_results.py index 034b9a2e..e1082908 100644 --- a/scripts/energy_system_retrofit_results.py +++ b/scripts/energy_system_retrofit_results.py @@ -1,68 +1,67 @@ import hub.helpers.constants as cte -def system_results(buildings): - system_performance_summary = {} - fields = ["Energy System Archetype", "Heating Equipments", "Cooling Equipments", "DHW Equipments", - "Photovoltaic System Capacity", "Heating Fuel", "Yearly HVAC Energy Consumption (MWh)", - "DHW Energy Consumption (MWH)", "PV Yearly Production (kWh)", "LCC Analysis Duration (Years)", - "Energy System Capital Cost (CAD)", "Energy System Average Yearly Operational Cost (CAD)", - "Energy System Life Cycle Cost (CAD)"] - for building in buildings: - system_performance_summary[f'{building.name}'] = {} - for field in fields: - system_performance_summary[f'{building.name}'][field] = '-' +def consumption_data(city): + current_status_energy_consumption_data = {} + for building in city.buildings: + current_status_energy_consumption_data[f'{building.name}'] = {'heating_consumption': building.heating_consumption, + 'cooling_consumption': building.cooling_consumption, + 'domestic_hot_water_consumption': + building.domestic_hot_water_consumption, + 'energy_consumption_breakdown': + building.energy_consumption_breakdown} + heating_demand_monthly = [] + cooling_demand_monthly = [] + dhw_demand_monthly = [] + lighting_appliance_monthly = [] + heating_consumption_monthly = [] + cooling_consumption_monthly = [] + dhw_consumption_monthly = [] + for i in range(12): + heating_demand = 0 + cooling_demand = 0 + dhw_demand = 0 + lighting_appliance_demand = 0 + heating_consumption = 0 + cooling_consumption = 0 + dhw_consumption = 0 + for building in city.buildings: + heating_demand += building.heating_demand[cte.MONTH][i] / 3.6e6 + cooling_demand += building.cooling_demand[cte.MONTH][i] / 3.6e6 + dhw_demand += building.domestic_hot_water_heat_demand[cte.MONTH][i] / 3.6e6 + lighting_appliance_demand += building.lighting_electrical_demand[cte.MONTH][i] / 3.6e6 + heating_consumption += building.heating_consumption[cte.MONTH][i] / 3.6e6 + if building.cooling_demand[cte.YEAR][0] == 0: + cooling_consumption += building.cooling_demand[cte.MONTH][i] / (3.6e6 * 2) + else: + cooling_consumption += building.cooling_consumption[cte.MONTH][i] / 3.6e6 + dhw_consumption += building.domestic_hot_water_consumption[cte.MONTH][i] / 3.6e6 + heating_demand_monthly.append(heating_demand) + cooling_demand_monthly.append(cooling_demand) + dhw_demand_monthly.append(dhw_demand) + lighting_appliance_monthly.append(lighting_appliance_demand) + heating_consumption_monthly.append(heating_consumption) + cooling_consumption_monthly.append(cooling_consumption) + dhw_consumption_monthly.append(dhw_consumption) - for building in buildings: - fuels = [] - system_performance_summary[f'{building.name}']['Energy System Archetype'] = building.energy_systems_archetype_name - energy_systems = building.energy_systems - for energy_system in energy_systems: - demand_types = energy_system.demand_types - for demand_type in demand_types: - if demand_type == cte.COOLING: - equipments = [] - for generation_system in energy_system.generation_systems: - if generation_system.fuel_type == cte.ELECTRICITY: - equipments.append(generation_system.name or generation_system.system_type) - cooling_equipments = ", ".join(equipments) - system_performance_summary[f'{building.name}']['Cooling Equipments'] = cooling_equipments - elif demand_type == cte.HEATING: - equipments = [] - for generation_system in energy_system.generation_systems: - if generation_system.nominal_heat_output is not None: - equipments.append(generation_system.name or generation_system.system_type) - fuels.append(generation_system.fuel_type) - heating_equipments = ", ".join(equipments) - system_performance_summary[f'{building.name}']['Heating Equipments'] = heating_equipments - elif demand_type == cte.DOMESTIC_HOT_WATER: - equipments = [] - for generation_system in energy_system.generation_systems: - equipments.append(generation_system.name or generation_system.system_type) - dhw_equipments = ", ".join(equipments) - system_performance_summary[f'{building.name}']['DHW Equipments'] = dhw_equipments - for generation_system in energy_system.generation_systems: - if generation_system.system_type == cte.PHOTOVOLTAIC: - system_performance_summary[f'{building.name}'][ - 'Photovoltaic System Capacity'] = generation_system.nominal_electricity_output or str(0) - heating_fuels = ", ".join(fuels) - system_performance_summary[f'{building.name}']['Heating Fuel'] = heating_fuels - system_performance_summary[f'{building.name}']['Yearly HVAC Energy Consumption (MWh)'] = format( - (building.heating_consumption[cte.YEAR][0] + building.cooling_consumption[cte.YEAR][0]) / 3.6e9, '.2f') - system_performance_summary[f'{building.name}']['DHW Energy Consumption (MWH)'] = format( - building.domestic_hot_water_consumption[cte.YEAR][0] / 1e6, '.2f') - return system_performance_summary + monthly_demands = {'heating': heating_demand_monthly, + 'cooling': cooling_demand_monthly, + 'dhw': dhw_demand_monthly, + 'lighting_appliance': lighting_appliance_monthly} + monthly_consumptions = {'heating': heating_consumption_monthly, + 'cooling': cooling_consumption_monthly, + 'dhw': dhw_consumption_monthly} + yearly_heating = 0 + yearly_cooling = 0 + yearly_dhw = 0 + for building in city.buildings: + yearly_heating += building.heating_consumption[cte.YEAR][0] / 3.6e6 + yearly_cooling += building.cooling_consumption[cte.YEAR][0] / 3.6e6 + yearly_dhw += building.domestic_hot_water_consumption[cte.YEAR][0] / 3.6e6 + total_consumption = yearly_heating + yearly_cooling + yearly_dhw + current_status_energy_consumption_data['monthly_demands'] = monthly_demands + current_status_energy_consumption_data['monthly_consumptions'] = monthly_consumptions + current_status_energy_consumption_data['total_consumption'] = total_consumption -def new_system_results(buildings): - new_system_performance_summary = {} - fields = ["Energy System Archetype", "Heating Equipments", "Cooling Equipments", "DHW Equipments", - "Photovoltaic System Capacity", "Heating Fuel", "Yearly HVAC Energy Consumption (MWh)", - "DHW Energy Consumption (MWH)", "PV Yearly Production (kWh)", "LCC Analysis Duration (Years)", - "Energy System Capital Cost (CAD)", "Energy System Average Yearly Operational Cost (CAD)", - "Energy System Life Cycle Cost (CAD)"] - for building in buildings: - new_system_performance_summary[f'{building.name}'] = {} - for field in fields: - new_system_performance_summary[f'{building.name}'][field] = '-' - return new_system_performance_summary + return current_status_energy_consumption_data diff --git a/scripts/geojson_creator.py b/scripts/geojson_creator.py index f3c28b2e..c96c340d 100644 --- a/scripts/geojson_creator.py +++ b/scripts/geojson_creator.py @@ -4,13 +4,16 @@ from shapely import Point from pathlib import Path -def process_geojson(x, y, diff): +def process_geojson(x, y, diff, expansion=False): selection_box = Polygon([[x + diff, y - diff], [x - diff, y - diff], [x - diff, y + diff], [x + diff, y + diff]]) geojson_file = Path('./data/collinear_clean 2.geojson').resolve() - output_file = Path('./input_files/output_buildings.geojson').resolve() + if not expansion: + output_file = Path('./input_files/output_buildings.geojson').resolve() + else: + output_file = Path('./input_files/output_buildings_expanded.geojson').resolve() buildings_in_region = [] with open(geojson_file, 'r') as file: diff --git a/scripts/pv_feasibility.py b/scripts/pv_feasibility.py new file mode 100644 index 00000000..00488e39 --- /dev/null +++ b/scripts/pv_feasibility.py @@ -0,0 +1,34 @@ +from pathlib import Path +import subprocess +from hub.imports.geometry_factory import GeometryFactory +from scripts.geojson_creator import process_geojson +from hub.helpers.dictionaries import Dictionaries +from hub.imports.weather_factory import WeatherFactory +from hub.imports.results_factory import ResultFactory +from hub.exports.exports_factory import ExportsFactory + + +def pv_feasibility(current_x, current_y, current_diff, selected_buildings): + new_diff = current_diff * 5 + geojson_file = process_geojson(x=current_x, y=current_y, diff=new_diff, expansion=True) + file_path = (Path(__file__).parent.parent / 'input_files' / 'output_buildings_expanded.geojson') + output_path = (Path(__file__).parent.parent / 'out_files').resolve() + city = GeometryFactory('geojson', + path=file_path, + height_field='height', + year_of_construction_field='year_of_construction', + function_field='function', + function_to_hub=Dictionaries().montreal_function_to_hub_function).city + WeatherFactory('epw', city).enrich() + 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() + for selected_building in selected_buildings: + for building in city.buildings: + if selected_building.name == building.name: + selected_building.roofs[0].global_irradiance = building.roofs[0].global_irradiance + + + + diff --git a/scripts/random_assignation.py b/scripts/random_assignation.py index 7483cced..ca7678bc 100644 --- a/scripts/random_assignation.py +++ b/scripts/random_assignation.py @@ -15,8 +15,8 @@ from hub.city_model_structure.building import Building energy_systems_format = 'montreal_custom' # parameters: -residential_systems_percentage = {'system 1 gas': 100, - 'system 1 electricity': 0, +residential_systems_percentage = {'system 1 gas': 44, + 'system 1 electricity': 6, 'system 2 gas': 0, 'system 2 electricity': 0, 'system 3 and 4 gas': 0, @@ -25,8 +25,8 @@ residential_systems_percentage = {'system 1 gas': 100, 'system 5 electricity': 0, 'system 6 gas': 0, 'system 6 electricity': 0, - 'system 8 gas': 0, - 'system 8 electricity': 0} + 'system 8 gas': 44, + 'system 8 electricity': 6} residential_new_systems_percentage = {'PV+ASHP+GasBoiler+TES': 0, 'PV+4Pipe+DHW': 100, diff --git a/scripts/report_creation.py b/scripts/report_creation.py index e1e88927..6298d232 100644 --- a/scripts/report_creation.py +++ b/scripts/report_creation.py @@ -15,6 +15,8 @@ class LatexReport: self.content.append(r'\usepackage[margin=2.5cm]{geometry}') self.content.append(r'\usepackage{graphicx}') self.content.append(r'\usepackage{tabularx}') + self.content.append(r'\usepackage{multirow}') + self.content.append(r'\usepackage{float}') self.content.append(r'\begin{document}') current_datetime = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") @@ -35,12 +37,16 @@ class LatexReport: def add_subsection(self, subsection_title): self.content.append(r'\subsection{' + subsection_title + r'}') + def add_subsubsection(self, subsection_title): + self.content.append(r'\subsubsection{' + subsection_title + r'}') + def add_text(self, text): self.content.append(text) - def add_table(self, table_data, caption=None, first_column_width=None): + def add_table(self, table_data, caption=None, first_column_width=None, merge_first_column=False): num_columns = len(table_data[0]) total_width = 0.9 + first_column_width_str = '' if first_column_width is not None: first_column_width_str = str(first_column_width) + 'cm' @@ -51,31 +57,58 @@ class LatexReport: self.content.append(r'\caption{' + caption + r'}') self.content.append(r'\centering') - self.content.append(r'\begin{tabularx}{\textwidth}{|p{' + first_column_width_str + r'}|' + '|'.join(['X'] * ( - num_columns - 1)) + '|}' if first_column_width is not None else r'\begin{tabularx}{\textwidth}{|' + '|'.join( - ['X'] * num_columns) + '|}') + column_format = '|p{' + first_column_width_str + r'}|' + '|'.join( + ['X'] * (num_columns - 1)) + '|' if first_column_width is not None else '|' + '|'.join(['X'] * num_columns) + '|' + self.content.append(r'\begin{tabularx}{\textwidth}{' + column_format + '}') self.content.append(r'\hline') - for row in table_data: - self.content.append(' & '.join(row) + r' \\') + + previous_first_column = None + rowspan_count = 1 + + for i, row in enumerate(table_data): + if merge_first_column and i > 0 and row[0] == previous_first_column: + rowspan_count += 1 + self.content.append(' & '.join(['' if j == 0 else cell for j, cell in enumerate(row)]) + r' \\') + else: + if merge_first_column and i > 0 and rowspan_count > 1: + self.content[-rowspan_count] = self.content[-rowspan_count].replace(r'\multirow{1}', + r'\multirow{' + str(rowspan_count) + '}') + rowspan_count = 1 + if merge_first_column and i < len(table_data) - 1 and row[0] == table_data[i + 1][0]: + self.content.append(r'\multirow{1}{*}{' + row[0] + '}' + ' & ' + ' & '.join(row[1:]) + r' \\') + else: + self.content.append(' & '.join(row) + r' \\') + previous_first_column = row[0] self.content.append(r'\hline') + + if merge_first_column and rowspan_count > 1: + self.content[-rowspan_count] = self.content[-rowspan_count].replace(r'\multirow{1}', + r'\multirow{' + str(rowspan_count) + '}') + self.content.append(r'\end{tabularx}') if caption: self.content.append(r'\end{table}') - def add_image(self, image_path, caption=None): + def add_image(self, image_path, caption=None, placement='ht'): if caption: - self.content.append(r'\begin{figure}[htbp]') + self.content.append(r'\begin{figure}[' + placement + r']') self.content.append(r'\centering') self.content.append(r'\includegraphics[width=\textwidth]{' + image_path + r'}') self.content.append(r'\caption{' + caption + r'}') self.content.append(r'\end{figure}') else: - self.content.append(r'\begin{figure}[htbp]') + self.content.append(r'\begin{figure}[' + placement + r']') self.content.append(r'\centering') self.content.append(r'\includegraphics[width=\textwidth]{' + image_path + r'}') self.content.append(r'\end{figure}') + def add_itemize(self, items): + self.content.append(r'\begin{itemize}') + for item in items: + self.content.append(r'\item ' + item) + self.content.append(r'\end{itemize}') + def save_report(self): self.content.append(r'\end{document}') with open(self.file_path, 'w') as f: diff --git a/scripts/system_simulation_models/archetype13.py b/scripts/system_simulation_models/archetype13.py index 5219af2f..892b9f3f 100644 --- a/scripts/system_simulation_models/archetype13.py +++ b/scripts/system_simulation_models/archetype13.py @@ -17,8 +17,8 @@ class Archetype13: self._heating_peak_load = building.heating_peak_load[cte.YEAR][0] self._cooling_peak_load = building.cooling_peak_load[cte.YEAR][0] self._domestic_hot_water_peak_load = building.domestic_hot_water_peak_load[cte.YEAR][0] - self._hourly_heating_demand = [demand / cte.HOUR_TO_SECONDS for demand in building.heating_demand[cte.HOUR]] - self._hourly_cooling_demand = [demand / cte.HOUR_TO_SECONDS for demand in building.cooling_demand[cte.HOUR]] + self._hourly_heating_demand = [demand / cte.WATTS_HOUR_TO_JULES for demand in building.heating_demand[cte.HOUR]] + self._hourly_cooling_demand = [demand / cte.WATTS_HOUR_TO_JULES for demand in building.cooling_demand[cte.HOUR]] self._hourly_dhw_demand = [demand / cte.WATTS_HOUR_TO_JULES for demand in building.domestic_hot_water_heat_demand[cte.HOUR]] self._output_path = output_path @@ -125,11 +125,11 @@ class Archetype13: m_dis[i + 1] = 0 t_ret[i + 1] = t_tank[i + 1] else: - if demand[i + 1] > 0.5 * self._heating_peak_load / cte.HOUR_TO_SECONDS: + if demand[i + 1] > 0.5 * self._heating_peak_load: factor = 8 else: factor = 4 - m_dis[i + 1] = self._heating_peak_load / (cte.WATER_HEAT_CAPACITY * factor * cte.HOUR_TO_SECONDS) + m_dis[i + 1] = self._heating_peak_load / (cte.WATER_HEAT_CAPACITY * factor) t_ret[i + 1] = t_tank[i + 1] - demand[i + 1] / (m_dis[i + 1] * cte.WATER_HEAT_CAPACITY) tes.temperature = [] hp_electricity_j = [(x * cte.WATTS_HOUR_TO_JULES) / number_of_ts for x in hp_electricity] @@ -191,11 +191,11 @@ class Archetype13: for i in range(1, len(demand)): if demand[i] > 0: - m[i] = self._cooling_peak_load / (cte.WATER_HEAT_CAPACITY * 5 * cte.HOUR_TO_SECONDS) + m[i] = hp.nominal_cooling_output / (cte.WATER_HEAT_CAPACITY * 5) if t_ret[i - 1] >= 13: - if demand[i] < 0.25 * self._cooling_peak_load / cte.HOUR_TO_SECONDS: + if demand[i] < 0.25 * self._cooling_peak_load: q_hp[i] = 0.25 * hp.nominal_cooling_output - elif demand[i] < 0.5 * self._cooling_peak_load / cte.HOUR_TO_SECONDS: + elif demand[i] < 0.5 * self._cooling_peak_load: q_hp[i] = 0.5 * hp.nominal_cooling_output else: q_hp[i] = hp.nominal_cooling_output @@ -210,7 +210,7 @@ class Archetype13: else: m[i] = 0 q_hp[i] = 0 - t_sup_hp[i] = t_ret[i -1] + t_sup_hp[i] = t_ret[i - 1] t_ret[i] = t_ret[i - 1] t_sup_hp_fahrenheit = 1.8 * t_sup_hp[i] + 32 t_out_fahrenheit = 1.8 * t_out[i] + 32 @@ -221,7 +221,7 @@ class Archetype13: eer_curve_coefficients[3] * t_out_fahrenheit + eer_curve_coefficients[4] * t_out_fahrenheit ** 2 + eer_curve_coefficients[5] * t_sup_hp_fahrenheit * t_out_fahrenheit) - hp_electricity[i] = q_hp[i] / hp_eer[i] + hp_electricity[i] = q_hp[i] / cooling_efficiency else: hp_eer[i] = 0 hp_electricity[i] = 0 -- 2.39.2 From f32c74f84a9191115284c6ff2fdb6670c6e17ec4 Mon Sep 17 00:00:00 2001 From: s_ranjbar Date: Thu, 18 Jul 2024 08:47:18 -0400 Subject: [PATCH 3/3] fix: The report is almost completed. It will be continued after fixing all the simulation models --- energy_system_retrofit.py | 94 ++++++++---- hub/data/costs/montreal_costs_completed.xml | 2 +- report_test.py | 71 --------- scripts/costs/total_operational_costs.py | 2 +- scripts/costs/total_operational_incomes.py | 7 +- scripts/energy_system_retrofit_report.py | 120 +++++++++++++-- scripts/energy_system_retrofit_results.py | 139 ++++++++++++++++-- scripts/ep_run_enrich.py | 4 +- scripts/pv_feasibility.py | 13 +- .../system_simulation_models/archetype13.py | 4 +- 10 files changed, 316 insertions(+), 140 deletions(-) delete mode 100644 report_test.py diff --git a/energy_system_retrofit.py b/energy_system_retrofit.py index 2e49b382..b6b8cd31 100644 --- a/energy_system_retrofit.py +++ b/energy_system_retrofit.py @@ -1,4 +1,3 @@ -from scripts.geojson_creator import process_geojson from pathlib import Path import subprocess from scripts.ep_run_enrich import energy_plus_workflow @@ -8,57 +7,92 @@ from hub.imports.construction_factory import ConstructionFactory from hub.imports.usage_factory import UsageFactory from hub.imports.weather_factory import WeatherFactory from hub.imports.results_factory import ResultFactory -from scripts.energy_system_analysis_report import EnergySystemAnalysisReport +from scripts.energy_system_retrofit_report import EnergySystemRetrofitReport +from scripts.geojson_creator import process_geojson from scripts import random_assignation from hub.imports.energy_systems_factory import EnergySystemsFactory from scripts.energy_system_sizing import SystemSizing -from scripts.energy_system_retrofit_results import system_results, new_system_results +from scripts.solar_angles import CitySolarAngles +from scripts.pv_sizing_and_simulation import PVSizingSimulation +from scripts.energy_system_retrofit_results import consumption_data, cost_data from scripts.energy_system_sizing_and_simulation_factory import EnergySystemsSimulationFactory from scripts.costs.cost import Cost -from scripts.costs.constants import SKIN_RETROFIT_AND_SYSTEM_RETROFIT_AND_PV, SYSTEM_RETROFIT_AND_PV +from scripts.costs.constants import SKIN_RETROFIT_AND_SYSTEM_RETROFIT_AND_PV, SYSTEM_RETROFIT_AND_PV, CURRENT_STATUS import hub.helpers.constants as cte from hub.exports.exports_factory import ExportsFactory +from scripts.pv_feasibility import pv_feasibility + # Specify the GeoJSON file path +input_files_path = (Path(__file__).parent / 'input_files') +input_files_path.mkdir(parents=True, exist_ok=True) geojson_file = process_geojson(x=-73.5681295982132, y=45.49218262677643, diff=0.0001) -file_path = (Path(__file__).parent / 'input_files' / 'output_buildings.geojson') -# Specify the output path for the PDF file +geojson_file_path = input_files_path / 'output_buildings.geojson' output_path = (Path(__file__).parent / 'out_files').resolve() -# Create city object from GeoJSON file -city = GeometryFactory('geojson', - path=file_path, +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) +city = GeometryFactory(file_type='geojson', + path=geojson_file_path, height_field='height', year_of_construction_field='year_of_construction', function_field='function', 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() -ExportsFactory('sra', city, output_path).export() -sra_path = (output_path / f'{city.name}_sra.xml').resolve() +ExportsFactory('sra', city, sra_output_path).export() +sra_path = (sra_output_path / f'{city.name}_sra.xml').resolve() subprocess.run(['sra', str(sra_path)]) -ResultFactory('sra', city, output_path).enrich() -energy_plus_workflow(city) +ResultFactory('sra', city, sra_output_path).enrich() +pv_feasibility(-73.5681295982132, 45.49218262677643, 0.0001, selected_buildings=city.buildings) +energy_plus_workflow(city, energy_plus_output_path) +solar_angles = CitySolarAngles(city.name, + city.latitude, + city.longitude, + tilt_angle=45, + surface_azimuth_angle=180).calculate random_assignation.call_random(city.buildings, random_assignation.residential_systems_percentage) EnergySystemsFactory('montreal_custom', city).enrich() SystemSizing(city.buildings).montreal_custom() -current_system = new_system_results(city.buildings) +current_status_energy_consumption = consumption_data(city) +current_status_life_cycle_cost = {} +for building in city.buildings: + cost_retrofit_scenario = CURRENT_STATUS + lcc_dataframe = Cost(building=building, + retrofit_scenario=cost_retrofit_scenario, + fuel_tariffs=['Electricity-D', 'Gas-Energir']).life_cycle + lcc_dataframe.to_csv(cost_analysis_output_path / f'{building.name}_current_status_lcc.csv') + current_status_life_cycle_cost[f'{building.name}'] = cost_data(building, lcc_dataframe, cost_retrofit_scenario) random_assignation.call_random(city.buildings, random_assignation.residential_new_systems_percentage) EnergySystemsFactory('montreal_future', city).enrich() for building in city.buildings: - EnergySystemsSimulationFactory('archetype1', building=building, output_path=output_path).enrich() - print(building.energy_consumption_breakdown[cte.ELECTRICITY][cte.COOLING] + - building.energy_consumption_breakdown[cte.ELECTRICITY][cte.HEATING] + - building.energy_consumption_breakdown[cte.ELECTRICITY][cte.DOMESTIC_HOT_WATER]) -new_system = new_system_results(city.buildings) -# EnergySystemAnalysisReport(city, output_path).create_report(current_system, new_system) + if 'PV' in building.energy_systems_archetype_name: + 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() + if building.energy_systems_archetype_name == 'PV+4Pipe+DHW': + EnergySystemsSimulationFactory('archetype13', building=building, output_path=simulation_results_path).enrich() +retrofitted_energy_consumption = consumption_data(city) +retrofitted_life_cycle_cost = {} for building in city.buildings: - costs = Cost(building=building, retrofit_scenario=SYSTEM_RETROFIT_AND_PV).life_cycle - costs.to_csv(output_path / f'{building.name}_lcc.csv') - (costs.loc['global_operational_costs', f'Scenario {SYSTEM_RETROFIT_AND_PV}']. - to_csv(output_path / f'{building.name}_op.csv')) - costs.loc['global_capital_costs', f'Scenario {SYSTEM_RETROFIT_AND_PV}'].to_csv( - output_path / f'{building.name}_cc.csv') - costs.loc['global_maintenance_costs', f'Scenario {SYSTEM_RETROFIT_AND_PV}'].to_csv( - output_path / f'{building.name}_m.csv') \ No newline at end of file + cost_retrofit_scenario = SYSTEM_RETROFIT_AND_PV + lcc_dataframe = Cost(building=building, + retrofit_scenario=cost_retrofit_scenario, + fuel_tariffs=['Electricity-D', 'Gas-Energir']).life_cycle + lcc_dataframe.to_csv(cost_analysis_output_path / f'{building.name}_retrofitted_lcc.csv') + retrofitted_life_cycle_cost[f'{building.name}'] = cost_data(building, lcc_dataframe, cost_retrofit_scenario) +(EnergySystemRetrofitReport(city, output_path, 'PV Implementation and System Retrofit', + current_status_energy_consumption, retrofitted_energy_consumption, + current_status_life_cycle_cost, retrofitted_life_cycle_cost).create_report()) + diff --git a/hub/data/costs/montreal_costs_completed.xml b/hub/data/costs/montreal_costs_completed.xml index fc23634a..6b3fc41f 100644 --- a/hub/data/costs/montreal_costs_completed.xml +++ b/hub/data/costs/montreal_costs_completed.xml @@ -187,7 +187,7 @@ 1.5 3.6 - 0.07 + 0.075 5 diff --git a/report_test.py b/report_test.py deleted file mode 100644 index aa67926b..00000000 --- a/report_test.py +++ /dev/null @@ -1,71 +0,0 @@ -from pathlib import Path -import subprocess -from scripts.ep_run_enrich import energy_plus_workflow -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 -from hub.imports.results_factory import ResultFactory -from scripts.energy_system_retrofit_report import EnergySystemRetrofitReport -from scripts.geojson_creator import process_geojson -from scripts import random_assignation -from hub.imports.energy_systems_factory import EnergySystemsFactory -from scripts.energy_system_sizing import SystemSizing -from scripts.solar_angles import CitySolarAngles -from scripts.pv_sizing_and_simulation import PVSizingSimulation -from scripts.energy_system_retrofit_results import consumption_data -from scripts.energy_system_sizing_and_simulation_factory import EnergySystemsSimulationFactory -from scripts.costs.cost import Cost -from scripts.costs.constants import SKIN_RETROFIT_AND_SYSTEM_RETROFIT_AND_PV, SYSTEM_RETROFIT_AND_PV -import hub.helpers.constants as cte -from hub.exports.exports_factory import ExportsFactory -from scripts.pv_feasibility import pv_feasibility - -geojson_file = process_geojson(x=-73.5681295982132, y=45.49218262677643, diff=0.0001) -file_path = (Path(__file__).parent / 'input_files' / 'output_buildings.geojson') -output_path = (Path(__file__).parent / 'out_files').resolve() -simulation_results_path = (Path(__file__).parent / 'out_files' / 'simulation_results').resolve() -simulation_results_path.mkdir(parents=True, exist_ok=True) -city = GeometryFactory(file_type='geojson', - path=file_path, - height_field='height', - year_of_construction_field='year_of_construction', - function_field='function', - function_to_hub=Dictionaries().montreal_function_to_hub_function).city -ConstructionFactory('nrcan', city).enrich() -UsageFactory('nrcan', city).enrich() -WeatherFactory('epw', city).enrich() -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() -pv_feasibility(-73.5681295982132, 45.49218262677643, 0.0001, selected_buildings=city.buildings) -energy_plus_workflow(city) -solar_angles = CitySolarAngles(city.name, - city.latitude, - city.longitude, - tilt_angle=45, - surface_azimuth_angle=180).calculate -random_assignation.call_random(city.buildings, random_assignation.residential_systems_percentage) -EnergySystemsFactory('montreal_custom', city).enrich() -SystemSizing(city.buildings).montreal_custom() -current_status_energy_consumption = consumption_data(city) -random_assignation.call_random(city.buildings, random_assignation.residential_new_systems_percentage) -EnergySystemsFactory('montreal_future', city).enrich() -for building in city.buildings: - if 'PV' in building.energy_systems_archetype_name: - 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() - if building.energy_systems_archetype_name == 'PV+4Pipe+DHW': - EnergySystemsSimulationFactory('archetype13', building=building, output_path=simulation_results_path).enrich() -retrofitted_energy_consumption = consumption_data(city) -(EnergySystemRetrofitReport(city, output_path, 'PV Implementation and System Retrofit', - current_status_energy_consumption, retrofitted_energy_consumption).create_report()) - diff --git a/scripts/costs/total_operational_costs.py b/scripts/costs/total_operational_costs.py index dc672fa1..60ed54a9 100644 --- a/scripts/costs/total_operational_costs.py +++ b/scripts/costs/total_operational_costs.py @@ -196,7 +196,7 @@ class TotalOperationalCosts(CostBase): if cooling is not None: hourly += cooling[i] / 3600 if dhw is not None: - dhw += dhw[i] / 3600 + hourly += dhw[i] / 3600 hourly_fuel_consumption.append(hourly) else: heating = None diff --git a/scripts/costs/total_operational_incomes.py b/scripts/costs/total_operational_incomes.py index 2a110761..66d789ed 100644 --- a/scripts/costs/total_operational_incomes.py +++ b/scripts/costs/total_operational_incomes.py @@ -36,11 +36,10 @@ class TotalOperationalIncomes(CostBase): for year in range(1, self._configuration.number_of_years + 1): price_increase_electricity = math.pow(1 + self._configuration.electricity_price_index, year) - # todo: check the adequate assignation of price. Pilar - price_export = archetype.income.electricity_export * cte.WATTS_HOUR_TO_JULES * 1000 # to account for unit change + price_export = archetype.income.electricity_export # to account for unit change self._yearly_operational_incomes.loc[year, 'Incomes electricity'] = ( - onsite_electricity_production * price_export * price_increase_electricity + (onsite_electricity_production / cte.WATTS_HOUR_TO_JULES) * price_export * price_increase_electricity ) self._yearly_operational_incomes.fillna(0, inplace=True) - return self._yearly_operational_incomes + return self._yearly_operational_incomes \ No newline at end of file diff --git a/scripts/energy_system_retrofit_report.py b/scripts/energy_system_retrofit_report.py index 60e82ece..fcfb764f 100644 --- a/scripts/energy_system_retrofit_report.py +++ b/scripts/energy_system_retrofit_report.py @@ -1,11 +1,8 @@ import os import hub.helpers.constants as cte import matplotlib.pyplot as plt -import random -import matplotlib.colors as mcolors from matplotlib import cm from scripts.report_creation import LatexReport -import matplotlib as mpl from matplotlib.ticker import MaxNLocator import numpy as np from pathlib import Path @@ -14,10 +11,12 @@ import glob class EnergySystemRetrofitReport: def __init__(self, city, output_path, retrofit_scenario, current_status_energy_consumption_data, - retrofitted_energy_consumption_data): + retrofitted_energy_consumption_data, current_status_lcc_data, retrofitted_lcc_data): self.city = city self.current_status_data = current_status_energy_consumption_data self.retrofitted_data = retrofitted_energy_consumption_data + self.current_status_lcc = current_status_lcc_data + self.retrofitted_lcc = retrofitted_lcc_data self.output_path = output_path self.content = [] self.retrofit_scenario = retrofit_scenario @@ -334,7 +333,7 @@ class EnergySystemRetrofitReport: ax.spines['right'].set_linewidth(1.1) # Plotting - fig, axs = plt.subplots(3, 1, figsize=(20, 15), dpi=96) + fig, axs = plt.subplots(3, 1, figsize=(20, 25), dpi=96) fig.suptitle(title, fontsize=16, weight='bold', alpha=.8) plot_double_bar_chart(axs[0], 'heating', consumptions['Heating'][1], consumptions['Heating'][2], @@ -356,11 +355,21 @@ class EnergySystemRetrofitReport: return chart_path - def yearly_consumption_comparison(self): - current_total_consumption = self.current_status_data['total_consumption'] - retrofitted_total_consumption = self.retrofitted_data['total_consumption'] - + current_total_consumption = round(self.current_status_data['total_consumption'], 2) + retrofitted_total_consumption = round(self.retrofitted_data['total_consumption'], 2) + text = ( + f'The total yearly energy consumption before and after the retrofit are {current_total_consumption} MWh and ' + f'{retrofitted_total_consumption} MWh, respectively.') + if retrofitted_total_consumption < current_total_consumption: + change = str(round((current_total_consumption - retrofitted_total_consumption) * 100 / current_total_consumption, + 2)) + text += f'Therefore, the total yearly energy consumption decreased by {change} \%.' + else: + change = str(round((retrofitted_total_consumption - current_total_consumption) * 100 / + retrofitted_total_consumption, 2)) + text += f'Therefore, the total yearly energy consumption increased by {change} \%. \par' + self.report.add_text(text) def pv_system(self): self.report.add_text('The first step in PV assessments is evaluating the potential of buildings for installing ' @@ -425,6 +434,85 @@ class EnergySystemRetrofitReport: self.report.add_table(pv_output_table, caption='PV System Simulation Results', first_column_width=3) + def life_cycle_cost_stacked_bar(self, file_name, title): + # Aggregate LCC components for current and retrofitted statuses + current_status_capex = 0 + current_status_opex = 0 + current_status_maintenance = 0 + current_status_end_of_life = 0 + retrofitted_capex = 0 + retrofitted_opex = 0 + retrofitted_maintenance = 0 + retrofitted_end_of_life = 0 + + for building in self.city.buildings: + current_status_capex += self.current_status_lcc[f'{building.name}']['capital_cost_per_sqm'] + retrofitted_capex += self.retrofitted_lcc[f'{building.name}']['capital_cost_per_sqm'] + current_status_opex += self.current_status_lcc[f'{building.name}']['operational_cost_per_sqm'] + retrofitted_opex += self.retrofitted_lcc[f'{building.name}']['operational_cost_per_sqm'] + current_status_maintenance += self.current_status_lcc[f'{building.name}']['maintenance_cost_per_sqm'] + retrofitted_maintenance += self.retrofitted_lcc[f'{building.name}']['maintenance_cost_per_sqm'] + current_status_end_of_life += self.current_status_lcc[f'{building.name}']['end_of_life_cost_per_sqm'] + retrofitted_end_of_life += self.retrofitted_lcc[f'{building.name}']['end_of_life_cost_per_sqm'] + + current_status_lcc_components_sqm = { + 'Capital Cost': current_status_capex / len(self.city.buildings), + 'Operational Cost': current_status_opex / len(self.city.buildings), + 'Maintenance Cost': current_status_maintenance / len(self.city.buildings), + 'End of Life Cost': current_status_end_of_life / len(self.city.buildings) + } + retrofitted_lcc_components_sqm = { + 'Capital Cost': retrofitted_capex / len(self.city.buildings), + 'Operational Cost': retrofitted_opex / len(self.city.buildings), + 'Maintenance Cost': retrofitted_maintenance / len(self.city.buildings), + 'End of Life Cost': retrofitted_end_of_life / len(self.city.buildings) + } + + labels = ['Current Status', 'Retrofitted Status'] + categories = ['Capital Cost', 'Operational Cost', 'Maintenance Cost', 'End of Life Cost'] + current_values = list(current_status_lcc_components_sqm.values()) + retrofitted_values = list(retrofitted_lcc_components_sqm.values()) + colors = ['#2196f3', '#ff5a5f', '#4caf50', '#ffc107'] + + # Data preparation + bar_width = 0.35 + r = np.arange(len(labels)) + + fig, ax = plt.subplots(figsize=(12, 8), dpi=96) + fig.suptitle(title, fontsize=16, weight='bold', alpha=.8) + + # Plotting current status data + bottom = np.zeros(2) + for i, (category, color) in enumerate(zip(categories, colors)): + values = [current_status_lcc_components_sqm[category], retrofitted_lcc_components_sqm[category]] + ax.bar(r, values, bottom=bottom, color=color, edgecolor='white', width=bar_width, label=category) + bottom += values + + # Adding summation annotations at the top of the bars + for idx, (x, total) in enumerate(zip(r, bottom)): + ax.text(x, total, f'{total:.1f}', ha='center', va='bottom', fontsize=12, fontweight='bold') + + # Adding labels, title, and grid + ax.set_xlabel('LCC Components', fontsize=12, labelpad=10) + ax.set_ylabel('Average Cost (CAD/m²)', fontsize=14, labelpad=10) + ax.grid(which="major", axis='y', color='#DAD8D7', alpha=0.5, zorder=1) + ax.set_xticks(r) + ax.set_xticklabels(labels, rotation=45, ha='right') + ax.legend() + + # Adding a white background + fig.patch.set_facecolor('white') + + # Adjusting the margins around the plot area + plt.subplots_adjust(left=0.05, right=0.95, top=0.9, bottom=0.2) + + # Save the plot + chart_path = self.charts_path / f'{file_name}.png' + plt.savefig(chart_path, bbox_inches='tight') + plt.close() + + return chart_path + def create_report(self): # Add sections and text to the report self.report.add_section('Overview of the Current Status in Buildings') @@ -448,6 +536,11 @@ class EnergySystemRetrofitReport: title='Monthly Energy Consumptions') current_consumption_breakdown_path = self.fuel_consumption_breakdown('City_Energy_Consumption_Breakdown', self.current_status_data) + retrofitted_consumption_breakdown_path = self.fuel_consumption_breakdown( + 'fuel_consumption_breakdown_after_retrofit', + self.retrofitted_data) + life_cycle_cost_sqm_stacked_bar_chart_path = self.life_cycle_cost_stacked_bar('lcc_per_sqm', + 'LCC Analysis') # Add current state of energy demands in the city self.report.add_subsection('Current State of Energy Demands in the City') self.report.add_text('The total monthly energy demands in the city are shown in Figure 1. It should be noted ' @@ -487,6 +580,15 @@ class EnergySystemRetrofitReport: self.report.add_image(str(consumption_comparison).replace('\\', '/'), caption='Comparison of Total Monthly Energy Consumption in City Buildings', placement='H') + self.yearly_consumption_comparison() + self.report.add_text('Figure 7 shows the fuel consumption breakdown in the area after the retrofit.') + self.report.add_image(str(retrofitted_consumption_breakdown_path).replace('\\', '/'), + caption=f'Fuel Consumption Breakdown After {self.retrofit_scenario}', + placement='H') + self.report.add_subsection('Life Cycle Cost Analysis') + self.report.add_image(str(life_cycle_cost_sqm_stacked_bar_chart_path).replace('\\', '/'), + caption='Average Life Cycle Cost Components', + placement='H') # Save and compile the report self.report.save_report() diff --git a/scripts/energy_system_retrofit_results.py b/scripts/energy_system_retrofit_results.py index e1082908..9d85d0d9 100644 --- a/scripts/energy_system_retrofit_results.py +++ b/scripts/energy_system_retrofit_results.py @@ -1,15 +1,88 @@ import hub.helpers.constants as cte +def hourly_electricity_consumption_profile(building): + hourly_electricity_consumption = [] + energy_systems = building.energy_systems + appliance = building.appliances_electrical_demand[cte.HOUR] + lighting = building.lighting_electrical_demand[cte.HOUR] + elec_heating = 0 + elec_cooling = 0 + elec_dhw = 0 + if cte.HEATING in building.energy_consumption_breakdown[cte.ELECTRICITY]: + elec_heating = 1 + if cte.COOLING in building.energy_consumption_breakdown[cte.ELECTRICITY]: + elec_cooling = 1 + if cte.DOMESTIC_HOT_WATER in building.energy_consumption_breakdown[cte.ELECTRICITY]: + elec_dhw = 1 + heating = None + cooling = None + dhw = None + if elec_heating == 1: + for energy_system in energy_systems: + if cte.HEATING in energy_system.demand_types: + for generation_system in energy_system.generation_systems: + if generation_system.fuel_type == cte.ELECTRICITY: + if cte.HEATING in generation_system.energy_consumption: + heating = generation_system.energy_consumption[cte.HEATING][cte.HOUR] + else: + if len(energy_system.generation_systems) > 1: + heating = [x / 2 for x in building.heating_consumption[cte.HOUR]] + else: + heating = building.heating_consumption[cte.HOUR] + if elec_dhw == 1: + for energy_system in energy_systems: + if cte.DOMESTIC_HOT_WATER in energy_system.demand_types: + for generation_system in energy_system.generation_systems: + if generation_system.fuel_type == cte.ELECTRICITY: + if cte.DOMESTIC_HOT_WATER in generation_system.energy_consumption: + dhw = generation_system.energy_consumption[cte.DOMESTIC_HOT_WATER][cte.HOUR] + else: + if len(energy_system.generation_systems) > 1: + dhw = [x / 2 for x in building.domestic_hot_water_consumption[cte.HOUR]] + else: + dhw = building.domestic_hot_water_consumption[cte.HOUR] + + if elec_cooling == 1: + for energy_system in energy_systems: + if cte.COOLING in energy_system.demand_types: + for generation_system in energy_system.generation_systems: + if cte.COOLING in generation_system.energy_consumption: + cooling = generation_system.energy_consumption[cte.COOLING][cte.HOUR] + else: + if len(energy_system.generation_systems) > 1: + cooling = [x / 2 for x in building.cooling_consumption[cte.HOUR]] + else: + cooling = building.cooling_consumption[cte.HOUR] + + for i in range(len(building.heating_demand[cte.HOUR])): + hourly = 0 + hourly += appliance[i] / 3600 + hourly += lighting[i] / 3600 + if heating is not None: + hourly += heating[i] / 3600 + if cooling is not None: + hourly += cooling[i] / 3600 + if dhw is not None: + hourly += dhw[i] / 3600 + hourly_electricity_consumption.append(hourly) + return hourly_electricity_consumption + + def consumption_data(city): - current_status_energy_consumption_data = {} + energy_consumption_data = {} for building in city.buildings: - current_status_energy_consumption_data[f'{building.name}'] = {'heating_consumption': building.heating_consumption, - 'cooling_consumption': building.cooling_consumption, - 'domestic_hot_water_consumption': - building.domestic_hot_water_consumption, - 'energy_consumption_breakdown': - building.energy_consumption_breakdown} + hourly_electricity_consumption = hourly_electricity_consumption_profile(building) + energy_consumption_data[f'{building.name}'] = {'heating_consumption': building.heating_consumption, + 'cooling_consumption': building.cooling_consumption, + 'domestic_hot_water_consumption': + building.domestic_hot_water_consumption, + 'energy_consumption_breakdown': + building.energy_consumption_breakdown, + 'hourly_electricity_consumption': hourly_electricity_consumption} + peak_electricity_consumption = 0 + for building in energy_consumption_data: + peak_electricity_consumption += max(energy_consumption_data[building]['hourly_electricity_consumption']) heating_demand_monthly = [] cooling_demand_monthly = [] dhw_demand_monthly = [] @@ -54,14 +127,50 @@ def consumption_data(city): yearly_heating = 0 yearly_cooling = 0 yearly_dhw = 0 + yearly_appliance = 0 + yearly_lighting = 0 for building in city.buildings: - yearly_heating += building.heating_consumption[cte.YEAR][0] / 3.6e6 - yearly_cooling += building.cooling_consumption[cte.YEAR][0] / 3.6e6 - yearly_dhw += building.domestic_hot_water_consumption[cte.YEAR][0] / 3.6e6 + yearly_appliance += building.appliances_electrical_demand[cte.YEAR][0] / 3.6e9 + yearly_lighting += building.lighting_electrical_demand[cte.YEAR][0] / 3.6e9 + yearly_heating += building.heating_consumption[cte.YEAR][0] / 3.6e9 + yearly_cooling += building.cooling_consumption[cte.YEAR][0] / 3.6e9 + yearly_dhw += building.domestic_hot_water_consumption[cte.YEAR][0] / 3.6e9 - total_consumption = yearly_heating + yearly_cooling + yearly_dhw - current_status_energy_consumption_data['monthly_demands'] = monthly_demands - current_status_energy_consumption_data['monthly_consumptions'] = monthly_consumptions - current_status_energy_consumption_data['total_consumption'] = total_consumption + total_consumption = yearly_heating + yearly_cooling + yearly_dhw + yearly_appliance + yearly_lighting + energy_consumption_data['monthly_demands'] = monthly_demands + energy_consumption_data['monthly_consumptions'] = monthly_consumptions + energy_consumption_data['total_consumption'] = total_consumption + energy_consumption_data['maximum_hourly_electricity_consumption'] = peak_electricity_consumption - return current_status_energy_consumption_data + return energy_consumption_data + + +def cost_data(building, lcc_dataframe, cost_retrofit_scenario): + total_floor_area = 0 + for thermal_zone in building.thermal_zones_from_internal_zones: + total_floor_area += thermal_zone.total_floor_area + capital_cost = lcc_dataframe.loc['total_capital_costs_systems', f'Scenario {cost_retrofit_scenario}'] + operational_cost = lcc_dataframe.loc['total_operational_costs', f'Scenario {cost_retrofit_scenario}'] + maintenance_cost = lcc_dataframe.loc['total_maintenance_costs', f'Scenario {cost_retrofit_scenario}'] + end_of_life_cost = lcc_dataframe.loc['end_of_life_costs', f'Scenario {cost_retrofit_scenario}'] + operational_income = lcc_dataframe.loc['operational_incomes', f'Scenario {cost_retrofit_scenario}'] + total_life_cycle_cost = capital_cost + operational_cost + maintenance_cost + end_of_life_cost + operational_income + specific_capital_cost = capital_cost / total_floor_area + specific_operational_cost = operational_cost / total_floor_area + specific_maintenance_cost = maintenance_cost / total_floor_area + specific_end_of_life_cost = end_of_life_cost / total_floor_area + specific_operational_income = operational_income / total_floor_area + specific_life_cycle_cost = total_life_cycle_cost / total_floor_area + life_cycle_cost_analysis = {'capital_cost': capital_cost, + 'capital_cost_per_sqm': specific_capital_cost, + 'operational_cost': operational_cost, + 'operational_cost_per_sqm': specific_operational_cost, + 'maintenance_cost': maintenance_cost, + 'maintenance_cost_per_sqm': specific_maintenance_cost, + 'end_of_life_cost': end_of_life_cost, + 'end_of_life_cost_per_sqm': specific_end_of_life_cost, + 'operational_income': operational_income, + 'operational_income_per_sqm': specific_operational_income, + 'total_life_cycle_cost': total_life_cycle_cost, + 'total_life_cycle_cost_per_sqm': specific_life_cycle_cost} + return life_cycle_cost_analysis diff --git a/scripts/ep_run_enrich.py b/scripts/ep_run_enrich.py index 24ee4b11..68c24c8c 100644 --- a/scripts/ep_run_enrich.py +++ b/scripts/ep_run_enrich.py @@ -9,10 +9,10 @@ from hub.imports.results_factory import ResultFactory sys.path.append('./') -def energy_plus_workflow(city): +def energy_plus_workflow(city, output_path): try: # city = city - out_path = (Path(__file__).parent.parent / 'out_files') + out_path = output_path files = glob.glob(f'{out_path}/*') # for file in files: diff --git a/scripts/pv_feasibility.py b/scripts/pv_feasibility.py index 00488e39..034a5efb 100644 --- a/scripts/pv_feasibility.py +++ b/scripts/pv_feasibility.py @@ -9,10 +9,13 @@ from hub.exports.exports_factory import ExportsFactory def pv_feasibility(current_x, current_y, current_diff, selected_buildings): + input_files_path = (Path(__file__).parent.parent / 'input_files') + output_path = (Path(__file__).parent.parent / 'out_files').resolve() + sra_output_path = output_path / 'sra_outputs' / 'extended_city_sra_outputs' + sra_output_path.mkdir(parents=True, exist_ok=True) new_diff = current_diff * 5 geojson_file = process_geojson(x=current_x, y=current_y, diff=new_diff, expansion=True) - file_path = (Path(__file__).parent.parent / 'input_files' / 'output_buildings_expanded.geojson') - output_path = (Path(__file__).parent.parent / 'out_files').resolve() + file_path = input_files_path / 'output_buildings.geojson' city = GeometryFactory('geojson', path=file_path, height_field='height', @@ -20,10 +23,10 @@ def pv_feasibility(current_x, current_y, current_diff, selected_buildings): function_field='function', function_to_hub=Dictionaries().montreal_function_to_hub_function).city WeatherFactory('epw', city).enrich() - ExportsFactory('sra', city, output_path).export() - sra_path = (output_path / f'{city.name}_sra.xml').resolve() + ExportsFactory('sra', city, sra_output_path).export() + sra_path = (sra_output_path / f'{city.name}_sra.xml').resolve() subprocess.run(['sra', str(sra_path)]) - ResultFactory('sra', city, output_path).enrich() + ResultFactory('sra', city, sra_output_path).enrich() for selected_building in selected_buildings: for building in city.buildings: if selected_building.name == building.name: diff --git a/scripts/system_simulation_models/archetype13.py b/scripts/system_simulation_models/archetype13.py index 892b9f3f..77b52da6 100644 --- a/scripts/system_simulation_models/archetype13.py +++ b/scripts/system_simulation_models/archetype13.py @@ -377,8 +377,8 @@ class Archetype13: self._building.domestic_hot_water_consumption[cte.HOUR] = dhw_consumption self._building.domestic_hot_water_consumption[cte.MONTH] = ( MonthlyValues.get_total_month(self._building.domestic_hot_water_consumption[cte.HOUR])) - self._building.domestic_hot_water_consumption[cte.YEAR] = ( - sum(self._building.domestic_hot_water_consumption[cte.MONTH])) + self._building.domestic_hot_water_consumption[cte.YEAR] = [ + sum(self._building.domestic_hot_water_consumption[cte.MONTH])] file_name = f'energy_system_simulation_results_{self._name}.csv' with open(self._output_path / file_name, 'w', newline='') as csvfile: output_file = csv.writer(csvfile) -- 2.39.2