Compare commits

..

52 Commits

Author SHA1 Message Date
ee9dd58f82 Merge pull request 'pv_sizing_modelling' (#21) from pv_sizing_modelling into main
Reviewed-on: #21
2024-11-19 07:27:51 -05:00
e4c761850b fix: pv modelling workflow updated, montreal custom systems added to future catalogue 2024-11-19 13:25:54 +01:00
b633dca635 fix: small changes before the meeting 2024-11-13 14:30:08 +01:00
69a1af535b feat: PV workflow updated and modified 2024-11-13 12:11:11 +01:00
b6ec189207 Merge pull request 'oriol_changes' (#20) from oriol_changes into main
Reviewed-on: #20
2024-10-25 09:56:35 -04:00
49a531da8f fix: final changes from Oriol were implemented and tested 2024-10-25 15:51:35 +02:00
d77bd7d337 fix: oriol changes in catalogues were implemented 2024-10-21 10:16:35 +02:00
20a801626a fix: small bug in irradiance units impacting MEB results was fixed 2024-09-09 11:18:00 -04:00
bf0c42315d fix: small issues in energy system parameter importer is fixed 2024-09-05 12:41:32 -04:00
3545418293 fix:redundant codes removed 2024-08-28 15:41:12 -04:00
1906862517 Merge branch 'cleaning_simulation_models' 2024-08-28 10:35:42 -04:00
0048e18e55 fix: small issue in cop calculation resolved 2024-08-28 10:34:05 -04:00
52f94c858f Merge pull request 'feat: the workflow is organized to separate system models' (#19) from cleaning_simulation_models into main
Reviewed-on: https://nextgenerations-cities.encs.concordia.ca/gitea/s_ranjbar/energy_system_modelling_workflow/pulls/19
2024-08-26 19:40:06 -04:00
b0e6c7d9ef feat: the workflow is organized to separate system models 2024-08-26 19:39:05 -04:00
9af87fe482 feature: add district heating network sizing workflow
Reviewed-on: https://nextgenerations-cities.encs.concordia.ca/gitea/s_ranjbar/energy_system_modelling_workflow/pulls/18
2024-08-15 15:50:16 -04:00
eb26c48627 feature: add pipe sizing to dhn analysis 2024-08-15 15:44:22 -04:00
2ca52c157a fix: replace id with name in node attributes 2024-08-12 12:30:26 -04:00
62c70b9c9f feature: add dhn factory to enrich graph with city.buildings 2024-08-12 12:30:16 -04:00
a62f5e6f38 test: add jupyter notebook for some tests 2024-08-12 12:30:16 -04:00
6deffa9323 chore: fix indentation and change building id to name 2024-08-12 12:30:16 -04:00
c046eacc63 chore: fixing some bugs in the pv assessment 2024-08-12 12:30:16 -04:00
05b9a42672 chore: changing building_id to building_name in dhn generator 2024-08-12 12:30:16 -04:00
90a7f5648b (WIP) feature: enrich graph with building demand 2024-08-12 12:30:16 -04:00
4e75024817 feature: update dhn generator with the parent repo 2024-08-12 12:30:16 -04:00
904fe91e5a feature: add directory manager 2024-08-12 12:29:56 -04:00
Majid Rezaei
d2e20312b2 feature: add district heating network creator 2024-08-12 12:29:18 -04:00
Majid Rezaei
74cf47e3e1 feature: add simultinity factor calculations 2024-08-12 12:29:18 -04:00
1a43e65992 Merge pull request 'optimization' (#17) from optimization into main
Reviewed-on: https://nextgenerations-cities.encs.concordia.ca/gitea/s_ranjbar/energy_system_modelling_workflow/pulls/17
2024-08-08 12:26:48 -04:00
3b5e12efaf feat: started optimization but had to stop to clean simulation models 2024-08-08 12:21:56 -04:00
08639d9dd7 fix: small bug in archetype 1 simulation model was fixed 2024-08-01 14:55:01 -04:00
435fc4c679 fix: small bug in archetype 1 simulation is fixed 2024-08-01 14:43:49 -04:00
1f95943cbb fix: small bugs and typos in montreal_custom_systems data file are fixed 2024-07-31 15:25:49 -04:00
8f1cbd4a67 Merge pull request 'dhw_model_fixing' (#16) from dhw_model_fixing into main
Reviewed-on: https://nextgenerations-cities.encs.concordia.ca/gitea/s_ranjbar/energy_system_modelling_workflow/pulls/16
2024-07-26 17:50:42 -04:00
05d88e2461 fix: small bug is cooling simulation and operational income are fixed
fix: simulation models and cop curves modified and finalized

fix: all system simulation models are fixed
2024-07-26 17:49:22 -04:00
ogavalda
a474a7d97e Change percentages according to Energuide and data from HQ and Energir 2024-07-26 15:19:13 -04:00
ogavalda
0ae92e77a1 Merge remote-tracking branch 'origin/main'
# Conflicts:
#	scripts/random_assignation.py
2024-07-26 15:19:01 -04:00
ogavalda
e09338c300 Change percentages according to Energuide and data from HQ and Energir 2024-07-26 15:15:34 -04:00
af988e28ed Merge pull request 'fix: small bug is cooling simulation and operational income are fixed' (#15) from cooling_system_simulation_fix into main
Reviewed-on: https://nextgenerations-cities.encs.concordia.ca/gitea/s_ranjbar/energy_system_modelling_workflow/pulls/15
2024-07-22 17:43:18 -04:00
96711ad41e fix: small bug is cooling simulation and operational income are fixed 2024-07-22 17:37:40 -04:00
faeb3e63d4 Merge pull request 'fix: importers are fixed for mixed use buildings' (#14) from mixed_usage into main
Reviewed-on: https://nextgenerations-cities.encs.concordia.ca/gitea/s_ranjbar/energy_system_modelling_workflow/pulls/14
2024-07-22 09:13:25 -04:00
93ab78b34e fix: importers are fixed for mixed use buildings 2024-07-22 09:12:31 -04:00
6044cfc4e5 fix: cleaning the repo 2024-07-18 08:52:58 -04:00
a717f9a644 Merge pull request 'report' (#13) from report into main
Reviewed-on: https://nextgenerations-cities.encs.concordia.ca/gitea/s_ranjbar/energy_system_modelling_workflow/pulls/13
2024-07-18 08:48:43 -04:00
f32c74f84a fix: The report is almost completed. It will be continued after fixing all the simulation models 2024-07-18 08:47:18 -04:00
c4f98a30c1 fix: current and retrofitted status energy consumption analysis and system schematic added to the report 2024-07-15 08:51:15 -04:00
7369bc65a4 fix: first stages of report creation are started 2024-06-28 16:40:37 -04:00
58201afda8 Merge pull request 'fix: cost workflow finalized' (#12) from finalizing_cost into main
Reviewed-on: https://nextgenerations-cities.encs.concordia.ca/gitea/s_ranjbar/energy_system_modelling_workflow/pulls/12
2024-06-25 19:58:48 -04:00
2ef3be7fe3 fix: cost workflow finalized 2024-06-25 19:57:22 -04:00
2bd8a9a47d Merge pull request 'central_heating_system_archetype' (#11) from central_heating_system_archetype into main
Reviewed-on: https://nextgenerations-cities.encs.concordia.ca/gitea/s_ranjbar/energy_system_modelling_workflow/pulls/11
2024-06-25 18:15:55 -04:00
5f95d2a5fb feat: simulation models of 2 archetypes with central heating and decentral cooling and dhw are added 2024-06-25 18:14:12 -04:00
ee6dc92b40 fix: new archetype to model a system with central heating and decenral cooling and dhw created 2024-06-24 19:21:28 -04:00
335b316072 Merge pull request 'fix: different fuel pricings are added, operational and maintenance cost calculations are modified, cost catalogue adjusted to the changes' (#10) from costing_modification into main
Reviewed-on: https://nextgenerations-cities.encs.concordia.ca/gitea/s_ranjbar/energy_system_modelling_workflow/pulls/10
2024-06-20 10:30:49 -04:00
105 changed files with 9052 additions and 6697 deletions

2
.gitignore vendored
View File

@ -12,3 +12,5 @@
cerc_hub.egg-info
/out_files
/input_files/output_buildings.geojson
*/.pyc
*.pyc

View File

@ -2,7 +2,7 @@
<module type="PYTHON_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$" />
<orderEntry type="jdk" jdkName="hub" jdkType="Python SDK" />
<orderEntry type="jdk" jdkName="Python 3.9 (hub)" jdkType="Python SDK" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

View File

@ -1,4 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectRootManager" version="2" project-jdk-name="hub" project-jdk-type="Python SDK" />
<component name="Black">
<option name="sdkUUID" value="97386509-ec9b-4dd7-929b-7585219c0447" />
</component>
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.9 (hub)" project-jdk-type="Python SDK" />
</project>

View File

@ -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:

View File

@ -4,13 +4,16 @@ from shapely import Point
from pathlib import Path
def process_geojson(x, y, diff):
def process_geojson(x, y, diff, path, expansion=False):
selection_box = Polygon([[x + diff, y - diff],
[x - diff, y - diff],
[x - diff, y + diff],
[x + diff, y + diff]])
geojson_file = Path('./data/collinear_clean 2.geojson').resolve()
output_file = Path('./input_files/output_buildings.geojson').resolve()
geojson_file = Path(path / 'data/collinear_clean 2.geojson').resolve()
if not expansion:
output_file = Path(path / 'input_files/output_buildings.geojson').resolve()
else:
output_file = Path(path / 'input_files/output_buildings_expanded.geojson').resolve()
buildings_in_region = []
with open(geojson_file, 'r') as file:

View File

@ -1,61 +0,0 @@
import pandas as pd
from scripts.geojson_creator import process_geojson
from pathlib import Path
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 import random_assignation
from hub.imports.energy_systems_factory import EnergySystemsFactory
from scripts.energy_system_sizing_and_simulation_factory import EnergySystemsSimulationFactory
from scripts.costs.cost import Cost
from scripts.costs.constants import SYSTEM_RETROFIT_AND_PV
from hub.exports.exports_factory import ExportsFactory
# Specify the GeoJSON file path
location = [45.49034212153445, -73.61435648647083]
geojson_file = process_geojson(x=location[1], y=location[0], diff=0.0001)
file_path = (Path(__file__).parent / 'input_files' / 'processed_output -single_building.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()
ResultFactory('energy_plus_multiple_buildings', city, output_path).enrich()
ExportsFactory('sra', city, output_path).export()
random_assignation.call_random(city.buildings, random_assignation.residential_new_systems_percentage)
EnergySystemsFactory('montreal_future', city).enrich()
for building in city.buildings:
EnergySystemsSimulationFactory('archetype13', building=building, output_path=output_path).enrich()
sum_floor_area = 0
buildings_list = []
for building in city.buildings:
buildings_list.append(building.name)
df = pd.DataFrame(columns=['building_name', 'total_floor_area', 'investment_cost', 'lc CAPEX'])
df['building_name'] = buildings_list
for building in city.buildings:
for thermal_zone in building.thermal_zones_from_internal_zones:
sum_floor_area += thermal_zone.total_floor_area
costs = Cost(building=building, retrofit_scenario=SYSTEM_RETROFIT_AND_PV).life_cycle
costs.loc['global_capital_costs', f'Scenario {SYSTEM_RETROFIT_AND_PV}'].to_csv(
output_path / f'{building.name}_cc.csv')
investment_cost = costs.loc['global_capital_costs',
f'Scenario {SYSTEM_RETROFIT_AND_PV}'].loc[0, 'D3020_heat_and_cooling_generating_systems']
lcc_capex = costs.loc['total_capital_costs_systems', f'Scenario {SYSTEM_RETROFIT_AND_PV}']
df.loc[df['building_name'] == building.name, 'total_floor_area'] = (
building.thermal_zones_from_internal_zones[0].total_floor_area)
df.loc[df['building_name'] == building.name, 'investment_cost'] = investment_cost
df.loc[df['building_name'] == building.name, 'lc CAPEX'] = lcc_capex
df.to_csv(output_path / 'economic analysis.csv')

View File

@ -11,10 +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, CURRENT_STATUS, 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):
@ -32,10 +32,12 @@ 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',
],
dtype='float'
@ -45,10 +47,12 @@ 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_incomes = pd.DataFrame(
@ -107,7 +111,7 @@ 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]
if self._configuration.retrofit_scenario not in (SYSTEM_RETROFIT_AND_PV, CURRENT_STATUS, PV):
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
@ -151,16 +155,23 @@ class CapitalCosts(CostBase):
chapter = self._capital_costs_chapter.chapter('D_services')
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 = 0
capital_cost_pv = self._surface_pv * chapter.item('D2010_photovoltaic_system').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 += 0
capital_cost_domestic_hot_water_equipment += chapter.item(component).initial_investment[0] * \
component_sizes[i]
elif component_categories[i] == 'distribution':
capital_cost_distribution_equipment += chapter.item(component).initial_investment[0] * \
component_sizes[i]
@ -169,19 +180,25 @@ class CapitalCosts(CostBase):
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):
self._yearly_capital_costs.loc[0, 'D3020_heat_and_cooling_generating_systems'] = (
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, 'D3040_distribution_systems'] = (
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, 'D3060_storage_systems'] = (
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_distribution_equipment,
capital_cost_energy_storage_equipment, capital_cost_domestic_hot_water_equipment, capital_cost_lighting, capital_cost_hvac)
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')
@ -201,59 +218,89 @@ 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, '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] == '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
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 = 0
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
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, 'D3060_storage_systems'] += reposition_cost_energy_storage_equipment
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 * 0 * costs_increase
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 * 0 * costs_increase
self._surface_pv * chapter.item('D2010_photovoltaic_system').reposition[0] * costs_increase
)
def system_components(self):
@ -279,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)
@ -289,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:

View File

@ -12,10 +12,12 @@ 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,
PV
PV,
SYSTEM_RETROFIT
]

View File

@ -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
@ -93,10 +92,12 @@ 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['D2010_photovoltaic_system']
)

View File

@ -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:

View File

@ -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):

View File

@ -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 TotalMaintenanceCosts(CostBase):

View File

@ -10,9 +10,9 @@ 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
from costing_package.configuration import Configuration
from costing_package.cost_base import CostBase
from costing_package.peak_load import PeakLoad
class TotalOperationalCosts(CostBase):
@ -196,7 +196,7 @@ class TotalOperationalCosts(CostBase):
if cooling is not None:
hourly += cooling[i] / 3600
if dhw is not None:
dhw += dhw[i] / 3600
hourly += dhw[i] / 3600
hourly_fuel_consumption.append(hourly)
else:
heating = None

View File

@ -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
View 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')
#%% --------------------------------------------------------------------------------------------------------------------

View File

@ -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()))

View File

@ -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)()

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -1,16 +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.archetype13_stratified_tes import Archetype13Stratified
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
"""
@ -20,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

View File

@ -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

View File

@ -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)

View File

@ -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 023 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])]

View File

@ -0,0 +1,6 @@
class HeuristicSizing:
def __init__(self, city):
pass
def enrich_buildings(self):
pass

View File

@ -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

View File

@ -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()

View File

@ -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

View File

@ -15,8 +15,8 @@ from hub.city_model_structure.building import Building
energy_systems_format = 'montreal_custom'
# parameters:
residential_systems_percentage = {'system 1 gas': 100,
'system 1 electricity': 0,
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': 100,
'system 5 electricity': 0,
'system 6 gas': 0,
'system 6 electricity': 0,
'system 8 gas': 0,
'system 8 electricity': 0}
'system 8 gas': 15,
'system 8 electricity': 35}
residential_new_systems_percentage = {'PV+ASHP+GasBoiler+TES': 0,
'PV+4Pipe+DHW': 100,
'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

View 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)])

View File

@ -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()

View File

View 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()

View File

@ -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):

View File

@ -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):

View File

@ -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):

View File

@ -48,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,

View File

@ -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
}
}

View File

@ -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

View File

@ -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

View File

@ -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,

View File

@ -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
}
}

View File

@ -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

View File

@ -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,
@ -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):

View File

@ -92,7 +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._pv_generation = {}
self._systems_archetype_cluster_id = None
@property
def shell(self) -> Polyhedron:
@ -810,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):
"""
@ -863,60 +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]
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[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:
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:
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]:
for generation_system in energy_system.generation_systems:
fuel_breakdown[generation_system.fuel_type][cte.COOLING] = self.cooling_consumption[cte.YEAR][0]
for fuel in heating_fuels:
if cte.HEATING not in fuel_breakdown[fuel]:
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:
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:
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.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]
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:
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]
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 pv_generation(self) -> dict:
return self._pv_generation
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
@pv_generation.setter
def pv_generation(self, value):
self._pv_generation = value

