Compare commits
61 Commits
Author | SHA1 | Date | |
---|---|---|---|
ee9dd58f82 | |||
e4c761850b | |||
b633dca635 | |||
69a1af535b | |||
b6ec189207 | |||
49a531da8f | |||
d77bd7d337 | |||
20a801626a | |||
bf0c42315d | |||
3545418293 | |||
1906862517 | |||
0048e18e55 | |||
52f94c858f | |||
b0e6c7d9ef | |||
9af87fe482 | |||
eb26c48627 | |||
2ca52c157a | |||
62c70b9c9f | |||
a62f5e6f38 | |||
6deffa9323 | |||
c046eacc63 | |||
05b9a42672 | |||
90a7f5648b | |||
4e75024817 | |||
904fe91e5a | |||
|
d2e20312b2 | ||
|
74cf47e3e1 | ||
1a43e65992 | |||
3b5e12efaf | |||
08639d9dd7 | |||
435fc4c679 | |||
1f95943cbb | |||
8f1cbd4a67 | |||
05d88e2461 | |||
|
a474a7d97e | ||
|
0ae92e77a1 | ||
|
e09338c300 | ||
af988e28ed | |||
96711ad41e | |||
faeb3e63d4 | |||
93ab78b34e | |||
6044cfc4e5 | |||
a717f9a644 | |||
f32c74f84a | |||
c4f98a30c1 | |||
7369bc65a4 | |||
58201afda8 | |||
2ef3be7fe3 | |||
2bd8a9a47d | |||
5f95d2a5fb | |||
ee6dc92b40 | |||
335b316072 | |||
ef21ad949d | |||
b1b5477b25 | |||
ee0b985245 | |||
cb842b5917 | |||
1e34687496 | |||
84bde67d0f | |||
e636459b86 | |||
c14c88005d | |||
dce5bb8c06 |
2
.gitignore
vendored
2
.gitignore
vendored
@ -12,3 +12,5 @@
|
||||
cerc_hub.egg-info
|
||||
/out_files
|
||||
/input_files/output_buildings.geojson
|
||||
*/.pyc
|
||||
*.pyc
|
||||
|
@ -2,7 +2,7 @@
|
||||
<module type="PYTHON_MODULE" version="4">
|
||||
<component name="NewModuleRootManager">
|
||||
<content url="file://$MODULE_DIR$" />
|
||||
<orderEntry type="jdk" jdkName="hub" jdkType="Python SDK" />
|
||||
<orderEntry type="jdk" jdkName="Python 3.9 (hub)" jdkType="Python SDK" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
@ -1,4 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectRootManager" version="2" project-jdk-name="hub" project-jdk-type="Python SDK" />
|
||||
<component name="Black">
|
||||
<option name="sdkUUID" value="97386509-ec9b-4dd7-929b-7585219c0447" />
|
||||
</component>
|
||||
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.9 (hub)" project-jdk-type="Python SDK" />
|
||||
</project>
|
@ -6,13 +6,13 @@ import csv
|
||||
from hub.exports.energy_building_exports_factory import EnergyBuildingsExportsFactory
|
||||
from hub.imports.results_factory import ResultFactory
|
||||
|
||||
sys.path.append('./')
|
||||
sys.path.append('../energy_system_modelling_package/')
|
||||
|
||||
|
||||
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:
|
@ -4,13 +4,16 @@ from shapely import Point
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def process_geojson(x, y, diff):
|
||||
def process_geojson(x, y, diff, path, 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()
|
||||
geojson_file = Path(path / 'data/collinear_clean 2.geojson').resolve()
|
||||
if not expansion:
|
||||
output_file = Path(path / 'input_files/output_buildings.geojson').resolve()
|
||||
else:
|
||||
output_file = Path(path / 'input_files/output_buildings_expanded.geojson').resolve()
|
||||
buildings_in_region = []
|
||||
|
||||
with open(geojson_file, 'r') as file:
|
@ -11,9 +11,10 @@ import pandas as pd
|
||||
import numpy_financial as npf
|
||||
from hub.city_model_structure.building import Building
|
||||
import hub.helpers.constants as cte
|
||||
from scripts.costs.configuration import Configuration
|
||||
from scripts.costs.constants import SKIN_RETROFIT, SKIN_RETROFIT_AND_SYSTEM_RETROFIT_AND_PV, SYSTEM_RETROFIT_AND_PV
|
||||
from scripts.costs.cost_base import CostBase
|
||||
from costing_package.configuration import Configuration
|
||||
from costing_package.constants import (SKIN_RETROFIT, SKIN_RETROFIT_AND_SYSTEM_RETROFIT_AND_PV,
|
||||
SYSTEM_RETROFIT_AND_PV, CURRENT_STATUS, PV, SYSTEM_RETROFIT)
|
||||
from costing_package.cost_base import CostBase
|
||||
|
||||
|
||||
class CapitalCosts(CostBase):
|
||||
@ -31,12 +32,13 @@ class CapitalCosts(CostBase):
|
||||
'B3010_opaque_roof',
|
||||
'B1010_superstructure',
|
||||
'D2010_photovoltaic_system',
|
||||
'D3020_heat_and_cooling_generating_systems',
|
||||
'D3040_distribution_systems',
|
||||
'D3050_other_hvac_ahu',
|
||||
'D3060_storage_systems',
|
||||
'D3020_simultaneous_heat_and_cooling_generating_systems',
|
||||
'D3030_heating_systems',
|
||||
'D3040_cooling_systems',
|
||||
'D3050_distribution_systems',
|
||||
'D3060_other_hvac_ahu',
|
||||
'D3070_storage_systems',
|
||||
'D40_dhw',
|
||||
'D5020_lighting_and_branch_wiring'
|
||||
],
|
||||
dtype='float'
|
||||
)
|
||||
@ -45,12 +47,13 @@ class CapitalCosts(CostBase):
|
||||
self._yearly_capital_costs.loc[0, 'B3010_opaque_roof'] = 0
|
||||
self._yearly_capital_costs.loc[0, 'B1010_superstructure'] = 0
|
||||
self._yearly_capital_costs.loc[0, 'D2010_photovoltaic_system'] = 0
|
||||
self._yearly_capital_costs.loc[0, 'D3020_heat_and_cooling_generating_systems'] = 0
|
||||
self._yearly_capital_costs.loc[0, 'D3040_distribution_systems'] = 0
|
||||
self._yearly_capital_costs.loc[0, 'D3080_other_hvac_ahu'] = 0
|
||||
self._yearly_capital_costs.loc[0, 'D3060_storage_systems'] = 0
|
||||
self._yearly_capital_costs.loc[0, 'D3020_simultaneous_heat_and_cooling_generating_systems'] = 0
|
||||
self._yearly_capital_costs.loc[0, 'D3030_heating_systems'] = 0
|
||||
self._yearly_capital_costs.loc[0, 'D3040_cooling_systems'] = 0
|
||||
self._yearly_capital_costs.loc[0, 'D3050_distribution_systems'] = 0
|
||||
self._yearly_capital_costs.loc[0, 'D3060_other_hvac_ahu'] = 0
|
||||
self._yearly_capital_costs.loc[0, 'D3070_storage_systems'] = 0
|
||||
self._yearly_capital_costs.loc[0, 'D40_dhw'] = 0
|
||||
# self._yearly_capital_costs.loc[0, 'D5020_lighting_and_branch_wiring'] = 0
|
||||
|
||||
self._yearly_capital_incomes = pd.DataFrame(
|
||||
index=self._rng,
|
||||
@ -70,12 +73,14 @@ class CapitalCosts(CostBase):
|
||||
for roof in self._building.roofs:
|
||||
self._surface_pv += roof.solid_polygon.area * roof.solar_collectors_area_reduction_factor
|
||||
|
||||
for roof in self._building.roofs:
|
||||
if roof.installed_solar_collector_area is not None:
|
||||
self._surface_pv += roof.installed_solar_collector_area
|
||||
else:
|
||||
self._surface_pv += roof.solid_polygon.area * roof.solar_collectors_area_reduction_factor
|
||||
def calculate(self) -> tuple[pd.DataFrame, pd.DataFrame]:
|
||||
if self._configuration.retrofit_scenario in (SKIN_RETROFIT, SKIN_RETROFIT_AND_SYSTEM_RETROFIT_AND_PV):
|
||||
self.skin_capital_cost()
|
||||
if self._configuration.retrofit_scenario in (SYSTEM_RETROFIT_AND_PV, SKIN_RETROFIT_AND_SYSTEM_RETROFIT_AND_PV):
|
||||
self.energy_system_capital_cost()
|
||||
|
||||
self.skin_capital_cost()
|
||||
self.energy_system_capital_cost()
|
||||
self.skin_yearly_capital_costs()
|
||||
self.yearly_energy_system_costs()
|
||||
self.yearly_incomes()
|
||||
@ -106,10 +111,11 @@ class CapitalCosts(CostBase):
|
||||
capital_cost_transparent = surface_transparent * chapter.item('B2020_transparent').refurbishment[0]
|
||||
capital_cost_roof = surface_roof * chapter.item('B3010_opaque_roof').refurbishment[0]
|
||||
capital_cost_ground = surface_ground * chapter.item('B1010_superstructure').refurbishment[0]
|
||||
self._yearly_capital_costs.loc[0, 'B2010_opaque_walls'] = capital_cost_opaque * self._own_capital
|
||||
self._yearly_capital_costs.loc[0, 'B2020_transparent'] = capital_cost_transparent * self._own_capital
|
||||
self._yearly_capital_costs.loc[0, 'B3010_opaque_roof'] = capital_cost_roof * self._own_capital
|
||||
self._yearly_capital_costs.loc[0, 'B1010_superstructure'] = capital_cost_ground * self._own_capital
|
||||
if self._configuration.retrofit_scenario not in (SYSTEM_RETROFIT_AND_PV, CURRENT_STATUS, PV, SYSTEM_RETROFIT):
|
||||
self._yearly_capital_costs.loc[0, 'B2010_opaque_walls'] = capital_cost_opaque * self._own_capital
|
||||
self._yearly_capital_costs.loc[0, 'B2020_transparent'] = capital_cost_transparent * self._own_capital
|
||||
self._yearly_capital_costs.loc[0, 'B3010_opaque_roof'] = capital_cost_roof * self._own_capital
|
||||
self._yearly_capital_costs.loc[0, 'B1010_superstructure'] = capital_cost_ground * self._own_capital
|
||||
capital_cost_skin = capital_cost_opaque + capital_cost_ground + capital_cost_transparent + capital_cost_roof
|
||||
return capital_cost_opaque, capital_cost_transparent, capital_cost_roof, capital_cost_ground, capital_cost_skin
|
||||
|
||||
@ -147,21 +153,22 @@ class CapitalCosts(CostBase):
|
||||
|
||||
def energy_system_capital_cost(self):
|
||||
chapter = self._capital_costs_chapter.chapter('D_services')
|
||||
energy_system_components = self.system_components()
|
||||
system_components = energy_system_components[0]
|
||||
component_categories = energy_system_components[1]
|
||||
component_sizes = energy_system_components[-1]
|
||||
system_components, component_categories, component_sizes = self.system_components()
|
||||
capital_cost_heating_and_cooling_equipment = 0
|
||||
capital_cost_heating_equipment = 0
|
||||
capital_cost_cooling_equipment = 0
|
||||
capital_cost_domestic_hot_water_equipment = 0
|
||||
capital_cost_energy_storage_equipment = 0
|
||||
capital_cost_distribution_equipment = 0
|
||||
capital_cost_lighting = 0
|
||||
capital_cost_pv = self._surface_pv * chapter.item('D2010_photovoltaic_system').initial_investment[0]
|
||||
# capital_cost_lighting = self._total_floor_area * \
|
||||
# chapter.item('D5020_lighting_and_branch_wiring').initial_investment[0]
|
||||
for (i, component) in enumerate(system_components):
|
||||
if component_categories[i] == 'generation':
|
||||
if component_categories[i] == 'multi_generation':
|
||||
capital_cost_heating_and_cooling_equipment += chapter.item(component).initial_investment[0] * component_sizes[i]
|
||||
elif component_categories[i] == 'heating':
|
||||
capital_cost_heating_equipment += chapter.item(component).initial_investment[0] * component_sizes[i]
|
||||
elif component_categories[i] == 'cooling':
|
||||
capital_cost_cooling_equipment += chapter.item(component).initial_investment[0] * component_sizes[i]
|
||||
elif component_categories[i] == 'dhw':
|
||||
capital_cost_domestic_hot_water_equipment += chapter.item(component).initial_investment[0] * \
|
||||
component_sizes[i]
|
||||
@ -171,26 +178,37 @@ class CapitalCosts(CostBase):
|
||||
else:
|
||||
capital_cost_energy_storage_equipment += chapter.item(component).initial_investment[0] * component_sizes[i]
|
||||
|
||||
self._yearly_capital_costs.loc[0, 'D2010_photovoltaic_system'] = capital_cost_pv
|
||||
self._yearly_capital_costs.loc[0, 'D3020_heat_and_cooling_generating_systems'] = (
|
||||
capital_cost_heating_and_cooling_equipment * self._own_capital)
|
||||
self._yearly_capital_costs.loc[0, 'D3040_distribution_systems'] = (
|
||||
capital_cost_distribution_equipment * self._own_capital)
|
||||
self._yearly_capital_costs.loc[0, 'D3060_storage_systems'] = (
|
||||
capital_cost_energy_storage_equipment * self._own_capital)
|
||||
self._yearly_capital_costs.loc[0, 'D40_dhw'] = (
|
||||
capital_cost_domestic_hot_water_equipment * self._own_capital)
|
||||
# self._yearly_capital_costs.loc[0, 'D5020_lighting_and_branch_wiring'] = capital_cost_lighting * self._own_capital
|
||||
capital_cost_hvac = capital_cost_heating_and_cooling_equipment + capital_cost_distribution_equipment + capital_cost_energy_storage_equipment + capital_cost_domestic_hot_water_equipment
|
||||
return (capital_cost_pv, capital_cost_heating_and_cooling_equipment, capital_cost_distribution_equipment,
|
||||
capital_cost_energy_storage_equipment, capital_cost_domestic_hot_water_equipment, capital_cost_lighting, capital_cost_hvac)
|
||||
if self._configuration.retrofit_scenario in (SKIN_RETROFIT_AND_SYSTEM_RETROFIT_AND_PV, SYSTEM_RETROFIT_AND_PV, PV):
|
||||
self._yearly_capital_costs.loc[0, 'D2010_photovoltaic_system'] = capital_cost_pv
|
||||
if (self._configuration.retrofit_scenario in
|
||||
(SYSTEM_RETROFIT_AND_PV, SKIN_RETROFIT_AND_SYSTEM_RETROFIT_AND_PV, SYSTEM_RETROFIT)):
|
||||
self._yearly_capital_costs.loc[0, 'D3020_simultaneous_heat_and_cooling_generating_systems'] = (
|
||||
capital_cost_heating_and_cooling_equipment * self._own_capital)
|
||||
self._yearly_capital_costs.loc[0, 'D3030_heating_systems'] = (
|
||||
capital_cost_heating_equipment * self._own_capital)
|
||||
self._yearly_capital_costs.loc[0, 'D3040_cooling_systems'] = (
|
||||
capital_cost_cooling_equipment * self._own_capital)
|
||||
self._yearly_capital_costs.loc[0, 'D3050_distribution_systems'] = (
|
||||
capital_cost_distribution_equipment * self._own_capital)
|
||||
self._yearly_capital_costs.loc[0, 'D3070_storage_systems'] = (
|
||||
capital_cost_energy_storage_equipment * self._own_capital)
|
||||
self._yearly_capital_costs.loc[0, 'D40_dhw'] = (
|
||||
capital_cost_domestic_hot_water_equipment * self._own_capital)
|
||||
capital_cost_hvac = (capital_cost_heating_and_cooling_equipment + capital_cost_distribution_equipment +
|
||||
capital_cost_energy_storage_equipment + capital_cost_domestic_hot_water_equipment)
|
||||
return (capital_cost_pv, capital_cost_heating_and_cooling_equipment, capital_cost_heating_equipment,
|
||||
capital_cost_distribution_equipment, capital_cost_cooling_equipment, capital_cost_energy_storage_equipment,
|
||||
capital_cost_domestic_hot_water_equipment, capital_cost_lighting, capital_cost_hvac)
|
||||
|
||||
def yearly_energy_system_costs(self):
|
||||
chapter = self._capital_costs_chapter.chapter('D_services')
|
||||
system_investment_costs = self.energy_system_capital_cost()
|
||||
system_components = self.system_components()[0]
|
||||
component_categories = self.system_components()[1]
|
||||
component_sizes = self.system_components()[2]
|
||||
system_components, component_categories, component_sizes = self.system_components()
|
||||
pv = False
|
||||
for energy_system in self._building.energy_systems:
|
||||
for generation_system in energy_system.generation_systems:
|
||||
if generation_system.system_type == cte.PHOTOVOLTAIC:
|
||||
pv = True
|
||||
for year in range(1, self._configuration.number_of_years):
|
||||
costs_increase = math.pow(1 + self._configuration.consumer_price_index, year)
|
||||
self._yearly_capital_costs.loc[year, 'D2010_photovoltaic_system'] = (
|
||||
@ -200,65 +218,90 @@ class CapitalCosts(CostBase):
|
||||
system_investment_costs[0] * self._configuration.percentage_credit
|
||||
)
|
||||
)
|
||||
self._yearly_capital_costs.loc[year, 'D3020_heat_and_cooling_generating_systems'] = (
|
||||
self._yearly_capital_costs.loc[year, 'D3020_simultaneous_heat_and_cooling_generating_systems'] = (
|
||||
-npf.pmt(
|
||||
self._configuration.interest_rate,
|
||||
self._configuration.credit_years,
|
||||
system_investment_costs[1] * self._configuration.percentage_credit
|
||||
)
|
||||
)
|
||||
self._yearly_capital_costs.loc[year, 'D3040_distribution_systems'] = (
|
||||
self._yearly_capital_costs.loc[year, 'D3030_heating_systems'] = (
|
||||
-npf.pmt(
|
||||
self._configuration.interest_rate,
|
||||
self._configuration.credit_years,
|
||||
system_investment_costs[2] * self._configuration.percentage_credit
|
||||
)
|
||||
)
|
||||
self._yearly_capital_costs.loc[year, 'D3060_storage_systems'] = (
|
||||
self._yearly_capital_costs.loc[year, 'D3040_cooling_systems'] = (
|
||||
-npf.pmt(
|
||||
self._configuration.interest_rate,
|
||||
self._configuration.credit_years,
|
||||
system_investment_costs[3] * self._configuration.percentage_credit
|
||||
)
|
||||
)
|
||||
self._yearly_capital_costs.loc[year, 'D40_dhw'] = (
|
||||
self._yearly_capital_costs.loc[year, 'D3050_distribution_systems'] = (
|
||||
-npf.pmt(
|
||||
self._configuration.interest_rate,
|
||||
self._configuration.credit_years,
|
||||
system_investment_costs[4] * self._configuration.percentage_credit
|
||||
)
|
||||
)
|
||||
# self._yearly_capital_costs.loc[year, 'D5020_lighting_and_branch_wiring'] = (
|
||||
# -npf.pmt(
|
||||
# self._configuration.interest_rate,
|
||||
# self._configuration.credit_years,
|
||||
# system_investment_costs[5] * self._configuration.percentage_credit
|
||||
# )
|
||||
# )
|
||||
# if (year % chapter.item('D5020_lighting_and_branch_wiring').lifetime) == 0:
|
||||
# reposition_cost_lighting = (
|
||||
# self._total_floor_area * chapter.item('D5020_lighting_and_branch_wiring').reposition[0] * costs_increase
|
||||
# )
|
||||
# self._yearly_capital_costs.loc[year, 'D5020_lighting_and_branch_wiring'] += reposition_cost_lighting
|
||||
if self._configuration.retrofit_scenario in (SYSTEM_RETROFIT_AND_PV, SKIN_RETROFIT_AND_SYSTEM_RETROFIT_AND_PV):
|
||||
self._yearly_capital_costs.loc[year, 'D3070_storage_systems'] = (
|
||||
-npf.pmt(
|
||||
self._configuration.interest_rate,
|
||||
self._configuration.credit_years,
|
||||
system_investment_costs[5] * self._configuration.percentage_credit
|
||||
)
|
||||
)
|
||||
self._yearly_capital_costs.loc[year, 'D40_dhw'] = (
|
||||
-npf.pmt(
|
||||
self._configuration.interest_rate,
|
||||
self._configuration.credit_years,
|
||||
system_investment_costs[6] * self._configuration.percentage_credit
|
||||
)
|
||||
)
|
||||
if self._configuration.retrofit_scenario not in (SKIN_RETROFIT, PV):
|
||||
for (i, component) in enumerate(system_components):
|
||||
if (year % chapter.item(component).lifetime) == 0 and year != (self._configuration.number_of_years - 1):
|
||||
if component_categories[i] == 'multi_generation':
|
||||
reposition_cost_heating_and_cooling_equipment = (chapter.item(component).reposition[0] *
|
||||
component_sizes[i] * costs_increase)
|
||||
self._yearly_capital_costs.loc[year, 'D3020_simultaneous_heat_and_cooling_generating_systems'] += (
|
||||
reposition_cost_heating_and_cooling_equipment)
|
||||
elif component_categories[i] == 'heating':
|
||||
reposition_cost_heating_equipment = (chapter.item(component).reposition[0] *
|
||||
component_sizes[i] * costs_increase)
|
||||
self._yearly_capital_costs.loc[year, 'D3030_heating_systems'] += (
|
||||
reposition_cost_heating_equipment)
|
||||
elif component_categories[i] == 'cooling':
|
||||
reposition_cost_cooling_equipment = (chapter.item(component).reposition[0] *
|
||||
component_sizes[i] * costs_increase)
|
||||
self._yearly_capital_costs.loc[year, 'D3040_cooling_systems'] += (
|
||||
reposition_cost_cooling_equipment)
|
||||
elif component_categories[i] == 'dhw':
|
||||
reposition_cost_domestic_hot_water_equipment = (
|
||||
chapter.item(component).reposition[0] * component_sizes[i] * costs_increase)
|
||||
self._yearly_capital_costs.loc[year, 'D40_dhw'] += reposition_cost_domestic_hot_water_equipment
|
||||
elif component_categories[i] == 'distribution':
|
||||
reposition_cost_distribution_equipment = (
|
||||
chapter.item(component).reposition[0] * component_sizes[i] * costs_increase)
|
||||
self._yearly_capital_costs.loc[year, 'D3050_distribution_systems'] += (
|
||||
reposition_cost_distribution_equipment)
|
||||
else:
|
||||
reposition_cost_energy_storage_equipment = (
|
||||
chapter.item(component).initial_investment[0] * component_sizes[i] * costs_increase)
|
||||
self._yearly_capital_costs.loc[year, 'D3070_storage_systems'] += reposition_cost_energy_storage_equipment
|
||||
if self._configuration.retrofit_scenario == CURRENT_STATUS and pv:
|
||||
if (year % chapter.item('D2010_photovoltaic_system').lifetime) == 0:
|
||||
self._yearly_capital_costs.loc[year, 'D2010_photovoltaic_system'] += (
|
||||
self._surface_pv * chapter.item('D2010_photovoltaic_system').reposition[0] * costs_increase
|
||||
)
|
||||
elif self._configuration.retrofit_scenario in (PV, SYSTEM_RETROFIT_AND_PV,
|
||||
SKIN_RETROFIT_AND_SYSTEM_RETROFIT_AND_PV):
|
||||
if (year % chapter.item('D2010_photovoltaic_system').lifetime) == 0:
|
||||
self._yearly_capital_costs.loc[year, 'D2010_photovoltaic_system'] += (
|
||||
self._surface_pv * chapter.item('D2010_photovoltaic_system').reposition[0] * costs_increase
|
||||
)
|
||||
for (i, component) in enumerate(system_components):
|
||||
if (year % chapter.item(component).lifetime) == 0 and year != (self._configuration.number_of_years - 1):
|
||||
if component_categories[i] == 'generation':
|
||||
reposition_cost_heating_and_cooling_equipment = chapter.item(component).reposition[0] * component_sizes[i] * costs_increase
|
||||
self._yearly_capital_costs.loc[year, 'D3020_heat_and_cooling_generating_systems'] += reposition_cost_heating_and_cooling_equipment
|
||||
elif component_categories[i] == 'dhw':
|
||||
reposition_cost_domestic_hot_water_equipment = chapter.item(component).reposition[0] * component_sizes[i] * costs_increase
|
||||
self._yearly_capital_costs.loc[year, 'D40_dhw'] += reposition_cost_domestic_hot_water_equipment
|
||||
elif component_categories[i] == 'distribution':
|
||||
reposition_cost_distribution_equipment = chapter.item(component).reposition[0] * component_sizes[i] * costs_increase
|
||||
self._yearly_capital_costs.loc[year, 'D3040_distribution_systems'] += reposition_cost_distribution_equipment
|
||||
else:
|
||||
reposition_cost_energy_storage_equipment = chapter.item(component).initial_investment[0] * component_sizes[i] * costs_increase
|
||||
self._yearly_capital_costs.loc[year, 'D3060_storage_systems'] += reposition_cost_energy_storage_equipment
|
||||
|
||||
def system_components(self):
|
||||
system_components = []
|
||||
@ -283,8 +326,11 @@ class CapitalCosts(CostBase):
|
||||
system_components.append(self.boiler_type(generation_system))
|
||||
else:
|
||||
system_components.append('D302010_template_heat')
|
||||
elif cte.HEATING or cte.COOLING in demand_types:
|
||||
component_categories.append('generation')
|
||||
elif cte.HEATING in demand_types:
|
||||
if cte.COOLING in demand_types and generation_system.fuel_type == cte.ELECTRICITY:
|
||||
component_categories.append('multi_generation')
|
||||
else:
|
||||
component_categories.append('heating')
|
||||
sizes.append(installed_capacity)
|
||||
if generation_system.system_type == cte.HEAT_PUMP:
|
||||
item_type = self.heat_pump_type(generation_system)
|
||||
@ -293,11 +339,18 @@ class CapitalCosts(CostBase):
|
||||
item_type = self.boiler_type(generation_system)
|
||||
system_components.append(item_type)
|
||||
else:
|
||||
if cte.COOLING in demand_types and cte.HEATING not in demand_types:
|
||||
if cooling_capacity > heating_capacity:
|
||||
system_components.append('D302090_template_cooling')
|
||||
else:
|
||||
system_components.append('D302010_template_heat')
|
||||
|
||||
elif cte.COOLING in demand_types:
|
||||
component_categories.append('cooling')
|
||||
sizes.append(installed_capacity)
|
||||
if generation_system.system_type == cte.HEAT_PUMP:
|
||||
item_type = self.heat_pump_type(generation_system)
|
||||
system_components.append(item_type)
|
||||
else:
|
||||
system_components.append('D302090_template_cooling')
|
||||
if generation_system.energy_storage_systems is not None:
|
||||
energy_storage_systems = generation_system.energy_storage_systems
|
||||
for storage_system in energy_storage_systems:
|
||||
@ -308,7 +361,7 @@ class CapitalCosts(CostBase):
|
||||
if distribution_systems is not None:
|
||||
for distribution_system in distribution_systems:
|
||||
component_categories.append('distribution')
|
||||
sizes.append(self._building.cooling_peak_load[cte.YEAR][0] / 3.6e6)
|
||||
sizes.append(self._building.cooling_peak_load[cte.YEAR][0] / 1000)
|
||||
system_components.append('D3040_distribution_systems')
|
||||
return system_components, component_categories, sizes
|
||||
|
@ -28,7 +28,8 @@ class Configuration:
|
||||
factories_handler,
|
||||
retrofit_scenario,
|
||||
fuel_type,
|
||||
dictionary
|
||||
dictionary,
|
||||
fuel_tariffs
|
||||
):
|
||||
self._number_of_years = number_of_years
|
||||
self._percentage_credit = percentage_credit
|
||||
@ -45,6 +46,7 @@ class Configuration:
|
||||
self._retrofit_scenario = retrofit_scenario
|
||||
self._fuel_type = fuel_type
|
||||
self._dictionary = dictionary
|
||||
self._fuel_tariffs = fuel_tariffs
|
||||
|
||||
@property
|
||||
def number_of_years(self):
|
||||
@ -227,3 +229,10 @@ class Configuration:
|
||||
Get hub function to cost function dictionary
|
||||
"""
|
||||
return self._dictionary
|
||||
|
||||
@property
|
||||
def fuel_tariffs(self):
|
||||
"""
|
||||
Get fuel tariffs
|
||||
"""
|
||||
return self._fuel_tariffs
|
@ -11,9 +11,13 @@ CURRENT_STATUS = 0
|
||||
SKIN_RETROFIT = 1
|
||||
SYSTEM_RETROFIT_AND_PV = 2
|
||||
SKIN_RETROFIT_AND_SYSTEM_RETROFIT_AND_PV = 3
|
||||
PV = 4
|
||||
SYSTEM_RETROFIT = 5
|
||||
RETROFITTING_SCENARIOS = [
|
||||
CURRENT_STATUS,
|
||||
SKIN_RETROFIT,
|
||||
SYSTEM_RETROFIT_AND_PV,
|
||||
SKIN_RETROFIT_AND_SYSTEM_RETROFIT_AND_PV
|
||||
SKIN_RETROFIT_AND_SYSTEM_RETROFIT_AND_PV,
|
||||
PV,
|
||||
SYSTEM_RETROFIT
|
||||
]
|
@ -5,19 +5,18 @@ Copyright © 2023 Project Coder Guille Gutierrez guillermo.gutierrezmorote@conco
|
||||
Code contributor Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca
|
||||
Code contributor Oriol Gavalda Torrellas oriol.gavalda@concordia.ca
|
||||
"""
|
||||
import datetime
|
||||
|
||||
import pandas as pd
|
||||
import numpy_financial as npf
|
||||
from hub.city_model_structure.building import Building
|
||||
from hub.helpers.dictionaries import Dictionaries
|
||||
from scripts.costs.configuration import Configuration
|
||||
from scripts.costs.capital_costs import CapitalCosts
|
||||
from scripts.costs.end_of_life_costs import EndOfLifeCosts
|
||||
from scripts.costs.total_maintenance_costs import TotalMaintenanceCosts
|
||||
from scripts.costs.total_operational_costs import TotalOperationalCosts
|
||||
from scripts.costs.total_operational_incomes import TotalOperationalIncomes
|
||||
from scripts.costs.constants import CURRENT_STATUS, SKIN_RETROFIT, SYSTEM_RETROFIT_AND_PV, SKIN_RETROFIT_AND_SYSTEM_RETROFIT_AND_PV
|
||||
from costing_package.configuration import Configuration
|
||||
from costing_package.capital_costs import CapitalCosts
|
||||
from costing_package.end_of_life_costs import EndOfLifeCosts
|
||||
from costing_package.total_maintenance_costs import TotalMaintenanceCosts
|
||||
from costing_package.total_operational_costs import TotalOperationalCosts
|
||||
from costing_package.total_operational_incomes import TotalOperationalIncomes
|
||||
from costing_package.constants import CURRENT_STATUS
|
||||
import hub.helpers.constants as cte
|
||||
|
||||
|
||||
@ -40,7 +39,10 @@ class Cost:
|
||||
retrofitting_year_construction=2020,
|
||||
factories_handler='montreal_new',
|
||||
retrofit_scenario=CURRENT_STATUS,
|
||||
dictionary=None):
|
||||
dictionary=None,
|
||||
fuel_tariffs=None):
|
||||
if fuel_tariffs is None:
|
||||
fuel_tariffs = ['Electricity-D', 'Gas-Energir']
|
||||
if dictionary is None:
|
||||
dictionary = Dictionaries().hub_function_to_montreal_custom_costs_function
|
||||
self._building = building
|
||||
@ -57,7 +59,8 @@ class Cost:
|
||||
factories_handler,
|
||||
retrofit_scenario,
|
||||
fuel_type,
|
||||
dictionary)
|
||||
dictionary,
|
||||
fuel_tariffs)
|
||||
|
||||
@property
|
||||
def building(self) -> Building:
|
||||
@ -89,12 +92,13 @@ class Cost:
|
||||
global_capital_costs['B1010_superstructure']
|
||||
)
|
||||
df_capital_costs_systems = (
|
||||
global_capital_costs['D3020_heat_and_cooling_generating_systems'] +
|
||||
global_capital_costs['D3040_distribution_systems'] +
|
||||
global_capital_costs['D3050_other_hvac_ahu'] +
|
||||
global_capital_costs['D3060_storage_systems'] +
|
||||
global_capital_costs['D3020_simultaneous_heat_and_cooling_generating_systems'] +
|
||||
global_capital_costs['D3030_heating_systems'] +
|
||||
global_capital_costs['D3040_cooling_systems'] +
|
||||
global_capital_costs['D3050_distribution_systems'] +
|
||||
global_capital_costs['D3060_other_hvac_ahu'] +
|
||||
global_capital_costs['D3070_storage_systems'] +
|
||||
global_capital_costs['D40_dhw'] +
|
||||
global_capital_costs['D5020_lighting_and_branch_wiring'] +
|
||||
global_capital_costs['D2010_photovoltaic_system']
|
||||
)
|
||||
|
@ -8,7 +8,7 @@ Code contributor Oriol Gavalda Torrellas oriol.gavalda@concordia.ca
|
||||
|
||||
from hub.city_model_structure.building import Building
|
||||
|
||||
from scripts.costs.configuration import Configuration
|
||||
from costing_package.configuration import Configuration
|
||||
|
||||
|
||||
class CostBase:
|
@ -9,8 +9,8 @@ import math
|
||||
import pandas as pd
|
||||
from hub.city_model_structure.building import Building
|
||||
|
||||
from scripts.costs.configuration import Configuration
|
||||
from scripts.costs.cost_base import CostBase
|
||||
from costing_package.configuration import Configuration
|
||||
from costing_package.cost_base import CostBase
|
||||
|
||||
|
||||
class EndOfLifeCosts(CostBase):
|
@ -45,7 +45,7 @@ class PeakLoad:
|
||||
conditioning_peak[i] = self._building.heating_peak_load[cte.MONTH][i] * heating
|
||||
else:
|
||||
conditioning_peak[i] = self._building.cooling_peak_load[cte.MONTH][i] * cooling
|
||||
monthly_electricity_peak[i] += 0.8 * conditioning_peak[i] / 3600
|
||||
monthly_electricity_peak[i] += 0.8 * conditioning_peak[i]
|
||||
|
||||
electricity_peak_load_results = pd.DataFrame(
|
||||
monthly_electricity_peak,
|
138
costing_package/total_maintenance_costs.py
Normal file
138
costing_package/total_maintenance_costs.py
Normal file
@ -0,0 +1,138 @@
|
||||
"""
|
||||
Total maintenance costs module
|
||||
SPDX - License - Identifier: LGPL - 3.0 - or -later
|
||||
Copyright © 2023 Project Coder Guille Gutierrez guillermo.gutierrezmorote@concordia.ca
|
||||
Code contributor Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca
|
||||
Code contributor Oriol Gavalda Torrellas oriol.gavalda@concordia.ca
|
||||
"""
|
||||
import math
|
||||
import pandas as pd
|
||||
from hub.city_model_structure.building import Building
|
||||
import hub.helpers.constants as cte
|
||||
|
||||
from costing_package.configuration import Configuration
|
||||
from costing_package.cost_base import CostBase
|
||||
|
||||
|
||||
class TotalMaintenanceCosts(CostBase):
|
||||
"""
|
||||
Total maintenance costs class
|
||||
"""
|
||||
def __init__(self, building: Building, configuration: Configuration):
|
||||
super().__init__(building, configuration)
|
||||
self._yearly_maintenance_costs = pd.DataFrame(
|
||||
index=self._rng,
|
||||
columns=[
|
||||
'Heating_maintenance',
|
||||
'Cooling_maintenance',
|
||||
'DHW_maintenance',
|
||||
'PV_maintenance'
|
||||
],
|
||||
dtype='float'
|
||||
)
|
||||
|
||||
def calculate(self) -> pd.DataFrame:
|
||||
"""
|
||||
Calculate total maintenance costs
|
||||
:return: pd.DataFrame
|
||||
"""
|
||||
building = self._building
|
||||
archetype = self._archetype
|
||||
# todo: change area pv when the variable exists
|
||||
roof_area = 0
|
||||
surface_pv = 0
|
||||
for roof in self._building.roofs:
|
||||
if roof.installed_solar_collector_area is not None:
|
||||
surface_pv += roof.installed_solar_collector_area
|
||||
else:
|
||||
surface_pv = roof_area * 0.5
|
||||
|
||||
energy_systems = building.energy_systems
|
||||
maintenance_heating_0 = 0
|
||||
maintenance_cooling_0 = 0
|
||||
maintenance_dhw_0 = 0
|
||||
heating_equipments = {}
|
||||
cooling_equipments = {}
|
||||
dhw_equipments = {}
|
||||
for energy_system in energy_systems:
|
||||
if cte.COOLING in energy_system.demand_types:
|
||||
for generation_system in energy_system.generation_systems:
|
||||
if generation_system.fuel_type == cte.ELECTRICITY:
|
||||
if generation_system.system_type == cte.HEAT_PUMP and generation_system.source_medium == cte.AIR:
|
||||
cooling_equipments['air_source_heat_pump'] = generation_system.nominal_cooling_output / 1000
|
||||
elif generation_system.system_type == cte.HEAT_PUMP and generation_system.source_medium == cte.GROUND:
|
||||
cooling_equipments['ground_source_heat_pump'] = generation_system.nominal_cooling_output / 1000
|
||||
elif generation_system.system_type == cte.HEAT_PUMP and generation_system.source_medium == cte.WATER:
|
||||
cooling_equipments['water_source_heat_pump'] = generation_system.nominal_cooling_output / 1000
|
||||
else:
|
||||
cooling_equipments['general_cooling_equipment'] = generation_system.nominal_cooling_output / 1000
|
||||
if cte.HEATING in energy_system.demand_types:
|
||||
for generation_system in energy_system.generation_systems:
|
||||
if generation_system.system_type == cte.HEAT_PUMP and generation_system.source_medium == cte.AIR:
|
||||
heating_equipments['air_source_heat_pump'] = generation_system.nominal_heat_output / 1000
|
||||
elif generation_system.system_type == cte.HEAT_PUMP and generation_system.source_medium == cte.GROUND:
|
||||
heating_equipments['ground_source_heat_pump'] = generation_system.nominal_heat_output / 1000
|
||||
elif generation_system.system_type == cte.HEAT_PUMP and generation_system.source_medium == cte.WATER:
|
||||
heating_equipments['water_source_heat_pump'] = generation_system.nominal_heat_output / 1000
|
||||
elif generation_system.system_type == cte.BOILER and generation_system.fuel_type == cte.GAS:
|
||||
heating_equipments['gas_boiler'] = generation_system.nominal_heat_output / 1000
|
||||
elif generation_system.system_type == cte.BOILER and generation_system.fuel_type == cte.ELECTRICITY:
|
||||
heating_equipments['electric_boiler'] = generation_system.nominal_heat_output / 1000
|
||||
else:
|
||||
heating_equipments['general_heating_equipment'] = generation_system.nominal_heat_output / 1000
|
||||
if cte.DOMESTIC_HOT_WATER in energy_system.demand_types and cte.HEATING not in energy_system.demand_types:
|
||||
for generation_system in energy_system.generation_systems:
|
||||
if generation_system.system_type == cte.HEAT_PUMP and generation_system.source_medium == cte.AIR:
|
||||
dhw_equipments['air_source_heat_pump'] = generation_system.nominal_heat_output / 1000
|
||||
elif generation_system.system_type == cte.HEAT_PUMP and generation_system.source_medium == cte.GROUND:
|
||||
dhw_equipments['ground_source_heat_pump'] = generation_system.nominal_heat_output / 1000
|
||||
elif generation_system.system_type == cte.HEAT_PUMP and generation_system.source_medium == cte.WATER:
|
||||
dhw_equipments['water_source_heat_pump'] = generation_system.nominal_heat_output / 1000
|
||||
elif generation_system.system_type == cte.BOILER and generation_system.fuel_type == cte.GAS:
|
||||
dhw_equipments['gas_boiler'] = generation_system.nominal_heat_output / 1000
|
||||
elif generation_system.system_type == cte.BOILER and generation_system.fuel_type == cte.ELECTRICITY:
|
||||
dhw_equipments['electric_boiler'] = generation_system.nominal_heat_output / 1000
|
||||
else:
|
||||
dhw_equipments['general_heating_equipment'] = generation_system.nominal_heat_output / 1000
|
||||
|
||||
|
||||
for heating_equipment in heating_equipments:
|
||||
component = self.search_hvac_equipment(heating_equipment)
|
||||
maintenance_cost = component.maintenance[0]
|
||||
maintenance_heating_0 += (heating_equipments[heating_equipment] * maintenance_cost)
|
||||
|
||||
for cooling_equipment in cooling_equipments:
|
||||
component = self.search_hvac_equipment(cooling_equipment)
|
||||
maintenance_cost = component.maintenance[0]
|
||||
maintenance_cooling_0 += (cooling_equipments[cooling_equipment] * maintenance_cost)
|
||||
|
||||
for dhw_equipment in dhw_equipments:
|
||||
component = self.search_hvac_equipment(dhw_equipment)
|
||||
maintenance_cost = component.maintenance[0]
|
||||
maintenance_dhw_0 += (dhw_equipments[dhw_equipment] * maintenance_cost)
|
||||
|
||||
maintenance_pv_0 = surface_pv * archetype.operational_cost.maintenance_pv
|
||||
|
||||
for year in range(1, self._configuration.number_of_years + 1):
|
||||
costs_increase = math.pow(1 + self._configuration.consumer_price_index, year)
|
||||
self._yearly_maintenance_costs.loc[year, 'Heating_maintenance'] = (
|
||||
maintenance_heating_0 * costs_increase
|
||||
)
|
||||
self._yearly_maintenance_costs.loc[year, 'Cooling_maintenance'] = (
|
||||
maintenance_cooling_0 * costs_increase
|
||||
)
|
||||
self._yearly_maintenance_costs.loc[year, 'DHW_maintenance'] = (
|
||||
maintenance_dhw_0 * costs_increase
|
||||
)
|
||||
self._yearly_maintenance_costs.loc[year, 'PV_maintenance'] = (
|
||||
maintenance_pv_0 * costs_increase
|
||||
)
|
||||
self._yearly_maintenance_costs.fillna(0, inplace=True)
|
||||
return self._yearly_maintenance_costs
|
||||
|
||||
def search_hvac_equipment(self, equipment_type):
|
||||
for component in self._archetype.operational_cost.maintenance_hvac:
|
||||
if component.type == equipment_type:
|
||||
return component
|
||||
|
||||
|
237
costing_package/total_operational_costs.py
Normal file
237
costing_package/total_operational_costs.py
Normal file
@ -0,0 +1,237 @@
|
||||
"""
|
||||
Total operational costs module
|
||||
SPDX - License - Identifier: LGPL - 3.0 - or -later
|
||||
Copyright © 2024 Project Coder Saeed Ranjbar saeed.ranjbar@mail.concordia.ca
|
||||
Code contributor Oriol Gavalda Torrellas oriol.gavalda@concordia.ca
|
||||
"""
|
||||
import math
|
||||
import pandas as pd
|
||||
|
||||
from hub.city_model_structure.building import Building
|
||||
import hub.helpers.constants as cte
|
||||
|
||||
from costing_package.configuration import Configuration
|
||||
from costing_package.cost_base import CostBase
|
||||
from costing_package.peak_load import PeakLoad
|
||||
|
||||
|
||||
class TotalOperationalCosts(CostBase):
|
||||
"""
|
||||
Total Operational costs class
|
||||
"""
|
||||
|
||||
def __init__(self, building: Building, configuration: Configuration):
|
||||
super().__init__(building, configuration)
|
||||
columns_list = self.columns()
|
||||
self._yearly_operational_costs = pd.DataFrame(
|
||||
index=self._rng,
|
||||
columns=columns_list,
|
||||
dtype='float'
|
||||
)
|
||||
|
||||
def calculate(self) -> pd.DataFrame:
|
||||
"""
|
||||
Calculate total operational costs
|
||||
:return: pd.DataFrame
|
||||
"""
|
||||
building = self._building
|
||||
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.energy_consumption_breakdown[cte.ELECTRICITY].values()) / 3600
|
||||
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
|
||||
for system_fuel in self._configuration.fuel_type:
|
||||
fuel = None
|
||||
for fuel_tariff in self._configuration.fuel_tariffs:
|
||||
if system_fuel in fuel_tariff:
|
||||
fuel = self.search_fuel(system_fuel, fuel_tariff)
|
||||
if fuel.type == cte.ELECTRICITY:
|
||||
if fuel.variable.rate_type == 'fixed':
|
||||
variable_electricity_cost_year_0 = (
|
||||
total_electricity_consumption * float(fuel.variable.values[0]) / 1000
|
||||
)
|
||||
else:
|
||||
hourly_electricity_consumption = self.hourly_fuel_consumption_profile(fuel.type)
|
||||
hourly_electricity_price_profile = fuel.variable.values * len(hourly_electricity_consumption)
|
||||
hourly_electricity_price = [hourly_electricity_consumption[i] / 1000 * hourly_electricity_price_profile[i]
|
||||
for i in range(len(hourly_electricity_consumption))]
|
||||
variable_electricity_cost_year_0 = sum(hourly_electricity_price)
|
||||
peak_electricity_cost_year_0 = peak_electricity_demand * fuel.fixed_power * 12
|
||||
monthly_electricity_cost_year_0 = fuel.fixed_monthly * 12 * factor
|
||||
for year in range(1, self._configuration.number_of_years + 1):
|
||||
price_increase_electricity = math.pow(1 + self._configuration.electricity_price_index, year)
|
||||
price_increase_peak_electricity = math.pow(1 + self._configuration.electricity_peak_index, year)
|
||||
self._yearly_operational_costs.at[year, 'Fixed Costs Electricity Peak'] = (
|
||||
peak_electricity_cost_year_0 * price_increase_peak_electricity
|
||||
)
|
||||
self._yearly_operational_costs.at[year, 'Fixed Costs Electricity Monthly'] = (
|
||||
monthly_electricity_cost_year_0 * price_increase_peak_electricity
|
||||
)
|
||||
if not isinstance(variable_electricity_cost_year_0, pd.DataFrame):
|
||||
variable_costs_electricity = variable_electricity_cost_year_0 * price_increase_electricity
|
||||
else:
|
||||
variable_costs_electricity = float(variable_electricity_cost_year_0.iloc[0] * price_increase_electricity)
|
||||
self._yearly_operational_costs.at[year, 'Variable Costs Electricity'] = (
|
||||
variable_costs_electricity
|
||||
)
|
||||
else:
|
||||
fuel_fixed_cost = fuel.fixed_monthly * 12 * factor
|
||||
if fuel.type == cte.BIOMASS:
|
||||
conversion_factor = 1
|
||||
else:
|
||||
conversion_factor = fuel.density[0]
|
||||
if fuel.variable.rate_type == 'fixed':
|
||||
variable_cost_fuel = (
|
||||
(sum(fuel_consumption_breakdown[fuel.type].values()) / (
|
||||
1e6 * fuel.lower_heating_value[0] * conversion_factor)) * fuel.variable.values[0])
|
||||
|
||||
else:
|
||||
hourly_fuel_consumption = self.hourly_fuel_consumption_profile(fuel.type)
|
||||
hourly_fuel_price_profile = fuel.variable.values * len(hourly_fuel_consumption)
|
||||
hourly_fuel_price = [hourly_fuel_consumption[i] / (
|
||||
1e6 * fuel.lower_heating_value[0] * conversion_factor) * hourly_fuel_price_profile[i]
|
||||
for i in range(len(hourly_fuel_consumption))]
|
||||
variable_cost_fuel = sum(hourly_fuel_price)
|
||||
|
||||
for year in range(1, self._configuration.number_of_years + 1):
|
||||
price_increase_gas = math.pow(1 + self._configuration.gas_price_index, year)
|
||||
self._yearly_operational_costs.at[year, f'Fixed Costs {fuel.type}'] = fuel_fixed_cost * price_increase_gas
|
||||
self._yearly_operational_costs.at[year, f'Variable Costs {fuel.type}'] = (
|
||||
variable_cost_fuel * price_increase_gas)
|
||||
self._yearly_operational_costs.fillna(0, inplace=True)
|
||||
|
||||
return self._yearly_operational_costs
|
||||
|
||||
def columns(self):
|
||||
columns_list = []
|
||||
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')
|
||||
columns_list.append('Fixed Costs Electricity Monthly')
|
||||
columns_list.append('Variable Costs Electricity')
|
||||
else:
|
||||
columns_list.append(f'Fixed Costs {fuel}')
|
||||
columns_list.append(f'Variable Costs {fuel}')
|
||||
|
||||
return columns_list
|
||||
|
||||
def search_fuel(self, system_fuel, tariff):
|
||||
fuels = self._archetype.operational_cost.fuels
|
||||
for fuel in fuels:
|
||||
if system_fuel == fuel.type and tariff == fuel.variable.name:
|
||||
return fuel
|
||||
raise KeyError(f'fuel {system_fuel} with {tariff} tariff not found')
|
||||
|
||||
|
||||
def hourly_fuel_consumption_profile(self, fuel_type):
|
||||
hourly_fuel_consumption = []
|
||||
energy_systems = self._building.energy_systems
|
||||
if fuel_type == cte.ELECTRICITY:
|
||||
appliance = self._building.appliances_electrical_demand[cte.HOUR]
|
||||
lighting = self._building.lighting_electrical_demand[cte.HOUR]
|
||||
elec_heating = 0
|
||||
elec_cooling = 0
|
||||
elec_dhw = 0
|
||||
if cte.HEATING in self._building.energy_consumption_breakdown[cte.ELECTRICITY]:
|
||||
elec_heating = 1
|
||||
if cte.COOLING in self._building.energy_consumption_breakdown[cte.ELECTRICITY]:
|
||||
elec_cooling = 1
|
||||
if cte.DOMESTIC_HOT_WATER in self._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 self._building.heating_consumption[cte.HOUR]]
|
||||
else:
|
||||
heating = self._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 self._building.domestic_hot_water_consumption[cte.HOUR]]
|
||||
else:
|
||||
dhw = self._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 self._building.cooling_consumption[cte.HOUR]]
|
||||
else:
|
||||
cooling = self._building.cooling_consumption[cte.HOUR]
|
||||
|
||||
for i in range(len(self._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_fuel_consumption.append(hourly)
|
||||
else:
|
||||
heating = None
|
||||
dhw = None
|
||||
if cte.HEATING in self._building.energy_consumption_breakdown[fuel_type]:
|
||||
for energy_system in energy_systems:
|
||||
if cte.HEATING in energy_system.demand_types:
|
||||
for generation_system in energy_system.generation_systems:
|
||||
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 self._building.heating_consumption[cte.HOUR]]
|
||||
else:
|
||||
heating = self._building.heating_consumption[cte.HOUR]
|
||||
if cte.DOMESTIC_HOT_WATER in self._building.energy_consumption_breakdown[fuel_type]:
|
||||
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 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 self._building.domestic_hot_water_consumption[cte.HOUR]]
|
||||
else:
|
||||
dhw = self._building.domestic_hot_water_consumption[cte.HOUR]
|
||||
|
||||
for i in range(len(self._building.heating_demand[cte.HOUR])):
|
||||
hourly = 0
|
||||
if heating is not None:
|
||||
hourly += heating[i] / 3600
|
||||
if dhw is not None:
|
||||
hourly += dhw[i] / 3600
|
||||
hourly_fuel_consumption.append(hourly)
|
||||
return hourly_fuel_consumption
|
||||
|
||||
|
||||
|
@ -10,8 +10,8 @@ import pandas as pd
|
||||
from hub.city_model_structure.building import Building
|
||||
import hub.helpers.constants as cte
|
||||
|
||||
from scripts.costs.configuration import Configuration
|
||||
from scripts.costs.cost_base import CostBase
|
||||
from costing_package.configuration import Configuration
|
||||
from costing_package.cost_base import CostBase
|
||||
|
||||
|
||||
class TotalOperationalIncomes(CostBase):
|
||||
@ -33,14 +33,12 @@ class TotalOperationalIncomes(CostBase):
|
||||
onsite_electricity_production = 0
|
||||
else:
|
||||
onsite_electricity_production = building.onsite_electrical_production[cte.YEAR][0]
|
||||
|
||||
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 / 3.6e6) * price_export * price_increase_electricity
|
||||
)
|
||||
|
||||
self._yearly_operational_incomes.fillna(0, inplace=True)
|
||||
return self._yearly_operational_incomes
|
||||
return self._yearly_operational_incomes
|
111
district_heating_network.py
Normal file
111
district_heating_network.py
Normal file
@ -0,0 +1,111 @@
|
||||
from scripts.district_heating_network.directory_manager import DirectoryManager
|
||||
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, 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, CURRENT_STATUS
|
||||
import hub.helpers.constants as cte
|
||||
from hub.exports.exports_factory import ExportsFactory
|
||||
from scripts.pv_feasibility import pv_feasibility
|
||||
import matplotlib.pyplot as plt
|
||||
import numpy as np
|
||||
from scripts.district_heating_network.district_heating_network_creator import DistrictHeatingNetworkCreator
|
||||
from scripts.district_heating_network.district_heating_factory import DistrictHeatingFactory
|
||||
import json
|
||||
|
||||
#%% --------------------------------------------------------------------------------------------------------------------
|
||||
# Manage File Path
|
||||
base_path = "./"
|
||||
dir_manager = DirectoryManager(base_path)
|
||||
|
||||
# Input files directory
|
||||
input_files_path = dir_manager.create_directory('input_files')
|
||||
geojson_file_path = input_files_path / 'output_buildings.geojson'
|
||||
pipe_data_file = input_files_path / 'pipe_data.json'
|
||||
|
||||
# Output files directory
|
||||
output_path = dir_manager.create_directory('out_files')
|
||||
|
||||
# Subdirectories for output files
|
||||
energy_plus_output_path = dir_manager.create_directory('out_files/energy_plus_outputs')
|
||||
simulation_results_path = dir_manager.create_directory('out_files/simulation_results')
|
||||
sra_output_path = dir_manager.create_directory('out_files/sra_outputs')
|
||||
cost_analysis_output_path = dir_manager.create_directory('out_files/cost_analysis')
|
||||
|
||||
#%% --------------------------------------------------------------------------------------------------------------------
|
||||
# Area Under Study
|
||||
location = [45.4934614681437, -73.57982834742518]
|
||||
|
||||
#%% --------------------------------------------------------------------------------------------------------------------
|
||||
# Create geojson of buildings
|
||||
process_geojson(x=location[1], y=location[0], diff=0.001)
|
||||
|
||||
#%% --------------------------------------------------------------------------------------------------------------------
|
||||
# Create ciry and run energyplus workflow
|
||||
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
|
||||
ConstructionFactory('nrcan', city).enrich()
|
||||
UsageFactory('nrcan', city).enrich()
|
||||
WeatherFactory('epw', city).enrich()
|
||||
|
||||
# SRA
|
||||
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()
|
||||
|
||||
# EP Workflow
|
||||
energy_plus_workflow(city, energy_plus_output_path)
|
||||
|
||||
#%% --------------------------------------------------------------------------------------------------------------------
|
||||
# District Heating Network Creator
|
||||
central_plant_locations = [(-73.57812571080625, 45.49499447346277)] # Add at least one location
|
||||
|
||||
roads_file = "./input_files/roads.json"
|
||||
|
||||
dhn_creator = DistrictHeatingNetworkCreator(geojson_file_path, roads_file, central_plant_locations)
|
||||
|
||||
network_graph = dhn_creator.run()
|
||||
|
||||
#%% --------------------------------------------------------------------------------------------------------------------
|
||||
# Pipe and pump sizing
|
||||
|
||||
with open(pipe_data_file, 'r') as f:
|
||||
pipe_data = json.load(f)
|
||||
|
||||
factory = DistrictHeatingFactory(
|
||||
city=city,
|
||||
graph=network_graph,
|
||||
supply_temperature=80 + 273, # in Kelvin
|
||||
return_temperature=60 + 273, # in Kelvin
|
||||
simultaneity_factor=0.9
|
||||
)
|
||||
|
||||
factory.enrich()
|
||||
factory.sizing()
|
||||
factory.calculate_diameters_and_costs(pipe_data)
|
||||
pipe_groups, total_cost = factory.analyze_costs()
|
||||
|
||||
# Save the pipe groups with total costs to a CSV file
|
||||
factory.save_pipe_groups_to_csv('pipe_groups.csv')
|
||||
|
||||
#%% --------------------------------------------------------------------------------------------------------------------
|
||||
|
@ -0,0 +1,116 @@
|
||||
import csv
|
||||
|
||||
from energy_system_modelling_package.energy_system_modelling_factories.hvac_dhw_systems_simulation_models.heat_pump_boiler_tes_heating import \
|
||||
HeatPumpBoilerTesHeating
|
||||
from energy_system_modelling_package.energy_system_modelling_factories.hvac_dhw_systems_simulation_models.heat_pump_cooling import \
|
||||
HeatPumpCooling
|
||||
from energy_system_modelling_package.energy_system_modelling_factories.hvac_dhw_systems_simulation_models.domestic_hot_water_heat_pump_with_tes import \
|
||||
DomesticHotWaterHeatPumpTes
|
||||
import hub.helpers.constants as cte
|
||||
from hub.helpers.monthly_values import MonthlyValues
|
||||
|
||||
|
||||
class ArchetypeCluster1:
|
||||
def __init__(self, building, dt, output_path, csv_output=True):
|
||||
self.building = building
|
||||
self.dt = dt
|
||||
self.output_path = output_path
|
||||
self.csv_output = csv_output
|
||||
self.heating_results, self.building_heating_hourly_consumption = self.heating_system_simulation()
|
||||
self.cooling_results, self.total_cooling_consumption_hourly = self.cooling_system_simulation()
|
||||
self.dhw_results, self.total_dhw_consumption_hourly = self.dhw_system_simulation()
|
||||
|
||||
def heating_system_simulation(self):
|
||||
building_heating_hourly_consumption = []
|
||||
boiler = self.building.energy_systems[1].generation_systems[0]
|
||||
hp = self.building.energy_systems[1].generation_systems[1]
|
||||
tes = self.building.energy_systems[1].generation_systems[0].energy_storage_systems[0]
|
||||
heating_demand_joules = self.building.heating_demand[cte.HOUR]
|
||||
heating_peak_load_watts = self.building.heating_peak_load[cte.YEAR][0]
|
||||
upper_limit_tes_heating = 55
|
||||
outdoor_temperature = self.building.external_temperature[cte.HOUR]
|
||||
results = HeatPumpBoilerTesHeating(hp=hp,
|
||||
boiler=boiler,
|
||||
tes=tes,
|
||||
hourly_heating_demand_joules=heating_demand_joules,
|
||||
heating_peak_load_watts=heating_peak_load_watts,
|
||||
upper_limit_tes=upper_limit_tes_heating,
|
||||
outdoor_temperature=outdoor_temperature,
|
||||
dt=self.dt).simulation()
|
||||
number_of_ts = int(cte.HOUR_TO_SECONDS/self.dt)
|
||||
heating_consumption_joules = [(x * cte.WATTS_HOUR_TO_JULES) / number_of_ts for x in
|
||||
results['Total Heating Power Consumption (W)']]
|
||||
heating_consumption = 0
|
||||
for i in range(1, len(heating_consumption_joules)):
|
||||
heating_consumption += heating_consumption_joules[i]
|
||||
if (i - 1) % number_of_ts == 0:
|
||||
building_heating_hourly_consumption.append(heating_consumption)
|
||||
heating_consumption = 0
|
||||
return results, building_heating_hourly_consumption
|
||||
|
||||
def cooling_system_simulation(self):
|
||||
hp = self.building.energy_systems[2].generation_systems[0]
|
||||
cooling_demand_joules = self.building.cooling_demand[cte.HOUR]
|
||||
cooling_peak_load = self.building.cooling_peak_load[cte.YEAR][0]
|
||||
cutoff_temperature = 13
|
||||
outdoor_temperature = self.building.external_temperature[cte.HOUR]
|
||||
results = HeatPumpCooling(hp=hp,
|
||||
hourly_cooling_demand_joules=cooling_demand_joules,
|
||||
cooling_peak_load_watts=cooling_peak_load,
|
||||
cutoff_temperature=cutoff_temperature,
|
||||
outdoor_temperature=outdoor_temperature,
|
||||
dt=self.dt).simulation()
|
||||
building_cooling_hourly_consumption = hp.energy_consumption[cte.COOLING][cte.HOUR]
|
||||
return results, building_cooling_hourly_consumption
|
||||
|
||||
def dhw_system_simulation(self):
|
||||
building_dhw_hourly_consumption = []
|
||||
hp = self.building.energy_systems[-1].generation_systems[0]
|
||||
tes = self.building.energy_systems[-1].generation_systems[0].energy_storage_systems[0]
|
||||
dhw_demand_joules = self.building.domestic_hot_water_heat_demand[cte.HOUR]
|
||||
upper_limit_tes = 65
|
||||
outdoor_temperature = self.building.external_temperature[cte.HOUR]
|
||||
results = DomesticHotWaterHeatPumpTes(hp=hp,
|
||||
tes=tes,
|
||||
hourly_dhw_demand_joules=dhw_demand_joules,
|
||||
upper_limit_tes=upper_limit_tes,
|
||||
outdoor_temperature=outdoor_temperature,
|
||||
dt=self.dt).simulation()
|
||||
number_of_ts = int(cte.HOUR_TO_SECONDS/self.dt)
|
||||
dhw_consumption_joules = [(x * cte.WATTS_HOUR_TO_JULES) / number_of_ts for x in
|
||||
results['Total DHW Power Consumption (W)']]
|
||||
dhw_consumption = 0
|
||||
for i in range(1, len(dhw_consumption_joules)):
|
||||
dhw_consumption += dhw_consumption_joules[i]
|
||||
if (i - 1) % number_of_ts == 0:
|
||||
building_dhw_hourly_consumption.append(dhw_consumption)
|
||||
dhw_consumption = 0
|
||||
return results, building_dhw_hourly_consumption
|
||||
|
||||
|
||||
def enrich_building(self):
|
||||
results = self.heating_results | self.cooling_results | self.dhw_results
|
||||
self.building.heating_consumption[cte.HOUR] = self.building_heating_hourly_consumption
|
||||
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.building.cooling_consumption[cte.HOUR] = self.total_cooling_consumption_hourly
|
||||
self.building.cooling_consumption[cte.MONTH] = (
|
||||
MonthlyValues.get_total_month(self.building.cooling_consumption[cte.HOUR]))
|
||||
self.building.cooling_consumption[cte.YEAR] = [sum(self.building.cooling_consumption[cte.MONTH])]
|
||||
self.building.domestic_hot_water_consumption[cte.HOUR] = self.total_dhw_consumption_hourly
|
||||
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])]
|
||||
if self.csv_output:
|
||||
file_name = f'energy_system_simulation_results_{self.building.name}.csv'
|
||||
with open(self.output_path / file_name, 'w', newline='') as csvfile:
|
||||
output_file = csv.writer(csvfile)
|
||||
# Write header
|
||||
output_file.writerow(results.keys())
|
||||
# Write data
|
||||
output_file.writerows(zip(*results.values()))
|
||||
|
||||
|
||||
|
@ -0,0 +1,52 @@
|
||||
"""
|
||||
EnergySystemSizingSimulationFactory retrieve the energy system archetype sizing and simulation module
|
||||
SPDX - License - Identifier: LGPL - 3.0 - or -later
|
||||
Copyright © 2024 Concordia CERC group
|
||||
Project Coder Saeed Ranjbar saeed.ranjbar@mail.concordia.ca
|
||||
"""
|
||||
|
||||
from energy_system_modelling_package.energy_system_modelling_factories.system_sizing_methods.peak_load_sizing import \
|
||||
PeakLoadSizing
|
||||
from energy_system_modelling_package.energy_system_modelling_factories.system_sizing_methods.heuristic_sizing import \
|
||||
HeuristicSizing
|
||||
|
||||
|
||||
class EnergySystemsSizingFactory:
|
||||
"""
|
||||
EnergySystemsFactory class
|
||||
"""
|
||||
|
||||
def __init__(self, handler, city):
|
||||
self._handler = '_' + handler.lower()
|
||||
self._city = city
|
||||
|
||||
def _peak_load_sizing(self):
|
||||
"""
|
||||
Size Energy Systems based on a Load Matching method using the heating, cooling, and dhw peak loads
|
||||
"""
|
||||
PeakLoadSizing(self._city).enrich_buildings()
|
||||
self._city.level_of_detail.energy_systems = 1
|
||||
for building in self._city.buildings:
|
||||
building.level_of_detail.energy_systems = 1
|
||||
|
||||
def _heurisitc_sizing(self):
|
||||
"""
|
||||
Size Energy Systems using a Single or Multi Objective GA
|
||||
"""
|
||||
HeuristicSizing(self._city).enrich_buildings()
|
||||
self._city.level_of_detail.energy_systems = 1
|
||||
for building in self._city.buildings:
|
||||
building.level_of_detail.energy_systems = 1
|
||||
|
||||
def _district_heating_cooling_sizing(self):
|
||||
"""
|
||||
Size District Heating and Cooling Network
|
||||
"""
|
||||
pass
|
||||
|
||||
def enrich(self):
|
||||
"""
|
||||
Enrich the city given to the class using the class given handler
|
||||
:return: None
|
||||
"""
|
||||
return getattr(self, self._handler, lambda: None)()
|
@ -0,0 +1,118 @@
|
||||
import hub.helpers.constants as cte
|
||||
from energy_system_modelling_package.energy_system_modelling_factories.hvac_dhw_systems_simulation_models.heat_pump_characteristics import HeatPump
|
||||
from energy_system_modelling_package.energy_system_modelling_factories.hvac_dhw_systems_simulation_models.thermal_storage_tank import StorageTank
|
||||
from hub.helpers.monthly_values import MonthlyValues
|
||||
|
||||
|
||||
class DomesticHotWaterHeatPumpTes:
|
||||
def __init__(self, hp, tes, hourly_dhw_demand_joules, upper_limit_tes,
|
||||
outdoor_temperature, dt=900):
|
||||
self.hp = hp
|
||||
self.tes = tes
|
||||
self.dhw_demand = [demand / cte.WATTS_HOUR_TO_JULES for demand in hourly_dhw_demand_joules]
|
||||
self.upper_limit_tes = upper_limit_tes
|
||||
self.hp_characteristics = HeatPump(self.hp, outdoor_temperature)
|
||||
self.t_out = outdoor_temperature
|
||||
self.dt = dt
|
||||
self.results = {}
|
||||
|
||||
def simulation(self):
|
||||
hp = self.hp
|
||||
tes = self.tes
|
||||
heating_coil_nominal_output = 0
|
||||
if tes.heating_coil_capacity is not None:
|
||||
heating_coil_nominal_output = float(tes.heating_coil_capacity)
|
||||
storage_tank = StorageTank(volume=float(tes.volume),
|
||||
height=float(tes.height),
|
||||
material_layers=tes.layers,
|
||||
heating_coil_capacity=heating_coil_nominal_output)
|
||||
|
||||
hp_delta_t = 8
|
||||
number_of_ts = int(cte.HOUR_TO_SECONDS / self.dt)
|
||||
source_temperature_hourly = self.hp_characteristics.hp_source_temperature()
|
||||
source_temperature = [x for x in source_temperature_hourly for _ in range(number_of_ts)]
|
||||
demand = [x for x in self.dhw_demand for _ in range(number_of_ts)]
|
||||
variable_names = ["t_sup_hp", "t_tank", "m_ch", "m_dis", "q_hp", "q_coil", "hp_cop",
|
||||
"hp_electricity", "available hot water (m3)", "refill flow rate (kg/s)", "total_consumption"]
|
||||
num_hours = len(demand)
|
||||
variables = {name: [0] * num_hours for name in variable_names}
|
||||
(t_sup_hp, t_tank, m_ch, m_dis, m_refill, q_hp, q_coil, hp_cop, hp_electricity, v_dhw, total_consumption) = \
|
||||
[variables[name] for name in variable_names]
|
||||
freshwater_temperature = 18
|
||||
t_tank[0] = 70
|
||||
for i in range(len(demand) - 1):
|
||||
delta_t_demand = demand[i] * (self.dt / (cte.WATER_DENSITY * cte.WATER_HEAT_CAPACITY *
|
||||
storage_tank.volume))
|
||||
if t_tank[i] < self.upper_limit_tes:
|
||||
q_hp[i] = hp.nominal_heat_output
|
||||
delta_t_hp = q_hp[i] * (self.dt / (cte.WATER_DENSITY * cte.WATER_HEAT_CAPACITY * storage_tank.volume))
|
||||
if demand[i] > 0:
|
||||
dhw_needed = (demand[i] * cte.HOUR_TO_SECONDS) / (cte.WATER_HEAT_CAPACITY * t_tank[i] * cte.WATER_DENSITY)
|
||||
m_dis[i] = dhw_needed * cte.WATER_DENSITY / cte.HOUR_TO_SECONDS
|
||||
m_refill[i] = m_dis[i]
|
||||
delta_t_freshwater = m_refill[i] * (t_tank[i] - freshwater_temperature) * (self.dt / (storage_tank.volume *
|
||||
cte.WATER_DENSITY))
|
||||
if t_tank[i] < 60:
|
||||
q_coil[i] = float(storage_tank.heating_coil_capacity)
|
||||
delta_t_coil = q_coil[i] * (self.dt / (cte.WATER_DENSITY * cte.WATER_HEAT_CAPACITY * storage_tank.volume))
|
||||
if q_hp[i] > 0:
|
||||
m_ch[i] = q_hp[i] / (cte.WATER_HEAT_CAPACITY * hp_delta_t)
|
||||
t_sup_hp[i] = (q_hp[i] / (m_ch[i] * cte.WATER_HEAT_CAPACITY)) + t_tank[i]
|
||||
else:
|
||||
m_ch[i] = 0
|
||||
t_sup_hp[i] = t_tank[i]
|
||||
if q_hp[i] > 0:
|
||||
if hp.source_medium == cte.AIR and hp.supply_medium == cte.WATER:
|
||||
hp_cop[i] = self.hp_characteristics.air_to_water_cop(source_temperature[i], t_tank[i],
|
||||
mode=cte.DOMESTIC_HOT_WATER)
|
||||
hp_electricity[i] = q_hp[i] / hp_cop[i]
|
||||
else:
|
||||
hp_cop[i] = 0
|
||||
hp_electricity[i] = 0
|
||||
|
||||
t_tank[i + 1] = t_tank[i] + (delta_t_hp - delta_t_freshwater - delta_t_demand + delta_t_coil)
|
||||
total_consumption[i] = hp_electricity[i] + q_coil[i]
|
||||
tes.temperature = []
|
||||
hp_electricity_j = [(x * cte.WATTS_HOUR_TO_JULES) / number_of_ts for x in hp_electricity]
|
||||
heating_coil_j = [(x * cte.WATTS_HOUR_TO_JULES) / number_of_ts for x in q_coil]
|
||||
hp_hourly = []
|
||||
coil_hourly = []
|
||||
coil_sum = 0
|
||||
hp_sum = 0
|
||||
for i in range(1, len(demand)):
|
||||
hp_sum += hp_electricity_j[i]
|
||||
coil_sum += heating_coil_j[i]
|
||||
if (i - 1) % number_of_ts == 0:
|
||||
tes.temperature.append(t_tank[i])
|
||||
hp_hourly.append(hp_sum)
|
||||
coil_hourly.append(coil_sum)
|
||||
hp_sum = 0
|
||||
coil_sum = 0
|
||||
hp.energy_consumption[cte.DOMESTIC_HOT_WATER] = {}
|
||||
hp.energy_consumption[cte.DOMESTIC_HOT_WATER][cte.HOUR] = hp_hourly
|
||||
hp.energy_consumption[cte.DOMESTIC_HOT_WATER][cte.MONTH] = MonthlyValues.get_total_month(
|
||||
hp.energy_consumption[cte.DOMESTIC_HOT_WATER][cte.HOUR])
|
||||
hp.energy_consumption[cte.DOMESTIC_HOT_WATER][cte.YEAR] = [
|
||||
sum(hp.energy_consumption[cte.DOMESTIC_HOT_WATER][cte.MONTH])]
|
||||
if self.tes.heating_coil_capacity is not None:
|
||||
tes.heating_coil_energy_consumption[cte.DOMESTIC_HOT_WATER] = {}
|
||||
tes.heating_coil_energy_consumption[cte.DOMESTIC_HOT_WATER][cte.HOUR] = coil_hourly
|
||||
tes.heating_coil_energy_consumption[cte.DOMESTIC_HOT_WATER][cte.MONTH] = MonthlyValues.get_total_month(
|
||||
tes.heating_coil_energy_consumption[cte.DOMESTIC_HOT_WATER][cte.HOUR])
|
||||
tes.heating_coil_energy_consumption[cte.DOMESTIC_HOT_WATER][cte.YEAR] = [
|
||||
sum(tes.heating_coil_energy_consumption[cte.DOMESTIC_HOT_WATER][cte.MONTH])]
|
||||
|
||||
self.results['DHW Demand (W)'] = demand
|
||||
self.results['DHW HP Heat Output (W)'] = q_hp
|
||||
self.results['DHW HP Electricity Consumption (W)'] = hp_electricity
|
||||
self.results['DHW HP Source Temperature'] = source_temperature
|
||||
self.results['DHW HP Supply Temperature'] = t_sup_hp
|
||||
self.results['DHW HP COP'] = hp_cop
|
||||
self.results['DHW TES Heating Coil Heat Output (W)'] = q_coil
|
||||
self.results['DHW TES Temperature'] = t_tank
|
||||
self.results['DHW TES Charging Flow Rate (kg/s)'] = m_ch
|
||||
self.results['DHW Flow Rate (kg/s)'] = m_dis
|
||||
self.results['DHW TES Refill Flow Rate (kg/s)'] = m_refill
|
||||
self.results['Available Water in Tank (m3)'] = v_dhw
|
||||
self.results['Total DHW Power Consumption (W)'] = total_consumption
|
||||
return self.results
|
@ -0,0 +1,166 @@
|
||||
import hub.helpers.constants as cte
|
||||
from hub.helpers.monthly_values import MonthlyValues
|
||||
from energy_system_modelling_package.energy_system_modelling_factories.hvac_dhw_systems_simulation_models.heat_pump_characteristics import \
|
||||
HeatPump
|
||||
from energy_system_modelling_package.energy_system_modelling_factories.hvac_dhw_systems_simulation_models.thermal_storage_tank import \
|
||||
StorageTank
|
||||
|
||||
|
||||
class HeatPumpBoilerTesHeating:
|
||||
def __init__(self, hp, boiler, tes, hourly_heating_demand_joules, heating_peak_load_watts, upper_limit_tes,
|
||||
outdoor_temperature, dt=900):
|
||||
self.hp = hp
|
||||
self.boiler = boiler
|
||||
self.tes = tes
|
||||
self.heating_demand = [demand / cte.WATTS_HOUR_TO_JULES for demand in hourly_heating_demand_joules]
|
||||
self.heating_peak_load = heating_peak_load_watts
|
||||
self.upper_limit_tes = upper_limit_tes
|
||||
self.hp_characteristics = HeatPump(self.hp, outdoor_temperature)
|
||||
self.t_out = outdoor_temperature
|
||||
self.dt = dt
|
||||
self.results = {}
|
||||
|
||||
def simulation(self):
|
||||
hp, boiler, tes = self.hp, self.boiler, self.tes
|
||||
heating_coil_nominal_output = 0
|
||||
if tes.heating_coil_capacity is not None:
|
||||
heating_coil_nominal_output = float(tes.heating_coil_capacity)
|
||||
storage_tank = StorageTank(volume=float(tes.volume),
|
||||
height=float(tes.height),
|
||||
material_layers=tes.layers,
|
||||
heating_coil_capacity=heating_coil_nominal_output)
|
||||
number_of_ts = int(cte.HOUR_TO_SECONDS / self.dt)
|
||||
demand = [0] + [x for x in self.heating_demand for _ in range(number_of_ts)]
|
||||
t_out = [0] + [x for x in self.t_out for _ in range(number_of_ts)]
|
||||
source_temperature_hourly = self.hp_characteristics.hp_source_temperature()
|
||||
source_temperature = [0] + [x for x in source_temperature_hourly for _ in range(number_of_ts)]
|
||||
variable_names = ["t_sup_hp", "t_tank", "t_ret", "m_ch", "m_dis", "q_hp", "q_boiler", "hp_cop",
|
||||
"hp_electricity", "boiler_gas_consumption", "t_sup_boiler", "boiler_energy_consumption",
|
||||
"heating_consumption", "heating_coil_output", "total_heating_energy_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_fuel_consumption, t_sup_boiler, boiler_energy_consumption, heating_consumption, q_coil,
|
||||
total_consumption) = [variables[name] for name in variable_names]
|
||||
t_tank[0] = self.upper_limit_tes
|
||||
hp_delta_t = 5
|
||||
# storage temperature prediction
|
||||
for i in range(len(demand) - 1):
|
||||
t_tank[i + 1] = storage_tank.calculate_space_heating_fully_mixed(charging_flow_rate=m_ch[i],
|
||||
discharging_flow_rate=m_dis[i],
|
||||
supply_temperature=t_sup_boiler[i],
|
||||
return_temperature=t_ret[i],
|
||||
current_tank_temperature=t_tank[i],
|
||||
heat_generator_input=q_coil[i],
|
||||
ambient_temperature=t_out[i],
|
||||
dt=self.dt)
|
||||
# hp operation
|
||||
if t_tank[i + 1] < 40:
|
||||
q_hp[i + 1] = hp.nominal_heat_output
|
||||
m_ch[i + 1] = q_hp[i + 1] / (cte.WATER_HEAT_CAPACITY * hp_delta_t)
|
||||
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] < self.upper_limit_tes 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] < self.upper_limit_tes and q_hp[i] > 0:
|
||||
q_hp[i + 1] = hp.nominal_heat_output
|
||||
m_ch[i + 1] = q_hp[i + 1] / (cte.WATER_HEAT_CAPACITY * hp_delta_t)
|
||||
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]
|
||||
if q_hp[i + 1] > 0:
|
||||
if hp.source_medium == cte.AIR and self.hp.supply_medium == cte.WATER:
|
||||
hp_cop[i + 1] = self.hp_characteristics.air_to_water_cop(source_temperature[i + 1], t_tank[i + 1], mode=cte.HEATING)
|
||||
hp_electricity[i + 1] = q_hp[i + 1] / hp_cop[i + 1]
|
||||
else:
|
||||
hp_cop[i + 1] = 0
|
||||
hp_electricity[i + 1] = 0
|
||||
# boiler operation
|
||||
if q_hp[i + 1] > 0:
|
||||
if t_sup_hp[i + 1] < 45:
|
||||
q_boiler[i + 1] = boiler.nominal_heat_output
|
||||
elif demand[i + 1] > 0.5 * self.heating_peak_load / self.dt:
|
||||
q_boiler[i + 1] = 0.5 * boiler.nominal_heat_output
|
||||
boiler_energy_consumption[i + 1] = q_boiler[i + 1] / float(boiler.heat_efficiency)
|
||||
if boiler.fuel_type == cte.ELECTRICITY:
|
||||
boiler_fuel_consumption[i + 1] = boiler_energy_consumption[i + 1]
|
||||
else:
|
||||
# TODO: Other fuels should be considered
|
||||
boiler_fuel_consumption[i + 1] = (q_boiler[i + 1] * self.dt) / (
|
||||
float(boiler.heat_efficiency) * cte.NATURAL_GAS_LHV)
|
||||
t_sup_boiler[i + 1] = t_sup_hp[i + 1] + (q_boiler[i + 1] / (m_ch[i + 1] * cte.WATER_HEAT_CAPACITY))
|
||||
# heating coil operation
|
||||
if t_tank[i + 1] < 35:
|
||||
q_coil[i + 1] = heating_coil_nominal_output
|
||||
# storage discharging
|
||||
if demand[i + 1] == 0:
|
||||
m_dis[i + 1] = 0
|
||||
t_ret[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)
|
||||
t_ret[i + 1] = t_tank[i + 1] - demand[i + 1] / (m_dis[i + 1] * cte.WATER_HEAT_CAPACITY)
|
||||
# total consumption
|
||||
total_consumption[i + 1] = hp_electricity[i + 1] + boiler_energy_consumption[i + 1] + q_coil[i + 1]
|
||||
tes.temperature = []
|
||||
hp_electricity_j = [(x * cte.WATTS_HOUR_TO_JULES) / number_of_ts for x in hp_electricity]
|
||||
boiler_consumption_j = [(x * cte.WATTS_HOUR_TO_JULES) / number_of_ts for x in boiler_energy_consumption]
|
||||
heating_coil_j = [(x * cte.WATTS_HOUR_TO_JULES) / number_of_ts for x in q_coil]
|
||||
hp_hourly = []
|
||||
boiler_hourly = []
|
||||
coil_hourly = []
|
||||
boiler_sum = 0
|
||||
hp_sum = 0
|
||||
coil_sum = 0
|
||||
for i in range(1, len(demand)):
|
||||
hp_sum += hp_electricity_j[i]
|
||||
boiler_sum += boiler_consumption_j[i]
|
||||
coil_sum += heating_coil_j[i]
|
||||
if (i - 1) % number_of_ts == 0:
|
||||
tes.temperature.append(t_tank[i])
|
||||
hp_hourly.append(hp_sum)
|
||||
boiler_hourly.append(boiler_sum)
|
||||
coil_hourly.append(coil_sum)
|
||||
hp_sum = 0
|
||||
boiler_sum = 0
|
||||
coil_sum = 0
|
||||
hp.energy_consumption[cte.HEATING] = {}
|
||||
hp.energy_consumption[cte.HEATING][cte.HOUR] = hp_hourly
|
||||
hp.energy_consumption[cte.HEATING][cte.MONTH] = MonthlyValues.get_total_month(
|
||||
hp.energy_consumption[cte.HEATING][cte.HOUR])
|
||||
hp.energy_consumption[cte.HEATING][cte.YEAR] = [
|
||||
sum(hp.energy_consumption[cte.HEATING][cte.MONTH])]
|
||||
boiler.energy_consumption[cte.HEATING] = {}
|
||||
boiler.energy_consumption[cte.HEATING][cte.HOUR] = boiler_hourly
|
||||
boiler.energy_consumption[cte.HEATING][cte.MONTH] = MonthlyValues.get_total_month(
|
||||
boiler.energy_consumption[cte.HEATING][cte.HOUR])
|
||||
boiler.energy_consumption[cte.HEATING][cte.YEAR] = [
|
||||
sum(boiler.energy_consumption[cte.HEATING][cte.MONTH])]
|
||||
if tes.heating_coil_capacity is not None:
|
||||
tes.heating_coil_energy_consumption[cte.HEATING] = {}
|
||||
tes.heating_coil_energy_consumption[cte.HEATING][cte.HOUR] = coil_hourly
|
||||
tes.heating_coil_energy_consumption[cte.HEATING][cte.MONTH] = MonthlyValues.get_total_month(
|
||||
tes.heating_coil_energy_consumption[cte.HEATING][cte.HOUR])
|
||||
tes.heating_coil_energy_consumption[cte.HEATING][cte.YEAR] = [
|
||||
sum(tes.heating_coil_energy_consumption[cte.HEATING][cte.MONTH])]
|
||||
self.results['Heating Demand (W)'] = demand
|
||||
self.results['HP Heat Output (W)'] = q_hp
|
||||
self.results['HP Source Temperature'] = source_temperature
|
||||
self.results['HP Supply Temperature'] = t_sup_hp
|
||||
self.results['HP COP'] = hp_cop
|
||||
self.results['HP Electricity Consumption (W)'] = hp_electricity
|
||||
self.results['Boiler Heat Output (W)'] = q_boiler
|
||||
self.results['Boiler Power Consumption (W)'] = boiler_energy_consumption
|
||||
self.results['Boiler Supply Temperature'] = t_sup_boiler
|
||||
self.results['Boiler Fuel Consumption'] = boiler_fuel_consumption
|
||||
self.results['TES Temperature'] = t_tank
|
||||
self.results['Heating Coil heat input'] = q_coil
|
||||
self.results['TES Charging Flow Rate (kg/s)'] = m_ch
|
||||
self.results['TES Discharge Flow Rate (kg/s)'] = m_dis
|
||||
self.results['Heating Loop Return Temperature'] = t_ret
|
||||
self.results['Total Heating Power Consumption (W)'] = total_consumption
|
||||
return self.results
|
@ -0,0 +1,54 @@
|
||||
import hub.helpers.constants as cte
|
||||
|
||||
|
||||
class HeatPump:
|
||||
def __init__(self, hp, t_out):
|
||||
self.hp = hp
|
||||
self.t_out = t_out
|
||||
|
||||
def hp_source_temperature(self):
|
||||
if self.hp.source_medium == cte.AIR:
|
||||
self.hp.source_temperature = self.t_out
|
||||
elif self.hp.source_medium == cte.GROUND:
|
||||
average_air_temperature = sum(self.t_out) / len(self.t_out)
|
||||
self.hp.source_temperature = [average_air_temperature + 10] * len(self.t_out)
|
||||
elif self.hp.source_medium == cte.WATER:
|
||||
self.hp.source_temperature = [15] * len(self.t_out)
|
||||
return self.hp.source_temperature
|
||||
|
||||
def air_to_water_cop(self, source_temperature, inlet_water_temperature, mode=cte.HEATING):
|
||||
cop_coefficient = 1
|
||||
t_inlet_water_fahrenheit = 1.8 * inlet_water_temperature + 32
|
||||
t_source_fahrenheit = 1.8 * source_temperature + 32
|
||||
if mode == cte.HEATING:
|
||||
if self.hp.heat_efficiency_curve is not None:
|
||||
cop_curve_coefficients = [float(coefficient) for coefficient in self.hp.heat_efficiency_curve.coefficients]
|
||||
cop_coefficient = (1 / (cop_curve_coefficients[0] +
|
||||
cop_curve_coefficients[1] * t_inlet_water_fahrenheit +
|
||||
cop_curve_coefficients[2] * t_inlet_water_fahrenheit ** 2 +
|
||||
cop_curve_coefficients[3] * t_source_fahrenheit +
|
||||
cop_curve_coefficients[4] * t_source_fahrenheit ** 2 +
|
||||
cop_curve_coefficients[5] * t_inlet_water_fahrenheit * t_source_fahrenheit))
|
||||
hp_efficiency = float(self.hp.heat_efficiency)
|
||||
elif mode == cte.COOLING:
|
||||
if self.hp.cooling_efficiency_curve is not None:
|
||||
cop_curve_coefficients = [float(coefficient) for coefficient in self.hp.cooling_efficiency_curve.coefficients]
|
||||
cop_coefficient = (1 / (cop_curve_coefficients[0] +
|
||||
cop_curve_coefficients[1] * t_inlet_water_fahrenheit +
|
||||
cop_curve_coefficients[2] * t_inlet_water_fahrenheit ** 2 +
|
||||
cop_curve_coefficients[3] * t_source_fahrenheit +
|
||||
cop_curve_coefficients[4] * t_source_fahrenheit ** 2 +
|
||||
cop_curve_coefficients[5] * t_inlet_water_fahrenheit * t_source_fahrenheit))
|
||||
hp_efficiency = float(self.hp.cooling_efficiency)
|
||||
else:
|
||||
if self.hp.heat_efficiency_curve is not None:
|
||||
cop_curve_coefficients = [float(coefficient) for coefficient in self.hp.heat_efficiency_curve.coefficients]
|
||||
cop_coefficient = (cop_curve_coefficients[0] +
|
||||
cop_curve_coefficients[1] * source_temperature +
|
||||
cop_curve_coefficients[2] * source_temperature ** 2 +
|
||||
cop_curve_coefficients[3] * inlet_water_temperature +
|
||||
cop_curve_coefficients[4] * inlet_water_temperature ** 2 +
|
||||
cop_curve_coefficients[5] * inlet_water_temperature * source_temperature)
|
||||
hp_efficiency = float(self.hp.heat_efficiency)
|
||||
hp_cop = cop_coefficient * hp_efficiency
|
||||
return hp_cop
|
@ -0,0 +1,86 @@
|
||||
import hub.helpers.constants as cte
|
||||
from hub.helpers.monthly_values import MonthlyValues
|
||||
from energy_system_modelling_package.energy_system_modelling_factories.hvac_dhw_systems_simulation_models.heat_pump_characteristics import HeatPump
|
||||
|
||||
|
||||
class HeatPumpCooling:
|
||||
def __init__(self, hp, hourly_cooling_demand_joules, cooling_peak_load_watts, cutoff_temperature, outdoor_temperature,
|
||||
dt=900):
|
||||
self.hp = hp
|
||||
self.cooling_demand = [demand / cte.WATTS_HOUR_TO_JULES for demand in hourly_cooling_demand_joules]
|
||||
self.cooling_peak_load = cooling_peak_load_watts
|
||||
self.cutoff_temperature = cutoff_temperature
|
||||
self.dt = dt
|
||||
self.results = {}
|
||||
self.heat_pump_characteristics = HeatPump(self.hp, outdoor_temperature)
|
||||
|
||||
def simulation(self):
|
||||
source_temperature_hourly = self.heat_pump_characteristics.hp_source_temperature()
|
||||
cooling_efficiency = float(self.hp.cooling_efficiency)
|
||||
number_of_ts = int(cte.HOUR_TO_SECONDS / self.dt)
|
||||
demand = [0] + [x for x in self.cooling_demand for _ in range(number_of_ts)]
|
||||
source_temperature = [0] + [x for x in source_temperature_hourly for _ in range(number_of_ts)]
|
||||
variable_names = ["t_sup_hp", "t_ret", "m", "q_hp", "hp_electricity", "hp_cop"]
|
||||
num_hours = len(demand)
|
||||
variables = {name: [0] * num_hours for name in variable_names}
|
||||
(t_sup_hp, t_ret, m, q_hp, hp_electricity, hp_cop) = [variables[name] for name in variable_names]
|
||||
t_ret[0] = self.cutoff_temperature
|
||||
|
||||
for i in range(1, len(demand)):
|
||||
if demand[i] > 0.15 * self.cooling_peak_load:
|
||||
m[i] = self.hp.nominal_cooling_output / (cte.WATER_HEAT_CAPACITY * 5)
|
||||
if t_ret[i - 1] >= self.cutoff_temperature:
|
||||
if demand[i] < 0.25 * self.cooling_peak_load:
|
||||
q_hp[i] = 0.25 * self.hp.nominal_cooling_output
|
||||
elif demand[i] < 0.5 * self.cooling_peak_load:
|
||||
q_hp[i] = 0.5 * self.hp.nominal_cooling_output
|
||||
else:
|
||||
q_hp[i] = self.hp.nominal_cooling_output
|
||||
t_sup_hp[i] = t_ret[i - 1] - q_hp[i] / (m[i] * cte.WATER_HEAT_CAPACITY)
|
||||
else:
|
||||
q_hp[i] = 0
|
||||
t_sup_hp[i] = t_ret[i - 1]
|
||||
if m[i] == 0:
|
||||
t_ret[i] = t_sup_hp[i]
|
||||
else:
|
||||
t_ret[i] = t_sup_hp[i] + demand[i] / (m[i] * cte.WATER_HEAT_CAPACITY)
|
||||
else:
|
||||
m[i] = 0
|
||||
q_hp[i] = 0
|
||||
t_sup_hp[i] = t_ret[i - 1]
|
||||
t_ret[i] = t_ret[i - 1]
|
||||
if q_hp[i] > 0:
|
||||
if self.hp.source_medium == cte.AIR and self.hp.supply_medium == cte.WATER:
|
||||
hp_cop[i] = self.heat_pump_characteristics.air_to_water_cop(source_temperature[i], t_ret[i],
|
||||
mode=cte.COOLING)
|
||||
hp_electricity[i] = q_hp[i] / cooling_efficiency
|
||||
else:
|
||||
hp_cop[i] = 0
|
||||
hp_electricity[i] = 0
|
||||
hp_electricity_j = [(x * cte.WATTS_HOUR_TO_JULES) / number_of_ts for x in hp_electricity]
|
||||
hp_hourly = []
|
||||
hp_sum = 0
|
||||
for i in range(1, len(demand)):
|
||||
hp_sum += hp_electricity_j[i]
|
||||
if (i - 1) % number_of_ts == 0:
|
||||
hp_hourly.append(hp_sum)
|
||||
hp_sum = 0
|
||||
self.hp.energy_consumption[cte.COOLING] = {}
|
||||
self.hp.energy_consumption[cte.COOLING][cte.HOUR] = hp_hourly
|
||||
self.hp.energy_consumption[cte.COOLING][cte.MONTH] = MonthlyValues.get_total_month(
|
||||
self.hp.energy_consumption[cte.COOLING][cte.HOUR])
|
||||
self.hp.energy_consumption[cte.COOLING][cte.YEAR] = [
|
||||
sum(self.hp.energy_consumption[cte.COOLING][cte.MONTH])]
|
||||
self.results['Cooling Demand (W)'] = demand
|
||||
self.results['HP Cooling Output (W)'] = q_hp
|
||||
self.results['HP Source Temperature'] = source_temperature
|
||||
self.results['HP Cooling Supply Temperature'] = t_sup_hp
|
||||
self.results['HP Cooling COP'] = hp_cop
|
||||
self.results['HP Electricity Consumption'] = hp_electricity
|
||||
self.results['Cooling Loop Flow Rate (kg/s)'] = m
|
||||
self.results['Cooling Loop Return Temperature'] = t_ret
|
||||
return self.results
|
||||
|
||||
|
||||
|
||||
|
@ -0,0 +1,47 @@
|
||||
import math
|
||||
|
||||
import hub.helpers.constants as cte
|
||||
|
||||
|
||||
class StorageTank:
|
||||
"""
|
||||
Calculation of the temperature inside a hot water storage tank in the next time step
|
||||
"""
|
||||
|
||||
def __init__(self, volume, height, material_layers, heating_coil_capacity, stratification_layer=1):
|
||||
self.volume = volume
|
||||
self.height = height
|
||||
self.materials = material_layers
|
||||
self.number_of_vertical_layers = stratification_layer
|
||||
self.heating_coil_capacity = heating_coil_capacity
|
||||
|
||||
def heat_loss_coefficient(self):
|
||||
r_tot = sum(float(layer.thickness) / float(layer.material.conductivity) for layer in
|
||||
self.materials)
|
||||
u_tot = 1 / r_tot
|
||||
d = math.sqrt((4 * self.volume) / (math.pi * self.height))
|
||||
a_side = math.pi * d * self.height
|
||||
a_top = math.pi * d ** 2 / 4
|
||||
if self.number_of_vertical_layers == 1:
|
||||
ua = u_tot * (2 * a_top + a_side)
|
||||
return ua
|
||||
else:
|
||||
ua_side = u_tot * a_side
|
||||
ua_top_bottom = u_tot * (a_top + a_side)
|
||||
return ua_side, ua_top_bottom
|
||||
|
||||
|
||||
def calculate_space_heating_fully_mixed(self, charging_flow_rate, discharging_flow_rate, supply_temperature,
|
||||
return_temperature,
|
||||
current_tank_temperature, heat_generator_input, ambient_temperature, dt):
|
||||
ua = self.heat_loss_coefficient()
|
||||
t_tank = (current_tank_temperature +
|
||||
(charging_flow_rate * (supply_temperature - current_tank_temperature) +
|
||||
(ua * (ambient_temperature - current_tank_temperature)) / cte.WATER_HEAT_CAPACITY -
|
||||
discharging_flow_rate * (current_tank_temperature - return_temperature) +
|
||||
heat_generator_input / cte.WATER_HEAT_CAPACITY) * (dt / (cte.WATER_DENSITY * self.volume)))
|
||||
return t_tank
|
||||
|
||||
def calculate_dhw_fully_mixed(self, charging_flow_rate, discharging_flow_rate, supply_temperature, return_temperature,
|
||||
current_tank_temperature, heat_generator_input, ambient_temperature, dt):
|
||||
pass
|
@ -1,15 +1,14 @@
|
||||
"""
|
||||
EnergySystemSizingSimulationFactory retrieve the energy system archetype sizing and simulation module
|
||||
SPDX - License - Identifier: LGPL - 3.0 - or -later
|
||||
Copyright © 2022 Concordia CERC group
|
||||
Copyright © 2024 Concordia CERC group
|
||||
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
|
||||
from energy_system_modelling_package.energy_system_modelling_factories.archetypes.montreal.archetype_cluster_1 import ArchetypeCluster1
|
||||
|
||||
|
||||
class EnergySystemsSimulationFactory:
|
||||
class MontrealEnergySystemArchetypesSimulationFactory:
|
||||
"""
|
||||
EnergySystemsFactory class
|
||||
"""
|
||||
@ -19,19 +18,12 @@ class EnergySystemsSimulationFactory:
|
||||
self._handler = '_' + handler.lower()
|
||||
self._building = building
|
||||
|
||||
def _archetype1(self):
|
||||
def _archetype_cluster_1(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, self._output_path).enrich_buildings()
|
||||
dt = 900
|
||||
ArchetypeCluster1(self._building, dt, self._output_path, csv_output=True).enrich_building()
|
||||
self._building.level_of_detail.energy_systems = 2
|
||||
self._building.level_of_detail.energy_systems = 2
|
||||
|
@ -0,0 +1,75 @@
|
||||
import hub.helpers.constants as cte
|
||||
class HourlyElectricityDemand:
|
||||
def __init__(self, building):
|
||||
self.building = building
|
||||
|
||||
def calculate(self):
|
||||
hourly_electricity_consumption = []
|
||||
energy_systems = self.building.energy_systems
|
||||
appliance = self.building.appliances_electrical_demand[cte.HOUR] if self.building.appliances_electrical_demand else 0
|
||||
lighting = self.building.lighting_electrical_demand[cte.HOUR] if self.building.lighting_electrical_demand else 0
|
||||
elec_heating = 0
|
||||
elec_cooling = 0
|
||||
elec_dhw = 0
|
||||
if cte.HEATING in self.building.energy_consumption_breakdown[cte.ELECTRICITY]:
|
||||
elec_heating = 1
|
||||
if cte.COOLING in self.building.energy_consumption_breakdown[cte.ELECTRICITY]:
|
||||
elec_cooling = 1
|
||||
if cte.DOMESTIC_HOT_WATER in self.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 self.building.heating_consumption[cte.HOUR]]
|
||||
else:
|
||||
heating = self.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 self.building.domestic_hot_water_consumption[cte.HOUR]]
|
||||
else:
|
||||
dhw = self.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 self.building.cooling_consumption[cte.HOUR]]
|
||||
else:
|
||||
cooling = self.building.cooling_consumption[cte.HOUR]
|
||||
|
||||
for i in range(8760):
|
||||
hourly = 0
|
||||
if isinstance(appliance, list):
|
||||
hourly += appliance[i]
|
||||
if isinstance(lighting, list):
|
||||
hourly += lighting[i]
|
||||
if heating is not None:
|
||||
hourly += heating[i]
|
||||
if cooling is not None:
|
||||
hourly += cooling[i]
|
||||
if dhw is not None:
|
||||
hourly += dhw[i]
|
||||
hourly_electricity_consumption.append(hourly)
|
||||
return hourly_electricity_consumption
|
@ -0,0 +1,225 @@
|
||||
import math
|
||||
import csv
|
||||
import hub.helpers.constants as cte
|
||||
from energy_system_modelling_package.energy_system_modelling_factories.pv_assessment.electricity_demand_calculator import \
|
||||
HourlyElectricityDemand
|
||||
from hub.catalog_factories.energy_systems_catalog_factory import EnergySystemsCatalogFactory
|
||||
from hub.helpers.monthly_values import MonthlyValues
|
||||
|
||||
|
||||
class PvSystemAssessment:
|
||||
def __init__(self, building=None, pv_system=None, battery=None, electricity_demand=None, tilt_angle=None,
|
||||
solar_angles=None, pv_installation_type=None, simulation_model_type=None, module_model_name=None,
|
||||
inverter_efficiency=None, system_catalogue_handler=None, roof_percentage_coverage=None,
|
||||
facade_coverage_percentage=None, csv_output=False, output_path=None):
|
||||
"""
|
||||
:param building:
|
||||
:param tilt_angle:
|
||||
:param solar_angles:
|
||||
:param simulation_model_type:
|
||||
:param module_model_name:
|
||||
:param inverter_efficiency:
|
||||
:param system_catalogue_handler:
|
||||
:param roof_percentage_coverage:
|
||||
:param facade_coverage_percentage:
|
||||
"""
|
||||
self.building = building
|
||||
self.electricity_demand = electricity_demand
|
||||
self.tilt_angle = tilt_angle
|
||||
self.solar_angles = solar_angles
|
||||
self.pv_installation_type = pv_installation_type
|
||||
self.simulation_model_type = simulation_model_type
|
||||
self.module_model_name = module_model_name
|
||||
self.inverter_efficiency = inverter_efficiency
|
||||
self.system_catalogue_handler = system_catalogue_handler
|
||||
self.roof_percentage_coverage = roof_percentage_coverage
|
||||
self.facade_coverage_percentage = facade_coverage_percentage
|
||||
self.pv_hourly_generation = None
|
||||
self.t_cell = None
|
||||
self.results = {}
|
||||
self.csv_output = csv_output
|
||||
self.output_path = output_path
|
||||
if pv_system is not None:
|
||||
self.pv_system = pv_system
|
||||
else:
|
||||
for energy_system in self.building.energy_systems:
|
||||
for generation_system in energy_system.generation_systems:
|
||||
if generation_system.system_type == cte.PHOTOVOLTAIC:
|
||||
self.pv_system = generation_system
|
||||
if battery is not None:
|
||||
self.battery = battery
|
||||
else:
|
||||
for energy_system in self.building.energy_systems:
|
||||
for generation_system in energy_system.generation_systems:
|
||||
if generation_system.system_type == cte.PHOTOVOLTAIC and generation_system.energy_storage_systems is not None:
|
||||
for storage_system in generation_system.energy_storage_systems:
|
||||
if storage_system.type_energy_stored == cte.ELECTRICAL:
|
||||
self.battery = storage_system
|
||||
|
||||
@staticmethod
|
||||
def explicit_model(pv_system, inverter_efficiency, number_of_panels, irradiance, outdoor_temperature):
|
||||
inverter_efficiency = inverter_efficiency
|
||||
stc_power = float(pv_system.standard_test_condition_maximum_power)
|
||||
stc_irradiance = float(pv_system.standard_test_condition_radiation)
|
||||
cell_temperature_coefficient = float(pv_system.cell_temperature_coefficient) / 100 if (
|
||||
pv_system.cell_temperature_coefficient is not None) else None
|
||||
stc_t_cell = float(pv_system.standard_test_condition_cell_temperature)
|
||||
nominal_condition_irradiance = float(pv_system.nominal_radiation)
|
||||
nominal_condition_cell_temperature = float(pv_system.nominal_cell_temperature)
|
||||
nominal_t_out = float(pv_system.nominal_ambient_temperature)
|
||||
g_i = irradiance
|
||||
t_out = outdoor_temperature
|
||||
t_cell = []
|
||||
pv_output = []
|
||||
for i in range(len(g_i)):
|
||||
t_cell.append((t_out[i] + (g_i[i] / nominal_condition_irradiance) *
|
||||
(nominal_condition_cell_temperature - nominal_t_out)))
|
||||
pv_output.append((inverter_efficiency * number_of_panels * (stc_power * (g_i[i] / stc_irradiance) *
|
||||
(1 - cell_temperature_coefficient *
|
||||
(t_cell[i] - stc_t_cell)))))
|
||||
return pv_output
|
||||
|
||||
def rooftop_sizing(self):
|
||||
pv_system = self.pv_system
|
||||
if self.module_model_name is not None:
|
||||
self.system_assignation()
|
||||
# System Sizing
|
||||
module_width = float(pv_system.width)
|
||||
module_height = float(pv_system.height)
|
||||
roof_area = 0
|
||||
for roof in self.building.roofs:
|
||||
roof_area += roof.perimeter_area
|
||||
pv_module_area = module_width * module_height
|
||||
available_roof = (self.roof_percentage_coverage * roof_area)
|
||||
# Inter-Row Spacing
|
||||
winter_solstice = self.solar_angles[(self.solar_angles['AST'].dt.month == 12) &
|
||||
(self.solar_angles['AST'].dt.day == 21) &
|
||||
(self.solar_angles['AST'].dt.hour == 12)]
|
||||
solar_altitude = winter_solstice['solar altitude'].values[0]
|
||||
solar_azimuth = winter_solstice['solar azimuth'].values[0]
|
||||
distance = ((module_height * math.sin(math.radians(self.tilt_angle)) * abs(
|
||||
math.cos(math.radians(solar_azimuth)))) / math.tan(math.radians(solar_altitude)))
|
||||
distance = float(format(distance, '.2f'))
|
||||
# Calculation of the number of panels
|
||||
space_dimension = math.sqrt(available_roof)
|
||||
space_dimension = float(format(space_dimension, '.2f'))
|
||||
panels_per_row = math.ceil(space_dimension / module_width)
|
||||
number_of_rows = math.ceil(space_dimension / distance)
|
||||
total_number_of_panels = panels_per_row * number_of_rows
|
||||
total_pv_area = total_number_of_panels * pv_module_area
|
||||
self.building.roofs[0].installed_solar_collector_area = total_pv_area
|
||||
return panels_per_row, number_of_rows
|
||||
|
||||
def system_assignation(self):
|
||||
generation_units_catalogue = EnergySystemsCatalogFactory(self.system_catalogue_handler).catalog
|
||||
catalog_pv_generation_equipments = [component for component in
|
||||
generation_units_catalogue.entries('generation_equipments') if
|
||||
component.system_type == 'photovoltaic']
|
||||
selected_pv_module = None
|
||||
for pv_module in catalog_pv_generation_equipments:
|
||||
if self.module_model_name == pv_module.model_name:
|
||||
selected_pv_module = pv_module
|
||||
if selected_pv_module is None:
|
||||
raise ValueError("No PV module with the provided model name exists in the catalogue")
|
||||
for energy_system in self.building.energy_systems:
|
||||
for idx, generation_system in enumerate(energy_system.generation_systems):
|
||||
if generation_system.system_type == cte.PHOTOVOLTAIC:
|
||||
new_system = selected_pv_module
|
||||
# Preserve attributes that exist in the original but not in the new system
|
||||
for attr in dir(generation_system):
|
||||
# Skip private attributes and methods
|
||||
if not attr.startswith('__') and not callable(getattr(generation_system, attr)):
|
||||
if not hasattr(new_system, attr):
|
||||
setattr(new_system, attr, getattr(generation_system, attr))
|
||||
# Replace the old generation system with the new one
|
||||
energy_system.generation_systems[idx] = new_system
|
||||
|
||||
def grid_tied_system(self):
|
||||
if self.electricity_demand is not None:
|
||||
electricity_demand = self.electricity_demand
|
||||
else:
|
||||
electricity_demand = [demand / cte.WATTS_HOUR_TO_JULES for demand in
|
||||
HourlyElectricityDemand(self.building).calculate()]
|
||||
rooftop_pv_output = [0] * 8760
|
||||
facade_pv_output = [0] * 8760
|
||||
rooftop_number_of_panels = 0
|
||||
if 'rooftop' in self.pv_installation_type.lower():
|
||||
np, ns = self.rooftop_sizing()
|
||||
if self.simulation_model_type == 'explicit':
|
||||
rooftop_number_of_panels = np * ns
|
||||
rooftop_pv_output = self.explicit_model(pv_system=self.pv_system,
|
||||
inverter_efficiency=self.inverter_efficiency,
|
||||
number_of_panels=rooftop_number_of_panels,
|
||||
irradiance=self.building.roofs[0].global_irradiance_tilted[
|
||||
cte.HOUR],
|
||||
outdoor_temperature=self.building.external_temperature[
|
||||
cte.HOUR])
|
||||
|
||||
total_hourly_pv_output = [rooftop_pv_output[i] + facade_pv_output[i] for i in range(8760)]
|
||||
imported_electricity = [0] * 8760
|
||||
exported_electricity = [0] * 8760
|
||||
for i in range(len(electricity_demand)):
|
||||
transfer = total_hourly_pv_output[i] - electricity_demand[i]
|
||||
if transfer > 0:
|
||||
exported_electricity[i] = transfer
|
||||
else:
|
||||
imported_electricity[i] = abs(transfer)
|
||||
|
||||
results = {'building_name': self.building.name,
|
||||
'total_floor_area_m2': self.building.thermal_zones_from_internal_zones[0].total_floor_area,
|
||||
'roof_area_m2': self.building.roofs[0].perimeter_area, 'rooftop_panels': rooftop_number_of_panels,
|
||||
'rooftop_panels_area_m2': self.building.roofs[0].installed_solar_collector_area,
|
||||
'yearly_rooftop_ghi_kW/m2': self.building.roofs[0].global_irradiance[cte.YEAR][0] / 1000,
|
||||
f'yearly_rooftop_tilted_radiation_{self.tilt_angle}_degree_kW/m2':
|
||||
self.building.roofs[0].global_irradiance_tilted[cte.YEAR][0] / 1000,
|
||||
'yearly_rooftop_pv_production_kWh': sum(rooftop_pv_output) / 1000,
|
||||
'yearly_total_pv_production_kWh': sum(total_hourly_pv_output) / 1000,
|
||||
'specific_pv_production_kWh/kWp': sum(rooftop_pv_output) / (
|
||||
float(self.pv_system.standard_test_condition_maximum_power) * rooftop_number_of_panels),
|
||||
'hourly_rooftop_poa_irradiance_W/m2': self.building.roofs[0].global_irradiance_tilted[cte.HOUR],
|
||||
'hourly_rooftop_pv_output_W': rooftop_pv_output, 'T_out': self.building.external_temperature[cte.HOUR],
|
||||
'building_electricity_demand_W': electricity_demand,
|
||||
'total_hourly_pv_system_output_W': total_hourly_pv_output, 'import_from_grid_W': imported_electricity,
|
||||
'export_to_grid_W': exported_electricity}
|
||||
return results
|
||||
|
||||
def enrich(self):
|
||||
system_archetype_name = self.building.energy_systems_archetype_name
|
||||
archetype_name = '_'.join(system_archetype_name.lower().split())
|
||||
if 'grid_tied' in archetype_name:
|
||||
self.results = self.grid_tied_system()
|
||||
hourly_pv_output = self.results['total_hourly_pv_system_output_W']
|
||||
self.building.onsite_electrical_production[cte.HOUR] = hourly_pv_output
|
||||
self.building.onsite_electrical_production[cte.MONTH] = MonthlyValues.get_total_month(hourly_pv_output)
|
||||
self.building.onsite_electrical_production[cte.YEAR] = [sum(hourly_pv_output)]
|
||||
if self.csv_output:
|
||||
self.save_to_csv(self.results, self.output_path, f'{self.building.name}_pv_system_analysis.csv')
|
||||
|
||||
@staticmethod
|
||||
def save_to_csv(data, output_path, filename='rooftop_system_results.csv'):
|
||||
# Separate keys based on whether their values are single values or lists
|
||||
single_value_keys = [key for key, value in data.items() if not isinstance(value, list)]
|
||||
list_value_keys = [key for key, value in data.items() if isinstance(value, list)]
|
||||
|
||||
# Check if all lists have the same length
|
||||
list_lengths = [len(data[key]) for key in list_value_keys]
|
||||
if not all(length == list_lengths[0] for length in list_lengths):
|
||||
raise ValueError("All lists in the dictionary must have the same length")
|
||||
|
||||
# Get the length of list values (assuming all lists are of the same length, e.g., 8760 for hourly data)
|
||||
num_rows = list_lengths[0] if list_value_keys else 1
|
||||
|
||||
# Open the CSV file for writing
|
||||
with open(output_path / filename, mode='w', newline='') as csv_file:
|
||||
writer = csv.writer(csv_file)
|
||||
# Write single-value data as a header section
|
||||
for key in single_value_keys:
|
||||
writer.writerow([key, data[key]])
|
||||
# Write an empty row for separation
|
||||
writer.writerow([])
|
||||
# Write the header for the list values
|
||||
writer.writerow(list_value_keys)
|
||||
# Write each row for the lists
|
||||
for i in range(num_rows):
|
||||
row = [data[key][i] for key in list_value_keys]
|
||||
writer.writerow(row)
|
@ -0,0 +1,221 @@
|
||||
import math
|
||||
import pandas as pd
|
||||
from datetime import datetime
|
||||
import hub.helpers.constants as cte
|
||||
from hub.helpers.monthly_values import MonthlyValues
|
||||
|
||||
|
||||
class SolarCalculator:
|
||||
def __init__(self, city, tilt_angle, surface_azimuth_angle, standard_meridian=-75,
|
||||
solar_constant=1366.1, maximum_clearness_index=1, min_cos_zenith=0.065, maximum_zenith_angle=87):
|
||||
"""
|
||||
A class to calculate the solar angles and solar irradiance on a tilted surface in the City
|
||||
:param city: An object from the City class -> City
|
||||
:param tilt_angle: tilt angle of surface -> float
|
||||
:param surface_azimuth_angle: The orientation of the surface. 0 is North -> float
|
||||
:param standard_meridian: A standard meridian is the meridian whose mean solar time is the basis of the time of day
|
||||
observed in a time zone -> float
|
||||
:param solar_constant: The amount of energy received by a given area one astronomical unit away from the Sun. It is
|
||||
constant and must not be changed
|
||||
:param maximum_clearness_index: This is used to calculate the diffuse fraction of the solar irradiance -> float
|
||||
:param min_cos_zenith: This is needed to avoid unrealistic values in tilted irradiance calculations -> float
|
||||
:param maximum_zenith_angle: This is needed to avoid negative values in tilted irradiance calculations -> float
|
||||
"""
|
||||
self.city = city
|
||||
self.location_latitude = city.latitude
|
||||
self.location_longitude = city.longitude
|
||||
self.location_latitude_rad = math.radians(self.location_latitude)
|
||||
self.surface_azimuth_angle = surface_azimuth_angle
|
||||
self.surface_azimuth_rad = math.radians(surface_azimuth_angle)
|
||||
self.tilt_angle = tilt_angle
|
||||
self.tilt_angle_rad = math.radians(tilt_angle)
|
||||
self.standard_meridian = standard_meridian
|
||||
self.longitude_correction = (self.location_longitude - standard_meridian) * 4
|
||||
self.solar_constant = solar_constant
|
||||
self.maximum_clearness_index = maximum_clearness_index
|
||||
self.min_cos_zenith = min_cos_zenith
|
||||
self.maximum_zenith_angle = maximum_zenith_angle
|
||||
timezone_offset = int(-standard_meridian / 15)
|
||||
self.timezone = f'Etc/GMT{"+" if timezone_offset < 0 else "-"}{abs(timezone_offset)}'
|
||||
self.eot = []
|
||||
self.ast = []
|
||||
self.hour_angles = []
|
||||
self.declinations = []
|
||||
self.solar_altitudes = []
|
||||
self.solar_azimuths = []
|
||||
self.zeniths = []
|
||||
self.incidents = []
|
||||
self.i_on = []
|
||||
self.i_oh = []
|
||||
self.times = pd.date_range(start='2023-01-01', end='2023-12-31 23:00', freq='h', tz=self.timezone)
|
||||
self.solar_angles = pd.DataFrame(index=self.times)
|
||||
self.day_of_year = self.solar_angles.index.dayofyear
|
||||
|
||||
def solar_time(self, datetime_val, day_of_year):
|
||||
b = (day_of_year - 81) * 2 * math.pi / 364
|
||||
eot = 9.87 * math.sin(2 * b) - 7.53 * math.cos(b) - 1.5 * math.sin(b)
|
||||
self.eot.append(eot)
|
||||
|
||||
# Calculate Local Solar Time (LST)
|
||||
lst_hour = datetime_val.hour
|
||||
lst_minute = datetime_val.minute
|
||||
lst_second = datetime_val.second
|
||||
lst = lst_hour + lst_minute / 60 + lst_second / 3600
|
||||
|
||||
# Calculate Apparent Solar Time (AST) in decimal hours
|
||||
ast_decimal = lst + eot / 60 + self.longitude_correction / 60
|
||||
ast_hours = int(ast_decimal) % 24 # Adjust hours to fit within 0–23 range
|
||||
ast_minutes = round((ast_decimal - ast_hours) * 60)
|
||||
|
||||
# Ensure ast_minutes is within valid range
|
||||
if ast_minutes == 60:
|
||||
ast_hours += 1
|
||||
ast_minutes = 0
|
||||
elif ast_minutes < 0:
|
||||
ast_minutes = 0
|
||||
ast_time = datetime(year=datetime_val.year, month=datetime_val.month, day=datetime_val.day,
|
||||
hour=ast_hours, minute=ast_minutes)
|
||||
self.ast.append(ast_time)
|
||||
return ast_time
|
||||
|
||||
def declination_angle(self, day_of_year):
|
||||
declination = 23.45 * math.sin(math.radians(360 / 365 * (284 + day_of_year)))
|
||||
declination_radian = math.radians(declination)
|
||||
self.declinations.append(declination)
|
||||
return declination_radian
|
||||
|
||||
def hour_angle(self, ast_time):
|
||||
hour_angle = ((ast_time.hour * 60 + ast_time.minute) - 720) / 4
|
||||
hour_angle_radian = math.radians(hour_angle)
|
||||
self.hour_angles.append(hour_angle)
|
||||
return hour_angle_radian
|
||||
|
||||
def solar_altitude(self, declination_radian, hour_angle_radian):
|
||||
solar_altitude_radians = math.asin(math.cos(self.location_latitude_rad) * math.cos(declination_radian) *
|
||||
math.cos(hour_angle_radian) + math.sin(self.location_latitude_rad) *
|
||||
math.sin(declination_radian))
|
||||
solar_altitude = math.degrees(solar_altitude_radians)
|
||||
self.solar_altitudes.append(solar_altitude)
|
||||
return solar_altitude_radians
|
||||
|
||||
def zenith(self, solar_altitude_radians):
|
||||
solar_altitude = math.degrees(solar_altitude_radians)
|
||||
zenith_degree = 90 - solar_altitude
|
||||
zenith_radian = math.radians(zenith_degree)
|
||||
self.zeniths.append(zenith_degree)
|
||||
return zenith_radian
|
||||
|
||||
def solar_azimuth_analytical(self, hourangle, declination, zenith):
|
||||
numer = (math.cos(zenith) * math.sin(self.location_latitude_rad) - math.sin(declination))
|
||||
denom = (math.sin(zenith) * math.cos(self.location_latitude_rad))
|
||||
if math.isclose(denom, 0.0, abs_tol=1e-8):
|
||||
cos_azi = 1.0
|
||||
else:
|
||||
cos_azi = numer / denom
|
||||
|
||||
cos_azi = max(-1.0, min(1.0, cos_azi))
|
||||
|
||||
sign_ha = math.copysign(1, hourangle)
|
||||
solar_azimuth_radians = sign_ha * math.acos(cos_azi) + math.pi
|
||||
solar_azimuth_degrees = math.degrees(solar_azimuth_radians)
|
||||
self.solar_azimuths.append(solar_azimuth_degrees)
|
||||
return solar_azimuth_radians
|
||||
|
||||
def incident_angle(self, solar_altitude_radians, solar_azimuth_radians):
|
||||
incident_radian = math.acos(math.cos(solar_altitude_radians) *
|
||||
math.cos(abs(solar_azimuth_radians - self.surface_azimuth_rad)) *
|
||||
math.sin(self.tilt_angle_rad) + math.sin(solar_altitude_radians) *
|
||||
math.cos(self.tilt_angle_rad))
|
||||
incident_angle_degrees = math.degrees(incident_radian)
|
||||
self.incidents.append(incident_angle_degrees)
|
||||
return incident_radian
|
||||
|
||||
def dni_extra(self, day_of_year, zenith_radian):
|
||||
i_on = self.solar_constant * (1 + 0.033 * math.cos(math.radians(360 * day_of_year / 365)))
|
||||
i_oh = i_on * max(math.cos(zenith_radian), self.min_cos_zenith)
|
||||
self.i_on.append(i_on)
|
||||
self.i_oh.append(i_oh)
|
||||
return i_on, i_oh
|
||||
|
||||
def clearness_index(self, ghi, i_oh):
|
||||
k_t = ghi / i_oh
|
||||
k_t = max(0, k_t)
|
||||
k_t = min(self.maximum_clearness_index, k_t)
|
||||
return k_t
|
||||
|
||||
def diffuse_fraction(self, k_t, zenith):
|
||||
if k_t <= 0.22:
|
||||
fraction_diffuse = 1 - 0.09 * k_t
|
||||
elif k_t <= 0.8:
|
||||
fraction_diffuse = (0.9511 - 0.1604 * k_t + 4.388 * k_t ** 2 - 16.638 * k_t ** 3 + 12.336 * k_t ** 4)
|
||||
else:
|
||||
fraction_diffuse = 0.165
|
||||
if zenith > self.maximum_zenith_angle:
|
||||
fraction_diffuse = 1
|
||||
return fraction_diffuse
|
||||
|
||||
def radiation_components_horizontal(self, ghi, fraction_diffuse, zenith):
|
||||
diffuse_horizontal = ghi * fraction_diffuse
|
||||
dni = (ghi - diffuse_horizontal) / math.cos(math.radians(zenith))
|
||||
if zenith > self.maximum_zenith_angle or dni < 0:
|
||||
dni = 0
|
||||
return diffuse_horizontal, dni
|
||||
|
||||
def radiation_components_tilted(self, diffuse_horizontal, dni, incident_angle):
|
||||
beam_tilted = dni * math.cos(math.radians(incident_angle))
|
||||
beam_tilted = max(beam_tilted, 0)
|
||||
diffuse_tilted = diffuse_horizontal * ((1 + math.cos(math.radians(self.tilt_angle))) / 2)
|
||||
total_radiation_tilted = beam_tilted + diffuse_tilted
|
||||
return total_radiation_tilted
|
||||
|
||||
def solar_angles_calculator(self, csv_output=False):
|
||||
for i in range(len(self.times)):
|
||||
datetime_val = self.times[i]
|
||||
day_of_year = self.day_of_year[i]
|
||||
declination_radians = self.declination_angle(day_of_year)
|
||||
ast_time = self.solar_time(datetime_val, day_of_year)
|
||||
hour_angle_radians = self.hour_angle(ast_time)
|
||||
solar_altitude_radians = self.solar_altitude(declination_radians, hour_angle_radians)
|
||||
zenith_radians = self.zenith(solar_altitude_radians)
|
||||
solar_azimuth_radians = self.solar_azimuth_analytical(hour_angle_radians, declination_radians, zenith_radians)
|
||||
self.incident_angle(solar_altitude_radians, solar_azimuth_radians)
|
||||
self.dni_extra(day_of_year=day_of_year, zenith_radian=zenith_radians)
|
||||
self.solar_angles['DateTime'] = self.times
|
||||
self.solar_angles['AST'] = self.ast
|
||||
self.solar_angles['hour angle'] = self.hour_angles
|
||||
self.solar_angles['eot'] = self.eot
|
||||
self.solar_angles['declination angle'] = self.declinations
|
||||
self.solar_angles['solar altitude'] = self.solar_altitudes
|
||||
self.solar_angles['zenith'] = self.zeniths
|
||||
self.solar_angles['solar azimuth'] = self.solar_azimuths
|
||||
self.solar_angles['incident angle'] = self.incidents
|
||||
self.solar_angles['extraterrestrial normal radiation (Wh/m2)'] = self.i_on
|
||||
self.solar_angles['extraterrestrial radiation on horizontal (Wh/m2)'] = self.i_oh
|
||||
if csv_output:
|
||||
self.solar_angles.to_csv('solar_angles_new.csv')
|
||||
|
||||
def tilted_irradiance_calculator(self):
|
||||
if self.solar_angles.empty:
|
||||
self.solar_angles_calculator()
|
||||
for building in self.city.buildings:
|
||||
hourly_tilted_irradiance = []
|
||||
roof_ghi = building.roofs[0].global_irradiance[cte.HOUR]
|
||||
for i in range(len(roof_ghi)):
|
||||
k_t = self.clearness_index(ghi=roof_ghi[i], i_oh=self.i_oh[i])
|
||||
fraction_diffuse = self.diffuse_fraction(k_t, self.zeniths[i])
|
||||
diffuse_horizontal, dni = self.radiation_components_horizontal(ghi=roof_ghi[i],
|
||||
fraction_diffuse=fraction_diffuse,
|
||||
zenith=self.zeniths[i])
|
||||
hourly_tilted_irradiance.append(int(self.radiation_components_tilted(diffuse_horizontal=diffuse_horizontal,
|
||||
dni=dni,
|
||||
incident_angle=self.incidents[i])))
|
||||
|
||||
building.roofs[0].global_irradiance_tilted[cte.HOUR] = hourly_tilted_irradiance
|
||||
building.roofs[0].global_irradiance_tilted[cte.MONTH] = (MonthlyValues.get_total_month(
|
||||
building.roofs[0].global_irradiance_tilted[cte.HOUR]))
|
||||
building.roofs[0].global_irradiance_tilted[cte.YEAR] = [sum(building.roofs[0].global_irradiance_tilted[cte.MONTH])]
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -0,0 +1,6 @@
|
||||
class HeuristicSizing:
|
||||
def __init__(self, city):
|
||||
pass
|
||||
|
||||
def enrich_buildings(self):
|
||||
pass
|
@ -0,0 +1,99 @@
|
||||
import hub.helpers.constants as cte
|
||||
|
||||
|
||||
class PeakLoadSizing:
|
||||
def __init__(self, city, default_primary_unit_percentage=0.7, storage_peak_coverage=3):
|
||||
self.city = city
|
||||
self.default_primary_unit_percentage = default_primary_unit_percentage
|
||||
self.storage_peak_coverage = storage_peak_coverage
|
||||
|
||||
def enrich_buildings(self):
|
||||
total_demand = 0
|
||||
for building in self.city.buildings:
|
||||
energy_systems = building.energy_systems
|
||||
for energy_system in energy_systems:
|
||||
if cte.HEATING in energy_system.demand_types:
|
||||
if cte.DOMESTIC_HOT_WATER in energy_system.demand_types:
|
||||
total_demand = [(building.heating_demand[cte.HOUR][i] +
|
||||
building.domestic_hot_water_heat_demand[cte.HOUR][i]) / cte.WATTS_HOUR_TO_JULES
|
||||
for i in range(len(building.heating_demand[cte.HOUR]))]
|
||||
else:
|
||||
total_demand = building.heating_peak_load[cte.YEAR]
|
||||
design_load = max(total_demand)
|
||||
self.allocate_capacity(energy_system, design_load, cte.HEATING, self.default_primary_unit_percentage)
|
||||
if cte.COOLING in energy_system.demand_types:
|
||||
cooling_design_load = building.cooling_peak_load[cte.YEAR][0]
|
||||
self.allocate_capacity(energy_system, cooling_design_load, cte.COOLING,
|
||||
self.default_primary_unit_percentage)
|
||||
|
||||
elif cte.COOLING in energy_system.demand_types:
|
||||
design_load = building.cooling_peak_load[cte.YEAR][0]
|
||||
self.allocate_capacity(energy_system, design_load, cte.COOLING, self.default_primary_unit_percentage)
|
||||
elif cte.DOMESTIC_HOT_WATER in energy_system.demand_types:
|
||||
design_load = building.domestic_hot_water_peak_load[cte.YEAR][0]
|
||||
self.allocate_capacity(energy_system, design_load, cte.DOMESTIC_HOT_WATER,
|
||||
self.default_primary_unit_percentage)
|
||||
|
||||
for generation_system in energy_system.generation_systems:
|
||||
storage_systems = generation_system.energy_storage_systems
|
||||
if storage_systems is not None:
|
||||
if cte.DOMESTIC_HOT_WATER in energy_system.demand_types:
|
||||
operation_range = 10
|
||||
else:
|
||||
operation_range = 20
|
||||
for storage_system in storage_systems:
|
||||
if storage_system.type_energy_stored == cte.THERMAL:
|
||||
self.tes_sizing(storage_system, max(total_demand), self.storage_peak_coverage, operation_range)
|
||||
|
||||
def allocate_capacity(self, energy_system, design_load, demand_type, default_primary_unit_percentage):
|
||||
if len(energy_system.generation_systems) == 1:
|
||||
# If there's only one generation system, it gets the full design load.
|
||||
if demand_type == cte.HEATING or demand_type == cte.DOMESTIC_HOT_WATER:
|
||||
energy_system.generation_systems[0].nominal_heat_output = design_load
|
||||
elif demand_type == cte.COOLING:
|
||||
energy_system.generation_systems[0].nominal_cooling_output = design_load
|
||||
else:
|
||||
cooling_equipments_number = 0
|
||||
# Distribute the load among generation systems.
|
||||
max_efficiency = 0
|
||||
main_generation_unit = None
|
||||
for generation_system in energy_system.generation_systems:
|
||||
if demand_type == cte.HEATING or demand_type == cte.DOMESTIC_HOT_WATER:
|
||||
if max_efficiency < float(generation_system.heat_efficiency):
|
||||
max_efficiency = float(generation_system.heat_efficiency)
|
||||
main_generation_unit = generation_system
|
||||
elif demand_type == cte.COOLING and generation_system.fuel_type == cte.ELECTRICITY:
|
||||
cooling_equipments_number += 1
|
||||
if max_efficiency < float(generation_system.cooling_efficiency):
|
||||
max_efficiency = float(generation_system.heat_efficiency)
|
||||
main_generation_unit = generation_system
|
||||
|
||||
for generation_system in energy_system.generation_systems:
|
||||
if generation_system.system_type == main_generation_unit.system_type:
|
||||
if demand_type == cte.HEATING or demand_type == cte.DOMESTIC_HOT_WATER:
|
||||
generation_system.nominal_heat_output = round(default_primary_unit_percentage * design_load)
|
||||
elif demand_type == cte.COOLING and cooling_equipments_number > 1:
|
||||
generation_system.nominal_cooling_output = round(default_primary_unit_percentage * design_load)
|
||||
else:
|
||||
generation_system.nominal_cooling_output = design_load
|
||||
else:
|
||||
if demand_type == cte.HEATING or demand_type == cte.DOMESTIC_HOT_WATER:
|
||||
generation_system.nominal_heat_output = round(((1 - default_primary_unit_percentage) * design_load /
|
||||
(len(energy_system.generation_systems) - 1)))
|
||||
elif demand_type == cte.COOLING and cooling_equipments_number > 1:
|
||||
generation_system.nominal_cooling_output = round(((1 - default_primary_unit_percentage) * design_load /
|
||||
(len(energy_system.generation_systems) - 1)))
|
||||
|
||||
|
||||
@staticmethod
|
||||
def tes_sizing(storage, peak_load, coverage, operational_temperature_range):
|
||||
storage.volume = round((peak_load * coverage * cte.WATTS_HOUR_TO_JULES) /
|
||||
(cte.WATER_HEAT_CAPACITY * cte.WATER_DENSITY * operational_temperature_range))
|
||||
|
||||
@staticmethod
|
||||
def cooling_equipments(energy_system):
|
||||
counter = 0
|
||||
for generation_system in energy_system.generation_systems:
|
||||
if generation_system.fuel_type == cte.ELECTRICITY:
|
||||
counter += 1
|
||||
return counter
|
@ -0,0 +1,596 @@
|
||||
import os
|
||||
import hub.helpers.constants as cte
|
||||
import matplotlib.pyplot as plt
|
||||
from matplotlib import cm
|
||||
from energy_system_modelling_package.report_creation import LatexReport
|
||||
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, current_status_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
|
||||
self.report = LatexReport('energy_system_retrofit_report',
|
||||
'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 $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
|
||||
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'))
|
||||
]
|
||||
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 plot_monthly_energy_demands(self, data, file_name, title):
|
||||
# Data preparation
|
||||
months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
|
||||
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_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(4, 1, figsize=(20, 16), dpi=96)
|
||||
fig.suptitle(title, fontsize=16, weight='bold', alpha=.8)
|
||||
|
||||
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, 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 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, 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],
|
||||
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 = 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 '
|
||||
'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 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
|
||||
current_status_operational_income = 0
|
||||
retrofitted_operational_income = 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_operational_income += self.current_status_lcc[f'{building.name}']['operational_income_per_sqm']
|
||||
retrofitted_operational_income += self.retrofitted_lcc[f'{building.name}']['operational_income_per_sqm']
|
||||
|
||||
current_status_lcc_components_sqm = {
|
||||
'Capital Cost': current_status_capex / len(self.city.buildings),
|
||||
'Operational Cost': (current_status_opex - current_status_operational_income) / 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 - retrofitted_operational_income) / 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']
|
||||
colors = ['#2196f3', '#ff5a5f', '#4caf50', '#ffc107'] # Added new color
|
||||
|
||||
# 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(len(labels))
|
||||
for category, color in 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')
|
||||
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()
|
||||
|
||||
# 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)
|
||||
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 '
|
||||
'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')
|
||||
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()
|
||||
self.report.compile_to_pdf()
|
@ -0,0 +1,176 @@
|
||||
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):
|
||||
energy_consumption_data = {}
|
||||
for building in city.buildings:
|
||||
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 = []
|
||||
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_consumption[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)
|
||||
|
||||
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
|
||||
yearly_appliance = 0
|
||||
yearly_lighting = 0
|
||||
for building in city.buildings:
|
||||
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 + 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 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
|
@ -15,8 +15,8 @@ from hub.city_model_structure.building import Building
|
||||
energy_systems_format = 'montreal_custom'
|
||||
|
||||
# parameters:
|
||||
residential_systems_percentage = {'system 1 gas': 44,
|
||||
'system 1 electricity': 6,
|
||||
residential_systems_percentage = {'system 1 gas': 15,
|
||||
'system 1 electricity': 35,
|
||||
'system 2 gas': 0,
|
||||
'system 2 electricity': 0,
|
||||
'system 3 and 4 gas': 0,
|
||||
@ -25,16 +25,46 @@ residential_systems_percentage = {'system 1 gas': 44,
|
||||
'system 5 electricity': 0,
|
||||
'system 6 gas': 0,
|
||||
'system 6 electricity': 0,
|
||||
'system 8 gas': 44,
|
||||
'system 8 electricity': 6}
|
||||
'system 8 gas': 15,
|
||||
'system 8 electricity': 35}
|
||||
|
||||
residential_new_systems_percentage = {'PV+ASHP+GasBoiler+TES': 100,
|
||||
'PV+4Pipe+DHW': 0,
|
||||
'PV+ASHP+ElectricBoiler+TES': 0,
|
||||
'PV+GSHP+GasBoiler+TES': 0,
|
||||
'PV+GSHP+ElectricBoiler+TES': 0,
|
||||
'PV+WSHP+GasBoiler+TES': 0,
|
||||
'PV+WSHP+ElectricBoiler+TES': 0}
|
||||
residential_new_systems_percentage = {
|
||||
'Central Hydronic Air and Gas Source Heating System with Unitary Split Cooling and Air Source HP DHW and Grid Tied PV': 100,
|
||||
'Central Hydronic Air and Electricity Source Heating System with Unitary Split Cooling and Air Source HP DHW and Grid Tied PV': 0,
|
||||
'Central Hydronic Ground and Gas Source Heating System with Unitary Split Cooling and Air Source HP DHW and Grid Tied PV': 0,
|
||||
'Central Hydronic Ground and Electricity Source Heating System with Unitary Split Cooling and Air Source HP DHW '
|
||||
'and Grid Tied PV': 0,
|
||||
'Central Hydronic Water and Gas Source Heating System with Unitary Split Cooling and Air Source HP DHW and Grid Tied PV': 0,
|
||||
'Central Hydronic Water and Electricity Source Heating System with Unitary Split Cooling and Air Source HP DHW '
|
||||
'and Grid Tied PV': 0,
|
||||
'Central Hydronic Air and Gas Source Heating System with Unitary Split and Air Source HP DHW': 0,
|
||||
'Central Hydronic Air and Electricity Source Heating System with Unitary Split and Air Source HP DHW': 0,
|
||||
'Central Hydronic Ground and Gas Source Heating System with Unitary Split and Air Source HP DHW': 0,
|
||||
'Central Hydronic Ground and Electricity Source Heating System with Unitary Split and Air Source HP DHW': 0,
|
||||
'Central Hydronic Water and Gas Source Heating System with Unitary Split and Air Source HP DHW': 0,
|
||||
'Central Hydronic Water and Electricity Source Heating System with Unitary Split and Air Source HP DHW': 0,
|
||||
'Grid Tied PV System': 0,
|
||||
'system 1 gas': 0,
|
||||
'system 1 gas grid tied pv': 0,
|
||||
'system 1 electricity': 0,
|
||||
'system 1 electricity grid tied pv': 0,
|
||||
'system 2 gas': 0,
|
||||
'system 2 gas grid tied pv': 0,
|
||||
'system 2 electricity': 0,
|
||||
'system 2 electricity grid tied pv': 0,
|
||||
'system 3 and 4 gas': 0,
|
||||
'system 3 and 4 gas grid tied pv': 0,
|
||||
'system 3 and 4 electricity': 0,
|
||||
'system 3 and 4 electricity grid tied pv': 0,
|
||||
'system 6 gas': 0,
|
||||
'system 6 gas grid tied pv': 0,
|
||||
'system 6 electricity': 0,
|
||||
'system 6 electricity grid tied pv': 0,
|
||||
'system 8 gas': 0,
|
||||
'system 8 gas grid tied pv': 0,
|
||||
'system 8 electricity': 0,
|
||||
'system 8 electricity grid tied pv': 0,
|
||||
}
|
||||
|
||||
non_residential_systems_percentage = {'system 1 gas': 0,
|
||||
'system 1 electricity': 0,
|
||||
@ -110,4 +140,3 @@ def call_random(_buildings: [Building], _systems_percentage):
|
||||
_buildings[_selected_buildings[_position]].energy_systems_archetype_name = case['system']
|
||||
_position += 1
|
||||
return _buildings
|
||||
|
119
energy_system_modelling_package/report_creation.py
Normal file
119
energy_system_modelling_package/report_creation.py
Normal file
@ -0,0 +1,119 @@
|
||||
import subprocess
|
||||
import datetime
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
class LatexReport:
|
||||
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'\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")
|
||||
|
||||
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'}')
|
||||
|
||||
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, 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'
|
||||
total_width -= first_column_width / 16.0
|
||||
|
||||
if caption:
|
||||
self.content.append(r'\begin{table}[htbp]')
|
||||
self.content.append(r'\caption{' + caption + r'}')
|
||||
self.content.append(r'\centering')
|
||||
|
||||
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')
|
||||
|
||||
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, placement='ht'):
|
||||
if caption:
|
||||
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}[' + 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:
|
||||
f.write('\n'.join(self.content))
|
||||
|
||||
def compile_to_pdf(self):
|
||||
subprocess.run(['pdflatex', '-output-directory', str(self.output_path), str(self.file_path)])
|
||||
|
114
energy_system_retrofit.py
Normal file
114
energy_system_retrofit.py
Normal file
@ -0,0 +1,114 @@
|
||||
from pathlib import Path
|
||||
import subprocess
|
||||
from building_modelling.ep_run_enrich import energy_plus_workflow
|
||||
from energy_system_modelling_package.energy_system_modelling_factories.montreal_energy_system_archetype_modelling_factory import \
|
||||
MontrealEnergySystemArchetypesSimulationFactory
|
||||
from energy_system_modelling_package.energy_system_modelling_factories.pv_assessment.pv_system_assessment import \
|
||||
PvSystemAssessment
|
||||
from energy_system_modelling_package.energy_system_modelling_factories.pv_assessment.solar_calculator import \
|
||||
SolarCalculator
|
||||
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 energy_system_modelling_package.energy_system_retrofit.energy_system_retrofit_report import EnergySystemRetrofitReport
|
||||
from building_modelling.geojson_creator import process_geojson
|
||||
from energy_system_modelling_package import random_assignation
|
||||
from hub.imports.energy_systems_factory import EnergySystemsFactory
|
||||
from energy_system_modelling_package.energy_system_modelling_factories.energy_system_sizing_factory import EnergySystemsSizingFactory
|
||||
from energy_system_modelling_package.energy_system_retrofit.energy_system_retrofit_results import consumption_data, cost_data
|
||||
from costing_package.cost import Cost
|
||||
from costing_package.constants import SYSTEM_RETROFIT_AND_PV, CURRENT_STATUS
|
||||
from hub.exports.exports_factory import ExportsFactory
|
||||
|
||||
# Specify the GeoJSON file path
|
||||
main_path = Path(__file__).parent.resolve()
|
||||
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.00006, path=main_path)
|
||||
geojson_file_path = input_files_path / 'output_buildings.geojson'
|
||||
output_path = (Path(__file__).parent / 'out_files').resolve()
|
||||
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)
|
||||
pv_assessment_path = output_path / 'pv_outputs'
|
||||
pv_assessment_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
|
||||
ConstructionFactory('nrcan', city).enrich()
|
||||
UsageFactory('nrcan', city).enrich()
|
||||
WeatherFactory('epw', city).enrich()
|
||||
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, sra_output_path).enrich()
|
||||
energy_plus_workflow(city, energy_plus_output_path)
|
||||
random_assignation.call_random(city.buildings, random_assignation.residential_systems_percentage)
|
||||
EnergySystemsFactory('montreal_custom', city).enrich()
|
||||
EnergySystemsSizingFactory('peak_load_sizing', city).enrich()
|
||||
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()
|
||||
EnergySystemsSizingFactory('peak_load_sizing', city).enrich()
|
||||
# # Initialize solar calculation parameters (e.g., azimuth, altitude) and compute irradiance and solar angles
|
||||
tilt_angle = 37
|
||||
solar_parameters = SolarCalculator(city=city,
|
||||
surface_azimuth_angle=180,
|
||||
tilt_angle=tilt_angle,
|
||||
standard_meridian=-75)
|
||||
solar_angles = solar_parameters.solar_angles # Obtain solar angles for further analysis
|
||||
solar_parameters.tilted_irradiance_calculator() # Calculate the solar radiation on a tilted surface
|
||||
for building in city.buildings:
|
||||
MontrealEnergySystemArchetypesSimulationFactory(f'archetype_cluster_{building.energy_systems_archetype_cluster_id}',
|
||||
building,
|
||||
simulation_results_path).enrich()
|
||||
if 'PV' in building.energy_systems_archetype_name:
|
||||
PvSystemAssessment(building=building,
|
||||
pv_system=None,
|
||||
battery=None,
|
||||
electricity_demand=None,
|
||||
tilt_angle=tilt_angle,
|
||||
solar_angles=solar_angles,
|
||||
pv_installation_type='rooftop',
|
||||
simulation_model_type='explicit',
|
||||
module_model_name=None,
|
||||
inverter_efficiency=0.95,
|
||||
system_catalogue_handler=None,
|
||||
roof_percentage_coverage=0.75,
|
||||
facade_coverage_percentage=0,
|
||||
csv_output=False,
|
||||
output_path=pv_assessment_path).enrich()
|
||||
retrofitted_energy_consumption = consumption_data(city)
|
||||
retrofitted_life_cycle_cost = {}
|
||||
for building in city.buildings:
|
||||
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()
|
||||
|
||||
|
0
example_codes/pv_potential_assessment.py
Normal file
0
example_codes/pv_potential_assessment.py
Normal file
86
example_codes/pv_system_assessment.py
Normal file
86
example_codes/pv_system_assessment.py
Normal file
@ -0,0 +1,86 @@
|
||||
from pathlib import Path
|
||||
import subprocess
|
||||
from building_modelling.ep_run_enrich import energy_plus_workflow
|
||||
from energy_system_modelling_package import random_assignation
|
||||
from energy_system_modelling_package.energy_system_modelling_factories.pv_assessment.pv_system_assessment import \
|
||||
PvSystemAssessment
|
||||
from energy_system_modelling_package.energy_system_modelling_factories.pv_assessment.solar_calculator import \
|
||||
SolarCalculator
|
||||
from hub.imports.energy_systems_factory import EnergySystemsFactory
|
||||
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 building_modelling.geojson_creator import process_geojson
|
||||
from hub.exports.exports_factory import ExportsFactory
|
||||
import hub.helpers.constants as cte
|
||||
# Define paths for input and output directories, ensuring directories are created if they do not exist
|
||||
main_path = Path(__file__).parent.parent.resolve()
|
||||
input_files_path = (Path(__file__).parent.parent / 'input_files')
|
||||
input_files_path.mkdir(parents=True, exist_ok=True)
|
||||
output_path = (Path(__file__).parent.parent / 'out_files').resolve()
|
||||
output_path.mkdir(parents=True, exist_ok=True)
|
||||
# Define specific paths for outputs from EnergyPlus and SRA (Simplified Radiosity Algorith) and PV calculation processes
|
||||
energy_plus_output_path = output_path / 'energy_plus_outputs'
|
||||
energy_plus_output_path.mkdir(parents=True, exist_ok=True)
|
||||
sra_output_path = output_path / 'sra_outputs'
|
||||
sra_output_path.mkdir(parents=True, exist_ok=True)
|
||||
pv_assessment_path = output_path / 'pv_outputs'
|
||||
pv_assessment_path.mkdir(parents=True, exist_ok=True)
|
||||
# Generate a GeoJSON file for city buildings based on latitude, longitude, and building dimensions
|
||||
geojson_file = process_geojson(x=-73.5681295982132, y=45.49218262677643, path=main_path, diff=0.0001)
|
||||
geojson_file_path = input_files_path / 'output_buildings.geojson'
|
||||
# Initialize a city object from the geojson file, mapping building functions using a predefined dictionary
|
||||
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 with construction, usage, and weather information specific to the location
|
||||
ConstructionFactory('nrcan', city).enrich()
|
||||
UsageFactory('nrcan', city).enrich()
|
||||
WeatherFactory('epw', city).enrich()
|
||||
# Execute the EnergyPlus workflow to simulate building energy performance and generate output
|
||||
# energy_plus_workflow(city, energy_plus_output_path)
|
||||
# Export the city data in SRA-compatible format to facilitate solar radiation assessment
|
||||
ExportsFactory('sra', city, sra_output_path).export()
|
||||
# Run SRA simulation using an external command, passing the generated SRA XML file path as input
|
||||
sra_path = (sra_output_path / f'{city.name}_sra.xml').resolve()
|
||||
subprocess.run(['sra', str(sra_path)])
|
||||
# Enrich city data with SRA simulation results for subsequent analysis
|
||||
ResultFactory('sra', city, sra_output_path).enrich()
|
||||
# Assign PV system archetype name to the buildings in city
|
||||
random_assignation.call_random(city.buildings, random_assignation.residential_new_systems_percentage)
|
||||
# Enrich city model with Montreal future systems parameters
|
||||
EnergySystemsFactory('montreal_future', city).enrich()
|
||||
# # Initialize solar calculation parameters (e.g., azimuth, altitude) and compute irradiance and solar angles
|
||||
tilt_angle = 37
|
||||
solar_parameters = SolarCalculator(city=city,
|
||||
surface_azimuth_angle=180,
|
||||
tilt_angle=tilt_angle,
|
||||
standard_meridian=-75)
|
||||
solar_angles = solar_parameters.solar_angles # Obtain solar angles for further analysis
|
||||
solar_parameters.tilted_irradiance_calculator() # Calculate the solar radiation on a tilted surface
|
||||
# # PV modelling building by building
|
||||
#List of available PV modules ['RE400CAA Pure 2', 'RE410CAA Pure 2', 'RE420CAA Pure 2', 'RE430CAA Pure 2',
|
||||
# 'REC600AA Pro M', 'REC610AA Pro M', 'REC620AA Pro M', 'REC630AA Pro M', 'REC640AA Pro M']
|
||||
for building in city.buildings:
|
||||
PvSystemAssessment(building=building,
|
||||
pv_system=None,
|
||||
battery=None,
|
||||
tilt_angle=tilt_angle,
|
||||
solar_angles=solar_angles,
|
||||
pv_installation_type='rooftop',
|
||||
simulation_model_type='explicit',
|
||||
module_model_name='REC640AA Pro M',
|
||||
inverter_efficiency=0.95,
|
||||
system_catalogue_handler='montreal_future',
|
||||
roof_percentage_coverage=0.75,
|
||||
facade_coverage_percentage=0,
|
||||
csv_output=False,
|
||||
output_path=pv_assessment_path).enrich()
|
||||
|
||||
|
@ -22,6 +22,7 @@ class EilatCatalog(Catalog):
|
||||
"""
|
||||
Eilat catalog class
|
||||
"""
|
||||
|
||||
def __init__(self, path):
|
||||
_path_archetypes = Path(path / 'eilat_archetypes.json').resolve()
|
||||
_path_constructions = (path / 'eilat_constructions.json').resolve()
|
||||
@ -121,8 +122,14 @@ class EilatCatalog(Catalog):
|
||||
construction_period = archetype['period_of_construction']
|
||||
average_storey_height = archetype['average_storey_height']
|
||||
extra_loses_due_to_thermal_bridges = archetype['extra_loses_due_thermal_bridges']
|
||||
infiltration_rate_for_ventilation_system_off = archetype['infiltration_rate_for_ventilation_system_off'] / cte.HOUR_TO_SECONDS
|
||||
infiltration_rate_for_ventilation_system_on = archetype['infiltration_rate_for_ventilation_system_on'] / cte.HOUR_TO_SECONDS
|
||||
infiltration_rate_for_ventilation_system_off = archetype[
|
||||
'infiltration_rate_for_ventilation_system_off'] / cte.HOUR_TO_SECONDS
|
||||
infiltration_rate_for_ventilation_system_on = archetype[
|
||||
'infiltration_rate_for_ventilation_system_on'] / cte.HOUR_TO_SECONDS
|
||||
infiltration_rate_area_for_ventilation_system_off = archetype[
|
||||
'infiltration_rate_area_for_ventilation_system_off']
|
||||
infiltration_rate_area_for_ventilation_system_on = archetype[
|
||||
'infiltration_rate_area_for_ventilation_system_on']
|
||||
|
||||
archetype_constructions = []
|
||||
for archetype_construction in archetype['constructions']:
|
||||
@ -160,7 +167,9 @@ class EilatCatalog(Catalog):
|
||||
extra_loses_due_to_thermal_bridges,
|
||||
None,
|
||||
infiltration_rate_for_ventilation_system_off,
|
||||
infiltration_rate_for_ventilation_system_on))
|
||||
infiltration_rate_for_ventilation_system_on,
|
||||
infiltration_rate_area_for_ventilation_system_off,
|
||||
infiltration_rate_area_for_ventilation_system_on))
|
||||
return _catalog_archetypes
|
||||
|
||||
def names(self, category=None):
|
||||
|
@ -128,6 +128,12 @@ class NrcanCatalog(Catalog):
|
||||
infiltration_rate_for_ventilation_system_on = (
|
||||
archetype['infiltration_rate_for_ventilation_system_on'] / cte.HOUR_TO_SECONDS
|
||||
)
|
||||
infiltration_rate_area_for_ventilation_system_off = (
|
||||
archetype['infiltration_rate_area_for_ventilation_system_off'] * 1
|
||||
)
|
||||
infiltration_rate_area_for_ventilation_system_on = (
|
||||
archetype['infiltration_rate_area_for_ventilation_system_on'] * 1
|
||||
)
|
||||
|
||||
archetype_constructions = []
|
||||
for archetype_construction in archetype['constructions']:
|
||||
@ -153,7 +159,6 @@ class NrcanCatalog(Catalog):
|
||||
_window)
|
||||
archetype_constructions.append(_construction)
|
||||
break
|
||||
|
||||
_catalog_archetypes.append(Archetype(archetype_id,
|
||||
name,
|
||||
function,
|
||||
@ -165,7 +170,10 @@ class NrcanCatalog(Catalog):
|
||||
extra_loses_due_to_thermal_bridges,
|
||||
None,
|
||||
infiltration_rate_for_ventilation_system_off,
|
||||
infiltration_rate_for_ventilation_system_on))
|
||||
infiltration_rate_for_ventilation_system_on,
|
||||
infiltration_rate_area_for_ventilation_system_off,
|
||||
infiltration_rate_area_for_ventilation_system_on
|
||||
))
|
||||
return _catalog_archetypes
|
||||
|
||||
def names(self, category=None):
|
||||
|
@ -129,6 +129,12 @@ class NrelCatalog(Catalog):
|
||||
infiltration_rate_for_ventilation_system_on = float(
|
||||
archetype['infiltration_rate_for_ventilation_system_on']['#text']
|
||||
) / cte.HOUR_TO_SECONDS
|
||||
infiltration_rate_area_for_ventilation_system_off = float(
|
||||
archetype['infiltration_rate_area_for_ventilation_system_on']['#text']
|
||||
)
|
||||
infiltration_rate_area_for_ventilation_system_on = float(
|
||||
archetype['infiltration_rate_area_for_ventilation_system_on']['#text']
|
||||
)
|
||||
|
||||
archetype_constructions = []
|
||||
for archetype_construction in archetype['constructions']['construction']:
|
||||
@ -162,7 +168,9 @@ class NrelCatalog(Catalog):
|
||||
extra_loses_due_to_thermal_bridges,
|
||||
indirect_heated_ratio,
|
||||
infiltration_rate_for_ventilation_system_off,
|
||||
infiltration_rate_for_ventilation_system_on))
|
||||
infiltration_rate_for_ventilation_system_on,
|
||||
infiltration_rate_area_for_ventilation_system_off,
|
||||
infiltration_rate_area_for_ventilation_system_on))
|
||||
return _catalog_archetypes
|
||||
|
||||
def names(self, category=None):
|
||||
|
@ -6,6 +6,7 @@ Project Coder Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca
|
||||
"""
|
||||
|
||||
import xmltodict
|
||||
import json
|
||||
from hub.catalog_factories.catalog import Catalog
|
||||
from hub.catalog_factories.data_models.cost.archetype import Archetype
|
||||
from hub.catalog_factories.data_models.cost.content import Content
|
||||
@ -15,6 +16,7 @@ from hub.catalog_factories.data_models.cost.item_description import ItemDescript
|
||||
from hub.catalog_factories.data_models.cost.operational_cost import OperationalCost
|
||||
from hub.catalog_factories.data_models.cost.fuel import Fuel
|
||||
from hub.catalog_factories.data_models.cost.income import Income
|
||||
from hub.catalog_factories.data_models.cost.pricing_rate import PricingRate
|
||||
|
||||
|
||||
class MontrealNewCatalog(Catalog):
|
||||
@ -24,6 +26,7 @@ class MontrealNewCatalog(Catalog):
|
||||
|
||||
def __init__(self, path):
|
||||
path = (path / 'montreal_costs_completed.xml').resolve()
|
||||
self._fuel_rates_path = (path.parent / 'fuel_rates.json').resolve()
|
||||
with open(path, 'r', encoding='utf-8') as xml:
|
||||
self._archetypes = xmltodict.parse(xml.read(), force_list='archetype')
|
||||
|
||||
@ -45,7 +48,7 @@ class MontrealNewCatalog(Catalog):
|
||||
construction = float(archetype['incomes']['subsidies']['construction']['#text'])
|
||||
hvac = float(archetype['incomes']['subsidies']['hvac']['#text'])
|
||||
photovoltaic_system = float(archetype['incomes']['subsidies']['photovoltaic']['#text'])
|
||||
electricity_exports = float(archetype['incomes']['electricity_export']['#text']) / 1000 / 3600
|
||||
electricity_exports = float(archetype['incomes']['electricity_export']['#text'])
|
||||
reduction_tax = float(archetype['incomes']['tax_reduction']['#text']) / 100
|
||||
income = Income(construction_subsidy=construction,
|
||||
hvac_subsidy=hvac,
|
||||
@ -75,7 +78,22 @@ class MontrealNewCatalog(Catalog):
|
||||
refurbishment_unit=_refurbishment_unit,
|
||||
reposition=None,
|
||||
reposition_unit=None,
|
||||
lifetime=None)
|
||||
lifetime=None,
|
||||
maintenance=None,
|
||||
maintenance_unit=None)
|
||||
elif 'maintenance_cost' in item.keys():
|
||||
maintenance_cost = float(item['maintenance_cost']['#text'])
|
||||
maintenance_unit = item['maintenance_cost']['@cost_unit']
|
||||
_item_description = ItemDescription(item_type,
|
||||
initial_investment=None,
|
||||
initial_investment_unit=None,
|
||||
refurbishment=None,
|
||||
refurbishment_unit=None,
|
||||
reposition=None,
|
||||
reposition_unit=None,
|
||||
lifetime=None,
|
||||
maintenance=maintenance_cost,
|
||||
maintenance_unit=maintenance_unit)
|
||||
else:
|
||||
_reposition = float(item['reposition']['#text'])
|
||||
_reposition_unit = item['reposition']['@cost_unit']
|
||||
@ -89,7 +107,9 @@ class MontrealNewCatalog(Catalog):
|
||||
refurbishment_unit=None,
|
||||
reposition=_reposition,
|
||||
reposition_unit=_reposition_unit,
|
||||
lifetime=_lifetime)
|
||||
lifetime=_lifetime,
|
||||
maintenance=None,
|
||||
maintenance_unit=None)
|
||||
|
||||
return _item_description
|
||||
|
||||
@ -137,13 +157,35 @@ class MontrealNewCatalog(Catalog):
|
||||
|
||||
return capital_costs
|
||||
|
||||
@staticmethod
|
||||
def _get_operational_costs(entry):
|
||||
def load_fuel_rates(self):
|
||||
rates = []
|
||||
with open(self._fuel_rates_path, 'r') as f:
|
||||
fuel_rates = json.load(f)
|
||||
for rate in fuel_rates['rates']['fuels']['rate']:
|
||||
name = rate['name']
|
||||
rate_type = rate['rate_type']
|
||||
units = rate['units']
|
||||
values = rate['values']
|
||||
rates.append(PricingRate(name=name, rate_type=rate_type, units=units, values=values))
|
||||
return rates
|
||||
|
||||
|
||||
def search_fuel_rates(self, rates, name):
|
||||
variable = None
|
||||
for rate in rates:
|
||||
if rate.name == name:
|
||||
variable = rate
|
||||
return variable
|
||||
|
||||
|
||||
|
||||
def _get_operational_costs(self, entry):
|
||||
fuels = []
|
||||
rates = self.load_fuel_rates()
|
||||
for item in entry['fuels']['fuel']:
|
||||
fuel_type = item['@fuel_type']
|
||||
fuel_variable = float(item['variable']['#text'])
|
||||
fuel_variable_units = item['variable']['@cost_unit']
|
||||
fuel_variable = item['variable']
|
||||
variable = self.search_fuel_rates(rates, fuel_variable)
|
||||
fuel_fixed_monthly = None
|
||||
fuel_fixed_peak = None
|
||||
density = None
|
||||
@ -165,20 +207,22 @@ class MontrealNewCatalog(Catalog):
|
||||
fuel = Fuel(fuel_type,
|
||||
fixed_monthly=fuel_fixed_monthly,
|
||||
fixed_power=fuel_fixed_peak,
|
||||
variable=fuel_variable,
|
||||
variable_units=fuel_variable_units,
|
||||
variable=variable,
|
||||
density=density,
|
||||
density_unit=density_unit,
|
||||
lower_heating_value=lower_heating_value,
|
||||
lower_heating_value_unit=lower_heating_value_unit)
|
||||
fuels.append(fuel)
|
||||
heating_equipment_maintenance = float(entry['maintenance']['heating_equipment']['#text'])
|
||||
cooling_equipment_maintenance = float(entry['maintenance']['cooling_equipment']['#text'])
|
||||
hvac_equipment = entry['maintenance']['hvac_equipment']
|
||||
items = []
|
||||
for item in hvac_equipment:
|
||||
items.append(self.item_description(item, hvac_equipment[item]))
|
||||
|
||||
|
||||
photovoltaic_system_maintenance = float(entry['maintenance']['photovoltaic_system']['#text'])
|
||||
co2_emissions = float(entry['co2_cost']['#text'])
|
||||
_operational_cost = OperationalCost(fuels,
|
||||
heating_equipment_maintenance,
|
||||
cooling_equipment_maintenance,
|
||||
items,
|
||||
photovoltaic_system_maintenance,
|
||||
co2_emissions)
|
||||
return _operational_cost
|
||||
|
@ -23,7 +23,10 @@ class Archetype:
|
||||
extra_loses_due_to_thermal_bridges,
|
||||
indirect_heated_ratio,
|
||||
infiltration_rate_for_ventilation_system_off,
|
||||
infiltration_rate_for_ventilation_system_on):
|
||||
infiltration_rate_for_ventilation_system_on,
|
||||
infiltration_rate_area_for_ventilation_system_off,
|
||||
infiltration_rate_area_for_ventilation_system_on
|
||||
):
|
||||
self._id = archetype_id
|
||||
self._name = name
|
||||
self._function = function
|
||||
@ -36,6 +39,8 @@ class Archetype:
|
||||
self._indirect_heated_ratio = indirect_heated_ratio
|
||||
self._infiltration_rate_for_ventilation_system_off = infiltration_rate_for_ventilation_system_off
|
||||
self._infiltration_rate_for_ventilation_system_on = infiltration_rate_for_ventilation_system_on
|
||||
self._infiltration_rate_area_for_ventilation_system_off = infiltration_rate_area_for_ventilation_system_off
|
||||
self._infiltration_rate_area_for_ventilation_system_on = infiltration_rate_area_for_ventilation_system_on
|
||||
|
||||
@property
|
||||
def id(self):
|
||||
@ -133,6 +138,22 @@ class Archetype:
|
||||
"""
|
||||
return self._infiltration_rate_for_ventilation_system_on
|
||||
|
||||
@property
|
||||
def infiltration_rate_area_for_ventilation_system_off(self):
|
||||
"""
|
||||
Get archetype infiltration rate for ventilation system off in m3/sm2
|
||||
:return: float
|
||||
"""
|
||||
return self._infiltration_rate_area_for_ventilation_system_off
|
||||
|
||||
@property
|
||||
def infiltration_rate_area_for_ventilation_system_on(self):
|
||||
"""
|
||||
Get archetype infiltration rate for ventilation system on in m3/sm2
|
||||
:return: float
|
||||
"""
|
||||
return self._infiltration_rate_for_ventilation_system_on
|
||||
|
||||
def to_dictionary(self):
|
||||
"""Class content to dictionary"""
|
||||
_constructions = []
|
||||
@ -149,6 +170,8 @@ class Archetype:
|
||||
'indirect heated ratio': self.indirect_heated_ratio,
|
||||
'infiltration rate for ventilation off [1/s]': self.infiltration_rate_for_ventilation_system_off,
|
||||
'infiltration rate for ventilation on [1/s]': self.infiltration_rate_for_ventilation_system_on,
|
||||
'infiltration rate area for ventilation off [m3/sm2]': self.infiltration_rate_area_for_ventilation_system_off,
|
||||
'infiltration rate area for ventilation on [m3/sm2]': self.infiltration_rate_area_for_ventilation_system_on,
|
||||
'constructions': _constructions
|
||||
}
|
||||
}
|
||||
|
@ -6,7 +6,7 @@ Project Coder Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca
|
||||
"""
|
||||
|
||||
from typing import Union
|
||||
|
||||
from hub.catalog_factories.data_models.cost.pricing_rate import PricingRate
|
||||
|
||||
class Fuel:
|
||||
"""
|
||||
@ -16,7 +16,6 @@ class Fuel:
|
||||
fixed_monthly=None,
|
||||
fixed_power=None,
|
||||
variable=None,
|
||||
variable_units=None,
|
||||
density=None,
|
||||
density_unit=None,
|
||||
lower_heating_value=None,
|
||||
@ -26,7 +25,6 @@ class Fuel:
|
||||
self._fixed_monthly = fixed_monthly
|
||||
self._fixed_power = fixed_power
|
||||
self._variable = variable
|
||||
self._variable_units = variable_units
|
||||
self._density = density
|
||||
self._density_unit = density_unit
|
||||
self._lower_heating_value = lower_heating_value
|
||||
@ -59,12 +57,12 @@ class Fuel:
|
||||
return None
|
||||
|
||||
@property
|
||||
def variable(self) -> Union[tuple[None, None], tuple[float, str]]:
|
||||
def variable(self) -> Union[None, PricingRate]:
|
||||
"""
|
||||
Get variable costs in given units
|
||||
:return: None, None or float, str
|
||||
"""
|
||||
return self._variable, self._variable_units
|
||||
return self._variable
|
||||
|
||||
@property
|
||||
def density(self) -> Union[tuple[None, None], tuple[float, str]]:
|
||||
@ -84,11 +82,13 @@ class Fuel:
|
||||
|
||||
def to_dictionary(self):
|
||||
"""Class content to dictionary"""
|
||||
variable_price = None
|
||||
if self.variable is not None:
|
||||
variable_price = self.variable.to_dictionary()
|
||||
content = {'Fuel': {'fuel type': self.type,
|
||||
'fixed operational costs [currency/month]': self.fixed_monthly,
|
||||
'fixed operational costs depending on the peak power consumed [currency/month W]': self.fixed_power,
|
||||
'variable operational costs': self.variable[0],
|
||||
'units': self.variable[1],
|
||||
'variable operational costs': variable_price,
|
||||
'density': self.density[0],
|
||||
'density unit': self.density[1],
|
||||
'lower heating value': self.lower_heating_value[0],
|
||||
|
@ -19,7 +19,9 @@ class ItemDescription:
|
||||
refurbishment_unit=None,
|
||||
reposition=None,
|
||||
reposition_unit=None,
|
||||
lifetime=None):
|
||||
lifetime=None,
|
||||
maintenance=None,
|
||||
maintenance_unit=None):
|
||||
|
||||
self._item_type = item_type
|
||||
self._initial_investment = initial_investment
|
||||
@ -29,6 +31,8 @@ class ItemDescription:
|
||||
self._reposition = reposition
|
||||
self._reposition_unit = reposition_unit
|
||||
self._lifetime = lifetime
|
||||
self._maintenance = maintenance
|
||||
self._maintenance_unit = maintenance_unit
|
||||
|
||||
@property
|
||||
def type(self):
|
||||
@ -70,6 +74,14 @@ class ItemDescription:
|
||||
"""
|
||||
return self._lifetime
|
||||
|
||||
@property
|
||||
def maintenance(self) -> Union[tuple[None, None], tuple[float, str]]:
|
||||
"""
|
||||
Get reposition costs of the specific item in given units
|
||||
:return: None, None or float, str
|
||||
"""
|
||||
return self._maintenance, self._maintenance_unit
|
||||
|
||||
def to_dictionary(self):
|
||||
"""Class content to dictionary"""
|
||||
content = {'Item': {'type': self.type,
|
||||
@ -79,7 +91,9 @@ class ItemDescription:
|
||||
'refurbishment units': self.refurbishment[1],
|
||||
'reposition': self.reposition[0],
|
||||
'reposition units': self.reposition[1],
|
||||
'life time [years]': self.lifetime
|
||||
'life time [years]': self.lifetime,
|
||||
'maintenance': self.maintenance[0],
|
||||
'maintenance units': self.maintenance[1]
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -7,16 +7,15 @@ Project Coder Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca
|
||||
|
||||
from typing import List
|
||||
from hub.catalog_factories.data_models.cost.fuel import Fuel
|
||||
|
||||
from hub.catalog_factories.data_models.cost.item_description import ItemDescription
|
||||
|
||||
class OperationalCost:
|
||||
"""
|
||||
Operational cost class
|
||||
"""
|
||||
def __init__(self, fuels, maintenance_heating, maintenance_cooling, maintenance_pv, co2):
|
||||
def __init__(self, fuels, maintenance_hvac, maintenance_pv, co2):
|
||||
self._fuels = fuels
|
||||
self._maintenance_heating = maintenance_heating
|
||||
self._maintenance_cooling = maintenance_cooling
|
||||
self._maintenance_hvac = maintenance_hvac
|
||||
self._maintenance_pv = maintenance_pv
|
||||
self._co2 = co2
|
||||
|
||||
@ -30,20 +29,12 @@ class OperationalCost:
|
||||
return self._fuels
|
||||
|
||||
@property
|
||||
def maintenance_heating(self):
|
||||
def maintenance_hvac(self) -> List[ItemDescription]:
|
||||
"""
|
||||
Get cost of maintaining the heating system in currency/W
|
||||
Get cost of maintaining the hvac system in currency/W
|
||||
:return: float
|
||||
"""
|
||||
return self._maintenance_heating
|
||||
|
||||
@property
|
||||
def maintenance_cooling(self):
|
||||
"""
|
||||
Get cost of maintaining the cooling system in currency/W
|
||||
:return: float
|
||||
"""
|
||||
return self._maintenance_cooling
|
||||
return self._maintenance_hvac
|
||||
|
||||
@property
|
||||
def maintenance_pv(self):
|
||||
@ -64,11 +55,13 @@ class OperationalCost:
|
||||
def to_dictionary(self):
|
||||
"""Class content to dictionary"""
|
||||
_fuels = []
|
||||
_hvac_maintenance = []
|
||||
for _fuel in self.fuels:
|
||||
_fuels.append(_fuel.to_dictionary())
|
||||
for _hvac in self.maintenance_hvac:
|
||||
_hvac_maintenance.append(_hvac.to_dictionary())
|
||||
content = {'Maintenance': {'fuels': _fuels,
|
||||
'cost of maintaining the heating system [currency/W]': self.maintenance_heating,
|
||||
'cost of maintaining the cooling system [currency/W]': self.maintenance_cooling,
|
||||
'cost of maintaining the hvac system [currency/W]': _hvac_maintenance,
|
||||
'cost of maintaining the PV system [currency/W]': self.maintenance_pv,
|
||||
'cost of CO2 emissions [currency/kgCO2]': self.co2
|
||||
}
|
||||
|
62
hub/catalog_factories/data_models/cost/pricing_rate.py
Normal file
62
hub/catalog_factories/data_models/cost/pricing_rate.py
Normal file
@ -0,0 +1,62 @@
|
||||
from typing import Union
|
||||
|
||||
|
||||
class PricingRate:
|
||||
def __init__(self, name=None, rate_type=None, time_range=None, units=None, values=None):
|
||||
self._name = name
|
||||
self._rate_type = rate_type
|
||||
self._time_range = time_range
|
||||
self._units = units
|
||||
self._values = values
|
||||
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""
|
||||
name of the rate
|
||||
:return: str
|
||||
"""
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def rate_type(self):
|
||||
"""
|
||||
type of rate between fixed and variable
|
||||
:return: str
|
||||
"""
|
||||
return self._rate_type
|
||||
|
||||
@property
|
||||
def time_range(self) -> Union[None, str]:
|
||||
"""
|
||||
Get schedule time range from:
|
||||
['minute', 'hour', 'day', 'week', 'month', 'year']
|
||||
:return: None or str
|
||||
"""
|
||||
return self._time_range
|
||||
|
||||
@property
|
||||
def units(self):
|
||||
"""
|
||||
get the consumption unit
|
||||
:return: str
|
||||
"""
|
||||
return self._units
|
||||
|
||||
@property
|
||||
def values(self):
|
||||
"""
|
||||
Get schedule values
|
||||
:return: [Any]
|
||||
"""
|
||||
return self._values
|
||||
|
||||
def to_dictionary(self):
|
||||
"""Class content to dictionary"""
|
||||
content = {'Pricing': {'name': self.name,
|
||||
'time range': self.time_range,
|
||||
'type': self.rate_type,
|
||||
'units': self.units,
|
||||
'values': self.values}
|
||||
}
|
||||
return content
|
@ -15,11 +15,20 @@ class Archetype:
|
||||
"""
|
||||
Archetype class
|
||||
"""
|
||||
def __init__(self, name, systems):
|
||||
|
||||
def __init__(self, name, systems, archetype_cluster_id=None):
|
||||
self._cluster_id = archetype_cluster_id
|
||||
self._name = name
|
||||
self._systems = systems
|
||||
|
||||
@property
|
||||
def cluster_id(self):
|
||||
"""
|
||||
Get id
|
||||
:return: string
|
||||
"""
|
||||
return self._cluster_id
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""
|
||||
@ -43,8 +52,9 @@ class Archetype:
|
||||
_systems.append(_system.to_dictionary())
|
||||
content = {
|
||||
'Archetype': {
|
||||
'cluster_id': self.cluster_id,
|
||||
'name': self.name,
|
||||
'systems': _systems
|
||||
}
|
||||
}
|
||||
}
|
||||
return content
|
||||
|
@ -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
|
||||
|
@ -17,8 +17,9 @@ class PvGenerationSystem(GenerationSystem):
|
||||
def __init__(self, system_id, name, system_type, model_name=None, manufacturer=None, electricity_efficiency=None,
|
||||
nominal_electricity_output=None, nominal_ambient_temperature=None, nominal_cell_temperature=None,
|
||||
nominal_radiation=None, standard_test_condition_cell_temperature=None,
|
||||
standard_test_condition_maximum_power=None, cell_temperature_coefficient=None, width=None, height=None,
|
||||
distribution_systems=None, energy_storage_systems=None):
|
||||
standard_test_condition_maximum_power=None, standard_test_condition_radiation=None,
|
||||
cell_temperature_coefficient=None, width=None, height=None, distribution_systems=None,
|
||||
energy_storage_systems=None):
|
||||
super().__init__(system_id=system_id, name=name, model_name=model_name,
|
||||
manufacturer=manufacturer, fuel_type='renewable', distribution_systems=distribution_systems,
|
||||
energy_storage_systems=energy_storage_systems)
|
||||
@ -30,6 +31,7 @@ class PvGenerationSystem(GenerationSystem):
|
||||
self._nominal_radiation = nominal_radiation
|
||||
self._standard_test_condition_cell_temperature = standard_test_condition_cell_temperature
|
||||
self._standard_test_condition_maximum_power = standard_test_condition_maximum_power
|
||||
self._standard_test_condition_radiation = standard_test_condition_radiation
|
||||
self._cell_temperature_coefficient = cell_temperature_coefficient
|
||||
self._width = width
|
||||
self._height = height
|
||||
@ -98,6 +100,15 @@ class PvGenerationSystem(GenerationSystem):
|
||||
"""
|
||||
return self._standard_test_condition_maximum_power
|
||||
|
||||
@property
|
||||
def standard_test_condition_radiation(self):
|
||||
"""
|
||||
Get standard test condition cell temperature of PV panels in W/m2
|
||||
:return: float
|
||||
"""
|
||||
return self._standard_test_condition_radiation
|
||||
|
||||
|
||||
@property
|
||||
def cell_temperature_coefficient(self):
|
||||
"""
|
||||
@ -143,6 +154,7 @@ class PvGenerationSystem(GenerationSystem):
|
||||
'nominal radiation [W/m2]': self.nominal_radiation,
|
||||
'standard test condition cell temperature [Celsius]': self.standard_test_condition_cell_temperature,
|
||||
'standard test condition maximum power [W]': self.standard_test_condition_maximum_power,
|
||||
'standard test condition radiation [W/m2]': self.standard_test_condition_radiation,
|
||||
'cell temperature coefficient': self.cell_temperature_coefficient,
|
||||
'width': self.width,
|
||||
'height': self.height,
|
||||
|
@ -119,7 +119,7 @@ class ThermalStorageSystem(EnergyStorageSystem):
|
||||
'height [m]': self.height,
|
||||
'layers': _layers,
|
||||
'maximum operating temperature [Celsius]': self.maximum_operating_temperature,
|
||||
'storage_medium': self.storage_medium.to_dictionary(),
|
||||
'storage_medium': _medias,
|
||||
'heating coil capacity [W]': self.heating_coil_capacity
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
||||
|
@ -30,7 +30,8 @@ class MontrealFutureSystemCatalogue(Catalog):
|
||||
path = str(path / 'montreal_future_systems.xml')
|
||||
with open(path, 'r', encoding='utf-8') as xml:
|
||||
self._archetypes = xmltodict.parse(xml.read(),
|
||||
force_list=['pv_generation_component', 'templateStorages', 'demand'])
|
||||
force_list=['pv_generation_component', 'templateStorages', 'demand',
|
||||
'system', 'system_id'])
|
||||
|
||||
self._storage_components = self._load_storage_components()
|
||||
self._generation_components = self._load_generation_components()
|
||||
@ -49,7 +50,7 @@ class MontrealFutureSystemCatalogue(Catalog):
|
||||
'non_pv_generation_component']
|
||||
if non_pv_generation_components is not None:
|
||||
for non_pv in non_pv_generation_components:
|
||||
system_id = non_pv['system_id']
|
||||
system_id = non_pv['generation_system_id']
|
||||
name = non_pv['name']
|
||||
system_type = non_pv['system_type']
|
||||
model_name = non_pv['model_name']
|
||||
@ -181,7 +182,7 @@ class MontrealFutureSystemCatalogue(Catalog):
|
||||
'pv_generation_component']
|
||||
if pv_generation_components is not None:
|
||||
for pv in pv_generation_components:
|
||||
system_id = pv['system_id']
|
||||
system_id = pv['generation_system_id']
|
||||
name = pv['name']
|
||||
system_type = pv['system_type']
|
||||
model_name = pv['model_name']
|
||||
@ -193,6 +194,7 @@ class MontrealFutureSystemCatalogue(Catalog):
|
||||
nominal_radiation = pv['nominal_radiation']
|
||||
standard_test_condition_cell_temperature = pv['standard_test_condition_cell_temperature']
|
||||
standard_test_condition_maximum_power = pv['standard_test_condition_maximum_power']
|
||||
standard_test_condition_radiation = pv['standard_test_condition_radiation']
|
||||
cell_temperature_coefficient = pv['cell_temperature_coefficient']
|
||||
width = pv['width']
|
||||
height = pv['height']
|
||||
@ -215,6 +217,7 @@ class MontrealFutureSystemCatalogue(Catalog):
|
||||
standard_test_condition_cell_temperature=
|
||||
standard_test_condition_cell_temperature,
|
||||
standard_test_condition_maximum_power=standard_test_condition_maximum_power,
|
||||
standard_test_condition_radiation=standard_test_condition_radiation,
|
||||
cell_temperature_coefficient=cell_temperature_coefficient,
|
||||
width=width,
|
||||
height=height,
|
||||
@ -262,7 +265,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,
|
||||
@ -298,7 +301,7 @@ class MontrealFutureSystemCatalogue(Catalog):
|
||||
layers = [insulation_layer, tank_layer]
|
||||
nominal_capacity = tes['nominal_capacity']
|
||||
losses_ratio = tes['losses_ratio']
|
||||
heating_coil_capacity = None
|
||||
heating_coil_capacity = tes['heating_coil_capacity']
|
||||
storage_component = ThermalStorageSystem(storage_id=storage_id,
|
||||
model_name=model_name,
|
||||
type_energy_stored=type_energy_stored,
|
||||
@ -338,7 +341,7 @@ class MontrealFutureSystemCatalogue(Catalog):
|
||||
nominal_capacity = template['nominal_capacity']
|
||||
losses_ratio = template['losses_ratio']
|
||||
volume = template['physical_characteristics']['volume']
|
||||
heating_coil_capacity = None
|
||||
heating_coil_capacity = template['heating_coil_capacity']
|
||||
storage_component = ThermalStorageSystem(storage_id=storage_id,
|
||||
model_name=model_name,
|
||||
type_energy_stored=type_energy_stored,
|
||||
@ -379,6 +382,7 @@ class MontrealFutureSystemCatalogue(Catalog):
|
||||
_system_archetypes = []
|
||||
system_clusters = self._archetypes['EnergySystemCatalog']['system_archetypes']['system_archetype']
|
||||
for system_cluster in system_clusters:
|
||||
archetype_id = system_cluster['@cluster_id']
|
||||
name = system_cluster['name']
|
||||
systems = system_cluster['systems']['system_id']
|
||||
integer_system_ids = [int(item) for item in systems]
|
||||
@ -386,7 +390,7 @@ class MontrealFutureSystemCatalogue(Catalog):
|
||||
for system_archetype in self._systems:
|
||||
if int(system_archetype.id) in integer_system_ids:
|
||||
_systems.append(system_archetype)
|
||||
_system_archetypes.append(Archetype(name=name, systems=_systems))
|
||||
_system_archetypes.append(Archetype(archetype_cluster_id=archetype_id, name=name, systems=_systems))
|
||||
return _system_archetypes
|
||||
|
||||
def _load_materials(self):
|
||||
|
@ -92,6 +92,7 @@ class Building(CityObject):
|
||||
logging.error('Building %s [%s] has an unexpected surface type %s.', self.name, self.aliases, surface.type)
|
||||
self._domestic_hot_water_peak_load = None
|
||||
self._fuel_consumption_breakdown = {}
|
||||
self._systems_archetype_cluster_id = None
|
||||
|
||||
@property
|
||||
def shell(self) -> Polyhedron:
|
||||
@ -450,8 +451,8 @@ class Building(CityObject):
|
||||
monthly_values = PeakLoads(self).heating_peak_loads_from_methodology
|
||||
if monthly_values is None:
|
||||
return None
|
||||
results[cte.MONTH] = [x * cte.WATTS_HOUR_TO_JULES for x in monthly_values]
|
||||
results[cte.YEAR] = [max(monthly_values)]
|
||||
results[cte.MONTH] = [x / cte.WATTS_HOUR_TO_JULES for x in monthly_values]
|
||||
results[cte.YEAR] = [max(monthly_values) / cte.WATTS_HOUR_TO_JULES]
|
||||
return results
|
||||
|
||||
@property
|
||||
@ -467,8 +468,8 @@ class Building(CityObject):
|
||||
monthly_values = PeakLoads(self).cooling_peak_loads_from_methodology
|
||||
if monthly_values is None:
|
||||
return None
|
||||
results[cte.MONTH] = [x * cte.WATTS_HOUR_TO_JULES for x in monthly_values]
|
||||
results[cte.YEAR] = [max(monthly_values)]
|
||||
results[cte.MONTH] = [x / cte.WATTS_HOUR_TO_JULES for x in monthly_values]
|
||||
results[cte.YEAR] = [max(monthly_values) / cte.WATTS_HOUR_TO_JULES]
|
||||
return results
|
||||
|
||||
@property
|
||||
@ -483,8 +484,8 @@ class Building(CityObject):
|
||||
monthly_values = PeakLoads().peak_loads_from_hourly(self.domestic_hot_water_heat_demand[cte.HOUR])
|
||||
if monthly_values is None:
|
||||
return None
|
||||
results[cte.MONTH] = [x for x in monthly_values]
|
||||
results[cte.YEAR] = [max(monthly_values)]
|
||||
results[cte.MONTH] = [x / cte.WATTS_HOUR_TO_JULES for x in monthly_values]
|
||||
results[cte.YEAR] = [max(monthly_values) / cte.WATTS_HOUR_TO_JULES]
|
||||
return results
|
||||
|
||||
@property
|
||||
@ -809,39 +810,16 @@ class Building(CityObject):
|
||||
Get total electricity produced onsite in J
|
||||
return: dict
|
||||
"""
|
||||
orientation_losses_factor = {cte.MONTH: {'north': [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
'east': [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
'south': [2.137931, 1.645503, 1.320946, 1.107817, 0.993213, 0.945175,
|
||||
0.967949, 1.065534, 1.24183, 1.486486, 1.918033, 2.210526],
|
||||
'west': [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]},
|
||||
cte.YEAR: {'north': [0],
|
||||
'east': [0],
|
||||
'south': [1.212544],
|
||||
'west': [0]}
|
||||
}
|
||||
|
||||
# Add other systems whenever new ones appear
|
||||
if self.energy_systems is None:
|
||||
return self._onsite_electrical_production
|
||||
for energy_system in self.energy_systems:
|
||||
for generation_system in energy_system.generation_systems:
|
||||
if generation_system.system_type == cte.PHOTOVOLTAIC:
|
||||
if generation_system.electricity_efficiency is not None:
|
||||
_efficiency = float(generation_system.electricity_efficiency)
|
||||
else:
|
||||
_efficiency = 0
|
||||
self._onsite_electrical_production = {}
|
||||
for _key in self.roofs[0].global_irradiance.keys():
|
||||
_results = [0 for _ in range(0, len(self.roofs[0].global_irradiance[_key]))]
|
||||
for surface in self.roofs:
|
||||
if _key in orientation_losses_factor:
|
||||
_results = [x + y * _efficiency * surface.perimeter_area
|
||||
* surface.solar_collectors_area_reduction_factor * z
|
||||
for x, y, z in zip(_results, surface.global_irradiance[_key],
|
||||
orientation_losses_factor[_key]['south'])]
|
||||
self._onsite_electrical_production[_key] = _results
|
||||
return self._onsite_electrical_production
|
||||
|
||||
@onsite_electrical_production.setter
|
||||
def onsite_electrical_production(self, value):
|
||||
"""
|
||||
set onsite electrical production from external pv simulations
|
||||
:return:
|
||||
"""
|
||||
self._onsite_electrical_production = value
|
||||
|
||||
@property
|
||||
def lower_corner(self):
|
||||
"""
|
||||
@ -862,51 +840,71 @@ class Building(CityObject):
|
||||
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]}}
|
||||
fuel_breakdown = {cte.ELECTRICITY: {cte.LIGHTING: self.lighting_electrical_demand[cte.YEAR][0] if self.lighting_electrical_demand else 0,
|
||||
cte.APPLIANCES: self.appliances_electrical_demand[cte.YEAR][0] if self.appliances_electrical_demand else 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:
|
||||
for generation_system in generation_systems:
|
||||
if generation_system.system_type != cte.PHOTOVOLTAIC:
|
||||
if generation_system.fuel_type not in fuel_breakdown:
|
||||
fuel_breakdown[generation_system.fuel_type] = {}
|
||||
if demand_type in generation_system.energy_consumption:
|
||||
fuel_breakdown[f'{generation_system.fuel_type}'][f'{demand_type}'] = (
|
||||
generation_system.energy_consumption)[f'{demand_type}'][cte.YEAR][0]
|
||||
#TODO: When simulation models of all energy system archetypes are created, this part can be removed
|
||||
heating = 0
|
||||
cooling = 0
|
||||
dhw = 0
|
||||
for key in fuel_breakdown:
|
||||
if cte.HEATING not in fuel_breakdown[key]:
|
||||
heating += 1
|
||||
if key == cte.ELECTRICITY and cte.COOLING not in fuel_breakdown[key]:
|
||||
cooling += 1
|
||||
if cte.DOMESTIC_HOT_WATER not in fuel_breakdown[key]:
|
||||
dhw += 1
|
||||
if heating > 0:
|
||||
if energy_systems is not None:
|
||||
for energy_system in energy_systems:
|
||||
demand_types = energy_system.demand_types
|
||||
generation_systems = energy_system.generation_systems
|
||||
for demand_type in demand_types:
|
||||
for generation_system in generation_systems:
|
||||
if generation_system.system_type != cte.PHOTOVOLTAIC:
|
||||
if generation_system.fuel_type not in fuel_breakdown:
|
||||
fuel_breakdown[generation_system.fuel_type] = {}
|
||||
if demand_type in generation_system.energy_consumption:
|
||||
fuel_breakdown[f'{generation_system.fuel_type}'][f'{demand_type}'] = (
|
||||
generation_system.energy_consumption)[f'{demand_type}'][cte.YEAR][0]
|
||||
storage_systems = generation_system.energy_storage_systems
|
||||
if storage_systems:
|
||||
for storage_system in storage_systems:
|
||||
if storage_system.type_energy_stored == 'thermal' and storage_system.heating_coil_energy_consumption:
|
||||
fuel_breakdown[cte.ELECTRICITY][f'{demand_type}'] += (
|
||||
storage_system.heating_coil_energy_consumption)[f'{demand_type}'][cte.YEAR][0]
|
||||
#TODO: When simulation models of all energy system archetypes are created, this part can be removed
|
||||
heating_fuels = []
|
||||
dhw_fuels = []
|
||||
for energy_system in self.energy_systems:
|
||||
if cte.HEATING in energy_system.demand_types:
|
||||
for generation_system in energy_system.generation_systems:
|
||||
fuel_breakdown[generation_system.fuel_type][cte.HEATING] = self.heating_consumption[cte.YEAR][0] / 3600
|
||||
if dhw > 0:
|
||||
for energy_system in energy_systems:
|
||||
heating_fuels.append(generation_system.fuel_type)
|
||||
if cte.DOMESTIC_HOT_WATER in energy_system.demand_types:
|
||||
for generation_system in energy_system.generation_systems:
|
||||
fuel_breakdown[generation_system.fuel_type][cte.DOMESTIC_HOT_WATER] = \
|
||||
self.domestic_hot_water_consumption[cte.YEAR][0] / 3600
|
||||
if cooling > 0:
|
||||
for energy_system in energy_systems:
|
||||
if cte.COOLING in energy_system.demand_types:
|
||||
for generation_system in energy_system.generation_systems:
|
||||
fuel_breakdown[generation_system.fuel_type][cte.COOLING] = self.cooling_consumption[cte.YEAR][0] / 3600
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
dhw_fuels.append(generation_system.fuel_type)
|
||||
for key in fuel_breakdown:
|
||||
if key == cte.ELECTRICITY and cte.COOLING not in fuel_breakdown[key]:
|
||||
for energy_system in energy_systems:
|
||||
if cte.COOLING in energy_system.demand_types and cte.COOLING not in fuel_breakdown[key]:
|
||||
if self.cooling_consumption:
|
||||
fuel_breakdown[energy_system.generation_systems[0].fuel_type][cte.COOLING] = self.cooling_consumption[cte.YEAR][0]
|
||||
for fuel in heating_fuels:
|
||||
if cte.HEATING not in fuel_breakdown[fuel]:
|
||||
for energy_system in energy_systems:
|
||||
if cte.HEATING in energy_system.demand_types:
|
||||
if self.heating_consumption:
|
||||
fuel_breakdown[energy_system.generation_systems[0].fuel_type][cte.HEATING] = self.heating_consumption[cte.YEAR][0]
|
||||
for fuel in dhw_fuels:
|
||||
if cte.DOMESTIC_HOT_WATER not in fuel_breakdown[fuel]:
|
||||
for energy_system in energy_systems:
|
||||
if cte.DOMESTIC_HOT_WATER in energy_system.demand_types:
|
||||
if self.domestic_hot_water_consumption:
|
||||
fuel_breakdown[energy_system.generation_systems[0].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
|
||||
|
||||
@property
|
||||
def energy_systems_archetype_cluster_id(self):
|
||||
"""
|
||||
Get energy systems archetype id
|
||||
:return: str
|
||||
"""
|
||||
return self._systems_archetype_cluster_id
|
||||
|
||||
@energy_systems_archetype_cluster_id.setter
|
||||
def energy_systems_archetype_cluster_id(self, value):
|
||||
"""
|
||||
Set energy systems archetype id
|
||||
:param value: str
|
||||
"""
|
||||
self._systems_archetype_cluster_id = value
|
||||
|
||||
|
@ -132,6 +132,8 @@ class InternalZone:
|
||||
_thermal_boundary = ThermalBoundary(surface, surface.solid_polygon.area, windows_areas)
|
||||
surface.associated_thermal_boundaries = [_thermal_boundary]
|
||||
_thermal_boundaries.append(_thermal_boundary)
|
||||
if self.thermal_archetype is None:
|
||||
return None # there are no archetype
|
||||
_number_of_storeys = int(self.volume / self.area / self.thermal_archetype.average_storey_height)
|
||||
_thermal_zone = ThermalZone(_thermal_boundaries, self, self.volume, self.area, _number_of_storeys)
|
||||
for thermal_boundary in _thermal_zone.thermal_boundaries:
|
||||
|
@ -42,10 +42,12 @@ class Surface:
|
||||
self._short_wave_reflectance = None
|
||||
self._long_wave_emittance = None
|
||||
self._inverse = None
|
||||
self._associated_thermal_boundaries = []
|
||||
self._associated_thermal_boundaries = None
|
||||
self._vegetation = None
|
||||
self._percentage_shared = None
|
||||
self._solar_collectors_area_reduction_factor = None
|
||||
self._global_irradiance_tilted = {}
|
||||
self._installed_solar_collector_area = None
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
@ -155,6 +157,7 @@ class Surface:
|
||||
if self._inclination is None:
|
||||
self._inclination = np.arccos(self.perimeter_polygon.normal[2])
|
||||
return self._inclination
|
||||
|
||||
@property
|
||||
def type(self):
|
||||
"""
|
||||
@ -178,7 +181,7 @@ class Surface:
|
||||
@property
|
||||
def global_irradiance(self) -> dict:
|
||||
"""
|
||||
Get global irradiance on surface in J/m2
|
||||
Get global irradiance on surface in W/m2
|
||||
:return: dict
|
||||
"""
|
||||
return self._global_irradiance
|
||||
@ -186,7 +189,7 @@ class Surface:
|
||||
@global_irradiance.setter
|
||||
def global_irradiance(self, value):
|
||||
"""
|
||||
Set global irradiance on surface in J/m2
|
||||
Set global irradiance on surface in W/m2
|
||||
:param value: dict
|
||||
"""
|
||||
self._global_irradiance = value
|
||||
@ -384,3 +387,35 @@ class Surface:
|
||||
:param value: float
|
||||
"""
|
||||
self._solar_collectors_area_reduction_factor = value
|
||||
|
||||
@property
|
||||
def global_irradiance_tilted(self) -> dict:
|
||||
"""
|
||||
Get global irradiance on a tilted surface in W/m2
|
||||
:return: dict
|
||||
"""
|
||||
return self._global_irradiance_tilted
|
||||
|
||||
@global_irradiance_tilted.setter
|
||||
def global_irradiance_tilted(self, value):
|
||||
"""
|
||||
Set global irradiance on a tilted surface in W/m2
|
||||
:param value: dict
|
||||
"""
|
||||
self._global_irradiance_tilted = value
|
||||
|
||||
@property
|
||||
def installed_solar_collector_area(self):
|
||||
"""
|
||||
Get installed solar collector area in m2
|
||||
:return: dict
|
||||
"""
|
||||
return self._installed_solar_collector_area
|
||||
|
||||
@installed_solar_collector_area.setter
|
||||
def installed_solar_collector_area(self, value):
|
||||
"""
|
||||
Set installed solar collector area in m2
|
||||
:return: dict
|
||||
"""
|
||||
self._installed_solar_collector_area = value
|
@ -20,6 +20,8 @@ class ThermalArchetype:
|
||||
self._indirect_heated_ratio = None
|
||||
self._infiltration_rate_for_ventilation_system_off = None
|
||||
self._infiltration_rate_for_ventilation_system_on = None
|
||||
self._infiltration_rate_area_for_ventilation_system_off = None
|
||||
self._infiltration_rate_area_for_ventilation_system_on = None
|
||||
|
||||
@property
|
||||
def constructions(self) -> [Construction]:
|
||||
@ -132,3 +134,35 @@ class ThermalArchetype:
|
||||
:param value: float
|
||||
"""
|
||||
self._infiltration_rate_for_ventilation_system_on = value
|
||||
|
||||
@property
|
||||
def infiltration_rate_area_for_ventilation_system_off(self):
|
||||
"""
|
||||
Get infiltration rate for ventilation system off in l/s/m2
|
||||
:return: float
|
||||
"""
|
||||
return self._infiltration_rate_area_for_ventilation_system_off
|
||||
|
||||
@infiltration_rate_area_for_ventilation_system_off.setter
|
||||
def infiltration_rate_area_for_ventilation_system_off(self, value):
|
||||
"""
|
||||
Set infiltration rate for ventilation system off in l/s/m2
|
||||
:param value: float
|
||||
"""
|
||||
self._infiltration_rate_area_for_ventilation_system_off = value
|
||||
|
||||
@property
|
||||
def infiltration_rate_area_for_ventilation_system_on(self):
|
||||
"""
|
||||
Get infiltration rate for ventilation system on in l/s/m2
|
||||
:return: float
|
||||
"""
|
||||
return self._infiltration_rate_area_for_ventilation_system_on
|
||||
|
||||
@infiltration_rate_area_for_ventilation_system_on.setter
|
||||
def infiltration_rate_area_for_ventilation_system_on(self, value):
|
||||
"""
|
||||
Set infiltration rate for ventilation system on in l/s/m2
|
||||
:param value: float
|
||||
"""
|
||||
self._infiltration_rate_area_for_ventilation_system_on = value
|
||||
|
@ -44,6 +44,8 @@ class ThermalZone:
|
||||
self._indirectly_heated_area_ratio = None
|
||||
self._infiltration_rate_system_on = None
|
||||
self._infiltration_rate_system_off = None
|
||||
self._infiltration_rate_area_system_on = None
|
||||
self._infiltration_rate_area_system_off = None
|
||||
self._volume = volume
|
||||
self._ordinate_number = None
|
||||
self._view_factors_matrix = None
|
||||
@ -166,6 +168,24 @@ class ThermalZone:
|
||||
self._infiltration_rate_system_off = self._parent_internal_zone.thermal_archetype.infiltration_rate_for_ventilation_system_off
|
||||
return self._infiltration_rate_system_off
|
||||
|
||||
@property
|
||||
def infiltration_rate_area_system_on(self):
|
||||
"""
|
||||
Get thermal zone infiltration rate system on in air changes per second (1/s)
|
||||
:return: None or float
|
||||
"""
|
||||
self._infiltration_rate_area_system_on = self._parent_internal_zone.thermal_archetype.infiltration_rate_area_for_ventilation_system_on
|
||||
return self._infiltration_rate_area_system_on
|
||||
|
||||
@property
|
||||
def infiltration_rate_area_system_off(self):
|
||||
"""
|
||||
Get thermal zone infiltration rate system off in air changes per second (1/s)
|
||||
:return: None or float
|
||||
"""
|
||||
self._infiltration_rate_area_system_off = self._parent_internal_zone.thermal_archetype.infiltration_rate_area_for_ventilation_system_off
|
||||
return self._infiltration_rate_area_system_off
|
||||
|
||||
@property
|
||||
def volume(self):
|
||||
"""
|
||||
|
@ -41,9 +41,10 @@ class CityObject:
|
||||
self._ground_temperature = {}
|
||||
self._global_horizontal = {}
|
||||
self._diffuse = {}
|
||||
self._beam = {}
|
||||
self._direct_normal = {}
|
||||
self._sensors = []
|
||||
self._neighbours = None
|
||||
self._beam = {}
|
||||
|
||||
@property
|
||||
def level_of_detail(self) -> LevelOfDetail:
|
||||
@ -238,20 +239,20 @@ class CityObject:
|
||||
self._diffuse = value
|
||||
|
||||
@property
|
||||
def beam(self) -> dict:
|
||||
def direct_normal(self) -> dict:
|
||||
"""
|
||||
Get beam radiation surrounding the city object in J/m2
|
||||
:return: dict{dict{[float]}}
|
||||
"""
|
||||
return self._beam
|
||||
return self._direct_normal
|
||||
|
||||
@beam.setter
|
||||
def beam(self, value):
|
||||
@direct_normal.setter
|
||||
def direct_normal(self, value):
|
||||
"""
|
||||
Set beam radiation surrounding the city object in J/m2
|
||||
:param value: dict{dict{[float]}}
|
||||
"""
|
||||
self._beam = value
|
||||
self._direct_normal = value
|
||||
|
||||
@property
|
||||
def lower_corner(self):
|
||||
@ -302,3 +303,19 @@ class CityObject:
|
||||
Set the list of neighbour_objects and their properties associated to the current city_object
|
||||
"""
|
||||
self._neighbours = value
|
||||
|
||||
@property
|
||||
def beam(self) -> dict:
|
||||
"""
|
||||
Get beam radiation surrounding the city object in J/m2
|
||||
:return: dict{dict{[float]}}
|
||||
"""
|
||||
return self._beam
|
||||
|
||||
@beam.setter
|
||||
def beam(self, value):
|
||||
"""
|
||||
Set beam radiation surrounding the city object in J/m2
|
||||
:param value: dict{dict{[float]}}
|
||||
"""
|
||||
self._beam = value
|
||||
|
@ -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):
|
||||
|
@ -26,6 +26,10 @@ class PvGenerationSystem(GenerationSystem):
|
||||
self._width = None
|
||||
self._height = None
|
||||
self._electricity_power = None
|
||||
self._tilt_angle = None
|
||||
self._surface_azimuth = None
|
||||
self._solar_altitude_angle = None
|
||||
self._solar_azimuth_angle = None
|
||||
|
||||
@property
|
||||
def nominal_electricity_output(self):
|
||||
@ -202,3 +206,35 @@ class PvGenerationSystem(GenerationSystem):
|
||||
:param value: float
|
||||
"""
|
||||
self._electricity_power = value
|
||||
|
||||
@property
|
||||
def tilt_angle(self):
|
||||
"""
|
||||
Get tilt angle of PV system in degrees
|
||||
:return: float
|
||||
"""
|
||||
return self._tilt_angle
|
||||
|
||||
@tilt_angle.setter
|
||||
def tilt_angle(self, value):
|
||||
"""
|
||||
Set PV system tilt angle in degrees
|
||||
:param value: float
|
||||
"""
|
||||
self._tilt_angle = value
|
||||
|
||||
@property
|
||||
def surface_azimuth(self):
|
||||
"""
|
||||
Get surface azimuth angle of PV system in degrees. 0 is North
|
||||
:return: float
|
||||
"""
|
||||
return self._surface_azimuth
|
||||
|
||||
@surface_azimuth.setter
|
||||
def surface_azimuth(self, value):
|
||||
"""
|
||||
Set PV system tilt angle in degrees
|
||||
:param value: float
|
||||
"""
|
||||
self._surface_azimuth = value
|
||||
|
@ -24,6 +24,7 @@ class ThermalStorageSystem(EnergyStorageSystem):
|
||||
self._maximum_operating_temperature = None
|
||||
self._heating_coil_capacity = None
|
||||
self._temperature = None
|
||||
self._heating_coil_energy_consumption = {}
|
||||
|
||||
@property
|
||||
def volume(self):
|
||||
@ -95,7 +96,7 @@ class ThermalStorageSystem(EnergyStorageSystem):
|
||||
Get heating coil capacity in Watts
|
||||
:return: float
|
||||
"""
|
||||
return self._maximum_operating_temperature
|
||||
return self._heating_coil_capacity
|
||||
|
||||
@heating_coil_capacity.setter
|
||||
def heating_coil_capacity(self, value):
|
||||
@ -120,3 +121,19 @@ class ThermalStorageSystem(EnergyStorageSystem):
|
||||
:param value: dict{[float]}
|
||||
"""
|
||||
self._temperature = value
|
||||
|
||||
@property
|
||||
def heating_coil_energy_consumption(self) -> dict:
|
||||
"""
|
||||
Get fuel consumption in W, m3, or kg
|
||||
:return: dict{[float]}
|
||||
"""
|
||||
return self._heating_coil_energy_consumption
|
||||
|
||||
@heating_coil_energy_consumption.setter
|
||||
def heating_coil_energy_consumption(self, value):
|
||||
"""
|
||||
Set fuel consumption in W, m3, or kg
|
||||
:param value: dict{[float]}
|
||||
"""
|
||||
self._heating_coil_energy_consumption = value
|
||||
|
@ -8,6 +8,8 @@
|
||||
"extra_loses_due_thermal_bridges": 0.1,
|
||||
"infiltration_rate_for_ventilation_system_on": 0,
|
||||
"infiltration_rate_for_ventilation_system_off": 0.9,
|
||||
"infiltration_rate_area_for_ventilation_system_on": 0,
|
||||
"infiltration_rate_area_for_ventilation_system_off": 0.006,
|
||||
"constructions": {
|
||||
"OutdoorsWall": {
|
||||
"opaque_surface_name": "residential_1000_1980_BWh",
|
||||
@ -42,6 +44,8 @@
|
||||
"extra_loses_due_thermal_bridges": 0.1,
|
||||
"infiltration_rate_for_ventilation_system_on": 0,
|
||||
"infiltration_rate_for_ventilation_system_off": 0.31,
|
||||
"infiltration_rate_area_for_ventilation_system_on": 0,
|
||||
"infiltration_rate_area_for_ventilation_system_off": 0.002,
|
||||
"constructions": {
|
||||
"OutdoorsWall": {
|
||||
"opaque_surface_name": "dormitory_2011_3000_BWh",
|
||||
@ -76,6 +80,8 @@
|
||||
"extra_loses_due_thermal_bridges": 0.09,
|
||||
"infiltration_rate_for_ventilation_system_on": 0,
|
||||
"infiltration_rate_for_ventilation_system_off": 0.65,
|
||||
"infiltration_rate_area_for_ventilation_system_on": 0,
|
||||
"infiltration_rate_area_for_ventilation_system_off": 0.004,
|
||||
"constructions": {
|
||||
"OutdoorsWall": {
|
||||
"opaque_surface_name": "hotel_employees_1981_2010_BWh",
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -21,6 +21,8 @@
|
||||
<indirect_heated_ratio units="-">0.15</indirect_heated_ratio>
|
||||
<infiltration_rate_for_ventilation_system_off units="ACH">0.5</infiltration_rate_for_ventilation_system_off>
|
||||
<infiltration_rate_for_ventilation_system_on units="ACH">0</infiltration_rate_for_ventilation_system_on>
|
||||
<infiltration_rate_area_for_ventilation_system_off units="ACH">0.003</infiltration_rate_area_for_ventilation_system_off>
|
||||
<infiltration_rate_area_for_ventilation_system_on units="ACH">0</infiltration_rate_area_for_ventilation_system_on>
|
||||
</archetype>
|
||||
<archetype id="2" building_type="medium office" reference_standard="ASHRAE 90.1_2004" climate_zone="ASHRAE_2004:4A">
|
||||
<constructions>
|
||||
@ -44,6 +46,8 @@
|
||||
<indirect_heated_ratio units="-">0.15</indirect_heated_ratio>
|
||||
<infiltration_rate_for_ventilation_system_off units="ACH">0.50</infiltration_rate_for_ventilation_system_off>
|
||||
<infiltration_rate_for_ventilation_system_on units="ACH">0</infiltration_rate_for_ventilation_system_on>
|
||||
<infiltration_rate_area_for_ventilation_system_off units="ACH">0.003</infiltration_rate_area_for_ventilation_system_off>
|
||||
<infiltration_rate_area_for_ventilation_system_on units="ACH">0</infiltration_rate_area_for_ventilation_system_on>
|
||||
</archetype>
|
||||
<archetype id="3" building_type="large office" reference_standard="ASHRAE 90.1_2004" climate_zone="ASHRAE_2004:4A">
|
||||
<constructions>
|
||||
@ -67,6 +71,8 @@
|
||||
<indirect_heated_ratio units="-">0.15</indirect_heated_ratio>
|
||||
<infiltration_rate_for_ventilation_system_off units="ACH">0.50</infiltration_rate_for_ventilation_system_off>
|
||||
<infiltration_rate_for_ventilation_system_on units="ACH">0</infiltration_rate_for_ventilation_system_on>
|
||||
<infiltration_rate_area_for_ventilation_system_off units="ACH">0.003</infiltration_rate_area_for_ventilation_system_off>
|
||||
<infiltration_rate_area_for_ventilation_system_on units="ACH">0</infiltration_rate_area_for_ventilation_system_on>
|
||||
</archetype>
|
||||
<archetype id="4" building_type="primary school" reference_standard="ASHRAE 90.1_2004" climate_zone="ASHRAE_2004:4A">
|
||||
<constructions>
|
||||
@ -89,6 +95,8 @@
|
||||
<indirect_heated_ratio units="-">0.15</indirect_heated_ratio>
|
||||
<infiltration_rate_for_ventilation_system_off units="ACH">0.50</infiltration_rate_for_ventilation_system_off>
|
||||
<infiltration_rate_for_ventilation_system_on units="ACH">0</infiltration_rate_for_ventilation_system_on>
|
||||
<infiltration_rate_area_for_ventilation_system_off units="ACH">0.003</infiltration_rate_area_for_ventilation_system_off>
|
||||
<infiltration_rate_area_for_ventilation_system_on units="ACH">0</infiltration_rate_area_for_ventilation_system_on>
|
||||
</archetype>
|
||||
<archetype id="5" building_type="secondary school" reference_standard="ASHRAE 90.1_2004" climate_zone="ASHRAE_2004:4A">
|
||||
<constructions>
|
||||
@ -111,6 +119,8 @@
|
||||
<indirect_heated_ratio units="-">0.15</indirect_heated_ratio>
|
||||
<infiltration_rate_for_ventilation_system_off units="ACH">0.50</infiltration_rate_for_ventilation_system_off>
|
||||
<infiltration_rate_for_ventilation_system_on units="ACH">0</infiltration_rate_for_ventilation_system_on>
|
||||
<infiltration_rate_area_for_ventilation_system_off units="ACH">0.003</infiltration_rate_area_for_ventilation_system_off>
|
||||
<infiltration_rate_area_for_ventilation_system_on units="ACH">0</infiltration_rate_area_for_ventilation_system_on>
|
||||
</archetype>
|
||||
<archetype id="6" building_type="stand-alone retail" reference_standard="ASHRAE 90.1_2004" climate_zone="ASHRAE_2004:4A">
|
||||
<constructions>
|
||||
@ -133,6 +143,8 @@
|
||||
<indirect_heated_ratio units="-">0.15</indirect_heated_ratio>
|
||||
<infiltration_rate_for_ventilation_system_off units="ACH">0.50</infiltration_rate_for_ventilation_system_off>
|
||||
<infiltration_rate_for_ventilation_system_on units="ACH">0</infiltration_rate_for_ventilation_system_on>
|
||||
<infiltration_rate_area_for_ventilation_system_off units="ACH">0.003</infiltration_rate_area_for_ventilation_system_off>
|
||||
<infiltration_rate_area_for_ventilation_system_on units="ACH">0</infiltration_rate_area_for_ventilation_system_on>
|
||||
</archetype>
|
||||
<archetype id="7" building_type="strip mall" reference_standard="ASHRAE 90.1_2004" climate_zone="ASHRAE_2004:4A">
|
||||
<constructions>
|
||||
@ -155,6 +167,8 @@
|
||||
<indirect_heated_ratio units="-">0.15</indirect_heated_ratio>
|
||||
<infiltration_rate_for_ventilation_system_off units="ACH">0.50</infiltration_rate_for_ventilation_system_off>
|
||||
<infiltration_rate_for_ventilation_system_on units="ACH">0</infiltration_rate_for_ventilation_system_on>
|
||||
<infiltration_rate_area_for_ventilation_system_off units="ACH">0.003</infiltration_rate_area_for_ventilation_system_off>
|
||||
<infiltration_rate_area_for_ventilation_system_on units="ACH">0</infiltration_rate_area_for_ventilation_system_on>
|
||||
</archetype>
|
||||
<archetype id="8" building_type="supermarket" reference_standard="ASHRAE 90.1_2004" climate_zone="ASHRAE_2004:4A">
|
||||
<constructions>
|
||||
@ -177,6 +191,8 @@
|
||||
<indirect_heated_ratio units="-">0.15</indirect_heated_ratio>
|
||||
<infiltration_rate_for_ventilation_system_off units="ACH">0.50</infiltration_rate_for_ventilation_system_off>
|
||||
<infiltration_rate_for_ventilation_system_on units="ACH">0</infiltration_rate_for_ventilation_system_on>
|
||||
<infiltration_rate_area_for_ventilation_system_off units="ACH">0.003</infiltration_rate_area_for_ventilation_system_off>
|
||||
<infiltration_rate_area_for_ventilation_system_on units="ACH">0</infiltration_rate_area_for_ventilation_system_on>
|
||||
</archetype>
|
||||
<archetype id="9" building_type="quick service restaurant" reference_standard="ASHRAE 90.1_2004" climate_zone="ASHRAE_2004:4A">
|
||||
<constructions>
|
||||
@ -199,6 +215,8 @@
|
||||
<indirect_heated_ratio units="-">0.15</indirect_heated_ratio>
|
||||
<infiltration_rate_for_ventilation_system_off units="ACH">0.50</infiltration_rate_for_ventilation_system_off>
|
||||
<infiltration_rate_for_ventilation_system_on units="ACH">0</infiltration_rate_for_ventilation_system_on>
|
||||
<infiltration_rate_area_for_ventilation_system_off units="ACH">0.003</infiltration_rate_area_for_ventilation_system_off>
|
||||
<infiltration_rate_area_for_ventilation_system_on units="ACH">0</infiltration_rate_area_for_ventilation_system_on>
|
||||
</archetype>
|
||||
<archetype id="10" building_type="full service restaurant" reference_standard="ASHRAE 90.1_2004" climate_zone="ASHRAE_2004:4A">
|
||||
<constructions>
|
||||
@ -221,6 +239,8 @@
|
||||
<indirect_heated_ratio units="-">0.15</indirect_heated_ratio>
|
||||
<infiltration_rate_for_ventilation_system_off units="ACH">0.50</infiltration_rate_for_ventilation_system_off>
|
||||
<infiltration_rate_for_ventilation_system_on units="ACH">0</infiltration_rate_for_ventilation_system_on>
|
||||
<infiltration_rate_area_for_ventilation_system_off units="ACH">0.003</infiltration_rate_area_for_ventilation_system_off>
|
||||
<infiltration_rate_area_for_ventilation_system_on units="ACH">0</infiltration_rate_area_for_ventilation_system_on>
|
||||
</archetype>
|
||||
<archetype id="11" building_type="small hotel" reference_standard="ASHRAE 90.1_2004" climate_zone="ASHRAE_2004:4A">
|
||||
<constructions>
|
||||
@ -243,6 +263,8 @@
|
||||
<indirect_heated_ratio units="-">0.15</indirect_heated_ratio>
|
||||
<infiltration_rate_for_ventilation_system_off units="ACH">0.50</infiltration_rate_for_ventilation_system_off>
|
||||
<infiltration_rate_for_ventilation_system_on units="ACH">0</infiltration_rate_for_ventilation_system_on>
|
||||
<infiltration_rate_area_for_ventilation_system_off units="ACH">0.003</infiltration_rate_area_for_ventilation_system_off>
|
||||
<infiltration_rate_area_for_ventilation_system_on units="ACH">0</infiltration_rate_area_for_ventilation_system_on>
|
||||
</archetype>
|
||||
<archetype id="12" building_type="large hotel" reference_standard="ASHRAE 90.1_2004" climate_zone="ASHRAE_2004:4A">
|
||||
<constructions>
|
||||
@ -265,6 +287,8 @@
|
||||
<indirect_heated_ratio units="-">0.15</indirect_heated_ratio>
|
||||
<infiltration_rate_for_ventilation_system_off units="ACH">0.50</infiltration_rate_for_ventilation_system_off>
|
||||
<infiltration_rate_for_ventilation_system_on units="ACH">0</infiltration_rate_for_ventilation_system_on>
|
||||
<infiltration_rate_area_for_ventilation_system_off units="ACH">0.003</infiltration_rate_area_for_ventilation_system_off>
|
||||
<infiltration_rate_area_for_ventilation_system_on units="ACH">0</infiltration_rate_area_for_ventilation_system_on>
|
||||
</archetype>
|
||||
<archetype id="13" building_type="hospital" reference_standard="ASHRAE 90.1_2004" climate_zone="ASHRAE_2004:4A">
|
||||
<constructions>
|
||||
@ -287,6 +311,8 @@
|
||||
<indirect_heated_ratio units="-">0.15</indirect_heated_ratio>
|
||||
<infiltration_rate_for_ventilation_system_off units="ACH">0.50</infiltration_rate_for_ventilation_system_off>
|
||||
<infiltration_rate_for_ventilation_system_on units="ACH">0</infiltration_rate_for_ventilation_system_on>
|
||||
<infiltration_rate_area_for_ventilation_system_off units="ACH">0.003</infiltration_rate_area_for_ventilation_system_off>
|
||||
<infiltration_rate_area_for_ventilation_system_on units="ACH">0</infiltration_rate_area_for_ventilation_system_on>
|
||||
</archetype>
|
||||
<archetype id="14" building_type="outpatient healthcare" reference_standard="ASHRAE 90.1_2004" climate_zone="ASHRAE_2004:4A">
|
||||
<constructions>
|
||||
@ -309,6 +335,8 @@
|
||||
<indirect_heated_ratio units="-">0.15</indirect_heated_ratio>
|
||||
<infiltration_rate_for_ventilation_system_off units="ACH">0.50</infiltration_rate_for_ventilation_system_off>
|
||||
<infiltration_rate_for_ventilation_system_on units="ACH">0</infiltration_rate_for_ventilation_system_on>
|
||||
<infiltration_rate_area_for_ventilation_system_off units="ACH">0.003</infiltration_rate_area_for_ventilation_system_off>
|
||||
<infiltration_rate_area_for_ventilation_system_on units="ACH">0</infiltration_rate_area_for_ventilation_system_on>
|
||||
</archetype>
|
||||
<archetype id="15" building_type="warehouse" reference_standard="ASHRAE 90.1_2004" climate_zone="ASHRAE_2004:4A">
|
||||
<constructions>
|
||||
@ -331,6 +359,8 @@
|
||||
<indirect_heated_ratio units="-">0.15</indirect_heated_ratio>
|
||||
<infiltration_rate_for_ventilation_system_off units="ACH">0.50</infiltration_rate_for_ventilation_system_off>
|
||||
<infiltration_rate_for_ventilation_system_on units="ACH">0</infiltration_rate_for_ventilation_system_on>
|
||||
<infiltration_rate_area_for_ventilation_system_off units="ACH">0.003</infiltration_rate_area_for_ventilation_system_off>
|
||||
<infiltration_rate_area_for_ventilation_system_on units="ACH">0</infiltration_rate_area_for_ventilation_system_on>
|
||||
</archetype>
|
||||
<archetype id="16" building_type="midrise apartment" reference_standard="ASHRAE 90.1_2004" climate_zone="ASHRAE_2004:4A">
|
||||
<constructions>
|
||||
@ -353,6 +383,8 @@
|
||||
<indirect_heated_ratio units="-">0.15</indirect_heated_ratio>
|
||||
<infiltration_rate_for_ventilation_system_off units="ACH">0.50</infiltration_rate_for_ventilation_system_off>
|
||||
<infiltration_rate_for_ventilation_system_on units="ACH">0</infiltration_rate_for_ventilation_system_on>
|
||||
<infiltration_rate_area_for_ventilation_system_off units="ACH">0.003</infiltration_rate_area_for_ventilation_system_off>
|
||||
<infiltration_rate_area_for_ventilation_system_on units="ACH">0</infiltration_rate_area_for_ventilation_system_on>
|
||||
</archetype>
|
||||
<archetype id="17" building_type="high-rise apartment" reference_standard="ASHRAE 90.1_2004" climate_zone="ASHRAE_2004:4A">
|
||||
<constructions>
|
||||
@ -375,6 +407,8 @@
|
||||
<indirect_heated_ratio units="-">0.15</indirect_heated_ratio>
|
||||
<infiltration_rate_for_ventilation_system_off units="ACH">0.50</infiltration_rate_for_ventilation_system_off>
|
||||
<infiltration_rate_for_ventilation_system_on units="ACH">0</infiltration_rate_for_ventilation_system_on>
|
||||
<infiltration_rate_area_for_ventilation_system_off units="ACH">0.003</infiltration_rate_area_for_ventilation_system_off>
|
||||
<infiltration_rate_area_for_ventilation_system_on units="ACH">0</infiltration_rate_area_for_ventilation_system_on>
|
||||
</archetype>
|
||||
<archetype id="18" building_type="small office" reference_standard="ASHRAE 189.1_2009" climate_zone="ASHRAE_2004:4A">
|
||||
<constructions>
|
||||
@ -397,6 +431,8 @@
|
||||
<indirect_heated_ratio units="-">0.15</indirect_heated_ratio>
|
||||
<infiltration_rate_for_ventilation_system_off units="ACH">0.1</infiltration_rate_for_ventilation_system_off>
|
||||
<infiltration_rate_for_ventilation_system_on units="ACH">0</infiltration_rate_for_ventilation_system_on>
|
||||
<infiltration_rate_area_for_ventilation_system_off units="ACH">0.0005</infiltration_rate_area_for_ventilation_system_off>
|
||||
<infiltration_rate_area_for_ventilation_system_on units="ACH">0</infiltration_rate_area_for_ventilation_system_on>
|
||||
</archetype>
|
||||
<archetype id="19" building_type="medium office" reference_standard="ASHRAE 189.1_2009" climate_zone="ASHRAE_2004:4A">
|
||||
<constructions>
|
||||
@ -419,6 +455,8 @@
|
||||
<indirect_heated_ratio units="-">0.15</indirect_heated_ratio>
|
||||
<infiltration_rate_for_ventilation_system_off units="ACH">0.1</infiltration_rate_for_ventilation_system_off>
|
||||
<infiltration_rate_for_ventilation_system_on units="ACH">0</infiltration_rate_for_ventilation_system_on>
|
||||
<infiltration_rate_area_for_ventilation_system_off units="ACH">0.0005</infiltration_rate_area_for_ventilation_system_off>
|
||||
<infiltration_rate_area_for_ventilation_system_on units="ACH">0</infiltration_rate_area_for_ventilation_system_on>
|
||||
</archetype>
|
||||
<archetype id="20" building_type="large office" reference_standard="ASHRAE 189.1_2009" climate_zone="ASHRAE_2004:4A">
|
||||
<constructions>
|
||||
@ -441,6 +479,8 @@
|
||||
<indirect_heated_ratio units="-">0.15</indirect_heated_ratio>
|
||||
<infiltration_rate_for_ventilation_system_off units="ACH">0.1</infiltration_rate_for_ventilation_system_off>
|
||||
<infiltration_rate_for_ventilation_system_on units="ACH">0</infiltration_rate_for_ventilation_system_on>
|
||||
<infiltration_rate_area_for_ventilation_system_off units="ACH">0.0005</infiltration_rate_area_for_ventilation_system_off>
|
||||
<infiltration_rate_area_for_ventilation_system_on units="ACH">0</infiltration_rate_area_for_ventilation_system_on>
|
||||
</archetype>
|
||||
<archetype id="21" building_type="primary school" reference_standard="ASHRAE 189.1_2009" climate_zone="ASHRAE_2004:4A">
|
||||
<constructions>
|
||||
@ -463,6 +503,8 @@
|
||||
<indirect_heated_ratio units="-">0.15</indirect_heated_ratio>
|
||||
<infiltration_rate_for_ventilation_system_off units="ACH">0.1</infiltration_rate_for_ventilation_system_off>
|
||||
<infiltration_rate_for_ventilation_system_on units="ACH">0</infiltration_rate_for_ventilation_system_on>
|
||||
<infiltration_rate_area_for_ventilation_system_off units="ACH">0.0005</infiltration_rate_area_for_ventilation_system_off>
|
||||
<infiltration_rate_area_for_ventilation_system_on units="ACH">0</infiltration_rate_area_for_ventilation_system_on>
|
||||
</archetype>
|
||||
<archetype id="22" building_type="secondary school" reference_standard="ASHRAE 189.1_2009" climate_zone="ASHRAE_2004:4A">
|
||||
<constructions>
|
||||
@ -485,6 +527,8 @@
|
||||
<indirect_heated_ratio units="-">0.15</indirect_heated_ratio>
|
||||
<infiltration_rate_for_ventilation_system_off units="ACH">0.1</infiltration_rate_for_ventilation_system_off>
|
||||
<infiltration_rate_for_ventilation_system_on units="ACH">0</infiltration_rate_for_ventilation_system_on>
|
||||
<infiltration_rate_area_for_ventilation_system_off units="ACH">0.0005</infiltration_rate_area_for_ventilation_system_off>
|
||||
<infiltration_rate_area_for_ventilation_system_on units="ACH">0</infiltration_rate_area_for_ventilation_system_on>
|
||||
</archetype>
|
||||
<archetype id="23" building_type="stand-alone retail" reference_standard="ASHRAE 189.1_2009" climate_zone="ASHRAE_2004:4A">
|
||||
<constructions>
|
||||
@ -507,6 +551,8 @@
|
||||
<indirect_heated_ratio units="-">0.15</indirect_heated_ratio>
|
||||
<infiltration_rate_for_ventilation_system_off units="ACH">0.1</infiltration_rate_for_ventilation_system_off>
|
||||
<infiltration_rate_for_ventilation_system_on units="ACH">0</infiltration_rate_for_ventilation_system_on>
|
||||
<infiltration_rate_area_for_ventilation_system_off units="ACH">0.0005</infiltration_rate_area_for_ventilation_system_off>
|
||||
<infiltration_rate_area_for_ventilation_system_on units="ACH">0</infiltration_rate_area_for_ventilation_system_on>
|
||||
</archetype>
|
||||
<archetype id="24" building_type="strip mall" reference_standard="ASHRAE 189.1_2009" climate_zone="ASHRAE_2004:4A">
|
||||
<constructions>
|
||||
@ -529,6 +575,8 @@
|
||||
<indirect_heated_ratio units="-">0.15</indirect_heated_ratio>
|
||||
<infiltration_rate_for_ventilation_system_off units="ACH">0.1</infiltration_rate_for_ventilation_system_off>
|
||||
<infiltration_rate_for_ventilation_system_on units="ACH">0</infiltration_rate_for_ventilation_system_on>
|
||||
<infiltration_rate_area_for_ventilation_system_off units="ACH">0.0005</infiltration_rate_area_for_ventilation_system_off>
|
||||
<infiltration_rate_area_for_ventilation_system_on units="ACH">0</infiltration_rate_area_for_ventilation_system_on>
|
||||
</archetype>
|
||||
<archetype id="25" building_type="supermarket" reference_standard="ASHRAE 189.1_2009" climate_zone="ASHRAE_2004:4A">
|
||||
<constructions>
|
||||
@ -551,6 +599,8 @@
|
||||
<indirect_heated_ratio units="-">0.15</indirect_heated_ratio>
|
||||
<infiltration_rate_for_ventilation_system_off units="ACH">0.10</infiltration_rate_for_ventilation_system_off>
|
||||
<infiltration_rate_for_ventilation_system_on units="ACH">0</infiltration_rate_for_ventilation_system_on>
|
||||
<infiltration_rate_area_for_ventilation_system_off units="ACH">0.0005</infiltration_rate_area_for_ventilation_system_off>
|
||||
<infiltration_rate_area_for_ventilation_system_on units="ACH">0</infiltration_rate_area_for_ventilation_system_on>
|
||||
</archetype>
|
||||
<archetype id="26" building_type="quick service restaurant" reference_standard="ASHRAE 189.1_2009" climate_zone="ASHRAE_2004:4A">
|
||||
<constructions>
|
||||
@ -573,6 +623,8 @@
|
||||
<indirect_heated_ratio units="-">0.15</indirect_heated_ratio>
|
||||
<infiltration_rate_for_ventilation_system_off units="ACH">0.10</infiltration_rate_for_ventilation_system_off>
|
||||
<infiltration_rate_for_ventilation_system_on units="ACH">0</infiltration_rate_for_ventilation_system_on>
|
||||
<infiltration_rate_area_for_ventilation_system_off units="ACH">0.0005</infiltration_rate_area_for_ventilation_system_off>
|
||||
<infiltration_rate_area_for_ventilation_system_on units="ACH">0</infiltration_rate_area_for_ventilation_system_on>
|
||||
</archetype>
|
||||
<archetype id="27" building_type="full service restaurant" reference_standard="ASHRAE 189.1_2009" climate_zone="ASHRAE_2004:4A">
|
||||
<constructions>
|
||||
@ -595,6 +647,8 @@
|
||||
<indirect_heated_ratio units="-">0.15</indirect_heated_ratio>
|
||||
<infiltration_rate_for_ventilation_system_off units="ACH">0.10</infiltration_rate_for_ventilation_system_off>
|
||||
<infiltration_rate_for_ventilation_system_on units="ACH">0</infiltration_rate_for_ventilation_system_on>
|
||||
<infiltration_rate_area_for_ventilation_system_off units="ACH">0.0005</infiltration_rate_area_for_ventilation_system_off>
|
||||
<infiltration_rate_area_for_ventilation_system_on units="ACH">0</infiltration_rate_area_for_ventilation_system_on>
|
||||
</archetype>
|
||||
<archetype id="28" building_type="small hotel" reference_standard="ASHRAE 189.1_2009" climate_zone="ASHRAE_2004:4A">
|
||||
<constructions>
|
||||
@ -617,6 +671,8 @@
|
||||
<indirect_heated_ratio units="-">0.15</indirect_heated_ratio>
|
||||
<infiltration_rate_for_ventilation_system_off units="ACH">0.10</infiltration_rate_for_ventilation_system_off>
|
||||
<infiltration_rate_for_ventilation_system_on units="ACH">0</infiltration_rate_for_ventilation_system_on>
|
||||
<infiltration_rate_area_for_ventilation_system_off units="ACH">0.0005</infiltration_rate_area_for_ventilation_system_off>
|
||||
<infiltration_rate_area_for_ventilation_system_on units="ACH">0</infiltration_rate_area_for_ventilation_system_on>
|
||||
</archetype>
|
||||
<archetype id="29" building_type="large hotel" reference_standard="ASHRAE 189.1_2009" climate_zone="ASHRAE_2004:4A">
|
||||
<constructions>
|
||||
@ -639,6 +695,8 @@
|
||||
<indirect_heated_ratio units="-">0.15</indirect_heated_ratio>
|
||||
<infiltration_rate_for_ventilation_system_off units="ACH">0.10</infiltration_rate_for_ventilation_system_off>
|
||||
<infiltration_rate_for_ventilation_system_on units="ACH">0</infiltration_rate_for_ventilation_system_on>
|
||||
<infiltration_rate_area_for_ventilation_system_off units="ACH">0.0005</infiltration_rate_area_for_ventilation_system_off>
|
||||
<infiltration_rate_area_for_ventilation_system_on units="ACH">0</infiltration_rate_area_for_ventilation_system_on>
|
||||
</archetype>
|
||||
<archetype id="30" building_type="hospital" reference_standard="ASHRAE 189.1_2009" climate_zone="ASHRAE_2004:4A">
|
||||
<constructions>
|
||||
@ -661,6 +719,8 @@
|
||||
<indirect_heated_ratio units="-">0.15</indirect_heated_ratio>
|
||||
<infiltration_rate_for_ventilation_system_off units="ACH">0.10</infiltration_rate_for_ventilation_system_off>
|
||||
<infiltration_rate_for_ventilation_system_on units="ACH">0</infiltration_rate_for_ventilation_system_on>
|
||||
<infiltration_rate_area_for_ventilation_system_off units="ACH">0.0005</infiltration_rate_area_for_ventilation_system_off>
|
||||
<infiltration_rate_area_for_ventilation_system_on units="ACH">0</infiltration_rate_area_for_ventilation_system_on>
|
||||
</archetype>
|
||||
<archetype id="31" building_type="outpatient healthcare" reference_standard="ASHRAE 189.1_2009" climate_zone="ASHRAE_2004:4A">
|
||||
<constructions>
|
||||
@ -683,6 +743,8 @@
|
||||
<indirect_heated_ratio units="-">0.15</indirect_heated_ratio>
|
||||
<infiltration_rate_for_ventilation_system_off units="ACH">0.10</infiltration_rate_for_ventilation_system_off>
|
||||
<infiltration_rate_for_ventilation_system_on units="ACH">0</infiltration_rate_for_ventilation_system_on>
|
||||
<infiltration_rate_area_for_ventilation_system_off units="ACH">0.0005</infiltration_rate_area_for_ventilation_system_off>
|
||||
<infiltration_rate_area_for_ventilation_system_on units="ACH">0</infiltration_rate_area_for_ventilation_system_on>
|
||||
</archetype>
|
||||
<archetype id="32" building_type="warehouse" reference_standard="ASHRAE 189.1_2009" climate_zone="ASHRAE_2004:4A">
|
||||
<constructions>
|
||||
@ -705,6 +767,8 @@
|
||||
<indirect_heated_ratio units="-">0.15</indirect_heated_ratio>
|
||||
<infiltration_rate_for_ventilation_system_off units="ACH">0.10</infiltration_rate_for_ventilation_system_off>
|
||||
<infiltration_rate_for_ventilation_system_on units="ACH">0</infiltration_rate_for_ventilation_system_on>
|
||||
<infiltration_rate_area_for_ventilation_system_off units="ACH">0.0005</infiltration_rate_area_for_ventilation_system_off>
|
||||
<infiltration_rate_area_for_ventilation_system_on units="ACH">0</infiltration_rate_area_for_ventilation_system_on>
|
||||
</archetype>
|
||||
<archetype id="33" building_type="midrise apartment" reference_standard="ASHRAE 189.1_2009" climate_zone="ASHRAE_2004:4A">
|
||||
<constructions>
|
||||
@ -727,6 +791,8 @@
|
||||
<indirect_heated_ratio units="-">0.15</indirect_heated_ratio>
|
||||
<infiltration_rate_for_ventilation_system_off units="ACH">0.10</infiltration_rate_for_ventilation_system_off>
|
||||
<infiltration_rate_for_ventilation_system_on units="ACH">0</infiltration_rate_for_ventilation_system_on>
|
||||
<infiltration_rate_area_for_ventilation_system_off units="ACH">0.0005</infiltration_rate_area_for_ventilation_system_off>
|
||||
<infiltration_rate_area_for_ventilation_system_on units="ACH">0</infiltration_rate_area_for_ventilation_system_on>
|
||||
</archetype>
|
||||
<archetype id="34" building_type="high-rise apartment" reference_standard="ASHRAE 189.1_2009" climate_zone="ASHRAE_2004:4A">
|
||||
<constructions>
|
||||
@ -749,6 +815,8 @@
|
||||
<indirect_heated_ratio units="-">0.15</indirect_heated_ratio>
|
||||
<infiltration_rate_for_ventilation_system_off units="ACH">0.10</infiltration_rate_for_ventilation_system_off>
|
||||
<infiltration_rate_for_ventilation_system_on units="ACH">0</infiltration_rate_for_ventilation_system_on>
|
||||
<infiltration_rate_area_for_ventilation_system_off units="ACH">0.0005</infiltration_rate_area_for_ventilation_system_off>
|
||||
<infiltration_rate_area_for_ventilation_system_on units="ACH">0</infiltration_rate_area_for_ventilation_system_on>
|
||||
</archetype>
|
||||
<archetype id="35" building_type="residential" reference_standard="ASHRAE 189.1_2009" climate_zone="ASHRAE_2004:4A">
|
||||
<constructions>
|
||||
@ -771,6 +839,8 @@
|
||||
<indirect_heated_ratio units="-">0.15</indirect_heated_ratio>
|
||||
<infiltration_rate_for_ventilation_system_off units="ACH">0.10</infiltration_rate_for_ventilation_system_off>
|
||||
<infiltration_rate_for_ventilation_system_on units="ACH">0</infiltration_rate_for_ventilation_system_on>
|
||||
<infiltration_rate_area_for_ventilation_system_off units="ACH">0.0005</infiltration_rate_area_for_ventilation_system_off>
|
||||
<infiltration_rate_area_for_ventilation_system_on units="ACH">0</infiltration_rate_area_for_ventilation_system_on>
|
||||
</archetype>
|
||||
<archetype id="36" building_type="residential" reference_standard="ASHRAE 90.1_2004" climate_zone="ASHRAE_2004:4A">
|
||||
<constructions>
|
||||
@ -793,6 +863,8 @@
|
||||
<indirect_heated_ratio units="-">0.15</indirect_heated_ratio>
|
||||
<infiltration_rate_for_ventilation_system_off units="ACH">0.50</infiltration_rate_for_ventilation_system_off>
|
||||
<infiltration_rate_for_ventilation_system_on units="ACH">0</infiltration_rate_for_ventilation_system_on>
|
||||
<infiltration_rate_area_for_ventilation_system_off units="ACH">0.003</infiltration_rate_area_for_ventilation_system_off>
|
||||
<infiltration_rate_area_for_ventilation_system_on units="ACH">0</infiltration_rate_area_for_ventilation_system_on>
|
||||
</archetype>
|
||||
<archetype id="37" building_type="industry" reference_standard="non_standard_dompark" climate_zone="ASHRAE_2004:4A">
|
||||
<constructions>
|
||||
@ -815,5 +887,7 @@
|
||||
<indirect_heated_ratio units="-">0.15</indirect_heated_ratio>
|
||||
<infiltration_rate_for_ventilation_system_off units="ACH">0.10</infiltration_rate_for_ventilation_system_off>
|
||||
<infiltration_rate_for_ventilation_system_on units="ACH">0</infiltration_rate_for_ventilation_system_on>
|
||||
<infiltration_rate_area_for_ventilation_system_off units="ACH">0.0005</infiltration_rate_area_for_ventilation_system_off>
|
||||
<infiltration_rate_area_for_ventilation_system_on units="ACH">0</infiltration_rate_area_for_ventilation_system_on>
|
||||
</archetype>
|
||||
</archetypes>
|
||||
|
106
hub/data/costs/fuel_rates.json
Normal file
106
hub/data/costs/fuel_rates.json
Normal file
@ -0,0 +1,106 @@
|
||||
{
|
||||
"rates": {
|
||||
"fuels": {
|
||||
"rate": [
|
||||
{
|
||||
"name": "Electricity-D",
|
||||
"fuel_type": "Electricity",
|
||||
"rate_name": "D",
|
||||
"units": "CAD/kWh",
|
||||
"usage_type": "residential",
|
||||
"maximum_power_demand_kW": 65,
|
||||
"rate_type": "fixed",
|
||||
"notes": null,
|
||||
"start_date": null,
|
||||
"end_date": null,
|
||||
"values": [
|
||||
0.075
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Electricity-Flex-D",
|
||||
"fuel_type": "Electricity",
|
||||
"rate_name": "Flex-D",
|
||||
"units": "CAD/kWh",
|
||||
"usage_type": "residential",
|
||||
"maximum_power_demand_kW": 65,
|
||||
"rate_type": "variable",
|
||||
"notes": null,
|
||||
"start_date": null,
|
||||
"end_date": null,
|
||||
"values": [
|
||||
0.075,
|
||||
0.075,
|
||||
0.075,
|
||||
0.075,
|
||||
0.075,
|
||||
0.075,
|
||||
0.551,
|
||||
0.551,
|
||||
0.551,
|
||||
0.075,
|
||||
0.075,
|
||||
0.075,
|
||||
0.075,
|
||||
0.075,
|
||||
0.075,
|
||||
0.075,
|
||||
0.551,
|
||||
0.551,
|
||||
0.551,
|
||||
0.551,
|
||||
0.075,
|
||||
0.075,
|
||||
0.075,
|
||||
0.075
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Gas-Energir",
|
||||
"fuel_type": "Gas",
|
||||
"rate_name": null,
|
||||
"units": "CAD/m3",
|
||||
"usage_type": "residential",
|
||||
"maximum_power_demand_kW": null,
|
||||
"rate_type": "fixed",
|
||||
"notes": null,
|
||||
"start_date": null,
|
||||
"end_date": null,
|
||||
"values": [
|
||||
0.4
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Diesel-Fixed",
|
||||
"fuel_type": "Diesel",
|
||||
"rate_name": null,
|
||||
"units": "CAD/l",
|
||||
"usage_type": "residential",
|
||||
"maximum_power_demand_kW": null,
|
||||
"rate_type": "fixed",
|
||||
"notes": null,
|
||||
"start_date": null,
|
||||
"end_date": null,
|
||||
"values": [
|
||||
1.2
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Biomass-Fixed",
|
||||
"fuel_type": "Biomass",
|
||||
"rate_name": null,
|
||||
"units": "CAD/kg",
|
||||
"usage_type": "residential",
|
||||
"maximum_power_demand_kW": null,
|
||||
"rate_type": "fixed",
|
||||
"notes": null,
|
||||
"start_date": null,
|
||||
"end_date": null,
|
||||
"values": [
|
||||
0.04
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
@ -25,8 +25,8 @@
|
||||
<D_services>
|
||||
<D20_onsite_generation>
|
||||
<D2010_photovoltaic_system>
|
||||
<investment_cost cost_unit="currency/m2"> 0 </investment_cost>
|
||||
<reposition cost_unit="currency/m2"> 0 </reposition>
|
||||
<investment_cost cost_unit="currency/m2"> 300 </investment_cost>
|
||||
<reposition cost_unit="currency/m2"> 300 </reposition>
|
||||
<lifetime_equipment lifetime="years"> 25 </lifetime_equipment>
|
||||
</D2010_photovoltaic_system>
|
||||
</D20_onsite_generation>
|
||||
@ -124,34 +124,58 @@
|
||||
<operational_cost>
|
||||
<fuels>
|
||||
<fuel fuel_type="Electricity">
|
||||
<fixed_monthly cost_unit="currency/month">12.27</fixed_monthly>
|
||||
<fixed_power cost_unit="currency/month*kW">0</fixed_power>
|
||||
<variable cost_unit="currency/kWh">0.075</variable>
|
||||
<density/>
|
||||
<lower_heating_value/>
|
||||
<fixed_monthly cost_unit="currency/month"> 12.27 </fixed_monthly>
|
||||
<fixed_power cost_unit="currency/(month*kW)"> 0 </fixed_power>
|
||||
<variable>Electricity-D</variable>
|
||||
</fuel>
|
||||
<fuel fuel_type="Electricity">
|
||||
<fixed_monthly cost_unit="currency/month"> 12.27 </fixed_monthly>
|
||||
<fixed_power cost_unit="currency/(month*kW)"> 0 </fixed_power>
|
||||
<variable>Electricity-Flex-D</variable>
|
||||
</fuel>
|
||||
<fuel fuel_type="Gas">
|
||||
<fixed_monthly cost_unit="currency/month"> 17.71 </fixed_monthly>
|
||||
<variable cost_unit="currency/m3"> 0.4 </variable>
|
||||
<variable>Gas-Energir</variable>
|
||||
<density density_unit="kg/m3"> 0.777 </density>
|
||||
<lower_heating_value lhv_unit="MJ/kg"> 47.1 </lower_heating_value>
|
||||
</fuel>
|
||||
<fuel fuel_type="Diesel">
|
||||
<fixed_monthly/>
|
||||
<variable cost_unit="currency/l"> 1.2 </variable>
|
||||
<variable>Diesel-Fixed</variable>
|
||||
<density density_unit="kg/l"> 0.846 </density>
|
||||
<lower_heating_value lhv_unit="MJ/kg"> 42.6 </lower_heating_value>
|
||||
</fuel>
|
||||
<fuel fuel_type="Biomass">
|
||||
<fixed_monthly/>
|
||||
<variable cost_unit="currency/kg"> 0.04 </variable>
|
||||
<variable>Biomass-Fixed</variable>
|
||||
<density/>
|
||||
<lower_heating_value lhv_unit="MJ/kg"> 18 </lower_heating_value>
|
||||
</fuel>
|
||||
</fuels>
|
||||
<maintenance>
|
||||
<heating_equipment cost_unit="currency/kW">40</heating_equipment>
|
||||
<cooling_equipment cost_unit="currency/kW">40</cooling_equipment>
|
||||
<hvac_equipment>
|
||||
<air_source_heat_pump>
|
||||
<maintenance_cost cost_unit="cureency/kW">100</maintenance_cost>
|
||||
</air_source_heat_pump>
|
||||
<ground_source_heat_pump>
|
||||
<maintenance_cost cost_unit="cureency/kW">60</maintenance_cost>
|
||||
</ground_source_heat_pump>
|
||||
<water_source_heat_pump>
|
||||
<maintenance_cost cost_unit="cureency/kW">50</maintenance_cost>
|
||||
</water_source_heat_pump>
|
||||
<gas_boiler>
|
||||
<maintenance_cost cost_unit="cureency/kW">50</maintenance_cost>
|
||||
</gas_boiler>
|
||||
<electric_boiler>
|
||||
<maintenance_cost cost_unit="cureency/kW">100</maintenance_cost>
|
||||
</electric_boiler>
|
||||
<general_heating_equipment>
|
||||
<maintenance_cost cost_unit="cureency/kW">60</maintenance_cost>
|
||||
</general_heating_equipment>
|
||||
<general_cooling_equipment>
|
||||
<maintenance_cost cost_unit="cureency/kW">50</maintenance_cost>
|
||||
</general_cooling_equipment>
|
||||
</hvac_equipment>
|
||||
<photovoltaic_system cost_unit="currency/m2">1</photovoltaic_system>
|
||||
</maintenance>
|
||||
<co2_cost cost_unit="currency/kgCO2"> 30 </co2_cost>
|
||||
@ -163,7 +187,7 @@
|
||||
<hvac cost_unit="%">1.5</hvac>
|
||||
<photovoltaic cost_unit="%">3.6</photovoltaic>
|
||||
</subsidies>
|
||||
<electricity_export cost_unit="currency/kWh">0.07</electricity_export>
|
||||
<electricity_export cost_unit="currency/kWh">0.075</electricity_export>
|
||||
<tax_reduction cost_unit="%">5</tax_reduction>
|
||||
</incomes>
|
||||
</archetype>
|
||||
@ -294,31 +318,57 @@
|
||||
<fuel fuel_type="Electricity">
|
||||
<fixed_monthly cost_unit="currency/month"> 12.27 </fixed_monthly>
|
||||
<fixed_power cost_unit="currency/(month*kW)"> 0 </fixed_power>
|
||||
<variable cost_unit="currency/kWh"> 0.075 </variable>
|
||||
<variable>Electricity-D</variable>
|
||||
</fuel>
|
||||
<fuel fuel_type="Electricity">
|
||||
<fixed_monthly cost_unit="currency/month"> 12.27 </fixed_monthly>
|
||||
<fixed_power cost_unit="currency/(month*kW)"> 0 </fixed_power>
|
||||
<variable>Electricity-Flex-D</variable>
|
||||
</fuel>
|
||||
<fuel fuel_type="Gas">
|
||||
<fixed_monthly cost_unit="currency/month"> 17.71 </fixed_monthly>
|
||||
<variable cost_unit="currency/m3"> 0.0640 </variable>
|
||||
<variable>Gas-Energir</variable>
|
||||
<density density_unit="kg/m3"> 0.777 </density>
|
||||
<lower_heating_value lhv_unit="MJ/kg"> 47.1 </lower_heating_value>
|
||||
</fuel>
|
||||
<fuel fuel_type="Diesel">
|
||||
<fixed_monthly/>
|
||||
<variable cost_unit="currency/l"> 1.2 </variable>
|
||||
<variable>Diesel-Fixed</variable>
|
||||
<density density_unit="kg/l"> 0.846 </density>
|
||||
<lower_heating_value lhv_unit="MJ/kg"> 42.6 </lower_heating_value>
|
||||
</fuel>
|
||||
<fuel fuel_type="Biomass">
|
||||
<fixed_monthly/>
|
||||
<variable cost_unit="currency/kg"> 0.04 </variable>
|
||||
<variable>Biomass-Fixed</variable>
|
||||
<density/>
|
||||
<lower_heating_value lhv_unit="MJ/kg"> 18 </lower_heating_value>
|
||||
</fuel>
|
||||
</fuels>
|
||||
<maintenance>
|
||||
<heating_equipment cost_unit="currency/kW">40</heating_equipment>
|
||||
<cooling_equipment cost_unit="currency/kW">40</cooling_equipment>
|
||||
<photovoltaic_system cost_unit="currency/m2">0</photovoltaic_system>
|
||||
<hvac_equipment>
|
||||
<air_source_heat_pump>
|
||||
<maintenance_cost cost_unit="cureency/kW">100</maintenance_cost>
|
||||
</air_source_heat_pump>
|
||||
<ground_source_heat_pump>
|
||||
<maintenance_cost cost_unit="cureency/kW">60</maintenance_cost>
|
||||
</ground_source_heat_pump>
|
||||
<water_source_heat_pump>
|
||||
<maintenance_cost cost_unit="cureency/kW">50</maintenance_cost>
|
||||
</water_source_heat_pump>
|
||||
<gas_boiler>
|
||||
<maintenance_cost cost_unit="cureency/kW">50</maintenance_cost>
|
||||
</gas_boiler>
|
||||
<electric_boiler>
|
||||
<maintenance_cost cost_unit="cureency/kW">100</maintenance_cost>
|
||||
</electric_boiler>
|
||||
<general_heating_equipment>
|
||||
<maintenance_cost cost_unit="cureency/kW">60</maintenance_cost>
|
||||
</general_heating_equipment>
|
||||
<general_cooling_equipment>
|
||||
<maintenance_cost cost_unit="cureency/kW">50</maintenance_cost>
|
||||
</general_cooling_equipment>
|
||||
</hvac_equipment>
|
||||
<photovoltaic_system cost_unit="currency/m2">1</photovoltaic_system>
|
||||
</maintenance>
|
||||
<co2_cost cost_unit="currency/kgCO2"> 30 </co2_cost>
|
||||
</operational_cost>
|
||||
|
@ -198,7 +198,7 @@
|
||||
<equipments>
|
||||
<generation_id>3</generation_id>
|
||||
<distribution_id>8</distribution_id>
|
||||
g </equipments>
|
||||
</equipments>
|
||||
</system>
|
||||
<system id="5">
|
||||
<name>Single zone packaged rooftop unit with electrical resistance furnace and baseboards and fuel boiler for acs</name>
|
||||
@ -240,7 +240,7 @@ g </equipments>
|
||||
<demand>domestic_hot_water</demand>
|
||||
</demands>
|
||||
<equipments>
|
||||
<generation_id>2</generation_id>
|
||||
<generation_id>1</generation_id>
|
||||
<distribution_id>3</distribution_id>
|
||||
</equipments>
|
||||
</system>
|
||||
@ -302,7 +302,7 @@ g </equipments>
|
||||
</demands>
|
||||
<equipments>
|
||||
<generation_id>5</generation_id>
|
||||
<distribution_id>6</distribution_id>
|
||||
<distribution_id>4</distribution_id>
|
||||
</equipments>
|
||||
</system>
|
||||
<system id="15">
|
||||
|
File diff suppressed because it is too large
Load Diff
BIN
hub/data/energy_systems/schemas/PV+4Pipe+DHW.jpg
Normal file
BIN
hub/data/energy_systems/schemas/PV+4Pipe+DHW.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 78 KiB |
@ -7,6 +7,9 @@ Code contributors: Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concord
|
||||
Oriol Gavalda Torrellas oriol.gavalda@concordia.ca
|
||||
"""
|
||||
import copy
|
||||
import datetime
|
||||
import glob
|
||||
import os
|
||||
from pathlib import Path
|
||||
from geomeppy import IDF
|
||||
import hub.helpers.constants as cte
|
||||
@ -275,11 +278,12 @@ class Idf:
|
||||
_kwargs[f'Field_{counter + 2}'] = 'Until: 24:00,0.0'
|
||||
self._idf.newidfobject(self._COMPACT_SCHEDULE, **_kwargs)
|
||||
|
||||
def _write_schedules_file(self, usage, schedule):
|
||||
file_name = str((Path(self._output_path) / f'{schedule.type} schedules {usage}.csv').resolve())
|
||||
with open(file_name, 'w', encoding='utf8') as file:
|
||||
for value in schedule.values:
|
||||
file.write(f'{str(value)},\n')
|
||||
def _write_schedules_file(self, schedule, usage):
|
||||
file_name = str((Path(self._output_path) / f'{schedule.type} schedules {usage.replace("/","_")}.csv').resolve())
|
||||
if not Path(file_name).exists():
|
||||
with open(file_name, 'w', encoding='utf8') as file:
|
||||
for value in schedule.values:
|
||||
file.write(f'{str(value)},\n')
|
||||
return Path(file_name).name
|
||||
|
||||
def _add_file_schedule(self, usage, schedule, file_name):
|
||||
@ -304,7 +308,7 @@ class Idf:
|
||||
for schedule in self._idf.idfobjects[self._FILE_SCHEDULE]:
|
||||
if schedule.Name == f'{schedule_type} schedules {usage}':
|
||||
return
|
||||
file_name = self._write_schedules_file(usage, new_schedules[0])
|
||||
file_name = self._write_schedules_file(new_schedules[0], usage)
|
||||
self._add_file_schedule(usage, new_schedules[0], file_name)
|
||||
return
|
||||
|
||||
@ -321,7 +325,7 @@ class Idf:
|
||||
if construction.Name == vegetation_name:
|
||||
return
|
||||
else:
|
||||
if construction.Name == thermal_boundary.construction_name:
|
||||
if construction.Name == f'{thermal_boundary.construction_name} {thermal_boundary.parent_surface.type}':
|
||||
return
|
||||
if thermal_boundary.layers is None:
|
||||
for material in self._idf.idfobjects[self._MATERIAL]:
|
||||
@ -340,7 +344,8 @@ class Idf:
|
||||
for i in range(0, len(layers) - 1):
|
||||
_kwargs[f'Layer_{i + 2}'] = layers[i].material_name
|
||||
else:
|
||||
_kwargs = {'Name': thermal_boundary.construction_name, 'Outside_Layer': layers[0].material_name}
|
||||
_kwargs = {'Name': f'{thermal_boundary.construction_name} {thermal_boundary.parent_surface.type}',
|
||||
'Outside_Layer': layers[0].material_name}
|
||||
for i in range(1, len(layers) - 1):
|
||||
_kwargs[f'Layer_{i + 1}'] = layers[i].material_name
|
||||
self._idf.newidfobject(self._CONSTRUCTION, **_kwargs)
|
||||
@ -387,9 +392,9 @@ class Idf:
|
||||
thermostat = self._add_thermostat(thermal_zone)
|
||||
self._idf.newidfobject(self._IDEAL_LOAD_AIR_SYSTEM,
|
||||
Zone_Name=zone_name,
|
||||
System_Availability_Schedule_Name=f'HVAC AVAIL SCHEDULES {thermal_zone.usage_name}',
|
||||
Heating_Availability_Schedule_Name=f'HVAC AVAIL SCHEDULES {thermal_zone.usage_name}',
|
||||
Cooling_Availability_Schedule_Name=f'HVAC AVAIL SCHEDULES {thermal_zone.usage_name}',
|
||||
System_Availability_Schedule_Name=f'Thermostat_availability schedules {thermal_zone.usage_name}',
|
||||
Heating_Availability_Schedule_Name=f'Thermostat_availability schedules {thermal_zone.usage_name}',
|
||||
Cooling_Availability_Schedule_Name=f'Thermostat_availability schedules {thermal_zone.usage_name}',
|
||||
Template_Thermostat_Name=thermostat.Name)
|
||||
|
||||
def _add_occupancy(self, thermal_zone, zone_name):
|
||||
@ -449,7 +454,7 @@ class Idf:
|
||||
)
|
||||
|
||||
def _add_infiltration(self, thermal_zone, zone_name):
|
||||
schedule = f'Infiltration schedules {thermal_zone.usage_name}'
|
||||
schedule = f'INF_CONST schedules {thermal_zone.usage_name}'
|
||||
_infiltration = thermal_zone.infiltration_rate_system_off * cte.HOUR_TO_SECONDS
|
||||
self._idf.newidfobject(self._INFILTRATION,
|
||||
Name=f'{zone_name}_infiltration',
|
||||
@ -459,6 +464,17 @@ class Idf:
|
||||
Air_Changes_per_Hour=_infiltration
|
||||
)
|
||||
|
||||
def _add_infiltration_surface(self, thermal_zone, zone_name):
|
||||
schedule = f'INF_CONST schedules {thermal_zone.usage_name}'
|
||||
_infiltration = thermal_zone.infiltration_rate_area_system_off*cte.INFILTRATION_75PA_TO_4PA
|
||||
self._idf.newidfobject(self._INFILTRATION,
|
||||
Name=f'{zone_name}_infiltration',
|
||||
Zone_or_ZoneList_or_Space_or_SpaceList_Name=zone_name,
|
||||
Schedule_Name=schedule,
|
||||
Design_Flow_Rate_Calculation_Method='Flow/ExteriorWallArea',
|
||||
Flow_Rate_per_Exterior_Surface_Area=_infiltration
|
||||
)
|
||||
|
||||
def _add_ventilation(self, thermal_zone, zone_name):
|
||||
schedule = f'Ventilation schedules {thermal_zone.usage_name}'
|
||||
_air_change = thermal_zone.mechanical_air_change * cte.HOUR_TO_SECONDS
|
||||
@ -470,7 +486,7 @@ class Idf:
|
||||
Air_Changes_per_Hour=_air_change
|
||||
)
|
||||
|
||||
def _add_dhw(self, thermal_zone, zone_name):
|
||||
def _add_dhw(self, thermal_zone, zone_name, usage):
|
||||
peak_flow_rate = thermal_zone.domestic_hot_water.peak_flow * thermal_zone.total_floor_area
|
||||
self._idf.newidfobject(self._DHW,
|
||||
Name=f'DHW {zone_name}',
|
||||
@ -478,7 +494,7 @@ class Idf:
|
||||
Flow_Rate_Fraction_Schedule_Name=f'DHW_prof schedules {thermal_zone.usage_name}',
|
||||
Target_Temperature_Schedule_Name=f'DHW_temp schedules {thermal_zone.usage_name}',
|
||||
Hot_Water_Supply_Temperature_Schedule_Name=f'DHW_temp schedules {thermal_zone.usage_name}',
|
||||
Cold_Water_Supply_Temperature_Schedule_Name=f'cold_temp schedules {zone_name}',
|
||||
Cold_Water_Supply_Temperature_Schedule_Name=f'cold_temp schedules {usage}',
|
||||
EndUse_Subcategory=f'DHW {zone_name}',
|
||||
Zone_Name=zone_name
|
||||
)
|
||||
@ -512,19 +528,25 @@ class Idf:
|
||||
self._rename_building(self._city.name)
|
||||
self._lod = self._city.level_of_detail.geometry
|
||||
for building in self._city.buildings:
|
||||
is_target = building.name in self._target_buildings or building.name in self._adjacent_buildings
|
||||
for internal_zone in building.internal_zones:
|
||||
if internal_zone.thermal_zones_from_internal_zones is None:
|
||||
self._target_buildings.remoidf_surface_typeve(building.name)
|
||||
is_target = False
|
||||
continue
|
||||
for thermal_zone in internal_zone.thermal_zones_from_internal_zones:
|
||||
for thermal_boundary in thermal_zone.thermal_boundaries:
|
||||
|
||||
for thermal_boundary in thermal_zone.thermal_boundaries:
|
||||
self._add_construction(thermal_boundary)
|
||||
if thermal_boundary.parent_surface.vegetation is not None:
|
||||
self._add_vegetation_material(thermal_boundary.parent_surface.vegetation)
|
||||
for thermal_opening in thermal_boundary.thermal_openings:
|
||||
self._add_window_construction_and_material(thermal_opening)
|
||||
usage = thermal_zone.usage_name
|
||||
if building.name in self._target_buildings or building.name in self._adjacent_buildings:
|
||||
|
||||
if is_target:
|
||||
start = datetime.datetime.now()
|
||||
service_temperature = thermal_zone.domestic_hot_water.service_temperature
|
||||
usage = thermal_zone.usage_name
|
||||
_new_schedules = self._create_infiltration_schedules(thermal_zone)
|
||||
self._add_schedules(usage, 'Infiltration', _new_schedules)
|
||||
_new_schedules = self._create_ventilation_schedules(thermal_zone)
|
||||
@ -536,12 +558,14 @@ class Idf:
|
||||
self._add_schedules(usage, 'Lighting', thermal_zone.lighting.schedules)
|
||||
self._add_schedules(usage, 'Appliance', thermal_zone.appliances.schedules)
|
||||
self._add_schedules(usage, 'DHW_prof', thermal_zone.domestic_hot_water.schedules)
|
||||
_new_schedules = self._create_yearly_values_schedules('cold_temp',
|
||||
building.cold_water_temperature[cte.HOUR])
|
||||
self._add_schedules(building.name, 'cold_temp', _new_schedules)
|
||||
value = thermal_zone.domestic_hot_water.service_temperature
|
||||
_new_schedules = self._create_constant_value_schedules('DHW_temp', value)
|
||||
_new_schedules = self._create_yearly_values_schedules('cold_temp', building.cold_water_temperature[cte.HOUR])
|
||||
self._add_schedules(usage, 'cold_temp', _new_schedules)
|
||||
_new_schedules = self._create_constant_value_schedules('DHW_temp', service_temperature)
|
||||
self._add_schedules(usage, 'DHW_temp', _new_schedules)
|
||||
_new_schedules = self._create_constant_value_schedules('INF_CONST', 1)
|
||||
self._add_schedules(usage, 'INF_CONST', _new_schedules)
|
||||
_new_schedules = self._create_constant_value_schedules('Thermostat_availability', 1)
|
||||
self._add_schedules(usage, 'Thermostat_availability', _new_schedules)
|
||||
_occ = thermal_zone.occupancy
|
||||
if _occ.occupancy_density == 0:
|
||||
_total_heat = 0
|
||||
@ -552,16 +576,18 @@ class Idf:
|
||||
self._add_schedules(usage, 'Activity Level', _new_schedules)
|
||||
self._add_zone(thermal_zone, building.name)
|
||||
self._add_heating_system(thermal_zone, building.name)
|
||||
self._add_infiltration(thermal_zone, building.name)
|
||||
self._add_infiltration_surface(thermal_zone, building.name)
|
||||
self._add_ventilation(thermal_zone, building.name)
|
||||
self._add_occupancy(thermal_zone, building.name)
|
||||
self._add_lighting(thermal_zone, building.name)
|
||||
self._add_appliances(thermal_zone, building.name)
|
||||
self._add_dhw(thermal_zone, building.name)
|
||||
self._add_dhw(thermal_zone, building.name, usage)
|
||||
if self._export_type == "Surfaces":
|
||||
if building.name in self._target_buildings or building.name in self._adjacent_buildings:
|
||||
if is_target:
|
||||
if building.thermal_zones_from_internal_zones is not None:
|
||||
start = datetime.datetime.now()
|
||||
self._add_surfaces(building, building.name)
|
||||
print(f'add surfaces {datetime.datetime.now() - start}')
|
||||
else:
|
||||
self._add_pure_geometry(building, building.name)
|
||||
else:
|
||||
@ -599,6 +625,18 @@ class Idf:
|
||||
Reporting_Frequency="Hourly",
|
||||
)
|
||||
|
||||
self._idf.newidfobject(
|
||||
"OUTPUT:VARIABLE",
|
||||
Variable_Name="Zone Air Temperature",
|
||||
Reporting_Frequency="Hourly",
|
||||
)
|
||||
|
||||
self._idf.newidfobject(
|
||||
"OUTPUT:VARIABLE",
|
||||
Variable_Name="Zone Air Relative Humidity",
|
||||
Reporting_Frequency="Hourly",
|
||||
)
|
||||
|
||||
# post-process to erase windows associated to adiabatic walls
|
||||
windows_list = []
|
||||
for window in self._idf.idfobjects[self._WINDOW]:
|
||||
@ -717,7 +755,7 @@ class Idf:
|
||||
if boundary.parent_surface.vegetation is not None:
|
||||
construction_name = f'{boundary.construction_name}_{boundary.parent_surface.vegetation.name}'
|
||||
else:
|
||||
construction_name = boundary.construction_name
|
||||
construction_name = f'{boundary.construction_name} {boundary.parent_surface.type}'
|
||||
_kwargs['Construction_Name'] = construction_name
|
||||
|
||||
surface = self._idf.newidfobject(self._SURFACE, **_kwargs)
|
||||
|
@ -1,4 +1,4 @@
|
||||
!IDD_Version 23.2.0
|
||||
!IDD_Version 24.1.0
|
||||
!IDD_BUILD 7636e6b3e9
|
||||
! ***************************************************************************
|
||||
! This file is the Input Data Dictionary (IDD) for EnergyPlus.
|
||||
@ -30002,10 +30002,10 @@ People,
|
||||
A7 , \field Mean Radiant Temperature Calculation Type
|
||||
\note optional (only required for thermal comfort runs)
|
||||
\type choice
|
||||
\key ZoneAveraged
|
||||
\key EnclosureAveraged
|
||||
\key SurfaceWeighted
|
||||
\key AngleFactor
|
||||
\default ZoneAveraged
|
||||
\default EnclosureAveraged
|
||||
A8 , \field Surface Name/Angle Factor List Name
|
||||
\type object-list
|
||||
\object-list AllHeatTranAngFacNames
|
||||
|
@ -13,7 +13,7 @@
|
||||
! HVAC: None.
|
||||
!
|
||||
|
||||
Version,23.2;
|
||||
Version,24.1;
|
||||
|
||||
Timestep,4;
|
||||
|
||||
@ -154,4 +154,4 @@
|
||||
Output:Meter,InteriorLights:Electricity,hourly;
|
||||
|
||||
OutputControl:IlluminanceMap:Style,
|
||||
Comma; !- Column separator
|
||||
Comma; !- Column separator
|
||||
|
@ -270,7 +270,7 @@ class InselMonthlyEnergyBalance:
|
||||
global_irradiance = surface.global_irradiance[cte.MONTH]
|
||||
for j in range(0, len(global_irradiance)):
|
||||
parameters.append(f'{j + 1} '
|
||||
f'{global_irradiance[j] * cte.WATTS_HOUR_TO_JULES / 24 / _NUMBER_DAYS_PER_MONTH[j]}')
|
||||
f'{global_irradiance[j] / 24 / _NUMBER_DAYS_PER_MONTH[j]}')
|
||||
else:
|
||||
for j in range(0, 12):
|
||||
parameters.append(f'{j + 1} 0.0')
|
||||
|
@ -20,9 +20,10 @@ class EnergyBuildingsExportsFactory:
|
||||
"""
|
||||
Energy Buildings exports factory class
|
||||
"""
|
||||
def __init__(self, handler, city, path, custom_insel_block='d18599', target_buildings=None):
|
||||
def __init__(self, handler, city, path, custom_insel_block='d18599', target_buildings=None, weather_file=None):
|
||||
self._city = city
|
||||
self._export_type = '_' + handler.lower()
|
||||
self._weather_file = weather_file
|
||||
validate_import_export_type(EnergyBuildingsExportsFactory, handler)
|
||||
if isinstance(path, str):
|
||||
path = Path(path)
|
||||
@ -53,12 +54,13 @@ class EnergyBuildingsExportsFactory:
|
||||
"""
|
||||
idf_data_path = (Path(__file__).parent / './building_energy/idf_files/').resolve()
|
||||
url = wh().epw_file(self._city.region_code)
|
||||
weather_path = (Path(__file__).parent.parent / f'data/weather/epw/{url.rsplit("/", 1)[1]}').resolve()
|
||||
if not weather_path.exists():
|
||||
with open(weather_path, 'wb') as epw_file:
|
||||
if self._weather_file is None:
|
||||
self._weather_file = (Path(__file__).parent.parent / f'data/weather/epw/{url.rsplit("/", 1)[1]}').resolve()
|
||||
if not self._weather_file.exists():
|
||||
with open(self._weather_file, 'wb') as epw_file:
|
||||
epw_file.write(requests.get(url, allow_redirects=True).content)
|
||||
return Idf(self._city, self._path, (idf_data_path / 'Minimal.idf'), (idf_data_path / 'Energy+.idd'), weather_path,
|
||||
target_buildings=self._target_buildings)
|
||||
return Idf(self._city, self._path, (idf_data_path / 'Minimal.idf'), (idf_data_path / 'Energy+.idd'),
|
||||
self._weather_file, target_buildings=self._target_buildings)
|
||||
|
||||
@property
|
||||
def _insel_monthly_energy_balance(self):
|
||||
|
@ -67,7 +67,7 @@ class SimplifiedRadiosityAlgorithm:
|
||||
i = (total_days + day - 1) * 24 + hour - 1
|
||||
representative_building = self._city.buildings[0]
|
||||
_global = representative_building.diffuse[cte.HOUR][i] / cte.WATTS_HOUR_TO_JULES
|
||||
_beam = representative_building.beam[cte.HOUR][i] / cte.WATTS_HOUR_TO_JULES
|
||||
_beam = representative_building.direct_normal[cte.HOUR][i] / cte.WATTS_HOUR_TO_JULES
|
||||
content += f'{day} {month} {hour} {_global} {_beam}\n'
|
||||
with open(file, 'w', encoding='utf-8') as file:
|
||||
file.write(content)
|
||||
|
@ -10,6 +10,7 @@ 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
|
||||
WATER_THERMAL_CONDUCTIVITY = 0.65 # W/mK
|
||||
NATURAL_GAS_LHV = 36.6e6 # J/m3
|
||||
AIR_DENSITY = 1.293 # kg/m3
|
||||
AIR_HEAT_CAPACITY = 1005.2 # J/kgK
|
||||
@ -23,6 +24,8 @@ BTU_H_TO_WATTS = 0.29307107
|
||||
KILO_WATTS_HOUR_TO_JULES = 3600000
|
||||
WATTS_HOUR_TO_JULES = 3600
|
||||
GALLONS_TO_QUBIC_METERS = 0.0037854117954011185
|
||||
INFILTRATION_75PA_TO_4PA = (4/75)**0.65
|
||||
|
||||
|
||||
# time
|
||||
SECOND = 'second'
|
||||
@ -300,6 +303,7 @@ GRID = 'Grid'
|
||||
ONSITE_ELECTRICITY = 'Onsite Electricity'
|
||||
PHOTOVOLTAIC = 'Photovoltaic'
|
||||
BOILER = 'Boiler'
|
||||
FURNACE = 'Furnace'
|
||||
HEAT_PUMP = 'Heat Pump'
|
||||
BASEBOARD = 'Baseboard'
|
||||
ELECTRICITY_GENERATOR = 'Electricity generator'
|
||||
@ -309,7 +313,8 @@ LATENT = 'Latent'
|
||||
LITHIUMION = 'Lithium Ion'
|
||||
NICD = 'NiCd'
|
||||
LEADACID = 'Lead Acid'
|
||||
|
||||
THERMAL = 'thermal'
|
||||
ELECTRICAL = 'electrical'
|
||||
# Geometry
|
||||
EPSILON = 0.0000001
|
||||
|
||||
|
@ -12,12 +12,16 @@ class MontrealCustomFuelToHubFuel:
|
||||
"""
|
||||
Montreal custom fuel to hub fuel class
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self._dictionary = {
|
||||
'gas': cte.GAS,
|
||||
'electricity': cte.ELECTRICITY,
|
||||
'renewable': cte.RENEWABLE
|
||||
}
|
||||
'gas': cte.GAS,
|
||||
'natural gas': cte.GAS,
|
||||
'diesel': cte.DIESEL,
|
||||
'biomass': cte.BIOMASS,
|
||||
'electricity': cte.ELECTRICITY,
|
||||
'renewable': cte.RENEWABLE
|
||||
}
|
||||
|
||||
@property
|
||||
def dictionary(self) -> dict:
|
||||
|
@ -18,7 +18,7 @@ class MontrealGenerationSystemToHubEnergyGenerationSystem:
|
||||
'furnace': cte.BASEBOARD,
|
||||
'cooler': cte.CHILLER,
|
||||
'electricity generator': cte.ELECTRICITY_GENERATOR,
|
||||
'PV system': cte.PHOTOVOLTAIC,
|
||||
'photovoltaic': cte.PHOTOVOLTAIC,
|
||||
'heat pump': cte.HEAT_PUMP
|
||||
}
|
||||
|
||||
|
@ -66,6 +66,8 @@ class EilatPhysicsParameters:
|
||||
thermal_archetype.indirect_heated_ratio = 0
|
||||
thermal_archetype.infiltration_rate_for_ventilation_system_on = catalog_archetype.infiltration_rate_for_ventilation_system_on
|
||||
thermal_archetype.infiltration_rate_for_ventilation_system_off = catalog_archetype.infiltration_rate_for_ventilation_system_off
|
||||
thermal_archetype.infiltration_rate_area_for_ventilation_system_on = catalog_archetype.infiltration_rate_area_for_ventilation_system_on
|
||||
thermal_archetype.infiltration_rate_area_for_ventilation_system_off = catalog_archetype.infiltration_rate_area_for_ventilation_system_off
|
||||
effective_thermal_capacity = 0
|
||||
_constructions = []
|
||||
for catalog_construction in catalog_archetype.constructions:
|
||||
|
@ -32,10 +32,21 @@ class NrcanPhysicsParameters:
|
||||
city = self._city
|
||||
nrcan_catalog = ConstructionCatalogFactory('nrcan').catalog
|
||||
for building in city.buildings:
|
||||
if building.function not in Dictionaries().hub_function_to_nrcan_construction_function:
|
||||
logging.error('Building %s has an unknown building function %s', building.name, building.function)
|
||||
main_function = None
|
||||
functions = building.function.split('_')
|
||||
if len(functions) > 1:
|
||||
maximum_percentage = 0
|
||||
for function in functions:
|
||||
percentage_and_function = function.split('-')
|
||||
if float(percentage_and_function[0]) > maximum_percentage:
|
||||
maximum_percentage = float(percentage_and_function[0])
|
||||
main_function = percentage_and_function[-1]
|
||||
else:
|
||||
main_function = functions[-1]
|
||||
if main_function not in Dictionaries().hub_function_to_nrcan_construction_function:
|
||||
logging.error('Building %s has an unknown building function %s', building.name, main_function)
|
||||
continue
|
||||
function = Dictionaries().hub_function_to_nrcan_construction_function[building.function]
|
||||
function = Dictionaries().hub_function_to_nrcan_construction_function[main_function]
|
||||
try:
|
||||
archetype = self._search_archetype(nrcan_catalog, function, building.year_of_construction, self._climate_zone)
|
||||
|
||||
@ -67,6 +78,9 @@ class NrcanPhysicsParameters:
|
||||
thermal_archetype.indirect_heated_ratio = 0
|
||||
thermal_archetype.infiltration_rate_for_ventilation_system_on = catalog_archetype.infiltration_rate_for_ventilation_system_on
|
||||
thermal_archetype.infiltration_rate_for_ventilation_system_off = catalog_archetype.infiltration_rate_for_ventilation_system_off
|
||||
thermal_archetype.infiltration_rate_area_for_ventilation_system_on = catalog_archetype.infiltration_rate_area_for_ventilation_system_on
|
||||
thermal_archetype.infiltration_rate_area_for_ventilation_system_off = catalog_archetype.infiltration_rate_area_for_ventilation_system_off
|
||||
|
||||
_constructions = []
|
||||
for catalog_construction in catalog_archetype.constructions:
|
||||
construction = Construction()
|
||||
|
@ -69,6 +69,8 @@ class NrelPhysicsParameters:
|
||||
thermal_archetype.indirect_heated_ratio = catalog_archetype.indirect_heated_ratio
|
||||
thermal_archetype.infiltration_rate_for_ventilation_system_on = catalog_archetype.infiltration_rate_for_ventilation_system_on
|
||||
thermal_archetype.infiltration_rate_for_ventilation_system_off = catalog_archetype.infiltration_rate_for_ventilation_system_off
|
||||
thermal_archetype.infiltration_rate_area_for_ventilation_system_on = catalog_archetype.infiltration_rate_area_for_ventilation_system_on
|
||||
thermal_archetype.infiltration_rate_area_for_ventilation_system_off = catalog_archetype.infiltration_rate_area_for_ventilation_system_off
|
||||
_constructions = []
|
||||
for catalog_construction in catalog_archetype.constructions:
|
||||
construction = Construction()
|
||||
|
@ -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
|
||||
|
||||
|
@ -43,6 +43,7 @@ class MontrealFutureEnergySystemParameters:
|
||||
archetype_name = building.energy_systems_archetype_name
|
||||
try:
|
||||
archetype = self._search_archetypes(montreal_custom_catalog, archetype_name)
|
||||
building.energy_systems_archetype_cluster_id = archetype.cluster_id
|
||||
except KeyError:
|
||||
logging.error('Building %s has unknown energy system archetype for system name %s', building.name,
|
||||
archetype_name)
|
||||
@ -82,18 +83,17 @@ class MontrealFutureEnergySystemParameters:
|
||||
|
||||
return _generic_energy_systems
|
||||
|
||||
@staticmethod
|
||||
def _create_generation_systems(archetype_system):
|
||||
def _create_generation_systems(self, archetype_system):
|
||||
_generation_systems = []
|
||||
archetype_generation_systems = archetype_system.generation_systems
|
||||
if archetype_generation_systems is not None:
|
||||
for archetype_generation_system in archetype_system.generation_systems:
|
||||
if archetype_generation_system.system_type == 'Photovoltaic':
|
||||
if archetype_generation_system.system_type == 'photovoltaic':
|
||||
_generation_system = PvGenerationSystem()
|
||||
_generation_system.name = archetype_generation_system.name
|
||||
_generation_system.model_name = archetype_generation_system.model_name
|
||||
_generation_system.manufacturer = archetype_generation_system.manufacturer
|
||||
_type = 'PV system'
|
||||
_type = archetype_generation_system.system_type
|
||||
_generation_system.system_type = Dictionaries().montreal_generation_system_to_hub_energy_generation_system[_type]
|
||||
_fuel_type = Dictionaries().montreal_custom_fuel_to_hub_fuel[archetype_generation_system.fuel_type]
|
||||
_generation_system.fuel_type = _fuel_type
|
||||
@ -104,14 +104,21 @@ class MontrealFutureEnergySystemParameters:
|
||||
_generation_system.nominal_radiation = archetype_generation_system.nominal_radiation
|
||||
_generation_system.standard_test_condition_cell_temperature = archetype_generation_system.standard_test_condition_cell_temperature
|
||||
_generation_system.standard_test_condition_maximum_power = archetype_generation_system.standard_test_condition_maximum_power
|
||||
_generation_system.standard_test_condition_radiation = archetype_generation_system.standard_test_condition_radiation
|
||||
_generation_system.cell_temperature_coefficient = archetype_generation_system.cell_temperature_coefficient
|
||||
_generation_system.width = archetype_generation_system.width
|
||||
_generation_system.height = archetype_generation_system.height
|
||||
_generation_system.tilt_angle = self._city.latitude
|
||||
_generic_storage_system = None
|
||||
if archetype_generation_system.energy_storage_systems is not None:
|
||||
_generic_storage_system = ElectricalStorageSystem()
|
||||
_generic_storage_system.type_energy_stored = 'electrical'
|
||||
_generation_system.energy_storage_systems = [_generic_storage_system]
|
||||
_storage_systems = []
|
||||
for storage_system in archetype_generation_system.energy_storage_systems:
|
||||
if storage_system.type_energy_stored == 'electrical':
|
||||
_generic_storage_system = ElectricalStorageSystem()
|
||||
_generic_storage_system.type_energy_stored = 'electrical'
|
||||
_storage_systems.append(_generic_storage_system)
|
||||
_generation_system.energy_storage_systems = _storage_systems
|
||||
|
||||
else:
|
||||
_generation_system = NonPvGenerationSystem()
|
||||
_generation_system.name = archetype_generation_system.name
|
||||
@ -119,7 +126,7 @@ class MontrealFutureEnergySystemParameters:
|
||||
_generation_system.manufacturer = archetype_generation_system.manufacturer
|
||||
_type = archetype_generation_system.system_type
|
||||
_generation_system.system_type = Dictionaries().montreal_generation_system_to_hub_energy_generation_system[_type]
|
||||
_fuel_type = Dictionaries().north_america_custom_fuel_to_hub_fuel[archetype_generation_system.fuel_type]
|
||||
_fuel_type = Dictionaries().montreal_custom_fuel_to_hub_fuel[archetype_generation_system.fuel_type]
|
||||
_generation_system.fuel_type = _fuel_type
|
||||
_generation_system.nominal_heat_output = archetype_generation_system.nominal_heat_output
|
||||
_generation_system.nominal_cooling_output = archetype_generation_system.nominal_cooling_output
|
||||
@ -160,6 +167,7 @@ class MontrealFutureEnergySystemParameters:
|
||||
_generic_storage_system.height = storage_system.height
|
||||
_generic_storage_system.layers = storage_system.layers
|
||||
_generic_storage_system.storage_medium = storage_system.storage_medium
|
||||
_generic_storage_system.heating_coil_capacity = storage_system.heating_coil_capacity
|
||||
_storage_systems.append(_generic_storage_system)
|
||||
_generation_system.energy_storage_systems = _storage_systems
|
||||
if archetype_generation_system.domestic_hot_water:
|
||||
@ -184,10 +192,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
|
||||
|
||||
|
@ -1,157 +0,0 @@
|
||||
"""
|
||||
Energy System catalog heat generation system
|
||||
SPDX - License - Identifier: LGPL - 3.0 - or -later
|
||||
Copyright © 2023 Concordia CERC group
|
||||
Project Coder Saeed Ranjbar saeed.ranjbar@concordia.ca
|
||||
Code contributors: Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca
|
||||
"""
|
||||
|
||||
import logging
|
||||
import copy
|
||||
|
||||
from hub.catalog_factories.energy_systems_catalog_factory import EnergySystemsCatalogFactory
|
||||
from hub.city_model_structure.energy_systems.energy_system import EnergySystem
|
||||
from hub.city_model_structure.energy_systems.distribution_system import DistributionSystem
|
||||
from hub.city_model_structure.energy_systems.non_pv_generation_system import NonPvGenerationSystem
|
||||
from hub.city_model_structure.energy_systems.pv_generation_system import PvGenerationSystem
|
||||
from hub.city_model_structure.energy_systems.electrical_storage_system import ElectricalStorageSystem
|
||||
from hub.city_model_structure.energy_systems.thermal_storage_system import ThermalStorageSystem
|
||||
from hub.city_model_structure.energy_systems.emission_system import EmissionSystem
|
||||
from hub.helpers.dictionaries import Dictionaries
|
||||
|
||||
|
||||
class NorthAmericaCustomEnergySystemParameters:
|
||||
"""
|
||||
MontrealCustomEnergySystemParameters class
|
||||
"""
|
||||
|
||||
def __init__(self, city):
|
||||
self._city = city
|
||||
|
||||
def enrich_buildings(self):
|
||||
"""
|
||||
Returns the city with the system parameters assigned to the buildings
|
||||
:return:
|
||||
"""
|
||||
city = self._city
|
||||
montreal_custom_catalog = EnergySystemsCatalogFactory('north_america').catalog
|
||||
if city.generic_energy_systems is None:
|
||||
_generic_energy_systems = {}
|
||||
else:
|
||||
_generic_energy_systems = city.generic_energy_systems
|
||||
for building in city.buildings:
|
||||
archetype_name = building.energy_systems_archetype_name
|
||||
try:
|
||||
archetype = self._search_archetypes(montreal_custom_catalog, archetype_name)
|
||||
except KeyError:
|
||||
logging.error('Building %s has unknown energy system archetype for system name %s', building.name,
|
||||
archetype_name)
|
||||
continue
|
||||
|
||||
if archetype.name not in _generic_energy_systems:
|
||||
_generic_energy_systems = self._create_generic_systems_list(archetype, _generic_energy_systems)
|
||||
|
||||
city.generic_energy_systems = _generic_energy_systems
|
||||
|
||||
self._assign_energy_systems_to_buildings(city)
|
||||
|
||||
@staticmethod
|
||||
def _search_archetypes(catalog, name):
|
||||
archetypes = catalog.entries('archetypes')
|
||||
for building_archetype in archetypes:
|
||||
if str(name) == str(building_archetype.name):
|
||||
return building_archetype
|
||||
raise KeyError('archetype not found')
|
||||
|
||||
def _create_generic_systems_list(self, archetype, _generic_energy_systems):
|
||||
building_systems = []
|
||||
for archetype_system in archetype.systems:
|
||||
energy_system = EnergySystem()
|
||||
_hub_demand_types = []
|
||||
for demand_type in archetype_system.demand_types:
|
||||
_hub_demand_types.append(Dictionaries().montreal_demand_type_to_hub_energy_demand_type[demand_type])
|
||||
energy_system.name = archetype_system.name
|
||||
energy_system.demand_types = _hub_demand_types
|
||||
energy_system.configuration_schema = archetype_system.configuration_schema
|
||||
energy_system.generation_systems = self._create_generation_systems(archetype_system)
|
||||
if energy_system.distribution_systems is not None:
|
||||
energy_system.distribution_systems = self._create_distribution_systems(archetype_system)
|
||||
building_systems.append(energy_system)
|
||||
|
||||
_generic_energy_systems[archetype.name] = building_systems
|
||||
|
||||
return _generic_energy_systems
|
||||
|
||||
@staticmethod
|
||||
def _create_generation_systems(archetype_system):
|
||||
_generation_systems = []
|
||||
for archetype_generation_system in archetype_system.generation_systems:
|
||||
if archetype_generation_system.system_type == 'PV system':
|
||||
_generation_system = PvGenerationSystem()
|
||||
_type = 'PV system'
|
||||
_generation_system.system_type = Dictionaries().montreal_generation_system_to_hub_energy_generation_system[_type]
|
||||
_fuel_type = Dictionaries().montreal_custom_fuel_to_hub_fuel[archetype_generation_system.fuel_type]
|
||||
_generation_system.fuel_type = _fuel_type
|
||||
_generation_system.electricity_efficiency = archetype_generation_system.electricity_efficiency
|
||||
_generic_storage_system = None
|
||||
if archetype_generation_system.energy_storage_systems is not None:
|
||||
_generic_storage_system = ElectricalStorageSystem()
|
||||
_generic_storage_system.type_energy_stored = 'electrical'
|
||||
_generation_system.energy_storage_systems = [_generic_storage_system]
|
||||
else:
|
||||
_generation_system = NonPvGenerationSystem()
|
||||
_type = archetype_generation_system.system_type
|
||||
_generation_system.system_type = Dictionaries().montreal_generation_system_to_hub_energy_generation_system[_type]
|
||||
_fuel_type = Dictionaries().north_america_custom_fuel_to_hub_fuel[archetype_generation_system.fuel_type]
|
||||
_generation_system.fuel_type = _fuel_type
|
||||
_generation_system.source_types = archetype_generation_system.source_medium
|
||||
_generation_system.heat_efficiency = archetype_generation_system.heat_efficiency
|
||||
_generation_system.cooling_efficiency = archetype_generation_system.cooling_efficiency
|
||||
_generation_system.electricity_efficiency = archetype_generation_system.electricity_efficiency
|
||||
_generic_storage_system = None
|
||||
if archetype_generation_system.energy_storage_systems is not None:
|
||||
_storage_systems = []
|
||||
for storage_system in archetype_generation_system.energy_storage_systems:
|
||||
if storage_system.type_energy_stored == 'electrical':
|
||||
_generic_storage_system = ElectricalStorageSystem()
|
||||
_generic_storage_system.type_energy_stored = 'electrical'
|
||||
else:
|
||||
_generic_storage_system = ThermalStorageSystem()
|
||||
_generic_storage_system.type_energy_stored = 'thermal'
|
||||
_storage_systems.append(_generic_storage_system)
|
||||
_generation_system.energy_storage_systems = [_storage_systems]
|
||||
if archetype_generation_system.dual_supply_capability:
|
||||
_generation_system.dual_supply_capability = True
|
||||
_generation_systems.append(_generation_system)
|
||||
return _generation_systems
|
||||
|
||||
@staticmethod
|
||||
def _create_distribution_systems(archetype_system):
|
||||
_distribution_systems = []
|
||||
for archetype_distribution_system in archetype_system.distribution_systems:
|
||||
_distribution_system = DistributionSystem()
|
||||
_distribution_system.type = archetype_distribution_system.type
|
||||
_distribution_system.distribution_consumption_fix_flow = \
|
||||
archetype_distribution_system.distribution_consumption_fix_flow
|
||||
_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
|
||||
if archetype_distribution_system.emission_systems is not None:
|
||||
_emission_system = EmissionSystem()
|
||||
_distribution_system.emission_systems = [_emission_system]
|
||||
_distribution_systems.append(_distribution_system)
|
||||
return _distribution_systems
|
||||
|
||||
@staticmethod
|
||||
def _assign_energy_systems_to_buildings(city):
|
||||
for building in city.buildings:
|
||||
_building_energy_systems = []
|
||||
energy_systems_cluster_name = building.energy_systems_archetype_name
|
||||
if str(energy_systems_cluster_name) == 'nan':
|
||||
break
|
||||
_generic_building_energy_systems = city.generic_energy_systems[energy_systems_cluster_name]
|
||||
for _generic_building_energy_system in _generic_building_energy_systems:
|
||||
_building_energy_systems.append(copy.deepcopy(_generic_building_energy_system))
|
||||
|
||||
building.energy_systems = _building_energy_systems
|
@ -6,17 +6,15 @@ Project Coder Pilar Monsalvete pilar.monsalvete@concordi.
|
||||
Code contributors: Peter Yefi peteryefi@gmail.com
|
||||
"""
|
||||
from pathlib import Path
|
||||
|
||||
from hub.helpers.utils import validate_import_export_type
|
||||
from hub.imports.energy_systems.montreal_custom_energy_system_parameters import MontrealCustomEnergySystemParameters
|
||||
from hub.imports.energy_systems.north_america_custom_energy_system_parameters import NorthAmericaCustomEnergySystemParameters
|
||||
from hub.imports.energy_systems.montreal_future_energy_systems_parameters import MontrealFutureEnergySystemParameters
|
||||
|
||||
|
||||
class EnergySystemsFactory:
|
||||
"""
|
||||
EnergySystemsFactory class
|
||||
"""
|
||||
|
||||
def __init__(self, handler, city, base_path=None):
|
||||
if base_path is None:
|
||||
base_path = Path(Path(__file__).parent.parent / 'data/energy_systems')
|
||||
@ -34,15 +32,6 @@ class EnergySystemsFactory:
|
||||
for building in self._city.buildings:
|
||||
building.level_of_detail.energy_systems = 1
|
||||
|
||||
def _north_america(self):
|
||||
"""
|
||||
Enrich the city by using north america custom energy systems catalog information
|
||||
"""
|
||||
NorthAmericaCustomEnergySystemParameters(self._city).enrich_buildings()
|
||||
self._city.level_of_detail.energy_systems = 2
|
||||
for building in self._city.buildings:
|
||||
building.level_of_detail.energy_systems = 2
|
||||
|
||||
def _montreal_future(self):
|
||||
"""
|
||||
Enrich the city by using north america custom energy systems catalog information
|
||||
|
@ -127,6 +127,27 @@ class Geojson:
|
||||
function = None
|
||||
if self._function_field is not None:
|
||||
function = str(feature['properties'][self._function_field])
|
||||
if function == 'Mixed use' or function == 'mixed use':
|
||||
function_parts = []
|
||||
if 'usages' in feature['properties']:
|
||||
usages = feature['properties']['usages']
|
||||
for usage in usages:
|
||||
if self._function_to_hub is not None and usage['usage'] in self._function_to_hub:
|
||||
function_parts.append(f"{usage['percentage']}-{self._function_to_hub[usage['usage']]}")
|
||||
else:
|
||||
function_parts.append(f"{usage['percentage']}-{usage['usage']}")
|
||||
else:
|
||||
for key, value in feature['properties'].items():
|
||||
if key.startswith("mixed_type_") and not key.endswith("_percentage"):
|
||||
type_key = key
|
||||
percentage_key = f"{key}_percentage"
|
||||
if percentage_key in feature['properties']:
|
||||
if self._function_to_hub is not None and feature['properties'][type_key] in self._function_to_hub:
|
||||
usage_function = self._function_to_hub[feature['properties'][type_key]]
|
||||
function_parts.append(f"{feature['properties'][percentage_key]}-{usage_function}")
|
||||
else:
|
||||
function_parts.append(f"{feature['properties'][percentage_key]}-{feature['properties'][type_key]}")
|
||||
function = "_".join(function_parts)
|
||||
if self._function_to_hub is not None:
|
||||
# use the transformation dictionary to retrieve the proper function
|
||||
if function in self._function_to_hub:
|
||||
@ -135,6 +156,8 @@ class Geojson:
|
||||
building_aliases = []
|
||||
if 'id' in feature:
|
||||
building_name = feature['id']
|
||||
elif 'id' in feature['properties']:
|
||||
building_name = feature['properties']['id']
|
||||
else:
|
||||
building_name = uuid.uuid4()
|
||||
if self._aliases_field is not None:
|
||||
|
@ -60,9 +60,12 @@ class EnergyPlusMultipleBuildings:
|
||||
for building in self._city.buildings:
|
||||
building.heating_demand[cte.HOUR] = building_energy_demands[f'Building {building.name} Heating Demand (J)']
|
||||
building.cooling_demand[cte.HOUR] = building_energy_demands[f'Building {building.name} Cooling Demand (J)']
|
||||
building.domestic_hot_water_heat_demand[cte.HOUR] = building_energy_demands[f'Building {building.name} DHW Demand (W)']
|
||||
building.appliances_electrical_demand[cte.HOUR] = building_energy_demands[f'Building {building.name} Appliances (W)']
|
||||
building.lighting_electrical_demand[cte.HOUR] = building_energy_demands[f'Building {building.name} Lighting (W)']
|
||||
building.domestic_hot_water_heat_demand[cte.HOUR] = \
|
||||
[x * cte.WATTS_HOUR_TO_JULES for x in building_energy_demands[f'Building {building.name} DHW Demand (W)']]
|
||||
building.appliances_electrical_demand[cte.HOUR] = \
|
||||
[x * cte.WATTS_HOUR_TO_JULES for x in building_energy_demands[f'Building {building.name} Appliances (W)']]
|
||||
building.lighting_electrical_demand[cte.HOUR] = \
|
||||
[x * cte.WATTS_HOUR_TO_JULES for x in building_energy_demands[f'Building {building.name} Lighting (W)']]
|
||||
building.heating_demand[cte.MONTH] = MonthlyValues.get_total_month(building.heating_demand[cte.HOUR])
|
||||
building.cooling_demand[cte.MONTH] = MonthlyValues.get_total_month(building.cooling_demand[cte.HOUR])
|
||||
building.domestic_hot_water_heat_demand[cte.MONTH] = (
|
||||
|
@ -35,29 +35,60 @@ class ComnetUsageParameters:
|
||||
city = self._city
|
||||
comnet_catalog = UsageCatalogFactory('comnet').catalog
|
||||
for building in city.buildings:
|
||||
usage_name = Dictionaries().hub_usage_to_comnet_usage[building.function]
|
||||
try:
|
||||
archetype_usage = self._search_archetypes(comnet_catalog, usage_name)
|
||||
except KeyError:
|
||||
logging.error('Building %s has unknown usage archetype for usage %s', building.name, usage_name)
|
||||
continue
|
||||
|
||||
for internal_zone in building.internal_zones:
|
||||
if internal_zone.area is None:
|
||||
raise TypeError('Internal zone area not defined, ACH cannot be calculated')
|
||||
if internal_zone.volume is None:
|
||||
raise TypeError('Internal zone volume not defined, ACH cannot be calculated')
|
||||
if internal_zone.area <= 0:
|
||||
raise TypeError('Internal zone area is zero, ACH cannot be calculated')
|
||||
volume_per_area = internal_zone.volume / internal_zone.area
|
||||
usage = Usage()
|
||||
usage.name = usage_name
|
||||
self._assign_values(usage, archetype_usage, volume_per_area, building.cold_water_temperature)
|
||||
usage.percentage = 1
|
||||
self._calculate_reduced_values_from_extended_library(usage, archetype_usage)
|
||||
|
||||
internal_zone.usages = [usage]
|
||||
usages = []
|
||||
comnet_archetype_usages = []
|
||||
building_functions = building.function.split('_')
|
||||
for function in building_functions:
|
||||
usages.append(function.split('-'))
|
||||
for usage in usages:
|
||||
comnet_usage_name = Dictionaries().hub_usage_to_comnet_usage[usage[-1]]
|
||||
try:
|
||||
comnet_archetype_usage = self._search_archetypes(comnet_catalog, comnet_usage_name)
|
||||
comnet_archetype_usages.append(comnet_archetype_usage)
|
||||
except KeyError:
|
||||
logging.error('Building %s has unknown usage archetype for usage %s', building.name, comnet_usage_name)
|
||||
continue
|
||||
for (i, internal_zone) in enumerate(building.internal_zones):
|
||||
internal_zone_usages = []
|
||||
if len(building.internal_zones) > 1:
|
||||
volume_per_area = 0
|
||||
if internal_zone.area is None:
|
||||
logging.error('Building %s has internal zone area not defined, ACH cannot be calculated for usage %s',
|
||||
building.name, usages[i][-1])
|
||||
continue
|
||||
if internal_zone.volume is None:
|
||||
logging.error('Building %s has internal zone volume not defined, ACH cannot be calculated for usage %s',
|
||||
building.name, usages[i][-1])
|
||||
continue
|
||||
if internal_zone.area <= 0:
|
||||
logging.error('Building %s has internal zone area equal to 0, ACH cannot be calculated for usage %s',
|
||||
building.name, usages[i][-1])
|
||||
continue
|
||||
volume_per_area += internal_zone.volume / internal_zone.area
|
||||
usage = Usage()
|
||||
usage.name = usages[i][-1]
|
||||
self._assign_values(usage, comnet_archetype_usages[i], volume_per_area, building.cold_water_temperature)
|
||||
usage.percentage = 1
|
||||
self._calculate_reduced_values_from_extended_library(usage, comnet_archetype_usages[i])
|
||||
internal_zone_usages.append(usage)
|
||||
else:
|
||||
if building.storeys_above_ground is None:
|
||||
logging.error('Building %s no number of storeys assigned, ACH cannot be calculated for usage %s',
|
||||
building.name, usages)
|
||||
continue
|
||||
volume_per_area = building.volume / building.floor_area / building.storeys_above_ground
|
||||
for (j, mixed_usage) in enumerate(usages):
|
||||
usage = Usage()
|
||||
usage.name = mixed_usage[-1]
|
||||
if len(usages) > 1:
|
||||
usage.percentage = float(mixed_usage[0]) / 100
|
||||
else:
|
||||
usage.percentage = 1
|
||||
self._assign_values(usage, comnet_archetype_usages[j], volume_per_area, building.cold_water_temperature)
|
||||
self._calculate_reduced_values_from_extended_library(usage, comnet_archetype_usages[j])
|
||||
internal_zone_usages.append(usage)
|
||||
|
||||
internal_zone.usages = internal_zone_usages
|
||||
@staticmethod
|
||||
def _search_archetypes(comnet_catalog, usage_name):
|
||||
comnet_archetypes = comnet_catalog.entries('archetypes').usages
|
||||
|
@ -33,53 +33,72 @@ class NrcanUsageParameters:
|
||||
city = self._city
|
||||
nrcan_catalog = UsageCatalogFactory('nrcan').catalog
|
||||
comnet_catalog = UsageCatalogFactory('comnet').catalog
|
||||
|
||||
for building in city.buildings:
|
||||
usage_name = Dictionaries().hub_usage_to_nrcan_usage[building.function]
|
||||
try:
|
||||
archetype_usage = self._search_archetypes(nrcan_catalog, usage_name)
|
||||
except KeyError:
|
||||
logging.error('Building %s has unknown usage archetype for usage %s', building.name, usage_name)
|
||||
continue
|
||||
usages = []
|
||||
nrcan_archetype_usages = []
|
||||
comnet_archetype_usages = []
|
||||
building_functions = building.function.split('_')
|
||||
for function in building_functions:
|
||||
usages.append(function.split('-'))
|
||||
for usage in usages:
|
||||
usage_name = Dictionaries().hub_usage_to_nrcan_usage[usage[-1]]
|
||||
try:
|
||||
archetype_usage = self._search_archetypes(nrcan_catalog, usage_name)
|
||||
nrcan_archetype_usages.append(archetype_usage)
|
||||
except KeyError:
|
||||
logging.error('Building %s has unknown usage archetype for usage %s', building.name, usage_name)
|
||||
continue
|
||||
comnet_usage_name = Dictionaries().hub_usage_to_comnet_usage[usage[-1]]
|
||||
try:
|
||||
comnet_archetype_usage = self._search_archetypes(comnet_catalog, comnet_usage_name)
|
||||
comnet_archetype_usages.append(comnet_archetype_usage)
|
||||
except KeyError:
|
||||
logging.error('Building %s has unknown usage archetype for usage %s', building.name, comnet_usage_name)
|
||||
continue
|
||||
|
||||
comnet_usage_name = Dictionaries().hub_usage_to_comnet_usage[building.function]
|
||||
try:
|
||||
comnet_archetype_usage = self._search_archetypes(comnet_catalog, comnet_usage_name)
|
||||
except KeyError:
|
||||
logging.error('Building %s has unknown usage archetype for usage %s', building.name, comnet_usage_name)
|
||||
continue
|
||||
|
||||
for internal_zone in building.internal_zones:
|
||||
for (i, internal_zone) in enumerate(building.internal_zones):
|
||||
internal_zone_usages = []
|
||||
if len(building.internal_zones) > 1:
|
||||
volume_per_area = 0
|
||||
if internal_zone.area is None:
|
||||
logging.error('Building %s has internal zone area not defined, ACH cannot be calculated for usage %s',
|
||||
building.name, usage_name)
|
||||
building.name, usages[i][-1])
|
||||
continue
|
||||
if internal_zone.volume is None:
|
||||
logging.error('Building %s has internal zone volume not defined, ACH cannot be calculated for usage %s',
|
||||
building.name, usage_name)
|
||||
building.name, usages[i][-1])
|
||||
continue
|
||||
if internal_zone.area <= 0:
|
||||
logging.error('Building %s has internal zone area equal to 0, ACH cannot be calculated for usage %s',
|
||||
building.name, usage_name)
|
||||
building.name, usages[i][-1])
|
||||
continue
|
||||
volume_per_area += internal_zone.volume / internal_zone.area
|
||||
usage = Usage()
|
||||
usage.name = usages[i][-1]
|
||||
self._assign_values(usage, nrcan_archetype_usages[i], volume_per_area, building.cold_water_temperature)
|
||||
self._assign_comnet_extra_values(usage, comnet_archetype_usages[i], nrcan_archetype_usages[i].occupancy.occupancy_density)
|
||||
usage.percentage = 1
|
||||
self._calculate_reduced_values_from_extended_library(usage, nrcan_archetype_usages[i])
|
||||
internal_zone_usages.append(usage)
|
||||
else:
|
||||
if building.storeys_above_ground is None:
|
||||
logging.error('Building %s no number of storeys assigned, ACH cannot be calculated for usage %s',
|
||||
building.name, usage_name)
|
||||
building.name, usages)
|
||||
continue
|
||||
volume_per_area = building.volume / building.floor_area / building.storeys_above_ground
|
||||
for (j, mixed_usage) in enumerate(usages):
|
||||
usage = Usage()
|
||||
usage.name = mixed_usage[-1]
|
||||
if len(usages) > 1:
|
||||
usage.percentage = float(mixed_usage[0]) / 100
|
||||
else:
|
||||
usage.percentage = 1
|
||||
self._assign_values(usage, nrcan_archetype_usages[j], volume_per_area, building.cold_water_temperature)
|
||||
self._assign_comnet_extra_values(usage, comnet_archetype_usages[j], nrcan_archetype_usages[j].occupancy.occupancy_density)
|
||||
self._calculate_reduced_values_from_extended_library(usage, nrcan_archetype_usages[j])
|
||||
internal_zone_usages.append(usage)
|
||||
|
||||
usage = Usage()
|
||||
usage.name = usage_name
|
||||
self._assign_values(usage, archetype_usage, volume_per_area, building.cold_water_temperature)
|
||||
self._assign_comnet_extra_values(usage, comnet_archetype_usage, archetype_usage.occupancy.occupancy_density)
|
||||
usage.percentage = 1
|
||||
self._calculate_reduced_values_from_extended_library(usage, archetype_usage)
|
||||
|
||||
internal_zone.usages = [usage]
|
||||
internal_zone.usages = internal_zone_usages
|
||||
|
||||
@staticmethod
|
||||
def _search_archetypes(catalog, usage_name):
|
||||
|
@ -114,18 +114,22 @@ class EpwWeatherParameters:
|
||||
for x in self._weather_values['global_horizontal_radiation_wh_m2']]
|
||||
building.diffuse[cte.HOUR] = [x * cte.WATTS_HOUR_TO_JULES
|
||||
for x in self._weather_values['diffuse_horizontal_radiation_wh_m2']]
|
||||
building.beam[cte.HOUR] = [x * cte.WATTS_HOUR_TO_JULES
|
||||
for x in self._weather_values['direct_normal_radiation_wh_m2']]
|
||||
building.direct_normal[cte.HOUR] = [x * cte.WATTS_HOUR_TO_JULES
|
||||
for x in self._weather_values['direct_normal_radiation_wh_m2']]
|
||||
building.beam[cte.HOUR] = [building.global_horizontal[cte.HOUR][i] -
|
||||
building.diffuse[cte.HOUR][i]
|
||||
for i in range(len(building.global_horizontal[cte.HOUR]))]
|
||||
building.cold_water_temperature[cte.HOUR] = wh().cold_water_temperature(building.external_temperature[cte.HOUR])
|
||||
|
||||
|
||||
# create the monthly and yearly values out of the hourly
|
||||
for building in self._city.buildings:
|
||||
building.external_temperature[cte.MONTH] = \
|
||||
MonthlyValues().get_mean_values(building.external_temperature[cte.HOUR])
|
||||
building.external_temperature[cte.YEAR] = [sum(building.external_temperature[cte.HOUR]) / 9870]
|
||||
building.external_temperature[cte.YEAR] = [sum(building.external_temperature[cte.HOUR]) / 8760]
|
||||
building.cold_water_temperature[cte.MONTH] = \
|
||||
MonthlyValues().get_mean_values(building.cold_water_temperature[cte.HOUR])
|
||||
building.cold_water_temperature[cte.YEAR] = [sum(building.cold_water_temperature[cte.HOUR]) / 9870]
|
||||
building.cold_water_temperature[cte.YEAR] = [sum(building.cold_water_temperature[cte.HOUR]) / 8760]
|
||||
|
||||
# If the usage has already being imported, the domestic hot water missing values must be calculated here that
|
||||
# the cold water temperature is finally known
|
||||
|
@ -8,7 +8,7 @@ Project Coder Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca
|
||||
import logging
|
||||
import math
|
||||
import hub.helpers.constants as cte
|
||||
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
class Weather:
|
||||
"""
|
||||
@ -55,25 +55,19 @@ class Weather:
|
||||
# and Craig Christensen, National Renewable Energy Laboratory
|
||||
# ambient temperatures( in °C)
|
||||
# cold water temperatures( in °C)
|
||||
ambient_temperature_fahrenheit = []
|
||||
average_temperature = 0
|
||||
maximum_temperature = -1000
|
||||
minimum_temperature = 1000
|
||||
for temperature in ambient_temperature:
|
||||
value = temperature * 9 / 5 + 32
|
||||
ambient_temperature_fahrenheit.append(value)
|
||||
average_temperature += value / 8760
|
||||
if value > maximum_temperature:
|
||||
maximum_temperature = value
|
||||
if value < minimum_temperature:
|
||||
minimum_temperature = value
|
||||
delta_temperature = maximum_temperature - minimum_temperature
|
||||
ratio = 0.4 + 0.01 * (average_temperature - 44)
|
||||
lag = 35 - 1 * (average_temperature - 44)
|
||||
t_out_fahrenheit = [1.8 * t_out + 32 for t_out in ambient_temperature]
|
||||
t_out_average = sum(t_out_fahrenheit) / len(t_out_fahrenheit)
|
||||
max_difference = max(t_out_fahrenheit) - min(t_out_fahrenheit)
|
||||
ratio = 0.4 + 0.01 * (t_out_average - 44)
|
||||
lag = 35 - (t_out_average - 35)
|
||||
number_of_day = [a for a in range(1, 366)]
|
||||
day_of_year = [day for day in number_of_day for _ in range(24)]
|
||||
cold_temperature_fahrenheit = []
|
||||
cold_temperature = []
|
||||
for temperature in ambient_temperature_fahrenheit:
|
||||
radians = (0.986 * (temperature-15-lag) - 90) * math.pi / 180
|
||||
cold_temperature.append((average_temperature + 6 + ratio * (delta_temperature/2) * math.sin(radians) - 32) * 5/9)
|
||||
for i in range(len(ambient_temperature)):
|
||||
cold_temperature_fahrenheit.append(t_out_average + 6 + ratio * (max_difference / 2) *
|
||||
math.sin(math.radians(0.986 * (day_of_year[i] - 15 - lag) - 90)))
|
||||
cold_temperature.append((cold_temperature_fahrenheit[i] - 32) / 1.8)
|
||||
return cold_temperature
|
||||
|
||||
def epw_file(self, region_code):
|
||||
|
@ -136,3 +136,7 @@ class City(Repository):
|
||||
except SQLAlchemyError as err:
|
||||
logging.error('Error while fetching city by name %s', err)
|
||||
raise SQLAlchemyError from err
|
||||
|
||||
def get_by_id(self, city_id) -> Model:
|
||||
with Session(self.engine) as session:
|
||||
return session.execute(select(Model).where(Model.id == city_id)).first()[0]
|
65
main.py
65
main.py
@ -1,65 +0,0 @@
|
||||
from scripts.geojson_creator import process_geojson
|
||||
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_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.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
|
||||
# Specify the GeoJSON file path
|
||||
# geojson_file = process_geojson(x=-73.5953602192335, y=45.492414530022515, diff=0.001)
|
||||
file_path = (Path(__file__).parent / 'input_files' / 'output_buildings.geojson')
|
||||
# Specify the output path for the PDF file
|
||||
output_path = (Path(__file__).parent / 'out_files').resolve()
|
||||
# Create city object from GeoJSON file
|
||||
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
|
||||
# Enrich city data
|
||||
ConstructionFactory('nrcan', city).enrich()
|
||||
|
||||
UsageFactory('nrcan', city).enrich()
|
||||
WeatherFactory('epw', city).enrich()
|
||||
energy_plus_workflow(city)
|
||||
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)
|
||||
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)
|
||||
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')
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -1,65 +0,0 @@
|
||||
"""
|
||||
Total maintenance costs module
|
||||
SPDX - License - Identifier: LGPL - 3.0 - or -later
|
||||
Copyright © 2023 Project Coder Guille Gutierrez guillermo.gutierrezmorote@concordia.ca
|
||||
Code contributor Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca
|
||||
Code contributor Oriol Gavalda Torrellas oriol.gavalda@concordia.ca
|
||||
"""
|
||||
import math
|
||||
import pandas as pd
|
||||
from hub.city_model_structure.building import Building
|
||||
import hub.helpers.constants as cte
|
||||
|
||||
from scripts.costs.configuration import Configuration
|
||||
from scripts.costs.cost_base import CostBase
|
||||
|
||||
|
||||
class TotalMaintenanceCosts(CostBase):
|
||||
"""
|
||||
Total maintenance costs class
|
||||
"""
|
||||
def __init__(self, building: Building, configuration: Configuration):
|
||||
super().__init__(building, configuration)
|
||||
self._yearly_maintenance_costs = pd.DataFrame(
|
||||
index=self._rng,
|
||||
columns=[
|
||||
'Heating_maintenance',
|
||||
'Cooling_maintenance',
|
||||
'PV_maintenance'
|
||||
],
|
||||
dtype='float'
|
||||
)
|
||||
|
||||
def calculate(self) -> pd.DataFrame:
|
||||
"""
|
||||
Calculate total maintenance costs
|
||||
:return: pd.DataFrame
|
||||
"""
|
||||
building = self._building
|
||||
archetype = self._archetype
|
||||
# todo: change area pv when the variable exists
|
||||
roof_area = 0
|
||||
for roof in building.roofs:
|
||||
roof_area += roof.solid_polygon.area
|
||||
surface_pv = roof_area * 0.5
|
||||
|
||||
peak_heating = building.heating_peak_load[cte.YEAR][0] / 3.6e6
|
||||
peak_cooling = building.cooling_peak_load[cte.YEAR][0] / 3.6e6
|
||||
|
||||
maintenance_heating_0 = peak_heating * archetype.operational_cost.maintenance_heating
|
||||
maintenance_cooling_0 = peak_cooling * archetype.operational_cost.maintenance_cooling
|
||||
maintenance_pv_0 = surface_pv * archetype.operational_cost.maintenance_pv
|
||||
|
||||
for year in range(1, self._configuration.number_of_years + 1):
|
||||
costs_increase = math.pow(1 + self._configuration.consumer_price_index, year)
|
||||
self._yearly_maintenance_costs.loc[year, 'Heating_maintenance'] = (
|
||||
maintenance_heating_0 * costs_increase
|
||||
)
|
||||
self._yearly_maintenance_costs.loc[year, 'Cooling_maintenance'] = (
|
||||
maintenance_cooling_0 * costs_increase
|
||||
)
|
||||
self._yearly_maintenance_costs.loc[year, 'PV_maintenance'] = (
|
||||
maintenance_pv_0 * costs_increase
|
||||
)
|
||||
self._yearly_maintenance_costs.fillna(0, inplace=True)
|
||||
return self._yearly_maintenance_costs
|
@ -1,104 +0,0 @@
|
||||
"""
|
||||
Total operational costs module
|
||||
SPDX - License - Identifier: LGPL - 3.0 - or -later
|
||||
Copyright © 2024 Project Coder Saeed Ranjbar saeed.ranjbar@mail.concordia.ca
|
||||
Code contributor Oriol Gavalda Torrellas oriol.gavalda@concordia.ca
|
||||
"""
|
||||
import math
|
||||
import pandas as pd
|
||||
|
||||
from hub.city_model_structure.building import Building
|
||||
import hub.helpers.constants as cte
|
||||
|
||||
from scripts.costs.configuration import Configuration
|
||||
from scripts.costs.cost_base import CostBase
|
||||
from scripts.costs.peak_load import PeakLoad
|
||||
|
||||
|
||||
class TotalOperationalCosts(CostBase):
|
||||
"""
|
||||
Total Operational costs class
|
||||
"""
|
||||
|
||||
def __init__(self, building: Building, configuration: Configuration):
|
||||
super().__init__(building, configuration)
|
||||
columns_list = self.columns()
|
||||
self._yearly_operational_costs = pd.DataFrame(
|
||||
index=self._rng,
|
||||
columns=columns_list,
|
||||
dtype='float'
|
||||
)
|
||||
|
||||
def calculate(self) -> pd.DataFrame:
|
||||
"""
|
||||
Calculate total operational costs
|
||||
:return: pd.DataFrame
|
||||
"""
|
||||
building = self._building
|
||||
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.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
|
||||
fuels = archetype.operational_cost.fuels
|
||||
for fuel in fuels:
|
||||
if fuel.type in fuel_consumption_breakdown.keys():
|
||||
if fuel.type == cte.ELECTRICITY:
|
||||
variable_electricity_cost_year_0 = (
|
||||
total_electricity_consumption * fuel.variable[0] / 1000
|
||||
)
|
||||
peak_electricity_cost_year_0 = peak_electricity_demand * fuel.fixed_power * 12
|
||||
monthly_electricity_cost_year_0 = fuel.fixed_monthly * 12 * factor
|
||||
for year in range(1, self._configuration.number_of_years + 1):
|
||||
price_increase_electricity = math.pow(1 + self._configuration.electricity_price_index, year)
|
||||
price_increase_peak_electricity = math.pow(1 + self._configuration.electricity_peak_index, year)
|
||||
self._yearly_operational_costs.at[year, 'Fixed Costs Electricity Peak'] = (
|
||||
peak_electricity_cost_year_0 * price_increase_peak_electricity
|
||||
)
|
||||
self._yearly_operational_costs.at[year, 'Fixed Costs Electricity Monthly'] = (
|
||||
monthly_electricity_cost_year_0 * price_increase_peak_electricity
|
||||
)
|
||||
if not isinstance(variable_electricity_cost_year_0, pd.DataFrame):
|
||||
variable_costs_electricity = variable_electricity_cost_year_0 * price_increase_electricity
|
||||
else:
|
||||
variable_costs_electricity = float(variable_electricity_cost_year_0.iloc[0] * price_increase_electricity)
|
||||
self._yearly_operational_costs.at[year, 'Variable Costs Electricity'] = (
|
||||
variable_costs_electricity
|
||||
)
|
||||
else:
|
||||
fuel_fixed_cost = fuel.fixed_monthly * 12 * factor
|
||||
if fuel.type == cte.BIOMASS:
|
||||
conversion_factor = 1
|
||||
else:
|
||||
conversion_factor = fuel.density[0]
|
||||
variable_cost_fuel = (
|
||||
((sum(fuel_consumption_breakdown[fuel.type].values()) * 3600)/(1e6*fuel.lower_heating_value[0] * conversion_factor)) * fuel.variable[0])
|
||||
for year in range(1, self._configuration.number_of_years + 1):
|
||||
price_increase_gas = math.pow(1 + self._configuration.gas_price_index, year)
|
||||
self._yearly_operational_costs.at[year, f'Fixed Costs {fuel.type}'] = fuel_fixed_cost * price_increase_gas
|
||||
self._yearly_operational_costs.at[year, f'Variable Costs {fuel.type}'] = (
|
||||
variable_cost_fuel * price_increase_gas)
|
||||
self._yearly_operational_costs.fillna(0, inplace=True)
|
||||
|
||||
return self._yearly_operational_costs
|
||||
|
||||
def columns(self):
|
||||
columns_list = []
|
||||
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')
|
||||
columns_list.append('Fixed Costs Electricity Monthly')
|
||||
columns_list.append('Variable Costs Electricity')
|
||||
else:
|
||||
columns_list.append(f'Fixed Costs {fuel}')
|
||||
columns_list.append(f'Variable Costs {fuel}')
|
||||
|
||||
return columns_list
|
||||
|
16
scripts/district_heating_network/directory_manager.py
Normal file
16
scripts/district_heating_network/directory_manager.py
Normal file
@ -0,0 +1,16 @@
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
class DirectoryManager:
|
||||
def __init__(self, base_path):
|
||||
self.base_path = Path(base_path)
|
||||
self.directories = {}
|
||||
|
||||
def create_directory(self, relative_path):
|
||||
full_path = self.base_path / relative_path
|
||||
full_path.mkdir(parents=True, exist_ok=True)
|
||||
self.directories[relative_path] = full_path
|
||||
return full_path
|
||||
|
||||
def get_directory(self, relative_path):
|
||||
return self.directories.get(relative_path, None)
|
248
scripts/district_heating_network/district_heating_factory.py
Normal file
248
scripts/district_heating_network/district_heating_factory.py
Normal file
@ -0,0 +1,248 @@
|
||||
import CoolProp.CoolProp as CP
|
||||
import math
|
||||
import logging
|
||||
import numpy as np
|
||||
import csv
|
||||
|
||||
|
||||
class DistrictHeatingFactory:
|
||||
"""
|
||||
DistrictHeatingFactory class
|
||||
|
||||
This class is responsible for managing the district heating network, including
|
||||
enriching the network graph with building data, calculating flow rates,
|
||||
sizing pipes, and analyzing costs.
|
||||
"""
|
||||
|
||||
def __init__(self, city, graph, supply_temperature, return_temperature, simultaneity_factor):
|
||||
"""
|
||||
Initialize the DistrictHeatingFactory object.
|
||||
|
||||
:param city: The city object containing buildings and their heating demands.
|
||||
:param graph: The network graph representing the district heating network.
|
||||
:param supply_temperature: The supply temperature of the heating fluid in the network (°C).
|
||||
:param return_temperature: The return temperature of the heating fluid in the network (°C).
|
||||
:param simultaneity_factor: The simultaneity factor used to adjust flow rates for non-building pipes.
|
||||
"""
|
||||
self._city = city
|
||||
self._network_graph = graph
|
||||
self._supply_temperature = supply_temperature
|
||||
self._return_temperature = return_temperature
|
||||
self.simultaneity_factor = simultaneity_factor
|
||||
self.fluid = "Water" # The fluid used in the heating network
|
||||
|
||||
def enrich(self):
|
||||
"""
|
||||
Enrich the network graph nodes with the whole building object from the city buildings.
|
||||
|
||||
This method associates each building node in the network graph with its corresponding
|
||||
building object from the city, allowing access to heating demand data during calculations.
|
||||
"""
|
||||
for node_id, node_attrs in self._network_graph.nodes(data=True):
|
||||
if node_attrs.get('type') == 'building':
|
||||
building_name = node_attrs.get('name')
|
||||
building_found = False
|
||||
for building in self._city.buildings:
|
||||
if building.name == building_name:
|
||||
self._network_graph.nodes[node_id]['building_obj'] = building
|
||||
building_found = True
|
||||
break
|
||||
if not building_found:
|
||||
logging.error(msg=f"Building with name '{building_name}' not found in city.")
|
||||
|
||||
def calculate_flow_rates(self, A, Gext):
|
||||
"""
|
||||
Solve the linear system to find the flow rates in each branch.
|
||||
|
||||
:param A: The incidence matrix representing the network connections.
|
||||
:param Gext: The external flow rates for each node in the network.
|
||||
:return: The calculated flow rates for each edge, or None if an error occurs.
|
||||
"""
|
||||
try:
|
||||
G = np.linalg.lstsq(A, Gext, rcond=None)[0]
|
||||
return G
|
||||
except np.linalg.LinAlgError as e:
|
||||
logging.error(f"Error solving the linear system: {e}")
|
||||
return None
|
||||
|
||||
def switch_nodes(self, A, edge_index, node_index, edge):
|
||||
"""
|
||||
Switch the in and out nodes for the given edge in the incidence matrix A.
|
||||
|
||||
:param A: The incidence matrix representing the network connections.
|
||||
:param edge_index: The index of edges in the incidence matrix.
|
||||
:param node_index: The index of nodes in the incidence matrix.
|
||||
:param edge: The edge (u, v) to switch.
|
||||
"""
|
||||
u, v = edge
|
||||
i = node_index[u]
|
||||
j = node_index[v]
|
||||
k = edge_index[edge]
|
||||
A[i, k], A[j, k] = -A[i, k], -A[j, k]
|
||||
|
||||
def sizing(self):
|
||||
"""
|
||||
Calculate the hourly mass flow rates, assign them to the edges, and determine the pipe diameters.
|
||||
|
||||
This method generates the flow rates for each hour, adjusting the incidence matrix as needed to
|
||||
ensure all flow rates are positive. It also applies the simultaneity factor to non-building pipes.
|
||||
"""
|
||||
num_nodes = self._network_graph.number_of_nodes()
|
||||
num_edges = self._network_graph.number_of_edges()
|
||||
A = np.zeros((num_nodes, num_edges)) # Initialize incidence matrix
|
||||
node_index = {node: i for i, node in enumerate(self._network_graph.nodes())}
|
||||
edge_index = {edge: i for i, edge in enumerate(self._network_graph.edges())}
|
||||
|
||||
# Initialize mass flow rate attribute for each edge
|
||||
for u, v, data in self._network_graph.edges(data=True):
|
||||
self._network_graph.edges[u, v]['mass_flow_rate'] = {"hour": [], "peak": None}
|
||||
|
||||
# Get the length of the hourly demand for the first building (assuming all buildings have the same length)
|
||||
building = next(iter(self._city.buildings))
|
||||
num_hours = len(building.heating_demand['hour'])
|
||||
|
||||
# Loop through each hour to generate Gext and solve AG = Gext
|
||||
for hour in range(8760):
|
||||
Gext = np.zeros(num_nodes)
|
||||
|
||||
# Calculate the hourly mass flow rates for each edge and fill Gext
|
||||
for edge in self._network_graph.edges(data=True):
|
||||
u, v, data = edge
|
||||
for node in [u, v]:
|
||||
if self._network_graph.nodes[node].get('type') == 'building':
|
||||
building = self._network_graph.nodes[node].get('building_obj')
|
||||
if building and "hour" in building.heating_demand:
|
||||
hourly_demand = building.heating_demand["hour"][hour] # Get demand for current hour
|
||||
specific_heat_capacity = CP.PropsSI('C', 'T', (self._supply_temperature + self._return_temperature) / 2,
|
||||
'P', 101325, self.fluid)
|
||||
mass_flow_rate = hourly_demand / 3600 / (
|
||||
specific_heat_capacity * (self._supply_temperature - self._return_temperature))
|
||||
Gext[node_index[node]] += mass_flow_rate
|
||||
|
||||
# Update incidence matrix A
|
||||
i = node_index[u]
|
||||
j = node_index[v]
|
||||
k = edge_index[(u, v)]
|
||||
A[i, k] = 1
|
||||
A[j, k] = -1
|
||||
|
||||
# Solve for G (flow rates)
|
||||
G = self.calculate_flow_rates(A, Gext)
|
||||
if G is None:
|
||||
return
|
||||
|
||||
# Check for negative flow rates and adjust A accordingly
|
||||
iterations = 0
|
||||
max_iterations = num_edges * 2
|
||||
while any(flow_rate < 0 for flow_rate in G) and iterations < max_iterations:
|
||||
for idx, flow_rate in enumerate(G):
|
||||
if flow_rate < 0:
|
||||
G[idx] = -G[idx] # Invert the sign directly
|
||||
iterations += 1
|
||||
|
||||
# Store the final flow rates in the edges for this hour
|
||||
for idx, (edge, flow_rate) in enumerate(zip(self._network_graph.edges(), G)):
|
||||
u, v = edge
|
||||
if not (self._network_graph.nodes[u].get('type') == 'building' or self._network_graph.nodes[v].get(
|
||||
'type') == 'building'):
|
||||
flow_rate *= self.simultaneity_factor # Apply simultaneity factor for non-building pipes
|
||||
data = self._network_graph.edges[u, v]
|
||||
data['mass_flow_rate']["hour"].append(flow_rate) # Append the calculated flow rate
|
||||
|
||||
# Calculate the peak flow rate for each edge
|
||||
for u, v, data in self._network_graph.edges(data=True):
|
||||
data['mass_flow_rate']['peak'] = max(data['mass_flow_rate']['hour'])
|
||||
|
||||
def calculate_diameters_and_costs(self, pipe_data):
|
||||
"""
|
||||
Calculate the diameter and costs of the pipes based on the maximum flow rate in each edge.
|
||||
|
||||
:param pipe_data: A list of dictionaries containing pipe specifications, including inner diameters
|
||||
and costs per meter for different nominal diameters (DN).
|
||||
"""
|
||||
for u, v, data in self._network_graph.edges(data=True):
|
||||
flow_rate = data.get('mass_flow_rate', {}).get('peak')
|
||||
if flow_rate is not None:
|
||||
try:
|
||||
# Calculate the density of the fluid
|
||||
density = CP.PropsSI('D', 'T', (self._supply_temperature + self._return_temperature) / 2, 'P', 101325,
|
||||
self.fluid)
|
||||
velocity = 0.9 # Desired fluid velocity in m/s
|
||||
# Calculate the diameter of the pipe required for the given flow rate
|
||||
diameter = math.sqrt((4 * abs(flow_rate)) / (density * velocity * math.pi)) * 1000 # Convert to mm
|
||||
self._network_graph.edges[u, v]['diameter'] = diameter
|
||||
|
||||
# Match to the closest nominal diameter from the pipe data
|
||||
closest_pipe = self.match_nominal_diameter(diameter, pipe_data)
|
||||
self._network_graph.edges[u, v]['nominal_diameter'] = closest_pipe['DN']
|
||||
self._network_graph.edges[u, v]['cost_per_meter'] = closest_pipe['cost_per_meter']
|
||||
except Exception as e:
|
||||
logging.error(f"Error calculating diameter or matching nominal diameter for edge ({u}, {v}): {e}")
|
||||
|
||||
def match_nominal_diameter(self, diameter, pipe_data):
|
||||
"""
|
||||
Match the calculated diameter to the closest nominal diameter.
|
||||
|
||||
:param diameter: The calculated diameter of the pipe (in mm).
|
||||
:param pipe_data: A list of dictionaries containing pipe specifications, including inner diameters
|
||||
and costs per meter for different nominal diameters (DN).
|
||||
:return: The dictionary representing the pipe with the closest nominal diameter.
|
||||
"""
|
||||
closest_pipe = min(pipe_data, key=lambda x: abs(x['inner_diameter'] - diameter))
|
||||
return closest_pipe
|
||||
|
||||
def analyze_costs(self):
|
||||
"""
|
||||
Analyze the costs based on the nominal diameters of the pipes.
|
||||
|
||||
This method calculates the total cost of piping for each nominal diameter group
|
||||
and returns a summary of the grouped pipes and the total cost.
|
||||
|
||||
:return: A tuple containing the grouped pipe data and the total cost of piping.
|
||||
"""
|
||||
pipe_groups = {}
|
||||
total_cost = 0 # Initialize total cost
|
||||
|
||||
for u, v, data in self._network_graph.edges(data=True):
|
||||
dn = data.get('nominal_diameter')
|
||||
if dn is not None:
|
||||
pipe_length = self._network_graph.edges[u, v].get('length', 1) * 2 # Multiply by 2 for supply and return
|
||||
cost_per_meter = data.get('cost_per_meter', 0)
|
||||
|
||||
if dn not in pipe_groups:
|
||||
pipe_groups[dn] = {
|
||||
'DN': dn,
|
||||
'total_length': 0,
|
||||
'cost_per_meter': cost_per_meter
|
||||
}
|
||||
pipe_groups[dn]['total_length'] += pipe_length
|
||||
group_cost = pipe_length * cost_per_meter
|
||||
total_cost += group_cost # Add to total cost
|
||||
|
||||
# Calculate total cost for each group
|
||||
for group in pipe_groups.values():
|
||||
group['total_cost'] = group['total_length'] * group['cost_per_meter']
|
||||
|
||||
return pipe_groups, total_cost # Return both the grouped data and total cost
|
||||
|
||||
def save_pipe_groups_to_csv(self, filename):
|
||||
"""
|
||||
Save the pipe groups and their total lengths to a CSV file.
|
||||
|
||||
:param filename: The name of the CSV file to save the data to.
|
||||
"""
|
||||
pipe_groups, _ = self.analyze_costs()
|
||||
|
||||
with open(filename, mode='w', newline='') as file:
|
||||
writer = csv.writer(file)
|
||||
# Write the header
|
||||
writer.writerow(["Nominal Diameter (DN)", "Total Length (m)", "Cost per Meter", "Total Cost"])
|
||||
|
||||
# Write the data for each pipe group
|
||||
for group in pipe_groups.values():
|
||||
writer.writerow([
|
||||
group['DN'],
|
||||
group['total_length'],
|
||||
group['cost_per_meter'],
|
||||
group['total_cost']
|
||||
])
|
@ -0,0 +1,372 @@
|
||||
import json
|
||||
import math
|
||||
import logging
|
||||
import matplotlib.pyplot as plt
|
||||
import networkx as nx
|
||||
from shapely.geometry import Polygon, Point, LineString
|
||||
from typing import List, Tuple
|
||||
from rtree import index
|
||||
|
||||
# Configure logging
|
||||
logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s")
|
||||
logging.getLogger("numexpr").setLevel(logging.ERROR)
|
||||
|
||||
def haversine(lon1, lat1, lon2, lat2):
|
||||
"""
|
||||
Calculate the great-circle distance between two points
|
||||
on the Earth specified by their longitude and latitude.
|
||||
"""
|
||||
R = 6371000 # Radius of the Earth in meters
|
||||
phi1 = math.radians(lat1)
|
||||
phi2 = math.radians(lat2)
|
||||
delta_phi = math.radians(lat2 - lat1)
|
||||
delta_lambda = math.radians(lon2 - lon1)
|
||||
|
||||
a = math.sin(delta_phi / 2.0) ** 2 + \
|
||||
math.cos(phi1) * math.cos(phi2) * \
|
||||
math.sin(delta_lambda / 2.0) ** 2
|
||||
|
||||
c = 2 * math.atan2(math.sqrt(a), math.sqrt(1 - a))
|
||||
return R * c # Output distance in meters
|
||||
|
||||
class DistrictHeatingNetworkCreator:
|
||||
def __init__(self, buildings_file: str, roads_file: str, central_plant_locations: List[Tuple[float, float]]):
|
||||
"""
|
||||
Initialize the class with paths to the buildings and roads data files, and central plant locations.
|
||||
|
||||
:param buildings_file: Path to the GeoJSON file containing building data.
|
||||
:param roads_file: Path to the GeoJSON file containing roads data.
|
||||
:param central_plant_locations: List of tuples containing the coordinates of central plant locations.
|
||||
"""
|
||||
if len(central_plant_locations) < 1:
|
||||
raise ValueError("The list of central plant locations must have at least one member.")
|
||||
|
||||
self.buildings_file = buildings_file
|
||||
self.roads_file = roads_file
|
||||
self.central_plant_locations = central_plant_locations
|
||||
|
||||
def run(self) -> nx.Graph:
|
||||
"""
|
||||
Main method to execute the district heating network creation process.
|
||||
:return: NetworkX graph with nodes and edges representing the network.
|
||||
"""
|
||||
try:
|
||||
self._load_and_process_data()
|
||||
self._find_nearest_roads()
|
||||
self._find_nearest_points()
|
||||
self._break_down_roads()
|
||||
self._create_graph()
|
||||
self._create_mst()
|
||||
self._iteratively_remove_edges()
|
||||
self._add_centroids_to_mst()
|
||||
self._convert_edge_weights_to_meters()
|
||||
self._create_final_network_graph()
|
||||
return self.network_graph
|
||||
except Exception as e:
|
||||
logging.error(f"Error during network creation: {e}")
|
||||
raise
|
||||
|
||||
def _load_and_process_data(self):
|
||||
"""
|
||||
Load and process the building and road data.
|
||||
"""
|
||||
try:
|
||||
# Load building data
|
||||
with open(self.buildings_file, 'r') as file:
|
||||
city = json.load(file)
|
||||
|
||||
self.centroids = []
|
||||
self.building_names = []
|
||||
self.building_positions = []
|
||||
buildings = city['features']
|
||||
for building in buildings:
|
||||
coordinates = building['geometry']['coordinates'][0]
|
||||
building_polygon = Polygon(coordinates)
|
||||
centroid = building_polygon.centroid
|
||||
self.centroids.append(centroid)
|
||||
self.building_names.append(str(building['id']))
|
||||
self.building_positions.append((centroid.x, centroid.y))
|
||||
|
||||
# Add central plant locations as centroids
|
||||
for i, loc in enumerate(self.central_plant_locations, start=1):
|
||||
centroid = Point(loc)
|
||||
self.centroids.append(centroid)
|
||||
self.building_names.append(f'central_plant_{i}')
|
||||
self.building_positions.append((centroid.x, centroid.y))
|
||||
|
||||
# Load road data
|
||||
with open(self.roads_file, 'r') as file:
|
||||
roads = json.load(file)
|
||||
|
||||
line_features = [feature for feature in roads['features'] if feature['geometry']['type'] == 'LineString']
|
||||
|
||||
self.lines = [LineString(feature['geometry']['coordinates']) for feature in line_features]
|
||||
self.cleaned_lines = [LineString([line.coords[0], line.coords[-1]]) for line in self.lines]
|
||||
except Exception as e:
|
||||
logging.error(f"Error loading and processing data: {e}")
|
||||
raise
|
||||
|
||||
def _find_nearest_roads(self):
|
||||
"""
|
||||
Find the nearest road for each building centroid.
|
||||
"""
|
||||
try:
|
||||
self.closest_roads = []
|
||||
unique_roads_set = set()
|
||||
|
||||
# Create spatial index for roads
|
||||
idx = index.Index()
|
||||
for pos, line in enumerate(self.cleaned_lines):
|
||||
idx.insert(pos, line.bounds)
|
||||
|
||||
for centroid in self.centroids:
|
||||
min_distance = float('inf')
|
||||
closest_road = None
|
||||
for pos in idx.nearest(centroid.bounds, 10):
|
||||
road = self.cleaned_lines[pos]
|
||||
distance = road.distance(centroid)
|
||||
if distance < min_distance:
|
||||
min_distance = distance
|
||||
closest_road = road
|
||||
|
||||
if closest_road and closest_road.wkt not in unique_roads_set:
|
||||
unique_roads_set.add(closest_road.wkt)
|
||||
self.closest_roads.append(closest_road)
|
||||
except Exception as e:
|
||||
logging.error(f"Error finding nearest roads: {e}")
|
||||
raise
|
||||
|
||||
def _find_nearest_points(self):
|
||||
"""
|
||||
Find the nearest point on each closest road for each centroid.
|
||||
"""
|
||||
|
||||
def find_nearest_point_on_line(point: Point, line: LineString) -> Point:
|
||||
return line.interpolate(line.project(point))
|
||||
|
||||
try:
|
||||
self.nearest_points = []
|
||||
for centroid in self.centroids:
|
||||
min_distance = float('inf')
|
||||
closest_road = None
|
||||
for road in self.closest_roads:
|
||||
distance = centroid.distance(road)
|
||||
if distance < min_distance:
|
||||
min_distance = distance
|
||||
closest_road = road
|
||||
|
||||
if closest_road:
|
||||
nearest_point = find_nearest_point_on_line(centroid, closest_road)
|
||||
self.nearest_points.append(nearest_point)
|
||||
except Exception as e:
|
||||
logging.error(f"Error finding nearest points: {e}")
|
||||
raise
|
||||
|
||||
def _break_down_roads(self):
|
||||
"""
|
||||
Break down roads into segments connecting nearest points.
|
||||
"""
|
||||
|
||||
def break_down_roads(closest_roads: List[LineString], nearest_points_list: List[Point]) -> List[LineString]:
|
||||
new_segments = []
|
||||
for road in closest_roads:
|
||||
coords = list(road.coords)
|
||||
points_on_road = [point for point in nearest_points_list if road.distance(point) < 0.000000001]
|
||||
sorted_points = sorted(points_on_road, key=lambda point: road.project(point))
|
||||
sorted_points.insert(0, Point(coords[0]))
|
||||
sorted_points.append(Point(coords[-1]))
|
||||
for i in range(len(sorted_points) - 1):
|
||||
segment = LineString([sorted_points[i], sorted_points[i + 1]])
|
||||
new_segments.append(segment)
|
||||
return new_segments
|
||||
|
||||
try:
|
||||
self.new_segments = break_down_roads(self.closest_roads, self.nearest_points)
|
||||
self.cleaned_lines = [line for line in self.cleaned_lines if line not in self.closest_roads]
|
||||
self.cleaned_lines.extend(self.new_segments)
|
||||
except Exception as e:
|
||||
logging.error(f"Error breaking down roads: {e}")
|
||||
raise
|
||||
|
||||
def _create_graph(self):
|
||||
"""
|
||||
Create a NetworkX graph from the cleaned lines.
|
||||
"""
|
||||
try:
|
||||
self.G = nx.Graph()
|
||||
for line in self.cleaned_lines:
|
||||
coords = list(line.coords)
|
||||
for i in range(len(coords) - 1):
|
||||
u = coords[i]
|
||||
v = coords[i + 1]
|
||||
self.G.add_edge(u, v, weight=Point(coords[i]).distance(Point(coords[i + 1])))
|
||||
except Exception as e:
|
||||
logging.error(f"Error creating graph: {e}")
|
||||
raise
|
||||
|
||||
def _create_mst(self):
|
||||
"""
|
||||
Create a Minimum Spanning Tree (MST) from the graph.
|
||||
"""
|
||||
|
||||
def find_paths_between_nearest_points(g: nx.Graph, nearest_points: List[Point]) -> List[Tuple]:
|
||||
edges = []
|
||||
for i, start_point in enumerate(nearest_points):
|
||||
start = (start_point.x, start_point.y)
|
||||
for end_point in nearest_points[i + 1:]:
|
||||
end = (end_point.x, end_point.y)
|
||||
if nx.has_path(g, start, end):
|
||||
path = nx.shortest_path(g, source=start, target=end, weight='weight')
|
||||
path_edges = list(zip(path[:-1], path[1:]))
|
||||
edges.extend((u, v, g[u][v]['weight']) for u, v in path_edges)
|
||||
return edges
|
||||
|
||||
try:
|
||||
edges = find_paths_between_nearest_points(self.G, self.nearest_points)
|
||||
h = nx.Graph()
|
||||
h.add_weighted_edges_from(edges)
|
||||
mst = nx.minimum_spanning_tree(h, weight='weight')
|
||||
final_edges = []
|
||||
for u, v in mst.edges():
|
||||
if nx.has_path(self.G, u, v):
|
||||
path = nx.shortest_path(self.G, source=u, target=v, weight='weight')
|
||||
path_edges = list(zip(path[:-1], path[1:]))
|
||||
final_edges.extend((x, y, self.G[x][y]['weight']) for x, y in path_edges)
|
||||
self.final_mst = nx.Graph()
|
||||
self.final_mst.add_weighted_edges_from(final_edges)
|
||||
except Exception as e:
|
||||
logging.error(f"Error creating MST: {e}")
|
||||
raise
|
||||
|
||||
def _iteratively_remove_edges(self):
|
||||
"""
|
||||
Iteratively remove edges that do not have any nearest points and have one end with only one connection.
|
||||
Also remove nodes that don't have any connections and street nodes with only one connection.
|
||||
"""
|
||||
nearest_points_tuples = [(point.x, point.y) for point in self.nearest_points]
|
||||
|
||||
def find_edges_to_remove(graph: nx.Graph) -> List[Tuple]:
|
||||
edges_to_remove = []
|
||||
for u, v, d in graph.edges(data=True):
|
||||
if u not in nearest_points_tuples and v not in nearest_points_tuples:
|
||||
if graph.degree(u) == 1 or graph.degree(v) == 1:
|
||||
edges_to_remove.append((u, v, d))
|
||||
return edges_to_remove
|
||||
|
||||
def find_nodes_to_remove(graph: nx.Graph) -> List[Tuple]:
|
||||
nodes_to_remove = []
|
||||
for node in graph.nodes():
|
||||
if graph.degree(node) == 0:
|
||||
nodes_to_remove.append(node)
|
||||
return nodes_to_remove
|
||||
|
||||
try:
|
||||
edges_to_remove = find_edges_to_remove(self.final_mst)
|
||||
self.final_mst_steps = [list(self.final_mst.edges(data=True))]
|
||||
|
||||
while edges_to_remove or find_nodes_to_remove(self.final_mst):
|
||||
self.final_mst.remove_edges_from(edges_to_remove)
|
||||
nodes_to_remove = find_nodes_to_remove(self.final_mst)
|
||||
self.final_mst.remove_nodes_from(nodes_to_remove)
|
||||
edges_to_remove = find_edges_to_remove(self.final_mst)
|
||||
self.final_mst_steps.append(list(self.final_mst.edges(data=True)))
|
||||
|
||||
def find_single_connection_street_nodes(graph: nx.Graph) -> List[Tuple]:
|
||||
single_connection_street_nodes = []
|
||||
for node in graph.nodes():
|
||||
if node not in nearest_points_tuples and graph.degree(node) == 1:
|
||||
single_connection_street_nodes.append(node)
|
||||
return single_connection_street_nodes
|
||||
|
||||
single_connection_street_nodes = find_single_connection_street_nodes(self.final_mst)
|
||||
|
||||
while single_connection_street_nodes:
|
||||
for node in single_connection_street_nodes:
|
||||
neighbors = list(self.final_mst.neighbors(node))
|
||||
self.final_mst.remove_node(node)
|
||||
for neighbor in neighbors:
|
||||
if self.final_mst.degree(neighbor) == 0:
|
||||
self.final_mst.remove_node(neighbor)
|
||||
single_connection_street_nodes = find_single_connection_street_nodes(self.final_mst)
|
||||
self.final_mst_steps.append(list(self.final_mst.edges(data=True)))
|
||||
except Exception as e:
|
||||
logging.error(f"Error iteratively removing edges: {e}")
|
||||
raise
|
||||
|
||||
def _add_centroids_to_mst(self):
|
||||
"""
|
||||
Add centroids to the final MST graph and connect them to their associated node on the graph.
|
||||
"""
|
||||
try:
|
||||
for i, centroid in enumerate(self.centroids):
|
||||
building_name = self.building_names[i]
|
||||
pos = (centroid.x, centroid.y)
|
||||
node_type = 'building' if 'central_plant' not in building_name else 'generation'
|
||||
self.final_mst.add_node(pos, type=node_type, name=building_name, pos=pos)
|
||||
|
||||
nearest_point = None
|
||||
min_distance = float('inf')
|
||||
for node in self.final_mst.nodes():
|
||||
if self.final_mst.nodes[node].get('type') != 'building' and self.final_mst.nodes[node].get('type') != 'generation':
|
||||
distance = centroid.distance(Point(node))
|
||||
if distance < min_distance:
|
||||
min_distance = distance
|
||||
nearest_point = node
|
||||
|
||||
if nearest_point:
|
||||
self.final_mst.add_edge(pos, nearest_point, weight=min_distance)
|
||||
except Exception as e:
|
||||
logging.error(f"Error adding centroids to MST: {e}")
|
||||
raise
|
||||
|
||||
def _convert_edge_weights_to_meters(self):
|
||||
"""
|
||||
Convert all edge weights in the final MST graph to meters using the Haversine formula.
|
||||
"""
|
||||
try:
|
||||
for u, v, data in self.final_mst.edges(data=True):
|
||||
distance = haversine(u[0], u[1], v[0], v[1])
|
||||
self.final_mst[u][v]['weight'] = distance
|
||||
except Exception as e:
|
||||
logging.error(f"Error converting edge weights to meters: {e}")
|
||||
raise
|
||||
|
||||
def _create_final_network_graph(self):
|
||||
"""
|
||||
Create the final network graph with the required attributes from the final MST.
|
||||
"""
|
||||
self.network_graph = nx.Graph()
|
||||
node_id = 1
|
||||
node_mapping = {}
|
||||
for node in self.final_mst.nodes:
|
||||
pos = node
|
||||
if 'type' in self.final_mst.nodes[node]:
|
||||
if self.final_mst.nodes[node]['type'] == 'building':
|
||||
name = self.final_mst.nodes[node]['name']
|
||||
node_type = 'building'
|
||||
elif self.final_mst.nodes[node]['type'] == 'generation':
|
||||
name = self.final_mst.nodes[node]['name']
|
||||
node_type = 'generation'
|
||||
else:
|
||||
name = f'junction_{node_id}'
|
||||
node_type = 'junction'
|
||||
self.network_graph.add_node(node_id, name=name, type=node_type, pos=pos)
|
||||
node_mapping[node] = node_id
|
||||
node_id += 1
|
||||
for u, v, data in self.final_mst.edges(data=True):
|
||||
u_new = node_mapping[u]
|
||||
v_new = node_mapping[v]
|
||||
length = data['weight']
|
||||
self.network_graph.add_edge(u_new, v_new, length=length)
|
||||
|
||||
def plot_network_graph(self):
|
||||
"""
|
||||
Plot the network graph using matplotlib and networkx.
|
||||
"""
|
||||
plt.figure(figsize=(15, 10))
|
||||
pos = {node: data['pos'] for node, data in self.network_graph.nodes(data=True)}
|
||||
nx.draw_networkx_nodes(self.network_graph, pos, node_color='blue', node_size=50)
|
||||
nx.draw_networkx_edges(self.network_graph, pos, edge_color='gray')
|
||||
plt.title('District Heating Network Graph')
|
||||
plt.axis('off')
|
||||
plt.show()
|
56
scripts/district_heating_network/road_processor.py
Normal file
56
scripts/district_heating_network/road_processor.py
Normal file
@ -0,0 +1,56 @@
|
||||
from pathlib import Path
|
||||
from shapely.geometry import Polygon, Point, shape
|
||||
import json
|
||||
|
||||
|
||||
def road_processor(x, y, diff):
|
||||
"""
|
||||
Processes a .JSON file to find roads that have at least one node within a specified polygon.
|
||||
|
||||
Parameters:
|
||||
x (float): The x-coordinate of the center of the selection box.
|
||||
y (float): The y-coordinate of the center of the selection box.
|
||||
diff (float): The half-width of the selection box.
|
||||
|
||||
Returns:
|
||||
str: The file path of the output GeoJSON file containing the selected roads.
|
||||
"""
|
||||
diff += 2 * diff
|
||||
# Define the selection polygon
|
||||
selection_box = Polygon([
|
||||
[x + diff, y - diff],
|
||||
[x - diff, y - diff],
|
||||
[x - diff, y + diff],
|
||||
[x + diff, y + diff]
|
||||
])
|
||||
|
||||
# Define input and output file paths
|
||||
geojson_file = Path("./input_files/roads.json").resolve()
|
||||
output_file = Path('./input_files/output_roads.geojson').resolve()
|
||||
|
||||
# Initialize a list to store the roads in the region
|
||||
roads_in_region = []
|
||||
|
||||
# Read the GeoJSON file
|
||||
with open(geojson_file, 'r') as file:
|
||||
roads = json.load(file)
|
||||
line_features = [feature for feature in roads['features'] if feature['geometry']['type'] == 'LineString']
|
||||
|
||||
# Check each road feature
|
||||
for feature in line_features:
|
||||
road_shape = shape(feature['geometry'])
|
||||
# Check if any node of the road is inside the selection box
|
||||
if any(selection_box.contains(Point(coord)) for coord in road_shape.coords):
|
||||
roads_in_region.append(feature)
|
||||
|
||||
# Create a new GeoJSON structure with the selected roads
|
||||
output_geojson = {
|
||||
"type": "FeatureCollection",
|
||||
"features": roads_in_region
|
||||
}
|
||||
|
||||
# Write the selected roads to the output file
|
||||
with open(output_file, 'w') as outfile:
|
||||
json.dump(output_geojson, outfile)
|
||||
|
||||
return output_file
|
@ -1,338 +0,0 @@
|
||||
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()
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user