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
This commit is contained in:
Saeed Ranjbar 2024-07-18 08:48:43 -04:00
commit a717f9a644
21 changed files with 1854 additions and 161 deletions

View File

@ -1,4 +1,3 @@
from scripts.geojson_creator import process_geojson
from pathlib import Path
import subprocess
from scripts.ep_run_enrich import energy_plus_workflow
@ -8,57 +7,92 @@ 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.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.energy_system_retrofit_results import system_results, new_system_results
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
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
# Specify the GeoJSON file path
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.0001)
file_path = (Path(__file__).parent / 'input_files' / 'output_buildings.geojson')
# Specify the output path for the PDF file
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)
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()
pv_feasibility(-73.5681295982132, 45.49218262677643, 0.0001, selected_buildings=city.buildings)
energy_plus_workflow(city, energy_plus_output_path)
solar_angles = CitySolarAngles(city.name,
city.latitude,
city.longitude,
tilt_angle=45,
surface_azimuth_angle=180).calculate
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)
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()
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)
if 'PV' in building.energy_systems_archetype_name:
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()
if building.energy_systems_archetype_name == 'PV+4Pipe+DHW':
EnergySystemsSimulationFactory('archetype13', building=building, output_path=simulation_results_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

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

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

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

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

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

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

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

@ -0,0 +1,863 @@
{
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"geometry": {
"type": "Polygon",
"coordinates": [
[
[
-73.56769087843276,
45.49251875903776
],
[
-73.56765050367694,
45.492560280202284
],
[
-73.5677794213865,
45.49262188364245
],
[
-73.56781916241786,
45.49258006136105
],
[
-73.56769087843276,
45.49251875903776
]
]
]
},
"id": 173347,
"properties": {
"name": "01044617",
"address": "rue Victor-Hugo (MTL) 1666",
"function": "1000",
"height": 9,
"year_of_construction": 1986
}
},
{
"type": "Feature",
"geometry": {
"type": "Polygon",
"coordinates": [
[
[
-73.56765050367694,
45.492560280202284
],
[
-73.56761436875776,
45.49259744179384
],
[
-73.5676075694645,
45.49260454199484
],
[
-73.56773226889548,
45.49266394156485
],
[
-73.56773726906921,
45.49266624130272
],
[
-73.5677794213865,
45.49262188364245
],
[
-73.56765050367694,
45.492560280202284
]
]
]
},
"id": 173348,
"properties": {
"name": "01044619",
"address": "rue Victor-Hugo (MTL) 1670",
"function": "1000",
"height": 9,
"year_of_construction": 1986
}
},
{
"type": "Feature",
"geometry": {
"type": "Polygon",
"coordinates": [
[
[
-73.56829026835214,
45.492524742569145
],
[
-73.56849646900322,
45.49262354174874
],
[
-73.56861067001111,
45.492505541343576
],
[
-73.56864076915663,
45.492519941474434
],
[
-73.56866246900178,
45.49249754209202
],
[
-73.56867696946317,
45.49250454136644
],
[
-73.56867726964143,
45.49250414255471
],
[
-73.56881486931461,
45.492362042624144
],
[
-73.56881686903772,
45.492359941181455
],
[
-73.5688004699483,
45.49235084193039
],
[
-73.56882097012145,
45.4923320417195
],
[
-73.56879846891101,
45.49232034109352
],
[
-73.56883736970825,
45.492284841271946
],
[
-73.56886806888434,
45.492256240993704
],
[
-73.56885337003277,
45.49224914198001
],
[
-73.56890226932418,
45.49219894164121
],
[
-73.56851866897392,
45.49201434154299
],
[
-73.56837326884313,
45.492163841620254
],
[
-73.56864696910176,
45.49229554163243
],
[
-73.5685268682051,
45.49241904187041
],
[
-73.56825396962694,
45.49228824183907
],
[
-73.56810906858335,
45.49243794104013
],
[
-73.56829026835214,
45.492524742569145
]
]
]
},
"id": 173403,
"properties": {
"name": "01044334",
"address": "rue Saint-Jacques (MTL) 1460",
"function": "1000",
"height": 15,
"year_of_construction": 1985
}
},
{
"type": "Feature",
"geometry": {
"type": "Polygon",
"coordinates": [
[
[
-73.5683896684674,
45.491800342137736
],
[
-73.56838616878639,
45.49180414157881
],
[
-73.56850686988925,
45.49185994152571
],
[
-73.56851286844197,
45.4918626410622
],
[
-73.56855549071014,
45.49181750806087
],
[
-73.56842962331187,
45.49175738300567
],
[
-73.5683896684674,
45.491800342137736
]
]
]
},
"id": 174898,
"properties": {
"name": "01044590",
"address": "rue Victor-Hugo (MTL) 1600",
"function": "1000",
"height": 9,
"year_of_construction": 1986
}
},
{
"type": "Feature",
"geometry": {
"type": "Polygon",
"coordinates": [
[
[
-73.5680637695714,
45.49212884162544
],
[
-73.56802228176146,
45.49217205619571
],
[
-73.56815668696326,
45.49223626189717
],
[
-73.56815766959974,
45.49223524178655
],
[
-73.56818746886172,
45.49224944155107
],
[
-73.56822816806918,
45.49220694186927
],
[
-73.5680637695714,
45.49212884162544
]
]
]
},
"id": 175785,
"properties": {
"name": "01044602",
"address": "rue Victor-Hugo (MTL) 1630",
"function": "1000",
"height": 12,
"year_of_construction": 1986
}
},
{
"type": "Feature",
"geometry": {
"type": "Polygon",
"coordinates": [
[
[
-73.56850793693103,
45.49167318076048
],
[
-73.56846877951091,
45.4917152818903
],
[
-73.56859506290321,
45.491775605518725
],
[
-73.56863463503653,
45.491733702062774
],
[
-73.56850793693103,
45.49167318076048
]
]
]
},
"id": 175910,
"properties": {
"name": "01044586",
"address": "rue Victor-Hugo (MTL) 1590",
"function": "1000",
"height": 9,
"year_of_construction": 1986
}
},
{
"type": "Feature",
"geometry": {
"type": "Polygon",
"coordinates": [
[
[
-73.56817543449134,
45.49201384773851
],
[
-73.56813497596143,
45.49205532773507
],
[
-73.56826745951075,
45.492118613912375
],
[
-73.56830763251781,
45.49207699906335
],
[
-73.56817543449134,
45.49201384773851
]
]
]
},
"id": 176056,
"properties": {
"name": "01044599",
"address": "rue Victor-Hugo (MTL) 1620",
"function": "1000",
"height": 8,
"year_of_construction": 1986
}
},
{
"type": "Feature",
"geometry": {
"type": "Polygon",
"coordinates": [
[
[
-73.56772876855176,
45.49247194194522
],
[
-73.56773406949068,
45.492474341387755
],
[
-73.56773125185198,
45.492477239659124
],
[
-73.56785890467093,
45.492538239964624
],
[
-73.56789966910456,
45.49249534173201
],
[
-73.56776616865103,
45.49243264153464
],
[
-73.56772876855176,
45.49247194194522
]
]
]
},
"id": 176261,
"properties": {
"name": "01044613",
"address": "rue Victor-Hugo (MTL) 1656",
"function": "1000",
"height": 10,
"year_of_construction": 1986
}
},
{
"type": "Feature",
"geometry": {
"type": "Polygon",
"coordinates": [
[
[
-73.56802228176146,
45.49217205619571
],
[
-73.56798225825526,
45.492213743742184
],
[
-73.56811660206223,
45.49227791893211
],
[
-73.56815668696326,
45.49223626189717
],
[
-73.56802228176146,
45.49217205619571
]
]
]
},
"id": 176293,
"properties": {
"name": "01044604",
"address": "rue Victor-Hugo (MTL) 1636",
"function": "1000",
"height": 12,
"year_of_construction": 1986
}
},
{
"type": "Feature",
"geometry": {
"type": "Polygon",
"coordinates": [
[
[
-73.56790222258577,
45.49229712328457
],
[
-73.56785996900595,
45.49234104192853
],
[
-73.56799446861396,
45.49240484193282
],
[
-73.56803643080562,
45.49236123475947
],
[
-73.56790222258577,
45.49229712328457
]
]
]
},
"id": 176296,
"properties": {
"name": "01044611",
"address": "rue Victor-Hugo (MTL) 1650",
"function": "1000",
"height": 10,
"year_of_construction": 1986
}
},
{
"type": "Feature",
"geometry": {
"type": "Polygon",
"coordinates": [
[
[
-73.56798225825526,
45.492213743742184
],
[
-73.56794223597048,
45.4922554321734
],
[
-73.56807651582375,
45.49231957685336
],
[
-73.56811660206223,
45.49227791893211
],
[
-73.56798225825526,
45.492213743742184
]
]
]
},
"id": 176298,
"properties": {
"name": "01044607",
"address": "rue Victor-Hugo (MTL) 1640",
"function": "1000",
"height": 12,
"year_of_construction": 1986
}
},
{
"type": "Feature",
"geometry": {
"type": "Polygon",
"coordinates": [
[
[
-73.56742736898599,
45.49184704208998
],
[
-73.56761256873325,
45.491896142437554
],
[
-73.56766926915839,
45.4917902412014
],
[
-73.56766956853903,
45.49179024192391
],
[
-73.56792966911675,
45.49183254222432
],
[
-73.56793006788594,
45.491831141828406
],
[
-73.56794526884076,
45.49174634219527
],
[
-73.56794516904765,
45.49174634225465
],
[
-73.56753896905731,
45.491638642248425
],
[
-73.56742736898599,
45.49184704208998
]
]
]
},
"id": 176918,
"properties": {
"name": "01097185",
"address": "rue Victor-Hugo (MTL) 1591",
"function": "1000",
"height": 10,
"year_of_construction": 1987
}
},
{
"type": "Feature",
"geometry": {
"type": "Polygon",
"coordinates": [
[
[
-73.56773125185198,
45.492477239659124
],
[
-73.56769087843276,
45.49251875903776
],
[
-73.56781916241786,
45.49258006136105
],
[
-73.56785890467093,
45.492538239964624
],
[
-73.56773125185198,
45.492477239659124
]
]
]
},
"id": 178164,
"properties": {
"name": "01044615",
"address": "rue Victor-Hugo (MTL) 1660",
"function": "1000",
"height": 9,
"year_of_construction": 1986
}
},
{
"type": "Feature",
"geometry": {
"type": "Polygon",
"coordinates": [
[
[
-73.56846877951091,
45.4917152818903
],
[
-73.56842962331187,
45.49175738300567
],
[
-73.56855549071014,
45.49181750806087
],
[
-73.56859506290321,
45.491775605518725
],
[
-73.56846877951091,
45.4917152818903
]
]
]
},
"id": 179679,
"properties": {
"name": "01044588",
"address": "rue Victor-Hugo (MTL) 1596",
"function": "1000",
"height": 9,
"year_of_construction": 1986
}
},
{
"type": "Feature",
"geometry": {
"type": "Polygon",
"coordinates": [
[
[
-73.56825635009473,
45.49193088860213
],
[
-73.56821589168355,
45.491972368627906
],
[
-73.5683477837006,
45.4920353716151
],
[
-73.56838787594006,
45.49199371809223
],
[
-73.56825635009473,
45.49193088860213
]
]
]
},
"id": 179789,
"properties": {
"name": "01044595",
"address": "rue Victor-Hugo (MTL) 1610",
"function": "1000",
"height": 8,
"year_of_construction": 1986
}
},
{
"type": "Feature",
"geometry": {
"type": "Polygon",
"coordinates": [
[
[
-73.56821589168355,
45.491972368627906
],
[
-73.56817543449134,
45.49201384773851
],
[
-73.56830763251781,
45.49207699906335
],
[
-73.5683477837006,
45.4920353716151
],
[
-73.56821589168355,
45.491972368627906
]
]
]
},
"id": 181310,
"properties": {
"name": "01044597",
"address": "rue Victor-Hugo (MTL) 1616",
"function": "1000",
"height": 8,
"year_of_construction": 1986
}
},
{
"type": "Feature",
"geometry": {
"type": "Polygon",
"coordinates": [
[
[
-73.56809506939487,
45.49209624228538
],
[
-73.56809246893268,
45.4920988416879
],
[
-73.56821287000538,
45.49216124158406
],
[
-73.56822186852654,
45.49216584161625
],
[
-73.56826745951075,
45.492118613912375
],
[
-73.56813497596143,
45.49205532773507
],
[
-73.56809506939487,
45.49209624228538
]
]
]
},
"id": 182393,
"properties": {
"name": "01044601",
"address": "rue Victor-Hugo (MTL) 1626",
"function": "1000",
"height": 8,
"year_of_construction": 1986
}
},
{
"type": "Feature",
"geometry": {
"type": "Polygon",
"coordinates": [
[
[
-73.56790756893894,
45.492291541967774
],
[
-73.56790222258577,
45.49229712328457
],
[
-73.56803643080562,
45.49236123475947
],
[
-73.56807651582375,
45.49231957685336
],
[
-73.56794223597048,
45.4922554321734
],
[
-73.56790756893894,
45.492291541967774
]
]
]
},
"id": 182442,
"properties": {
"name": "01044609",
"address": "rue Victor-Hugo (MTL) 1646",
"function": "1000",
"height": 11,
"year_of_construction": 1986
}
},
{
"type": "Feature",
"geometry": {
"type": "Polygon",
"coordinates": [
[
[
-73.56829706912258,
45.49188914205178
],
[
-73.56825635009473,
45.49193088860213
],
[
-73.56838787594006,
45.49199371809223
],
[
-73.56842846901456,
45.49195154234486
],
[
-73.56829706912258,
45.49188914205178
]
]
]
},
"id": 182546,
"properties": {
"name": "01044592",
"address": "rue Victor-Hugo (MTL) 1606",
"function": "1000",
"height": 8,
"year_of_construction": 1986
}
}
]
}