View File

@ -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:

View File

@ -42,7 +42,7 @@ 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
@ -157,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):
"""
@ -180,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
@ -188,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
@ -390,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
@ -398,7 +399,7 @@ 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

View File

@ -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

View File

@ -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):
"""

View File

@ -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):

View File

@ -24,7 +24,7 @@ class ThermalStorageSystem(EnergyStorageSystem):
self._maximum_operating_temperature = None
self._heating_coil_capacity = None
self._temperature = None
self._heating_coil_energy_consumption = None
self._heating_coil_energy_consumption = {}
@property
def volume(self):

View File

@ -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

View File

@ -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>

View File

@ -187,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>

View File

@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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')

View File

@ -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):

View File

@ -24,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'
@ -301,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'
@ -310,7 +313,8 @@ LATENT = 'Latent'
LITHIUMION = 'Lithium Ion'
NICD = 'NiCd'
LEADACID = 'Lead Acid'
THERMAL = 'thermal'
ELECTRICAL = 'electrical'
# Geometry
EPSILON = 0.0000001

View File

@ -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:

View File

@ -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
}

View File

@ -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:

View File

@ -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()

View File

@ -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()

View File

@ -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

View File

@ -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
@ -185,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

View File

@ -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

View File

@ -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

View File

@ -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:

