Merge pull request 'Costing initiated' (#3) from fixing_simulation_model into main

Reviewed-on: https://nextgenerations-cities.encs.concordia.ca/gitea/s_ranjbar/energy_system_modelling_workflow/pulls/3
This commit is contained in:
Saeed Ranjbar 2024-05-02 18:38:02 -04:00
commit 181de80aff
22 changed files with 186 additions and 94 deletions

View File

@ -90,10 +90,8 @@ class Building(CityObject):
self._interior_slabs.append(surface)
else:
logging.error('Building %s [%s] has an unexpected surface type %s.', self.name, self.aliases, surface.type)
self._heating_consumption_disaggregated = {}
self._domestic_hot_water_peak_load = None
self._fuel_consumption_breakdown = {}
self._domestic_how_water_consumption_disaggregated = {}
@property
def shell(self) -> Polyhedron:
@ -844,38 +842,6 @@ class Building(CityObject):
self._onsite_electrical_production[_key] = _results
return self._onsite_electrical_production
@property
def heating_consumption_disaggregated(self) -> dict:
"""
Get energy consumed for heating from different fuels in J
return: dict
"""
return self._heating_consumption_disaggregated
@heating_consumption_disaggregated.setter
def heating_consumption_disaggregated(self, value):
"""
Get energy consumed for heating from different fuels in J
return: dict
"""
self._heating_consumption_disaggregated = value
@property
def domestic_how_water_consumption_disaggregated(self) -> dict:
"""
Get energy consumed for heating from different fuels in J
return: dict
"""
return self._domestic_how_water_consumption_disaggregated
@domestic_how_water_consumption_disaggregated.setter
def domestic_how_water_consumption_disaggregated(self, value):
"""
Get energy consumed for heating from different fuels in J
return: dict
"""
self._domestic_how_water_consumption_disaggregated = value
@property
def lower_corner(self):
"""
@ -891,45 +857,12 @@ class Building(CityObject):
return [self._max_x, self._max_y, self._max_z]
@property
def fuel_consumption_breakdown(self) -> dict:
def energy_consumption_breakdown(self) -> dict:
"""
Get energy consumption of different sectors
return: dict
"""
fuel_breakdown = {cte.ELECTRICITY: {cte.LIGHTING: self.lighting_electrical_demand[cte.YEAR][0],
cte.APPLIANCES: self.appliances_electrical_demand[cte.YEAR][0]}}
energy_systems = self.energy_systems
for energy_system in energy_systems:
demand_types = energy_system.demand_types
generation_systems = energy_system.generation_systems
for demand_type in demand_types:
if demand_type == cte.COOLING:
fuel_breakdown[cte.ELECTRICITY][cte.COOLING] = self.cooling_consumption[cte.YEAR][0] / 3600
elif demand_type == cte.HEATING:
heating_fuels = [generation_system.fuel_type for generation_system in generation_systems]
if len(heating_fuels) > 1:
for fuel in heating_fuels:
if fuel == cte.ELECTRICITY:
fuel_breakdown[cte.ELECTRICITY][cte.HEATING] = self._heating_consumption_disaggregated[cte.ELECTRICITY][cte.YEAR][0]
elif fuel not in fuel_breakdown.keys():
fuel_breakdown[fuel] = {cte.HEATING: self._heating_consumption_disaggregated[fuel][cte.YEAR][0]}
else:
fuel_breakdown[fuel][cte.HEATING] = self._heating_consumption_disaggregated[fuel][cte.YEAR][0]
else:
fuel = heating_fuels[0]
if fuel == cte.ELECTRICITY:
fuel_breakdown[cte.ELECTRICITY][cte.HEATING] = self.heating_consumption[cte.YEAR][0]
elif fuel not in fuel_breakdown.keys():
fuel_breakdown[fuel] = {cte.HEATING: self.heating_consumption[cte.YEAR][0]}
else:
fuel_breakdown[fuel][cte.HEATING] = self.heating_consumption[cte.YEAR][0]
elif demand_type == cte.DOMESTIC_HOT_WATER:
for generation_system in generation_systems:
if generation_system.fuel_type == cte.ELECTRICITY:
fuel_breakdown[cte.ELECTRICITY][cte.DOMESTIC_HOT_WATER] = self.domestic_hot_water_consumption[cte.YEAR][0]
elif generation_system.fuel_type not in fuel_breakdown.keys():
fuel_breakdown[generation_system.fuel_type] = {cte.DOMESTIC_HOT_WATER: self.domestic_hot_water_consumption[cte.YEAR][0]}
else:
fuel_breakdown[generation_system.fuel_type][cte.DOMESTIC_HOT_WATER] = self.domestic_hot_water_consumption[cte.YEAR][0]
self._fuel_consumption_breakdown = fuel_breakdown
return self._fuel_consumption_breakdown

View File

@ -47,6 +47,7 @@ class NonPvGenerationSystem(GenerationSystem):
self._cooling_supply_temperature = None
self._reversible = None
self._simultaneous_heat_cold = None
self._heating_fuel_consumption = {}
@property
def nominal_heat_output(self):
@ -520,3 +521,19 @@ class NonPvGenerationSystem(GenerationSystem):
"""
self._simultaneous_heat_cold = value
@property
def heating_fuel_consumption(self) -> dict:
"""
Get fuel consumption in W, m3, or kg
:return: dict{[float]}
"""
return self._heating_fuel_consumption
@heating_fuel_consumption.setter
def heating_fuel_consumption(self, value):
"""
Set fuel consumption in W, m3, or kg
:param value: dict{[float]}
"""
self._heating_fuel_consumption = value

View File

@ -1311,7 +1311,6 @@
<demands>
<demand>heating</demand>
<demand>cooling</demand>
<demand>domestic_hot_water</demand>
</demands>
<components>
<generation_id>21</generation_id>
@ -1408,8 +1407,8 @@
<demand>cooling</demand>
</demands>
<components>
<generation_id>21</generation_id>
<generation_id>18</generation_id>
<generation_id>23</generation_id>
<generation_id>16</generation_id>
</components>
</system>
<system>
@ -1444,6 +1443,7 @@
<systems>
<system_id>7</system_id>
<system_id>1</system_id>
<system_id>10</system_id>
</systems>
</system_archetype>
<system_archetype id="2">

View File

@ -10,11 +10,10 @@ Project Coder Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca
KELVIN = 273.15
WATER_DENSITY = 1000 # kg/m3
WATER_HEAT_CAPACITY = 4182 # J/kgK
NATURAL_GAS_LHV = 36.6e6 # J/m3
AIR_DENSITY = 1.293 # kg/m3
AIR_HEAT_CAPACITY = 1005.2 # J/kgK
# converters
HOUR_TO_MINUTES = 60
MINUTES_TO_SECONDS = 60

14
main.py
View File

@ -13,10 +13,8 @@ from scripts.energy_system_analysis_report import EnergySystemAnalysisReport
from scripts import random_assignation
from hub.imports.energy_systems_factory import EnergySystemsFactory
from scripts.energy_system_sizing import SystemSizing
from scripts.system_simulation import SystemSimulation
from scripts.costs.cost import Cost
from costs.constants import CURRENT_STATUS, SKIN_RETROFIT, SKIN_RETROFIT_AND_SYSTEM_RETROFIT_AND_PV, SYSTEM_RETROFIT_AND_PV
from scripts.energy_system_retrofit_results import system_results, new_system_results
from scripts.energy_system_sizing_and_simulation_factory import EnergySystemsSimulationFactory
# Specify the GeoJSON file path
geojson_file = process_geojson(x=-73.5681295982132, y=45.49218262677643, diff=0.0001)
@ -43,15 +41,13 @@ random_assignation.call_random(city.buildings, random_assignation.residential_sy
EnergySystemsFactory('montreal_custom', city).enrich()
SystemSizing(city.buildings).montreal_custom()
current_system = system_results(city.buildings)
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()
new_system = system_results(city.buildings)
EnergySystemAnalysisReport(city, output_path).create_report(current_system, new_system)
for building in city.buildings:
costs = Cost(building, retrofit_scenario=SYSTEM_RETROFIT_AND_PV).life_cycle
Cost(building, retrofit_scenario=SKIN_RETROFIT_AND_SYSTEM_RETROFIT_AND_PV).life_cycle.to_csv(output_path / f'{building.name}_lcc.csv')
costs.loc['global_capital_costs', f'Scenario {SYSTEM_RETROFIT_AND_PV}'].to_csv(output_path / f'{building.name}_global_capital.csv')
costs.loc['global_operational_costs', f'Scenario {SYSTEM_RETROFIT_AND_PV}'].to_csv(
output_path / f'{building.name}_global_operational.csv')

View File

@ -44,7 +44,7 @@ class Cost:
if dictionary is None:
dictionary = Dictionaries().hub_function_to_montreal_custom_costs_function
self._building = building
fuel_type = self._building.fuel_consumption_breakdown.keys()
fuel_type = self._building.energy_consumption_breakdown.keys()
self._configuration = Configuration(number_of_years,
percentage_credit,
interest_rate, credit_years,
@ -106,10 +106,10 @@ class Cost:
]
additional_costs = [
global_operational_costs[f'Fixed Costs {fuel}'] for fuel in
self._building.fuel_consumption_breakdown.keys() if fuel != cte.ELECTRICITY
self._building.energy_consumption_breakdown.keys() if fuel != cte.ELECTRICITY
] + [
global_operational_costs[f'Variable Costs {fuel}'] for fuel in
self._building.fuel_consumption_breakdown.keys() if fuel != cte.ELECTRICITY
self._building.energy_consumption_breakdown.keys() if fuel != cte.ELECTRICITY
]
df_operational_costs = sum(operational_costs_list + additional_costs)
df_maintenance_costs = (

View File

@ -35,14 +35,14 @@ class TotalOperationalCosts(CostBase):
:return: pd.DataFrame
"""
building = self._building
fuel_consumption_breakdown = building.fuel_consumption_breakdown
fuel_consumption_breakdown = building.energy_consumption_breakdown
archetype = self._archetype
total_floor_area = self._total_floor_area
if archetype.function == 'residential':
factor = total_floor_area / 80
else:
factor = 1
total_electricity_consumption = sum(self._building.fuel_consumption_breakdown[cte.ELECTRICITY].values())
total_electricity_consumption = sum(self._building.energy_consumption_breakdown[cte.ELECTRICITY].values())
peak_electricity_load = PeakLoad(self._building).electricity_peak_load
peak_load_value = peak_electricity_load.max(axis=1)
peak_electricity_demand = peak_load_value[1] / 1000 # self._peak_electricity_demand adapted to kW
@ -90,7 +90,7 @@ class TotalOperationalCosts(CostBase):
def columns(self):
columns_list = []
fuels = [key for key in self._building.fuel_consumption_breakdown.keys()]
fuels = [key for key in self._building.energy_consumption_breakdown.keys()]
for fuel in fuels:
if fuel == cte.ELECTRICITY:
columns_list.append('Fixed Costs Electricity Peak')

View File

@ -31,7 +31,7 @@ class SystemSizing:
if generation.energy_storage_systems is not None:
for storage in generation.energy_storage_systems:
if storage.type_energy_stored == 'thermal':
storage.volume = (sizing_demand * 1000) / (cte.WATER_HEAT_CAPACITY*cte.WATER_DENSITY)
storage.volume = building.heating_peak_load[cte.YEAR][0] / (cte.WATER_HEAT_CAPACITY*cte.WATER_DENSITY * 20)
elif generation.system_type == 'Boiler':
generation.nominal_heat_output = 0.4 * sizing_demand / 1000

View File

@ -6,6 +6,7 @@ Project Coder Saeed Ranjbar saeed.ranjbar@mail.concordia.ca
"""
from scripts.system_simulation_models.archetype13 import Archetype13
from scripts.system_simulation_models.archetype1 import Archetype1
class EnergySystemsSimulationFactory:
@ -13,15 +14,24 @@ class EnergySystemsSimulationFactory:
EnergySystemsFactory class
"""
def __init__(self, handler, building):
def __init__(self, handler, building, output_path):
self._output_path = output_path
self._handler = '_' + handler.lower()
self._building = building
def _archetype1(self):
"""
Enrich the city by using the sizing and simulation model developed for archetype13 of montreal_future_systems
"""
Archetype1(self._building, self._output_path).enrich_buildings()
self._building.level_of_detail.energy_systems = 2
self._building.level_of_detail.energy_systems = 2
def _archetype13(self):
"""
Enrich the city by using the sizing and simulation model developed for archetype13 of montreal_future_systems
"""
Archetype13(self._building).enrich_buildings()
Archetype13(self._building, self._output_path).enrich_buildings()
self._building.level_of_detail.energy_systems = 2
self._building.level_of_detail.energy_systems = 2

View File

@ -81,7 +81,7 @@ class SystemSimulation:
else:
factor = 4
m_dis[i + 1] = self.maximum_heating_demand / (cte.WATER_HEAT_CAPACITY * factor * 3600)
t_return = T[i + 1] - self.heating_demand[i + 1] / (m_dis[i + 1] * cte.WATER_HEAT_CAPACITY)
t_return = T[i + 1] - self.heating_demand[i + 1] / (m_dis[i + 1] * cte.WATER_HEAT_CAPACITY * 3600)
if m_dis[i + 1] == 0 or (m_dis[i + 1] > 0 and t_return < 25):
T_ret[i + 1] = max(25, T[i + 1])
else:
@ -89,7 +89,7 @@ class SystemSimulation:
tes_output = m_dis[i + 1] * cte.WATER_HEAT_CAPACITY * (T[i + 1] - T_ret[i + 1])
if tes_output < (self.heating_demand[i + 1] / 3600):
q_aux[i + 1] = (self.heating_demand[i + 1] / 3600) - tes_output
aux_fuel[i + 1] = (q_aux[i + 1] * dt) / 50e6
aux_fuel[i + 1] = (q_aux[i + 1] * dt) / 35.8e6
boiler_consumption[i + 1] = q_aux[i + 1] / boiler_efficiency
heating_consumption[i + 1] = boiler_consumption[i + 1] + hp_electricity[i + 1]
data = list(zip(T, T_sup, T_ret, m_ch, m_dis, q_hp, hp_electricity, aux_fuel, q_aux, self.heating_demand))
@ -129,7 +129,7 @@ class SystemSimulation:
disaggregated_consumption[generation_system.fuel_type][cte.HOUR])
disaggregated_consumption[generation_system.fuel_type][cte.YEAR] = [
sum(disaggregated_consumption[generation_system.fuel_type][cte.MONTH])]
self.building.heating_consumption_disaggregated = disaggregated_consumption
self.building.heating_fuel_consumption_disaggregated = disaggregated_consumption
return self.building

View File

@ -0,0 +1,137 @@
import math
import csv
import hub.helpers.constants as cte
from hub.helpers.monthly_values import MonthlyValues
class Archetype1:
def __init__(self, building, output_path):
self._building = building
self._name = building.name
self._pv_system = building.energy_systems[1]
self._hvac_system = building.energy_systems[0]
self._dhw_system = building.energy_systems[-1]
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 = [0] + [demand / 3600 for demand in building.heating_demand[cte.HOUR]]
self._hourly_cooling_demand = [demand / 3600 for demand in building.cooling_demand[cte.HOUR]]
self._hourly_dhw_demand = building.domestic_hot_water_heat_demand[cte.HOUR]
self._output_path = output_path
self._t_out = building.external_temperature[cte.HOUR]
def hvac_sizing(self):
storage_factor = 3
heat_pump = self._hvac_system.generation_systems[0]
boiler = self._hvac_system.generation_systems[1]
thermal_storage = heat_pump.energy_storage_systems[0]
heat_pump.nominal_heat_output = round(0.5 * self._heating_peak_load / 3600)
heat_pump.nominal_cooling_output = round(self._cooling_peak_load / 3600)
boiler.nominal_heat_output = round(0.5 * self._heating_peak_load / 3600)
thermal_storage.volume = round(
(self._heating_peak_load * storage_factor) / (cte.WATER_HEAT_CAPACITY * cte.WATER_DENSITY * 30))
return heat_pump, boiler, thermal_storage
def hvac_simulation(self):
hp, boiler, tes = self.hvac_sizing()
demand = self._hourly_heating_demand
if hp.source_medium == cte.AIR:
hp.source_temperature = self._t_out
# Heating System Simulation
variable_names = ["t_sup_hp", "t_tank", "t_ret", "m_ch", "m_dis", "q_hp", "q_boiler", "hp_cop",
"hp_electricity", "boiler_gas", "boiler_consumption", "t_sup_boiler", "heating_consumption"]
num_hours = len(demand)
variables = {name: [0] * num_hours for name in variable_names}
(t_sup_hp, t_tank, t_ret, m_ch, m_dis, q_hp, q_boiler, hp_cop,
hp_electricity, boiler_gas, boiler_consumption, t_sup_boiler, heating_consumption) = [variables[name] for name in
variable_names]
t_tank[0] = 30
dt = 3600
hp_heating_cap = hp.nominal_heat_output
hp_efficiency = float(hp.heat_efficiency)
boiler_efficiency = float(boiler.heat_efficiency)
v, h = float(tes.volume), float(tes.height)
r_tot = sum(float(layer.thickness) / float(layer.material.conductivity) for layer in
tes.layers)
u_tot = 1 / r_tot
d = math.sqrt((4 * v) / (math.pi * h))
a_side = math.pi * d * h
a_top = math.pi * d ** 2 / 4
ua = u_tot * (2 * a_top + a_side)
for i in range(len(demand) - 1):
t_tank[i + 1] = (t_tank[i] +
((m_ch[i] * (t_sup_hp[i] - t_tank[i])) +
(ua * (self._t_out[i] - t_tank[i] + 5)) / cte.WATER_HEAT_CAPACITY -
m_dis[i] * (t_tank[i] - t_ret[i])) * (dt / (cte.WATER_DENSITY * v)))
if t_tank[i + 1] < 40:
q_hp[i + 1] = hp_heating_cap
m_ch[i + 1] = q_hp[i + 1] / (cte.WATER_HEAT_CAPACITY * 7)
t_sup_hp[i + 1] = (q_hp[i + 1] / (m_ch[i + 1] * cte.WATER_HEAT_CAPACITY)) + t_tank[i + 1]
elif 40 <= t_tank[i + 1] < 55 and q_hp[i] == 0:
q_hp[i + 1] = 0
m_ch[i + 1] = 0
t_sup_hp[i + 1] = t_tank[i + 1]
elif 40 <= t_tank[i + 1] < 55 and q_hp[i] > 0:
q_hp[i + 1] = hp_heating_cap
m_ch[i + 1] = q_hp[i + 1] / (cte.WATER_HEAT_CAPACITY * 3)
t_sup_hp[i + 1] = (q_hp[i + 1] / (m_ch[i + 1] * cte.WATER_HEAT_CAPACITY)) + t_tank[i + 1]
else:
q_hp[i + 1], m_ch[i + 1], t_sup_hp[i + 1] = 0, 0, t_tank[i + 1]
hp_electricity[i + 1] = q_hp[i + 1] / hp_efficiency
if demand[i + 1] == 0:
m_dis[i + 1], t_return, t_ret[i + 1] = 0, t_tank[i + 1], t_tank[i + 1]
else:
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 * dt)
t_return = t_tank[i + 1] - demand[i + 1] / (m_dis[i + 1] * cte.WATER_HEAT_CAPACITY * dt)
if m_dis[i + 1] == 0 or (m_dis[i + 1] > 0 and t_return < 25):
t_ret[i + 1] = max(25, t_tank[i + 1])
else:
t_ret[i + 1] = t_tank[i + 1] - demand[i + 1] / (m_dis[i + 1] * cte.WATER_HEAT_CAPACITY * dt)
tes_output = m_dis[i + 1] * cte.WATER_HEAT_CAPACITY * (t_tank[i + 1] - t_ret[i + 1])
if tes_output < (demand[i + 1] / dt):
q_boiler[i + 1] = (demand[i + 1] / dt) - tes_output
boiler_gas[i + 1] = (q_boiler[i + 1] * dt) / cte.NATURAL_GAS_LHV
if q_boiler[i + 1] > 0:
t_sup_boiler[i + 1] = q_boiler[i + 1] / (m_dis[i + 1] * cte.WATER_HEAT_CAPACITY) + t_tank[i + 1]
else:
t_sup_boiler[i + 1] = t_tank[i + 1]
boiler_consumption[i + 1] = q_boiler[i + 1] / boiler_efficiency
heating_consumption[i + 1] = boiler_consumption[i + 1] + hp_electricity[i + 1]
data = list(zip(q_hp, hp_electricity, hp_cop, m_ch, t_sup_hp, t_tank, m_dis, q_boiler,
boiler_gas, t_sup_boiler, t_ret, demand))
file_name = f'simulation_results_{self._name}.csv'
with open(self._output_path / file_name, 'w', newline='') as csvfile:
output_file = csv.writer(csvfile)
# Write header
output_file.writerow(['HP_heat_output(W)', 'HP_electricity_consumption(W)', 'HP_COP',
'TES_charge_flow_rate(kg/s)', 'TES_supply_temperature(C)', 'TES_temperature(C)',
'TES_discharge_flow_rate(kg/s)', 'Boiler_heat_output(W)', 'Boiler_gas_consumption(m3)',
'Boiler_supply_temperature(C)', 'Return_temperature(C)', 'heating_demand'])
# Write data
output_file.writerows(data)
return heating_consumption, hp_electricity, boiler_gas, t_sup_hp, t_sup_boiler
def enrich_buildings(self):
(self._building.heating_consumption[cte.HOUR], hp_electricity,
boiler_gas, t_sup_hp, t_sup_boiler) = self.hvac_simulation()
self._building.heating_consumption[cte.MONTH] = MonthlyValues.get_total_month(
self._building.heating_consumption[cte.HOUR])
self._building.heating_consumption[cte.YEAR] = [sum(self._building.heating_consumption[cte.MONTH])]
self._hvac_system.generation_systems[0].heating_fuel_consumption[cte.HOUR] = hp_electricity
self._hvac_system.generation_systems[0].heating_fuel_consumption[cte.MONTH] = MonthlyValues.get_total_month(
self._hvac_system.generation_systems[0].heating_fuel_consumption[cte.HOUR])
self._hvac_system.generation_systems[0].heating_fuel_consumption[cte.YEAR] = \
[sum(self._hvac_system.generation_systems[0].heating_fuel_consumption[cte.MONTH])]
self._hvac_system.generation_systems[1].heating_fuel_consumption[cte.HOUR] = boiler_gas
self._hvac_system.generation_systems[1].heating_fuel_consumption[cte.MONTH] = MonthlyValues.get_total_month(
self._hvac_system.generation_systems[1].heating_fuel_consumption[cte.HOUR])
self._hvac_system.generation_systems[1].heating_fuel_consumption[cte.YEAR] = \
[sum(self._hvac_system.generation_systems[1].heating_fuel_consumption[cte.MONTH])]
self._hvac_system.generation_systems[0].heat_supply_temperature = t_sup_hp
self._hvac_system.generation_systems[1].heat_supply_temperature = t_sup_boiler