View File

@ -65,4 +65,4 @@ for building in city.buildings:
costs.loc['global_capital_costs', f'Scenario {SYSTEM_RETROFIT}'].to_csv(
output_path / f'{building.name}_cc.csv')
costs.loc['global_maintenance_costs', f'Scenario {SYSTEM_RETROFIT}'].to_csv(
output_path / f'{building.name}_m.csv')
output_path / f'{building.name}_m.csv')

View File

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

@ -36,11 +36,10 @@ class TotalOperationalIncomes(CostBase):
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 / cte.WATTS_HOUR_TO_JULES) * price_export * price_increase_electricity
)
self._yearly_operational_incomes.fillna(0, inplace=True)
return self._yearly_operational_incomes
return self._yearly_operational_incomes

View File

@ -0,0 +1,595 @@
import os
import hub.helpers.constants as cte
import matplotlib.pyplot as plt
from matplotlib import cm
from scripts.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_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(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
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_lcc_components_sqm = {
'Capital Cost': current_status_capex / len(self.city.buildings),
'Operational Cost': current_status_opex / 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 / 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']
current_values = list(current_status_lcc_components_sqm.values())
retrofitted_values = list(retrofitted_lcc_components_sqm.values())
colors = ['#2196f3', '#ff5a5f', '#4caf50', '#ffc107']
# 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(2)
for i, (category, color) in enumerate(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

@ -1,68 +1,176 @@
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
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:
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
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 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
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_demand[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

@ -9,10 +9,10 @@ from hub.imports.results_factory import ResultFactory
sys.path.append('./')
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, 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()
if not expansion:
output_file = Path('./input_files/output_buildings.geojson').resolve()
else:
output_file = Path('./input_files/output_buildings_expanded.geojson').resolve()
buildings_in_region = []
with open(geojson_file, 'r') as file:

37
scripts/pv_feasibility.py Normal file
View File

@ -0,0 +1,37 @@
from pathlib import Path
import subprocess
from hub.imports.geometry_factory import GeometryFactory
from scripts.geojson_creator import process_geojson
from hub.helpers.dictionaries import Dictionaries
from hub.imports.weather_factory import WeatherFactory
from hub.imports.results_factory import ResultFactory
from hub.exports.exports_factory import ExportsFactory
def pv_feasibility(current_x, current_y, current_diff, selected_buildings):
input_files_path = (Path(__file__).parent.parent / 'input_files')
output_path = (Path(__file__).parent.parent / 'out_files').resolve()
sra_output_path = output_path / 'sra_outputs' / 'extended_city_sra_outputs'
sra_output_path.mkdir(parents=True, exist_ok=True)
new_diff = current_diff * 5
geojson_file = process_geojson(x=current_x, y=current_y, diff=new_diff, expansion=True)
file_path = input_files_path / 'output_buildings.geojson'
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
WeatherFactory('epw', city).enrich()
ExportsFactory('sra', city, sra_output_path).export()
sra_path = (sra_output_path / f'{city.name}_sra.xml').resolve()
subprocess.run(['sra', str(sra_path)])
ResultFactory('sra', city, sra_output_path).enrich()
for selected_building in selected_buildings:
for building in city.buildings:
if selected_building.name == building.name:
selected_building.roofs[0].global_irradiance = building.roofs[0].global_irradiance

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': 44,
'system 1 electricity': 6,
'system 2 gas': 0,
'system 2 electricity': 0,
'system 3 and 4 gas': 0,
@ -25,8 +25,8 @@ 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': 44,
'system 8 electricity': 6}
residential_new_systems_percentage = {'PV+ASHP+GasBoiler+TES': 0,
'PV+4Pipe+DHW': 100,

View File

@ -1,73 +1,119 @@
import subprocess
import datetime
import os
from pathlib import Path
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 __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'}')
self.content.append(r'\section{' + section_title + r'}')
def add_subsection(self, subsection_title):
self.content.append(r'\subsection{' + subsection_title + r'}')
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)
self.content.append(text)
def add_table(self, table_data, caption=None, first_column_width=None):
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 # Default total width
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 # Adjust total width for the first column
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')
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) + '|}')
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')
for row in table_data:
self.content.append(' & '.join(row) + r' \\')
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):
def add_image(self, image_path, caption=None, placement='ht'):
if caption:
self.content.append(r'\begin{figure}[htbp]')
self.content.append(r'\begin{figure}[' + placement + r']')
self.content.append(r'\centering')
self.content.append(r'\includegraphics[width=0.8\textwidth]{' + image_path + r'}')
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}[htbp]')
self.content.append(r'\begin{figure}[' + placement + r']')
self.content.append(r'\centering')
self.content.append(r'\includegraphics[width=0.8\textwidth]{' + image_path + r'}')
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}') # Add this line to close the document
with open(self.file_name, 'w') as f:
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', self.file_name])
subprocess.run(['pdflatex', '-output-directory', str(self.output_path), str(self.file_path)])