View File

@ -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]

View File

@ -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

View File

@ -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):

View File

@ -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]

View File

@ -1,50 +0,0 @@
{
"type": "FeatureCollection",
"name": "lachine_group_mach_buildings",
"crs": {
"type": "name",
"properties": {
"name": "urn:ogc:def:crs:OGC:1.3:CRS84"
}
},
"features": [
{
"type": "Feature",
"properties": {
"name": "1",
"address": "",
"function": 1000,
"height": 23.29,
"year_of_construction": 2023
},
"geometry": {
"type": "Polygon",
"coordinates": [
[
[
-73.66557613653009,
45.43551716511939
],
[
-73.66530891881455,
45.43551716511939
],
[
-73.66530891881455,
45.43590129058549
],
[
-73.66557613653009,
45.43590129058549
],
[
-73.66557613653009,
45.43551716511939
]
]
]
},
"id": 1
}
]
}

File diff suppressed because it is too large Load Diff

52
main.py
View File

@ -1,52 +0,0 @@
import pandas as pd
from pathlib import Path
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 scripts import random_assignation
from hub.imports.energy_systems_factory import EnergySystemsFactory
from scripts.energy_system_sizing_and_simulation_factory import EnergySystemsSimulationFactory
from scripts.costs.cost import Cost
from scripts.costs.constants import SYSTEM_RETROFIT_AND_PV
# Specify the GeoJSON file path
file_path = (Path(__file__).parent / 'input_files' / 'processed_output.geojson')
output_path = (Path(__file__).parent / 'out_files').resolve()
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
ConstructionFactory('nrcan', city).enrich()
UsageFactory('nrcan', city).enrich()
WeatherFactory('epw', city).enrich()
energy_plus_workflow(city)
random_assignation.call_random(city.buildings, random_assignation.residential_new_systems_percentage)
EnergySystemsFactory('montreal_future', city).enrich()
for building in city.buildings:
EnergySystemsSimulationFactory('archetype13', building=building, output_path=output_path).enrich()
sum_floor_area = 0
buildings_list = []
for building in city.buildings:
buildings_list.append(building.name)
df = pd.DataFrame(columns=['building_name', 'total_floor_area', 'investment_cost', 'lc CAPEX'])
df['building_name'] = buildings_list
for building in city.buildings:
for thermal_zone in building.thermal_zones_from_internal_zones:
sum_floor_area += thermal_zone.total_floor_area
costs = Cost(building=building, retrofit_scenario=SYSTEM_RETROFIT_AND_PV).life_cycle
costs.loc['global_capital_costs', f'Scenario {SYSTEM_RETROFIT_AND_PV}'].to_csv(
output_path / f'{building.name}_cc.csv')
investment_cost = costs.loc['global_capital_costs',
f'Scenario {SYSTEM_RETROFIT_AND_PV}'].loc[0, 'D3020_heat_and_cooling_generating_systems']
lcc_capex = costs.loc['total_capital_costs_systems', f'Scenario {SYSTEM_RETROFIT_AND_PV}']
df.loc[df['building_name'] == building.name, 'total_floor_area'] = (
building.thermal_zones_from_internal_zones[0].total_floor_area)
df.loc[df['building_name'] == building.name, 'investment_cost'] = investment_cost
df.loc[df['building_name'] == building.name, 'lc CAPEX'] = lcc_capex
df.to_csv(output_path / 'economic analysis.csv')

View File

@ -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')

View 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)

View 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']
])

View File

@ -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()

View 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

View File

