339 lines
17 KiB
Python
339 lines
17 KiB
Python
|
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
|
||
|
|
||
|
class EnergySystemAnalysisReport:
|
||
|
def __init__(self, city, output_path):
|
||
|
self.city = city
|
||
|
self.output_path = output_path
|
||
|
self.content = []
|
||
|
self.report = LatexReport('energy_system_analysis_report.tex')
|
||
|
|
||
|
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] / 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] / (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]) /
|
||
|
(1e3 * total_floor_area), '.2f'))
|
||
|
]
|
||
|
table_data.append(building_data)
|
||
|
intensity_table_data.append(intensity_data)
|
||
|
|
||
|
self.report.add_table(table_data, caption='City Buildings Energy Demands')
|
||
|
self.report.add_table(intensity_table_data, caption='Energy Intensity Information')
|
||
|
|
||
|
def base_case_charts(self):
|
||
|
save_directory = self.output_path
|
||
|
|
||
|
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')
|
||
|
|
||
|
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(save_directory / '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(save_directory / filename)
|
||
|
plt.close()
|
||
|
|
||
|
building_names = [building.name for building in self.city.buildings]
|
||
|
yearly_heating_demand = [building.heating_demand[cte.YEAR][0] / 3.6e9 for building in self.city.buildings]
|
||
|
yearly_cooling_demand = [building.cooling_demand[cte.YEAR][0] / 3.6e9 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):
|
||
|
save_directory = self.output_path
|
||
|
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 (kWh)')
|
||
|
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 (kWh)')
|
||
|
axs[1].tick_params(axis='x', rotation=45)
|
||
|
|
||
|
plt.tight_layout() # Adjust layout to prevent overlapping
|
||
|
plt.savefig(save_directory / f'{building.name}_monthly_maximum_hvac_loads.jpg')
|
||
|
plt.close()
|
||
|
|
||
|
def load_duration_curves(self):
|
||
|
save_directory = self.output_path
|
||
|
for building in self.city.buildings:
|
||
|
heating_demand = [demand / 3.6e6 for demand in building.heating_demand[cte.HOUR]]
|
||
|
cooling_demand = [demand / 3.6e6 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('ggplot')
|
||
|
|
||
|
# 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 (kWh)', 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 (kWh)', 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()
|
||
|
plt.savefig(save_directory / f'{building.name}_load_duration_curve.jpg')
|
||
|
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.report.add_table(table_data, caption=f'{building.name} Information', first_column_width=1.5)
|
||
|
|
||
|
def building_system_retrofit_results(self, building_name, current_system, new_system):
|
||
|
current_system_archetype = current_system[f'{building_name}']['Energy System Archetype']
|
||
|
current_system_heating = current_system[f'{building_name}']['Heating Equipments']
|
||
|
current_system_cooling = current_system[f'{building_name}']['Cooling Equipments']
|
||
|
current_system_dhw = current_system[f'{building_name}']['DHW Equipments']
|
||
|
current_system_pv = current_system[f'{building_name}']['Photovoltaic System Capacity']
|
||
|
current_system_heating_fuel = current_system[f'{building_name}']['Heating Fuel']
|
||
|
current_system_hvac_consumption = current_system[f'{building_name}']['Yearly HVAC Energy Consumption (MWh)']
|
||
|
current_system_dhw_consumption = current_system[f'{building_name}']['DHW Energy Consumption (MWH)']
|
||
|
current_pv_production = current_system[f'{building_name}']['PV Yearly Production (kWh)']
|
||
|
current_capital_cost = current_system[f'{building_name}']['Energy System Capital Cost (CAD)']
|
||
|
current_operational = current_system[f'{building_name}']['Energy System Average Yearly Operational Cost (CAD)']
|
||
|
current_lcc = current_system[f'{building_name}']['Energy System Life Cycle Cost (CAD)']
|
||
|
new_system_archetype = new_system[f'{building_name}']['Energy System Archetype']
|
||
|
new_system_heating = new_system[f'{building_name}']['Heating Equipments']
|
||
|
new_system_cooling = new_system[f'{building_name}']['Cooling Equipments']
|
||
|
new_system_dhw = new_system[f'{building_name}']['DHW Equipments']
|
||
|
new_system_pv = new_system[f'{building_name}']['Photovoltaic System Capacity']
|
||
|
new_system_heating_fuel = new_system[f'{building_name}']['Heating Fuel']
|
||
|
new_system_hvac_consumption = new_system[f'{building_name}']['Yearly HVAC Energy Consumption (MWh)']
|
||
|
new_system_dhw_consumption = new_system[f'{building_name}']['DHW Energy Consumption (MWH)']
|
||
|
new_pv_production = new_system[f'{building_name}']['PV Yearly Production (kWh)']
|
||
|
new_capital_cost = new_system[f'{building_name}']['Energy System Capital Cost (CAD)']
|
||
|
new_operational = new_system[f'{building_name}']['Energy System Average Yearly Operational Cost (CAD)']
|
||
|
new_lcc = new_system[f'{building_name}']['Energy System Life Cycle Cost (CAD)']
|
||
|
|
||
|
energy_system_table_data = [
|
||
|
["Detail", "Existing System", "Proposed System"],
|
||
|
["Energy System Archetype", current_system_archetype, new_system_archetype],
|
||
|
["Heating Equipments", current_system_heating, new_system_heating],
|
||
|
["Cooling Equipments", current_system_cooling, new_system_cooling],
|
||
|
["DHW Equipments", current_system_dhw, new_system_dhw],
|
||
|
["Photovoltaic System Capacity", current_system_pv, new_system_pv],
|
||
|
["Heating Fuel", current_system_heating_fuel, new_system_heating_fuel],
|
||
|
["Yearly HVAC Energy Consumption (MWh)", current_system_hvac_consumption, new_system_hvac_consumption],
|
||
|
["DHW Energy Consumption (MWH)", current_system_dhw_consumption, new_system_dhw_consumption],
|
||
|
["PV Yearly Production (kWh)", current_pv_production, new_pv_production],
|
||
|
["Energy System Capital Cost (CAD)", current_capital_cost, new_capital_cost],
|
||
|
["Energy System Average Yearly Operational Cost (CAD)", current_operational, new_operational],
|
||
|
["Energy System Life Cycle Cost (CAD)", current_lcc, new_lcc]
|
||
|
]
|
||
|
self.report.add_table(energy_system_table_data, caption=f'Building {building_name} Energy System Characteristics')
|
||
|
|
||
|
def building_fuel_consumption_breakdown(self, building):
|
||
|
save_directory = self.output_path
|
||
|
# 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:
|
||
|
if demand_type == cte.HEATING:
|
||
|
consumption = building.heating_consumption[cte.YEAR][0] / 3.6e9
|
||
|
for generation_system in energy_system.generation_systems:
|
||
|
if generation_system.fuel_type == cte.ELECTRICITY:
|
||
|
fuel_breakdown[demand_type]["Electricity"] += consumption
|
||
|
else:
|
||
|
fuel_breakdown[demand_type]["Gas"] += consumption
|
||
|
elif demand_type == cte.DOMESTIC_HOT_WATER:
|
||
|
consumption = building.domestic_hot_water_consumption[cte.YEAR][0] / 1e6
|
||
|
for generation_system in energy_system.generation_systems:
|
||
|
if generation_system.fuel_type == cte.ELECTRICITY:
|
||
|
fuel_breakdown[demand_type]["Electricity"] += consumption
|
||
|
else:
|
||
|
fuel_breakdown[demand_type]["Gas"] += consumption
|
||
|
elif demand_type == cte.COOLING:
|
||
|
consumption = building.cooling_consumption[cte.YEAR][0] / 3.6e9
|
||
|
fuel_breakdown[demand_type]["Electricity"] += 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('ggplot')
|
||
|
|
||
|
# 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(save_directory / f'{building.name}_energy_consumption_breakdown.png', dpi=300)
|
||
|
plt.close()
|
||
|
|
||
|
def create_report(self, current_system, new_system):
|
||
|
os.chdir(self.output_path)
|
||
|
self.report.add_section('Current Status')
|
||
|
self.building_energy_info()
|
||
|
self.base_case_charts()
|
||
|
self.report.add_image('hvac_demand_chart.jpg', caption='Yearly HVAC Demands')
|
||
|
self.report.add_image('dhw_demand_chart.jpg', caption='Yearly DHW Demands')
|
||
|
self.report.add_image('electricity_demand_chart.jpg', caption='Yearly Electricity Demands')
|
||
|
self.maximum_monthly_hvac_chart()
|
||
|
self.load_duration_curves()
|
||
|
for building in self.city.buildings:
|
||
|
self.individual_building_info(building)
|
||
|
self.building_system_retrofit_results(building_name=building.name, current_system=current_system, new_system=new_system)
|
||
|
self.building_fuel_consumption_breakdown(building)
|
||
|
self.report.add_image(f'{building.name}_energy_consumption_breakdown.png',
|
||
|
caption=f'Building {building.name} Consumption by source and sector breakdown')
|
||
|
self.report.save_report()
|
||
|
self.report.compile_to_pdf()
|