Saeed Ranjbar
f8fa5a4ed3
The report creation class is completed. Compared to the previous version, building system info tables are added, fuel consumption breakdown graphs are also added.
377 lines
16 KiB
Python
377 lines
16 KiB
Python
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])
|