@ -1,338 +0,0 @@
import os
import hub.helpers.constants as cte
import matplotlib.pyplot as plt
import random
import matplotlib.colors as mcolors
from matplotlib import cm
from scripts.report_creation import LatexReport
class EnergySystemAnalysisReport:
def __init__(self, city, output_path):
self.city = city
self.output_path = output_path
self.content = []
self.report = LatexReport('energy_system_analysis_report.tex')
def building_energy_info(self):
table_data = [
["Building Name", "Year of Construction", "function", "Yearly Heating Demand (MWh)",
"Yearly Cooling Demand (MWh)", "Yearly DHW Demand (MWh)", "Yearly Electricity Demand (MWh)"]
]
intensity_table_data = [["Building Name", "Total Floor Area m2", "Heating Demand Intensity kWh/m2",
"Cooling Demand Intensity kWh/m2", "Electricity Intensity kWh/m2"]]
for building in self.city.buildings:
total_floor_area = 0
for zone in building.thermal_zones_from_internal_zones:
total_floor_area += zone.total_floor_area
building_data = [
building.name,
str(building.year_of_construction),
building.function,
str(format(building.heating_demand[cte.YEAR][0] / 3.6e9, '.2f')),
str(format(building.cooling_demand[cte.YEAR][0] / 3.6e9, '.2f')),
str(format(building.domestic_hot_water_heat_demand[cte.YEAR][0] / 1e6, '.2f')),
str(format(
(building.lighting_electrical_demand[cte.YEAR][0] + building.appliances_electrical_demand[cte.YEAR][0])
/ 1e6, '.2f')),
]
intensity_data = [
building.name,
str(format(total_floor_area, '.2f')),
str(format(building.heating_demand[cte.YEAR][0] / (3.6e6 * total_floor_area), '.2f')),
str(format(building.cooling_demand[cte.YEAR][0] / (3.6e6 * total_floor_area), '.2f')),
str(format(
(building.lighting_electrical_demand[cte.YEAR][0] + building.appliances_electrical_demand[cte.YEAR][0]) /
(1e3 * total_floor_area), '.2f'))
]
table_data.append(building_data)
intensity_table_data.append(intensity_data)
self.report.add_table(table_data, caption='City Buildings Energy Demands')
self.report.add_table(intensity_table_data, caption='Energy Intensity Information')
def base_case_charts(self):
save_directory = self.output_path
def autolabel(bars, ax):
for bar in bars:
height = bar.get_height()
ax.annotate('{:.1f}'.format(height),
xy=(bar.get_x() + bar.get_width() / 2, height),
xytext=(0, 3), # 3 points vertical offset
textcoords="offset points",
ha='center', va='bottom')
def create_hvac_demand_chart(building_names, yearly_heating_demand, yearly_cooling_demand):
fig, ax = plt.subplots()
bar_width = 0.35
index = range(len(building_names))
bars1 = ax.bar(index, yearly_heating_demand, bar_width, label='Yearly Heating Demand (MWh)')
bars2 = ax.bar([i + bar_width for i in index], yearly_cooling_demand, bar_width,
label='Yearly Cooling Demand (MWh)')
ax.set_xlabel('Building Name')
ax.set_ylabel('Energy Demand (MWh)')
ax.set_title('Yearly HVAC Demands')
ax.set_xticks([i + bar_width / 2 for i in index])
ax.set_xticklabels(building_names, rotation=45, ha='right')
ax.legend()
autolabel(bars1, ax)
autolabel(bars2, ax)
fig.tight_layout()
plt.savefig(save_directory / 'hvac_demand_chart.jpg')
plt.close()
def create_bar_chart(title, ylabel, data, filename, bar_color=None):
fig, ax = plt.subplots()
bar_width = 0.35
index = range(len(building_names))
if bar_color is None:
# Generate a random color
bar_color = random.choice(list(mcolors.CSS4_COLORS.values()))
bars = ax.bar(index, data, bar_width, label=ylabel, color=bar_color)
ax.set_xlabel('Building Name')
ax.set_ylabel('Energy Demand (MWh)')
ax.set_title(title)
ax.set_xticks([i + bar_width / 2 for i in index])
ax.set_xticklabels(building_names, rotation=45, ha='right')
ax.legend()
autolabel(bars, ax)
fig.tight_layout()
plt.savefig(save_directory / filename)
plt.close()
building_names = [building.name for building in self.city.buildings]
yearly_heating_demand = [building.heating_demand[cte.YEAR][0] / 3.6e9 for building in self.city.buildings]
yearly_cooling_demand = [building.cooling_demand[cte.YEAR][0] / 3.6e9 for building in self.city.buildings]
yearly_dhw_demand = [building.domestic_hot_water_heat_demand[cte.YEAR][0] / 1e6 for building in
self.city.buildings]
yearly_electricity_demand = [(building.lighting_electrical_demand[cte.YEAR][0] +
building.appliances_electrical_demand[cte.YEAR][0]) / 1e6 for building in
self.city.buildings]
create_hvac_demand_chart(building_names, yearly_heating_demand, yearly_cooling_demand)
create_bar_chart('Yearly DHW Demands', 'Energy Demand (MWh)', yearly_dhw_demand, 'dhw_demand_chart.jpg', )
create_bar_chart('Yearly Electricity Demands', 'Energy Demand (MWh)', yearly_electricity_demand,
'electricity_demand_chart.jpg')
def maximum_monthly_hvac_chart(self):
save_directory = self.output_path
months = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October',
'November', 'December']
for building in self.city.buildings:
maximum_monthly_heating_load = []
maximum_monthly_cooling_load = []
fig, axs = plt.subplots(1, 2, figsize=(12, 6)) # Create a figure with 2 subplots side by side
for demand in building.heating_peak_load[cte.MONTH]:
maximum_monthly_heating_load.append(demand / 3.6e6)
for demand in building.cooling_peak_load[cte.MONTH]:
maximum_monthly_cooling_load.append(demand / 3.6e6)
# Plot maximum monthly heating load
axs[0].bar(months, maximum_monthly_heating_load, color='red') # Plot on the first subplot
axs[0].set_title('Maximum Monthly Heating Load')
axs[0].set_xlabel('Month')
axs[0].set_ylabel('Load (kWh)')
axs[0].tick_params(axis='x', rotation=45)
# Plot maximum monthly cooling load
axs[1].bar(months, maximum_monthly_cooling_load, color='blue') # Plot on the second subplot
axs[1].set_title('Maximum Monthly Cooling Load')
axs[1].set_xlabel('Month')
axs[1].set_ylabel('Load (kWh)')
axs[1].tick_params(axis='x', rotation=45)
plt.tight_layout() # Adjust layout to prevent overlapping
plt.savefig(save_directory / f'{building.name}_monthly_maximum_hvac_loads.jpg')
plt.close()
def load_duration_curves(self):
save_directory = self.output_path
for building in self.city.buildings:
heating_demand = [demand / 3.6e6 for demand in building.heating_demand[cte.HOUR]]
cooling_demand = [demand / 3.6e6 for demand in building.cooling_demand[cte.HOUR]]
heating_demand_sorted = sorted(heating_demand, reverse=True)
cooling_demand_sorted = sorted(cooling_demand, reverse=True)
plt.style.use('ggplot')
# Create figure and axis objects with 1 row and 2 columns
fig, axs = plt.subplots(1, 2, figsize=(12, 6))
# Plot sorted heating demand
axs[0].plot(heating_demand_sorted, color='red', linewidth=2, label='Heating Demand')
axs[0].set_xlabel('Hour', fontsize=14)
axs[0].set_ylabel('Heating Demand (kWh)', fontsize=14)
axs[0].set_title('Heating Load Duration Curve', fontsize=16)
axs[0].grid(True)
axs[0].legend(loc='upper right', fontsize=12)
# Plot sorted cooling demand
axs[1].plot(cooling_demand_sorted, color='blue', linewidth=2, label='Cooling Demand')
axs[1].set_xlabel('Hour', fontsize=14)
axs[1].set_ylabel('Cooling Demand (kWh)', fontsize=14)
axs[1].set_title('Cooling Load Duration Curve', fontsize=16)
axs[1].grid(True)
axs[1].legend(loc='upper right', fontsize=12)
# Adjust layout
plt.tight_layout()
plt.savefig(save_directory / f'{building.name}_load_duration_curve.jpg')
plt.close()
def individual_building_info(self, building):
table_data = [
["Maximum Monthly HVAC Demands",
f"\\includegraphics[width=1\\linewidth]{{{building.name}_monthly_maximum_hvac_loads.jpg}}"],
["Load Duration Curve", f"\\includegraphics[width=1\\linewidth]{{{building.name}_load_duration_curve.jpg}}"],
]
self.report.add_table(table_data, caption=f'{building.name} Information', first_column_width=1.5)
def building_system_retrofit_results(self, building_name, current_system, new_system):
current_system_archetype = current_system[f'{building_name}']['Energy System Archetype']
current_system_heating = current_system[f'{building_name}']['Heating Equipments']
current_system_cooling = current_system[f'{building_name}']['Cooling Equipments']
current_system_dhw = current_system[f'{building_name}']['DHW Equipments']
current_system_pv = current_system[f'{building_name}']['Photovoltaic System Capacity']
current_system_heating_fuel = current_system[f'{building_name}']['Heating Fuel']
current_system_hvac_consumption = current_system[f'{building_name}']['Yearly HVAC Energy Consumption (MWh)']
current_system_dhw_consumption = current_system[f'{building_name}']['DHW Energy Consumption (MWH)']
current_pv_production = current_system[f'{building_name}']['PV Yearly Production (kWh)']
current_capital_cost = current_system[f'{building_name}']['Energy System Capital Cost (CAD)']
current_operational = current_system[f'{building_name}']['Energy System Average Yearly Operational Cost (CAD)']
current_lcc = current_system[f'{building_name}']['Energy System Life Cycle Cost (CAD)']
new_system_archetype = new_system[f'{building_name}']['Energy System Archetype']
new_system_heating = new_system[f'{building_name}']['Heating Equipments']
new_system_cooling = new_system[f'{building_name}']['Cooling Equipments']
new_system_dhw = new_system[f'{building_name}']['DHW Equipments']
new_system_pv = new_system[f'{building_name}']['Photovoltaic System Capacity']
new_system_heating_fuel = new_system[f'{building_name}']['Heating Fuel']
new_system_hvac_consumption = new_system[f'{building_name}']['Yearly HVAC Energy Consumption (MWh)']
new_system_dhw_consumption = new_system[f'{building_name}']['DHW Energy Consumption (MWH)']
new_pv_production = new_system[f'{building_name}']['PV Yearly Production (kWh)']
new_capital_cost = new_system[f'{building_name}']['Energy System Capital Cost (CAD)']
new_operational = new_system[f'{building_name}']['Energy System Average Yearly Operational Cost (CAD)']
new_lcc = new_system[f'{building_name}']['Energy System Life Cycle Cost (CAD)']
energy_system_table_data = [
["Detail", "Existing System", "Proposed System"],
["Energy System Archetype", current_system_archetype, new_system_archetype],
["Heating Equipments", current_system_heating, new_system_heating],
["Cooling Equipments", current_system_cooling, new_system_cooling],
["DHW Equipments", current_system_dhw, new_system_dhw],
["Photovoltaic System Capacity", current_system_pv, new_system_pv],
["Heating Fuel", current_system_heating_fuel, new_system_heating_fuel],
["Yearly HVAC Energy Consumption (MWh)", current_system_hvac_consumption, new_system_hvac_consumption],
["DHW Energy Consumption (MWH)", current_system_dhw_consumption, new_system_dhw_consumption],
["PV Yearly Production (kWh)", current_pv_production, new_pv_production],
["Energy System Capital Cost (CAD)", current_capital_cost, new_capital_cost],
["Energy System Average Yearly Operational Cost (CAD)", current_operational, new_operational],
["Energy System Life Cycle Cost (CAD)", current_lcc, new_lcc]
]
self.report.add_table(energy_system_table_data, caption=f'Building {building_name} Energy System Characteristics')
def building_fuel_consumption_breakdown(self, building):
save_directory = self.output_path
# Initialize variables to store fuel consumption breakdown
fuel_breakdown = {
"Heating": {"Gas": 0, "Electricity": 0},
"Domestic Hot Water": {"Gas": 0, "Electricity": 0},
"Cooling": {"Electricity": 0},
"Appliance": building.appliances_electrical_demand[cte.YEAR][0] / 1e6,
"Lighting": building.lighting_electrical_demand[cte.YEAR][0] / 1e6
}
# Iterate through energy systems of the building
for energy_system in building.energy_systems:
for demand_type in energy_system.demand_types:
if demand_type == cte.HEATING:
consumption = building.heating_consumption[cte.YEAR][0] / 3.6e9
for generation_system in energy_system.generation_systems:
if generation_system.fuel_type == cte.ELECTRICITY:
fuel_breakdown[demand_type]["Electricity"] += consumption
else:
fuel_breakdown[demand_type]["Gas"] += consumption
elif demand_type == cte.DOMESTIC_HOT_WATER:
consumption = building.domestic_hot_water_consumption[cte.YEAR][0] / 1e6
for generation_system in energy_system.generation_systems:
if generation_system.fuel_type == cte.ELECTRICITY:
fuel_breakdown[demand_type]["Electricity"] += consumption
else:
fuel_breakdown[demand_type]["Gas"] += consumption
elif demand_type == cte.COOLING:
consumption = building.cooling_consumption[cte.YEAR][0] / 3.6e9
fuel_breakdown[demand_type]["Electricity"] += consumption
electricity_labels = ['Appliance', 'Lighting']
electricity_sizes = [fuel_breakdown['Appliance'], fuel_breakdown['Lighting']]
if fuel_breakdown['Heating']['Electricity'] > 0:
electricity_labels.append('Heating')
electricity_sizes.append(fuel_breakdown['Heating']['Electricity'])
if fuel_breakdown['Cooling']['Electricity'] > 0:
electricity_labels.append('Cooling')
electricity_sizes.append(fuel_breakdown['Cooling']['Electricity'])
if fuel_breakdown['Domestic Hot Water']['Electricity'] > 0:
electricity_labels.append('Domestic Hot Water')
electricity_sizes.append(fuel_breakdown['Domestic Hot Water']['Electricity'])
# Data for bar chart
gas_labels = ['Heating', 'Domestic Hot Water']
gas_sizes = [fuel_breakdown['Heating']['Gas'], fuel_breakdown['Domestic Hot Water']['Gas']]
# Set the style
plt.style.use('ggplot')
# Create plot grid
fig, axs = plt.subplots(1, 2, figsize=(12, 6))
# Plot pie chart for electricity consumption breakdown
colors = cm.get_cmap('tab20c', len(electricity_labels))
axs[0].pie(electricity_sizes, labels=electricity_labels,
autopct=lambda pct: f"{pct:.1f}%\n({pct / 100 * sum(electricity_sizes):.2f})",
startangle=90, colors=[colors(i) for i in range(len(electricity_labels))])
axs[0].set_title('Electricity Consumption Breakdown')
# Plot bar chart for natural gas consumption breakdown
colors = cm.get_cmap('Paired', len(gas_labels))
axs[1].bar(gas_labels, gas_sizes, color=[colors(i) for i in range(len(gas_labels))])
axs[1].set_ylabel('Consumption (MWh)')
axs[1].set_title('Natural Gas Consumption Breakdown')
# Add grid to bar chart
axs[1].grid(axis='y', linestyle='--', alpha=0.7)
# Add a title to the entire figure
plt.suptitle('Building Energy Consumption Breakdown', fontsize=16, fontweight='bold')
# Adjust layout
plt.tight_layout()
# Save the plot as a high-quality image
plt.savefig(save_directory / f'{building.name}_energy_consumption_breakdown.png', dpi=300)
plt.close()
def create_report(self, current_system, new_system):
os.chdir(self.output_path)
self.report.add_section('Current Status')
self.building_energy_info()
self.base_case_charts()
self.report.add_image('hvac_demand_chart.jpg', caption='Yearly HVAC Demands')
self.report.add_image('dhw_demand_chart.jpg', caption='Yearly DHW Demands')
self.report.add_image('electricity_demand_chart.jpg', caption='Yearly Electricity Demands')
self.maximum_monthly_hvac_chart()
self.load_duration_curves()
for building in self.city.buildings:
self.individual_building_info(building)
self.building_system_retrofit_results(building_name=building.name, current_system=current_system, new_system=new_system)
self.building_fuel_consumption_breakdown(building)
self.report.add_image(f'{building.name}_energy_consumption_breakdown.png',
caption=f'Building {building.name} Consumption by source and sector breakdown')
self.report.save_report()
self.report.compile_to_pdf()

