Compare commits
56 Commits
saeed_raye
...
main
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 |
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="C:\Users\sr283\miniconda3\envs\Hub" jdkType="Python SDK" />
|
||||
<orderEntry type="jdk" jdkName="Python 3.9 (hub)" jdkType="Python SDK" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
@ -1,7 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="Black">
|
||||
<option name="sdkName" value="C:\Users\sr283\miniconda3\envs\Hub" />
|
||||
<option name="sdkUUID" value="97386509-ec9b-4dd7-929b-7585219c0447" />
|
||||
</component>
|
||||
<component name="ProjectRootManager" version="2" project-jdk-name="C:\Users\sr283\miniconda3\envs\Hub" project-jdk-type="Python SDK" />
|
||||
<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:
|
37
building_modelling/geojson_creator.py
Normal file
37
building_modelling/geojson_creator.py
Normal file
@ -0,0 +1,37 @@
|
||||
import json
|
||||
from shapely import Polygon
|
||||
from shapely import Point
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
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(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:
|
||||
city = json.load(file)
|
||||
buildings = city['features']
|
||||
|
||||
for building in buildings:
|
||||
coordinates = building['geometry']['coordinates'][0]
|
||||
building_polygon = Polygon(coordinates)
|
||||
centroid = Point(building_polygon.centroid)
|
||||
|
||||
if centroid.within(selection_box):
|
||||
buildings_in_region.append(building)
|
||||
|
||||
output_region = {"type": "FeatureCollection",
|
||||
"features": buildings_in_region}
|
||||
|
||||
with open(output_file, 'w') as file:
|
||||
file.write(json.dumps(output_region, indent=2))
|
||||
|
||||
return output_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)])
|
||||
|
@ -1,64 +1,114 @@
|
||||
from scripts.geojson_creator import process_geojson
|
||||
from pathlib import Path
|
||||
import subprocess
|
||||
from scripts.ep_run_enrich import energy_plus_workflow
|
||||
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 scripts.energy_system_analysis_report import EnergySystemAnalysisReport
|
||||
from scripts import random_assignation
|
||||
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 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 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
|
||||
geojson_file = process_geojson(x=-73.5681295982132, y=45.49218262677643, diff=0.0001)
|
||||
file_path = (Path(__file__).parent / 'input_files' / 'output_buildings.geojson')
|
||||
# Specify the output path for the PDF file
|
||||
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()
|
||||
# Create city object from GeoJSON file
|
||||
city = GeometryFactory('geojson',
|
||||
path=file_path,
|
||||
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
|
||||
# Enrich city data
|
||||
ConstructionFactory('nrcan', city).enrich()
|
||||
|
||||
UsageFactory('nrcan', city).enrich()
|
||||
WeatherFactory('epw', city).enrich()
|
||||
ExportsFactory('sra', city, output_path).export()
|
||||
sra_path = (output_path / f'{city.name}_sra.xml').resolve()
|
||||
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, output_path).enrich()
|
||||
energy_plus_workflow(city)
|
||||
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()
|
||||
SystemSizing(city.buildings).montreal_custom()
|
||||
current_system = new_system_results(city.buildings)
|
||||
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:
|
||||
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)
|
||||
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:
|
||||
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')
|
||||
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,11 +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):
|
||||
@ -156,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):
|
||||
"""
|
||||
@ -179,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
|
||||
@ -187,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
|
||||
@ -389,7 +391,7 @@ class Surface:
|
||||
@property
|
||||
def global_irradiance_tilted(self) -> dict:
|
||||
"""
|
||||
Get global irradiance on a tilted surface in J/m2
|
||||
Get global irradiance on a tilted surface in W/m2
|
||||
:return: dict
|
||||
"""
|
||||
return self._global_irradiance_tilted
|
||||
@ -397,7 +399,23 @@ class Surface:
|
||||
@global_irradiance_tilted.setter
|
||||
def global_irradiance_tilted(self, value):
|
||||
"""
|
||||
Set global irradiance on a tilted surface in J/m2
|
||||
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):
|
||||
"""
|
||||
|
@ -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):
|
||||
|
@ -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):
|
||||
|
@ -1,106 +1,16 @@
|
||||
"""
|
||||
export a city into STL format. (Each building is a solid, suitable for RC models such as CityBEM)
|
||||
export a city into Stl format
|
||||
SPDX - License - Identifier: LGPL - 3.0 - or -later
|
||||
Copyright © 2024 Concordia CERC group
|
||||
Project Coder Saeed Rayegan sr283100@gmail.com
|
||||
Copyright © 2022 Concordia CERC group
|
||||
Project Coder Guille Gutierrez guillermo.gutierrezmorote@concordia.ca
|
||||
"""
|
||||
from pathlib import Path
|
||||
import numpy as np
|
||||
from scipy.spatial import Delaunay
|
||||
|
||||
class Stl:
|
||||
from hub.exports.formats.triangular import Triangular
|
||||
|
||||
|
||||
class Stl(Triangular):
|
||||
"""
|
||||
Export to stl format
|
||||
Export to STL
|
||||
"""
|
||||
def __init__(self, city, path):
|
||||
self._city = city
|
||||
self._path = path
|
||||
self._export()
|
||||
|
||||
def _triangulate_stl(self, points_2d, height):
|
||||
#This function requires a set of 2D points for triangulation
|
||||
# Assuming vertices is a NumPy array
|
||||
tri = Delaunay(points_2d)
|
||||
triangles2D = points_2d[tri.simplices]
|
||||
triangles3D = []
|
||||
|
||||
# Iterate through each triangle in triangles2D
|
||||
for triangle in triangles2D:
|
||||
# Extract the existing x and y coordinates
|
||||
x1, y1 = triangle[0]
|
||||
x2, y2 = triangle[1]
|
||||
x3, y3 = triangle[2]
|
||||
|
||||
# Create a 3D point with the specified height
|
||||
point3D=[[x1, height, y1],[x2, height, y2],[x3, height, y3]]
|
||||
|
||||
# Append the 3D points to the triangle list
|
||||
triangles3D.append(point3D)
|
||||
|
||||
return triangles3D
|
||||
|
||||
def _ground(self, coordinate):
|
||||
x = coordinate[0] - self._city.lower_corner[0]
|
||||
y = coordinate[1] - self._city.lower_corner[1]
|
||||
z = coordinate[2] - self._city.lower_corner[2]
|
||||
return x, y, z
|
||||
|
||||
def _to_vertex_stl(self, coordinate):
|
||||
x, y, z = self._ground(coordinate)
|
||||
return [x, z, -y] # Return as a list # to match opengl expectations (check it later)
|
||||
|
||||
def _to_normal_vertex_stl(self, coordinates):
|
||||
ground_vertex = []
|
||||
for coordinate in coordinates:
|
||||
x, y, z = self._ground(coordinate)
|
||||
ground_vertex.append(np.array([x, y, z]))
|
||||
# recalculate the normal to get grounded values
|
||||
edge_1 = ground_vertex[1] - ground_vertex[0]
|
||||
edge_2 = ground_vertex[2] - ground_vertex[0]
|
||||
normal = np.cross(edge_1, edge_2)
|
||||
normal = normal / np.linalg.norm(normal)
|
||||
# Convert normal to list for easier handling in the write operation
|
||||
return normal.tolist()
|
||||
|
||||
|
||||
def _export(self):
|
||||
if self._city.name is None:
|
||||
self._city.name = 'unknown_city'
|
||||
stl_name = f'{self._city.name}.stl'
|
||||
stl_file_path = (Path(self._path).resolve() / stl_name).resolve()
|
||||
with open(stl_file_path, 'w', encoding='utf-8') as stl:
|
||||
for building in self._city.buildings:
|
||||
stl.write(f"solid building{building.name}\n")
|
||||
for surface in building.surfaces:
|
||||
vertices = []
|
||||
normal = self._to_normal_vertex_stl(surface.perimeter_polygon.coordinates) #the normal vector should be calculated for every surface
|
||||
for coordinate in surface.perimeter_polygon.coordinates:
|
||||
vertex = self._to_vertex_stl(coordinate)
|
||||
if vertex not in vertices:
|
||||
vertices.append(vertex)
|
||||
vertices = np.array(vertices)
|
||||
#After collecting the unique vertices of a surface, there is a need to identify if it is located on the roof, floor, or side walls
|
||||
roofStatus=1 #multiplication of the height of all vertices in a surface
|
||||
heightSum=0 #summation of the height of all vertices in a surface
|
||||
for vertex in vertices:
|
||||
roofStatus *= vertex[1]
|
||||
heightSum += vertex[1]
|
||||
if roofStatus>0:
|
||||
#this surface is the roof (first and third elements of vertices should be passed to the triangulation function)
|
||||
triangles=self._triangulate_stl(vertices[:, [0, 2]], vertices[0][1])
|
||||
elif roofStatus==0 and heightSum==0:
|
||||
# this surface is the floor
|
||||
triangles=self._triangulate_stl(vertices[:, [0, 2]], vertices[0][1])
|
||||
elif roofStatus==0 and heightSum>0:
|
||||
# this surface is a vertical wall (no need for triangulation as it can be done manually)
|
||||
triangles = [[vertices[0],vertices[1],vertices[2]], [vertices[2], vertices[3], vertices[0]]]
|
||||
|
||||
# write the facets (triangles) in the stl file
|
||||
for triangle in triangles:
|
||||
stl.write(f"facet normal {normal[0]} {normal[2]} {normal[1]}\n") #following the idea that y axis is the height
|
||||
stl.write(" outer loop\n")
|
||||
for vertex in triangle:
|
||||
stl.write(f" vertex {vertex[0]} {vertex[1]} {vertex[2]}\n")
|
||||
stl.write(" endloop\n")
|
||||
stl.write("endfacet\n")
|
||||
stl.write(f"endsolid building{building.name}\n")
|
||||
super().__init__(city, path, 'stl', write_mode='wb')
|
||||
|
@ -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)
|
||||
@ -87,12 +88,12 @@ class MontrealFutureEnergySystemParameters:
|
||||
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
|
||||
@ -103,15 +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] = (
|
||||
|
@ -34,7 +34,7 @@ class SimplifiedRadiosityAlgorithm:
|
||||
for key in self._results:
|
||||
_irradiance = {}
|
||||
header_name = key.split(':')
|
||||
result = [x * cte.WATTS_HOUR_TO_JULES for x in self._results[key]]
|
||||
result = [x for x in self._results[key]]
|
||||
city_object_name = header_name[1]
|
||||
building = self._city.city_object(city_object_name)
|
||||
surface_id = header_name[2]
|
||||
|
@ -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):
|
||||
|
@ -126,10 +126,10 @@ class EpwWeatherParameters:
|
||||
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]
|
35
main.py
35
main.py
@ -1,35 +0,0 @@
|
||||
from scripts.geojson_creator import process_geojson
|
||||
from pathlib import Path
|
||||
from scripts.ep_run_enrich import energy_plus_workflow
|
||||
from scripts.CityBEM_run import CityBEM_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
|
||||
import hub.helpers.constants as cte
|
||||
from hub.exports.exports_factory import ExportsFactory
|
||||
|
||||
# Specify the GeoJSON file path
|
||||
# geojson_file = process_geojson(a=-73.5681295982132, b=45.49218262677643, c=-73.5681295982132, d=45.51218262677643,
|
||||
# e=-73.5881295982132, f=45.49218262677643,g=-73.5881295982132, h=45.51218262677643)
|
||||
geojson_file = process_geojson(x=-73.5681295982132, y=45.49218262677643, diff=0.0001)
|
||||
|
||||
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()
|
||||
CityBEM_workflow(city) #run the city using a fast City-Building Energy Model, CityBEM
|
||||
energy_plus_workflow(city)
|
||||
print ("test done")
|
@ -1,73 +0,0 @@
|
||||
import pandas as pd
|
||||
from scripts.geojson_creator import process_geojson
|
||||
from pathlib import Path
|
||||
import subprocess
|
||||
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.solar_angles import CitySolarAngles
|
||||
from scripts.ep_run_enrich import energy_plus_workflow
|
||||
import hub.helpers.constants as cte
|
||||
from hub.exports.exports_factory import ExportsFactory
|
||||
from scripts.pv_sizing_and_simulation import PVSizingSimulation
|
||||
# Specify the GeoJSON file path
|
||||
geojson_file = process_geojson(x=-73.5681295982132, y=45.49218262677643, diff=0.0005)
|
||||
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()
|
||||
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()
|
||||
energy_plus_workflow(city)
|
||||
solar_angles = CitySolarAngles(city.name,
|
||||
city.latitude,
|
||||
city.longitude,
|
||||
tilt_angle=45,
|
||||
surface_azimuth_angle=180).calculate
|
||||
df = pd.DataFrame()
|
||||
df.index = ['yearly lighting (kWh)', 'yearly appliance (kWh)', 'yearly heating (kWh)', 'yearly cooling (kWh)',
|
||||
'yearly dhw (kWh)', 'roof area (m2)', 'used area for pv (m2)', 'number of panels', 'pv production (kWh)']
|
||||
for building in city.buildings:
|
||||
ghi = [x / cte.WATTS_HOUR_TO_JULES for x in building.roofs[0].global_irradiance[cte.HOUR]]
|
||||
pv_sizing_simulation = PVSizingSimulation(building,
|
||||
solar_angles,
|
||||
tilt_angle=45,
|
||||
module_height=1,
|
||||
module_width=2,
|
||||
ghi=ghi)
|
||||
pv_sizing_simulation.pv_output()
|
||||
yearly_lighting = building.lighting_electrical_demand[cte.YEAR][0] / 1000
|
||||
yearly_appliance = building.appliances_electrical_demand[cte.YEAR][0] / 1000
|
||||
yearly_heating = building.heating_demand[cte.YEAR][0] / (3.6e6 * 3)
|
||||
yearly_cooling = building.cooling_demand[cte.YEAR][0] / (3.6e6 * 4.5)
|
||||
yearly_dhw = building.domestic_hot_water_heat_demand[cte.YEAR][0] / 1000
|
||||
roof_area = building.roofs[0].perimeter_area
|
||||
used_roof = pv_sizing_simulation.available_space()
|
||||
number_of_pv_panels = pv_sizing_simulation.total_number_of_panels
|
||||
yearly_pv = building.onsite_electrical_production[cte.YEAR][0] / 1000
|
||||
df[f'{building.name}'] = [yearly_lighting, yearly_appliance, yearly_heating, yearly_cooling, yearly_dhw, roof_area,
|
||||
used_roof, number_of_pv_panels, yearly_pv]
|
||||
|
||||
df.to_csv(output_path / 'pv.csv')
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -1,391 +0,0 @@
|
||||
import pandas as pd
|
||||
import sys
|
||||
import csv
|
||||
import json
|
||||
from shapely.geometry import Polygon
|
||||
from pathlib import Path
|
||||
import subprocess
|
||||
from hub.exports.exports_factory import ExportsFactory
|
||||
from hub.imports.weather.epw_weather_parameters import EpwWeatherParameters
|
||||
|
||||
sys.path.append('./')
|
||||
|
||||
|
||||
def CityBEM_workflow(city):
|
||||
"""
|
||||
Main function to run the CityBEM under the CityLayer's hub.
|
||||
|
||||
:Note: City object contains necessary attributes for the CityBEM workflow.
|
||||
The first final version is created at 10/07/2024
|
||||
"""
|
||||
#general output path for the CityLayer's hub
|
||||
out_path = Path(__file__).parent.parent / 'out_files'
|
||||
#create a directory for running CityBEM under the main out_path
|
||||
CityBEM_path = out_path / 'CityBEM_input_output'
|
||||
if not CityBEM_path.exists():
|
||||
CityBEM_path.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
# Define the path to the GeoJSON file
|
||||
file_path = Path(__file__).parent.parent / 'input_files' / 'output_buildings.geojson'
|
||||
|
||||
#load the geojson file (for now this is necessary, later, it should be removed to extract building usage type code, center lat and lon). Later, these should be added to the building class
|
||||
with open(file_path, 'r') as f:
|
||||
geojson_data = json.load(f)
|
||||
|
||||
#call functions to provide inputs for CityBEM and finally run CityBEM
|
||||
export_geometry(city, CityBEM_path)
|
||||
export_building_info(city, CityBEM_path,geojson_data)
|
||||
export_weather_data(city, CityBEM_path)
|
||||
export_comprehensive_building_data(city, CityBEM_path)
|
||||
export_indoor_temperature_setpoint_data(city, CityBEM_path)
|
||||
export_internal_heat_gain_data(city, CityBEM_path)
|
||||
run_CityBEM(CityBEM_path)
|
||||
def export_geometry(city, CityBEM_path):
|
||||
"""
|
||||
Export the STL geometry from the hub and rename the exported geometry to a proper name for CityBEM.
|
||||
:param city: City object containing necessary attributes for the workflow.
|
||||
:param CityBEM_path: Path where CityBEM input and output files are stored.
|
||||
"""
|
||||
ExportsFactory('stl', city, CityBEM_path).export()
|
||||
hubGeometryName = city.name + '.stl'
|
||||
#delete old files related to geometry if they exist
|
||||
CityBEMGeometryPath1 = CityBEM_path / 'Input_City_scale_geometry_CityBEM.stl'
|
||||
CityBEMGeometryPath2 = CityBEM_path / 'Input_City_scale_geometry_CityBEM.txt' #delete this file to ensure CityBEM generates a new one based on the new input geometry
|
||||
if CityBEMGeometryPath1.exists():
|
||||
CityBEMGeometryPath1.unlink()
|
||||
if CityBEMGeometryPath2.exists():
|
||||
CityBEMGeometryPath2.unlink()
|
||||
(CityBEM_path / hubGeometryName).rename(CityBEM_path / CityBEMGeometryPath1)
|
||||
print("CityBEM input geometry file named Input_City_scale_geometry_CityBEM.stl file has been created successfully")
|
||||
def get_building_info(geojson_data, building_id):
|
||||
for feature in geojson_data['features']:
|
||||
if feature['id'] == building_id:
|
||||
function_code = feature['properties']['function']
|
||||
coordinates = feature['geometry']['coordinates'][0]
|
||||
|
||||
#calculate the center of the polygon
|
||||
polygon = Polygon(coordinates)
|
||||
center = polygon.centroid
|
||||
|
||||
return function_code, (center.x, center.y)
|
||||
return None, None
|
||||
def export_building_info(city, CityBEM_path, geojson_file):
|
||||
"""
|
||||
Generate the input building information file for CityBEM.
|
||||
|
||||
:param city: City object containing necessary attributes for the workflow.
|
||||
:param CityBEM_path: Path where CityBEM input and output files are stored.
|
||||
"""
|
||||
buildingInfo_path = CityBEM_path / 'Input_City_scale_building_info.txt'
|
||||
with open(buildingInfo_path, "w", newline="") as textfile: #here, "w" refers to write mode. This deletes everything inside the file if the file exists.
|
||||
writer = csv.writer(textfile, delimiter="\t") #use tab delimiter for all CityBEM inputs
|
||||
writer.writerow(["building_stl", "building_osm", "constructionYear", "codeUsageType", "centerLongitude", "centerLatitude"]) # Header
|
||||
for building in city.buildings:
|
||||
function_code, center_coordinates = get_building_info(geojson_file, int (building.name))
|
||||
row = ["b" + building.name, "999999", str(building.year_of_construction), str(function_code), str(center_coordinates[0]), str(center_coordinates[1])]
|
||||
#note: based on CityBEM legacy, using a number like "999999" means that the data is not known/available.
|
||||
writer.writerow(row)
|
||||
|
||||
print("CityBEM input file named Input_City_scale_building_info.txt file has been created successfully")
|
||||
def export_weather_data(city, CityBEM_path):
|
||||
"""
|
||||
Generate the input weather data file compatible to CityBEM.
|
||||
|
||||
:param city: City object containing necessary attributes for the workflow.
|
||||
:param CityBEM_path: Path where CityBEM input and output files are stored.
|
||||
"""
|
||||
weatherParameters = EpwWeatherParameters(city)._weather_values
|
||||
weatherParameters = pd.DataFrame(weatherParameters) #transfer the weather data to a DataFrame
|
||||
|
||||
with open(CityBEM_path / 'Input_weatherdata.txt', 'w') as textfile:
|
||||
# write the header information
|
||||
textfile.write('Weather_timestep(s)\t3600\n')
|
||||
textfile.write('Weather_columns\t11\n') #so far, 11 columns can be extracted from the epw weather data.
|
||||
textfile.write('Date\tTime\tGHI\tDNI\tDHI\tTa\tTD\tTG\tRH\tWS\tWD\n')
|
||||
for _, row in weatherParameters.iterrows():
|
||||
#form the Date and Time
|
||||
Date = f"{int(row['year'])}-{int(row['month']):02d}-{int(row['day']):02d}"
|
||||
Time = f"{int(row['hour']):02d}:{int(row['minute']):02d}"
|
||||
#retrieve the weather data
|
||||
GHI = row['global_horizontal_radiation_wh_m2']
|
||||
DNI = row['direct_normal_radiation_wh_m2']
|
||||
DHI = row['diffuse_horizontal_radiation_wh_m2']
|
||||
Ta = row['dry_bulb_temperature_c']
|
||||
TD = row['dew_point_temperature_c']
|
||||
TG = row['dry_bulb_temperature_c']
|
||||
RH = row['relative_humidity_perc']
|
||||
WS = row['wind_speed_m_s']
|
||||
WD = row['wind_direction_deg']
|
||||
#write the data in tab-separated format into the text file
|
||||
textfile.write(f"{Date}\t{Time}\t{GHI}\t{DNI}\t{DHI}\t{Ta}\t{TD}\t{TG}\t{RH}\t{WS}\t{WD}\n")
|
||||
|
||||
print("CityBEM input file named Input_weatherdata.txt file has been created successfully")
|
||||
|
||||
def export_comprehensive_building_data(city, CityBEM_path):
|
||||
"""
|
||||
Extract and export detailed individual building data from the hub to replace CityBEM input archetypes, including both physical and thermal properties.
|
||||
:param city: City object containing necessary attributes for the workflow.
|
||||
:param CityBEM_path: Path where CityBEM input and output files are stored.
|
||||
"""
|
||||
with open(CityBEM_path / 'Input_comprehensive_building_data_CityLayer.txt', 'w') as textfile:
|
||||
writer = csv.writer(textfile, delimiter=',')
|
||||
header_row="\t".join([
|
||||
#building general information
|
||||
"buildingName",
|
||||
"constructionYear",
|
||||
"function",
|
||||
"roofType",
|
||||
"maxHeight",
|
||||
"storyHeight",
|
||||
"storiesAboveGround",
|
||||
"floorArea",
|
||||
"volume",
|
||||
"totalFloorArea",
|
||||
#roof details
|
||||
"roofThickness",
|
||||
"roofExternalH",
|
||||
"roofInternalH",
|
||||
"roofUvalue",
|
||||
"roofLongWaveEmittance",
|
||||
"roofShortWaveReflectance",
|
||||
"roofDensity",
|
||||
"roofSpecificHeat",
|
||||
"roofWWR",
|
||||
#floor details
|
||||
"floorThickness",
|
||||
"floorExternalH",
|
||||
"floorInternalH",
|
||||
"floorUvalue",
|
||||
"floorLongWaveEmittance",
|
||||
"floorShortWaveReflectance",
|
||||
"floorDensity",
|
||||
"floorSpecificHeat",
|
||||
"floorWWR",
|
||||
#wall details
|
||||
"wallThickness",
|
||||
"wallExternalH",
|
||||
"wallInternalH",
|
||||
"wallUValue",
|
||||
"wallLongWaveEmittance",
|
||||
"wallShortWaveReflectance",
|
||||
"wallDensity",
|
||||
"wallSpecificHeat",
|
||||
"wallWWRNorth",
|
||||
"wallWWREast",
|
||||
"wallWWRSouth",
|
||||
"wallWWRWest",
|
||||
#window details
|
||||
"windowOverallUValue",
|
||||
"windowGValue",
|
||||
"windowFrameRatio",
|
||||
#building thermal details
|
||||
"thermalBridgesExtraLoses",
|
||||
"infiltrationRateOff",
|
||||
"infiltrationRateOn"
|
||||
])
|
||||
textfile.write(header_row + "\n") #write the header row
|
||||
#extract and write comprehensive building data from the CityLayer's hub
|
||||
for building in city.buildings:
|
||||
#data should be appended based on the order of the headers.
|
||||
row=[]
|
||||
row.append("b" + building.name)
|
||||
row.append(building.year_of_construction)
|
||||
row.append(building.function)
|
||||
row.append(building.roof_type)
|
||||
row.append(building.max_height)
|
||||
row.append(building._storeys_above_ground)
|
||||
row.append(building.average_storey_height)
|
||||
row.append(building.floor_area)
|
||||
row.append(building.volume)
|
||||
# Initialize boundary rows
|
||||
row_roof = [None, None, None, None, None]
|
||||
row_ground = [None, None, None, None, None]
|
||||
row_wall = [None, None, None, None, None]
|
||||
wallCount = 0 # so far, the data for one wall represents all the walls
|
||||
for internal_zone in building.internal_zones:
|
||||
totalFloorArea = internal_zone.thermal_zones_from_internal_zones[0].total_floor_area
|
||||
row.append(totalFloorArea) #append the last item in "building general information"
|
||||
WWR = internal_zone.thermal_archetype.constructions[0].window_ratio #window to wall ratio for the walls
|
||||
northWWR = float(WWR['north'])/100. #the values from the hub is in percent. The conversion is needed.
|
||||
eastWWR = float(WWR['east'])/100.
|
||||
southWWR = float(WWR['south'])/100.
|
||||
westWWR = float(WWR['west'])/100.
|
||||
windowOverallUValue = internal_zone.thermal_archetype.constructions[0].window_overall_u_value
|
||||
windowGValue = internal_zone.thermal_archetype.constructions[0].window_g_value
|
||||
windowFrameRatio = internal_zone.thermal_archetype.constructions[0].window_frame_ratio
|
||||
thermalBridgesExtraLoses = internal_zone.thermal_archetype.extra_loses_due_to_thermal_bridges
|
||||
infiltrationRateOff = internal_zone.thermal_archetype.infiltration_rate_for_ventilation_system_off
|
||||
infiltrationRateOn = internal_zone.thermal_archetype.infiltration_rate_for_ventilation_system_on
|
||||
for boundary in internal_zone.thermal_zones_from_internal_zones:
|
||||
for thermal_boundary in boundary.thermal_boundaries:
|
||||
if thermal_boundary.type == "Roof":
|
||||
layers = thermal_boundary.layers #access the roof construction layers
|
||||
non_zero_layers = [layer for layer in layers if layer.thickness > 0] #filter out layers with zero thickness
|
||||
total_thickness = thermal_boundary.thickness
|
||||
if total_thickness > 0:
|
||||
weighted_density = sum(layer.thickness * layer.density for layer in non_zero_layers) / total_thickness #weighted average represneting the entire layer.
|
||||
weighted_specific_heat = sum(
|
||||
layer.thickness * layer.specific_heat for layer in non_zero_layers) / total_thickness
|
||||
else:
|
||||
weighted_specific_heat = 0 #handle the case where total_thickness is zero to avoid division by zero
|
||||
weighted_density = 0
|
||||
row_roof = [
|
||||
thermal_boundary.thickness,
|
||||
thermal_boundary.he,
|
||||
thermal_boundary.hi,
|
||||
thermal_boundary.u_value,
|
||||
thermal_boundary.external_surface.long_wave_emittance,
|
||||
thermal_boundary.external_surface.short_wave_reflectance,
|
||||
weighted_density,
|
||||
weighted_specific_heat,
|
||||
thermal_boundary.window_ratio
|
||||
]
|
||||
elif thermal_boundary.type == "Ground": #ground means floor in CityBEM based on the legacy in CityBEM.
|
||||
layers = thermal_boundary.layers # access the roof construction layers
|
||||
non_zero_layers = [layer for layer in layers if layer.thickness > 0] # filter out layers with zero thickness
|
||||
total_thickness = thermal_boundary.thickness
|
||||
if total_thickness > 0:
|
||||
weighted_density = sum(
|
||||
layer.thickness * layer.density for layer in non_zero_layers) / total_thickness
|
||||
weighted_specific_heat = sum(
|
||||
layer.thickness * layer.specific_heat for layer in non_zero_layers) / total_thickness
|
||||
else:
|
||||
weighted_specific_heat = 0 # Handle the case where total_thickness is zero to avoid division by zero
|
||||
weighted_density = 0
|
||||
row_ground = [
|
||||
thermal_boundary.thickness,
|
||||
thermal_boundary.he,
|
||||
thermal_boundary.hi,
|
||||
thermal_boundary.u_value,
|
||||
thermal_boundary.external_surface.long_wave_emittance,
|
||||
thermal_boundary.external_surface.short_wave_reflectance,
|
||||
weighted_density,
|
||||
weighted_specific_heat,
|
||||
thermal_boundary.window_ratio
|
||||
]
|
||||
elif thermal_boundary.type == "Wall" and wallCount == 0:
|
||||
wallCount += 1 #wall counter. So far, it is assumed that all the walls have a similar properties to be exported to CityBEM, except the WWR
|
||||
layers = thermal_boundary.layers # access the roof construction layers
|
||||
non_zero_layers = [layer for layer in layers if
|
||||
layer.thickness > 0] # filter out layers with zero thickness
|
||||
total_thickness = thermal_boundary.thickness
|
||||
if total_thickness > 0:
|
||||
weighted_density = sum(layer.thickness * layer.density for layer in non_zero_layers) / total_thickness
|
||||
weighted_specific_heat = sum(
|
||||
layer.thickness * layer.specific_heat for layer in non_zero_layers) / total_thickness
|
||||
else:
|
||||
weighted_specific_heat = 0 # Handle the case where total_thickness is zero to avoid division by zero
|
||||
weighted_density = 0
|
||||
row_wall = [
|
||||
thermal_boundary.thickness,
|
||||
thermal_boundary.he,
|
||||
thermal_boundary.hi,
|
||||
thermal_boundary.u_value,
|
||||
thermal_boundary.external_surface.long_wave_emittance,
|
||||
thermal_boundary.external_surface.short_wave_reflectance,
|
||||
weighted_density,
|
||||
weighted_specific_heat,
|
||||
northWWR,
|
||||
eastWWR,
|
||||
southWWR,
|
||||
westWWR
|
||||
]
|
||||
row.extend(row_roof)
|
||||
row.extend(row_ground)
|
||||
row.extend(row_wall)
|
||||
#append window details
|
||||
row.append(windowOverallUValue)
|
||||
row.append(windowGValue)
|
||||
row.append(windowFrameRatio)
|
||||
#append building thermal details
|
||||
row.append(thermalBridgesExtraLoses)
|
||||
row.append(infiltrationRateOff)
|
||||
row.append(infiltrationRateOn)
|
||||
|
||||
#convert each item in row to string (if needed) and join with tabs (tab separated data)
|
||||
row_str = "\t".join(map(str, row))
|
||||
#write the final row to the text file
|
||||
textfile.write(row_str + "\n")
|
||||
print("Individual building data is exported into a file named comprehensive_building_data.txt")
|
||||
def export_indoor_temperature_setpoint_data(city, CityBEM_path):
|
||||
"""
|
||||
Extract and export individual building data on indoor temperature setpoints
|
||||
:param city: City object containing necessary attributes for the workflow.
|
||||
:param CityBEM_path: Path where CityBEM input and output files are stored.
|
||||
"""
|
||||
#open a text file in write mode (write mode removes the content if there is any)
|
||||
with open(CityBEM_path /'Input_indoor_setpoint_temperature_CityLayer.txt', 'w') as textfile:
|
||||
#iterate through each building
|
||||
for building in city.buildings:
|
||||
#write the building name
|
||||
textfile.write("building"+building.name + '\t')
|
||||
#iterate through each internal zone in the building
|
||||
for internal_zone in building.internal_zones:
|
||||
#iterate through each boundary in the internal zone
|
||||
for boundary in internal_zone.thermal_zones_from_internal_zones:
|
||||
#gather all indoor setpoint values for both cooling and heating
|
||||
indoorSetpointValues = []
|
||||
indoorSetpointValues.extend(boundary.thermal_control.cooling_set_point_schedules[0].values)#cooling on working days
|
||||
indoorSetpointValues.extend(boundary.thermal_control.cooling_set_point_schedules[1].values)#cooling on Saturday
|
||||
indoorSetpointValues.extend(boundary.thermal_control.cooling_set_point_schedules[2].values)#cooling on Sunday/holidays
|
||||
indoorSetpointValues.extend(boundary.thermal_control.heating_set_point_schedules[0].values)#heating on working days
|
||||
indoorSetpointValues.extend(boundary.thermal_control.heating_set_point_schedules[1].values)#heating on Saturday
|
||||
indoorSetpointValues.extend(boundary.thermal_control.heating_set_point_schedules[2].values)#heating on Sunday/holidays
|
||||
|
||||
#convert values to a tab-separated strings
|
||||
values_str = '\t'.join(map(str, indoorSetpointValues))
|
||||
|
||||
#write the values to the text file for this building
|
||||
textfile.write(values_str + '\n')
|
||||
|
||||
print("Indoor temperature setpoints for every building is successfully exported into a text file named Input_indoor_setpoint_temperature_CityLayer.txt")
|
||||
def export_internal_heat_gain_data(city, CityBEM_path):
|
||||
"""
|
||||
Extract and export individual building data on internal heat gains (occupant, lighting, and equipment)
|
||||
:param city: City object containing necessary attributes for the workflow.
|
||||
:param CityBEM_path: Path where CityBEM input and output files are stored.
|
||||
"""
|
||||
# open a text file in write mode (write mode removes the content if there is any)
|
||||
with open(CityBEM_path / 'Input_internal_heat_gain_CityLayer.txt', 'w') as textfile:
|
||||
# iterate through each building
|
||||
for building in city.buildings:
|
||||
# write the building name
|
||||
textfile.write("building" + building.name + '\t') # (1) building name
|
||||
# gather all internal heat gains for every building
|
||||
internalHeatGains = []
|
||||
# iterate through each internal zone in the building
|
||||
for internal_zone in building.internal_zones:
|
||||
# iterate through each internal usage in the internal zone
|
||||
for usage in internal_zone.usages:
|
||||
# iterate through internal heat gains
|
||||
for internalGain in usage.internal_gains: # order: Occupancy, Lighting, and Appliances
|
||||
internalHeatGains.append(internalGain.average_internal_gain) # (2) average_internal_gain
|
||||
internalHeatGains.append(internalGain.convective_fraction) # (3) convective_fraction
|
||||
internalHeatGains.append(internalGain.latent_fraction) # (4) latent_fraction
|
||||
internalHeatGains.append(internalGain.radiative_fraction) # (5) radiative_fraction
|
||||
internalHeatGains.extend(internalGain.schedules[0].values) # (6-29) Working day
|
||||
internalHeatGains.extend(internalGain.schedules[1].values) # (30-54) Saturday
|
||||
internalHeatGains.extend(internalGain.schedules[2].values) # (55-79)Sunday
|
||||
# convert values to a tab-separated strings
|
||||
values_str = '\t'.join(map(str, internalHeatGains))
|
||||
# write the values to the text file for this building
|
||||
textfile.write(values_str + '\n')
|
||||
print("Internal heat gains for every building is successfully exported into a text file named Input_internal_heat_gain_CityLayer.txt")
|
||||
def run_CityBEM(CityBEM_path):
|
||||
"""
|
||||
Run the CityBEM executable after all inputs are processed.
|
||||
:param CityBEM_path: Path where CityBEM input and output files are stored.
|
||||
"""
|
||||
try:
|
||||
print('CityBEM execution began:')
|
||||
CityBEM_exe = CityBEM_path / 'CityBEM.exe' # path to the CityBEM executable
|
||||
# check if the executable file exists
|
||||
if not CityBEM_exe.exists():
|
||||
print(f"Error: {CityBEM_exe} does not exist.")
|
||||
subprocess.run(str(CityBEM_exe), check=True, cwd=str(CityBEM_path)) # execute the CityBEM executable
|
||||
print("CityBEM executable has finished successfully.")
|
||||
except Exception as ex:
|
||||
print(ex)
|
||||
print('error: ', ex)
|
||||
print('[CityBEM simulation abort]')
|
||||
sys.stdout.flush() #print all the running information on the screen
|
@ -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
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user