diff --git a/hub/catalog_factories/data_models/energy_systems/emission_system.py b/hub/catalog_factories/data_models/energy_systems/emission_system.py index a8ac91b6..538954d3 100644 --- a/hub/catalog_factories/data_models/energy_systems/emission_system.py +++ b/hub/catalog_factories/data_models/energy_systems/emission_system.py @@ -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 diff --git a/hub/catalog_factories/energy_systems/montreal_custom_catalog.py b/hub/catalog_factories/energy_systems/montreal_custom_catalog.py index d3e37e36..cace9278 100644 --- a/hub/catalog_factories/energy_systems/montreal_custom_catalog.py +++ b/hub/catalog_factories/energy_systems/montreal_custom_catalog.py @@ -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 diff --git a/hub/catalog_factories/energy_systems/montreal_future_system_catalogue.py b/hub/catalog_factories/energy_systems/montreal_future_system_catalogue.py index 625e362c..a4477ba2 100644 --- a/hub/catalog_factories/energy_systems/montreal_future_system_catalogue.py +++ b/hub/catalog_factories/energy_systems/montreal_future_system_catalogue.py @@ -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, diff --git a/hub/city_model_structure/energy_systems/emission_system.py b/hub/city_model_structure/energy_systems/emission_system.py index 32bf7c17..e8773013 100644 --- a/hub/city_model_structure/energy_systems/emission_system.py +++ b/hub/city_model_structure/energy_systems/emission_system.py @@ -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): diff --git a/hub/data/energy_systems/schemas/PV+4Pipe+DHW.jpg b/hub/data/energy_systems/schemas/PV+4Pipe+DHW.jpg new file mode 100644 index 00000000..7daad987 Binary files /dev/null and b/hub/data/energy_systems/schemas/PV+4Pipe+DHW.jpg differ diff --git a/hub/imports/energy_systems/montreal_custom_energy_system_parameters.py b/hub/imports/energy_systems/montreal_custom_energy_system_parameters.py index 15833c9b..eb1b9e92 100644 --- a/hub/imports/energy_systems/montreal_custom_energy_system_parameters.py +++ b/hub/imports/energy_systems/montreal_custom_energy_system_parameters.py @@ -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 diff --git a/hub/imports/energy_systems/montreal_future_energy_systems_parameters.py b/hub/imports/energy_systems/montreal_future_energy_systems_parameters.py index 1bcde834..b5628d9f 100644 --- a/hub/imports/energy_systems/montreal_future_energy_systems_parameters.py +++ b/hub/imports/energy_systems/montreal_future_energy_systems_parameters.py @@ -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 diff --git a/input_files/output_buildings_expanded.geojson b/input_files/output_buildings_expanded.geojson new file mode 100644 index 00000000..43fd4d3f --- /dev/null +++ b/input_files/output_buildings_expanded.geojson @@ -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 + } + } + ] +} \ No newline at end of file diff --git a/report_test.py b/report_test.py index 638b9213..aa67926b 100644 --- a/report_test.py +++ b/report_test.py @@ -12,17 +12,22 @@ 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 from scripts.energy_system_sizing_and_simulation_factory import EnergySystemsSimulationFactory from scripts.costs.cost import Cost from scripts.costs.constants import SKIN_RETROFIT_AND_SYSTEM_RETROFIT_AND_PV, SYSTEM_RETROFIT_AND_PV import hub.helpers.constants as cte from hub.exports.exports_factory import ExportsFactory +from scripts.pv_feasibility import pv_feasibility geojson_file = process_geojson(x=-73.5681295982132, y=45.49218262677643, diff=0.0001) file_path = (Path(__file__).parent / 'input_files' / 'output_buildings.geojson') output_path = (Path(__file__).parent / 'out_files').resolve() -city = GeometryFactory('geojson', +simulation_results_path = (Path(__file__).parent / 'out_files' / 'simulation_results').resolve() +simulation_results_path.mkdir(parents=True, exist_ok=True) +city = GeometryFactory(file_type='geojson', path=file_path, height_field='height', year_of_construction_field='year_of_construction', @@ -35,7 +40,32 @@ 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() +pv_feasibility(-73.5681295982132, 45.49218262677643, 0.0001, selected_buildings=city.buildings) energy_plus_workflow(city) +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_status_energy_consumption = consumption_data(city) +random_assignation.call_random(city.buildings, random_assignation.residential_new_systems_percentage) +EnergySystemsFactory('montreal_future', city).enrich() +for building in city.buildings: + 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) +(EnergySystemRetrofitReport(city, output_path, 'PV Implementation and System Retrofit', + current_status_energy_consumption, retrofitted_energy_consumption).create_report()) -(EnergySystemRetrofitReport(city, output_path, 'PV Implementation and HVAC Retrofit'). - create_report(current_system=None, new_system=None)) diff --git a/scripts/energy_system_retrofit_report.py b/scripts/energy_system_retrofit_report.py index 6209c5bc..60e82ece 100644 --- a/scripts/energy_system_retrofit_report.py +++ b/scripts/energy_system_retrofit_report.py @@ -9,24 +9,36 @@ import matplotlib as mpl 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): + def __init__(self, city, output_path, retrofit_scenario, current_status_energy_consumption_data, + retrofitted_energy_consumption_data): self.city = city + self.current_status_data = current_status_energy_consumption_data + self.retrofitted_data = retrofitted_energy_consumption_data self.output_path = output_path self.content = [] + self.retrofit_scenario = retrofit_scenario self.report = LatexReport('energy_system_retrofit_report', - 'Energy System Retrofit Report', retrofit_scenario, output_path) + '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 m2", "Heating Demand Intensity kWh/m2", - "Cooling Demand Intensity kWh/m2", "Electricity Intensity kWh/m2"]] + 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 @@ -52,128 +64,68 @@ class EnergySystemRetrofitReport: (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 monthly_demands(self): - heating = [] - cooling = [] - dhw = [] - lighting_appliance = [] - for i in range(12): - heating_demand = 0 - cooling_demand = 0 - dhw_demand = 0 - lighting_appliance_demand = 0 - for building in self.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.append(heating_demand) - cooling.append(cooling_demand) - dhw.append(dhw_demand) - lighting_appliance.append(lighting_appliance_demand) - - monthly_demands = {'heating': heating, - 'cooling': cooling, - 'dhw': dhw, - 'lighting_appliance': lighting_appliance} - return monthly_demands - - def plot_monthly_energy_demands(self, demands, file_name): + 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'] - heating = demands['heating'] - cooling = demands['cooling'] - dhw = demands['dhw'] - electricity = demands['lighting_appliance'] + 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(2, 2, figsize=(15, 10), dpi=96) - fig.suptitle('Monthly Energy Demands', fontsize=16, weight='bold', alpha=.8) + fig, axs = plt.subplots(4, 1, figsize=(20, 16), dpi=96) + fig.suptitle(title, fontsize=16, weight='bold', alpha=.8) - # Heating bar chart - axs[0, 0].bar(months, heating, color='#2196f3', width=0.6, zorder=2) - axs[0, 0].grid(which="major", axis='x', color='#DAD8D7', alpha=0.5, zorder=1) - axs[0, 0].grid(which="major", axis='y', color='#DAD8D7', alpha=0.5, zorder=1) - axs[0, 0].set_xlabel('Month', fontsize=12, labelpad=10) - axs[0, 0].set_ylabel('Heating Demand (kWh)', fontsize=12, labelpad=10) - axs[0, 0].set_title('Monthly Heating Demands', fontsize=14, weight='bold', alpha=.8) - axs[0, 0].xaxis.set_major_locator(MaxNLocator(integer=True)) - axs[0, 0].yaxis.set_major_locator(MaxNLocator(integer=True)) - axs[0, 0].set_xticks(np.arange(len(months))) - axs[0, 0].set_xticklabels(months, rotation=45, ha='right') - axs[0, 0].bar_label(axs[0, 0].containers[0], padding=3, color='black', fontsize=8) - axs[0, 0].spines[['top', 'left', 'bottom']].set_visible(False) - axs[0, 0].spines['right'].set_linewidth(1.1) - average_heating = np.mean(heating) - axs[0, 0].axhline(y=average_heating, color='grey', linewidth=2, linestyle='--') - axs[0, 0].text(len(months)-1, average_heating, f'Average = {average_heating:.1f} kWh', ha='right', va='bottom', color='grey') - - # Cooling bar chart - axs[0, 1].bar(months, cooling, color='#ff5a5f', width=0.6, zorder=2) - axs[0, 1].grid(which="major", axis='x', color='#DAD8D7', alpha=0.5, zorder=1) - axs[0, 1].grid(which="major", axis='y', color='#DAD8D7', alpha=0.5, zorder=1) - axs[0, 1].set_xlabel('Month', fontsize=12, labelpad=10) - axs[0, 1].set_ylabel('Cooling Demand (kWh)', fontsize=12, labelpad=10) - axs[0, 1].set_title('Monthly Cooling Demands', fontsize=14, weight='bold', alpha=.8) - axs[0, 1].xaxis.set_major_locator(MaxNLocator(integer=True)) - axs[0, 1].yaxis.set_major_locator(MaxNLocator(integer=True)) - axs[0, 1].set_xticks(np.arange(len(months))) - axs[0, 1].set_xticklabels(months, rotation=45, ha='right') - axs[0, 1].bar_label(axs[0, 1].containers[0], padding=3, color='black', fontsize=8) - axs[0, 1].spines[['top', 'left', 'bottom']].set_visible(False) - axs[0, 1].spines['right'].set_linewidth(1.1) - average_cooling = np.mean(cooling) - axs[0, 1].axhline(y=average_cooling, color='grey', linewidth=2, linestyle='--') - axs[0, 1].text(len(months)-1, average_cooling, f'Average = {average_cooling:.1f} kWh', ha='right', va='bottom', color='grey') - - # DHW bar chart - axs[1, 0].bar(months, dhw, color='#4caf50', width=0.6, zorder=2) - axs[1, 0].grid(which="major", axis='x', color='#DAD8D7', alpha=0.5, zorder=1) - axs[1, 0].grid(which="major", axis='y', color='#DAD8D7', alpha=0.5, zorder=1) - axs[1, 0].set_xlabel('Month', fontsize=12, labelpad=10) - axs[1, 0].set_ylabel('DHW Demand (kWh)', fontsize=12, labelpad=10) - axs[1, 0].set_title('Monthly DHW Demands', fontsize=14, weight='bold', alpha=.8) - axs[1, 0].xaxis.set_major_locator(MaxNLocator(integer=True)) - axs[1, 0].yaxis.set_major_locator(MaxNLocator(integer=True)) - axs[1, 0].set_xticks(np.arange(len(months))) - axs[1, 0].set_xticklabels(months, rotation=45, ha='right') - axs[1, 0].bar_label(axs[1, 0].containers[0], padding=3, color='black', fontsize=8) - axs[1, 0].spines[['top', 'left', 'bottom']].set_visible(False) - axs[1, 0].spines['right'].set_linewidth(1.1) - average_dhw = np.mean(dhw) - axs[1, 0].axhline(y=average_dhw, color='grey', linewidth=2, linestyle='--') - axs[1, 0].text(len(months)-1, average_dhw, f'Average = {average_dhw:.1f} kWh', ha='right', va='bottom', color='grey') - - # Electricity bar chart - axs[1, 1].bar(months, electricity, color='#ffc107', width=0.6, zorder=2) - axs[1, 1].grid(which="major", axis='x', color='#DAD8D7', alpha=0.5, zorder=1) - axs[1, 1].grid(which="major", axis='y', color='#DAD8D7', alpha=0.5, zorder=1) - axs[1, 1].set_xlabel('Month', fontsize=12, labelpad=10) - axs[1, 1].set_ylabel('Electricity Demand (kWh)', fontsize=12, labelpad=10) - axs[1, 1].set_title('Monthly Electricity Demands', fontsize=14, weight='bold', alpha=.8) - axs[1, 1].xaxis.set_major_locator(MaxNLocator(integer=True)) - axs[1, 1].yaxis.set_major_locator(MaxNLocator(integer=True)) - axs[1, 1].set_xticks(np.arange(len(months))) - axs[1, 1].set_xticklabels(months, rotation=45, ha='right') - axs[1, 1].bar_label(axs[1, 1].containers[0], padding=3, color='black', fontsize=8) - axs[1, 1].spines[['top', 'left', 'bottom']].set_visible(False) - axs[1, 1].spines['right'].set_linewidth(1.1) - average_electricity = np.mean(electricity) - axs[1, 1].axhline(y=average_electricity, color='grey', linewidth=2, linestyle='--') - axs[1, 1].text(len(months)-1, average_electricity * 0.95, f'Average = {average_electricity:.1f} kWh', ha='right', va='top', color='grey') + 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, wspace=0.3, hspace=0.5) - + 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' @@ -182,15 +134,360 @@ class EnergySystemRetrofitReport: return chart_path - def create_report(self, current_system, new_system): - os.chdir(self.charts_path) - self.report.add_section('Current Status') - self.report.add_subsection('City Buildings Characteristics') + 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, 15), 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 = self.current_status_data['total_consumption'] + retrofitted_total_consumption = self.retrofitted_data['total_consumption'] + + + 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 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() - monthly_demands = self.monthly_demands() - monthly_demands_path = str(Path(self.charts_path / 'monthly_demands.png')) - self.plot_monthly_energy_demands(demands=monthly_demands, - file_name='monthly_demands') - self.report.add_image('monthly_demands.png', 'Total Monthly Energy Demands in City' ) + + # 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) + # 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') + + # Save and compile the report self.report.save_report() self.report.compile_to_pdf() diff --git a/scripts/energy_system_retrofit_results.py b/scripts/energy_system_retrofit_results.py index 034b9a2e..e1082908 100644 --- a/scripts/energy_system_retrofit_results.py +++ b/scripts/energy_system_retrofit_results.py @@ -1,68 +1,67 @@ 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] = '-' +def consumption_data(city): + current_status_energy_consumption_data = {} + for building in city.buildings: + current_status_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} + 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) - 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 + 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 + for building in city.buildings: + yearly_heating += building.heating_consumption[cte.YEAR][0] / 3.6e6 + yearly_cooling += building.cooling_consumption[cte.YEAR][0] / 3.6e6 + yearly_dhw += building.domestic_hot_water_consumption[cte.YEAR][0] / 3.6e6 + total_consumption = yearly_heating + yearly_cooling + yearly_dhw + current_status_energy_consumption_data['monthly_demands'] = monthly_demands + current_status_energy_consumption_data['monthly_consumptions'] = monthly_consumptions + current_status_energy_consumption_data['total_consumption'] = total_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 + return current_status_energy_consumption_data diff --git a/scripts/geojson_creator.py b/scripts/geojson_creator.py index f3c28b2e..c96c340d 100644 --- a/scripts/geojson_creator.py +++ b/scripts/geojson_creator.py @@ -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: diff --git a/scripts/pv_feasibility.py b/scripts/pv_feasibility.py new file mode 100644 index 00000000..00488e39 --- /dev/null +++ b/scripts/pv_feasibility.py @@ -0,0 +1,34 @@ +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): + new_diff = current_diff * 5 + geojson_file = process_geojson(x=current_x, y=current_y, diff=new_diff, expansion=True) + file_path = (Path(__file__).parent.parent / 'input_files' / 'output_buildings_expanded.geojson') + output_path = (Path(__file__).parent.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 + 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() + 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 + + + + diff --git a/scripts/random_assignation.py b/scripts/random_assignation.py index 7483cced..ca7678bc 100644 --- a/scripts/random_assignation.py +++ b/scripts/random_assignation.py @@ -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, diff --git a/scripts/report_creation.py b/scripts/report_creation.py index e1e88927..6298d232 100644 --- a/scripts/report_creation.py +++ b/scripts/report_creation.py @@ -15,6 +15,8 @@ class LatexReport: 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") @@ -35,12 +37,16 @@ class LatexReport: 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): + 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' @@ -51,31 +57,58 @@ class LatexReport: 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=\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=\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: diff --git a/scripts/system_simulation_models/archetype13.py b/scripts/system_simulation_models/archetype13.py index 5219af2f..892b9f3f 100644 --- a/scripts/system_simulation_models/archetype13.py +++ b/scripts/system_simulation_models/archetype13.py @@ -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