View File

@ -1,68 +0,0 @@
import hub.helpers.constants as cte
def system_results(buildings):
system_performance_summary = {}
fields = ["Energy System Archetype", "Heating Equipments", "Cooling Equipments", "DHW Equipments",
"Photovoltaic System Capacity", "Heating Fuel", "Yearly HVAC Energy Consumption (MWh)",
"DHW Energy Consumption (MWH)", "PV Yearly Production (kWh)", "LCC Analysis Duration (Years)",
"Energy System Capital Cost (CAD)", "Energy System Average Yearly Operational Cost (CAD)",
"Energy System Life Cycle Cost (CAD)"]
for building in buildings:
system_performance_summary[f'{building.name}'] = {}
for field in fields:
system_performance_summary[f'{building.name}'][field] = '-'
for building in buildings:
fuels = []
system_performance_summary[f'{building.name}']['Energy System Archetype'] = building.energy_systems_archetype_name
energy_systems = building.energy_systems
for energy_system in energy_systems:
demand_types = energy_system.demand_types
for demand_type in demand_types:
if demand_type == cte.COOLING:
equipments = []
for generation_system in energy_system.generation_systems:
if generation_system.fuel_type == cte.ELECTRICITY:
equipments.append(generation_system.name or generation_system.system_type)
cooling_equipments = ", ".join(equipments)
system_performance_summary[f'{building.name}']['Cooling Equipments'] = cooling_equipments
elif demand_type == cte.HEATING:
equipments = []
for generation_system in energy_system.generation_systems:
if generation_system.nominal_heat_output is not None:
equipments.append(generation_system.name or generation_system.system_type)
fuels.append(generation_system.fuel_type)
heating_equipments = ", ".join(equipments)
system_performance_summary[f'{building.name}']['Heating Equipments'] = heating_equipments
elif demand_type == cte.DOMESTIC_HOT_WATER:
equipments = []
for generation_system in energy_system.generation_systems:
equipments.append(generation_system.name or generation_system.system_type)
dhw_equipments = ", ".join(equipments)
system_performance_summary[f'{building.name}']['DHW Equipments'] = dhw_equipments
for generation_system in energy_system.generation_systems:
if generation_system.system_type == cte.PHOTOVOLTAIC:
system_performance_summary[f'{building.name}'][
'Photovoltaic System Capacity'] = generation_system.nominal_electricity_output or str(0)
heating_fuels = ", ".join(fuels)
system_performance_summary[f'{building.name}']['Heating Fuel'] = heating_fuels
system_performance_summary[f'{building.name}']['Yearly HVAC Energy Consumption (MWh)'] = format(
(building.heating_consumption[cte.YEAR][0] + building.cooling_consumption[cte.YEAR][0]) / 3.6e9, '.2f')
system_performance_summary[f'{building.name}']['DHW Energy Consumption (MWH)'] = format(
building.domestic_hot_water_consumption[cte.YEAR][0] / 1e6, '.2f')
return system_performance_summary
def new_system_results(buildings):
new_system_performance_summary = {}
fields = ["Energy System Archetype", "Heating Equipments", "Cooling Equipments", "DHW Equipments",
"Photovoltaic System Capacity", "Heating Fuel", "Yearly HVAC Energy Consumption (MWh)",
"DHW Energy Consumption (MWH)", "PV Yearly Production (kWh)", "LCC Analysis Duration (Years)",
"Energy System Capital Cost (CAD)", "Energy System Average Yearly Operational Cost (CAD)",
"Energy System Life Cycle Cost (CAD)"]
for building in buildings:
new_system_performance_summary[f'{building.name}'] = {}
for field in fields:
new_system_performance_summary[f'{building.name}'][field] = '-'
return new_system_performance_summary

