import subprocess import hub.helpers.constants as cte import matplotlib.pyplot as plt import random import matplotlib.colors as mcolors from matplotlib import cm class EnergySystemAnalysisReport: def __init__(self, file_name, city): self.city = city 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}') self.content.append(r'\title{' + 'Energy System Analysis Report' + r'}') self.content.append(r'\author{Next-Generation Cities Institute}') self.content.append(r'\date{\today}') self.content.append(r'\maketitle') def add_section(self, section_title): self.content.append(r'\section{' + section_title + r'}') def add_subsection(self, subsection_title): self.content.append(r'\subsection{' + subsection_title + r'}') def add_text(self, 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 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 if caption: self.content.append(r'\begin{table}[htbp]') 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) + '|}') self.content.append(r'\hline') for row in table_data: self.content.append(' & '.join(row) + r' \\') self.content.append(r'\hline') self.content.append(r'\end{tabularx}') if caption: self.content.append(r'\end{table}') def add_image(self, image_path, caption=None): 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'\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'\end{figure}') 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] / 1e6, '.2f')), str(format(building.cooling_demand[cte.YEAR][0] / 1e6, '.2f')), str(format(building.domestic_hot_water_heat_demand[cte.YEAR][0] / 1e6, '.2f')), str(format((building.lighting_electrical_demand[cte.YEAR][0] + building.appliances_electrical_demand[cte.YEAR][0]) / 1e6, '.2f')), ] intensity_data = [ building.name, str(format(total_floor_area, '.2f')), str(format(building.heating_demand[cte.YEAR][0] / (1e3 * total_floor_area), '.2f')), str(format(building.cooling_demand[cte.YEAR][0] / (1e3 * total_floor_area), '.2f')), str(format( (building.lighting_electrical_demand[cte.YEAR][0] + building.appliances_electrical_demand[cte.YEAR][0]) / (1e3 * total_floor_area), '.2f')) ] table_data.append(building_data) intensity_table_data.append(intensity_data) self.add_table(table_data, caption='City Buildings Energy Demands') self.add_table(intensity_table_data, caption='Energy Intensity Information') def base_case_charts(self): def create_hvac_demand_chart(building_names, yearly_heating_demand, yearly_cooling_demand): fig, ax = plt.subplots() bar_width = 0.35 index = range(len(building_names)) bars1 = ax.bar(index, yearly_heating_demand, bar_width, label='Yearly Heating Demand (MWh)') bars2 = ax.bar([i + bar_width for i in index], yearly_cooling_demand, bar_width, label='Yearly Cooling Demand (MWh)') ax.set_xlabel('Building Name') ax.set_ylabel('Energy Demand (MWh)') ax.set_title('Yearly HVAC Demands') ax.set_xticks([i + bar_width / 2 for i in index]) ax.set_xticklabels(building_names, rotation=45, ha='right') ax.legend() autolabel(bars1, ax) autolabel(bars2, ax) fig.tight_layout() plt.savefig('hvac_demand_chart.jpg') plt.close() def create_bar_chart(title, ylabel, data, filename, bar_color=None): fig, ax = plt.subplots() bar_width = 0.35 index = range(len(building_names)) if bar_color is None: # Generate a random color bar_color = random.choice(list(mcolors.CSS4_COLORS.values())) bars = ax.bar(index, data, bar_width, label=ylabel, color=bar_color) ax.set_xlabel('Building Name') ax.set_ylabel('Energy Demand (MWh)') ax.set_title(title) ax.set_xticks([i + bar_width / 2 for i in index]) ax.set_xticklabels(building_names, rotation=45, ha='right') ax.legend() autolabel(bars, ax) fig.tight_layout() plt.savefig(filename) plt.close() def autolabel(bars, ax): for bar in bars: height = bar.get_height() ax.annotate('{:.1f}'.format(height), xy=(bar.get_x() + bar.get_width() / 2, height), xytext=(0, 3), # 3 points vertical offset textcoords="offset points", ha='center', va='bottom') building_names = [building.name for building in self.city.buildings] yearly_heating_demand = [building.heating_demand[cte.YEAR][0] / 1e6 for building in self.city.buildings] yearly_cooling_demand = [building.cooling_demand[cte.YEAR][0] / 1e6 for building in self.city.buildings] yearly_dhw_demand = [building.domestic_hot_water_heat_demand[cte.YEAR][0] / 1e6 for building in self.city.buildings] yearly_electricity_demand = [(building.lighting_electrical_demand[cte.YEAR][0] + building.appliances_electrical_demand[cte.YEAR][0]) / 1e6 for building in self.city.buildings] create_hvac_demand_chart(building_names, yearly_heating_demand, yearly_cooling_demand) create_bar_chart('Yearly DHW Demands', 'Energy Demand (MWh)', yearly_dhw_demand, 'dhw_demand_chart.jpg', ) create_bar_chart('Yearly Electricity Demands', 'Energy Demand (MWh)', yearly_electricity_demand, 'electricity_demand_chart.jpg') def maximum_monthly_hvac_chart(self): months = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'] for building in self.city.buildings: maximum_monthly_heating_load = [] maximum_monthly_cooling_load = [] fig, axs = plt.subplots(1, 2, figsize=(12, 6)) # Create a figure with 2 subplots side by side for demand in building.heating_peak_load[cte.MONTH]: maximum_monthly_heating_load.append(demand / 3.6e6) for demand in building.cooling_peak_load[cte.MONTH]: maximum_monthly_cooling_load.append(demand / 3.6e6) # Plot maximum monthly heating load axs[0].bar(months, maximum_monthly_heating_load, color='red') # Plot on the first subplot axs[0].set_title('Maximum Monthly Heating Load') axs[0].set_xlabel('Month') axs[0].set_ylabel('Load (kW)') axs[0].tick_params(axis='x', rotation=45) # Plot maximum monthly cooling load axs[1].bar(months, maximum_monthly_cooling_load, color='blue') # Plot on the second subplot axs[1].set_title('Maximum Monthly Cooling Load') axs[1].set_xlabel('Month') axs[1].set_ylabel('Load (kW)') axs[1].tick_params(axis='x', rotation=45) plt.tight_layout() # Adjust layout to prevent overlapping plt.savefig(f'{building.name}_monthly_maximum_hvac_loads.jpg') plt.close() def load_duration_curves(self): for building in self.city.buildings: heating_demand = [demand / 1000 for demand in building.heating_demand[cte.HOUR]] cooling_demand = [demand / 1000 for demand in building.cooling_demand[cte.HOUR]] heating_demand_sorted = sorted(heating_demand, reverse=True) cooling_demand_sorted = sorted(cooling_demand, reverse=True) plt.style.use('seaborn-darkgrid') # Create figure and axis objects with 1 row and 2 columns fig, axs = plt.subplots(1, 2, figsize=(12, 6)) # Plot sorted heating demand axs[0].plot(heating_demand_sorted, color='red', linewidth=2, label='Heating Demand') axs[0].set_xlabel('Hour', fontsize=14) axs[0].set_ylabel('Heating Demand', fontsize=14) axs[0].set_title('Heating Load Duration Curve', fontsize=16) axs[0].grid(True) axs[0].legend(loc='upper right', fontsize=12) # Plot sorted cooling demand axs[1].plot(cooling_demand_sorted, color='blue', linewidth=2, label='Cooling Demand') axs[1].set_xlabel('Hour', fontsize=14) axs[1].set_ylabel('Cooling Demand', fontsize=14) axs[1].set_title('Cooling Load Duration Curve', fontsize=16) axs[1].grid(True) axs[1].legend(loc='upper right', fontsize=12) # Adjust layout plt.tight_layout() # Save the plot plt.savefig(f'{building.name}_load_duration_curve.jpg') # Close the plot to release memory plt.close() def individual_building_info(self, building): table_data = [ ["Maximum Monthly HVAC Demands", f"\\includegraphics[width=1\\linewidth]{{{building.name}_monthly_maximum_hvac_loads.jpg}}"], ["Load Duration Curve", f"\\includegraphics[width=1\\linewidth]{{{building.name}_load_duration_curve.jpg}}"], ] self.add_table(table_data, caption=f'{building.name} Information', first_column_width=1.5) def building_existing_system_info(self, building): existing_archetype = building.energy_systems_archetype_name fuels = [] system_schematic = "-" heating_system = "-" cooling_system = "-" dhw = "-" electricity = "Grid" hvac_ec = format((building.heating_consumption[cte.YEAR][0] + building.cooling_consumption[cte.YEAR][0])/1e6, '.2f') dhw_ec = format(building.domestic_hot_water_consumption[cte.YEAR][0]/1e6, '.2f') on_site_generation = "-" yearly_operational_cost = "-" life_cycle_cost = "-" for energy_system in building.energy_systems: if cte.HEATING and cte.DOMESTIC_HOT_WATER in energy_system.demand_types: heating_system = energy_system.name dhw = energy_system.name elif cte.DOMESTIC_HOT_WATER in energy_system.demand_types: dhw = energy_system.name elif cte.HEATING in energy_system.demand_types: heating_system = energy_system.name elif cte.COOLING in energy_system.demand_types: cooling_system = energy_system.name for generation_system in energy_system.generation_systems: fuels.append(generation_system.fuel_type) if generation_system.system_type == cte.PHOTOVOLTAIC: electricity = "Grid-tied PV" energy_system_table_data = [ ["Detail", "Existing System", "Proposed System"], ["Energy System Archetype", existing_archetype, "-"], ["System Schematic", system_schematic, system_schematic], ["Heating System", heating_system, "-"], ["Cooling System", cooling_system, "-"], ["DHW System", dhw, "-"], ["Electricity", electricity, "-"], ["Fuel(s)", str(fuels), "-"], ["HVAC Energy Consumption (MWh)", hvac_ec, "-"], ["DHW Energy Consumption (MWH)", dhw_ec, "-"], ["Yearly Operational Cost (CAD)", yearly_operational_cost, "-"], ["Life Cycle Cost (CAD)", life_cycle_cost, "-"] ] self.add_table(energy_system_table_data, caption= f'Building {building.name} Energy System Characteristics') def building_fuel_consumption_breakdown(self, building): # Initialize variables to store fuel consumption breakdown fuel_breakdown = { "Heating": {"Gas": 0, "Electricity": 0}, "Domestic Hot Water": {"Gas": 0, "Electricity": 0}, "Cooling": {"Electricity": 0}, "Appliance": building.appliances_electrical_demand[cte.YEAR][0] / 1e6, "Lighting": building.lighting_electrical_demand[cte.YEAR][0] / 1e6 } # Iterate through energy systems of the building for energy_system in building.energy_systems: for demand_type in energy_system.demand_types: for generation_system in energy_system.generation_systems: consumption = 0 if demand_type == cte.HEATING: consumption = building.heating_consumption[cte.YEAR][0] / 1e6 elif demand_type == cte.DOMESTIC_HOT_WATER: consumption = building.domestic_hot_water_consumption[cte.YEAR][0] / 1e6 elif demand_type == cte.COOLING: consumption = building.cooling_consumption[cte.YEAR][0] / 1e6 if generation_system.fuel_type == cte.ELECTRICITY: fuel_breakdown[demand_type]["Electricity"] += consumption else: fuel_breakdown[demand_type]["Gas"] += consumption electricity_labels = ['Appliance', 'Lighting'] electricity_sizes = [fuel_breakdown['Appliance'], fuel_breakdown['Lighting']] if fuel_breakdown['Heating']['Electricity'] > 0: electricity_labels.append('Heating') electricity_sizes.append(fuel_breakdown['Heating']['Electricity']) if fuel_breakdown['Cooling']['Electricity'] > 0: electricity_labels.append('Cooling') electricity_sizes.append(fuel_breakdown['Cooling']['Electricity']) if fuel_breakdown['Domestic Hot Water']['Electricity'] > 0: electricity_labels.append('Domestic Hot Water') electricity_sizes.append(fuel_breakdown['Domestic Hot Water']['Electricity']) # Data for bar chart gas_labels = ['Heating', 'Domestic Hot Water'] gas_sizes = [fuel_breakdown['Heating']['Gas'], fuel_breakdown['Domestic Hot Water']['Gas']] # Set the style plt.style.use('seaborn-muted') # Create plot grid fig, axs = plt.subplots(1, 2, figsize=(12, 6)) # Plot pie chart for electricity consumption breakdown colors = cm.get_cmap('tab20c', len(electricity_labels)) axs[0].pie(electricity_sizes, labels=electricity_labels, autopct=lambda pct: f"{pct:.1f}%\n({pct / 100 * sum(electricity_sizes):.2f})", startangle=90, colors=[colors(i) for i in range(len(electricity_labels))]) axs[0].set_title('Electricity Consumption Breakdown') # Plot bar chart for natural gas consumption breakdown colors = cm.get_cmap('Paired', len(gas_labels)) axs[1].bar(gas_labels, gas_sizes, color=[colors(i) for i in range(len(gas_labels))]) axs[1].set_ylabel('Consumption (MWh)') axs[1].set_title('Natural Gas Consumption Breakdown') # Add grid to bar chart axs[1].grid(axis='y', linestyle='--', alpha=0.7) # Add a title to the entire figure plt.suptitle('Building Energy Consumption Breakdown', fontsize=16, fontweight='bold') # Adjust layout plt.tight_layout() # Save the plot as a high-quality image plt.savefig(f'{building.name}_energy_consumption_breakdown.png', dpi=300) plt.close() 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: f.write('\n'.join(self.content)) def compile_to_pdf(self): subprocess.run(['pdflatex', self.file_name])