View File

@ -17,8 +17,8 @@ class Archetype13:
self._heating_peak_load = building.heating_peak_load[cte.YEAR][0]
self._cooling_peak_load = building.cooling_peak_load[cte.YEAR][0]
self._domestic_hot_water_peak_load = building.domestic_hot_water_peak_load[cte.YEAR][0]
self._hourly_heating_demand = [demand / cte.HOUR_TO_SECONDS for demand in building.heating_demand[cte.HOUR]]
self._hourly_cooling_demand = [demand / cte.HOUR_TO_SECONDS for demand in building.cooling_demand[cte.HOUR]]
self._hourly_heating_demand = [demand / cte.WATTS_HOUR_TO_JULES for demand in building.heating_demand[cte.HOUR]]
self._hourly_cooling_demand = [demand / cte.WATTS_HOUR_TO_JULES for demand in building.cooling_demand[cte.HOUR]]
self._hourly_dhw_demand = [demand / cte.WATTS_HOUR_TO_JULES for demand in
building.domestic_hot_water_heat_demand[cte.HOUR]]
self._output_path = output_path
@ -125,11 +125,11 @@ class Archetype13:
m_dis[i + 1] = 0
t_ret[i + 1] = t_tank[i + 1]
else:
if demand[i + 1] > 0.5 * self._heating_peak_load / cte.HOUR_TO_SECONDS:
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 * cte.HOUR_TO_SECONDS)
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)
tes.temperature = []
hp_electricity_j = [(x * cte.WATTS_HOUR_TO_JULES) / number_of_ts for x in hp_electricity]
@ -191,11 +191,11 @@ class Archetype13:
for i in range(1, len(demand)):
if demand[i] > 0:
m[i] = self._cooling_peak_load / (cte.WATER_HEAT_CAPACITY * 5 * cte.HOUR_TO_SECONDS)
m[i] = hp.nominal_cooling_output / (cte.WATER_HEAT_CAPACITY * 5)
if t_ret[i - 1] >= 13:
if demand[i] < 0.25 * self._cooling_peak_load / cte.HOUR_TO_SECONDS:
if demand[i] < 0.25 * self._cooling_peak_load:
q_hp[i] = 0.25 * hp.nominal_cooling_output
elif demand[i] < 0.5 * self._cooling_peak_load / cte.HOUR_TO_SECONDS:
elif demand[i] < 0.5 * self._cooling_peak_load:
q_hp[i] = 0.5 * hp.nominal_cooling_output
else:
q_hp[i] = hp.nominal_cooling_output
@ -210,7 +210,7 @@ class Archetype13:
else:
m[i] = 0
q_hp[i] = 0
t_sup_hp[i] = t_ret[i -1]
t_sup_hp[i] = t_ret[i - 1]
t_ret[i] = t_ret[i - 1]
t_sup_hp_fahrenheit = 1.8 * t_sup_hp[i] + 32
t_out_fahrenheit = 1.8 * t_out[i] + 32
@ -221,7 +221,7 @@ class Archetype13:
eer_curve_coefficients[3] * t_out_fahrenheit +
eer_curve_coefficients[4] * t_out_fahrenheit ** 2 +
eer_curve_coefficients[5] * t_sup_hp_fahrenheit * t_out_fahrenheit)
hp_electricity[i] = q_hp[i] / hp_eer[i]
hp_electricity[i] = q_hp[i] / cooling_efficiency
else:
hp_eer[i] = 0
hp_electricity[i] = 0
@ -377,8 +377,8 @@ class Archetype13:
self._building.domestic_hot_water_consumption[cte.HOUR] = dhw_consumption
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]))
self._building.domestic_hot_water_consumption[cte.YEAR] = [
sum(self._building.domestic_hot_water_consumption[cte.MONTH])]
file_name = f'energy_system_simulation_results_{self._name}.csv'
with open(self._output_path / file_name, 'w', newline='') as csvfile:
output_file = csv.writer(csvfile)