View File

@ -1,70 +0,0 @@
"""
Energy System rule-based sizing
SPDX - License - Identifier: LGPL - 3.0 - or -later
Copyright © 2023 Concordia CERC group
Project Coder Saeed Ranjbar saeed.ranjbar@concordia.ca
"""
import hub.helpers.constants as cte
class SystemSizing:
"""
The energy system sizing class
"""
def __init__(self, buildings):
self.buildings = buildings
def hvac_sizing(self):
for building in self.buildings:
peak_heating_demand = building.heating_peak_load[cte.YEAR][0] / 3600
peak_cooling_demand = building.cooling_peak_load[cte.YEAR][0] / 3600
if peak_heating_demand > peak_cooling_demand:
sizing_demand = peak_heating_demand
for system in building.energy_systems:
if 'Heating' in system.demand_types:
for generation in system.generation_systems:
if generation.system_type == 'Heat Pump':
if generation.source_medium == cte.AIR:
generation.source_temperature = building.external_temperature
generation.nominal_heat_output = 0.6 * sizing_demand / 1000
if generation.energy_storage_systems is not None:
for storage in generation.energy_storage_systems:
if storage.type_energy_stored == 'thermal':
storage.volume = building.heating_peak_load[cte.YEAR][0] / (cte.WATER_HEAT_CAPACITY*cte.WATER_DENSITY * 20)
elif generation.system_type == 'Boiler':
generation.nominal_heat_output = 0.4 * sizing_demand / 1000
else:
sizing_demand = peak_cooling_demand
for system in building.energy_systems:
if 'Cooling' in system.demand_types:
for generation in system.generation_systems:
if generation.system_type == 'Heat Pump':
generation.nominal_heat_output = sizing_demand / 1000
def montreal_custom(self):
for building in self.buildings:
energy_systems = building.energy_systems
for energy_system in energy_systems:
demand_types = energy_system.demand_types
generation_systems = energy_system.generation_systems
if cte.HEATING in demand_types:
if len(generation_systems) == 1:
for generation in generation_systems:
generation.nominal_heat_output = building.heating_peak_load[cte.YEAR][0]
else:
for generation in generation_systems:
generation.nominal_heat_output = building.heating_peak_load[cte.YEAR][0] / (len(generation_systems))
elif cte.COOLING in demand_types:
if len(generation_systems) == 1:
for generation in generation_systems:
generation.nominal_cooling_output = building.cooling_peak_load[cte.YEAR][0]
else:
for generation in generation_systems:
generation.nominal_heat_output = building.cooling_peak_load[cte.YEAR][0] / (len(generation_systems))

View File

@ -1,63 +0,0 @@
import glob
import os
import sys
from pathlib import Path
import csv
from hub.imports.geometry_factory import GeometryFactory
from hub.imports.construction_factory import ConstructionFactory
from hub.imports.usage_factory import UsageFactory
from hub.imports.weather_factory import WeatherFactory
from hub.exports.energy_building_exports_factory import EnergyBuildingsExportsFactory
from hub.helpers.dictionaries import Dictionaries
from hub.imports.results_factory import ResultFactory
sys.path.append('./')
try:
file_path = (Path(__file__).parent.parent / 'input_files' / 'eilat.geojson')
out_path = (Path(__file__).parent.parent / 'out_files')
files = glob.glob(f'{out_path}/*')
for file in files:
if file != '.gitignore':
os.remove(file)
print('[simulation start]')
city = GeometryFactory('geojson',
path=file_path,
height_field='heightmax',
year_of_construction_field='ANNEE_CONS',
function_field='CODE_UTILI',
function_to_hub=Dictionaries().eilat_function_to_hub_function).city
print(f'city created from {file_path}')
ConstructionFactory('eilat', city).enrich()
print('enrich constructions... done')
UsageFactory('eilat', city).enrich()
print('enrich usage... done')
WeatherFactory('epw', city).enrich()
print('enrich weather... done')
area = 0
volume = 0
for building in city.buildings:
volume = building.volume
for ground in building.grounds:
area += ground.perimeter_polygon.area
print('exporting:')
_idf = EnergyBuildingsExportsFactory('idf', city, out_path).export()
print(' idf exported...')
_idf.run()
csv_file = str((out_path / f'{city.name}_out.csv').resolve())
eso_file = str((out_path / f'{city.name}_out.eso').resolve())
idf_file = str((out_path / f'{city.name}.idf').resolve())
obj_file = str((out_path / f'{city.name}.obj').resolve())
#ResultFactory('energy_plus_multiple_buildings', city, out_path).enrich()
except Exception as ex:
print(ex)
print('error: ', ex)
print('[simulation abort]')
sys.stdout.flush()

View File

@ -1,66 +0,0 @@
import math
from scripts.radiation_tilted import RadiationTilted
import hub.helpers.constants as cte
from hub.helpers.monthly_values import MonthlyValues
class PVSizingSimulation(RadiationTilted):
def __init__(self, building, solar_angles, tilt_angle, module_height, module_width, ghi):
super().__init__(building, solar_angles, tilt_angle, ghi)
self.module_height = module_height
self.module_width = module_width
self.total_number_of_panels = 0
self.enrich()
def available_space(self):
roof_area = self.building.roofs[0].perimeter_area
maintenance_factor = 0.1
orientation_factor = 0.2
if self.building.function == cte.RESIDENTIAL:
mechanical_equipment_factor = 0.2
else:
mechanical_equipment_factor = 0.3
available_roof = (maintenance_factor + orientation_factor + mechanical_equipment_factor) * roof_area
return available_roof
def inter_row_spacing(self):
winter_solstice = self.df[(self.df['AST'].dt.month == 12) &
(self.df['AST'].dt.day == 21) &
(self.df['AST'].dt.hour == 12)]
solar_altitude = winter_solstice['solar altitude'].values[0]
solar_azimuth = winter_solstice['solar azimuth'].values[0]
distance = ((self.module_height * abs(math.cos(math.radians(solar_azimuth)))) /
math.tan(math.radians(solar_altitude)))
distance = float(format(distance, '.1f'))
return distance
def number_of_panels(self, available_roof, inter_row_distance):
space_dimension = math.sqrt(available_roof)
space_dimension = float(format(space_dimension, '.2f'))
panels_per_row = math.ceil(space_dimension / self.module_width)
number_of_rows = math.ceil(space_dimension / inter_row_distance)
self.total_number_of_panels = panels_per_row * number_of_rows
return panels_per_row, number_of_rows
def pv_output(self):
radiation = self.total_radiation_tilted
pv_module_area = self.module_width * self.module_height
available_roof = self.available_space()
inter_row_spacing = self.inter_row_spacing()
self.number_of_panels(available_roof, inter_row_spacing)
system_efficiency = 0.2
pv_hourly_production = [x * system_efficiency * self.total_number_of_panels * pv_module_area for x in radiation]
self.building.onsite_electrical_production[cte.HOUR] = pv_hourly_production
self.building.onsite_electrical_production[cte.MONTH] = (
MonthlyValues.get_total_month(self.building.onsite_electrical_production[cte.HOUR]))
self.building.onsite_electrical_production[cte.YEAR] = [sum(self.building.onsite_electrical_production[cte.MONTH])]

View File

@ -1,113 +0,0 @@
import pandas as pd
import math
import hub.helpers.constants as cte
from hub.helpers.monthly_values import MonthlyValues
class RadiationTilted:
def __init__(self, building, solar_angles, tilt_angle, ghi, solar_constant=1366.1, maximum_clearness_index=1,
min_cos_zenith=0.065, maximum_zenith_angle=87):
self.building = building
self.ghi = ghi
self.tilt_angle = tilt_angle
self.zeniths = solar_angles['zenith'].tolist()[:-1]
self.incidents = solar_angles['incident angle'].tolist()[:-1]
self.date_time = solar_angles['DateTime'].tolist()[:-1]
self.ast = solar_angles['AST'].tolist()[:-1]
self.solar_azimuth = solar_angles['solar azimuth'].tolist()[:-1]
self.solar_altitude = solar_angles['solar altitude'].tolist()[:-1]
data = {'DateTime': self.date_time, 'AST': self.ast, 'solar altitude': self.solar_altitude, 'zenith': self.zeniths,
'solar azimuth': self.solar_azimuth, 'incident angle': self.incidents, 'ghi': self.ghi}
self.df = pd.DataFrame(data)
self.df['DateTime'] = pd.to_datetime(self.df['DateTime'])
self.df['AST'] = pd.to_datetime(self.df['AST'])
self.df.set_index('DateTime', inplace=True)
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
self.i_on = []
self.i_oh = []
self.k_t = []
self.fraction_diffuse = []
self.diffuse_horizontal = []
self.beam_horizontal = []
self.dni = []
self.beam_tilted = []
self.diffuse_tilted = []
self.total_radiation_tilted = []
self.calculate()
def dni_extra(self):
for i in range(len(self.df)):
self.i_on.append(self.solar_constant * (1 + 0.033 * math.cos(math.radians(360 * self.df.index.dayofyear[i] / 365))))
self.df['extraterrestrial normal radiation (Wh/m2)'] = self.i_on
def clearness_index(self):
for i in range(len(self.df)):
self.i_oh.append(self.i_on[i] * max(math.cos(math.radians(self.zeniths[i])), self.min_cos_zenith))
self.k_t.append(self.ghi[i] / self.i_oh[i])
self.k_t[i] = max(0, self.k_t[i])
self.k_t[i] = min(self.maximum_clearness_index, self.k_t[i])
self.df['extraterrestrial radiation on horizontal (Wh/m2)'] = self.i_oh
self.df['clearness index'] = self.k_t
def diffuse_fraction(self):
for i in range(len(self.df)):
if self.k_t[i] <= 0.22:
self.fraction_diffuse.append(1 - 0.09 * self.k_t[i])
elif self.k_t[i] <= 0.8:
self.fraction_diffuse.append(0.9511 - 0.1604 * self.k_t[i] + 4.388 * self.k_t[i] ** 2 -
16.638 * self.k_t[i] ** 3 + 12.336 * self.k_t[i] ** 4)
else:
self.fraction_diffuse.append(0.165)
if self.zeniths[i] > self.maximum_zenith_angle:
self.fraction_diffuse[i] = 1
self.df['diffuse fraction'] = self.fraction_diffuse
def radiation_components_horizontal(self):
for i in range(len(self.df)):
self.diffuse_horizontal.append(self.ghi[i] * self.fraction_diffuse[i])
self.beam_horizontal.append(self.ghi[i] - self.diffuse_horizontal[i])
self.dni.append((self.ghi[i] - self.diffuse_horizontal[i]) / math.cos(math.radians(self.zeniths[i])))
if self.zeniths[i] > self.maximum_zenith_angle or self.dni[i] < 0:
self.dni[i] = 0
self.df['diffuse horizontal (Wh/m2)'] = self.diffuse_horizontal
self.df['dni (Wh/m2)'] = self.dni
self.df['beam horizontal (Wh/m2)'] = self.beam_horizontal
def radiation_components_tilted(self):
for i in range(len(self.df)):
self.beam_tilted.append(self.dni[i] * math.cos(math.radians(self.incidents[i])))
self.beam_tilted[i] = max(self.beam_tilted[i], 0)
self.diffuse_tilted.append(self.diffuse_horizontal[i] * ((1 + math.cos(math.radians(self.tilt_angle))) / 2))
self.total_radiation_tilted.append(self.beam_tilted[i] + self.diffuse_tilted[i])
self.df['beam tilted (Wh/m2)'] = self.beam_tilted
self.df['diffuse tilted (Wh/m2)'] = self.diffuse_tilted
self.df['total radiation tilted (Wh/m2)'] = self.total_radiation_tilted
def calculate(self) -> pd.DataFrame:
self.dni_extra()
self.clearness_index()
self.diffuse_fraction()
self.radiation_components_horizontal()
self.radiation_components_tilted()
return self.df
def enrich(self):
tilted_radiation = self.total_radiation_tilted
self.building.roofs[0].global_irradiance_tilted[cte.HOUR] = [x * cte.WATTS_HOUR_TO_JULES for x in
tilted_radiation]
self.building.roofs[0].global_irradiance_tilted[cte.HOUR] = [x * cte.WATTS_HOUR_TO_JULES for x in
tilted_radiation]
self.building.roofs[0].global_irradiance_tilted[cte.MONTH] = (
MonthlyValues.get_total_month(self.building.roofs[0].global_irradiance_tilted[cte.HOUR]))
self.building.roofs[0].global_irradiance_tilted[cte.YEAR] = \
[sum(self.building.roofs[0].global_irradiance_tilted[cte.MONTH])]

View File

@ -1,73 +0,0 @@
import subprocess
import datetime
class LatexReport:
def __init__(self, file_name):
self.file_name = file_name
self.content = []
self.content.append(r'\documentclass{article}')
self.content.append(r'\usepackage[margin=2.5cm]{geometry}') # Adjust page margins
self.content.append(r'\usepackage{graphicx}')
self.content.append(r'\usepackage{tabularx}')
self.content.append(r'\begin{document}')
# Get current date and time
current_datetime = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
self.content.append(r'\title{Energy System Analysis Report - ' + current_datetime + r'}')
self.content.append(r'\author{Next-Generation Cities Institute}')
self.content.append(r'\date{}') # Remove the date field, as it's included in the title now
self.content.append(r'\maketitle')
def add_section(self, section_title):
self.content.append(r'\section{' + section_title + r'}')
def add_subsection(self, subsection_title):
self.content.append(r'\subsection{' + subsection_title + r'}')
def add_text(self, text):
self.content.append(text)
def add_table(self, table_data, caption=None, first_column_width=None):
num_columns = len(table_data[0])
total_width = 0.9 # Default total width
if first_column_width is not None:
first_column_width_str = str(first_column_width) + 'cm'
total_width -= first_column_width / 16.0 # Adjust total width for the first column
if caption:
self.content.append(r'\begin{table}[htbp]')
self.content.append(r'\caption{' + caption + r'}')
self.content.append(r'\centering')
self.content.append(r'\begin{tabularx}{\textwidth}{|p{' + first_column_width_str + r'}|' + '|'.join(['X'] * (
num_columns - 1)) + '|}' if first_column_width is not None else r'\begin{tabularx}{\textwidth}{|' + '|'.join(
['X'] * num_columns) + '|}')
self.content.append(r'\hline')
for row in table_data:
self.content.append(' & '.join(row) + r' \\')
self.content.append(r'\hline')
self.content.append(r'\end{tabularx}')
if caption:
self.content.append(r'\end{table}')
def add_image(self, image_path, caption=None):
if caption:
self.content.append(r'\begin{figure}[htbp]')
self.content.append(r'\centering')
self.content.append(r'\includegraphics[width=0.8\textwidth]{' + image_path + r'}')
self.content.append(r'\caption{' + caption + r'}')
self.content.append(r'\end{figure}')
else:
self.content.append(r'\begin{figure}[htbp]')
self.content.append(r'\centering')
self.content.append(r'\includegraphics[width=0.8\textwidth]{' + image_path + r'}')
self.content.append(r'\end{figure}')
def save_report(self):
self.content.append(r'\end{document}') # Add this line to close the document
with open(self.file_name, 'w') as f:
f.write('\n'.join(self.content))
def compile_to_pdf(self):
subprocess.run(['pdflatex', self.file_name])

View File

@ -1,146 +0,0 @@
import math
import pandas as pd
from datetime import datetime
from pathlib import Path
class CitySolarAngles:
def __init__(self, file_name, location_latitude, location_longitude, tilt_angle, surface_azimuth_angle,
standard_meridian=-75):
self.file_name = file_name
self.location_latitude = location_latitude
self.location_longitude = location_longitude
self.location_latitude_rad = math.radians(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 = (location_longitude - standard_meridian) * 4
self.timezone = 'Etc/GMT+5'
self.eot = []
self.ast = []
self.hour_angles = []
self.declinations = []
self.solar_altitudes = []
self.solar_azimuths = []
self.zeniths = []
self.incidents = []
self.beam_tilted = []
self.factor = []
self.times = pd.date_range(start='2023-01-01', end='2024-01-01', freq='H', tz=self.timezone)
self.df = pd.DataFrame(index=self.times)
self.day_of_year = self.df.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)
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
@property
def calculate(self) -> pd.DataFrame:
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)
incident_angle_radian = self.incident_angle(solar_altitude_radians, solar_azimuth_radians)
self.df['DateTime'] = self.times
self.df['AST'] = self.ast
self.df['hour angle'] = self.hour_angles
self.df['eot'] = self.eot
self.df['declination angle'] = self.declinations
self.df['solar altitude'] = self.solar_altitudes
self.df['zenith'] = self.zeniths
self.df['solar azimuth'] = self.solar_azimuths
self.df['incident angle'] = self.incidents
return self.df

View File

@ -1,135 +0,0 @@
import csv
import math
from typing import List
from pathlib import Path
import hub.helpers.constants as cte
from hub.helpers.monthly_values import MonthlyValues
class SystemSimulation:
def __init__(self, building, out_path):
self.building = building
self.energy_systems = building.energy_systems
self.heating_demand = [0] + building.heating_demand[cte.HOUR]
self.cooling_demand = building.cooling_demand
self.dhw_demand = building.domestic_hot_water_heat_demand
self.T_out = building.external_temperature[cte.HOUR]
self.maximum_heating_demand = building.heating_peak_load[cte.YEAR][0]
self.maximum_cooling_demand = building.cooling_peak_load[cte.YEAR][0]
self.name = building.name
self.energy_system_archetype = building.energy_systems_archetype_name
self.out_path = out_path
def archetype1(self):
out_path = self.out_path
T, T_sup, T_ret, m_ch, m_dis, q_hp, q_aux = [0] * len(self.heating_demand), [0] * len(
self.heating_demand), [0] * len(self.heating_demand), [0] * len(self.heating_demand), [0] * len(
self.heating_demand), [0] * len(self.heating_demand), [0] * len(self.heating_demand)
hp_electricity: List[float] = [0.0] * len(self.heating_demand)
aux_fuel: List[float] = [0.0] * len(self.heating_demand)
heating_consumption: List[float] = [0.0] * len(self.heating_demand)
boiler_consumption: List[float] = [0.0] * len(self.heating_demand)
T[0], dt = 25, 3600 # Assuming dt is defined somewhere
ua, v, hp_cap, hp_efficiency, boiler_efficiency = 0, 0, 0, 0, 0
for energy_system in self.energy_systems:
if cte.ELECTRICITY not in energy_system.demand_types:
generation_systems = energy_system.generation_systems
for generation_system in generation_systems:
if generation_system.system_type == cte.HEAT_PUMP and cte.HEATING in energy_system.demand_types:
hp_cap = generation_system.nominal_heat_output
hp_efficiency = float(generation_system.heat_efficiency)
for storage in generation_system.energy_storage_systems:
if storage.type_energy_stored == 'thermal':
v, h = float(storage.volume), float(storage.height)
r_tot = sum(float(layer.thickness) / float(layer.material.conductivity) for layer in
storage.layers)
u_tot = 1 / r_tot
d = math.sqrt((4 * v) / (math.pi * h))
a_side = math.pi * d * h
a_top = math.pi * d ** 2 / 4
ua = u_tot * (2 * a_top + a_side)
elif generation_system.system_type == cte.BOILER:
boiler_cap = generation_system.nominal_heat_output
boiler_efficiency = float(generation_system.heat_efficiency)
for i in range(len(self.heating_demand) - 1):
T[i + 1] = T[i] + ((m_ch[i] * (T_sup[i] - T[i])) + (
ua * (self.T_out[i] - T[i])) / cte.WATER_HEAT_CAPACITY - m_dis[i] * (T[i] - T_ret[i])) * (dt / (cte.WATER_DENSITY * v))
if T[i + 1] < 35:
q_hp[i + 1] = hp_cap * 1000
m_ch[i + 1] = q_hp[i + 1] / (cte.WATER_HEAT_CAPACITY * 7)
T_sup[i + 1] = (q_hp[i + 1] / (m_ch[i + 1] * cte.WATER_HEAT_CAPACITY)) + T[i + 1]
elif 35 <= T[i + 1] < 45 and q_hp[i] == 0:
q_hp[i + 1] = 0
m_ch[i + 1] = 0
T_sup[i + 1] = T[i + 1]
elif 35 <= T[i + 1] < 45 and q_hp[i] > 0:
q_hp[i + 1] = hp_cap * 1000
m_ch[i + 1] = q_hp[i + 1] / (cte.WATER_HEAT_CAPACITY * 3)
T_sup[i + 1] = (q_hp[i + 1] / (m_ch[i + 1] * cte.WATER_HEAT_CAPACITY)) + T[i + 1]
else:
q_hp[i + 1], m_ch[i + 1], T_sup[i + 1] = 0, 0, T[i + 1]
hp_electricity[i + 1] = q_hp[i + 1] / hp_efficiency
if self.heating_demand[i + 1] == 0:
m_dis[i + 1], t_return, T_ret[i + 1] = 0, T[i + 1], T[i + 1]
else:
if self.heating_demand[i + 1] > 0.5 * self.maximum_heating_demand:
factor = 8
else:
factor = 4
m_dis[i + 1] = self.maximum_heating_demand / (cte.WATER_HEAT_CAPACITY * factor * 3600)
t_return = T[i + 1] - self.heating_demand[i + 1] / (m_dis[i + 1] * cte.WATER_HEAT_CAPACITY * 3600)
if m_dis[i + 1] == 0 or (m_dis[i + 1] > 0 and t_return < 25):
T_ret[i + 1] = max(25, T[i + 1])
else:
T_ret[i + 1] = T[i + 1] - self.heating_demand[i + 1] / (m_dis[i + 1] * cte.WATER_HEAT_CAPACITY * 3600)
tes_output = m_dis[i + 1] * cte.WATER_HEAT_CAPACITY * (T[i + 1] - T_ret[i + 1])
if tes_output < (self.heating_demand[i + 1] / 3600):
q_aux[i + 1] = (self.heating_demand[i + 1] / 3600) - tes_output
aux_fuel[i + 1] = (q_aux[i + 1] * dt) / 35.8e6
boiler_consumption[i + 1] = q_aux[i + 1] / boiler_efficiency
heating_consumption[i + 1] = boiler_consumption[i + 1] + hp_electricity[i + 1]
data = list(zip(T, T_sup, T_ret, m_ch, m_dis, q_hp, hp_electricity, aux_fuel, q_aux, self.heating_demand))
file_name = f'simulation_results_{self.name}.csv'
with open(out_path / file_name, 'w', newline='') as csvfile:
output_file = csv.writer(csvfile)
# Write header
output_file.writerow(['T', 'T_sup', 'T_ret', 'm_ch', 'm_dis', 'q_hp', 'hp_electricity', 'aux_fuel', 'q_aux', 'heating_demand'])
# Write data
output_file.writerows(data)
return heating_consumption, hp_electricity, boiler_consumption, T_sup
def enrich(self):
if self.energy_system_archetype == 'PV+ASHP+GasBoiler+TES' or 'PV+4Pipe+DHW':
building_new_heating_consumption, building_heating_electricity_consumption, building_heating_gas_consumption, supply_temperature = (
self.archetype1())
self.building.heating_consumption[cte.HOUR] = building_new_heating_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])]
disaggregated_consumption = {}
for energy_system in self.building.energy_systems:
if cte.HEATING in energy_system.demand_types:
for generation_system in energy_system.generation_systems:
if generation_system.system_type == cte.HEAT_PUMP:
generation_system.heat_supply_temperature = supply_temperature
disaggregated_consumption[generation_system.fuel_type] = {}
if generation_system.fuel_type == cte.ELECTRICITY:
disaggregated_consumption[generation_system.fuel_type][
cte.HOUR] = building_heating_electricity_consumption
disaggregated_consumption[generation_system.fuel_type][cte.MONTH] = MonthlyValues.get_total_month(
disaggregated_consumption[generation_system.fuel_type][cte.HOUR])
disaggregated_consumption[generation_system.fuel_type][cte.YEAR] = [
sum(disaggregated_consumption[generation_system.fuel_type][cte.MONTH])]
else:
disaggregated_consumption[generation_system.fuel_type][cte.HOUR] = building_heating_gas_consumption
disaggregated_consumption[generation_system.fuel_type][cte.MONTH] = MonthlyValues.get_total_month(
disaggregated_consumption[generation_system.fuel_type][cte.HOUR])
disaggregated_consumption[generation_system.fuel_type][cte.YEAR] = [
sum(disaggregated_consumption[generation_system.fuel_type][cte.MONTH])]
self.building.heating_fuel_consumption_disaggregated = disaggregated_consumption
return self.building

Some files were not shown because too many files have changed in this diff Show More