From b0e6c7d9ef06c470f35219b5100d959e8249271a Mon Sep 17 00:00:00 2001 From: s_ranjbar Date: Mon, 26 Aug 2024 19:36:23 -0400 Subject: [PATCH] feat: the workflow is organized to separate system models --- .../ep_run_enrich.py | 2 +- .../geojson_creator.py | 0 .../capital_costs.py | 8 +- .../configuration.py | 0 .../costs => costing_package}/constants.py | 0 {scripts/costs => costing_package}/cost.py | 15 +- .../costs => costing_package}/cost_base.py | 2 +- .../end_of_life_costs.py | 4 +- .../costs => costing_package}/peak_load.py | 0 .../total_maintenance_costs.py | 4 +- .../total_operational_costs.py | 6 +- .../total_operational_incomes.py | 4 +- {scripts/costs => costing_package}/version.py | 0 .../montreal/archetype_cluster_1.py | 147 ++++++ .../energy_system_sizing_factory.py | 80 ++++ .../domestic_hot_water_heat_pump_with_tes.py | 118 +++++ .../heat_pump_boiler_tes_heating.py | 166 +++++++ .../heat_pump_characteristics.py | 54 +++ .../heat_pump_cooling.py | 86 ++++ .../thermal_storage_tank.py | 52 +++ ...ergy_system_archetype_modelling_factory.py | 35 ++ .../electricity_demand_calculator.py | 73 +++ .../pv_assessment}/pv_feasibility.py | 6 +- .../pv_assessment/pv_model.py | 42 ++ .../pv_assessment/pv_sizing.py | 70 +++ .../pv_sizing_and_simulation.py | 4 +- .../pv_assessment}/radiation_tilted.py | 5 +- .../pv_assessment}/solar_angles.py | 5 +- .../system_sizing_methods/heuristic_sizing.py | 6 + .../system_sizing_methods/peak_load_sizing.py | 99 ++++ .../energy_system_retrofit_report.py | 4 +- .../energy_system_retrofit_results.py | 0 .../energy_system_sizing.py | 0 ...gy_system_sizing_and_simulation_factory.py | 8 +- .../random_assignation.py | 22 +- .../report_creation.py | 0 energy_system_retrofit.py | 48 +- .../data_models/energy_systems/archetype.py | 14 +- .../energy_systems/pv_generation_system.py | 16 +- .../montreal_future_system_catalogue.py | 5 +- hub/city_model_structure/building.py | 22 +- .../building_demand/surface.py | 12 +- .../energy_systems/thermal_storage_system.py | 2 +- .../montreal_future_systems.xml | 428 ++++++++++-------- hub/exports/building_energy/idf.py | 54 ++- .../insel/insel_monthly_energy_balance.py | 2 +- hub/helpers/constants.py | 3 +- ...ntreal_future_energy_systems_parameters.py | 13 +- ...america_custom_energy_system_parameters.py | 157 ------- hub/imports/energy_systems_factory.py | 13 +- hub/imports/geometry/geojson.py | 2 + main.py | 95 ---- plot_nrcan.png | Bin 47568 -> 0 bytes pv_assessment.py | 73 --- scripts/ep_workflow.py | 63 --- .../energy_system_sizing_optimization.py | 26 -- scripts/optimization/objectives.py | 16 - .../system_simulation_models/archetype1.py | 378 ---------------- .../system_simulation_models/archetype13.py | 385 ---------------- .../archetype13_stratified_tes.py | 416 ----------------- .../archetypes14_15.py | 398 ---------------- tests/test_systems_catalog.py | 4 +- tests/test_systems_factory.py | 6 +- 63 files changed, 1444 insertions(+), 2334 deletions(-) rename {scripts => building_modelling}/ep_run_enrich.py (95%) rename {scripts => building_modelling}/geojson_creator.py (100%) rename {scripts/costs => costing_package}/capital_costs.py (98%) rename {scripts/costs => costing_package}/configuration.py (100%) rename {scripts/costs => costing_package}/constants.py (100%) rename {scripts/costs => costing_package}/cost.py (92%) rename {scripts/costs => costing_package}/cost_base.py (96%) rename {scripts/costs => costing_package}/end_of_life_costs.py (92%) rename {scripts/costs => costing_package}/peak_load.py (100%) rename {scripts/costs => costing_package}/total_maintenance_costs.py (98%) rename {scripts/costs => costing_package}/total_operational_costs.py (98%) rename {scripts/costs => costing_package}/total_operational_incomes.py (91%) rename {scripts/costs => costing_package}/version.py (100%) create mode 100644 energy_system_modelling_package/energy_system_modelling_factories/archetypes/montreal/archetype_cluster_1.py create mode 100644 energy_system_modelling_package/energy_system_modelling_factories/energy_system_sizing_factory.py create mode 100644 energy_system_modelling_package/energy_system_modelling_factories/hvac_dhw_systems_simulation_models/domestic_hot_water_heat_pump_with_tes.py create mode 100644 energy_system_modelling_package/energy_system_modelling_factories/hvac_dhw_systems_simulation_models/heat_pump_boiler_tes_heating.py create mode 100644 energy_system_modelling_package/energy_system_modelling_factories/hvac_dhw_systems_simulation_models/heat_pump_characteristics.py create mode 100644 energy_system_modelling_package/energy_system_modelling_factories/hvac_dhw_systems_simulation_models/heat_pump_cooling.py create mode 100644 energy_system_modelling_package/energy_system_modelling_factories/hvac_dhw_systems_simulation_models/thermal_storage_tank.py create mode 100644 energy_system_modelling_package/energy_system_modelling_factories/montreal_energy_system_archetype_modelling_factory.py create mode 100644 energy_system_modelling_package/energy_system_modelling_factories/pv_assessment/electricity_demand_calculator.py rename {scripts => energy_system_modelling_package/energy_system_modelling_factories/pv_assessment}/pv_feasibility.py (86%) create mode 100644 energy_system_modelling_package/energy_system_modelling_factories/pv_assessment/pv_model.py create mode 100644 energy_system_modelling_package/energy_system_modelling_factories/pv_assessment/pv_sizing.py rename {scripts => energy_system_modelling_package/energy_system_modelling_factories/pv_assessment}/pv_sizing_and_simulation.py (92%) rename {scripts => energy_system_modelling_package/energy_system_modelling_factories/pv_assessment}/radiation_tilted.py (94%) rename {scripts => energy_system_modelling_package/energy_system_modelling_factories/pv_assessment}/solar_angles.py (97%) create mode 100644 energy_system_modelling_package/energy_system_modelling_factories/system_sizing_methods/heuristic_sizing.py create mode 100644 energy_system_modelling_package/energy_system_modelling_factories/system_sizing_methods/peak_load_sizing.py rename {scripts => energy_system_modelling_package/energy_system_retrofit}/energy_system_retrofit_report.py (99%) rename {scripts => energy_system_modelling_package/energy_system_retrofit}/energy_system_retrofit_results.py (100%) rename {scripts => energy_system_modelling_package}/energy_system_sizing.py (100%) rename {scripts => energy_system_modelling_package}/energy_system_sizing_and_simulation_factory.py (80%) rename {scripts => energy_system_modelling_package}/random_assignation.py (77%) rename {scripts => energy_system_modelling_package}/report_creation.py (100%) delete mode 100644 hub/imports/energy_systems/north_america_custom_energy_system_parameters.py delete mode 100644 plot_nrcan.png delete mode 100644 pv_assessment.py delete mode 100644 scripts/ep_workflow.py delete mode 100644 scripts/optimization/energy_system_sizing_optimization.py delete mode 100644 scripts/optimization/objectives.py delete mode 100644 scripts/system_simulation_models/archetype1.py delete mode 100644 scripts/system_simulation_models/archetype13.py delete mode 100644 scripts/system_simulation_models/archetype13_stratified_tes.py delete mode 100644 scripts/system_simulation_models/archetypes14_15.py diff --git a/scripts/ep_run_enrich.py b/building_modelling/ep_run_enrich.py similarity index 95% rename from scripts/ep_run_enrich.py rename to building_modelling/ep_run_enrich.py index 68c24c8c..455cce98 100644 --- a/scripts/ep_run_enrich.py +++ b/building_modelling/ep_run_enrich.py @@ -6,7 +6,7 @@ import csv from hub.exports.energy_building_exports_factory import EnergyBuildingsExportsFactory from hub.imports.results_factory import ResultFactory -sys.path.append('./') +sys.path.append('../energy_system_modelling_package/') def energy_plus_workflow(city, output_path): diff --git a/scripts/geojson_creator.py b/building_modelling/geojson_creator.py similarity index 100% rename from scripts/geojson_creator.py rename to building_modelling/geojson_creator.py diff --git a/scripts/costs/capital_costs.py b/costing_package/capital_costs.py similarity index 98% rename from scripts/costs/capital_costs.py rename to costing_package/capital_costs.py index 832aeb7d..951e92c1 100644 --- a/scripts/costs/capital_costs.py +++ b/costing_package/capital_costs.py @@ -11,10 +11,10 @@ import pandas as pd import numpy_financial as npf from hub.city_model_structure.building import Building import hub.helpers.constants as cte -from scripts.costs.configuration import Configuration -from scripts.costs.constants import (SKIN_RETROFIT, SKIN_RETROFIT_AND_SYSTEM_RETROFIT_AND_PV, - SYSTEM_RETROFIT_AND_PV, CURRENT_STATUS, PV, SYSTEM_RETROFIT) -from scripts.costs.cost_base import CostBase +from costing_package.configuration import Configuration +from costing_package.constants import (SKIN_RETROFIT, SKIN_RETROFIT_AND_SYSTEM_RETROFIT_AND_PV, + SYSTEM_RETROFIT_AND_PV, CURRENT_STATUS, PV, SYSTEM_RETROFIT) +from costing_package.cost_base import CostBase class CapitalCosts(CostBase): diff --git a/scripts/costs/configuration.py b/costing_package/configuration.py similarity index 100% rename from scripts/costs/configuration.py rename to costing_package/configuration.py diff --git a/scripts/costs/constants.py b/costing_package/constants.py similarity index 100% rename from scripts/costs/constants.py rename to costing_package/constants.py diff --git a/scripts/costs/cost.py b/costing_package/cost.py similarity index 92% rename from scripts/costs/cost.py rename to costing_package/cost.py index 91a13034..2b2bdcd7 100644 --- a/scripts/costs/cost.py +++ b/costing_package/cost.py @@ -5,19 +5,18 @@ Copyright © 2023 Project Coder Guille Gutierrez guillermo.gutierrezmorote@conco Code contributor Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca Code contributor Oriol Gavalda Torrellas oriol.gavalda@concordia.ca """ -import datetime import pandas as pd import numpy_financial as npf from hub.city_model_structure.building import Building from hub.helpers.dictionaries import Dictionaries -from scripts.costs.configuration import Configuration -from scripts.costs.capital_costs import CapitalCosts -from scripts.costs.end_of_life_costs import EndOfLifeCosts -from scripts.costs.total_maintenance_costs import TotalMaintenanceCosts -from scripts.costs.total_operational_costs import TotalOperationalCosts -from scripts.costs.total_operational_incomes import TotalOperationalIncomes -from scripts.costs.constants import CURRENT_STATUS, SKIN_RETROFIT, SYSTEM_RETROFIT_AND_PV, SKIN_RETROFIT_AND_SYSTEM_RETROFIT_AND_PV +from costing_package.configuration import Configuration +from costing_package.capital_costs import CapitalCosts +from costing_package.end_of_life_costs import EndOfLifeCosts +from costing_package.total_maintenance_costs import TotalMaintenanceCosts +from costing_package.total_operational_costs import TotalOperationalCosts +from costing_package.total_operational_incomes import TotalOperationalIncomes +from costing_package.constants import CURRENT_STATUS import hub.helpers.constants as cte diff --git a/scripts/costs/cost_base.py b/costing_package/cost_base.py similarity index 96% rename from scripts/costs/cost_base.py rename to costing_package/cost_base.py index 31b8cd95..ad5a5aee 100644 --- a/scripts/costs/cost_base.py +++ b/costing_package/cost_base.py @@ -8,7 +8,7 @@ Code contributor Oriol Gavalda Torrellas oriol.gavalda@concordia.ca from hub.city_model_structure.building import Building -from scripts.costs.configuration import Configuration +from costing_package.configuration import Configuration class CostBase: diff --git a/scripts/costs/end_of_life_costs.py b/costing_package/end_of_life_costs.py similarity index 92% rename from scripts/costs/end_of_life_costs.py rename to costing_package/end_of_life_costs.py index 8dacfecc..f04386ef 100644 --- a/scripts/costs/end_of_life_costs.py +++ b/costing_package/end_of_life_costs.py @@ -9,8 +9,8 @@ import math import pandas as pd from hub.city_model_structure.building import Building -from scripts.costs.configuration import Configuration -from scripts.costs.cost_base import CostBase +from costing_package.configuration import Configuration +from costing_package.cost_base import CostBase class EndOfLifeCosts(CostBase): diff --git a/scripts/costs/peak_load.py b/costing_package/peak_load.py similarity index 100% rename from scripts/costs/peak_load.py rename to costing_package/peak_load.py diff --git a/scripts/costs/total_maintenance_costs.py b/costing_package/total_maintenance_costs.py similarity index 98% rename from scripts/costs/total_maintenance_costs.py rename to costing_package/total_maintenance_costs.py index 7a11b9b6..c4e89f20 100644 --- a/scripts/costs/total_maintenance_costs.py +++ b/costing_package/total_maintenance_costs.py @@ -10,8 +10,8 @@ import pandas as pd from hub.city_model_structure.building import Building import hub.helpers.constants as cte -from scripts.costs.configuration import Configuration -from scripts.costs.cost_base import CostBase +from costing_package.configuration import Configuration +from costing_package.cost_base import CostBase class TotalMaintenanceCosts(CostBase): diff --git a/scripts/costs/total_operational_costs.py b/costing_package/total_operational_costs.py similarity index 98% rename from scripts/costs/total_operational_costs.py rename to costing_package/total_operational_costs.py index 60ed54a9..cc8c56d6 100644 --- a/scripts/costs/total_operational_costs.py +++ b/costing_package/total_operational_costs.py @@ -10,9 +10,9 @@ import pandas as pd from hub.city_model_structure.building import Building import hub.helpers.constants as cte -from scripts.costs.configuration import Configuration -from scripts.costs.cost_base import CostBase -from scripts.costs.peak_load import PeakLoad +from costing_package.configuration import Configuration +from costing_package.cost_base import CostBase +from costing_package.peak_load import PeakLoad class TotalOperationalCosts(CostBase): diff --git a/scripts/costs/total_operational_incomes.py b/costing_package/total_operational_incomes.py similarity index 91% rename from scripts/costs/total_operational_incomes.py rename to costing_package/total_operational_incomes.py index f3a9f8ac..bb190999 100644 --- a/scripts/costs/total_operational_incomes.py +++ b/costing_package/total_operational_incomes.py @@ -10,8 +10,8 @@ import pandas as pd from hub.city_model_structure.building import Building import hub.helpers.constants as cte -from scripts.costs.configuration import Configuration -from scripts.costs.cost_base import CostBase +from costing_package.configuration import Configuration +from costing_package.cost_base import CostBase class TotalOperationalIncomes(CostBase): diff --git a/scripts/costs/version.py b/costing_package/version.py similarity index 100% rename from scripts/costs/version.py rename to costing_package/version.py diff --git a/energy_system_modelling_package/energy_system_modelling_factories/archetypes/montreal/archetype_cluster_1.py b/energy_system_modelling_package/energy_system_modelling_factories/archetypes/montreal/archetype_cluster_1.py new file mode 100644 index 00000000..dfe6f973 --- /dev/null +++ b/energy_system_modelling_package/energy_system_modelling_factories/archetypes/montreal/archetype_cluster_1.py @@ -0,0 +1,147 @@ +import csv + +from energy_system_modelling_package.energy_system_modelling_factories.hvac_dhw_systems_simulation_models.heat_pump_boiler_tes_heating import \ + HeatPumpBoilerTesHeating +from energy_system_modelling_package.energy_system_modelling_factories.hvac_dhw_systems_simulation_models.heat_pump_cooling import \ + HeatPumpCooling +from energy_system_modelling_package.energy_system_modelling_factories.hvac_dhw_systems_simulation_models.domestic_hot_water_heat_pump_with_tes import \ + DomesticHotWaterHeatPumpTes +from energy_system_modelling_package.energy_system_modelling_factories.pv_assessment.pv_model import PVModel +from energy_system_modelling_package.energy_system_modelling_factories.pv_assessment.electricity_demand_calculator import HourlyElectricityDemand +import hub.helpers.constants as cte +from hub.helpers.monthly_values import MonthlyValues + + +class ArchetypeCluster1: + def __init__(self, building, dt, output_path, csv_output=True): + self.building = building + self.dt = dt + self.output_path = output_path + self.csv_output = csv_output + self.heating_results, self.building_heating_hourly_consumption = self.heating_system_simulation() + self.cooling_results, self.total_cooling_consumption_hourly = self.cooling_system_simulation() + self.dhw_results, self.total_dhw_consumption_hourly = self.dhw_system_simulation() + if 'PV' in self.building.energy_systems_archetype_name: + self.pv_results = self.pv_system_simulation() + else: + self.pv_results = None + + def heating_system_simulation(self): + building_heating_hourly_consumption = [] + boiler = self.building.energy_systems[1].generation_systems[0] + hp = self.building.energy_systems[1].generation_systems[1] + tes = self.building.energy_systems[1].generation_systems[0].energy_storage_systems[0] + heating_demand_joules = self.building.heating_demand[cte.HOUR] + heating_peak_load_watts = self.building.heating_peak_load[cte.YEAR][0] + upper_limit_tes_heating = 55 + outdoor_temperature = self.building.external_temperature[cte.HOUR] + results = HeatPumpBoilerTesHeating(hp=hp, + boiler=boiler, + tes=tes, + hourly_heating_demand_joules=heating_demand_joules, + heating_peak_load_watts=heating_peak_load_watts, + upper_limit_tes=upper_limit_tes_heating, + outdoor_temperature=outdoor_temperature, + dt=self.dt).simulation() + number_of_ts = int(cte.HOUR_TO_SECONDS/self.dt) + heating_consumption_joules = [(x * cte.WATTS_HOUR_TO_JULES) / number_of_ts for x in + results['Total Heating Power Consumption (W)']] + heating_consumption = 0 + for i in range(1, len(heating_consumption_joules)): + heating_consumption += heating_consumption_joules[i] + if (i - 1) % number_of_ts == 0: + building_heating_hourly_consumption.append(heating_consumption) + heating_consumption = 0 + return results, building_heating_hourly_consumption + + def cooling_system_simulation(self): + hp = self.building.energy_systems[1].generation_systems[1] + cooling_demand_joules = self.building.cooling_demand[cte.HOUR] + cooling_peak_load = self.building.cooling_peak_load[cte.YEAR][0] + cutoff_temperature = 13 + outdoor_temperature = self.building.external_temperature[cte.HOUR] + results = HeatPumpCooling(hp=hp, + hourly_cooling_demand_joules=cooling_demand_joules, + cooling_peak_load_watts=cooling_peak_load, + cutoff_temperature=cutoff_temperature, + outdoor_temperature=outdoor_temperature, + dt=self.dt).simulation() + building_cooling_hourly_consumption = hp.energy_consumption[cte.COOLING][cte.HOUR] + return results, building_cooling_hourly_consumption + + def dhw_system_simulation(self): + building_dhw_hourly_consumption = [] + hp = self.building.energy_systems[2].generation_systems[0] + tes = self.building.energy_systems[2].generation_systems[0].energy_storage_systems[0] + dhw_demand_joules = self.building.domestic_hot_water_heat_demand[cte.HOUR] + upper_limit_tes = 65 + outdoor_temperature = self.building.external_temperature[cte.HOUR] + results = DomesticHotWaterHeatPumpTes(hp=hp, + tes=tes, + hourly_dhw_demand_joules=dhw_demand_joules, + upper_limit_tes=upper_limit_tes, + outdoor_temperature=outdoor_temperature, + dt=self.dt).simulation() + number_of_ts = int(cte.HOUR_TO_SECONDS/self.dt) + dhw_consumption_joules = [(x * cte.WATTS_HOUR_TO_JULES) / number_of_ts for x in + results['Total DHW Power Consumption (W)']] + dhw_consumption = 0 + for i in range(1, len(dhw_consumption_joules)): + dhw_consumption += dhw_consumption_joules[i] + if (i - 1) % number_of_ts == 0: + building_dhw_hourly_consumption.append(dhw_consumption) + dhw_consumption = 0 + return results, building_dhw_hourly_consumption + + def pv_system_simulation(self): + results = None + pv = self.building.energy_systems[0].generation_systems[0] + hourly_electricity_demand = HourlyElectricityDemand(self.building).calculate() + model_type = 'fixed_efficiency' + if model_type == 'fixed_efficiency': + results = PVModel(pv=pv, + hourly_electricity_demand_joules=hourly_electricity_demand, + solar_radiation=self.building.roofs[0].global_irradiance_tilted[cte.HOUR], + installed_pv_area=self.building.roofs[0].installed_solar_collector_area, + model_type='fixed_efficiency').fixed_efficiency() + return results + + def enrich_building(self): + results = self.heating_results | self.cooling_results | self.dhw_results + self.building.heating_consumption[cte.HOUR] = self.building_heating_hourly_consumption + self.building.heating_consumption[cte.MONTH] = ( + MonthlyValues.get_total_month(self.building.heating_consumption[cte.HOUR])) + self.building.heating_consumption[cte.YEAR] = [sum(self.building.heating_consumption[cte.MONTH])] + self.building.cooling_consumption[cte.HOUR] = self.total_cooling_consumption_hourly + self.building.cooling_consumption[cte.MONTH] = ( + MonthlyValues.get_total_month(self.building.cooling_consumption[cte.HOUR])) + self.building.cooling_consumption[cte.YEAR] = [sum(self.building.cooling_consumption[cte.MONTH])] + self.building.domestic_hot_water_consumption[cte.HOUR] = self.total_dhw_consumption_hourly + self.building.domestic_hot_water_consumption[cte.MONTH] = ( + MonthlyValues.get_total_month(self.building.domestic_hot_water_consumption[cte.HOUR])) + self.building.domestic_hot_water_consumption[cte.YEAR] = [ + sum(self.building.domestic_hot_water_consumption[cte.MONTH])] + if self.pv_results is not None: + self.building.onsite_electrical_production[cte.HOUR] = [x * cte.WATTS_HOUR_TO_JULES for x in + self.pv_results['PV Output (W)']] + self.building.onsite_electrical_production[cte.MONTH] = MonthlyValues.get_total_month(self.building.onsite_electrical_production[cte.HOUR]) + self.building.onsite_electrical_production[cte.YEAR] = [sum(self.building.onsite_electrical_production[cte.MONTH])] + if self.csv_output: + file_name = f'pv_system_simulation_results_{self.building.name}.csv' + with open(self.output_path / file_name, 'w', newline='') as csvfile: + output_file = csv.writer(csvfile) + # Write header + output_file.writerow(self.pv_results.keys()) + # Write data + output_file.writerows(zip(*self.pv_results.values())) + if self.csv_output: + file_name = f'energy_system_simulation_results_{self.building.name}.csv' + with open(self.output_path / file_name, 'w', newline='') as csvfile: + output_file = csv.writer(csvfile) + # Write header + output_file.writerow(results.keys()) + # Write data + output_file.writerows(zip(*results.values())) + + + diff --git a/energy_system_modelling_package/energy_system_modelling_factories/energy_system_sizing_factory.py b/energy_system_modelling_package/energy_system_modelling_factories/energy_system_sizing_factory.py new file mode 100644 index 00000000..eab3c9f2 --- /dev/null +++ b/energy_system_modelling_package/energy_system_modelling_factories/energy_system_sizing_factory.py @@ -0,0 +1,80 @@ +""" +EnergySystemSizingSimulationFactory retrieve the energy system archetype sizing and simulation module +SPDX - License - Identifier: LGPL - 3.0 - or -later +Copyright © 2024 Concordia CERC group +Project Coder Saeed Ranjbar saeed.ranjbar@mail.concordia.ca +""" + +from energy_system_modelling_package.energy_system_modelling_factories.system_sizing_methods.peak_load_sizing import \ + PeakLoadSizing +from energy_system_modelling_package.energy_system_modelling_factories.system_sizing_methods.heuristic_sizing import \ + HeuristicSizing +from energy_system_modelling_package.energy_system_modelling_factories.pv_assessment.pv_sizing import PVSizing + + +class EnergySystemsSizingFactory: + """ + EnergySystemsFactory class + """ + + def __init__(self, handler, city): + self._handler = '_' + handler.lower() + self._city = city + + def _peak_load_sizing(self): + """ + Size Energy Systems based on a Load Matching method using the heating, cooling, and dhw peak loads + """ + PeakLoadSizing(self._city).enrich_buildings() + self._city.level_of_detail.energy_systems = 1 + for building in self._city.buildings: + building.level_of_detail.energy_systems = 1 + + def _heurisitc_sizing(self): + """ + Size Energy Systems using a Single or Multi Objective GA + """ + HeuristicSizing(self._city).enrich_buildings() + self._city.level_of_detail.energy_systems = 1 + for building in self._city.buildings: + building.level_of_detail.energy_systems = 1 + + def _pv_sizing(self): + """ + Size rooftop, facade or mixture of them for buildings + """ + system_type = 'rooftop' + results = {} + if system_type == 'rooftop': + surface_azimuth = 180 + maintenance_factor = 0.1 + mechanical_equipment_factor = 0.3 + orientation_factor = 0.1 + tilt_angle = self._city.latitude + pv_sizing = PVSizing(self._city, + tilt_angle=tilt_angle, + surface_azimuth=surface_azimuth, + mechanical_equipment_factor=mechanical_equipment_factor, + maintenance_factor=maintenance_factor, + orientation_factor=orientation_factor, + system_type=system_type) + results = pv_sizing.rooftop_sizing() + pv_sizing.rooftop_tilted_radiation() + + self._city.level_of_detail.energy_systems = 1 + for building in self._city.buildings: + building.level_of_detail.energy_systems = 1 + return results + + def _district_heating_cooling_sizing(self): + """ + Size District Heating and Cooling Network + """ + pass + + def enrich(self): + """ + Enrich the city given to the class using the class given handler + :return: None + """ + return getattr(self, self._handler, lambda: None)() diff --git a/energy_system_modelling_package/energy_system_modelling_factories/hvac_dhw_systems_simulation_models/domestic_hot_water_heat_pump_with_tes.py b/energy_system_modelling_package/energy_system_modelling_factories/hvac_dhw_systems_simulation_models/domestic_hot_water_heat_pump_with_tes.py new file mode 100644 index 00000000..da7aa289 --- /dev/null +++ b/energy_system_modelling_package/energy_system_modelling_factories/hvac_dhw_systems_simulation_models/domestic_hot_water_heat_pump_with_tes.py @@ -0,0 +1,118 @@ +import hub.helpers.constants as cte +from energy_system_modelling_package.energy_system_modelling_factories.hvac_dhw_systems_simulation_models.heat_pump_characteristics import HeatPump +from energy_system_modelling_package.energy_system_modelling_factories.hvac_dhw_systems_simulation_models.thermal_storage_tank import StorageTank +from hub.helpers.monthly_values import MonthlyValues + + +class DomesticHotWaterHeatPumpTes: + def __init__(self, hp, tes, hourly_dhw_demand_joules, upper_limit_tes, + outdoor_temperature, dt=900): + self.hp = hp + self.tes = tes + self.dhw_demand = [demand / cte.WATTS_HOUR_TO_JULES for demand in hourly_dhw_demand_joules] + self.upper_limit_tes = upper_limit_tes + self.hp_characteristics = HeatPump(self.hp, outdoor_temperature) + self.t_out = outdoor_temperature + self.dt = dt + self.results = {} + + def simulation(self): + hp = self.hp + tes = self.tes + heating_coil_nominal_output = 0 + if tes.heating_coil_capacity is not None: + heating_coil_nominal_output = float(tes.heating_coil_capacity) + storage_tank = StorageTank(volume=float(tes.volume), + height=float(tes.height), + material_layers=tes.layers, + heating_coil_capacity=heating_coil_nominal_output) + + hp_delta_t = 8 + number_of_ts = int(cte.HOUR_TO_SECONDS / self.dt) + source_temperature_hourly = self.hp_characteristics.hp_source_temperature() + source_temperature = [x for x in source_temperature_hourly for _ in range(number_of_ts)] + demand = [x for x in self.dhw_demand for _ in range(number_of_ts)] + variable_names = ["t_sup_hp", "t_tank", "m_ch", "m_dis", "q_hp", "q_coil", "hp_cop", + "hp_electricity", "available hot water (m3)", "refill flow rate (kg/s)", "total_consumption"] + num_hours = len(demand) + variables = {name: [0] * num_hours for name in variable_names} + (t_sup_hp, t_tank, m_ch, m_dis, m_refill, q_hp, q_coil, hp_cop, hp_electricity, v_dhw, total_consumption) = \ + [variables[name] for name in variable_names] + freshwater_temperature = 18 + t_tank[0] = 70 + for i in range(len(demand) - 1): + delta_t_demand = demand[i] * (self.dt / (cte.WATER_DENSITY * cte.WATER_HEAT_CAPACITY * + storage_tank.volume)) + if t_tank[i] < self.upper_limit_tes: + q_hp[i] = hp.nominal_heat_output + delta_t_hp = q_hp[i] * (self.dt / (cte.WATER_DENSITY * cte.WATER_HEAT_CAPACITY * storage_tank.volume)) + if demand[i] > 0: + dhw_needed = (demand[i] * cte.HOUR_TO_SECONDS) / (cte.WATER_HEAT_CAPACITY * t_tank[i] * cte.WATER_DENSITY) + m_dis[i] = dhw_needed * cte.WATER_DENSITY / cte.HOUR_TO_SECONDS + m_refill[i] = m_dis[i] + delta_t_freshwater = m_refill[i] * (t_tank[i] - freshwater_temperature) * (self.dt / (storage_tank.volume * + cte.WATER_DENSITY)) + if t_tank[i] < 60: + q_coil[i] = float(storage_tank.heating_coil_capacity) + delta_t_coil = q_coil[i] * (self.dt / (cte.WATER_DENSITY * cte.WATER_HEAT_CAPACITY * storage_tank.volume)) + if q_hp[i] > 0: + m_ch[i] = q_hp[i] / (cte.WATER_HEAT_CAPACITY * hp_delta_t) + t_sup_hp[i] = (q_hp[i] / (m_ch[i] * cte.WATER_HEAT_CAPACITY)) + t_tank[i] + else: + m_ch[i] = 0 + t_sup_hp[i] = t_tank[i] + if q_hp[i] > 0: + if hp.source_medium == cte.AIR and hp.supply_medium == cte.WATER: + hp_cop[i] = self.hp_characteristics.air_to_water_cop(source_temperature[i], t_tank[i], + mode=cte.DOMESTIC_HOT_WATER) + hp_electricity[i] = q_hp[i] / hp_cop[i] + else: + hp_cop[i] = 0 + hp_electricity[i] = 0 + + t_tank[i + 1] = t_tank[i] + (delta_t_hp - delta_t_freshwater - delta_t_demand + delta_t_coil) + total_consumption[i] = q_hp[i] + q_coil[i] + tes.temperature = [] + hp_electricity_j = [(x * cte.WATTS_HOUR_TO_JULES) / number_of_ts for x in hp_electricity] + heating_coil_j = [(x * cte.WATTS_HOUR_TO_JULES) / number_of_ts for x in q_coil] + hp_hourly = [] + coil_hourly = [] + coil_sum = 0 + hp_sum = 0 + for i in range(1, len(demand)): + hp_sum += hp_electricity_j[i] + coil_sum += heating_coil_j[i] + if (i - 1) % number_of_ts == 0: + tes.temperature.append(t_tank[i]) + hp_hourly.append(hp_sum) + coil_hourly.append(coil_sum) + hp_sum = 0 + coil_sum = 0 + hp.energy_consumption[cte.DOMESTIC_HOT_WATER] = {} + hp.energy_consumption[cte.DOMESTIC_HOT_WATER][cte.HOUR] = hp_hourly + hp.energy_consumption[cte.DOMESTIC_HOT_WATER][cte.MONTH] = MonthlyValues.get_total_month( + hp.energy_consumption[cte.DOMESTIC_HOT_WATER][cte.HOUR]) + hp.energy_consumption[cte.DOMESTIC_HOT_WATER][cte.YEAR] = [ + sum(hp.energy_consumption[cte.DOMESTIC_HOT_WATER][cte.MONTH])] + if self.tes.heating_coil_capacity is not None: + tes.heating_coil_energy_consumption[cte.DOMESTIC_HOT_WATER] = {} + tes.heating_coil_energy_consumption[cte.DOMESTIC_HOT_WATER][cte.HOUR] = coil_hourly + tes.heating_coil_energy_consumption[cte.DOMESTIC_HOT_WATER][cte.MONTH] = MonthlyValues.get_total_month( + tes.heating_coil_energy_consumption[cte.DOMESTIC_HOT_WATER][cte.HOUR]) + tes.heating_coil_energy_consumption[cte.DOMESTIC_HOT_WATER][cte.YEAR] = [ + sum(tes.heating_coil_energy_consumption[cte.DOMESTIC_HOT_WATER][cte.MONTH])] + + self.results['DHW Demand (W)'] = demand + self.results['DHW HP Heat Output (W)'] = q_hp + self.results['DHW HP Electricity Consumption (W)'] = hp_electricity + self.results['DHW HP Source Temperature'] = source_temperature + self.results['DHW HP Supply Temperature'] = t_sup_hp + self.results['DHW HP COP'] = hp_cop + self.results['DHW TES Heating Coil Heat Output (W)'] = q_coil + self.results['DHW TES Temperature'] = t_tank + self.results['DHW TES Charging Flow Rate (kg/s)'] = m_ch + self.results['DHW Flow Rate (kg/s)'] = m_dis + self.results['DHW TES Refill Flow Rate (kg/s)'] = m_refill + self.results['Available Water in Tank (m3)'] = v_dhw + self.results['Total DHW Power Consumption (W)'] = total_consumption + return self.results diff --git a/energy_system_modelling_package/energy_system_modelling_factories/hvac_dhw_systems_simulation_models/heat_pump_boiler_tes_heating.py b/energy_system_modelling_package/energy_system_modelling_factories/hvac_dhw_systems_simulation_models/heat_pump_boiler_tes_heating.py new file mode 100644 index 00000000..4c0ac639 --- /dev/null +++ b/energy_system_modelling_package/energy_system_modelling_factories/hvac_dhw_systems_simulation_models/heat_pump_boiler_tes_heating.py @@ -0,0 +1,166 @@ +import hub.helpers.constants as cte +from hub.helpers.monthly_values import MonthlyValues +from energy_system_modelling_package.energy_system_modelling_factories.hvac_dhw_systems_simulation_models.heat_pump_characteristics import \ + HeatPump +from energy_system_modelling_package.energy_system_modelling_factories.hvac_dhw_systems_simulation_models.thermal_storage_tank import \ + StorageTank + + +class HeatPumpBoilerTesHeating: + def __init__(self, hp, boiler, tes, hourly_heating_demand_joules, heating_peak_load_watts, upper_limit_tes, + outdoor_temperature, dt=900): + self.hp = hp + self.boiler = boiler + self.tes = tes + self.heating_demand = [demand / cte.WATTS_HOUR_TO_JULES for demand in hourly_heating_demand_joules] + self.heating_peak_load = heating_peak_load_watts + self.upper_limit_tes = upper_limit_tes + self.hp_characteristics = HeatPump(self.hp, outdoor_temperature) + self.t_out = outdoor_temperature + self.dt = dt + self.results = {} + + def simulation(self): + hp, boiler, tes = self.hp, self.boiler, self.tes + heating_coil_nominal_output = 0 + if tes.heating_coil_capacity is not None: + heating_coil_nominal_output = float(tes.heating_coil_capacity) + storage_tank = StorageTank(volume=float(tes.volume), + height=float(tes.height), + material_layers=tes.layers, + heating_coil_capacity=heating_coil_nominal_output) + number_of_ts = int(cte.HOUR_TO_SECONDS / self.dt) + demand = [0] + [x for x in self.heating_demand for _ in range(number_of_ts)] + t_out = [0] + [x for x in self.t_out for _ in range(number_of_ts)] + source_temperature_hourly = self.hp_characteristics.hp_source_temperature() + source_temperature = [0] + [x for x in source_temperature_hourly for _ in range(number_of_ts)] + variable_names = ["t_sup_hp", "t_tank", "t_ret", "m_ch", "m_dis", "q_hp", "q_boiler", "hp_cop", + "hp_electricity", "boiler_gas_consumption", "t_sup_boiler", "boiler_energy_consumption", + "heating_consumption", "heating_coil_output", "total_heating_energy_consumption"] + num_hours = len(demand) + variables = {name: [0] * num_hours for name in variable_names} + (t_sup_hp, t_tank, t_ret, m_ch, m_dis, q_hp, q_boiler, hp_cop, + hp_electricity, boiler_fuel_consumption, t_sup_boiler, boiler_energy_consumption, heating_consumption, q_coil, + total_consumption) = [variables[name] for name in variable_names] + t_tank[0] = self.upper_limit_tes + hp_delta_t = 5 + # storage temperature prediction + for i in range(len(demand) - 1): + t_tank[i + 1] = storage_tank.calculate_space_heating_fully_mixed(charging_flow_rate=m_ch[i], + discharging_flow_rate=m_dis[i], + supply_temperature=t_sup_boiler[i], + return_temperature=t_ret[i], + current_tank_temperature=t_tank[i], + heat_generator_input=q_coil[i], + ambient_temperature=t_out[i], + dt=self.dt) + # hp operation + if t_tank[i + 1] < 40: + q_hp[i + 1] = hp.nominal_heat_output + m_ch[i + 1] = q_hp[i + 1] / (cte.WATER_HEAT_CAPACITY * hp_delta_t) + t_sup_hp[i + 1] = (q_hp[i + 1] / (m_ch[i + 1] * cte.WATER_HEAT_CAPACITY)) + t_tank[i + 1] + elif 40 <= t_tank[i + 1] < self.upper_limit_tes and q_hp[i] == 0: + q_hp[i + 1] = 0 + m_ch[i + 1] = 0 + t_sup_hp[i + 1] = t_tank[i + 1] + elif 40 <= t_tank[i + 1] < self.upper_limit_tes and q_hp[i] > 0: + q_hp[i + 1] = hp.nominal_heat_output + m_ch[i + 1] = q_hp[i + 1] / (cte.WATER_HEAT_CAPACITY * hp_delta_t) + t_sup_hp[i + 1] = (q_hp[i + 1] / (m_ch[i + 1] * cte.WATER_HEAT_CAPACITY)) + t_tank[i + 1] + else: + q_hp[i + 1], m_ch[i + 1], t_sup_hp[i + 1] = 0, 0, t_tank[i + 1] + if q_hp[i + 1] > 0: + if hp.source_medium == cte.AIR and self.hp.supply_medium == cte.WATER: + hp_cop[i + 1] = self.hp_characteristics.air_to_water_cop(source_temperature[i + 1], t_tank[i + 1], mode=cte.HEATING) + hp_electricity[i + 1] = q_hp[i + 1] / hp_cop[i + 1] + else: + hp_cop[i] = 0 + hp_electricity[i] = 0 + # boiler operation + if q_hp[i + 1] > 0: + if t_sup_hp[i + 1] < 45: + q_boiler[i + 1] = boiler.nominal_heat_output + elif demand[i + 1] > 0.5 * self.heating_peak_load / self.dt: + q_boiler[i + 1] = 0.5 * boiler.nominal_heat_output + boiler_energy_consumption[i + 1] = q_boiler[i + 1] / float(boiler.heat_efficiency) + if boiler.fuel_type == cte.ELECTRICITY: + boiler_fuel_consumption[i + 1] = boiler_energy_consumption[i + 1] + else: + # TODO: Other fuels should be considered + boiler_fuel_consumption[i + 1] = (q_boiler[i + 1] * self.dt) / ( + float(boiler.heat_efficiency) * cte.NATURAL_GAS_LHV) + t_sup_boiler[i + 1] = t_sup_hp[i + 1] + (q_boiler[i + 1] / (m_ch[i + 1] * cte.WATER_HEAT_CAPACITY)) + # heating coil operation + if t_tank[i + 1] < 35: + q_coil[i + 1] = heating_coil_nominal_output + # storage discharging + if demand[i + 1] == 0: + m_dis[i + 1] = 0 + t_ret[i + 1] = t_tank[i + 1] + else: + if demand[i + 1] > 0.5 * self.heating_peak_load: + factor = 8 + else: + factor = 4 + m_dis[i + 1] = self.heating_peak_load / (cte.WATER_HEAT_CAPACITY * factor) + t_ret[i + 1] = t_tank[i + 1] - demand[i + 1] / (m_dis[i + 1] * cte.WATER_HEAT_CAPACITY) + # total consumption + total_consumption[i + 1] = q_hp[i + 1] + q_boiler[i + 1] + q_coil[i + 1] + tes.temperature = [] + hp_electricity_j = [(x * cte.WATTS_HOUR_TO_JULES) / number_of_ts for x in hp_electricity] + boiler_consumption_j = [(x * cte.WATTS_HOUR_TO_JULES) / number_of_ts for x in boiler_energy_consumption] + heating_coil_j = [(x * cte.WATTS_HOUR_TO_JULES) / number_of_ts for x in q_coil] + hp_hourly = [] + boiler_hourly = [] + coil_hourly = [] + boiler_sum = 0 + hp_sum = 0 + coil_sum = 0 + for i in range(1, len(demand)): + hp_sum += hp_electricity_j[i] + boiler_sum += boiler_consumption_j[i] + coil_sum += heating_coil_j[i] + if (i - 1) % number_of_ts == 0: + tes.temperature.append(t_tank[i]) + hp_hourly.append(hp_sum) + boiler_hourly.append(boiler_sum) + coil_hourly.append(coil_sum) + hp_sum = 0 + boiler_sum = 0 + coil_sum = 0 + hp.energy_consumption[cte.HEATING] = {} + hp.energy_consumption[cte.HEATING][cte.HOUR] = hp_hourly + hp.energy_consumption[cte.HEATING][cte.MONTH] = MonthlyValues.get_total_month( + hp.energy_consumption[cte.HEATING][cte.HOUR]) + hp.energy_consumption[cte.HEATING][cte.YEAR] = [ + sum(hp.energy_consumption[cte.HEATING][cte.MONTH])] + boiler.energy_consumption[cte.HEATING] = {} + boiler.energy_consumption[cte.HEATING][cte.HOUR] = boiler_hourly + boiler.energy_consumption[cte.HEATING][cte.MONTH] = MonthlyValues.get_total_month( + boiler.energy_consumption[cte.HEATING][cte.HOUR]) + boiler.energy_consumption[cte.HEATING][cte.YEAR] = [ + sum(boiler.energy_consumption[cte.HEATING][cte.MONTH])] + if tes.heating_coil_capacity is not None: + tes.heating_coil_energy_consumption[cte.HEATING] = {} + tes.heating_coil_energy_consumption[cte.HEATING][cte.HOUR] = coil_hourly + tes.heating_coil_energy_consumption[cte.HEATING][cte.MONTH] = MonthlyValues.get_total_month( + tes.heating_coil_energy_consumption[cte.HEATING][cte.HOUR]) + tes.heating_coil_energy_consumption[cte.HEATING][cte.YEAR] = [ + sum(tes.heating_coil_energy_consumption[cte.HEATING][cte.MONTH])] + self.results['Heating Demand (W)'] = demand + self.results['HP Heat Output (W)'] = q_hp + self.results['HP Source Temperature'] = source_temperature + self.results['HP Supply Temperature'] = t_sup_hp + self.results['HP COP'] = hp_cop + self.results['HP Electricity Consumption (W)'] = hp_electricity + self.results['Boiler Heat Output (W)'] = q_boiler + self.results['Boiler Power Consumption (W)'] = boiler_energy_consumption + self.results['Boiler Supply Temperature'] = t_sup_boiler + self.results['Boiler Fuel Consumption'] = boiler_fuel_consumption + self.results['TES Temperature'] = t_tank + self.results['Heating Coil heat input'] = q_coil + self.results['TES Charging Flow Rate (kg/s)'] = m_ch + self.results['TES Discharge Flow Rate (kg/s)'] = m_dis + self.results['Heating Loop Return Temperature'] = t_ret + self.results['Total Heating Power Consumption (W)'] = total_consumption + return self.results diff --git a/energy_system_modelling_package/energy_system_modelling_factories/hvac_dhw_systems_simulation_models/heat_pump_characteristics.py b/energy_system_modelling_package/energy_system_modelling_factories/hvac_dhw_systems_simulation_models/heat_pump_characteristics.py new file mode 100644 index 00000000..bd14bf04 --- /dev/null +++ b/energy_system_modelling_package/energy_system_modelling_factories/hvac_dhw_systems_simulation_models/heat_pump_characteristics.py @@ -0,0 +1,54 @@ +import hub.helpers.constants as cte + + +class HeatPump: + def __init__(self, hp, t_out): + self.hp = hp + self.t_out = t_out + + def hp_source_temperature(self): + if self.hp.source_medium == cte.AIR: + self.hp.source_temperature = self.t_out + elif self.hp.source_medium == cte.GROUND: + average_air_temperature = sum(self.t_out) / len(self.t_out) + self.hp.source_temperature = [average_air_temperature + 10] * len(self.t_out) + elif self.hp.source_medium == cte.WATER: + self.hp.source_temperature = [15] * len(self.t_out) + return self.hp.source_temperature + + def air_to_water_cop(self, source_temperature, inlet_water_temperature, mode=cte.HEATING): + cop_coefficient = 1 + t_inlet_water_fahrenheit = 1.8 * inlet_water_temperature + 32 + t_source_fahrenheit = 1.8 * source_temperature + 32 + if mode == cte.HEATING: + if self.hp.heat_efficiency_curve is not None: + cop_curve_coefficients = [float(coefficient) for coefficient in self.hp.heat_efficiency_curve.coefficients] + cop_coefficient = (1 / (cop_curve_coefficients[0] + + cop_curve_coefficients[1] * t_inlet_water_fahrenheit + + cop_curve_coefficients[2] * t_inlet_water_fahrenheit ** 2 + + cop_curve_coefficients[3] * t_source_fahrenheit + + cop_curve_coefficients[4] * t_source_fahrenheit ** 2 + + cop_curve_coefficients[5] * t_inlet_water_fahrenheit * t_source_fahrenheit)) + hp_efficiency = float(self.hp.heat_efficiency) + elif mode == cte.COOLING: + if self.hp.cooling_efficiency_curve is not None: + cop_curve_coefficients = [float(coefficient) for coefficient in self.hp.cooling_efficiency_curve.coefficients] + cop_coefficient = (1 / (cop_curve_coefficients[0] + + cop_curve_coefficients[1] * t_inlet_water_fahrenheit + + cop_curve_coefficients[2] * t_inlet_water_fahrenheit ** 2 + + cop_curve_coefficients[3] * t_source_fahrenheit + + cop_curve_coefficients[4] * t_source_fahrenheit ** 2 + + cop_curve_coefficients[5] * t_inlet_water_fahrenheit * t_source_fahrenheit)) + hp_efficiency = float(self.hp.cooling_efficiency) + else: + if self.hp.heat_efficiency_curve is not None: + cop_curve_coefficients = [float(coefficient) for coefficient in self.hp.heat_efficiency_curve.coefficients] + cop_coefficient = (1 / (cop_curve_coefficients[0] + + cop_curve_coefficients[1] * inlet_water_temperature + + cop_curve_coefficients[2] * inlet_water_temperature ** 2 + + cop_curve_coefficients[3] * source_temperature + + cop_curve_coefficients[4] * source_temperature ** 2 + + cop_curve_coefficients[5] * inlet_water_temperature * source_temperature)) + hp_efficiency = float(self.hp.heat_efficiency) + hp_cop = cop_coefficient * hp_efficiency + return hp_cop diff --git a/energy_system_modelling_package/energy_system_modelling_factories/hvac_dhw_systems_simulation_models/heat_pump_cooling.py b/energy_system_modelling_package/energy_system_modelling_factories/hvac_dhw_systems_simulation_models/heat_pump_cooling.py new file mode 100644 index 00000000..291024d9 --- /dev/null +++ b/energy_system_modelling_package/energy_system_modelling_factories/hvac_dhw_systems_simulation_models/heat_pump_cooling.py @@ -0,0 +1,86 @@ +import hub.helpers.constants as cte +from hub.helpers.monthly_values import MonthlyValues +from energy_system_modelling_package.energy_system_modelling_factories.hvac_dhw_systems_simulation_models.heat_pump_characteristics import HeatPump + + +class HeatPumpCooling: + def __init__(self, hp, hourly_cooling_demand_joules, cooling_peak_load_watts, cutoff_temperature, outdoor_temperature, + dt=900): + self.hp = hp + self.cooling_demand = [demand / cte.WATTS_HOUR_TO_JULES for demand in hourly_cooling_demand_joules] + self.cooling_peak_load = cooling_peak_load_watts + self.cutoff_temperature = cutoff_temperature + self.dt = dt + self.results = {} + self.heat_pump_characteristics = HeatPump(self.hp, outdoor_temperature) + + def simulation(self): + source_temperature_hourly = self.heat_pump_characteristics.hp_source_temperature() + cooling_efficiency = float(self.hp.cooling_efficiency) + number_of_ts = int(cte.HOUR_TO_SECONDS / self.dt) + demand = [0] + [x for x in self.cooling_demand for _ in range(number_of_ts)] + source_temperature = [0] + [x for x in source_temperature_hourly for _ in range(number_of_ts)] + variable_names = ["t_sup_hp", "t_ret", "m", "q_hp", "hp_electricity", "hp_cop"] + num_hours = len(demand) + variables = {name: [0] * num_hours for name in variable_names} + (t_sup_hp, t_ret, m, q_hp, hp_electricity, hp_cop) = [variables[name] for name in variable_names] + t_ret[0] = self.cutoff_temperature + + for i in range(1, len(demand)): + if demand[i] > 0.15 * self.cooling_peak_load: + m[i] = self.hp.nominal_cooling_output / (cte.WATER_HEAT_CAPACITY * 5) + if t_ret[i - 1] >= self.cutoff_temperature: + if demand[i] < 0.25 * self.cooling_peak_load: + q_hp[i] = 0.25 * self.hp.nominal_cooling_output + elif demand[i] < 0.5 * self.cooling_peak_load: + q_hp[i] = 0.5 * self.hp.nominal_cooling_output + else: + q_hp[i] = self.hp.nominal_cooling_output + t_sup_hp[i] = t_ret[i - 1] - q_hp[i] / (m[i] * cte.WATER_HEAT_CAPACITY) + else: + q_hp[i] = 0 + t_sup_hp[i] = t_ret[i - 1] + if m[i] == 0: + t_ret[i] = t_sup_hp[i] + else: + t_ret[i] = t_sup_hp[i] + demand[i] / (m[i] * cte.WATER_HEAT_CAPACITY) + else: + m[i] = 0 + q_hp[i] = 0 + t_sup_hp[i] = t_ret[i - 1] + t_ret[i] = t_ret[i - 1] + if q_hp[i] > 0: + if self.hp.source_medium == cte.AIR and self.hp.supply_medium == cte.WATER: + hp_cop[i] = self.heat_pump_characteristics.air_to_water_cop(source_temperature[i], t_ret[i], + mode=cte.COOLING) + hp_electricity[i] = q_hp[i] / cooling_efficiency + else: + hp_cop[i] = 0 + hp_electricity[i] = 0 + hp_electricity_j = [(x * cte.WATTS_HOUR_TO_JULES) / number_of_ts for x in hp_electricity] + hp_hourly = [] + hp_sum = 0 + for i in range(1, len(demand)): + hp_sum += hp_electricity_j[i] + if (i - 1) % number_of_ts == 0: + hp_hourly.append(hp_sum) + hp_sum = 0 + self.hp.energy_consumption[cte.COOLING] = {} + self.hp.energy_consumption[cte.COOLING][cte.HOUR] = hp_hourly + self.hp.energy_consumption[cte.COOLING][cte.MONTH] = MonthlyValues.get_total_month( + self.hp.energy_consumption[cte.COOLING][cte.HOUR]) + self.hp.energy_consumption[cte.COOLING][cte.YEAR] = [ + sum(self.hp.energy_consumption[cte.COOLING][cte.MONTH])] + self.results['Cooling Demand (W)'] = demand + self.results['HP Cooling Output (W)'] = q_hp + self.results['HP Source Temperature'] = source_temperature + self.results['HP Cooling Supply Temperature'] = t_sup_hp + self.results['HP Cooling COP'] = hp_cop + self.results['HP Electricity Consumption'] = hp_electricity + self.results['Cooling Loop Flow Rate (kg/s)'] = m + self.results['Cooling Loop Return Temperature'] = t_ret + return self.results + + + + diff --git a/energy_system_modelling_package/energy_system_modelling_factories/hvac_dhw_systems_simulation_models/thermal_storage_tank.py b/energy_system_modelling_package/energy_system_modelling_factories/hvac_dhw_systems_simulation_models/thermal_storage_tank.py new file mode 100644 index 00000000..ea427348 --- /dev/null +++ b/energy_system_modelling_package/energy_system_modelling_factories/hvac_dhw_systems_simulation_models/thermal_storage_tank.py @@ -0,0 +1,52 @@ +import math + +import hub.helpers.constants as cte + + +class StorageTank: + """ + Calculation of the temperature inside a hot water storage tank in the next time step + """ + + def __init__(self, volume, height, material_layers, heating_coil_capacity, stratification_layer=1): + self.volume = volume + self.height = height + self.materials = material_layers + self.number_of_vertical_layers = stratification_layer + self.heating_coil_capacity = heating_coil_capacity + + def heat_loss_coefficient(self): + if self.number_of_vertical_layers == 1: + r_tot = sum(float(layer.thickness) / float(layer.material.conductivity) for layer in + self.materials) + u_tot = 1 / r_tot + d = math.sqrt((4 * self.volume) / (math.pi * self.height)) + a_side = math.pi * d * self.height + a_top = math.pi * d ** 2 / 4 + ua = u_tot * (2 * a_top + a_side) + return ua + else: + r_tot = sum(float(layer.thickness) / float(layer.material.conductivity) for layer in + self.materials) + u_tot = 1 / r_tot + d = math.sqrt((4 * self.volume) / (math.pi * self.height)) + a_side = math.pi * d * self.height + a_top = math.pi * d ** 2 / 4 + ua_side = u_tot * a_side + ua_top_bottom = u_tot * (a_top + a_side) + return ua_side, ua_top_bottom + + def calculate_space_heating_fully_mixed(self, charging_flow_rate, discharging_flow_rate, supply_temperature, return_temperature, + current_tank_temperature, heat_generator_input, ambient_temperature, dt): + ua = self.heat_loss_coefficient() + t_tank = (current_tank_temperature + + (charging_flow_rate * (supply_temperature - current_tank_temperature) + + (ua * (ambient_temperature - current_tank_temperature)) / cte.WATER_HEAT_CAPACITY - + discharging_flow_rate * (current_tank_temperature - return_temperature) + + heat_generator_input / cte.WATER_HEAT_CAPACITY) * (dt / (cte.WATER_DENSITY * self.volume))) + return t_tank + + def calculate_dhw_fully_mixed(self, charging_flow_rate, discharging_flow_rate, supply_temperature, return_temperature, + current_tank_temperature, heat_generator_input, ambient_temperature, dt): + pass + diff --git a/energy_system_modelling_package/energy_system_modelling_factories/montreal_energy_system_archetype_modelling_factory.py b/energy_system_modelling_package/energy_system_modelling_factories/montreal_energy_system_archetype_modelling_factory.py new file mode 100644 index 00000000..dee467f4 --- /dev/null +++ b/energy_system_modelling_package/energy_system_modelling_factories/montreal_energy_system_archetype_modelling_factory.py @@ -0,0 +1,35 @@ +""" +EnergySystemSizingSimulationFactory retrieve the energy system archetype sizing and simulation module +SPDX - License - Identifier: LGPL - 3.0 - or -later +Copyright © 2024 Concordia CERC group +Project Coder Saeed Ranjbar saeed.ranjbar@mail.concordia.ca +""" + +from energy_system_modelling_package.energy_system_modelling_factories.archetypes.montreal.archetype_cluster_1 import ArchetypeCluster1 + + +class MontrealEnergySystemArchetypesSimulationFactory: + """ + EnergySystemsFactory class + """ + + def __init__(self, handler, building, output_path): + self._output_path = output_path + self._handler = '_' + handler.lower() + self._building = building + + def _archetype_cluster_1(self): + """ + Enrich the city by using the sizing and simulation model developed for archetype13 of montreal_future_systems + """ + dt = 900 + ArchetypeCluster1(self._building, dt, self._output_path, csv_output=True).enrich_building() + self._building.level_of_detail.energy_systems = 2 + self._building.level_of_detail.energy_systems = 2 + + def enrich(self): + """ + Enrich the city given to the class using the class given handler + :return: None + """ + getattr(self, self._handler, lambda: None)() diff --git a/energy_system_modelling_package/energy_system_modelling_factories/pv_assessment/electricity_demand_calculator.py b/energy_system_modelling_package/energy_system_modelling_factories/pv_assessment/electricity_demand_calculator.py new file mode 100644 index 00000000..175f367e --- /dev/null +++ b/energy_system_modelling_package/energy_system_modelling_factories/pv_assessment/electricity_demand_calculator.py @@ -0,0 +1,73 @@ +import hub.helpers.constants as cte +class HourlyElectricityDemand: + def __init__(self, building): + self.building = building + + def calculate(self): + hourly_electricity_consumption = [] + energy_systems = self.building.energy_systems + appliance = self.building.appliances_electrical_demand[cte.HOUR] + lighting = self.building.lighting_electrical_demand[cte.HOUR] + elec_heating = 0 + elec_cooling = 0 + elec_dhw = 0 + if cte.HEATING in self.building.energy_consumption_breakdown[cte.ELECTRICITY]: + elec_heating = 1 + if cte.COOLING in self.building.energy_consumption_breakdown[cte.ELECTRICITY]: + elec_cooling = 1 + if cte.DOMESTIC_HOT_WATER in self.building.energy_consumption_breakdown[cte.ELECTRICITY]: + elec_dhw = 1 + heating = None + cooling = None + dhw = None + + if elec_heating == 1: + for energy_system in energy_systems: + if cte.HEATING in energy_system.demand_types: + for generation_system in energy_system.generation_systems: + if generation_system.fuel_type == cte.ELECTRICITY: + if cte.HEATING in generation_system.energy_consumption: + heating = generation_system.energy_consumption[cte.HEATING][cte.HOUR] + else: + if len(energy_system.generation_systems) > 1: + heating = [x / 2 for x in self.building.heating_consumption[cte.HOUR]] + else: + heating = self.building.heating_consumption[cte.HOUR] + + if elec_dhw == 1: + for energy_system in energy_systems: + if cte.DOMESTIC_HOT_WATER in energy_system.demand_types: + for generation_system in energy_system.generation_systems: + if generation_system.fuel_type == cte.ELECTRICITY: + if cte.DOMESTIC_HOT_WATER in generation_system.energy_consumption: + dhw = generation_system.energy_consumption[cte.DOMESTIC_HOT_WATER][cte.HOUR] + else: + if len(energy_system.generation_systems) > 1: + dhw = [x / 2 for x in self.building.domestic_hot_water_consumption[cte.HOUR]] + else: + dhw = self.building.domestic_hot_water_consumption[cte.HOUR] + + if elec_cooling == 1: + for energy_system in energy_systems: + if cte.COOLING in energy_system.demand_types: + for generation_system in energy_system.generation_systems: + if cte.COOLING in generation_system.energy_consumption: + cooling = generation_system.energy_consumption[cte.COOLING][cte.HOUR] + else: + if len(energy_system.generation_systems) > 1: + cooling = [x / 2 for x in self.building.cooling_consumption[cte.HOUR]] + else: + cooling = self.building.cooling_consumption[cte.HOUR] + + for i in range(len(self.building.heating_demand[cte.HOUR])): + hourly = 0 + hourly += appliance[i] + hourly += lighting[i] + if heating is not None: + hourly += heating[i] + if cooling is not None: + hourly += cooling[i] + if dhw is not None: + hourly += dhw[i] + hourly_electricity_consumption.append(hourly) + return hourly_electricity_consumption diff --git a/scripts/pv_feasibility.py b/energy_system_modelling_package/energy_system_modelling_factories/pv_assessment/pv_feasibility.py similarity index 86% rename from scripts/pv_feasibility.py rename to energy_system_modelling_package/energy_system_modelling_factories/pv_assessment/pv_feasibility.py index 034a5efb..9278b16c 100644 --- a/scripts/pv_feasibility.py +++ b/energy_system_modelling_package/energy_system_modelling_factories/pv_assessment/pv_feasibility.py @@ -1,7 +1,7 @@ from pathlib import Path import subprocess from hub.imports.geometry_factory import GeometryFactory -from scripts.geojson_creator import process_geojson +from building_modelling.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 @@ -9,8 +9,8 @@ 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() + input_files_path = (Path(__file__).parent.parent.parent.parent / 'input_files') + output_path = (Path(__file__).parent.parent.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 diff --git a/energy_system_modelling_package/energy_system_modelling_factories/pv_assessment/pv_model.py b/energy_system_modelling_package/energy_system_modelling_factories/pv_assessment/pv_model.py new file mode 100644 index 00000000..2714befa --- /dev/null +++ b/energy_system_modelling_package/energy_system_modelling_factories/pv_assessment/pv_model.py @@ -0,0 +1,42 @@ +import math +import hub.helpers.constants as cte +from hub.helpers.monthly_values import MonthlyValues + + +class PVModel: + def __init__(self, pv, hourly_electricity_demand_joules, solar_radiation, installed_pv_area, model_type, ns=None, + np=None): + self.pv = pv + self.hourly_electricity_demand = [demand / cte.WATTS_HOUR_TO_JULES for demand in hourly_electricity_demand_joules] + self.solar_radiation = solar_radiation + self.installed_pv_area = installed_pv_area + self._model_type = '_' + model_type.lower() + self.ns = ns + self.np = np + self.results = {} + + def fixed_efficiency(self): + module_efficiency = float(self.pv.electricity_efficiency) + variable_names = ["pv_output", "import", "export", "self_sufficiency_ratio"] + variables = {name: [0] * len(self.hourly_electricity_demand) for name in variable_names} + (pv_out, grid_import, grid_export, self_sufficiency_ratio) = [variables[name] for name in variable_names] + for i in range(len(self.hourly_electricity_demand)): + pv_out[i] = module_efficiency * self.installed_pv_area * self.solar_radiation[i] / cte.WATTS_HOUR_TO_JULES + if pv_out[i] < self.hourly_electricity_demand[i]: + grid_import[i] = self.hourly_electricity_demand[i] - pv_out[i] + else: + grid_export[i] = pv_out[i] - self.hourly_electricity_demand[i] + self_sufficiency_ratio[i] = pv_out[i] / self.hourly_electricity_demand[i] + self.results['Electricity Demand (W)'] = self.hourly_electricity_demand + self.results['PV Output (W)'] = pv_out + self.results['Imported from Grid (W)'] = grid_import + self.results['Exported to Grid (W)'] = grid_export + self.results['Self Sufficiency Ratio'] = self_sufficiency_ratio + return self.results + + def enrich(self): + """ + Enrich the city given to the class using the class given handler + :return: None + """ + return getattr(self, self._model_type, lambda: None)() diff --git a/energy_system_modelling_package/energy_system_modelling_factories/pv_assessment/pv_sizing.py b/energy_system_modelling_package/energy_system_modelling_factories/pv_assessment/pv_sizing.py new file mode 100644 index 00000000..b53ba465 --- /dev/null +++ b/energy_system_modelling_package/energy_system_modelling_factories/pv_assessment/pv_sizing.py @@ -0,0 +1,70 @@ +import math +import hub.helpers.constants as cte +from energy_system_modelling_package.energy_system_modelling_factories.pv_assessment.solar_angles import CitySolarAngles +from energy_system_modelling_package.energy_system_modelling_factories.pv_assessment.radiation_tilted import RadiationTilted + + +class PVSizing(CitySolarAngles): + def __init__(self, city, tilt_angle, surface_azimuth=180, maintenance_factor=0.1, mechanical_equipment_factor=0.3, + orientation_factor=0.1, system_type='rooftop'): + super().__init__(location_latitude=city.latitude, + location_longitude=city.longitude, + tilt_angle=tilt_angle, + surface_azimuth_angle=surface_azimuth) + self.city = city + self.maintenance_factor = maintenance_factor + self.mechanical_equipment_factor = mechanical_equipment_factor + self.orientation_factor = orientation_factor + self.angles = self.calculate + self.system_type = system_type + + def rooftop_sizing(self): + results = {} + # Available Roof Area + for building in self.city.buildings: + for energy_system in building.energy_systems: + for generation_system in energy_system.generation_systems: + if generation_system.system_type == cte.PHOTOVOLTAIC: + module_width = float(generation_system.width) + module_height = float(generation_system.height) + roof_area = 0 + for roof in building.roofs: + roof_area += roof.perimeter_area + pv_module_area = module_width * module_height + available_roof = ((self.maintenance_factor + self.orientation_factor + self.mechanical_equipment_factor) * + roof_area) + # Inter-Row Spacing + winter_solstice = self.angles[(self.angles['AST'].dt.month == 12) & + (self.angles['AST'].dt.day == 21) & + (self.angles['AST'].dt.hour == 12)] + solar_altitude = winter_solstice['solar altitude'].values[0] + solar_azimuth = winter_solstice['solar azimuth'].values[0] + distance = ((module_height * abs(math.cos(math.radians(solar_azimuth)))) / + math.tan(math.radians(solar_altitude))) + distance = float(format(distance, '.1f')) + # Calculation of the number of panels + space_dimension = math.sqrt(available_roof) + space_dimension = float(format(space_dimension, '.2f')) + panels_per_row = math.ceil(space_dimension / module_width) + number_of_rows = math.ceil(space_dimension / distance) + total_number_of_panels = panels_per_row * number_of_rows + total_pv_area = panels_per_row * number_of_rows * pv_module_area + building.roofs[0].installed_solar_collector_area = total_pv_area + results[f'Building {building.name}'] = {'total_roof_area': roof_area, + 'PV dedicated area': available_roof, + 'total_pv_area': total_pv_area, + 'total_number_of_panels': total_number_of_panels, + 'number_of_rows': number_of_rows, + 'panels_per_row': panels_per_row} + return results + + def rooftop_tilted_radiation(self): + for building in self.city.buildings: + RadiationTilted(building=building, + solar_angles=self.angles, + tilt_angle=self.tilt_angle, + ghi=building.roofs[0].global_irradiance[cte.HOUR], + ).enrich() + + def facade_sizing(self): + pass diff --git a/scripts/pv_sizing_and_simulation.py b/energy_system_modelling_package/energy_system_modelling_factories/pv_assessment/pv_sizing_and_simulation.py similarity index 92% rename from scripts/pv_sizing_and_simulation.py rename to energy_system_modelling_package/energy_system_modelling_factories/pv_assessment/pv_sizing_and_simulation.py index 6ef1a51d..fc26fe7e 100644 --- a/scripts/pv_sizing_and_simulation.py +++ b/energy_system_modelling_package/energy_system_modelling_factories/pv_assessment/pv_sizing_and_simulation.py @@ -1,6 +1,6 @@ import math -from scripts.radiation_tilted import RadiationTilted +from energy_system_modelling_package.energy_system_modelling_factories.pv_assessment.radiation_tilted import RadiationTilted import hub.helpers.constants as cte from hub.helpers.monthly_values import MonthlyValues @@ -43,7 +43,7 @@ class PVSizingSimulation(RadiationTilted): self.total_number_of_panels = panels_per_row * number_of_rows return panels_per_row, number_of_rows - def pv_output(self): + def pv_output_constant_efficiency(self): radiation = self.total_radiation_tilted pv_module_area = self.module_width * self.module_height available_roof = self.available_space() diff --git a/scripts/radiation_tilted.py b/energy_system_modelling_package/energy_system_modelling_factories/pv_assessment/radiation_tilted.py similarity index 94% rename from scripts/radiation_tilted.py rename to energy_system_modelling_package/energy_system_modelling_factories/pv_assessment/radiation_tilted.py index beb62088..31bd5636 100644 --- a/scripts/radiation_tilted.py +++ b/energy_system_modelling_package/energy_system_modelling_factories/pv_assessment/radiation_tilted.py @@ -100,10 +100,7 @@ class RadiationTilted: def enrich(self): tilted_radiation = self.total_radiation_tilted - self.building.roofs[0].global_irradiance_tilted[cte.HOUR] = [x * cte.WATTS_HOUR_TO_JULES for x in - tilted_radiation] - self.building.roofs[0].global_irradiance_tilted[cte.HOUR] = [x * cte.WATTS_HOUR_TO_JULES for x in - tilted_radiation] + self.building.roofs[0].global_irradiance_tilted[cte.HOUR] = tilted_radiation self.building.roofs[0].global_irradiance_tilted[cte.MONTH] = ( MonthlyValues.get_total_month(self.building.roofs[0].global_irradiance_tilted[cte.HOUR])) self.building.roofs[0].global_irradiance_tilted[cte.YEAR] = \ diff --git a/scripts/solar_angles.py b/energy_system_modelling_package/energy_system_modelling_factories/pv_assessment/solar_angles.py similarity index 97% rename from scripts/solar_angles.py rename to energy_system_modelling_package/energy_system_modelling_factories/pv_assessment/solar_angles.py index d65fcf58..560bd27c 100644 --- a/scripts/solar_angles.py +++ b/energy_system_modelling_package/energy_system_modelling_factories/pv_assessment/solar_angles.py @@ -5,9 +5,8 @@ from pathlib import Path class CitySolarAngles: - def __init__(self, file_name, location_latitude, location_longitude, tilt_angle, surface_azimuth_angle, + def __init__(self, location_latitude, location_longitude, tilt_angle, surface_azimuth_angle, standard_meridian=-75): - self.file_name = file_name self.location_latitude = location_latitude self.location_longitude = location_longitude self.location_latitude_rad = math.radians(location_latitude) @@ -29,7 +28,7 @@ class CitySolarAngles: self.incidents = [] self.beam_tilted = [] self.factor = [] - self.times = pd.date_range(start='2023-01-01', end='2024-01-01', freq='H', tz=self.timezone) + self.times = pd.date_range(start='2023-01-01', end='2024-01-01', freq='h', tz=self.timezone) self.df = pd.DataFrame(index=self.times) self.day_of_year = self.df.index.dayofyear diff --git a/energy_system_modelling_package/energy_system_modelling_factories/system_sizing_methods/heuristic_sizing.py b/energy_system_modelling_package/energy_system_modelling_factories/system_sizing_methods/heuristic_sizing.py new file mode 100644 index 00000000..5cf9c167 --- /dev/null +++ b/energy_system_modelling_package/energy_system_modelling_factories/system_sizing_methods/heuristic_sizing.py @@ -0,0 +1,6 @@ +class HeuristicSizing: + def __init__(self, city): + pass + + def enrich_buildings(self): + pass diff --git a/energy_system_modelling_package/energy_system_modelling_factories/system_sizing_methods/peak_load_sizing.py b/energy_system_modelling_package/energy_system_modelling_factories/system_sizing_methods/peak_load_sizing.py new file mode 100644 index 00000000..1f1fe74a --- /dev/null +++ b/energy_system_modelling_package/energy_system_modelling_factories/system_sizing_methods/peak_load_sizing.py @@ -0,0 +1,99 @@ +import hub.helpers.constants as cte + + +class PeakLoadSizing: + def __init__(self, city, default_primary_unit_percentage=0.7, storage_peak_coverage=3): + self.city = city + self.default_primary_unit_percentage = default_primary_unit_percentage + self.storage_peak_coverage = storage_peak_coverage + + def enrich_buildings(self): + total_demand = 0 + for building in self.city.buildings: + energy_systems = building.energy_systems + for energy_system in energy_systems: + if cte.HEATING in energy_system.demand_types: + if cte.DOMESTIC_HOT_WATER in energy_system.demand_types: + total_demand = [(building.heating_demand[cte.HOUR][i] + + building.domestic_hot_water_heat_demand[cte.HOUR][i]) / cte.WATTS_HOUR_TO_JULES + for i in range(len(building.heating_demand[cte.HOUR]))] + else: + total_demand = building.heating_peak_load[cte.YEAR] + design_load = max(total_demand) + self.allocate_capacity(energy_system, design_load, cte.HEATING, self.default_primary_unit_percentage) + if cte.COOLING in energy_system.demand_types: + cooling_design_load = building.cooling_peak_load[cte.YEAR][0] + self.allocate_capacity(energy_system, cooling_design_load, cte.COOLING, + self.default_primary_unit_percentage) + + elif cte.COOLING in energy_system.demand_types: + design_load = building.cooling_peak_load[cte.YEAR][0] + self.allocate_capacity(energy_system, design_load, cte.COOLING, self.default_primary_unit_percentage) + elif cte.DOMESTIC_HOT_WATER in energy_system.demand_types: + design_load = building.domestic_hot_water_peak_load[cte.YEAR][0] + self.allocate_capacity(energy_system, design_load, cte.DOMESTIC_HOT_WATER, + self.default_primary_unit_percentage) + + for generation_system in energy_system.generation_systems: + storage_systems = generation_system.energy_storage_systems + if storage_systems is not None: + if cte.DOMESTIC_HOT_WATER in energy_system.demand_types: + operation_range = 10 + else: + operation_range = 20 + for storage_system in storage_systems: + if storage_system.type_energy_stored == cte.THERMAL: + self.tes_sizing(storage_system, max(total_demand), self.storage_peak_coverage, operation_range) + + def allocate_capacity(self, energy_system, design_load, demand_type, default_primary_unit_percentage): + if len(energy_system.generation_systems) == 1: + # If there's only one generation system, it gets the full design load. + if demand_type == cte.HEATING or demand_type == cte.DOMESTIC_HOT_WATER: + energy_system.generation_systems[0].nominal_heat_output = design_load + elif demand_type == cte.COOLING: + energy_system.generation_systems[0].nominal_cooling_output = design_load + else: + cooling_equipments_number = 0 + # Distribute the load among generation systems. + max_efficiency = 0 + main_generation_unit = None + for generation_system in energy_system.generation_systems: + if demand_type == cte.HEATING or demand_type == cte.DOMESTIC_HOT_WATER: + if max_efficiency < float(generation_system.heat_efficiency): + max_efficiency = float(generation_system.heat_efficiency) + main_generation_unit = generation_system + elif demand_type == cte.COOLING and generation_system.fuel_type == cte.ELECTRICITY: + cooling_equipments_number += 1 + if max_efficiency < float(generation_system.cooling_efficiency): + max_efficiency = float(generation_system.heat_efficiency) + main_generation_unit = generation_system + + for generation_system in energy_system.generation_systems: + if generation_system.system_type == main_generation_unit.system_type: + if demand_type == cte.HEATING or demand_type == cte.DOMESTIC_HOT_WATER: + generation_system.nominal_heat_output = round(default_primary_unit_percentage * design_load) + elif demand_type == cte.COOLING and cooling_equipments_number > 1: + generation_system.nominal_cooling_output = round(default_primary_unit_percentage * design_load) + else: + generation_system.nominal_cooling_output = design_load + else: + if demand_type == cte.HEATING or demand_type == cte.DOMESTIC_HOT_WATER: + generation_system.nominal_heat_output = round(((1 - default_primary_unit_percentage) * design_load / + (len(energy_system.generation_systems) - 1))) + elif demand_type == cte.COOLING and cooling_equipments_number > 1: + generation_system.nominal_cooling_output = round(((1 - default_primary_unit_percentage) * design_load / + (len(energy_system.generation_systems) - 1))) + + + @staticmethod + def tes_sizing(storage, peak_load, coverage, operational_temperature_range): + storage.volume = round((peak_load * coverage * cte.WATTS_HOUR_TO_JULES) / + (cte.WATER_HEAT_CAPACITY * cte.WATER_DENSITY * operational_temperature_range)) + + @staticmethod + def cooling_equipments(energy_system): + counter = 0 + for generation_system in energy_system.generation_systems: + if generation_system.fuel_type == cte.ELECTRICITY: + counter += 1 + return counter diff --git a/scripts/energy_system_retrofit_report.py b/energy_system_modelling_package/energy_system_retrofit/energy_system_retrofit_report.py similarity index 99% rename from scripts/energy_system_retrofit_report.py rename to energy_system_modelling_package/energy_system_retrofit/energy_system_retrofit_report.py index 738437c9..9bbc4b23 100644 --- a/scripts/energy_system_retrofit_report.py +++ b/energy_system_modelling_package/energy_system_retrofit/energy_system_retrofit_report.py @@ -2,7 +2,7 @@ import os import hub.helpers.constants as cte import matplotlib.pyplot as plt from matplotlib import cm -from scripts.report_creation import LatexReport +from energy_system_modelling_package.report_creation import LatexReport from matplotlib.ticker import MaxNLocator import numpy as np from pathlib import Path @@ -565,7 +565,7 @@ class EnergySystemRetrofitReport: placement='H') self.report.add_section(f'{self.retrofit_scenario}') self.report.add_subsection('Proposed Systems') - self.energy_system_archetype_schematic() + # self.energy_system_archetype_schematic() if 'PV' in self.retrofit_scenario: self.report.add_subsection('Rooftop Photovoltaic System Implementation') self.pv_system() diff --git a/scripts/energy_system_retrofit_results.py b/energy_system_modelling_package/energy_system_retrofit/energy_system_retrofit_results.py similarity index 100% rename from scripts/energy_system_retrofit_results.py rename to energy_system_modelling_package/energy_system_retrofit/energy_system_retrofit_results.py diff --git a/scripts/energy_system_sizing.py b/energy_system_modelling_package/energy_system_sizing.py similarity index 100% rename from scripts/energy_system_sizing.py rename to energy_system_modelling_package/energy_system_sizing.py diff --git a/scripts/energy_system_sizing_and_simulation_factory.py b/energy_system_modelling_package/energy_system_sizing_and_simulation_factory.py similarity index 80% rename from scripts/energy_system_sizing_and_simulation_factory.py rename to energy_system_modelling_package/energy_system_sizing_and_simulation_factory.py index 9a0e14bb..ac94862e 100644 --- a/scripts/energy_system_sizing_and_simulation_factory.py +++ b/energy_system_modelling_package/energy_system_sizing_and_simulation_factory.py @@ -5,10 +5,10 @@ Copyright © 2022 Concordia CERC group Project Coder Saeed Ranjbar saeed.ranjbar@mail.concordia.ca """ -from scripts.system_simulation_models.archetype13 import Archetype13 -from scripts.system_simulation_models.archetype13_stratified_tes import Archetype13Stratified -from scripts.system_simulation_models.archetype1 import Archetype1 -from scripts.system_simulation_models.archetypes14_15 import Archetype14_15 +from energy_system_modelling_package.system_simulation_models.archetype13 import Archetype13 +from energy_system_modelling_package.system_simulation_models.archetype13_stratified_tes import Archetype13Stratified +from energy_system_modelling_package.system_simulation_models.archetype1 import Archetype1 +from energy_system_modelling_package.system_simulation_models.archetypes14_15 import Archetype14_15 class EnergySystemsSimulationFactory: diff --git a/scripts/random_assignation.py b/energy_system_modelling_package/random_assignation.py similarity index 77% rename from scripts/random_assignation.py rename to energy_system_modelling_package/random_assignation.py index a478c35c..390a0948 100644 --- a/scripts/random_assignation.py +++ b/energy_system_modelling_package/random_assignation.py @@ -28,13 +28,21 @@ residential_systems_percentage = {'system 1 gas': 15, 'system 8 gas': 15, 'system 8 electricity': 35} -residential_new_systems_percentage = {'PV+ASHP+GasBoiler+TES': 0, - 'PV+4Pipe+DHW': 100, - 'PV+ASHP+ElectricBoiler+TES': 0, - 'PV+GSHP+GasBoiler+TES': 0, - 'PV+GSHP+ElectricBoiler+TES': 0, - 'PV+WSHP+GasBoiler+TES': 0, - 'PV+WSHP+ElectricBoiler+TES': 0} +residential_new_systems_percentage = { + 'Central 4 Pipes Air to Water Heat Pump and Gas Boiler with Independent Water Heating and PV': 100, + 'Central 4 Pipes Air to Water Heat Pump and electrical Boiler with Independent Water Heating and PV': 0, + 'Central 4 Pipes Ground to Water Heat Pump and Gas Boiler with Independent Water Heating and PV': 0, + 'Central 4 Pipes Ground to Water Heat Pump and electrical Boiler with Independent Water Heating and PV': 0, + 'Central 4 Pipes Water to Water Heat Pump and Gas Boiler with Independent Water Heating and PV': 0, + 'Central 4 Pipes Water to Water Heat Pump and electrical Boiler with Independent Water Heating and PV': 0, + 'Central 4 Pipes Air to Water Heat Pump and Gas Boiler with Independent Water Heating': 0, + 'Central 4 Pipes Air to Water Heat Pump and electrical Boiler with Independent Water Heating': 0, + 'Central 4 Pipes Ground to Water Heat Pump and Gas Boiler with Independent Water Heating': 0, + 'Central 4 Pipes Ground to Water Heat Pump and electrical Boiler with Independent Water Heating': 0, + 'Central 4 Pipes Water to Water Heat Pump and Gas Boiler with Independent Water Heating': 0, + 'Central 4 Pipes Water to Water Heat Pump and electrical Boiler with Independent Water Heating': 0, + 'Rooftop PV System': 0 +} non_residential_systems_percentage = {'system 1 gas': 0, 'system 1 electricity': 0, diff --git a/scripts/report_creation.py b/energy_system_modelling_package/report_creation.py similarity index 100% rename from scripts/report_creation.py rename to energy_system_modelling_package/report_creation.py diff --git a/energy_system_retrofit.py b/energy_system_retrofit.py index 36c74439..22c5586b 100644 --- a/energy_system_retrofit.py +++ b/energy_system_retrofit.py @@ -1,26 +1,25 @@ from pathlib import Path import subprocess -from scripts.ep_run_enrich import energy_plus_workflow +from building_modelling.ep_run_enrich import energy_plus_workflow +from energy_system_modelling_package.energy_system_modelling_factories.montreal_energy_system_archetype_modelling_factory import \ + MontrealEnergySystemArchetypesSimulationFactory +from energy_system_modelling_package.energy_system_modelling_factories.pv_assessment.pv_feasibility import \ + pv_feasibility from hub.imports.geometry_factory import GeometryFactory from hub.helpers.dictionaries import Dictionaries from hub.imports.construction_factory import ConstructionFactory from hub.imports.usage_factory import UsageFactory from hub.imports.weather_factory import WeatherFactory from hub.imports.results_factory import ResultFactory -from scripts.energy_system_retrofit_report import EnergySystemRetrofitReport -from scripts.geojson_creator import process_geojson -from scripts import random_assignation +from energy_system_modelling_package.energy_system_retrofit.energy_system_retrofit_report import EnergySystemRetrofitReport +from building_modelling.geojson_creator import process_geojson +from energy_system_modelling_package import random_assignation from hub.imports.energy_systems_factory import EnergySystemsFactory -from scripts.energy_system_sizing import SystemSizing -from scripts.solar_angles import CitySolarAngles -from scripts.pv_sizing_and_simulation import PVSizingSimulation -from scripts.energy_system_retrofit_results import consumption_data, cost_data -from scripts.energy_system_sizing_and_simulation_factory import EnergySystemsSimulationFactory -from scripts.costs.cost import Cost -from scripts.costs.constants import SKIN_RETROFIT_AND_SYSTEM_RETROFIT_AND_PV, SYSTEM_RETROFIT_AND_PV, CURRENT_STATUS -import hub.helpers.constants as cte +from energy_system_modelling_package.energy_system_modelling_factories.energy_system_sizing_factory import EnergySystemsSizingFactory +from energy_system_modelling_package.energy_system_retrofit.energy_system_retrofit_results import consumption_data, cost_data +from costing_package.cost import Cost +from costing_package.constants import SYSTEM_RETROFIT_AND_PV, CURRENT_STATUS from hub.exports.exports_factory import ExportsFactory -from scripts.pv_feasibility import pv_feasibility # Specify the GeoJSON file path input_files_path = (Path(__file__).parent / 'input_files') @@ -52,14 +51,9 @@ subprocess.run(['sra', str(sra_path)]) 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() +EnergySystemsSizingFactory('peak_load_sizing', city).enrich() current_status_energy_consumption = consumption_data(city) current_status_life_cycle_cost = {} for building in city.buildings: @@ -71,18 +65,12 @@ for building in city.buildings: current_status_life_cycle_cost[f'{building.name}'] = cost_data(building, lcc_dataframe, cost_retrofit_scenario) random_assignation.call_random(city.buildings, random_assignation.residential_new_systems_percentage) EnergySystemsFactory('montreal_future', city).enrich() +EnergySystemsSizingFactory('pv_sizing', city).enrich() +EnergySystemsSizingFactory('peak_load_sizing', 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() + MontrealEnergySystemArchetypesSimulationFactory(f'archetype_cluster_{building.energy_systems_archetype_cluster_id}', + building, + simulation_results_path).enrich() retrofitted_energy_consumption = consumption_data(city) retrofitted_life_cycle_cost = {} for building in city.buildings: diff --git a/hub/catalog_factories/data_models/energy_systems/archetype.py b/hub/catalog_factories/data_models/energy_systems/archetype.py index 84bcef9a..7115767d 100644 --- a/hub/catalog_factories/data_models/energy_systems/archetype.py +++ b/hub/catalog_factories/data_models/energy_systems/archetype.py @@ -15,11 +15,20 @@ class Archetype: """ Archetype class """ - def __init__(self, name, systems): + def __init__(self, name, systems, archetype_cluster_id=None): + self._cluster_id = archetype_cluster_id self._name = name self._systems = systems + @property + def cluster_id(self): + """ + Get id + :return: string + """ + return self._cluster_id + @property def name(self): """ @@ -43,8 +52,9 @@ class Archetype: _systems.append(_system.to_dictionary()) content = { 'Archetype': { + 'cluster_id': self.cluster_id, 'name': self.name, 'systems': _systems - } } + } return content diff --git a/hub/catalog_factories/data_models/energy_systems/pv_generation_system.py b/hub/catalog_factories/data_models/energy_systems/pv_generation_system.py index 87228afa..68ac1460 100644 --- a/hub/catalog_factories/data_models/energy_systems/pv_generation_system.py +++ b/hub/catalog_factories/data_models/energy_systems/pv_generation_system.py @@ -17,8 +17,9 @@ class PvGenerationSystem(GenerationSystem): def __init__(self, system_id, name, system_type, model_name=None, manufacturer=None, electricity_efficiency=None, nominal_electricity_output=None, nominal_ambient_temperature=None, nominal_cell_temperature=None, nominal_radiation=None, standard_test_condition_cell_temperature=None, - standard_test_condition_maximum_power=None, cell_temperature_coefficient=None, width=None, height=None, - distribution_systems=None, energy_storage_systems=None): + standard_test_condition_maximum_power=None, standard_test_condition_radiation=None, + cell_temperature_coefficient=None, width=None, height=None, distribution_systems=None, + energy_storage_systems=None): super().__init__(system_id=system_id, name=name, model_name=model_name, manufacturer=manufacturer, fuel_type='renewable', distribution_systems=distribution_systems, energy_storage_systems=energy_storage_systems) @@ -30,6 +31,7 @@ class PvGenerationSystem(GenerationSystem): self._nominal_radiation = nominal_radiation self._standard_test_condition_cell_temperature = standard_test_condition_cell_temperature self._standard_test_condition_maximum_power = standard_test_condition_maximum_power + self._standard_test_condition_radiation = standard_test_condition_radiation self._cell_temperature_coefficient = cell_temperature_coefficient self._width = width self._height = height @@ -98,6 +100,15 @@ class PvGenerationSystem(GenerationSystem): """ return self._standard_test_condition_maximum_power + @property + def standard_test_condition_radiation(self): + """ + Get standard test condition cell temperature of PV panels in W/m2 + :return: float + """ + return self._standard_test_condition_radiation + + @property def cell_temperature_coefficient(self): """ @@ -143,6 +154,7 @@ class PvGenerationSystem(GenerationSystem): 'nominal radiation [W/m2]': self.nominal_radiation, 'standard test condition cell temperature [Celsius]': self.standard_test_condition_cell_temperature, 'standard test condition maximum power [W]': self.standard_test_condition_maximum_power, + 'standard test condition radiation [W/m2]': self.standard_test_condition_radiation, 'cell temperature coefficient': self.cell_temperature_coefficient, 'width': self.width, 'height': self.height, 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 a4477ba2..4a9672ad 100644 --- a/hub/catalog_factories/energy_systems/montreal_future_system_catalogue.py +++ b/hub/catalog_factories/energy_systems/montreal_future_system_catalogue.py @@ -193,6 +193,7 @@ class MontrealFutureSystemCatalogue(Catalog): nominal_radiation = pv['nominal_radiation'] standard_test_condition_cell_temperature = pv['standard_test_condition_cell_temperature'] standard_test_condition_maximum_power = pv['standard_test_condition_maximum_power'] + standard_test_condition_radiation = pv['standard_test_condition_radiation'] cell_temperature_coefficient = pv['cell_temperature_coefficient'] width = pv['width'] height = pv['height'] @@ -215,6 +216,7 @@ class MontrealFutureSystemCatalogue(Catalog): standard_test_condition_cell_temperature= standard_test_condition_cell_temperature, standard_test_condition_maximum_power=standard_test_condition_maximum_power, + standard_test_condition_radiation=standard_test_condition_radiation, cell_temperature_coefficient=cell_temperature_coefficient, width=width, height=height, @@ -379,6 +381,7 @@ class MontrealFutureSystemCatalogue(Catalog): _system_archetypes = [] system_clusters = self._archetypes['EnergySystemCatalog']['system_archetypes']['system_archetype'] for system_cluster in system_clusters: + archetype_id = system_cluster['@cluster_id'] name = system_cluster['name'] systems = system_cluster['systems']['system_id'] integer_system_ids = [int(item) for item in systems] @@ -386,7 +389,7 @@ class MontrealFutureSystemCatalogue(Catalog): for system_archetype in self._systems: if int(system_archetype.id) in integer_system_ids: _systems.append(system_archetype) - _system_archetypes.append(Archetype(name=name, systems=_systems)) + _system_archetypes.append(Archetype(archetype_cluster_id=archetype_id, name=name, systems=_systems)) return _system_archetypes def _load_materials(self): diff --git a/hub/city_model_structure/building.py b/hub/city_model_structure/building.py index 132e7a5c..c5fb36c8 100644 --- a/hub/city_model_structure/building.py +++ b/hub/city_model_structure/building.py @@ -92,7 +92,7 @@ class Building(CityObject): logging.error('Building %s [%s] has an unexpected surface type %s.', self.name, self.aliases, surface.type) self._domestic_hot_water_peak_load = None self._fuel_consumption_breakdown = {} - self._pv_generation = {} + self._systems_archetype_cluster_id = None @property def shell(self) -> Polyhedron: @@ -857,8 +857,8 @@ class Building(CityObject): storage_systems = generation_system.energy_storage_systems if storage_systems: for storage_system in storage_systems: - if storage_system.type_energy_stored == 'thermal' and storage_system.heating_coil_energy_consumption: - fuel_breakdown[cte.ELECTRICITY][f'{demand_type}'] += storage_system.heating_coil_energy_consumption[cte.YEAR][0] + if storage_system.type_energy_stored == 'thermal' and storage_system.heating_coil_capacity is not None: + fuel_breakdown[cte.ELECTRICITY][f'{demand_type}'] += storage_system.heating_coil_energy_consumption[f'{demand_type}'][cte.YEAR][0] #TODO: When simulation models of all energy system archetypes are created, this part can be removed heating_fuels = [] dhw_fuels = [] @@ -890,3 +890,19 @@ class Building(CityObject): self._fuel_consumption_breakdown = fuel_breakdown return self._fuel_consumption_breakdown + @property + def energy_systems_archetype_cluster_id(self): + """ + Get energy systems archetype id + :return: str + """ + return self._systems_archetype_cluster_id + + @energy_systems_archetype_cluster_id.setter + def energy_systems_archetype_cluster_id(self, value): + """ + Set energy systems archetype id + :param value: str + """ + self._systems_archetype_cluster_id = value + diff --git a/hub/city_model_structure/building_demand/surface.py b/hub/city_model_structure/building_demand/surface.py index 65f90ae3..f054e577 100644 --- a/hub/city_model_structure/building_demand/surface.py +++ b/hub/city_model_structure/building_demand/surface.py @@ -18,7 +18,6 @@ from hub.city_model_structure.attributes.point import Point from hub.city_model_structure.greenery.vegetation import Vegetation from hub.city_model_structure.building_demand.thermal_boundary import ThermalBoundary import hub.helpers.constants as cte -from hub.helpers.configuration_helper import ConfigurationHelper class Surface: @@ -157,6 +156,7 @@ class Surface: if self._inclination is None: self._inclination = np.arccos(self.perimeter_polygon.normal[2]) return self._inclination + @property def type(self): """ @@ -167,10 +167,10 @@ class Surface: """ if self._type is None: inclination_cos = math.cos(self.inclination) - # 170 degrees + # 170 degrees if inclination_cos <= -0.98: self._type = 'Ground' - # between 80 and 100 degrees + # between 80 and 100 degrees elif abs(inclination_cos) <= 0.17: self._type = 'Wall' else: @@ -365,12 +365,12 @@ class Surface: _protected_building_restriction = 1 # 10 degrees range if abs(math.sin(self.inclination)) < 0.17: - # horizontal + # horizontal _construction_restriction = 0.8 _separation_of_panels = 0.46 _shadow_between_panels = 0.7 else: - # tilted + # tilted _construction_restriction = 0.9 _separation_of_panels = 0.9 _shadow_between_panels = 1 @@ -417,4 +417,4 @@ class Surface: Set installed solar collector area in m2 :return: dict """ - self._installed_solar_collector_area = value \ No newline at end of file + self._installed_solar_collector_area = value diff --git a/hub/city_model_structure/energy_systems/thermal_storage_system.py b/hub/city_model_structure/energy_systems/thermal_storage_system.py index fd9b8f81..fc25e5a8 100644 --- a/hub/city_model_structure/energy_systems/thermal_storage_system.py +++ b/hub/city_model_structure/energy_systems/thermal_storage_system.py @@ -24,7 +24,7 @@ class ThermalStorageSystem(EnergyStorageSystem): self._maximum_operating_temperature = None self._heating_coil_capacity = None self._temperature = None - self._heating_coil_energy_consumption = None + self._heating_coil_energy_consumption = {} @property def volume(self): diff --git a/hub/data/energy_systems/montreal_future_systems.xml b/hub/data/energy_systems/montreal_future_systems.xml index 3ef396d5..a1de1af4 100644 --- a/hub/data/energy_systems/montreal_future_systems.xml +++ b/hub/data/energy_systems/montreal_future_systems.xml @@ -457,6 +457,7 @@ + 2.01 @@ -911,7 +912,7 @@ - 4.5 + 4 @@ -962,7 +963,7 @@ - + 5 @@ -993,7 +994,7 @@ - 3.5 + 4 True electricity Water @@ -1001,7 +1002,7 @@ - + 6 @@ -1035,9 +1036,10 @@ + - 1.0 + 2.0 1.0 @@ -1180,7 +1182,8 @@ 1 90.0 - + + 2 0 1.5 @@ -1309,89 +1312,6 @@ 1 - 4 pipe storage equipped air source heat pump and gas boiler - schemas/ASHP+TES+GasBoiler.jpg - - heating - cooling - - - 21 - 18 - - - - 2 - 4 pipe storage equipped air source heat pump and electrical boiler - schemas/ASHP+TES+GasBoiler.jpg - - heating - cooling - domestic_hot_water - - - 22 - 18 - - - - 3 - 4 pipe storage equipped ground source heat pump and gas boiler - schemas/GSHP+TES+GasBoiler.jpg - - heating - cooling - domestic_hot_water - - - 21 - 19 - - - - 4 - 4 pipe storage equipped ground source heat pump and electrical boiler - schemas/GSHP+TES+ElectricBoiler.jpg - - heating - cooling - domestic_hot_water - - - 22 - 19 - - - - 5 - 4 pipe storage equipped ground source heat pump and gas boiler - schemas/WSHP+TES+GasBoiler.jpg - - heating - cooling - domestic_hot_water - - - 21 - 20 - - - - 6 - 4 pipe storage equipped ground source heat pump and electrical boiler - schemas/WSHP+TES+ElectricBoiler.jpg - - heating - cooling - domestic_hot_water - - - 22 - 20 - - - - 7 Photovoltaic System schemas/PV.jpg @@ -1402,8 +1322,8 @@ - 8 - 4 pipe system with air source heat pump storage and gas boiler + 2 + 4 pipe central air to water heat pump with storage tank and gas boiler schemas/ASHP+TES+GasBoiler.jpg heating @@ -1415,20 +1335,177 @@ - 9 - 4 pipe system with air source heat pump storage and electric boiler + 3 + 4 pipe central air to water heat pump with storage tank and electric boiler schemas/ASHP+TES+GasBoiler.jpg heating cooling - 22 - 18 + 23 + 17 + + + + 4 + 4 pipe central ground to water heat pump with storage tank and gas boiler + schemas/ASHP+TES+GasBoiler.jpg + + heating + cooling + + + 24 + 16 + + + + 5 + 4 pipe central ground to water heat pump with storage tank and electric boiler + schemas/ASHP+TES+GasBoiler.jpg + + heating + cooling + + + 24 + 17 + + + + 6 + 4 pipe central water to water heat pump with storage tank and gas boiler + schemas/ASHP+TES+GasBoiler.jpg + + heating + cooling + + + 25 + 16 + + + + 7 + 4 pipe central water to water heat pump with storage tank and electric boiler + schemas/ASHP+TES+GasBoiler.jpg + + heating + cooling + + + 25 + 17 + + + + 8 + district heating network with air to water heat pump gas boiler thermal storage tank + schemas/ASHP+TES+GasBoiler.jpg + + heating + + + 23 + 16 + + + + 9 + district heating network with air to water heat pump electrical boiler thermal storage tank + schemas/ASHP+TES+GasBoiler.jpg + + heating + + + 23 + 17 10 + district heating network with ground to water heat pump gas boiler thermal storage tank + schemas/ASHP+TES+GasBoiler.jpg + + heating + + + 24 + 16 + + + + 11 + district heating network with ground to water heat pump electrical boiler thermal storage tank + schemas/ASHP+TES+GasBoiler.jpg + + heating + + + 24 + 17 + + + + 12 + district heating network with water to water heat pump gas boiler thermal storage tank + schemas/ASHP+TES+GasBoiler.jpg + + heating + + + 25 + 16 + + + + 13 + district heating network with water to water heat pump electrical boiler thermal storage tank + schemas/ASHP+TES+GasBoiler.jpg + + heating + + + 25 + 17 + + + + 14 + Unitary air to water heat pump cooling system + schemas/ASHP+TES+GasBoiler.jpg + + cooling + + + 23 + + + + 15 + Unitary ground to water heat pump cooling system + schemas/ASHP+TES+GasBoiler.jpg + + cooling + + + 24 + + + + 16 + unitary water to water heat pump cooling system + schemas/ASHP+TES+GasBoiler.jpg + + cooling + + + 25 + + + + 17 Domestic Hot Water Heat Pump with Coiled Storage schemas/ASHP+TES+GasBoiler.jpg @@ -1438,134 +1515,103 @@ 27 - - 11 - Central Heating System َASHP Gas-Boiler TES - schemas/ASHP+TES+GasBoiler.jpg - - heating - - - 23 - 16 - - - - 12 - Unitary ASHP Cooling System - schemas/ASHP+TES+GasBoiler.jpg - - cooling - - - 23 - - - - PV+ASHP+GasBoiler+TES - - 7 - 1 - 10 - - - - PV+ASHP+ElectricBoiler+TES - - 7 - 2 - - - - PV+GSHP+GasBoiler+TES - - 7 - 3 - - - - PV+GSHP+ElectricBoiler+TES - - 7 - 4 - - - - PV+WSHP+GasBoiler+TES - - 7 - 5 - - - - PV+WSHP+ElectricBoiler+TES - - 7 - 6 - - - - ASHP+GasBoiler+TES + + Central 4 Pipes Air to Water Heat Pump and Gas Boiler with Independent Water Heating and PV 1 + 2 + 17 - - ASHP+ElectricBoiler+TES + + Central 4 Pipes Air to Water Heat Pump and electrical Boiler with Independent Water Heating and PV + + 1 + 3 + 8 + + + + Central 4 Pipes Ground to Water Heat Pump and Gas Boiler with Independent Water Heating and PV + + 1 + 4 + 17 + + + + Central 4 Pipes Ground to Water Heat Pump and electrical Boiler with Independent Water Heating and PV + + 1 + 5 + 17 + + + + Central 4 Pipes Water to Water Heat Pump and Gas Boiler with Independent Water Heating and PV + + 1 + 6 + 17 + + + + Central 4 Pipes Water to Water Heat Pump and electrical Boiler with Independent Water Heating and PV + + 1 + 7 + 17 + + + + Central 4 Pipes Air to Water Heat Pump and Gas Boiler with Independent Water Heating 2 + 17 - - GSHP+GasBoiler+TES + + Central 4 Pipes Air to Water Heat Pump and electrical Boiler with Independent Water Heating 3 + 17 - - GSHP+ElectricBoiler+TES + + Central 4 Pipes Ground to Water Heat Pump and Gas Boiler with Independent Water Heating 4 + 17 - - WSHP+GasBoiler+TES + + Central 4 Pipes Ground to Water Heat Pump and electrical Boiler with Independent Water Heating 5 + 17 - - WSHP+ElectricBoiler+TES + + Central 4 Pipes Water to Water Heat Pump and Gas Boiler with Independent Water Heating 6 + 17 - - PV+4Pipe+DHW + + Central 4 Pipes Water to Water Heat Pump and electrical Boiler with Independent Water Heating 7 - 8 - 10 + 17 - - Central Heating+Unitary Cooling+Unitary DHW + + Rooftop PV System - 10 - 11 - 12 - - - - Central Heating+Unitary Cooling+Unitary DHW+PV - - 7 - 10 - 11 - 12 + 1 diff --git a/hub/exports/building_energy/idf.py b/hub/exports/building_energy/idf.py index cf1646e3..93b03cdf 100644 --- a/hub/exports/building_energy/idf.py +++ b/hub/exports/building_energy/idf.py @@ -7,6 +7,9 @@ Code contributors: Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concord Oriol Gavalda Torrellas oriol.gavalda@concordia.ca """ import copy +import datetime +import glob +import os from pathlib import Path from geomeppy import IDF import hub.helpers.constants as cte @@ -275,11 +278,12 @@ class Idf: _kwargs[f'Field_{counter + 2}'] = 'Until: 24:00,0.0' self._idf.newidfobject(self._COMPACT_SCHEDULE, **_kwargs) - def _write_schedules_file(self, usage, schedule): - file_name = str((Path(self._output_path) / f'{schedule.type} schedules {usage}.csv').resolve()) - with open(file_name, 'w', encoding='utf8') as file: - for value in schedule.values: - file.write(f'{str(value)},\n') + def _write_schedules_file(self, schedule, usage): + file_name = str((Path(self._output_path) / f'{schedule.type} schedules {usage.replace("/","_")}.csv').resolve()) + if not Path(file_name).exists(): + with open(file_name, 'w', encoding='utf8') as file: + for value in schedule.values: + file.write(f'{str(value)},\n') return Path(file_name).name def _add_file_schedule(self, usage, schedule, file_name): @@ -304,7 +308,7 @@ class Idf: for schedule in self._idf.idfobjects[self._FILE_SCHEDULE]: if schedule.Name == f'{schedule_type} schedules {usage}': return - file_name = self._write_schedules_file(usage, new_schedules[0]) + file_name = self._write_schedules_file(new_schedules[0], usage) self._add_file_schedule(usage, new_schedules[0], file_name) return @@ -321,7 +325,7 @@ class Idf: if construction.Name == vegetation_name: return else: - if construction.Name == thermal_boundary.construction_name: + if construction.Name == f'{thermal_boundary.construction_name} {thermal_boundary.parent_surface.type}': return if thermal_boundary.layers is None: for material in self._idf.idfobjects[self._MATERIAL]: @@ -340,7 +344,8 @@ class Idf: for i in range(0, len(layers) - 1): _kwargs[f'Layer_{i + 2}'] = layers[i].material_name else: - _kwargs = {'Name': thermal_boundary.construction_name, 'Outside_Layer': layers[0].material_name} + _kwargs = {'Name': f'{thermal_boundary.construction_name} {thermal_boundary.parent_surface.type}', + 'Outside_Layer': layers[0].material_name} for i in range(1, len(layers) - 1): _kwargs[f'Layer_{i + 1}'] = layers[i].material_name self._idf.newidfobject(self._CONSTRUCTION, **_kwargs) @@ -470,7 +475,7 @@ class Idf: Air_Changes_per_Hour=_air_change ) - def _add_dhw(self, thermal_zone, zone_name): + def _add_dhw(self, thermal_zone, zone_name, usage): peak_flow_rate = thermal_zone.domestic_hot_water.peak_flow * thermal_zone.total_floor_area self._idf.newidfobject(self._DHW, Name=f'DHW {zone_name}', @@ -478,7 +483,7 @@ class Idf: Flow_Rate_Fraction_Schedule_Name=f'DHW_prof schedules {thermal_zone.usage_name}', Target_Temperature_Schedule_Name=f'DHW_temp schedules {thermal_zone.usage_name}', Hot_Water_Supply_Temperature_Schedule_Name=f'DHW_temp schedules {thermal_zone.usage_name}', - Cold_Water_Supply_Temperature_Schedule_Name=f'cold_temp schedules {zone_name}', + Cold_Water_Supply_Temperature_Schedule_Name=f'cold_temp schedules {usage}', EndUse_Subcategory=f'DHW {zone_name}', Zone_Name=zone_name ) @@ -512,19 +517,25 @@ class Idf: self._rename_building(self._city.name) self._lod = self._city.level_of_detail.geometry for building in self._city.buildings: + is_target = building.name in self._target_buildings or building.name in self._adjacent_buildings for internal_zone in building.internal_zones: if internal_zone.thermal_zones_from_internal_zones is None: + self._target_buildings.remoidf_surface_typeve(building.name) + is_target = False continue for thermal_zone in internal_zone.thermal_zones_from_internal_zones: - for thermal_boundary in thermal_zone.thermal_boundaries: + for thermal_boundary in thermal_zone.thermal_boundaries: self._add_construction(thermal_boundary) if thermal_boundary.parent_surface.vegetation is not None: self._add_vegetation_material(thermal_boundary.parent_surface.vegetation) for thermal_opening in thermal_boundary.thermal_openings: self._add_window_construction_and_material(thermal_opening) - usage = thermal_zone.usage_name - if building.name in self._target_buildings or building.name in self._adjacent_buildings: + + if is_target: + start = datetime.datetime.now() + service_temperature = thermal_zone.domestic_hot_water.service_temperature + usage = thermal_zone.usage_name _new_schedules = self._create_infiltration_schedules(thermal_zone) self._add_schedules(usage, 'Infiltration', _new_schedules) _new_schedules = self._create_ventilation_schedules(thermal_zone) @@ -536,11 +547,10 @@ class Idf: self._add_schedules(usage, 'Lighting', thermal_zone.lighting.schedules) self._add_schedules(usage, 'Appliance', thermal_zone.appliances.schedules) self._add_schedules(usage, 'DHW_prof', thermal_zone.domestic_hot_water.schedules) - _new_schedules = self._create_yearly_values_schedules('cold_temp', - building.cold_water_temperature[cte.HOUR]) - self._add_schedules(building.name, 'cold_temp', _new_schedules) - value = thermal_zone.domestic_hot_water.service_temperature - _new_schedules = self._create_constant_value_schedules('DHW_temp', value) + _new_schedules = self._create_yearly_values_schedules('cold_temp', building.cold_water_temperature[cte.HOUR]) + self._add_schedules(usage, 'cold_temp', _new_schedules) + + _new_schedules = self._create_constant_value_schedules('DHW_temp', service_temperature) self._add_schedules(usage, 'DHW_temp', _new_schedules) _occ = thermal_zone.occupancy if _occ.occupancy_density == 0: @@ -557,11 +567,13 @@ class Idf: self._add_occupancy(thermal_zone, building.name) self._add_lighting(thermal_zone, building.name) self._add_appliances(thermal_zone, building.name) - self._add_dhw(thermal_zone, building.name) + self._add_dhw(thermal_zone, building.name, usage) if self._export_type == "Surfaces": - if building.name in self._target_buildings or building.name in self._adjacent_buildings: + if is_target: if building.thermal_zones_from_internal_zones is not None: + start = datetime.datetime.now() self._add_surfaces(building, building.name) + print(f'add surfaces {datetime.datetime.now() - start}') else: self._add_pure_geometry(building, building.name) else: @@ -717,7 +729,7 @@ class Idf: if boundary.parent_surface.vegetation is not None: construction_name = f'{boundary.construction_name}_{boundary.parent_surface.vegetation.name}' else: - construction_name = boundary.construction_name + construction_name = f'{boundary.construction_name} {boundary.parent_surface.type}' _kwargs['Construction_Name'] = construction_name surface = self._idf.newidfobject(self._SURFACE, **_kwargs) diff --git a/hub/exports/building_energy/insel/insel_monthly_energy_balance.py b/hub/exports/building_energy/insel/insel_monthly_energy_balance.py index f14ec946..329e9a13 100644 --- a/hub/exports/building_energy/insel/insel_monthly_energy_balance.py +++ b/hub/exports/building_energy/insel/insel_monthly_energy_balance.py @@ -270,7 +270,7 @@ class InselMonthlyEnergyBalance: global_irradiance = surface.global_irradiance[cte.MONTH] for j in range(0, len(global_irradiance)): parameters.append(f'{j + 1} ' - f'{global_irradiance[j] * cte.WATTS_HOUR_TO_JULES / 24 / _NUMBER_DAYS_PER_MONTH[j]}') + f'{global_irradiance[j] / 24 / _NUMBER_DAYS_PER_MONTH[j]}') else: for j in range(0, 12): parameters.append(f'{j + 1} 0.0') diff --git a/hub/helpers/constants.py b/hub/helpers/constants.py index ad32c835..f9f07776 100644 --- a/hub/helpers/constants.py +++ b/hub/helpers/constants.py @@ -310,7 +310,8 @@ LATENT = 'Latent' LITHIUMION = 'Lithium Ion' NICD = 'NiCd' LEADACID = 'Lead Acid' - +THERMAL = 'thermal' +ELECTRICAL = 'electrical' # Geometry EPSILON = 0.0000001 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 b5628d9f..1b1638f1 100644 --- a/hub/imports/energy_systems/montreal_future_energy_systems_parameters.py +++ b/hub/imports/energy_systems/montreal_future_energy_systems_parameters.py @@ -43,6 +43,7 @@ class MontrealFutureEnergySystemParameters: archetype_name = building.energy_systems_archetype_name try: archetype = self._search_archetypes(montreal_custom_catalog, archetype_name) + building.energy_systems_archetype_cluster_id = archetype.cluster_id except KeyError: logging.error('Building %s has unknown energy system archetype for system name %s', building.name, archetype_name) @@ -103,15 +104,21 @@ class MontrealFutureEnergySystemParameters: _generation_system.nominal_radiation = archetype_generation_system.nominal_radiation _generation_system.standard_test_condition_cell_temperature = archetype_generation_system.standard_test_condition_cell_temperature _generation_system.standard_test_condition_maximum_power = archetype_generation_system.standard_test_condition_maximum_power + _generation_system.standard_test_condition_radiation = archetype_generation_system.standard_test_condition_radiation _generation_system.cell_temperature_coefficient = archetype_generation_system.cell_temperature_coefficient _generation_system.width = archetype_generation_system.width _generation_system.height = archetype_generation_system.height _generation_system.tilt_angle = self._city.latitude _generic_storage_system = None if archetype_generation_system.energy_storage_systems is not None: - _generic_storage_system = ElectricalStorageSystem() - _generic_storage_system.type_energy_stored = 'electrical' - _generation_system.energy_storage_systems = [_generic_storage_system] + _storage_systems = [] + for storage_system in archetype_generation_system.energy_storage_systems: + if storage_system.type_energy_stored == 'electrical': + _generic_storage_system = ElectricalStorageSystem() + _generic_storage_system.type_energy_stored = 'electrical' + _storage_systems.append(_generic_storage_system) + _generation_system.energy_storage_systems = _storage_systems + else: _generation_system = NonPvGenerationSystem() _generation_system.name = archetype_generation_system.name diff --git a/hub/imports/energy_systems/north_america_custom_energy_system_parameters.py b/hub/imports/energy_systems/north_america_custom_energy_system_parameters.py deleted file mode 100644 index 92eb866e..00000000 --- a/hub/imports/energy_systems/north_america_custom_energy_system_parameters.py +++ /dev/null @@ -1,157 +0,0 @@ -""" -Energy System catalog heat generation system -SPDX - License - Identifier: LGPL - 3.0 - or -later -Copyright © 2023 Concordia CERC group -Project Coder Saeed Ranjbar saeed.ranjbar@concordia.ca -Code contributors: Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca -""" - -import logging -import copy - -from hub.catalog_factories.energy_systems_catalog_factory import EnergySystemsCatalogFactory -from hub.city_model_structure.energy_systems.energy_system import EnergySystem -from hub.city_model_structure.energy_systems.distribution_system import DistributionSystem -from hub.city_model_structure.energy_systems.non_pv_generation_system import NonPvGenerationSystem -from hub.city_model_structure.energy_systems.pv_generation_system import PvGenerationSystem -from hub.city_model_structure.energy_systems.electrical_storage_system import ElectricalStorageSystem -from hub.city_model_structure.energy_systems.thermal_storage_system import ThermalStorageSystem -from hub.city_model_structure.energy_systems.emission_system import EmissionSystem -from hub.helpers.dictionaries import Dictionaries - - -class NorthAmericaCustomEnergySystemParameters: - """ - MontrealCustomEnergySystemParameters class - """ - - def __init__(self, city): - self._city = city - - def enrich_buildings(self): - """ - Returns the city with the system parameters assigned to the buildings - :return: - """ - city = self._city - montreal_custom_catalog = EnergySystemsCatalogFactory('north_america').catalog - if city.generic_energy_systems is None: - _generic_energy_systems = {} - else: - _generic_energy_systems = city.generic_energy_systems - for building in city.buildings: - archetype_name = building.energy_systems_archetype_name - try: - archetype = self._search_archetypes(montreal_custom_catalog, archetype_name) - except KeyError: - logging.error('Building %s has unknown energy system archetype for system name %s', building.name, - archetype_name) - continue - - if archetype.name not in _generic_energy_systems: - _generic_energy_systems = self._create_generic_systems_list(archetype, _generic_energy_systems) - - city.generic_energy_systems = _generic_energy_systems - - self._assign_energy_systems_to_buildings(city) - - @staticmethod - def _search_archetypes(catalog, name): - archetypes = catalog.entries('archetypes') - for building_archetype in archetypes: - if str(name) == str(building_archetype.name): - return building_archetype - raise KeyError('archetype not found') - - def _create_generic_systems_list(self, archetype, _generic_energy_systems): - building_systems = [] - for archetype_system in archetype.systems: - energy_system = EnergySystem() - _hub_demand_types = [] - for demand_type in archetype_system.demand_types: - _hub_demand_types.append(Dictionaries().montreal_demand_type_to_hub_energy_demand_type[demand_type]) - energy_system.name = archetype_system.name - energy_system.demand_types = _hub_demand_types - energy_system.configuration_schema = archetype_system.configuration_schema - energy_system.generation_systems = self._create_generation_systems(archetype_system) - if energy_system.distribution_systems is not None: - energy_system.distribution_systems = self._create_distribution_systems(archetype_system) - building_systems.append(energy_system) - - _generic_energy_systems[archetype.name] = building_systems - - return _generic_energy_systems - - @staticmethod - def _create_generation_systems(archetype_system): - _generation_systems = [] - for archetype_generation_system in archetype_system.generation_systems: - if archetype_generation_system.system_type == 'PV system': - _generation_system = PvGenerationSystem() - _type = 'PV system' - _generation_system.system_type = Dictionaries().montreal_generation_system_to_hub_energy_generation_system[_type] - _fuel_type = Dictionaries().montreal_custom_fuel_to_hub_fuel[archetype_generation_system.fuel_type] - _generation_system.fuel_type = _fuel_type - _generation_system.electricity_efficiency = archetype_generation_system.electricity_efficiency - _generic_storage_system = None - if archetype_generation_system.energy_storage_systems is not None: - _generic_storage_system = ElectricalStorageSystem() - _generic_storage_system.type_energy_stored = 'electrical' - _generation_system.energy_storage_systems = [_generic_storage_system] - else: - _generation_system = NonPvGenerationSystem() - _type = archetype_generation_system.system_type - _generation_system.system_type = Dictionaries().montreal_generation_system_to_hub_energy_generation_system[_type] - _fuel_type = Dictionaries().north_america_custom_fuel_to_hub_fuel[archetype_generation_system.fuel_type] - _generation_system.fuel_type = _fuel_type - _generation_system.source_types = archetype_generation_system.source_medium - _generation_system.heat_efficiency = archetype_generation_system.heat_efficiency - _generation_system.cooling_efficiency = archetype_generation_system.cooling_efficiency - _generation_system.electricity_efficiency = archetype_generation_system.electricity_efficiency - _generic_storage_system = None - if archetype_generation_system.energy_storage_systems is not None: - _storage_systems = [] - for storage_system in archetype_generation_system.energy_storage_systems: - if storage_system.type_energy_stored == 'electrical': - _generic_storage_system = ElectricalStorageSystem() - _generic_storage_system.type_energy_stored = 'electrical' - else: - _generic_storage_system = ThermalStorageSystem() - _generic_storage_system.type_energy_stored = 'thermal' - _storage_systems.append(_generic_storage_system) - _generation_system.energy_storage_systems = [_storage_systems] - if archetype_generation_system.dual_supply_capability: - _generation_system.dual_supply_capability = True - _generation_systems.append(_generation_system) - return _generation_systems - - @staticmethod - def _create_distribution_systems(archetype_system): - _distribution_systems = [] - for archetype_distribution_system in archetype_system.distribution_systems: - _distribution_system = DistributionSystem() - _distribution_system.type = archetype_distribution_system.type - _distribution_system.distribution_consumption_fix_flow = \ - archetype_distribution_system.distribution_consumption_fix_flow - _distribution_system.distribution_consumption_variable_flow = \ - archetype_distribution_system.distribution_consumption_variable_flow - _distribution_system.heat_losses = archetype_distribution_system.heat_losses - _emission_system = None - if archetype_distribution_system.emission_systems is not None: - _emission_system = EmissionSystem() - _distribution_system.emission_systems = [_emission_system] - _distribution_systems.append(_distribution_system) - return _distribution_systems - - @staticmethod - def _assign_energy_systems_to_buildings(city): - for building in city.buildings: - _building_energy_systems = [] - energy_systems_cluster_name = building.energy_systems_archetype_name - if str(energy_systems_cluster_name) == 'nan': - break - _generic_building_energy_systems = city.generic_energy_systems[energy_systems_cluster_name] - for _generic_building_energy_system in _generic_building_energy_systems: - _building_energy_systems.append(copy.deepcopy(_generic_building_energy_system)) - - building.energy_systems = _building_energy_systems diff --git a/hub/imports/energy_systems_factory.py b/hub/imports/energy_systems_factory.py index 5a9344b8..66b454a4 100644 --- a/hub/imports/energy_systems_factory.py +++ b/hub/imports/energy_systems_factory.py @@ -6,17 +6,15 @@ Project Coder Pilar Monsalvete pilar.monsalvete@concordi. Code contributors: Peter Yefi peteryefi@gmail.com """ from pathlib import Path - from hub.helpers.utils import validate_import_export_type from hub.imports.energy_systems.montreal_custom_energy_system_parameters import MontrealCustomEnergySystemParameters -from hub.imports.energy_systems.north_america_custom_energy_system_parameters import NorthAmericaCustomEnergySystemParameters from hub.imports.energy_systems.montreal_future_energy_systems_parameters import MontrealFutureEnergySystemParameters + class EnergySystemsFactory: """ EnergySystemsFactory class """ - def __init__(self, handler, city, base_path=None): if base_path is None: base_path = Path(Path(__file__).parent.parent / 'data/energy_systems') @@ -34,15 +32,6 @@ class EnergySystemsFactory: for building in self._city.buildings: building.level_of_detail.energy_systems = 1 - def _north_america(self): - """ - Enrich the city by using north america custom energy systems catalog information - """ - NorthAmericaCustomEnergySystemParameters(self._city).enrich_buildings() - self._city.level_of_detail.energy_systems = 2 - for building in self._city.buildings: - building.level_of_detail.energy_systems = 2 - def _montreal_future(self): """ Enrich the city by using north america custom energy systems catalog information diff --git a/hub/imports/geometry/geojson.py b/hub/imports/geometry/geojson.py index 6680b6b6..998a2298 100644 --- a/hub/imports/geometry/geojson.py +++ b/hub/imports/geometry/geojson.py @@ -156,6 +156,8 @@ class Geojson: building_aliases = [] if 'id' in feature: building_name = feature['id'] + elif 'id' in feature['properties']: + building_name = feature['properties']['id'] else: building_name = uuid.uuid4() if self._aliases_field is not None: diff --git a/main.py b/main.py index 312345fe..e69de29b 100644 --- a/main.py +++ b/main.py @@ -1,95 +0,0 @@ -from pathlib import Path -from scripts.district_heating_network.directory_manager import DirectoryManager -import subprocess -from scripts.ep_run_enrich import energy_plus_workflow -from hub.imports.geometry_factory import GeometryFactory -from hub.helpers.dictionaries import Dictionaries -from hub.imports.construction_factory import ConstructionFactory -from hub.imports.usage_factory import UsageFactory -from hub.imports.weather_factory import WeatherFactory -from hub.imports.results_factory import ResultFactory -from scripts.energy_system_retrofit_report import EnergySystemRetrofitReport -from scripts.geojson_creator import process_geojson -from scripts import random_assignation -from hub.imports.energy_systems_factory import EnergySystemsFactory -from scripts.energy_system_sizing import SystemSizing -from scripts.solar_angles import CitySolarAngles -from scripts.pv_sizing_and_simulation import PVSizingSimulation -from scripts.energy_system_retrofit_results import consumption_data, cost_data -from scripts.energy_system_sizing_and_simulation_factory import EnergySystemsSimulationFactory -from scripts.costs.cost import Cost -from scripts.costs.constants import SKIN_RETROFIT_AND_SYSTEM_RETROFIT_AND_PV, SYSTEM_RETROFIT_AND_PV, CURRENT_STATUS -import hub.helpers.constants as cte -from hub.exports.exports_factory import ExportsFactory -from scripts.pv_feasibility import pv_feasibility -import matplotlib.pyplot as plt -from scripts.district_heating_network.district_heating_network_creator import DistrictHeatingNetworkCreator -from scripts.district_heating_network.road_processor import road_processor -from scripts.district_heating_network.district_heating_factory import DistrictHeatingFactory - -base_path = Path(__file__).parent -dir_manager = DirectoryManager(base_path) - -# Input files directory -input_files_path = dir_manager.create_directory('input_files') -geojson_file_path = input_files_path / 'output_buildings.geojson' - -# Output files directory -output_path = dir_manager.create_directory('out_files') - -# Subdirectories for output files -energy_plus_output_path = dir_manager.create_directory('out_files/energy_plus_outputs') -simulation_results_path = dir_manager.create_directory('out_files/simulation_results') -sra_output_path = dir_manager.create_directory('out_files/sra_outputs') -cost_analysis_output_path = dir_manager.create_directory('out_files/cost_analysis') - -# Select city area -location = [45.53067276979674, -73.70234652694087] -process_geojson(x=location[1], y=location[0], diff=0.001) - -# Create city object -city = GeometryFactory(file_type='geojson', - path=geojson_file_path, - height_field='height', - year_of_construction_field='year_of_construction', - function_field='function', - function_to_hub=Dictionaries().montreal_function_to_hub_function).city -ConstructionFactory('nrcan', city).enrich() -UsageFactory('nrcan', city).enrich() -# WeatherFactory('epw', city).enrich() -# energy_plus_workflow(city, energy_plus_output_path) -# data[f'{city.buildings[0].function}'] = city.buildings[0].heating_demand[cte.YEAR][0] / 3.6e9 -# city.buildings[0].function = cte.COMMERCIAL -# ConstructionFactory('nrcan', city).enrich() -# UsageFactory('nrcan', city).enrich() -# energy_plus_workflow(city, energy_plus_output_path) -# data[f'{city.buildings[0].function}'] = city.buildings[0].heating_demand[cte.YEAR][0] / 3.6e9 -# city.buildings[0].function = cte.MEDIUM_OFFICE -# ConstructionFactory('nrcan', city).enrich() -# UsageFactory('nrcan', city).enrich() -# energy_plus_workflow(city, energy_plus_output_path) -# data[f'{city.buildings[0].function}'] = city.buildings[0].heating_demand[cte.YEAR][0] / 3.6e9 -# categories = list(data.keys()) -# values = list(data.values()) -# # Plotting -# fig, ax = plt.subplots(figsize=(10, 6), dpi=96) -# fig.suptitle('Impact of different usages on yearly heating demand', fontsize=16, weight='bold', alpha=.8) -# ax.bar(categories, values, color=['#2196f3', '#ff5a5f', '#4caf50'], 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('Building Type', fontsize=12, labelpad=10) -# ax.set_ylabel('Energy Consumption (MWh)', fontsize=14, labelpad=10) -# ax.yaxis.set_major_locator(plt.MaxNLocator(integer=True)) -# ax.set_xticks(np.arange(len(categories))) -# ax.set_xticklabels(categories, rotation=45, ha='right') -# ax.bar_label(ax.containers[0], padding=3, color='black', fontsize=12, rotation=0) -# ax.spines[['top', 'left', 'bottom']].set_visible(False) -# ax.spines['right'].set_linewidth(1.1) -# # Set a white background -# fig.patch.set_facecolor('white') -# # Adjust the margins around the plot area -# plt.subplots_adjust(left=0.1, right=0.9, top=0.85, bottom=0.25) -# # Save the plot -# plt.savefig('plot_nrcan.png', bbox_inches='tight') -# plt.close() -print('test') diff --git a/plot_nrcan.png b/plot_nrcan.png deleted file mode 100644 index c780de3f1e96c7a0462569834f4f66216736a0df..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 47568 zcmeFZc{G-7|2KL`Q50oP<~brtg$$V@Q;0}rDNQnlP{~Y@q9_f3!Dzt^Mw`-~YC??&p4P!*yNfc^=32`VSB!lj&AOUPdIOu z+9tJW1D}(Jhl{(y#*N4S`3q7f+zxGAZ`E@KFS5c#d!IXn!cay2p^B4WSVy50D(%ux zHStayYV|T_X`b0S>YI74z{FQwdh1fv>YaI!R*mLiA4k`1u|L1_JqP_2^W;>gAgzWbKYaMJUfzADz}31Q zK7oa=M~!biC>jf2Y23^k7r(j1p-Xe&Td4>2O2QVt-k%R=UqilPuE~ro{+97(5$naj zJo3CMa^bhMU&VVb{MMSxgs61t+ix>{e)c9eCnY6uMg&E;4Skdu`tpR4kw>w7W@3c@ zY8@5j^y$-1&G%ciBz<2e%FDTZ4I7@YE5`H*819m&2?KtX}CzrUYy_SCNdyP7C& zURISgah8K0vMY-`2ZNfAHs9BLc<|NxqStuM&Zc{2l(TnD?{(qLj##zdF@B#{lKQr4 z-iWhDx%uel{LDV3v~#%Pw-ik#M$a#0XH?iWn2+vi&ddm940Vct0X^}hAf2pNax%X74F zA-}(WalnTtIQgBEQvU22Rr~1~$*iobi&w6MtXj8~kB^UXqPAnwrnvzh~jzezK~sF(ooFC-?pp zpW@Q5eeGpsqknMg==~Vkr7XjO>DlSN#BUE79d#LMEoR^5+*1DK$q6b-oP^oVdwa7x z`uh`ds=xQtUF~Zvell8g=*`X1-@l(@(T$Fj{UNI|kY*Lr@RmJI3Z;MMeUE+2r8@LF zQPsmkR!?8QF>{Zo-_KXuDolGT+EQBTmaxpMRC(X?OTA~KbxAoM#~Mz~2(uh(eo4ty z#+fV!4<5|0DO2X$=6t@?e@1QhZpIxaACw$=!_)HAmwoq5MO8mPMJ6Vu+c`PW5v&r) zyDp1N{;1p7m|>K%q}zXbeEr^Ri{QGe8wIy)VcolTFNd@(<CWZJo((r#(&x7%@6&4wiJ6YZa+Cxeqr6V)vT<+_4Rt%X~yQ}=C|!C zLvkNI(rhpFSDCc_wzp;EnBA@53l~;xJ@)Y|-m80VW+G$XLy^+iAG*Q2FF9^nTjVto z6&uS!*-?D*d&jqLS97dK+MbU6=+UK|jf-PF`TetStfQl2=hv5)(_rQbQc|Ndm4#-!7nAnuqk1sf`Wqdwr!!fw&~yBBiSS^Sc?4RfAE;O7j0yp zINjPcd=ZV9n2OZ;X=E zmdVM>cV0hHYUDJJ8$ZAC6T9%bL(D?=H$E6)6x z3&qOhS61d3X)g;cEmcPK3rW|%Zr^ZQbxO)md}jOQFWoiKd8d9Iu`G03mYJEE`}py5 z6tKppzFxs?T1;pToa}6DG-uA7F=~F$(P3*@d=f?BSb0S-z3b0j{iLC(pWn=jPf8{y zCrdj#Kd*N6dVGACQOeHFwx_-o`PkdDznX0dyvGEvGAPW<%t=F&W8G6l?NpycMLRn? zmoqZfU{U8{J^6_?iW(%A?^c-;8i=Agz+2#8Z@-#{CyHM^L~Qn^*Z{VByqv4RO=W-a zYd0@mJR26a9380x+b!hQE#9G_A(Nuv{CwH->$Wl2+1XX(Z#&Uui;l2-#R}C6oLfwa zlpSC6xNWd1GVw@`@5YOHV{0&QD+J*JQm`t5rM{s*a42|)C8wk`J&wXtt*or1KG9aP zCS6lglQfsG^XIFu!<6$2Z1!Ar6jF?6AR?{4R8T0dLsn-Y(1Fy1W zR+X-4`zJh`4Ho&@9xdnbDK81@UW|`lzHws?<8y&H9r85MjCY&&qJ|me*=r^lczYLI z^q|^tOW8l@54NYDpWhFw7rS=vuKE7uiR;Yo0Z{EyI$@Lns*1M%_Wg;pqZ8+IpRi%*`5VC30CTYi7mc;E|2Ju(WMfTh= z=tL`@oEa&zJ9cb?_@0BjaW4;;7rHqH44xV5T4r719l$JVVEgq&42r1K?~^QMt`l`n zeJAW2lQokMvv0JnO4GfnJWzV5&@zhm%sP zvfrcSssGH@gKutLDE1m*E8-F73cW0Pq1bO~)7c@31k-zaS<}nT{NWMQimJkE89h9x zFMFbmiz!?~OvR<+Y+n#xj_<@U*(n+OpGaNXb|T8Hr*?&7G_T6@w`saRU!`PcuR414 zsO<62EV$pAXgeAc+p!jVu8*Km#ml>=_mvCb>6ZAo=R3D@rl+S@cR-KTylX09WY59O9EeuY*;?#Hc~Cs%+1H>temYjd z%pm`?W|mEvb*x8!Utf7!8xN3-_wS*#uWza3G`P9l+jEBjU7qhVBjq-FfAU1H`iR{P z1lmY|Jpv6Zo8eI1rVa zekS?uhc#?$A=sz4fzE(@*uM|rS+f{8v-(7{%&k;;4y>fH%d9eFRp-C}-S+LxnlI`Jiz03CK-wK=`UVpYHp^Az@WWU0gR=g*x!JrEW*y&D9e z!BSWXI1z5(^s2tTAzgo+oSfY9!3&X*m8rWfYrOo0k{FZe!1xp?i`wG*w?t&Bg=4|xOx1?}wZ&jGVleE6`{W@Fg7 z51;ZJv^sx{b=PRlm}0Bkw=5JWK|k8=;prJ^c~G^j)IZYw+v_B}v{PepaE35HkOr#! zlZ?^_GfuwN_pW(b$#qC&0CLSw|dadtw4PA}q zQL3HrwRn2UGvBFkNk~YDoJXHwUh^%7Kl5|Rsk#C&w^7Ck9@P_J7H;-YIyJsrSy@@_ zt#FLCt%C#gp+kon^U!7;%pV;wjd5K{OH2RM+D+7H=J)qX;JKuf6lyfrMxUL=#>N4= z*e=e^%N~Ba^#1+(E^cn;;^N{)=k#@TD`%&E($C)SF&y`zd7(V~I$lP7d`^1H7P8h2 zQ6HkXvVoL_^{opYg=+3K2*227brUUUEITe*REXw_wGr|&d%OOE5r?UsJkLb zd646Twf5*mOpBYBJ}KB?YXpM=U#R?<4a5dmPWyI^;7;2gUtdbg%CbnROI=0JT3fN= z1^RPjYN}9D8_ElRk$qG4{%xeJiM{EMNV#{9UP3}*!@dVXHWTNmsA*Vc2HKtl(rO0k ze)Y(fm6qOd@Sw=SSJ#BAxOjP^u~9pVM@pHCoWJr+e+1Ihy|Tf`yA>Dk@u}~VQ44_> z+o>NtYF<}Tw4y7To1d6}p{Cr}?zQvV>l;5kk56@mf<`Dc9S5Puq3Pc*KcJ`uAR~j))mbGaDXHHx7%zMHQp2Vq*Y2H-(Wji8#I9{U zes|P2)meUq;o@9deWLtkn<;cV&34hFM+eokY!uJTD~jmFQ}||{DtQfa5wJ!nCy>3X znm*}1U>Qgjql1G(YFe6IS5%N z@}`{Y5|#N$!LvhBnFL<}x=@Lg@v>BX`m`2>o#|3g@=mo^02=%yZ?IsNFJHcTm{G}d zAhv;%=Jv-{ZTG>3Bx*J(%jMkM+${yJ)Ie@bwQB0>nq~U6!zm&nB6wF{AfH#y|p4g~B< z7eGr9Ii+;;!-k}~R(xCLz z^9*>DMets8vL=0!?uD3`HAkB7UjQ=QEygQjA2jMP+|j`&WKD@s6kE4;tsQn&#Zhbw zkMEyN`}PuSea|e1ZY~!88wE_+4JeP=uWaw&Kwsh(iys4tCIOy?g@>=1K1ls(MSZl& zyjYQO26nHMRT0ZKNh57-?XbRIs42m}jvPJO{pCa%t5x>=MEe}2e=Kknys7AueCgME!Eeg9xu68ZZ$1>25f%0N5BHLdmIY_)>+6@~#OtOR z>)lk`XOe#BPADKjTy_cy=;-L^YU?M5-|ZX^%oth`R%DWCtU37y3+!W&r$knEwh=IZ z$-TX+(8gai>u0G2(|J$*5XM>w10n6eQ`FwIYmHUGi?PWuht5AOlB+4w$399YUB7bk zCJ&0r9;Yr=CME%eXAzN+b>=;+9SYvSHo=K)az5yQcN;J&e{CtgUi$|o| znOj&y6gBvViI+V@NeIc#mOMM;pf~(=g;?8s7nX4SOz*38K|w*~O--y=XUvkKUQCyY zXIL(_`j3Y4o?dPJZB9>JO)aQO_tPBTMsN{Q!aPb|9d?w8z>9%EZs01O`2Iyy$hxro z4Em}0zJ2~KKEVmncIQUtPP|QBv2xX_VEQwK?ak4jw0@0`-@bP*(&o$@E4XMiz~I>Y z+>CwM)w{-tbDK`Mb$UnkBDHJ-;LUjlHJ+cyVrID@DWSY1bP=++#{z(wfKX_j;+MP z0^z6KF+Y-1SSU|cFn+xol>+Tgx3grhwlMhyR%AG{sJP$im;LsTKO`Ch za==1v9m{BKkz}A4K5@2c90Xm8INcXHaAlum;Y=vC*yxdaXk9y*b4tGWqD=}EPp74( z@@?JvxLhJ)6{oD&sJm&8!a&nK(SZniFa&Y4uR8O0WDmSpov7%;1PZ1F2RYIoR^KBmcS}I=$a)3RkAlkwT|uV0B(wtBep)eTv{h;~WGiMmU(>x|#e#TLG? z`BBOqaq~-w{|p-{Fq{58tKzMw->cBrs;aAd)^(&y^SZZ)y}Z@-0nFbbn1qeHD&64Lo0S5M3LkG3A*G6YV)SMrkXNJ-lIGE zuCF>^qhIgeBXuH+S8&9%_uNiaiN3k%*dNX#1+LvJoc-B+!EzE`Bmea(Z$Q=eqYb! z21(eEUoG$TWG7#-g#NI^AjtLx+2Pr#9CFbn^og-`zOZ!#uVDUS>{v;dLda^8|trZp&uI?qdb`S(~`1w$y9qYijcUh z+d$0_t&e}b*N*-B*P*yo0Tfd46-U|)^S8COv?%WzS>`xll%OZ_`PM*l4kNUYPTx6f z9LC2VoQ#L>mynI_;gK(vAT{7?7285>N?GQWw;}6GT6lPPMpGg5xQg#9uDL-zX$+14 zpj=)f#OK>?Dmx|~g$;1vKx$vXz5e#eOA#rY#+h;P@nW2l9iRjd#tKF)2psBv>A!gk zpYxA0?9?Some5&hz1B9CN2krReYT{LH8*kHd-wa3OU{;-mO9C6UT87pz)fu&^c?)K zV(>+6ZEdq8|B1Qi##}q~#)m%O80Sdm&OuR`-!&{M7Lk&YBG%tl=zgcKaf!TJ$m2G! z+e3v>6&d!twc9tfqiP2EPk`|(r=t`1XoJwvji*@AbW&1M5;cYu3Ih9$@?MY1mowZF zX7>|ax)Nh!V@D#Koqa-2w>>_7z2UexlueW}NsqRY2?d~8DVwV*e7->?c>dgg39RYd z3LeWLeKFBc8X6l_0sV<)+W0e;&ca$%cIw95YJY$KM(P*tDiwkwefO7D|Hd8`l#()# zm6VX62i!9+a7l7eX~J{7+dDigWhta68Z>Q@`flO}lf) z0hnC#{7tD6?Yk`}KatjR z)9YvNtEp4eC1CkfR8;X>j$SdAVbe1DCYqO*S8I8o%k>{X>+d4TdhhkHIQ)wpf%ZEF= zd5^W1TFK=>me+*pW0+&D1XW7o{fMP_T*bS0KLhJbVu4(Maw%BQluuUXB~PE)9X%R3 zKJL*{2%Z*Z`^21-Nj6J z!j6-JP9V??8AgHxc0ebF(Y4y(y&DbhWc=KYrc~qgr^fqYM$T#Dv#^$)*v%T-ss-)A zFxz5_U0FE1E$9{D*R~w(0;b|E;kfKoW(A>o2{m;RFe(_v@)X^>bzn2D5Pp*)e?TK$ z4O0N?umc!~*K_x^J9q9hc(PG?U*CuX0Z`w)d!r4#u{BubPJn2avUjLi0anE)pVijV zp>KNs>|Mp9bSemVc=ucJR(x!2A*N2n1E9h(RHaX0@&I}eerq?VAC zH8=fB9si8=H3~K=ol2E^>}{0Ugg^iI=~E|2aDiJ-gjhdd5?i`n0;G!1k0dy^9uq4u zz`BKI=Q20#*}3ay@Zq)+d3;idqElj*Uy=)f4$dxDWR6Fsq5m6=7YcE5L&WVc2w--f z9ylhiD`W zs}+Z;M7)v7nVD{|QUX#kGuK;MS;=5?GoEyxV#&|XhwZTvej_!pBvGn~Pll4ov36|| z6j+#K!I6==rOvFk-YL)-XBrD1&fW zoxT6bYHscez)G;C-rqGP0x9gc8)7kJC&FVC3=`C-g>bx3|8&ex6+=&Fs%ND$7@{tU~o#!_H1yXvqM3h^$Ij z(0~v%>^6S<^>F(F?(Va&NCtMP@OM0Q>Qsf`PGw=oiAHF{_NWv@0{H8^neX540M(@L zeI_#?o>8AdU7SQH*r+XT6IsPw-Q981+|wNJCba-%A?=HhJ&x*#r&J9?@dA8>u;Co> z*TKudFQ6AMum%+9vDqniQsyQ1Kd!<$aK+;mC^jxGD6oYt_p#V(BU#hB5rwWQsypm42R&52P|jhRYH zN`hvFg_u?_@F-61;$_;M%5V&^kK{LP*bSrI(JF+g5;qr%6d2RBr4GXoNTn$_t!vmYNXCGk~1+8faKVT`cmN1sTR-!9z6N|N&Ao1ZcR;G!j(R% zj}$|=sD6LuCO9Ls=b(kfpZHp?8c36be>fvDh9aV)*_Ad+<-p(TfHE=}v}R2vBT2kW z4{gDYu(q})X$R|4zxC%&A3Aa*0(CV7hMbGLyTpf8ld-oF64r8YagDy()*i+~Z)nkT zzM~mNjvqD0#p%MrLc+ii2-pEPA8RV*?_+zw*liN7H|A}*wP1$LZaU#i=b?eqmPEBF`sAss3 zv?=*bw0gnQ2!zyncW-tCjKFSay36h#1>$mQxJ^YsaoK5qbibWR98mV8?W>lPd;vrV z$c7sp%meYj>!Ab8M|mdRBAVY`Co2t&)U_ncmGS;_q5GkLU2h@YHNut#ukJ#}uIa1{ zC8${Mh63y6gY~NiZa_sxJmndXkn7~A9cfV{@&g&3pq0i=XxGt*3*A2RXC4ZA`B^Gz zY3CNXEbFJ!fvz_dJn5c2dzPD*rwXGkG7I?*i}C(J=}IN= z!E`{li6;-oIp&v;TcPEAxV?b5>sL2<`!>zW`W8f@G~RWtHj=4&{yZ6Qi0u5)CNmp5 zXsVO_cWzNAB;5t&odk=L05~cX!RwwEx_=vmh({rt64sq&`yYTabdzKV0UsYY(o^`b zX;JmnLw|qI`swv$7?lSVVRXxtD3{B+S+qh_WpikxO?|*h^&NhPYo!Yp5;K^ zp(0^$ zCgRteVe+Aac0LN$oM#q*q4NHo86)(U)#+9RE>yVF)n}<}A_h>bSP+U)hYB8(*DGYj z`v6W3j08JC6}l!-6l7XbIK(}nS>-y`v1M5nJ}CbI{h+*}VhP018i0Sk%P(_bxE^aQ zV!bT7TgvT|jEoG*&uY&VC7y$9l-sJ*uG>_x3Mu7iu__1MmjdCEl{P;;xDowRT}z7= zomSFgdw5vi&!m)o3jWw+J=i*YvViDlM(7BBg27-y>RS)DGPFCn%koC5-l2+G6rb)6nRJPYNgp}DyRp$fXh6Nn0+ zwb0Jb{@ndOd%v00b;AtYa}o|~Z|B8+qNczXry|}ud6sq+OWP#WG{zI`N%}GEId~$` zPDoA*U07RRpS9L_&mQ2CP&!s|Tc~f|2n+#vErGwjkYT!*kPwcxX!kzDC^t8EwWKfB zJ4qjmz1ywwtgcSi!`x9pRBs(V$6>?NRD z!U^ND5UMnZoIs+|C==K%99b!EfF1CVNotCUic0*oYhBQ@2#FZ@kS!70Lonjockc-L zZX8ErwFfPYSM+fUkUT6u;RYMds&PEgw-{71_0spceY@>gxKjfWR8-Da7#60p z3Lq~Z)V=%x$=W%h=~e@+Hm2#a>p}78?d_HG{k;L6ZpZL&6oBLs3g}S>h`)&bHCs4N zgeT@ILi~Y&PocmSCuSSTJw?T_BIR`+&(Q%jbP}lL_MJPT_1liT7dzHgvKC(z`A<%? zcR{H_+(B1FSlABOjq+fBots5H38#@1WPQ9$SX5L9WW4Ai0#u>1h%! zg49Bifn%tI{zG5D8;=OZ)b+UO)2faacJ1j2*EUkLUTx$$8voy=Y2<&(@alMU^$ZU) z0Pmt+(7ii7^L?8iticS!q$L|9C1uXc`+Y6FAc+s49VEQJ#AlzQClJ(+4cYM`Mvy$9+|zTX zlA`dGa5Dq(D_zhNh)xm0$SW0VQ{=G%Y{op-mg>N!wWp{1MTy{qaPh5UUl!gW!()9+ zcr$ba88liWwPh3tFgwtJ8Xq6uPuYPrfXqn5U~>*Ng%BpJGm;PqKxTHsh7F^28_PP7 z$0ZRkiY<0---qmV(-R|%pqk?&?Yz)UssZ3tzVBor31_2Bqpq9X!dL4$dwPPgX_CR8 zk~e{I%}#y0MMS*MPfkQ5D2kq73q6o>7P-8>!NC;7Ybq4=ugL~LSF5O~AfY0#CA*ID zfaD&ua5}~Z^%6=-R^zMnC=DMzgdiIszV)iCp43(PGa8NoRqmSB39l=>~)8teJm zpVO-D`Bd(H{;hiBo7^g4Yg7(|Qfh!Xf5w?+nXlpD;pzMIP!u(R@B%R9%2%&eQOF8C zb}aFPGWOkz1Dlefhz8w)vLg28=(BUnNaotQFwD@C?i=NmJry>e-IXe+(|`sBs-A| z!5p6uFf|tMB1(P1$~ygX30!Bh`E&@_=RW2tPh=Cg@n=2;*%hG zsF|surJ!mOJCD5a(D&~#2tMJ-K&_&pR8&^RT)9Gnpd{t&)2B)#TVrt=F$o}jy7Rlt z%&zX*yIwR#J4i)9z<49i(aa5<@;m!)_kA-OHz0wr*a<|i9^(gO3E(Lpf%oz8@pZ_^HI)S{zKQs*uUtC8 z8HS(ca#HLLIzMx!m~36SHmQGkM%I_%oc`ru$}4U<#PR?exVXBi!X<<9NuO<&T*SGs zAJ)Wa{J$O*_&*7~adH3i5W~Z0gA&A%NWP`2swzW(XJNP@*;wmN!RC@vzv!SFR-u-= z&d>Uw2huqbX&^6;q>Ru9gCR5DlQm!blS|s2Rf7oW5k$J0g9AaMm1M_*1Cr1gM17)( zLnS8?s>I%`av&xnB&|p|6#>q(sACFG(2eoc_b|<{tEt%Q1eek&7NW+)uHVy;W1~XS zQVI&o-ps=!OH}sfB%du&-aYr&r%j}xSrvI09UfhHoEpLbYT?b0Zwc}nA8y^B6ukGo z$Ud0z1Eb1Fl;BcY?!2WPPkR{(M(ID9{MS@SHIIR^ zmbbSYUUR{IMl2c|dujjzvZCMQXvz`{7~o61KUSv%`gJ2N1}X0rGC2^WwtQdZCdpM%{%p%#BX_VZ^Y*w`98-M&62B(|TS zo}zkE&ISc7EhsD`nRECqM8d+H1Tm4%)V>lV7nE$Qky%yu?j18WH;D?~?KXPxw^5N~ zrK~?usZhJHOp>y*FZ|UlmNPK04G`*ulpT=`5t!(Gb1NF*98xDJxhL9{4;(ybKR-Jq zEiHW(c@8M<=U657`n^5A@C1_Is%IX&hcIy`q+fO9$w-O%_~lFGix=8Wd92z}S1ar4 z=r8T7S0n<%t9ErBhbr$wZbgMQ9y{O zn4m`kPGg}3KMN|n#ydollJ4D`$-Ay|iwdxRc1_$eCVko+jy%y%O!kg)rHmVnZ#EGd zd;9k7IgLwyzL`s1O+iL3_%$l+4`E^6qo~yv>v}kpPI<)qLYwFW?gMR1K?|l(@Lq&K ztZCW;rLy+GrhNfD@{T^tIm_tTY_VLD5AR8zwEg@(v-;D9X}y;{JKWqh*@Hk)*-#}L z*I2%zGE89quimvsYzz*Cc|dsco!^)qslTPdi_qzY2W^N(6VvE@jt%ep{CvfDV(NMr ziY6++K3K@?10*eH<1;+ve*NEj79xa#y*IcH_V(VY_BtDuk-vHKlLn9F8n>3dkP0zzt*x!?mfM~M$d|!l`k6blnbZksjO>GHGnx*jO!E^-q z_;zq`r6P8P9TxQD$u_Vj{x}^HozuG}+yCyE5P@cE;uM$)&sDm_U%yU2p|DGciO94D z1_q=efS7bb8AlH45smJ6rsG6eR7gVMvSKR3Ptpg1(gklna4s%O7yOGb{ue@jvqF+g zs1TX{TwtKMF4b^h@$Y`|?K^^CZP$pe|c6?tFY&F-cGR|lfj zwEO)84%@WJSi6e6)WG}oiO@{InIRPu7Wew5(!HpHzqom28qe*-N|<(JR7crQj?(dK zuo*=0EK-0-@?HS`iCemBv0PvBH|bh-K*dB3Vm*WiI71a!sRZKy$3hLVWR>~bubrw5 zN(dNXVG){bE%9MS0VfC_YwH4*<1@r8AZ=^FX??$D^TKU43Qu43IX;a-9po##Ju5U z!l7om9e(Ox5{J6!5~lL<`Ub&!+ot|NW4072(ZLwmQCk6%w+*k`}WmEm3e-XfOie ztaz10?K$VQ`2vmz(-98lyC6)*DvJb7_s^& zL7iRng21~m3<-;aoJRZ1vO2$ddU!N|jsvkqS}`uJN|sbC2H0Fk5O5P$2p)orZ8>En z1S@>7f%BNv#8*bu9&eWm_?aLq0XSK~7*v{{pE*Ga0styeh;Tjc*NQHFmhd!}uDD7> z8A)CNCY(I3|fv~v6H_#A2*%ha5W5bK#x(aCRV*qlq@SzBg zLt24|LZ;8P7w(@)GV4*73+FH!MkaTN=>@fa1%v|#DR%|7{>2@tnQS>wTF8u2A5!*Y zDic8ibj;;MWF*k3t1Edx?(g2rvVY`#15yb@-D+sHF&IK3Gn{a>LQuv)mz!7w7O(AP zx=R;aSIH9Zu_e$nIJO?+07jreYxR7-bMf}cmt@j`&kl?hSQRG2FOtJ(jfaXAiQAA(}Wd=}e+ytsb z{yzrpQY;=FL8g}BtJ~C_j~WZNO*~k~q-%@!sBKV4hye1(8*NH8V6Jxb_J&|UFzGb_ zlnXo}QsnUP7JAs@W9wsQrl#yb%TdiWpwU7uB7cB^A<7Qa{g##%Ec74%t)(-VJF!j|UcR#~{;ar$(v-CH2YY_Kd?O=K&yZ;fz0JEHh}eX-`RMT`L9di1dIMk-#w zd1xkNI1}vx@>LLopCuIX$)Jm{c{00twbHh0*4-5Y3Z;J_%*PI~l^A#_kY6IfQCVG7f6$ z>cq&0W#>o$;BZrJDAdU`?F6a+y+ z;8hp7Ohtioq){jPpGYooLg>l*J$};12TV+lNga;2WDpV+73^)@?sCyUoF2*tCC*1? zBzX(>l19;mYhdHOW2h9&l!E}b9emw*CEu->?9Ds=Wh;XT6$7XN`JqACl zB7>O7j=tZNRRW2UNX)pvWJvx_2s=ZUz>~SMVc!z)?bwYrJkV|eVg6%gFK7Y#;P7>nRnQsTyBq1I&){RXJpO9laK z&{8zWY`bY-4*c6W~j;{cj(EXO{T< zq@j%d{=T2kR#@+t+*v|_Gfl>XiPlXxE)cC9ruj+rBnB6SW1BM<fcrV+P&hG9&SlqWUX^8xT78DD_B5e;GSjty?{QyE| z5Rq-2ouiPzg*H!*+xqs!)&&e<^inj&U5qtZ?SBs)T8AI`E2@fz|ML`t|54h=GG{b= zJ&4O%NFsD~!9Un%djq@5zg~Qy(1+&$a32A7UjbHx6?YMx0lk}CF#;68Wj1mDBpf=T*7C5g?G7Ef6Y)=gibeSM zD3WSuQ1C}pp*7$ln5}`t&y4QY`j)cp{Xcd?@TL^3a8v-XtL<^CPuNTgT&IP$ zOx8Z`NfIb8-NwJ`PcF8oW)KYK;>!b?A9iVLm%~tsqq{4O`$3RD>CZT`z@z`|GG}LJ z;@*7d@9m`_k##LCL22pL1j}O7Kx#uJ*fHW;8Yn&>UY%dRF5R|mn~;czc*)Ae=hbB> zK6ZD{o!7L?F{lbDC{48+N)18wLJ=`LJ9I??-5ltZUqpoF|6co9UWe&RR#@o(EcDdW z)I`m|ZT(nwrmUiF@m7m7r0pz&!a~<%q}5!4TS00(4&w;b5O{{+S3^vLQ;4n58>0L>J@+kOB>;0}mPV@qM&c!NWDjAo3x1pf0PAq7D!%-hOg= z{Ht5xsFn#^uKJe4od2%>e8~+&V4sDi+b|6&R6aIbXk|*dHztZdvV=c(ma=fsR~y8>zk@+-=!Ov9+~O;DR7ng<;$00J`)Tj)zP&} z+=<#`U0>{^AG%|F7g3xpR@baI+1*)H2OoBPx;%gy=B;b#DZo2#Oq)Z-4Fv*NUo>eC zZN1W~-JcewF0tf^RbwJv4XIA^7vfA*6l4gsOMr8JG$GkoWSEoT_?6Hgz3W@`WiLot zr-hbbE4bMV0WMDHG+mxcl{d>b~b1caL!KraR* zO@ixh{`goza-^Bg-o0!vZHO~WG~a(~!vV3QU#fdWHO+S>i4e_d-1@rj*MYY$mu3BuqAQi z22q44M1sbNAcP!{n;jNL1PE}fU`GN5$izli7}4AO{QV*S(R+D$1z%NNtP@;1t=*nD zmjmgGyK=FNGiWEd$Z=uut08LM*ht3rHNZHiD6HZpJFr1W(A?RX0mr_eve>^(-Bqy( zOZp<{6?TQH$mJx6nnVJD_2OLZ1EP(-dRD;y<3U_4Se; zSDmd5;TM$Uri%0YGPi}BY7svQg2;ntgUDlg+&$@6?5QYr3SnumvqDY8gMm5Vb)ag| zBBrsDSDBm;V0f}|;lec1B8;t8Va<0!1wjs?J91;`4gfJD;AZSpl2;=6dkK_sYATT}iF3VE@imdIs0II&Le(#h;Q8BE8_C{#o0 z>eCDSzVBzcNn|rVMq}BTGv~SFH`yd;2n1Zc$_}Un3uLm9ifL+UDhb{whA;RDl}4R) zASXS!_pU@*no%-)Xpk1&pXdT)+KeP{fhx(k8B7_Cx^-KQutf1FMIbj`0RapF)u8Lw z*CAR-&NiW7Sc%ia!(+6PQKh`4p5@eZPwvrXDZ-4fl}w#-SuxL;g~<$A$%)&tBmj+2 zy0qI@Z8GBnABP<5($d8p9`Hblq?yFvV{%lLe z`Hl|Gv``D$W=uazLeh%NVg?j}_$%?yTOg)n7dr*@ddXSousWcWF#vTqg@rF1$?yy+ zGKZ|wMQMF>Skk<(&A?P66Wd!_EWLAo*NEGc`jw{AQ^=_k$mQHUz)jZpxDUG0G^jVp z`=94gQfOb%MFu~ik90#AnZ&G^a zKK+79gejPHIHf`je+1ob7O8~EfqOa9n8=nr`e7vsQWa1giTmL2t5p=DBkbAp&aX}u z!KGUF{e^DAWKN#a(9~3of{8ibT*l|1+7URLhMZl2@bkj(J5knhte;i@WYFI@fvYER zm5M-bu%R_ZFK>^#`~Xx&24o2Z&68fz278s7crTdZw$`*`a?}Mw24=pUk`jmj^@?{7 zA3VSW;yJ9&4%kby7yWO+C>+Uk>(L=F27+p}bvtB;hFm-9)H{dB8ETWiznmbR64H7s z^6emS8adEpsZpLcpp_!g5)>0-RO$oCITFVq*nZ5l!&6k1Pp9nx0+n8O`2!2XmKE8_Q;y&2WOIKzP`2jYYDa+8Ee7O4MtO- zBxHC7NSUoLkvIr&j&OXBk-fx&9`3?EdDrNG46(?QjOJ5S_q1r zZo!>eB_GlGF-WfXIwzhYn;C zbpi1p0!siPy2wdi0BNGv4ncK6?vv)=>%?%R%L!P*%$*a??K7-_13>04;GL7b3m1wM zC!DyXiXknHy1ExHRcMY zr>@N6=8({^iMV90Gpt-?@f~El2qa%J$gIM#E#=Rj(~z(LWTD8c>%RHh7JvFcJG1I2 z9vZoWgbGtp)7*xvg5c3!&tre_%{lCUXjNn|l#oJH+gZyGZ{&wWh*Hq%^s_T z_kP{B^BO|{KAd)UET^R){86kIhqsF+dnzT3X(ReInJsL}TfEdb9jpP!`c>r0I5;x? zO_dlPQ$XJD^W)=@Sb`*iAIu=S5#i~hAF`quE-l=E452;ZW7lx3!{{HZa#x()vSjd| z$5L(@BV%@VyZ>^XM_|erdd?n@`+M&s|DAF+bv*)K3?5`yw^c8}^zUcm#Ag%SdHyW0 zjHT2YGm#i=3I*H;Dde>9W=56Sv!G0*xsb(<+)pUs_BicjYCze4ngu&az==VOt=4I)s<;FfasTU%kK70@}6Cc1!Uy90;wpf*npK;{J!W5Oz^HS0yn11^F9 zSSVr%fr)j*IS#=_F2OTz|g{*_o*z&QVvSlFTU?N1vg92bP%)kH4$_!@frXJp}C zt-i2$9^Jqty;~bkW{Bu_%LLZakpzR$%k;iO8CR<4DP-~x839j89)b{v-hdGvI~@5W zLJ%PagpwdbPxZ#UJU}65+clEYw{UXLWmfZ>d#-F7vp{%}Og&!P>KMV8u7Bh1`;(bB z1JbE)Z<6Y@%1>9nZ7<#IK{4qai&u3F{+II25pYR=LFRrWWDO!tgdPBeraW)bK9Gqd zX)baEk{&XW>yc)}S(N)a{N#7-v9wHdI)ziCaC)fJ`Y3xp@W;!$-A*uO{ERh1vw2FJeOu z#>K%par{VjeHT5kg$Tq)tq&lBc0WL-aMGpexWc#7ISY;p@I6w?DkS|)28j^Z!m%cV zuu^uQ953(*kX+EwJ3C+#vcDD{$rX zpa4EM<-D`+u00tVQ9flk^d|MmbK-Gl@(4+$(3xs$d85pOc52H`Imo~Db3 z2O50I-LAiiWgdztv8eEr8h_p=4i;b*0n(TSc!o-|FqA89u=vtUx|vlHP>z7iDh3g# z=|H@M9P@xDm4}myh+^jZd&p?tKizF~KYRP+j7NhwEDUKc%o0RPzXEWV^l)p-{ z0+eJlsZNliib22y;>^PQs>nwK3rdB*U(mu$(-2NHu4FigCrqqabTH&i!jyb}-v(?+ zHd;B-S{w=+GjoIC;%B|2gLoF6Ip7(=%Q)0k+_VBHJLm`841Np_<9V3s$NqikczjX} zS-OC;6HAjq25wNrlN14bh>Hj7(u7%fp@bQ&NFQg8Lf)kj%Hxtc0P!JvF+iPxbZ*S@Tg)0{@2IxQp zZdozgVlZSqK*4_m)&5!oo_O?FBcXWcx1qn0c^y2gyWNYgBEBVdU}Hv34&kquEyUqg zY!R<)Od1>@@>YydkfHRZL015wq9#|z(v%c5iye?(}{B^F@hKde-}w* z3^c(KBe`CP{C6W_7WbpZ9)&TWg>^v3F*x87@`UHb%PrS2( z193HNG0p(6xt?vK^?4En-SE#SC_)5y-sDWxM%&-(11xa84XErsGvHRoZ}4BS>*Y%(w!z*My_@&lHA(JG#1-AjplG0Z<^LDd-VU zq39vsp_n+}ptyzc8MH!_Xc7n`C;^+X3ONalIx^mYDJeSW9>fO6ecN024-|5HoH~~3 zHhe;oO22Dw6!ur0fsuHgbBm72$?oT?Nc$k@79oRC9O8+?$5;VViQ+^d9GO^u1ZLsg z&-1GA;7pz8h(wXIa?!!bkzwW!4y-7hpFV1d$t(PHCkZtT56|yRf!w~9i+Ig9GNBkgOujeigy*qvZZ|%Q!{lfu z>RJ`f_wDHESw?}avWS%Ypcts(1RZsp06{0YFp4l$wBI11EAa51J9q6bo0RWBd~@w$ zeA7@S{-^>){O2NCp(RH({2b;MDj6_Me%aN{a`%o)muFYi3UY7-9`+yGRuT(F4Tokt znHt`VEiZHYvn-dQ_v+WLUy}(FvH-|9$(gxd>_`Dnh#^5k^XKZth1541GRkpsPp{OQ zqmTsQ+Fx_}{H>|$mo@oFhZZ&djHR86crhy8i_9#jSJdCrSOiZcSm;^G=yWiQs z#~RYuEu%}C<5Qi{E7q{mMz1xwLeB7r){)XwQ3P?&3Juqw#uBq#=S8z=F<83?@@|6k3$d05VS z+xC5$$ru?5At4nd8L}c0qDVxULMbK7kTFwaN+pV-WT;4FDr1Bak&t9qnHCKQ%c=+& z>iHb!XYmuT?OQ-<2uca|>QKl2-o6cQiF3E#p~kxP1At+7(^5 z2}eiW=YVGnS>?LFxJi894MBGo)^vFI`mQ!rsDKH$g^?v<=aqm?PjIjLOvU6_-JCAdk^J+29;^7T-4JmO`43ANaAOIgk&+Db1BjBV8G5_HLgZmH}}x2A|beNk;l+G2t}f} z93#%|&#_t0h*RA~6e}WVrGk*TMg96ws_(X6SY-Ry+6ZMD;>aMHC+3<3Y+JZcZR%uk z?y3B_{6LLg<;@}LIoIT#<9NZLvn;53ZvCp;d3{4V<;BripbQ=T#Pu%&eLXz3~M6-mST-U6KRkF(op zTusI-=nujgY?E(cT@HQ{4CJo~#48Rb1bE>e0RIBs;;%Rjfw=U%5V6uX$`2AmE!$#K z1^AJ4cM-pOM5}g!GWT@9bA`(6GxaJOMwcw7=(n0WE>+pggtTM7mIiTS* z7j)GL=}MhPLk=V4DCbehth&@uTnX`smiGW@*B9--IDblQ2b3xWmHVxoYk0%?kbtT5 zdkz;HDsRdNpOP2V$olK4Fw>C1V`;B<;yFJsk1kSfEyaTqd;(p=yq8yMF`rRGL4&zQ ztd3r7%PNu~FJ`K(*4`_J|C0?37~-$v2uvfs03ohJ?0E3t%#a?hr_@m}?$s37W-vna z^`6KWS+H?9o?+?lCl(fuvG4!QRJY78^2mT`P^x{GebS@fuxILRMG=gy2;k)R3f}?9 zK!amRT$)!=<`!0SR6ydy-SI@lpuis)*0~)<&;R$~u=t=%)ujB1I-+&}p51-?+B>>z z8BE~+tZ#9YvJk>YDmn3#j?QWf-O6}_81K!UJ68b#R(JB`$tBmzib}LCoITRsa$Z3Y zHXyP=B6h*)i9PO{GVx6C8uIYnABi33Absayv|QfS;Y3t+PZAKReEq|*sD&UeTXU7W z85dVqAc6Sw*OQib5LDmVKV~|r)Oj>OZsUh3gIEi*;g0rH@->miH_$S*-Wf*+Pt}BQ z!+2`Km^nzvHc&3VNgP1~TT_9iXOP2Ls8&(xj6N2>b!!um(a=yBC5h{u?4F>CST}q} zZAy5oE?1UJzk%XaCL5g{IXG1LzwVB;t3)e;4x36wn?3`fMWwM6Yp*pQ;aP-EkSj}k zURP{3d<&l;9)NIw0#q}`skHg_r_E=OMu}iqHev3|;mF`^$6)hM^B7U*t+*1a15I$ENSl`?`M zP<1-*Ar!vgUG>}l_M|Z?U%u5iEprJ4Ij?64TgSNBr3EhtgrX+hSy`|penLVEX_9$; z)2Wga8t*E17El@>Pq~h7cfgS)s83~tj#OaqdGXd+dJpo&np*~ zy8Kr(rY9gWBn}Sze0w8zqUz1d!YbSi7}gx9A7-g)Q}r$Yeehk@Q?l@Hv0rb&`0CBG zL8OQ)NGvH~rPV_=XjMc9I)sQIYdd7-{m0i*O!>aYD+e~>3r*0oKC|4`@t~Xjl0H}3 zNLmLhmN9L#O-~;RMnvHhn-blDLTZ}3`v%RP?$apAl{KJ#np6!pC%TR8aE!Ml3FR?x%n>sbN)yGd8QZso|A^Fyo~?T4&Lcw_!Elo zE=gF1lZA&9t`Dp_6=;g*TuUKuSuC@;2Ci-sD@N&BFSv-Fx+^Qpk z!Y@FkCR2&YV%)%g9rb(c{(o=AWPo4vpP0dsY&`zF)L#UWV0v{Ip+~4?WMm{-K-w)? z(t{MD8o7hIcqJy6bx=fuogL@R5-I}!+&=c!D5!}iW3vnx$ zK04^Ba6z(_Ky&K)#8ao%!9^-WF)oB4{?W|?-VM&25=^*&A_-qR%VQ_+6at)5c-LW9 z`>u>Rq57_Obc&)0TM(FL2z+bSub)`FStesL{AR@J*w~I>sWO`g5>hnDZrQT)@mU?W z_8&9S-m$I!&8D5Sn_Fw?=KO+?Bjw;L2BK0^PEE?I7jB%Fa^?AR?SJ?{)^P|QNYSt4 z1Al)KKKJrPypVXetmSchGvFe4|IS?z`GL#IOKGrWAW?1rrh*=Oun(KgRpfbOpABMS zNRWK=N<&sHkw<}Kk<+^S&mO%-f9gL@U*2Hx%%8T$=s}3Kb;Y^1f5oKE+tLvIJ3Qhz zqURYB{(5`!84RLdX|H1zXIq+f>d;sVZv%&G|Iz+w^w(|?@(q|g(sa@kU zyY|LL+6u+jZc{MjXm%^AM~zYwO+b?pn2ofg#Yb0s|Lgo|FS&ppo}F)T-VQ3j^)@tU zv*xv2UAy)>_4n#>JjJd$=|^@k>ngFjBD|UQs%A(;1?dm?@f^gvfPt@8Jy!H>+DvW0 z-UC}Y25uP}zXu47xi}E`03hz7C-%L%QF(BS219{Tz8Rj6u-6aM`Db=;%%M{^%A%6| zgveFO#=oabObJ-^Tr1>!M8ruWGxe^)*Bjhh$I#IFw}ezJipR=n7kha^0BK*Q%G)qpg@V3W*pnwu3P`ju8?)BaqCR^zMb8%w z6vVNMd7Tc}k`WB!xdm(9-GA`lXHckNOtzi9MQ1B3AK@)FS?Q=(#utwH^zmOT9VPaB z*>SLTv|D}{HYp6RbnvnpIdTXtF8~b~>d_N`IjL*hu-f$|zxs-L{S|N%f;$HO|tDK%uI*JBPOl*P9WqO(s zZ;O}XRZ}RQ9YYp)Y#Vc1Nr%GWl(p`NN5XsViXv>zUKzrQnTw1Ffl zaXOMZhsm3(l=ADmNy#F?p@136{T$cAP=eP=?PYD}35t`^_QQp#xg9hD2|)NgiPTR< z+}}SwU0;fbuy<(wKqipe=-2x<&Q%S28gVHFKWRDfI$4n}*Rh|m;G{Y+_bkEt8U7(* zc2630r{@{fx49{ZJv3!KA%&1Ar#t#>8q6Fuc@i6+QL;ajpU!m!+LoJNj|5ou0b44kbWg42=ZOD4H=5*>c z(DxiR8T#u-M;2c-*ccuz)k^%o^vnma6A-Do>?d@0Z{g(3-Q6;!mOOiP`%Q5R^H0;h z{;2%^w^Vu|`KbZnJBBK;4z2>OPc%tl#**bQMpR!=LLiyM^>Gt0bna3L+9 z)TVH%*8y^|DB1{MAVn!MO%&y=dbrfYLqSZPg zvg~U>#b*_Zp9v1ipl!!R8PO)F6|WI1t&YwkP1oG|%w-3GnDRypz7Q>l1y{Jy)b6@+ zWPeS>RZ}2*z7Ag{HWI+>pK|B^LWGtwXbAQ;er6l}5EjtzW3d!`GPk>S~{!N^2 zTwHV_uTxnl)?KYXaLpod40t#Be`et$H-Gb}9ocC0&2u}K=>9vmo?7CdV_)yeKfgoeXLGoz5lkZEEG_%R%QTIk`1H=v7vL}T zKmiy#tETv0eE^)Izbt_cST%Hbi=5ZPnyr1`#6I)dKYz2H*04j%gCB_t8$~XDLmjB- z;c^6R6jAr5caIwLz&Xc5kZ%(`DEsQrl~1o^4&uKX{Xdp=R22k6k2nZTgcfTfM6k34 zz~o;6K&0ZT-?25&E=$o`GseOr5$|5(m<|dS%gEVAd+7(-PZ!9wPK_>9Vqeyt2fCM5 zUyY>L6=RTE;dyy^K+EUu`XF`@_K|ab$iQ6r%k^DHOmS3^o-x@C|CuKG|6+ITe+3A3 z-zX!{|1H@0-z>uY|IJ^PAQ~nxgnx`DsR{XR9{mhePcwSw(&qmbRQ_W^WmZ2^)eo)V zho+%C5mXsjEhL380v=fg#p;vDMw^v^ctrcQBkPnTZ=z$Tz*2Hek^eEc>fm7UpZENy zZfVn%AD9#o0nAnp6|vJ^XUNL#*Xs`{n3f;LDybR@83>8n6&b1Kmi(_FkdNi%*PtQq zH+M<2Qo{BlPElKmu$ACF&19ro`eNUa!$!N5E{)c*t+WYFUzykJjjLFqS?dP0P1Y^T z?lkU)EqdGZt5<7+CJh5rSznBlbr5B!xE=?*ZFxWcKx}Nf(de!r%$fnT>vrmthE@Yd z%AA=&*X!%(H`4AoLIhsFB+tgZd*lB2cj--0FRQLDM_pn@vm%V;*kFQ-l|?eFKGwDM>9BUyJqxfcXi zPDv0Aj~Ce}(0{;Sv)3&-&*1vx37M0|*{fGut(Bd3F13#c4GZg}J4L{9>byGO;hqF+?~9?zdYXXnKgD*0*(nFogPHEq*YedQ?cJ4Ou0Fm_%Og>Tm1*r;d4?urQGAL38kvzf+C-LNF&p`C z-uC^&ZByd!gDq3PU3tg37rD9h`O2>ajceAbB`)Po43RPaB^s@ULv5`hLn9*U@gPMk zOUWbb%B+V+Hqj9lV4QcA+N_#_3I+(MDYAHBn7n#>=-l&v`X#7_I60^z8U{g~hEpP0+}4E`S!_FS@yW}~Uhx%_%V;Ucgw_^} zib^*YkEh565&ovSU;1mwpdTHKw0m4marH~%6I2s%`&C-W&6ERWH3Q;X_k2d+2bG&lP@C7zO+-tcZa8Q zgbsymTPP#}O<^jBRMZG!rO(6-qTURCFcJS)^p-EAfkNt5wrHeo%)PGEP_q3RiTAqY zr?~(!I*5!tyNm z{h(j`7*djj2r?vtP+Al=Z06jTcB6Pfhmenl5*e?nZJ1C3oP@mJBN5wZ%FcGH1 zrvZ8~HZ`>Zi&(!=uV<-_erp-QMS7&TG3V!Dywv#^a`35SMaM3kz2qXCJy8z2Fyl(0 zyJ17eE-`jR)S2iFHAbR#NM|lVI2Rs3-Lt(5b=}Oj?DL~5*Kf|SkyuY4h3NR@^g8Z? zsPGtMnN2Ua3?hMSXpeUXa*1K*%$#*XN~R5Ws9o5m3)qrE!6>^(NW@G87D1v0qBoh^ z)5UpchzYg}A-jE>7MzsJ8VSDt2_*)S1qIsjJXUlu%sO54urIkVC z)!8yGyA7c)2hpPFooQ}GVyrM;^wxp&HIN2^7cS_o7;lPcnnILkvb-0~aemJW{x|nM zjScC>c3mP6wL=;QK9SoZ%L^$O;jpeU&fy2&lMe?d^8L8Myt_g)elmLoW~q0|`wLYn zY6Zn=ddnEFW04ZB`dRKMQ*(&esa|Ce2bpPfW=V0+Ys^Yrg+rAU9grvj5-L&wUZrnq zGhjt48K-BmkGYRP#yn)By$q}{3o06A?%IiVz3nx(EjLX9jgLXn`v%5|w=LATLdLxj zRfrdSDz7G8ou`iic^}0oU!|sE-NudSRK5JRVT|s`o^HcQXBx;X_q>KZCo5OdCtrkK;h3LZJQ3Zc6oOr4(Frv6-i;14d0#PB!)c_Zim8-!9 zgB?B>KK=R}3It(^wW*e-f@Y&O`A5uXWlBXX@mG9kWUpDq8V#}cqlIMLER|-{7_GH9 zNg|B7#!|oaOyGz)wgeT~U`$CPA`k^@2;~9YRjJ|m-shdUVF&ls~$%h>1!O)oMq~v9;^4@@q@=l1Jn)TGlJA(CywiI zG=AKy@y&bb)E%QSU@LB!VWx*Ni)|2*hL0@(*T|aktP7A90dT zm*a8u;lprLYpYn2%HJ&XKcc?WszrTmM~!-QGJ8P!G~*I%2lW?jD%A1)QCf#ZCY|Ng zV^w|5cU$L`SAXl=c?02t%}Iv0m&&l=^Q8q1y~4ZMwW?+06Z34Z^`mf3e5R9Trh<& z(`OE>9)~;R(Ab0bSooUd;;5c^kUc^SQuMGcVM94nA(s@D$gEm5sLhUFC=f>A zB6(QVC83d;#Agp-a`?qQR=Quj7cIIr&G-a){Q0tX-PpZVO`M7ux8VShx4ksmHVb)r zeTIo_iJHjvS2kkPV#}IL;SE8B+q7*vfi16mf0q4Re_V94*@%wLp#^U5ht`(aJv3gT zbP$nJjoJooX=Ryk>y)hfhl$ttMZ= zyWIh#W}Lb+Yt^5OxZxW8qLbOgjKF+!TH=dK(G))E9mi!`hkA)r4Lj3o%Qtt7=bcIzt{e$Mg%h}f*+0nnhHJ$ zduBp(h6E@i(a{QdV%zzUP~zQgMM*VYhdWKX;s=N~57h)LmKw?>Iefp-{3bpQYR;jt z&(6(XE5DneeB4q?%gYbU$P9gYeo-LhnfRC%6ct_LnO8GCJz96i1uA$RYPEgbsHnd4 z{V1;`NQfghLC==Qeq{0i|H7K2cHvFVS(LNt$q$eF2-H&Dr(Uw+@aUHfe3-jFjYs!k^gMTpGCY0 zVg7;r=$39UZr^K)wUr?^D}(X~1$=;;=URGRO6dm2^~?3>l%3rA_33U_JHh@qs|= zMm>QOnY+A2%_N-#Wt_WMq*YW*xM1p|)^+Ld;P6ZCUI>KHbIRNec1VK>&HtQ>G43ZL zDCNe+Sk>b&%CPd2C%d>`V@PHgMc+g)BrA&$q3@$)c-Vb{RZ`yG%N=K|h?>9UF7+Q+ zs~^n)BfQ)7?62sT-^{g3vpMqi@7_kYmd>}W>i<(G0-J3fsoTnpe%$D-rOO0OF`+)> z2`WUFQNM*D)zeyf1N)>I!@a`wb;i3Jw=(=brxPvQ`}a}B7iz|L*fGOmQ7?^z%&(nh z0VE-E7|qWT_;+}IEU@@HGwjkao0c{qPdgU4AH*D>kpG1aV&b00=t*aoB=W|eulP6; zdA==E{zse={hb2yW@o^C^TcKD8(F#=?^YS%_FNify?AU%(}dxTr_fgvVrV9>f!7*_ zLWg9#+4g1qg=;i2kDAO0e*gOQb%-(+eZ1n(K^CN$bc<)SY5)HH5}HjT2J3zqQFVzi z?7?apdZ}K=FJ@3Mm3;izkt_~~?38SodG!8+(MeM>S{$Voy+**sP;~rp=i4vR))pha zK*w17#5yDO%-u|n71`V;CSwfon#!5!wKB4O0Uv?*+3hul|06By*p#L7y}}H2#=Gzo zFSp)2ly++%$%>c(u@*tpyvJ@XMhAbtrr)VguK$i12|>B_?RNLOMrilJ;J=!lb4p(c zwbP96Qfv|dc?27Wp11N&{B*jN6nSJ%iliE{n3}gJX$gEsWIEfsO=_XQsX5_+fzHF} zCJ~0Y^${%FO`hC_9Wspit|6P{fA}zp1;}7)0^-P~4vNb=kKJs#$Eq7;_Abs{|Dy8K z6^e*vO`BdJT_uZ$I{t^#F`N)h6<26J0LkwzdO9G;Xrgb^IB{8Nx$QZ|G&kZ`SPzDB znz6^a$+|5^%-uP?_pU?i*%7-{n!4AB|JwKK7YS5QgCZjn{HI=n2KSfLPgGmfYy0ew z#K60N6H+(J6_+2@)m=v$-4k3v60Kw}cF(rv)IT(wWOd}l-{*H<`tp1^^ALedMCXE) z0GTKZ`*_^1K2i5e=D8N;hK4~@(IQ$&h2+dT<=IREGTGov(1wIv(N2MJS|iR_H=Y7T zTY!UsJCzL2GMxA^`oy$B$XeF}v(iXh&&}ON^4QwNPJ3WW>yW3qu3xTD8ig259l$u1 zd`F1G4!wGX5fAXG5uxn-g$o_NScXhC{P{hN^$HVE7*e++n)eaAn$FEo&$Kr)i=Z#W z%lFvd8ZNyeXX#DQU#a)!?A{;2bVODo$B~xQ@HHdqvUvu0#8sBi<#^osq3(6e7&mtzi;h#tk_*YR$}vMFIX{FbSN72~;hLzf(Ro5{8K5 zj48s5w~N1VeLgafG0FWN5#r&};o-Y5CB_I`+(ywM@I*EHFEiyGV_h}?*?9ZhipYt2 zp%dQO8cs9$7cTy0$WdO#!5&Un#r=`L zfji!+|FTmXDu;L<3VIZH_W80v-kC8~3QZZ4%L|s0$Rqfq3aR!1WMFLHM0N-b+@I}N zk&tH$p&kRO#HWuNwL5@Doa7Zt=Z5$-mnilAukvw@-i=1~+CD6tk%hErsniPI?^&cH zrq$t#vmw6;gC@&A&nB*k4HqB`a<{dhf)f%JswbM9%)13}*dxN0c`iTxt!&f zIQqv-_mi4y)2Q@~7^)73;%GM$CLD9+C>gDwye_7lp~`*5BYO=-aMi=~?VZ!s-IpM+ zpsl_Q3Pr-L(HX-^gAk`93P6Kjyf<)k(gAaG>Yy7#<<3qr%Xj z6});Si;|?JY`5}LNKwWuQXeeOYYMRkBd260YNQle3?DVhotw(veZQx4XSGCgg(1>p=EV+{nJs&Q&^x_Z}Z#YAV+7PT6E{! z>28mjFEVSMIfVt8aHBAxOnk3ih9Io2pIef~QANFgF*K9Ed0 ziW-Ynzd`L9O!P+d(+@19btt<_MLvJ=;tU=NHZYhA$mtg}nYcr_&mNp?uyHJ9>hgGL zcj%5Z=bpk_cMIuz=Hu9RRQ=c=L=rwqQ212;a$z5fa3;o1R!@uxs`|^07MxiX<~TD6 zn4Ub_c@``F$~(Cctmo5$Qj!sCrG1%U5u#aBnZS{B-iUHr=bfk;PyX5d6s(OhEKoc%=6v;)Wg)9#$UjhEVFF$j}5rK?iA1Au4+?s zlDDC{ti84xcSX$pK7K6V8AEDqlYqY(C!P}WO%kiZDAd9XTKM#xSkS3)r;Dv8@C@aI zP?*jDEs(%Wy#~8K82f3;-i8P*7}gt^UZs<~HOJ%BkzzG9HJ$eDWfZK8zmjJ;n4ZpX z`PZWCz3nDVdQzwNw3Z=HXX4Pa-EcxEkF%PLste>JqpX|@f>vh}lTeakL2h|yRf?o3#pp%1`*bCX3U(q5ex-^EVWrMdMQwG z8Fs1HxG}Z<9?I(li~~^nA&MIJrl~r6jjh?Zy5@VId3$^-eV+TgZi`k$)683!c>;=t zG5f964IK5$N=zS&){f*861PQce6HOQgukLz0t6Gdh7&#Ej^mrAnY+nJ1IZ3f7nhxO zv)i*}z&C$H_S|~O=iFo@oM*9+ok&fQImh>b`#!b-kZv~6G)$?@d1S{p2{-mtP3ozItF1ea9{ZZh8#;qEJFN)O(T|VRKBO^02O;o-VdonY@`_(l!kJ`7dJq~9`oNA)l z5#)$ZMz{{C8a1V1(OkC%${LTlRzM0gRFaSo7_-#fO`vNk07iiliQ4GNJc@1(xf;ox z9vT}W1zLXo6>#yM{o4UCfag%g<@leo?!LY9CtXesppwKQ=20l#4{)Dl0X5bfVT4)% zfaO#}V2EGgX`7Zlx;&7}VcO?O%7ZD;vj==Pq@1AKYND-u2WV20xP(D6YSmzt(CRJR zRF~^6c^RzEc~ym*V4Ez)+Ol?S4FbI@Sr_PPn1s3{Y`SLmCZx6<5pI}C!@^7K1rXSo zx|A7_74`OzHLATJXqf;;Mq5O>E8dMiu3*deh{R$~5 zgHa5V=Jti2(N)8;ELa75_I)c0pnOc)Zt(iCp>m;%5neVmY3SfJHdZEYv9m^Os$2<% z;PBl$8|fgYg!?X$bCmu=@7snddSDMZiY)-d@-u4S4{Whjs@l{8z* zX}!~VL=zAl(RmyM`(k2jCx$E1M}ajSOzouOn*g%vaKtI+v75ht*#7L`HC0bsGVa_7 z#?`>t_va!|!*nV{X5esI&}3+6J0F_}7INKgY{Ob#-+frG*t_zxrp#1>I6_$qU;}j_ zRuh1fT*Zme6ZwC9n(kj>WV-8i)Ne}~r6$=qqH>U$flw!H2g$-@&eOh<$e8>Axwd{d zx+K(giTLUcfifl5?f0#!L}o8^r4+F=HN!`Y5aYEKWtm+l_y_Ik$hv_W7A6t443zBU z3%i~cVX-qv?@oxm2;{MFqFY}$gT?;nnNt}A~<_5NBG7}~Z7MI)=AX7mN(EuTnT>Vd< zPPymlOc>#n;+;s}i#JQ=UJDD5?mE+GmdJX+L_Hy;(87O8eb@XPH=;0`RzE5{d?@G{ zfvN+;G!hpn$Apam+KV8YAgQ6Y*Ill+v}lax;75WH(kLV^{wwl8w;u+_Q~WY#xDa+B zh-8izt}QH~g}Qo3)U6pN)0th%oT1aV?@mT--gl(1yS$FS0~wtP5mXh zUGQL`%4a`0vl&*o+LvJ&Onff^-gk=z8pk)~88%*Grh)VdWj^+2LXd7NaWth}CjB?Dw zPA)5vz%5e^V%*Nd838@n+(08yyVmi^Zz-0bYJ!O`!fuN0zobOCWr1-hy1bd1@Y>Op zQ)7pbXcLl|%+D~ecA|~%tB9G>_sb9pV^|LjMqdpuePFyJv+o1wyEN%oC4CdU3tqmg zmdce+gRqS0HHE*d?5>>c9m%0Bq<{1Dgq08N6z}Xz2ZWf-$7HJZQz~J?l+_XbK^O2 zmM|PN=HqOn6@L{V58rd_nb{K{cdqfkoxt~(e0W;zRGs+3IkUP{X=E;KSq!ek#;WP2 zT8Ac{KJ2!xzKkugHYfAmz>XA%G~FChSI?f}%3Y?%|DO(<%F?L^&iiyc4!;~*EO09j zUWSLMP+&;TrIaGoNigGaN|r%%@g;S#*T`y0m8j1T)BN)7(m~PUz9dqh@!TzN8VhSE zq$22jsqBi#*_1O)V{^WyR(D*lw80~)b#%6iAZLJ0+yRp@^g2%-9K!Vwr4#2_q3pcF zF7VB*tdiD7dYN~+x66h8VZy^Xd5<55a%VY9qT-Op#s#l2nsP-h!caA5yLgMn4sm_1 zNhibia1khICF zh|%EgPD3+H(}fJT(*5pnDX&>)*Eh@lzW7DxE(WV&i+q?j!j4B`CFcpfSuQ)@!QyUH zO6IYzeqU!9x6#qb8-Bajp3wq16Pcyf(J*q3B1pNKM3AJI+Sx<3A#RUSCQ+3j49W+K z7HjcYno2|U(Ayd*dj`_??C0TPUT_cHItN88E6|Jx3kjl?8$GxV6>>5_K85Ytw1Kpi z-NrU(o+97CDrt+~qh@cwzosEQ64F50RPg2jUtiQxU?@l&q(^vG>@=7~zll8Vt zfWjNSy4G@P;nB0tle|wWpGkF2@Ks8b#3Q*f*UZH;ndf_Q1}z$Tno-asER;=jk5K+> z=$glw7Hdp;XRz(5mT)4_HGANK5~&(6OFKGFwR!!=rlBd``}mCwYNrpr=uYm(z!~*o zU7IYG1TItaREEJufE{TDA)&S<5M*L||9gM$Or5G~uX%^)w{iXzGWmo+}R2!M$qEd@kdAJ$vQj z*Y@*sY&4G5fD~~?6^OPWkvht%@RR(-6O)`OOuQ!F+5NXkQh?vu7kr$+2jCZ~$9p$w@ z|J=*}z!d=r{`unv>MDwR`3RY58YxF^J^WlbJG;;GrE6(T7SMQc7NuFGwn5IaNeXi2 z_VFyZ%?ISgr_Ju&6VeL9nt=lct`4hGlfOy5qKley-g_#_6fjs+&C}=lR_*nrlo22Z zWej49o7kD3qe4U`7}WN+aPS%Fps}r<=X>0U4I47Cee&eVURA1%0u?QJKD%Vp263#- zo9fap(D+Kl&hi3b?mz-1=PMXIdPQj&{xSEyt5< z6h#8H{C+BHfDBLtc^8HC4!#H*88kHZK*&(i7$wJR@{h>w!%M=T=!LI@LXz;yK^u40 zQhr;=!PRkE?rVd-FT7a(aFWz%dgr71VA5~hra?X^&O34 zpw$&bWi!^S(*>V&2~%C$=BKtew#e*zvB?Y4GBA!)T!Xm3uZ8X0c>_nDr5}=C%eXxl zA0V9aqa^tcO-WjvH~Y-GV^SS}Hz3-yV%knAsyLeWm}WxyTe2W?5w~acQtrUAen>pO z#WU;V&=5v}Nml2jFKaPhl@2%kwQ!rAuNO0Kl^o1`#}I9Joht3m=#`E8lI`gD=Wki8|xejbiG;j7f7Z4#857U^akJy0c>z zDW96l?-L8k{pIT{q<>rh&IcCbQxxV1ebEt~18Y5@dx}OVv*gu@r!wBD%U>FKj5cop z_Bb{RHY4A4UjA1vKrY#5iwUw;h3cLvzpYFG^C8{HkR)781n+=pX3GQ^-5)0)mTo}M zK@$I@U*|c~ox;VRhF!!?NPb0B1yq~=@?{#bDbUUCU=Bc_8;CYte?`VIYR+5q2-fh) zhzmyo=OUGsG822pB*=JyDD!5=4O~scTEC&J2wDKVN=8|Y zkbnjw4hwLR0ikJBbv}fiuGAw&G~z@QQ4vAARl|GqhNK|ad|nJynGEs@&7mqp)N}kF zrK>e)P{?49Y!yqXc(n@?_Pr3yCOBH76-y;Tp*)Bt=<&*3dha^jBS~Jmb$4E&#f+zI z%zy^aNi>D8LW+7D=#wo{4ZxHSazhnQFRg6NC<)^4;Z+C6m8c&5GY(oi{-`E1iQ70& ztygm|dmy%x?wKUR#O9HrHXvOuR=xNYeAH_l_uYCaE&&`xR5U<_Vk49Fk)$ANhM0-* z7it>J!C;)e^6;2N#A#Ko587-h%{2NlO%OT&seA%?%EvpueR`jl>s`q&Ogw#hT*roT zqtz>H)SRoP;ZP69$_w_}xUmlOo~TPjVn;!k=OF z+Op>~<>%)o@*i#`8O>J8x8sgqmB%N<*WgjDy$b5FHiI0sT0SNSahQhmAKFY>{H4l z6zGlFUl$tkJG`4K^(0%if^h$2lodgc#Kk7%wuypszL)pOQt?a4SEkj)vzT^7_X z{_r#twkvtn&wjL#(4}kXfybQku=fgAP4^^U9eXyz;9^Jy?MBxr=S^W@8D-Bs^ZTTm z;oWSaE!Ru8E?^9@s1u8mRUfOq`M5MMI)K@EN$XMY)!FkKIT$V=H7#uoT{uHx8}nw5 zuQOQrIx@XKKkWWu{&|Wmq-v=@dpWZPQt^XHSB-8Hw^QnM%&qw6`?lQ^@B(^*>Kgn^ zV{MvZH9o{3!!R*rxMYC!4`(;QnKO2`la!VV>OPj{&AWo8xq5lo&}d`T&F0MNGO2p0 zs&cu2k)-Kg=h*35v$GlRvfm|v#&;UsNmthxzFQ0$;p-@#b;*mAyoW8X4T! z+4qqm9WaJ~(5gaa4k&{+`8n=>byMdFr71E!Ju@nFAZWB)lz#-ft zzRCib{>odKA>J!Wz5x)C9BY%k5eJSwztB^5x`D!TQD=gsQ`*+pmbw2|h%*}#{P*!W zy>|&jAv4uVl}yc4WUXRINNm%=Lw96P<^9*aezOOcZ_5_y;EAi|63m3EhvWc+ss(HX zaEpcuJTnBX5RB-r#*#m#&L8`71t`-5u2@8NH+C4cLyM?G`lnnKdLir56a->AfNf7N zF5l7SX_Trh@-|C2UV5|DdAWEXn;Fzo^XJVw5tYjYpaZd?E2YerEX3^*Ju1*D#P!MX z70TnRQ#Cd?@46S{3Ab33d0a>-_b7Sz(CZziu57*zQSFD-JPycCxUEm?dyRrdBpqUfckSAGYWI8ag0OfaH#O~~Ai^SAY(evwr7^gc zk?NKl^0q3GWf~$;_=7)65|kzieVWV{Q1AP&D~5G#{h9Xpr`9Qqp0ILr%hl*0apFlu z)`T!z)GoXd`5ieqAt)fk@RP@Ba`dM%2>|pK`oE~Pl}123ZEz6-xS;m4`C_>(a~)UV z^JlX-RRa>@+ijR_uUj`fOkF4`d{X#}2#8mCdY(_uB{nxu?W?Jn>wR^AvnH)2ffaX> zp`%AP;s4u@V)$`_vLUhBV{Nwh!ybwP3M)4vH39yxTZ;1RniH(rcONjrm?G@WEOG}Yg%8`yQJvWNDc(c{;X3^9!R^=Ct`Y5e+= zNjv>im(#ihIxf`%clm$vmtCF3kzw{G@rQ_ujyUxY784Jjs@&zItG3kZAP;lQK8uus z|NP8;wCd~2jl{?q{oGw$@#YSqskw1%a&N=O~BI3yx(%KmUuTMx!=|bXlWFj z;~^!bpw8rhQi^KugFPFU8IeEG5)4MW(az>~<~X9Mp0^ZTtlUh)!ClkNDv(nKw*lif z9IVms`pJ`>f%Sn@baY%6fm5gBF}<31Zcq3Z2|Bfp5AbEXw-=ExA)EG{eY*dhFW>s8 zwJ@}XKk2rG{eRWOhw02CzVwzYaasZX*1_dw`e?nJI_c0YMbUbk*sMBWN%EaA1e?O^rK1x2@n1A#eBb{b>iu)kgDhr>o<9 z0jEcRocW6ubyDdk)Z4YI+WSsJJ>-@ggR5_YhQigZA>pp7C~7HsZL(YCgY!qK@@ZU| z1Kdi9oJdb0N({m3Q8ZMRFBiE55OERjR6x72H7pgL()F< znE)jDl?te5TMMEHX9cUy-*QVyF(owcJy|2ePo7&=e)zz#h0r6i;hyGQC~^Lf^xI?( z5&B>6h-Oab3&Ikz(KA@BZN>A6FBP~x8Frky;;NWHx{mf+7T(sN8xY*__Fz(;^t2CJe`7wBDeq85?I>a#KuBK6$*AQdG516lA@|3 zr!KU{5qLM!&q=~29nnAzuavh;J=%n?c#abny2Z2Wx+|b@Acw2Umy@o{b6uxS{nK{r zM;XAmeAzdyb1RNU*ammAII!9!3NaE7M=tX7x?zL+N$L*yh*aYlm;e8P=ltUZjS0|%a1(jFIj9-BW&6@SL#%p{T zck}cu?tgCyFF|QPVBU}8!9N{`1ZX6#i#+l*2<@#XHR)vs0@^}Ce$8s}&G*Za76#)R zq?XMET^YormUiDq>FEWD_YHJHpP&S?vht4N#bZDhVf*_7n=t)rpbQlZpc-DMKl`}1 z_rzlWef&fbqMdM8>hB^%ek)F0>1n<9j(DnNB{EzB%=yXb`DMJ=%s<}hiE-vmq{IF1 zI&^#{y(_1!+ZJAhSW%-AsID36I%0_33l5%AJIIuUzyVY_OkiQAD|DU?yoG1f+E2Vaw!A=M4}dCtP8g);$b}YL8B47 z3S~}(LW7`TFg{4;n#cv$$0Yb$&zb33nea@9yIGjnQ){yb!Y%V+W$gq_t(eufYqz(~ z9=ksW`hYlZ`7r^q$HO$AEF3xnm3|8T89ro`)iKLye*XiPnkPR1 diff --git a/pv_assessment.py b/pv_assessment.py deleted file mode 100644 index f44be785..00000000 --- a/pv_assessment.py +++ /dev/null @@ -1,73 +0,0 @@ -import pandas as pd -from scripts.geojson_creator import process_geojson -from pathlib import Path -import subprocess -from hub.imports.geometry_factory import GeometryFactory -from hub.helpers.dictionaries import Dictionaries -from hub.imports.construction_factory import ConstructionFactory -from hub.imports.usage_factory import UsageFactory -from hub.imports.weather_factory import WeatherFactory -from hub.imports.results_factory import ResultFactory -from scripts.solar_angles import CitySolarAngles -from scripts.ep_run_enrich import energy_plus_workflow -import hub.helpers.constants as cte -from hub.exports.exports_factory import ExportsFactory -from scripts.pv_sizing_and_simulation import PVSizingSimulation -# Specify the GeoJSON file path -geojson_file = process_geojson(x=-73.5681295982132, y=45.49218262677643, diff=0.0001) -file_path = (Path(__file__).parent / 'input_files' / 'output_buildings.geojson') -# Specify the output path for the PDF file -output_path = (Path(__file__).parent / 'out_files').resolve() -# Create city object from GeoJSON file -city = GeometryFactory('geojson', - path=file_path, - height_field='height', - year_of_construction_field='year_of_construction', - function_field='function', - function_to_hub=Dictionaries().montreal_function_to_hub_function).city -# Enrich city data -ConstructionFactory('nrcan', city).enrich() - -UsageFactory('nrcan', city).enrich() -WeatherFactory('epw', city).enrich() -ExportsFactory('sra', city, output_path).export() -sra_path = (output_path / f'{city.name}_sra.xml').resolve() -subprocess.run(['sra', str(sra_path)]) -ResultFactory('sra', city, output_path).enrich() -energy_plus_workflow(city, output_path=output_path) -solar_angles = CitySolarAngles(city.name, - city.latitude, - city.longitude, - tilt_angle=45, - surface_azimuth_angle=180).calculate -df = pd.DataFrame() -df.index = ['yearly lighting (kWh)', 'yearly appliance (kWh)', 'yearly heating (kWh)', 'yearly cooling (kWh)', - 'yearly dhw (kWh)', 'roof area (m2)', 'used area for pv (m2)', 'number of panels', 'pv production (kWh)'] -for building in city.buildings: - ghi = [x / cte.WATTS_HOUR_TO_JULES for x in building.roofs[0].global_irradiance[cte.HOUR]] - pv_sizing_simulation = PVSizingSimulation(building, - solar_angles, - tilt_angle=45, - module_height=1, - module_width=2, - ghi=ghi) - pv_sizing_simulation.pv_output() - yearly_lighting = building.lighting_electrical_demand[cte.YEAR][0] / 1000 - yearly_appliance = building.appliances_electrical_demand[cte.YEAR][0] / 1000 - yearly_heating = building.heating_demand[cte.YEAR][0] / (3.6e6 * 3) - yearly_cooling = building.cooling_demand[cte.YEAR][0] / (3.6e6 * 4.5) - yearly_dhw = building.domestic_hot_water_heat_demand[cte.YEAR][0] / 1000 - roof_area = building.roofs[0].perimeter_area - used_roof = pv_sizing_simulation.available_space() - number_of_pv_panels = pv_sizing_simulation.total_number_of_panels - yearly_pv = building.onsite_electrical_production[cte.YEAR][0] / 1000 - df[f'{building.name}'] = [yearly_lighting, yearly_appliance, yearly_heating, yearly_cooling, yearly_dhw, roof_area, - used_roof, number_of_pv_panels, yearly_pv] - -df.to_csv(output_path / 'pv.csv') - - - - - - diff --git a/scripts/ep_workflow.py b/scripts/ep_workflow.py deleted file mode 100644 index 92a00958..00000000 --- a/scripts/ep_workflow.py +++ /dev/null @@ -1,63 +0,0 @@ -import glob -import os -import sys -from pathlib import Path -import csv - -from hub.imports.geometry_factory import GeometryFactory -from hub.imports.construction_factory import ConstructionFactory -from hub.imports.usage_factory import UsageFactory -from hub.imports.weather_factory import WeatherFactory -from hub.exports.energy_building_exports_factory import EnergyBuildingsExportsFactory -from hub.helpers.dictionaries import Dictionaries -from hub.imports.results_factory import ResultFactory - -sys.path.append('./') - -try: - file_path = (Path(__file__).parent.parent / 'input_files' / 'eilat.geojson') - out_path = (Path(__file__).parent.parent / 'out_files') - files = glob.glob(f'{out_path}/*') - - for file in files: - if file != '.gitignore': - os.remove(file) - - print('[simulation start]') - city = GeometryFactory('geojson', - path=file_path, - height_field='heightmax', - year_of_construction_field='ANNEE_CONS', - function_field='CODE_UTILI', - function_to_hub=Dictionaries().eilat_function_to_hub_function).city - print(f'city created from {file_path}') - ConstructionFactory('eilat', city).enrich() - print('enrich constructions... done') - UsageFactory('eilat', city).enrich() - print('enrich usage... done') - WeatherFactory('epw', city).enrich() - print('enrich weather... done') - - area = 0 - volume = 0 - for building in city.buildings: - volume = building.volume - for ground in building.grounds: - area += ground.perimeter_polygon.area - - print('exporting:') - _idf = EnergyBuildingsExportsFactory('idf', city, out_path).export() - print(' idf exported...') - _idf.run() - - csv_file = str((out_path / f'{city.name}_out.csv').resolve()) - eso_file = str((out_path / f'{city.name}_out.eso').resolve()) - idf_file = str((out_path / f'{city.name}.idf').resolve()) - obj_file = str((out_path / f'{city.name}.obj').resolve()) - #ResultFactory('energy_plus_multiple_buildings', city, out_path).enrich() - -except Exception as ex: - print(ex) - print('error: ', ex) - print('[simulation abort]') -sys.stdout.flush() diff --git a/scripts/optimization/energy_system_sizing_optimization.py b/scripts/optimization/energy_system_sizing_optimization.py deleted file mode 100644 index c5ef366a..00000000 --- a/scripts/optimization/energy_system_sizing_optimization.py +++ /dev/null @@ -1,26 +0,0 @@ -import random -import numpy as np -import hub.helpers.constants as cte - -class EnergySystemOptimizer: - def __init__(self, building, objectives, population_size=20, number_of_generations=100, crossover_rate=0.8, - mutation_rate=0.1): - self.building = building - self.objectives = objectives - self.population_size = population_size - self.num_gen = number_of_generations - self.crossover_rate = crossover_rate - self.mutation_rate = mutation_rate - -# Initialize population - def initialize_population(self): - population = [] - for _ in range(self.population_size): - individual = [ - random.uniform(0.1, 10.0), # hp_size - random.uniform(0.1, 10.0), # boiler_size - random.uniform(0.1, 10.0), # tes_size - random.uniform(40, 60) # cutoff_temp - ] - population.append(individual) - return population \ No newline at end of file diff --git a/scripts/optimization/objectives.py b/scripts/optimization/objectives.py deleted file mode 100644 index fce62a8d..00000000 --- a/scripts/optimization/objectives.py +++ /dev/null @@ -1,16 +0,0 @@ -# Objective functions -MINIMUM_LCC = 1 -MINIMUM_EC = 2 -MINIMUM_EMISSIONS = 3 -MINIMUM_LCC_MINIMUM_EC = 4 -MINIMUM_LCC_MINIMUM_EMISSIONS = 5 -MINIMUM_EC_MINIMUM_EMISSIONS = 6 -MINIMUM_LCC_MINIMUM_EC_MINIMUM_EMISSIONS = 7 -OBJECTIVE_FUNCTIONS = [MINIMUM_LCC, - MINIMUM_EC, - MINIMUM_EMISSIONS, - MINIMUM_LCC_MINIMUM_EC, - MINIMUM_LCC_MINIMUM_EMISSIONS, - MINIMUM_EC_MINIMUM_EMISSIONS, - MINIMUM_LCC_MINIMUM_EC_MINIMUM_EMISSIONS] - diff --git a/scripts/system_simulation_models/archetype1.py b/scripts/system_simulation_models/archetype1.py deleted file mode 100644 index 5c56ec66..00000000 --- a/scripts/system_simulation_models/archetype1.py +++ /dev/null @@ -1,378 +0,0 @@ -import math -import csv -import hub.helpers.constants as cte -from hub.helpers.monthly_values import MonthlyValues - - -class Archetype1: - def __init__(self, building, output_path): - self._building = building - self._name = building.name - self._pv_system = building.energy_systems[1] - self._hvac_system = building.energy_systems[0] - self._dhw_system = building.energy_systems[-1] - 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 = [0] + [demand / 3600 for demand in building.heating_demand[cte.HOUR]] - self._hourly_cooling_demand = [demand / 3600 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 - self._t_out = [0] + building.external_temperature[cte.HOUR] - self.results = {} - self.dt = 900 - - def hvac_sizing(self): - storage_factor = 3 - heat_pump = self._hvac_system.generation_systems[0] - boiler = self._hvac_system.generation_systems[1] - thermal_storage = heat_pump.energy_storage_systems[0] - heat_pump.nominal_heat_output = round(0.5 * self._heating_peak_load) - heat_pump.nominal_cooling_output = round(self._cooling_peak_load) - boiler.nominal_heat_output = round(0.5 * self._heating_peak_load) - thermal_storage.volume = round( - (self._heating_peak_load * storage_factor * cte.WATTS_HOUR_TO_JULES) / - (cte.WATER_HEAT_CAPACITY * cte.WATER_DENSITY * 25)) - return heat_pump, boiler, thermal_storage - - def dhw_sizing(self): - storage_factor = 3 - dhw_hp = self._dhw_system.generation_systems[0] - dhw_hp.nominal_heat_output = 0.7 * self._domestic_hot_water_peak_load - dhw_hp.source_temperature = self._t_out - dhw_tes = dhw_hp.energy_storage_systems[0] - dhw_tes.volume = round( - (self._domestic_hot_water_peak_load * storage_factor * 3600) / (cte.WATER_HEAT_CAPACITY * cte.WATER_DENSITY * 10)) - return dhw_hp, dhw_tes - - def heating_system_simulation(self): - hp, boiler, tes = self.hvac_sizing() - cop_curve_coefficients = [float(coefficient) for coefficient in hp.heat_efficiency_curve.coefficients] - number_of_ts = int(cte.HOUR_TO_SECONDS / self.dt) - demand = [0] + [x for x in self._hourly_heating_demand for _ in range(number_of_ts)] - t_out = [0] + [x for x in self._t_out for _ in range(number_of_ts)] - hp.source_temperature = self._t_out - # Heating System Simulation - variable_names = ["t_sup_hp", "t_tank", "t_ret", "m_ch", "m_dis", "q_hp", "q_boiler", "hp_cop", - "hp_electricity", "boiler_gas", "boiler_consumption", "t_sup_boiler", "heating_consumption"] - num_hours = len(demand) - variables = {name: [0] * num_hours for name in variable_names} - (t_sup_hp, t_tank, t_ret, m_ch, m_dis, q_hp, q_boiler, hp_cop, - hp_electricity, boiler_gas, boiler_consumption, t_sup_boiler, heating_consumption) = [variables[name] for name in - variable_names] - t_tank[0] = 30 - dt = 3600 - hp_heating_cap = hp.nominal_heat_output - hp_efficiency = float(hp.heat_efficiency) - boiler_efficiency = float(boiler.heat_efficiency) - v, h = float(tes.volume), float(tes.height) - r_tot = sum(float(layer.thickness) / float(layer.material.conductivity) for layer in - tes.layers) - u_tot = 1 / r_tot - d = math.sqrt((4 * v) / (math.pi * h)) - a_side = math.pi * d * h - a_top = math.pi * d ** 2 / 4 - ua = u_tot * (2 * a_top + a_side) - for i in range(len(demand) - 1): - t_tank[i + 1] = (t_tank[i] + - ((m_ch[i] * (t_sup_hp[i] - t_tank[i])) + - (ua * (t_out[i] - t_tank[i])) / cte.WATER_HEAT_CAPACITY - - m_dis[i] * (t_tank[i] - t_ret[i])) * (dt / (cte.WATER_DENSITY * v))) - if t_tank[i + 1] < 40: - q_hp[i + 1] = hp_heating_cap - m_ch[i + 1] = q_hp[i + 1] / (cte.WATER_HEAT_CAPACITY * 5) - t_sup_hp[i + 1] = (q_hp[i + 1] / (m_ch[i + 1] * cte.WATER_HEAT_CAPACITY)) + t_tank[i + 1] - elif 40 <= t_tank[i + 1] < 55 and q_hp[i] == 0: - q_hp[i + 1] = 0 - m_ch[i + 1] = 0 - t_sup_hp[i + 1] = t_tank[i + 1] - elif 40 <= t_tank[i + 1] < 55 and q_hp[i] > 0: - q_hp[i + 1] = hp_heating_cap - m_ch[i + 1] = q_hp[i + 1] / (cte.WATER_HEAT_CAPACITY * 3) - t_sup_hp[i + 1] = (q_hp[i + 1] / (m_ch[i + 1] * cte.WATER_HEAT_CAPACITY)) + t_tank[i + 1] - else: - q_hp[i + 1], m_ch[i + 1], t_sup_hp[i + 1] = 0, 0, t_tank[i + 1] - t_tank_fahrenheit = 1.8 * t_tank[i + 1] + 32 - t_out_fahrenheit = 1.8 * t_out[i + 1] + 32 - if q_hp[i + 1] > 0: - hp_cop[i + 1] = (1 / (cop_curve_coefficients[0] + - cop_curve_coefficients[1] * t_tank_fahrenheit + - cop_curve_coefficients[2] * t_tank_fahrenheit ** 2 + - cop_curve_coefficients[3] * t_out_fahrenheit + - cop_curve_coefficients[4] * t_out_fahrenheit ** 2 + - cop_curve_coefficients[5] * t_tank_fahrenheit * t_out_fahrenheit)) * hp_efficiency - hp_electricity[i + 1] = q_hp[i + 1] / hp_cop[i + 1] - else: - hp_cop[i + 1] = 0 - hp_electricity[i + 1] = 0 - if demand[i + 1] == 0: - m_dis[i + 1], t_return, t_ret[i + 1] = 0, t_tank[i + 1], t_tank[i + 1] - else: - if demand[i + 1] > 0.5 * self._heating_peak_load / dt: - factor = 8 - else: - factor = 4 - m_dis[i + 1] = self._heating_peak_load / (cte.WATER_HEAT_CAPACITY * factor * dt) - t_return = t_tank[i + 1] - demand[i + 1] / (m_dis[i + 1] * cte.WATER_HEAT_CAPACITY) - if t_return >= 25: - t_ret[i + 1] = t_tank[i + 1] - demand[i + 1] / (m_dis[i + 1] * cte.WATER_HEAT_CAPACITY) - q_boiler[i + 1] = 0 - t_sup_boiler[i + 1] = t_tank[i + 1] - else: - t_ret[i + 1] = 25 - t_sup_boiler[i + 1] = t_ret[i + 1] + (demand[i + 1] / (m_dis[i + 1] * cte.WATER_HEAT_CAPACITY)) - q_boiler[i + 1] = m_dis[i + 1] * cte.WATER_HEAT_CAPACITY * (t_sup_boiler[i + 1] - t_tank[i + 1]) - boiler_gas[i + 1] = (q_boiler[i + 1] * dt) / cte.NATURAL_GAS_LHV - boiler_consumption[i + 1] = q_boiler[i + 1] / boiler_efficiency - heating_consumption[i + 1] = boiler_consumption[i + 1] + hp_electricity[i + 1] - tes.temperature = [] - hp_electricity_j = [(x * cte.WATTS_HOUR_TO_JULES) / number_of_ts for x in hp_electricity] - boiler_consumption_j = [(x * cte.WATTS_HOUR_TO_JULES) / number_of_ts for x in boiler_consumption] - hp_hourly = [] - boiler_hourly = [] - boiler_sum = 0 - hp_sum = 0 - for i in range(1, len(demand)): - hp_sum += hp_electricity_j[i] - boiler_sum += boiler_consumption_j[i] - if (i - 1) % number_of_ts == 0: - tes.temperature.append(t_tank[i]) - hp_hourly.append(hp_sum) - boiler_hourly.append(boiler_sum) - hp_sum = 0 - boiler_sum = 0 - hp.energy_consumption[cte.HEATING] = {} - hp.energy_consumption[cte.HEATING][cte.HOUR] = hp_hourly - hp.energy_consumption[cte.HEATING][cte.MONTH] = MonthlyValues.get_total_month( - hp.energy_consumption[cte.HEATING][cte.HOUR]) - hp.energy_consumption[cte.HEATING][cte.YEAR] = [ - sum(hp.energy_consumption[cte.HEATING][cte.MONTH])] - boiler.energy_consumption[cte.HEATING] = {} - boiler.energy_consumption[cte.HEATING][cte.HOUR] = boiler_hourly - boiler.energy_consumption[cte.HEATING][cte.MONTH] = MonthlyValues.get_total_month( - boiler.energy_consumption[cte.HEATING][cte.HOUR]) - boiler.energy_consumption[cte.HEATING][cte.YEAR] = [ - sum(boiler.energy_consumption[cte.HEATING][cte.MONTH])] - self.results['Heating Demand (W)'] = demand - self.results['HP Heat Output (W)'] = q_hp - self.results['HP Source Temperature'] = t_out - self.results['HP Supply Temperature'] = t_sup_hp - self.results['HP COP'] = hp_cop - self.results['HP Electricity Consumption (W)'] = hp_electricity - self.results['Boiler Heat Output (W)'] = q_boiler - self.results['Boiler Supply Temperature'] = t_sup_boiler - self.results['Boiler Gas Consumption'] = boiler_consumption - self.results['TES Temperature'] = t_tank - self.results['TES Charging Flow Rate (kg/s)'] = m_ch - self.results['TES Discharge Flow Rate (kg/s)'] = m_dis - self.results['Heating Loop Return Temperature'] = t_ret - return hp_hourly, boiler_hourly - - def cooling_system_simulation(self): - hp = self.hvac_sizing()[0] - eer_curve_coefficients = [float(coefficient) for coefficient in hp.cooling_efficiency_curve.coefficients] - cooling_efficiency = float(hp.cooling_efficiency) - number_of_ts = int(cte.HOUR_TO_SECONDS / self.dt) - demand = [0] + [x for x in self._hourly_cooling_demand for _ in range(number_of_ts)] - t_out = [0] + [x for x in self._t_out for _ in range(number_of_ts)] - hp.source_temperature = self._t_out - variable_names = ["t_sup_hp", "t_ret", "m", "q_hp", "hp_electricity", "hp_cop"] - num_hours = len(demand) - variables = {name: [0] * num_hours for name in variable_names} - (t_sup_hp, t_ret, m, q_hp, hp_electricity, hp_cop) = [variables[name] for name in variable_names] - t_ret[0] = 13 - - for i in range(1, len(demand)): - if demand[i] > 0.15 * self._cooling_peak_load: - 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: - q_hp[i] = 0.25 * hp.nominal_cooling_output - 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 - t_sup_hp[i] = t_ret[i - 1] - q_hp[i] / (m[i] * cte.WATER_HEAT_CAPACITY) - else: - q_hp[i] = 0 - t_sup_hp[i] = t_ret[i - 1] - if m[i] == 0: - t_ret[i] = t_sup_hp[i] - else: - t_ret[i] = t_sup_hp[i] + demand[i] / (m[i] * cte.WATER_HEAT_CAPACITY) - else: - m[i] = 0 - q_hp[i] = 0 - t_sup_hp[i] = t_ret[i - 1] - t_ret[i] = t_ret[i - 1] - t_sup_hp_fahrenheit = 1.8 * t_sup_hp[i] + 32 - t_out_fahrenheit = 1.8 * t_out[i] + 32 - if q_hp[i] > 0: - hp_cop[i] = (1 / (eer_curve_coefficients[0] + - eer_curve_coefficients[1] * t_sup_hp_fahrenheit + - eer_curve_coefficients[2] * t_sup_hp_fahrenheit ** 2 + - 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)) * cooling_efficiency / 3.41 - hp_electricity[i] = q_hp[i] / cooling_efficiency - else: - hp_cop[i] = 0 - hp_electricity[i] = 0 - hp_electricity_j = [(x * cte.WATTS_HOUR_TO_JULES) / number_of_ts for x in hp_electricity] - hp_hourly = [] - hp_sum = 0 - for i in range(1, len(demand)): - hp_sum += hp_electricity_j[i] - if (i - 1) % number_of_ts == 0: - hp_hourly.append(hp_sum) - hp_sum = 0 - hp.energy_consumption[cte.COOLING] = {} - hp.energy_consumption[cte.COOLING][cte.HOUR] = hp_hourly - hp.energy_consumption[cte.COOLING][cte.MONTH] = MonthlyValues.get_total_month( - hp.energy_consumption[cte.COOLING][cte.HOUR]) - hp.energy_consumption[cte.COOLING][cte.YEAR] = [ - sum(hp.energy_consumption[cte.COOLING][cte.MONTH])] - self.results['Cooling Demand (W)'] = demand - self.results['HP Cooling Output (W)'] = q_hp - self.results['HP Cooling Supply Temperature'] = t_sup_hp - self.results['HP Cooling COP'] = hp_cop - self.results['HP Electricity Consumption'] = hp_electricity - self.results['Cooling Loop Flow Rate (kg/s)'] = m - self.results['Cooling Loop Return Temperature'] = t_ret - return hp_hourly - - def dhw_system_simulation(self): - hp, tes = self.dhw_sizing() - cop_curve_coefficients = [float(coefficient) for coefficient in hp.heat_efficiency_curve.coefficients] - number_of_ts = int(cte.HOUR_TO_SECONDS / self.dt) - demand = [0] + [x for x in self._hourly_dhw_demand for _ in range(number_of_ts)] - t_out = [0] + [x for x in self._t_out for _ in range(number_of_ts)] - variable_names = ["t_sup_hp", "t_tank", "m_ch", "m_dis", "q_hp", "q_coil", "hp_cop", - "hp_electricity", "available hot water (m3)", "refill flow rate (kg/s)"] - num_hours = len(demand) - variables = {name: [0] * num_hours for name in variable_names} - (t_sup_hp, t_tank, m_ch, m_dis, m_refill, q_hp, q_coil, hp_cop, hp_electricity, v_dhw) = \ - [variables[name] for name in variable_names] - t_tank[0] = 70 - v_dhw[0] = tes.volume - - hp_heating_cap = hp.nominal_heat_output - hp_delta_t = 8 - v, h = float(tes.volume), float(tes.height) - r_tot = sum(float(layer.thickness) / float(layer.material.conductivity) for layer in - tes.layers) - u_tot = 1 / r_tot - d = math.sqrt((4 * v) / (math.pi * h)) - a_side = math.pi * d * h - a_top = math.pi * d ** 2 / 4 - ua = u_tot * (2 * a_top + a_side) - freshwater_temperature = 18 - for i in range(len(demand) - 1): - delta_t_demand = demand[i] * (self.dt / (cte.WATER_DENSITY * cte.WATER_HEAT_CAPACITY * v)) - if t_tank[i] < 62: - q_hp[i] = hp_heating_cap - delta_t_hp = q_hp[i] * (self.dt / (cte.WATER_DENSITY * cte.WATER_HEAT_CAPACITY * v)) - if demand[i] > 0: - dhw_needed = (demand[i] * cte.HOUR_TO_SECONDS) / (cte.WATER_HEAT_CAPACITY * t_tank[i] * cte.WATER_DENSITY) - m_dis[i] = dhw_needed * cte.WATER_DENSITY / cte.HOUR_TO_SECONDS - m_refill[i] = m_dis[i] - delta_t_freshwater = m_refill[i] * (t_tank[i] - freshwater_temperature) * (self.dt / (v * cte.WATER_DENSITY)) - if t_tank[i] < 60: - q_coil[i] = float(tes.heating_coil_capacity) - delta_t_coil = q_coil[i] * (self.dt / (cte.WATER_DENSITY * cte.WATER_HEAT_CAPACITY * v)) - - if q_hp[i] > 0: - m_ch[i] = q_hp[i] / (cte.WATER_HEAT_CAPACITY * hp_delta_t) - t_sup_hp[i] = (q_hp[i] / (m_ch[i] * cte.WATER_HEAT_CAPACITY)) + t_tank[i] - else: - m_ch[i] = 0 - t_sup_hp[i] = t_tank[i] - t_sup_hp_fahrenheit = 1.8 * t_sup_hp[i] + 32 - t_out_fahrenheit = 1.8 * t_out[i] + 32 - if q_hp[i] > 0: - hp_cop[i] = (cop_curve_coefficients[0] + - cop_curve_coefficients[1] * t_out[i] + - cop_curve_coefficients[2] * t_out[i] ** 2 + - cop_curve_coefficients[3] * t_tank[i] + - cop_curve_coefficients[4] * t_tank[i] ** 2 + - cop_curve_coefficients[5] * t_tank[i] * t_out[i]) * float(hp.heat_efficiency) - hp_electricity[i] = q_hp[i] / hp_cop[i] - else: - hp_cop[i] = 0 - hp_electricity[i] = 0 - - t_tank[i + 1] = t_tank[i] + (delta_t_hp - delta_t_freshwater - delta_t_demand + delta_t_coil) - tes.temperature = [] - hp_electricity_j = [(x * cte.WATTS_HOUR_TO_JULES) / number_of_ts for x in hp_electricity] - heating_coil_j = [(x * cte.WATTS_HOUR_TO_JULES) / number_of_ts for x in q_coil] - hp_hourly = [] - coil_hourly = [] - coil_sum = 0 - hp_sum = 0 - for i in range(1, len(demand)): - hp_sum += hp_electricity_j[i] - coil_sum += heating_coil_j[i] - if (i - 1) % number_of_ts == 0: - tes.temperature.append(t_tank[i]) - hp_hourly.append(hp_sum) - coil_hourly.append(coil_sum) - hp_sum = 0 - coil_sum = 0 - - hp.energy_consumption[cte.DOMESTIC_HOT_WATER] = {} - hp.energy_consumption[cte.DOMESTIC_HOT_WATER][cte.HOUR] = hp_hourly - hp.energy_consumption[cte.DOMESTIC_HOT_WATER][cte.MONTH] = MonthlyValues.get_total_month( - hp.energy_consumption[cte.DOMESTIC_HOT_WATER][cte.HOUR]) - hp.energy_consumption[cte.DOMESTIC_HOT_WATER][cte.YEAR] = [ - sum(hp.energy_consumption[cte.DOMESTIC_HOT_WATER][cte.MONTH])] - tes.heating_coil_energy_consumption = {} - tes.heating_coil_energy_consumption[cte.HOUR] = coil_hourly - tes.heating_coil_energy_consumption[cte.MONTH] = MonthlyValues.get_total_month( - tes.heating_coil_energy_consumption[cte.HOUR]) - tes.heating_coil_energy_consumption[cte.YEAR] = [ - sum(tes.heating_coil_energy_consumption[cte.MONTH])] - tes.temperature = t_tank - - self.results['DHW Demand (W)'] = demand - self.results['DHW HP Heat Output (W)'] = q_hp - self.results['DHW HP Electricity Consumption (W)'] = hp_electricity - self.results['DHW HP Source Temperature'] = t_out - self.results['DHW HP Supply Temperature'] = t_sup_hp - self.results['DHW HP COP'] = hp_cop - self.results['DHW TES Heating Coil Heat Output (W)'] = q_coil - self.results['DHW TES Temperature'] = t_tank - self.results['DHW TES Charging Flow Rate (kg/s)'] = m_ch - self.results['DHW Flow Rate (kg/s)'] = m_dis - self.results['DHW TES Refill Flow Rate (kg/s)'] = m_refill - self.results['Available Water in Tank (m3)'] = v_dhw - return hp_hourly, coil_hourly - - def enrich_buildings(self): - hp_heating, boiler_consumption = self.heating_system_simulation() - hp_cooling = self.cooling_system_simulation() - hp_dhw, heating_coil = self.dhw_system_simulation() - heating_consumption = [hp_heating[i] + boiler_consumption[i] for i in range(len(hp_heating))] - dhw_consumption = [hp_dhw[i] + heating_coil[i] for i in range(len(hp_dhw))] - self._building.heating_consumption[cte.HOUR] = heating_consumption - self._building.heating_consumption[cte.MONTH] = ( - MonthlyValues.get_total_month(self._building.heating_consumption[cte.HOUR])) - self._building.heating_consumption[cte.YEAR] = [sum(self._building.heating_consumption[cte.MONTH])] - self._building.cooling_consumption[cte.HOUR] = hp_cooling - self._building.cooling_consumption[cte.MONTH] = ( - MonthlyValues.get_total_month(self._building.cooling_consumption[cte.HOUR])) - self._building.cooling_consumption[cte.YEAR] = [sum(self._building.cooling_consumption[cte.MONTH])] - self._building.domestic_hot_water_consumption[cte.HOUR] = 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])] - 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) - # Write header - output_file.writerow(self.results.keys()) - # Write data - output_file.writerows(zip(*self.results.values())) - diff --git a/scripts/system_simulation_models/archetype13.py b/scripts/system_simulation_models/archetype13.py deleted file mode 100644 index 48ebee50..00000000 --- a/scripts/system_simulation_models/archetype13.py +++ /dev/null @@ -1,385 +0,0 @@ -import math -import hub.helpers.constants as cte -import csv -from hub.helpers.monthly_values import MonthlyValues - - -class Archetype13: - def __init__(self, building, output_path, csv_output=True,): - self._building = building - self._name = building.name - self._pv_system = building.energy_systems[0] - self._hvac_system = building.energy_systems[1] - self._dhw_system = building.energy_systems[-1] - self._dhw_peak_flow_rate = (building.thermal_zones_from_internal_zones[0].total_floor_area * - building.thermal_zones_from_internal_zones[0].domestic_hot_water.peak_flow * - cte.WATER_DENSITY) - 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.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 - self._t_out = building.external_temperature[cte.HOUR] - self.results = {} - self.dt = 900 - self.csv_output = csv_output - - def hvac_sizing(self): - storage_factor = 3 - heat_pump = self._hvac_system.generation_systems[1] - boiler = self._hvac_system.generation_systems[0] - thermal_storage = boiler.energy_storage_systems[0] - heat_pump.nominal_heat_output = round(0.5 * self._heating_peak_load) - heat_pump.nominal_cooling_output = round(self._cooling_peak_load) - boiler.nominal_heat_output = round(0.5 * self._heating_peak_load) - thermal_storage.volume = round( - (self._heating_peak_load * storage_factor * cte.WATTS_HOUR_TO_JULES) / - (cte.WATER_HEAT_CAPACITY * cte.WATER_DENSITY * 25)) - return heat_pump, boiler, thermal_storage - - def dhw_sizing(self): - storage_factor = 3 - dhw_hp = self._dhw_system.generation_systems[0] - dhw_hp.nominal_heat_output = 0.7 * self._domestic_hot_water_peak_load - dhw_hp.source_temperature = self._t_out - dhw_tes = dhw_hp.energy_storage_systems[0] - dhw_tes.volume = round( - (self._domestic_hot_water_peak_load * storage_factor * 3600) / (cte.WATER_HEAT_CAPACITY * cte.WATER_DENSITY * 10)) - return dhw_hp, dhw_tes - - def heating_system_simulation(self): - hp, boiler, tes = self.hvac_sizing() - cop_curve_coefficients = [float(coefficient) for coefficient in hp.heat_efficiency_curve.coefficients] - number_of_ts = int(cte.HOUR_TO_SECONDS / self.dt) - demand = [0] + [x for x in self._hourly_heating_demand for _ in range(number_of_ts)] - t_out = [0] + [x for x in self._t_out for _ in range(number_of_ts)] - hp.source_temperature = self._t_out - variable_names = ["t_sup_hp", "t_tank", "t_ret", "m_ch", "m_dis", "q_hp", "q_boiler", "hp_cop", - "hp_electricity", "boiler_gas_consumption", "t_sup_boiler", "boiler_energy_consumption", - "heating_consumption"] - num_hours = len(demand) - variables = {name: [0] * num_hours for name in variable_names} - (t_sup_hp, t_tank, t_ret, m_ch, m_dis, q_hp, q_boiler, hp_cop, - hp_electricity, boiler_gas_consumption, t_sup_boiler, boiler_energy_consumption, heating_consumption) = \ - [variables[name] for name in variable_names] - t_tank[0] = 55 - hp_heating_cap = hp.nominal_heat_output - hp_efficiency = float(hp.heat_efficiency) - boiler_heating_cap = boiler.nominal_heat_output - hp_delta_t = 5 - boiler_efficiency = float(boiler.heat_efficiency) - v, h = float(tes.volume), float(tes.height) - r_tot = sum(float(layer.thickness) / float(layer.material.conductivity) for layer in - tes.layers) - u_tot = 1 / r_tot - d = math.sqrt((4 * v) / (math.pi * h)) - a_side = math.pi * d * h - a_top = math.pi * d ** 2 / 4 - ua = u_tot * (2 * a_top + a_side) - # storage temperature prediction - for i in range(len(demand) - 1): - t_tank[i + 1] = (t_tank[i] + - (m_ch[i] * (t_sup_boiler[i] - t_tank[i]) + - (ua * (t_out[i] - t_tank[i])) / cte.WATER_HEAT_CAPACITY - - m_dis[i] * (t_tank[i] - t_ret[i])) * (self.dt / (cte.WATER_DENSITY * v))) - # hp operation - if t_tank[i + 1] < 40: - q_hp[i + 1] = hp_heating_cap - m_ch[i + 1] = q_hp[i + 1] / (cte.WATER_HEAT_CAPACITY * hp_delta_t) - t_sup_hp[i + 1] = (q_hp[i + 1] / (m_ch[i + 1] * cte.WATER_HEAT_CAPACITY)) + t_tank[i + 1] - elif 40 <= t_tank[i + 1] < 55 and q_hp[i] == 0: - q_hp[i + 1] = 0 - m_ch[i + 1] = 0 - t_sup_hp[i + 1] = t_tank[i + 1] - elif 40 <= t_tank[i + 1] < 55 and q_hp[i] > 0: - q_hp[i + 1] = hp_heating_cap - m_ch[i + 1] = q_hp[i + 1] / (cte.WATER_HEAT_CAPACITY * hp_delta_t) - t_sup_hp[i + 1] = (q_hp[i + 1] / (m_ch[i + 1] * cte.WATER_HEAT_CAPACITY)) + t_tank[i + 1] - else: - q_hp[i + 1], m_ch[i + 1], t_sup_hp[i + 1] = 0, 0, t_tank[i + 1] - t_tank_fahrenheit = 1.8 * t_tank[i + 1] + 32 - t_out_fahrenheit = 1.8 * t_out[i + 1] + 32 - if q_hp[i + 1] > 0: - hp_cop[i + 1] = (1 / (cop_curve_coefficients[0] + - cop_curve_coefficients[1] * t_tank_fahrenheit + - cop_curve_coefficients[2] * t_tank_fahrenheit ** 2 + - cop_curve_coefficients[3] * t_out_fahrenheit + - cop_curve_coefficients[4] * t_out_fahrenheit ** 2 + - cop_curve_coefficients[5] * t_tank_fahrenheit * t_out_fahrenheit)) * hp_efficiency - hp_electricity[i + 1] = q_hp[i + 1] / hp_cop[i + 1] - else: - hp_cop[i + 1] = 0 - hp_electricity[i + 1] = 0 - # boiler operation - if q_hp[i + 1] > 0: - if t_sup_hp[i + 1] < 45: - q_boiler[i + 1] = boiler_heating_cap - elif demand[i + 1] > 0.5 * self._heating_peak_load / self.dt: - q_boiler[i + 1] = 0.5 * boiler_heating_cap - boiler_energy_consumption[i + 1] = q_boiler[i + 1] / boiler_efficiency - boiler_gas_consumption[i + 1] = (q_boiler[i + 1] * self.dt) / (boiler_efficiency * cte.NATURAL_GAS_LHV) - t_sup_boiler[i + 1] = t_sup_hp[i + 1] + (q_boiler[i + 1] / (m_ch[i + 1] * cte.WATER_HEAT_CAPACITY)) - # storage discharging - if demand[i + 1] == 0: - m_dis[i + 1] = 0 - t_ret[i + 1] = t_tank[i + 1] - else: - if demand[i + 1] > 0.5 * self._heating_peak_load: - factor = 8 - else: - factor = 4 - m_dis[i + 1] = self._heating_peak_load / (cte.WATER_HEAT_CAPACITY * factor) - t_ret[i + 1] = t_tank[i + 1] - demand[i + 1] / (m_dis[i + 1] * cte.WATER_HEAT_CAPACITY) - tes.temperature = [] - hp_electricity_j = [(x * cte.WATTS_HOUR_TO_JULES) / number_of_ts for x in hp_electricity] - boiler_consumption_j = [(x * cte.WATTS_HOUR_TO_JULES) / number_of_ts for x in boiler_energy_consumption] - hp_hourly = [] - boiler_hourly = [] - boiler_sum = 0 - hp_sum = 0 - for i in range(1, len(demand)): - hp_sum += hp_electricity_j[i] - boiler_sum += boiler_consumption_j[i] - if (i - 1) % number_of_ts == 0: - tes.temperature.append(t_tank[i]) - hp_hourly.append(hp_sum) - boiler_hourly.append(boiler_sum) - hp_sum = 0 - boiler_sum = 0 - hp.energy_consumption[cte.HEATING] = {} - hp.energy_consumption[cte.HEATING][cte.HOUR] = hp_hourly - hp.energy_consumption[cte.HEATING][cte.MONTH] = MonthlyValues.get_total_month( - hp.energy_consumption[cte.HEATING][cte.HOUR]) - hp.energy_consumption[cte.HEATING][cte.YEAR] = [ - sum(hp.energy_consumption[cte.HEATING][cte.MONTH])] - boiler.energy_consumption[cte.HEATING] = {} - boiler.energy_consumption[cte.HEATING][cte.HOUR] = boiler_hourly - boiler.energy_consumption[cte.HEATING][cte.MONTH] = MonthlyValues.get_total_month( - boiler.energy_consumption[cte.HEATING][cte.HOUR]) - boiler.energy_consumption[cte.HEATING][cte.YEAR] = [ - sum(boiler.energy_consumption[cte.HEATING][cte.MONTH])] - - self.results['Heating Demand (W)'] = demand - self.results['HP Heat Output (W)'] = q_hp - self.results['HP Source Temperature'] = t_out - self.results['HP Supply Temperature'] = t_sup_hp - self.results['HP COP'] = hp_cop - self.results['HP Electricity Consumption (W)'] = hp_electricity - self.results['Boiler Heat Output (W)'] = q_boiler - self.results['Boiler Supply Temperature'] = t_sup_boiler - self.results['Boiler Gas Consumption'] = boiler_gas_consumption - self.results['TES Temperature'] = t_tank - self.results['TES Charging Flow Rate (kg/s)'] = m_ch - self.results['TES Discharge Flow Rate (kg/s)'] = m_dis - self.results['Heating Loop Return Temperature'] = t_ret - return hp_hourly, boiler_hourly - - def cooling_system_simulation(self): - hp = self.hvac_sizing()[0] - eer_curve_coefficients = [float(coefficient) for coefficient in hp.cooling_efficiency_curve.coefficients] - cooling_efficiency = float(hp.cooling_efficiency) - number_of_ts = int(cte.HOUR_TO_SECONDS / self.dt) - demand = [0] + [x for x in self._hourly_cooling_demand for _ in range(number_of_ts)] - t_out = [0] + [x for x in self._t_out for _ in range(number_of_ts)] - hp.source_temperature = self._t_out - variable_names = ["t_sup_hp", "t_ret", "m", "q_hp", "hp_electricity", "hp_cop"] - num_hours = len(demand) - variables = {name: [0] * num_hours for name in variable_names} - (t_sup_hp, t_ret, m, q_hp, hp_electricity, hp_cop) = [variables[name] for name in variable_names] - t_ret[0] = 13 - - for i in range(1, len(demand)): - if demand[i] > 0.15 * self._cooling_peak_load: - 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: - q_hp[i] = 0.25 * hp.nominal_cooling_output - 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 - t_sup_hp[i] = t_ret[i - 1] - q_hp[i] / (m[i] * cte.WATER_HEAT_CAPACITY) - else: - q_hp[i] = 0 - t_sup_hp[i] = t_ret[i - 1] - if m[i] == 0: - t_ret[i] = t_sup_hp[i] - else: - t_ret[i] = t_sup_hp[i] + demand[i] / (m[i] * cte.WATER_HEAT_CAPACITY) - else: - m[i] = 0 - q_hp[i] = 0 - t_sup_hp[i] = t_ret[i - 1] - t_ret[i] = t_ret[i - 1] - t_sup_hp_fahrenheit = 1.8 * t_sup_hp[i] + 32 - t_out_fahrenheit = 1.8 * t_out[i] + 32 - if q_hp[i] > 0: - hp_cop[i] = (1 / (eer_curve_coefficients[0] + - eer_curve_coefficients[1] * t_sup_hp_fahrenheit + - eer_curve_coefficients[2] * t_sup_hp_fahrenheit ** 2 + - 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)) * cooling_efficiency / 3.41 - hp_electricity[i] = q_hp[i] / cooling_efficiency - else: - hp_cop[i] = 0 - hp_electricity[i] = 0 - hp_electricity_j = [(x * cte.WATTS_HOUR_TO_JULES) / number_of_ts for x in hp_electricity] - hp_hourly = [] - hp_sum = 0 - for i in range(1, len(demand)): - hp_sum += hp_electricity_j[i] - if (i - 1) % number_of_ts == 0: - hp_hourly.append(hp_sum) - hp_sum = 0 - hp.energy_consumption[cte.COOLING] = {} - hp.energy_consumption[cte.COOLING][cte.HOUR] = hp_hourly - hp.energy_consumption[cte.COOLING][cte.MONTH] = MonthlyValues.get_total_month( - hp.energy_consumption[cte.COOLING][cte.HOUR]) - hp.energy_consumption[cte.COOLING][cte.YEAR] = [ - sum(hp.energy_consumption[cte.COOLING][cte.MONTH])] - self.results['Cooling Demand (W)'] = demand - self.results['HP Cooling Output (W)'] = q_hp - self.results['HP Cooling Supply Temperature'] = t_sup_hp - self.results['HP Cooling COP'] = hp_cop - self.results['HP Electricity Consumption'] = hp_electricity - self.results['Cooling Loop Flow Rate (kg/s)'] = m - self.results['Cooling Loop Return Temperature'] = t_ret - return hp_hourly - - def dhw_system_simulation(self): - hp, tes = self.dhw_sizing() - cop_curve_coefficients = [float(coefficient) for coefficient in hp.heat_efficiency_curve.coefficients] - number_of_ts = int(cte.HOUR_TO_SECONDS / self.dt) - demand = [0] + [x for x in self._hourly_dhw_demand for _ in range(number_of_ts)] - t_out = [0] + [x for x in self._t_out for _ in range(number_of_ts)] - variable_names = ["t_sup_hp", "t_tank", "m_ch", "m_dis", "q_hp", "q_coil", "hp_cop", - "hp_electricity", "available hot water (m3)", "refill flow rate (kg/s)"] - num_hours = len(demand) - variables = {name: [0] * num_hours for name in variable_names} - (t_sup_hp, t_tank, m_ch, m_dis, m_refill, q_hp, q_coil, hp_cop, hp_electricity, v_dhw) = \ - [variables[name] for name in variable_names] - t_tank[0] = 70 - v_dhw[0] = tes.volume - - hp_heating_cap = hp.nominal_heat_output - hp_delta_t = 8 - v, h = float(tes.volume), float(tes.height) - r_tot = sum(float(layer.thickness) / float(layer.material.conductivity) for layer in - tes.layers) - u_tot = 1 / r_tot - d = math.sqrt((4 * v) / (math.pi * h)) - a_side = math.pi * d * h - a_top = math.pi * d ** 2 / 4 - ua = u_tot * (2 * a_top + a_side) - freshwater_temperature = 18 - for i in range(len(demand) - 1): - delta_t_demand = demand[i] * (self.dt / (cte.WATER_DENSITY * cte.WATER_HEAT_CAPACITY * v)) - if t_tank[i] < 62: - q_hp[i] = hp_heating_cap - delta_t_hp = q_hp[i] * (self.dt / (cte.WATER_DENSITY * cte.WATER_HEAT_CAPACITY * v)) - if demand[i] > 0: - dhw_needed = (demand[i] * cte.HOUR_TO_SECONDS) / (cte.WATER_HEAT_CAPACITY * t_tank[i] * cte.WATER_DENSITY) - m_dis[i] = dhw_needed * cte.WATER_DENSITY / cte.HOUR_TO_SECONDS - m_refill[i] = m_dis[i] - delta_t_freshwater = m_refill[i] * (t_tank[i] - freshwater_temperature) * (self.dt / (v * cte.WATER_DENSITY)) - if t_tank[i] < 60: - q_coil[i] = float(tes.heating_coil_capacity) - delta_t_coil = q_coil[i] * (self.dt / (cte.WATER_DENSITY * cte.WATER_HEAT_CAPACITY * v)) - - if q_hp[i] > 0: - m_ch[i] = q_hp[i] / (cte.WATER_HEAT_CAPACITY * hp_delta_t) - t_sup_hp[i] = (q_hp[i] / (m_ch[i] * cte.WATER_HEAT_CAPACITY)) + t_tank[i] - else: - m_ch[i] = 0 - t_sup_hp[i] = t_tank[i] - t_sup_hp_fahrenheit = 1.8 * t_sup_hp[i] + 32 - t_out_fahrenheit = 1.8 * t_out[i] + 32 - if q_hp[i] > 0: - hp_cop[i] = (cop_curve_coefficients[0] + - cop_curve_coefficients[1] * t_out[i] + - cop_curve_coefficients[2] * t_out[i] ** 2 + - cop_curve_coefficients[3] * t_tank[i] + - cop_curve_coefficients[4] * t_tank[i] ** 2 + - cop_curve_coefficients[5] * t_tank[i] * t_out[i]) * float(hp.heat_efficiency) - hp_electricity[i] = q_hp[i] / hp_cop[i] - else: - hp_cop[i] = 0 - hp_electricity[i] = 0 - - t_tank[i + 1] = t_tank[i] + (delta_t_hp - delta_t_freshwater - delta_t_demand + delta_t_coil) - tes.temperature = [] - hp_electricity_j = [(x * cte.WATTS_HOUR_TO_JULES) / number_of_ts for x in hp_electricity] - heating_coil_j = [(x * cte.WATTS_HOUR_TO_JULES) / number_of_ts for x in q_coil] - hp_hourly = [] - coil_hourly = [] - coil_sum = 0 - hp_sum = 0 - for i in range(1, len(demand)): - hp_sum += hp_electricity_j[i] - coil_sum += heating_coil_j[i] - if (i - 1) % number_of_ts == 0: - tes.temperature.append(t_tank[i]) - hp_hourly.append(hp_sum) - coil_hourly.append(coil_sum) - hp_sum = 0 - coil_sum = 0 - - hp.energy_consumption[cte.DOMESTIC_HOT_WATER] = {} - hp.energy_consumption[cte.DOMESTIC_HOT_WATER][cte.HOUR] = hp_hourly - hp.energy_consumption[cte.DOMESTIC_HOT_WATER][cte.MONTH] = MonthlyValues.get_total_month( - hp.energy_consumption[cte.DOMESTIC_HOT_WATER][cte.HOUR]) - hp.energy_consumption[cte.DOMESTIC_HOT_WATER][cte.YEAR] = [ - sum(hp.energy_consumption[cte.DOMESTIC_HOT_WATER][cte.MONTH])] - tes.heating_coil_energy_consumption = {} - tes.heating_coil_energy_consumption[cte.HOUR] = coil_hourly - tes.heating_coil_energy_consumption[cte.MONTH] = MonthlyValues.get_total_month( - tes.heating_coil_energy_consumption[cte.HOUR]) - tes.heating_coil_energy_consumption[cte.YEAR] = [ - sum(tes.heating_coil_energy_consumption[cte.MONTH])] - tes.temperature = t_tank - - self.results['DHW Demand (W)'] = demand - self.results['DHW HP Heat Output (W)'] = q_hp - self.results['DHW HP Electricity Consumption (W)'] = hp_electricity - self.results['DHW HP Source Temperature'] = t_out - self.results['DHW HP Supply Temperature'] = t_sup_hp - self.results['DHW HP COP'] = hp_cop - self.results['DHW TES Heating Coil Heat Output (W)'] = q_coil - self.results['DHW TES Temperature'] = t_tank - self.results['DHW TES Charging Flow Rate (kg/s)'] = m_ch - self.results['DHW Flow Rate (kg/s)'] = m_dis - self.results['DHW TES Refill Flow Rate (kg/s)'] = m_refill - self.results['Available Water in Tank (m3)'] = v_dhw - return hp_hourly, coil_hourly - - def enrich_buildings(self): - hp_heating, boiler_consumption = self.heating_system_simulation() - hp_cooling = self.cooling_system_simulation() - hp_dhw, heating_coil = self.dhw_system_simulation() - heating_consumption = [hp_heating[i] + boiler_consumption[i] for i in range(len(hp_heating))] - dhw_consumption = [hp_dhw[i] + heating_coil[i] for i in range(len(hp_dhw))] - self._building.heating_consumption[cte.HOUR] = heating_consumption - self._building.heating_consumption[cte.MONTH] = ( - MonthlyValues.get_total_month(self._building.heating_consumption[cte.HOUR])) - self._building.heating_consumption[cte.YEAR] = [sum(self._building.heating_consumption[cte.MONTH])] - self._building.cooling_consumption[cte.HOUR] = hp_cooling - self._building.cooling_consumption[cte.MONTH] = ( - MonthlyValues.get_total_month(self._building.cooling_consumption[cte.HOUR])) - self._building.cooling_consumption[cte.YEAR] = [sum(self._building.cooling_consumption[cte.MONTH])] - self._building.domestic_hot_water_consumption[cte.HOUR] = 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])] - if self.csv_output: - 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) - # Write header - output_file.writerow(self.results.keys()) - # Write data - output_file.writerows(zip(*self.results.values())) diff --git a/scripts/system_simulation_models/archetype13_stratified_tes.py b/scripts/system_simulation_models/archetype13_stratified_tes.py deleted file mode 100644 index 632ed304..00000000 --- a/scripts/system_simulation_models/archetype13_stratified_tes.py +++ /dev/null @@ -1,416 +0,0 @@ -import math -import hub.helpers.constants as cte -import csv -from hub.helpers.monthly_values import MonthlyValues -import numpy as np - - -class Archetype13Stratified: - def __init__(self, building, output_path): - self._building = building - self._name = building.name - self._pv_system = building.energy_systems[0] - self._hvac_system = building.energy_systems[1] - self._dhw_system = building.energy_systems[-1] - self._dhw_peak_flow_rate = (building.thermal_zones_from_internal_zones[0].total_floor_area * - building.thermal_zones_from_internal_zones[0].domestic_hot_water.peak_flow * - cte.WATER_DENSITY) - 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 / 3600 for demand in building.heating_demand[cte.HOUR]] - self._hourly_cooling_demand = [demand / 3600 for demand in building.cooling_demand[cte.HOUR]] - self._hourly_dhw_demand = [0] + building.domestic_hot_water_heat_demand[cte.HOUR] - self._output_path = output_path - self._t_out = building.external_temperature[cte.HOUR] - self.results = {} - self.dt = 300 - - def hvac_sizing(self): - storage_factor = 3 - heat_pump = self._hvac_system.generation_systems[1] - boiler = self._hvac_system.generation_systems[0] - thermal_storage = boiler.energy_storage_systems[0] - heat_pump.nominal_heat_output = round(0.5 * self._heating_peak_load / 3600) - heat_pump.nominal_cooling_output = round(self._cooling_peak_load / 3600) - boiler.nominal_heat_output = round(0.5 * self._heating_peak_load / 3600) - thermal_storage.volume = round( - (self._heating_peak_load * storage_factor) / (cte.WATER_HEAT_CAPACITY * cte.WATER_DENSITY * 25)) - return heat_pump, boiler, thermal_storage - - def dhw_sizing(self): - storage_factor = 3 - dhw_hp = self._dhw_system.generation_systems[0] - dhw_hp.nominal_heat_output = 0.7 * self._domestic_hot_water_peak_load - dhw_hp.source_temperature = self._t_out - dhw_tes = dhw_hp.energy_storage_systems[0] - dhw_tes.volume = round( - (self._domestic_hot_water_peak_load * storage_factor * 3600) / (cte.WATER_HEAT_CAPACITY * cte.WATER_DENSITY * 10)) - return dhw_hp, dhw_tes - - def heating_system_simulation_stratified(self): - hp, boiler, tes = self.hvac_sizing() - hp_efficiency = float(hp.heat_efficiency) - cop_curve_coefficients = [float(coefficient) for coefficient in hp.heat_efficiency_curve.coefficients] - demand = [0] + [x for x in self._hourly_heating_demand for _ in range(12)] - hp.source_temperature = self._t_out - t_out = [0] + [x for x in self._t_out for _ in range(12)] - variable_names = ["t_sup_hp", "t1", "t2", "t3", "t4", "t_tank", "t_ret", "m_ch", "m_dis", "q_hp", "q_boiler", - "hp_cop", "hp_electricity", "boiler_gas_consumption", "t_sup_boiler", "boiler_energy_consumption", - "heating_consumption"] - num_hours = len(demand) - variables = {name: [0] * num_hours for name in variable_names} - (t_sup_hp, t1, t2, t3, t4, t_tank, t_ret, m_ch, m_dis, q_hp, q_boiler, hp_cop, - hp_electricity, boiler_gas_consumption, t_sup_boiler, boiler_energy_consumption, heating_consumption) = \ - [variables[name] for name in variable_names] - t_tank[0] = 55 - t1[0] = 55 - t2[0] = 55 - t3[0] = 55 - t4[0] = 55 - dt = 300 - hp_heating_cap = hp.nominal_heat_output - boiler_heating_cap = boiler.nominal_heat_output - hp_delta_t = 5 - boiler_efficiency = float(boiler.heat_efficiency) - v, h = float(tes.volume) / 4, float(tes.height) / 4 - r_tot = sum(float(layer.thickness) / float(layer.material.conductivity) for layer in - tes.layers) - u_tot = 1 / r_tot - d = math.sqrt((4 * v) / (math.pi * h)) - a_side = math.pi * d * h - a_top = math.pi * d ** 2 / 4 - ua_side = u_tot * a_side - ua_top_bottom = u_tot * (a_top + a_side) - # storage temperature prediction - for i in range(len(demand) - 1): - t1[i + 1] = t1[i] + ((m_ch[i] * (t_sup_boiler[i] - t1[i])) + ( - np.heaviside((m_dis[i] - m_ch[i]), 0) * (m_ch[i] - m_dis[i]) * (t1[i] - t2[i])) + ( - ua_top_bottom * (t_out[i] - t1[i])) / cte.WATER_HEAT_CAPACITY - cte.WATER_THERMAL_CONDUCTIVITY * (a_top * (t1[i] - t2[i])) / ( - cte.WATER_HEAT_CAPACITY * h)) * (dt / (cte.WATER_DENSITY * v)) - t2[i + 1] = t2[i] + ((np.heaviside((m_dis[i] - m_ch[i]), 0) * (m_ch[i] - m_dis[i]) * (t2[i] - t3[i])) + ( - ua_side * (t_out[i] - t2[i])) / cte.WATER_HEAT_CAPACITY - (cte.WATER_THERMAL_CONDUCTIVITY * (a_top * (t2[i] - t1[i])) / (cte.WATER_HEAT_CAPACITY * h)) - ( - cte.WATER_THERMAL_CONDUCTIVITY * (a_top * (t2[i] - t3[i])) / (cte.WATER_HEAT_CAPACITY * h)) + ( - np.heaviside((m_ch[i] - m_dis[i]), 0) * (m_ch[i] - m_dis[i]) * ( - t1[i] - t2[i]))) * (dt / (cte.WATER_DENSITY * v)) - t3[i + 1] = t3[i] + ((np.heaviside((m_dis[i] - m_ch[i]), 0) * (m_ch[i] - m_dis[i]) * (t3[i] - t4[i])) + ( - ua_side * (t_out[i] - t3[i])) / cte.WATER_HEAT_CAPACITY - (cte.WATER_THERMAL_CONDUCTIVITY * (a_top * (t3[i] - t2[i])) / (cte.WATER_HEAT_CAPACITY * h)) - ( - cte.WATER_THERMAL_CONDUCTIVITY * (a_top * (t3[i] - t4[i])) / (cte.WATER_HEAT_CAPACITY * h)) + ( - np.heaviside((m_ch[i] - m_dis[i]), 0) * (m_ch[i] - m_dis[i]) * ( - t2[i] - t3[i]))) * (dt / (cte.WATER_DENSITY * v)) - t4[i + 1] = t4[i] + (np.heaviside((m_ch[i] - m_dis[i]), 0) * ((m_ch[i] - m_dis[i]) * (t3[i] - t4[i])) + ( - ua_top_bottom * (t_out[i] - t4[-1])) / cte.WATER_HEAT_CAPACITY - m_dis[i] * ((t4[i] - t_ret[i])) - ( - cte.WATER_THERMAL_CONDUCTIVITY * (a_top * (t4[i] - t3[i])) / (cte.WATER_HEAT_CAPACITY * h))) * (dt / (cte.WATER_DENSITY * v)) - # hp operation - if t1[i + 1] < 40: - q_hp[i + 1] = hp_heating_cap - m_ch[i + 1] = q_hp[i + 1] / (cte.WATER_HEAT_CAPACITY * hp_delta_t) - t_sup_hp[i + 1] = (q_hp[i + 1] / (m_ch[i + 1] * cte.WATER_HEAT_CAPACITY)) + t4[i + 1] - elif 40 <= t1[i + 1] < 55 and q_hp[i] == 0: - q_hp[i + 1] = 0 - m_ch[i + 1] = 0 - t_sup_hp[i + 1] = t4[i + 1] - elif 40 <= t1[i + 1] < 55 and q_hp[i] > 0: - q_hp[i + 1] = hp_heating_cap - m_ch[i + 1] = q_hp[i + 1] / (cte.WATER_HEAT_CAPACITY * hp_delta_t) - t_sup_hp[i + 1] = (q_hp[i + 1] / (m_ch[i + 1] * cte.WATER_HEAT_CAPACITY)) + t4[i + 1] - else: - q_hp[i + 1], m_ch[i + 1], t_sup_hp[i + 1] = 0, 0, t4[i + 1] - t_tank_fahrenheit = 1.8 * t4[i + 1] + 32 - t_out_fahrenheit = 1.8 * t_out[i + 1] + 32 - if q_hp[i + 1] > 0: - hp_cop[i + 1] = (1 / (cop_curve_coefficients[0] + - cop_curve_coefficients[1] * t_tank_fahrenheit + - cop_curve_coefficients[2] * t_tank_fahrenheit ** 2 + - cop_curve_coefficients[3] * t_out_fahrenheit + - cop_curve_coefficients[4] * t_out_fahrenheit ** 2 + - cop_curve_coefficients[5] * t_tank_fahrenheit * t_out_fahrenheit)) * hp_efficiency - hp_electricity[i + 1] = q_hp[i + 1] / hp_cop[i + 1] - else: - hp_cop[i + 1] = 0 - hp_electricity[i + 1] = 0 - # boiler operation - if q_hp[i + 1] > 0: - if t_sup_hp[i + 1] < 45: - q_boiler[i + 1] = boiler_heating_cap - elif demand[i + 1] > 0.5 * self._heating_peak_load / dt: - q_boiler[i + 1] = 0.5 * boiler_heating_cap - boiler_energy_consumption[i + 1] = q_boiler[i + 1] / boiler_efficiency - boiler_gas_consumption[i + 1] = (q_boiler[i + 1] * dt) / (boiler_efficiency * cte.NATURAL_GAS_LHV) - t_sup_boiler[i + 1] = t_sup_hp[i + 1] + (q_boiler[i + 1] / (m_ch[i + 1] * cte.WATER_HEAT_CAPACITY)) - # storage discharging - if demand[i + 1] == 0: - m_dis[i + 1] = 0 - t_ret[i + 1] = t1[i + 1] - else: - if demand[i + 1] > 0.5 * self._heating_peak_load / cte.HOUR_TO_SECONDS: - factor = 8 - else: - factor = 4 - m_dis[i + 1] = self._heating_peak_load / (cte.WATER_HEAT_CAPACITY * factor * cte.HOUR_TO_SECONDS) - t_ret[i + 1] = t1[i + 1] - demand[i + 1] / (m_dis[i + 1] * cte.WATER_HEAT_CAPACITY) - - hp_electricity_wh = [x / 12 for x in hp_electricity] - boiler_consumption_wh = [x / 12 for x in boiler_energy_consumption] - hp_hourly = [] - boiler_hourly = [] - tes.temperature = {} - tes.temperature['layer_1'] = [] - tes.temperature['layer_2'] = [] - tes.temperature['layer_3'] = [] - tes.temperature['layer_4'] = [] - for i in range(1, len(demand), 12): - tes.temperature['layer_1'].append(t1[i]) - tes.temperature['layer_2'].append(t2[i]) - tes.temperature['layer_3'].append(t3[i]) - tes.temperature['layer_4'].append(t4[i]) - demand_modified = demand[1:] - hp_hourly.append(hp_electricity[1]) - boiler_hourly.append(boiler_energy_consumption[1]) - boiler_sum = 0 - hp_sum = 0 - for i in range(1, len(demand_modified) + 1): - hp_sum += hp_electricity_wh[i] - boiler_sum += boiler_consumption_wh[i] - if i % 12 == 0: - hp_hourly.append(hp_sum) - boiler_hourly.append(boiler_sum) - hp_sum = 0 - boiler_sum = 0 - - hp.energy_consumption[cte.HEATING] = {} - hp.energy_consumption[cte.HEATING][cte.HOUR] = hp_hourly - hp.energy_consumption[cte.HEATING][cte.MONTH] = MonthlyValues.get_total_month( - hp.energy_consumption[cte.HEATING][cte.HOUR]) - hp.energy_consumption[cte.HEATING][cte.YEAR] = [ - sum(hp.energy_consumption[cte.HEATING][cte.MONTH])] - boiler.energy_consumption[cte.HEATING] = {} - boiler.energy_consumption[cte.HEATING][cte.HOUR] = boiler_hourly - boiler.energy_consumption[cte.HEATING][cte.MONTH] = MonthlyValues.get_total_month( - boiler.energy_consumption[cte.HEATING][cte.HOUR]) - boiler.energy_consumption[cte.HEATING][cte.YEAR] = [ - sum(boiler.energy_consumption[cte.HEATING][cte.MONTH])] - - self.results['Heating Demand (W)'] = demand - self.results['HP Heat Output (W)'] = q_hp - self.results['HP Source Temperature'] = t_out - self.results['HP Supply Temperature'] = t_sup_hp - self.results['HP COP'] = hp_cop - self.results['HP Electricity Consumption (W)'] = hp_electricity - self.results['Boiler Heat Output (W)'] = q_boiler - self.results['Boiler Supply Temperature'] = t_sup_boiler - self.results['Boiler Gas Consumption'] = boiler_gas_consumption - self.results['TES Layer 1 Temperature'] = t1 - self.results['TES Layer 2 Temperature'] = t2 - self.results['TES Layer 3 Temperature'] = t3 - self.results['TES Layer 4 Temperature'] = t4 - self.results['TES Charging Flow Rate (kg/s)'] = m_ch - self.results['TES Discharge Flow Rate (kg/s)'] = m_dis - self.results['Heating Loop Return Temperature'] = t_ret - return hp_electricity, boiler_energy_consumption - - def cooling_system_simulation(self): - hp = self.hvac_sizing()[0] - eer_curve_coefficients = [float(coefficient) for coefficient in hp.cooling_efficiency_curve.coefficients] - cooling_efficiency = float(hp.cooling_efficiency) - number_of_ts = int(cte.HOUR_TO_SECONDS / self.dt) - demand = [0] + [x for x in self._hourly_cooling_demand for _ in range(number_of_ts)] - t_out = [0] + [x for x in self._t_out for _ in range(number_of_ts)] - hp.source_temperature = self._t_out - variable_names = ["t_sup_hp", "t_ret", "m", "q_hp", "hp_electricity", "hp_cop"] - num_hours = len(demand) - variables = {name: [0] * num_hours for name in variable_names} - (t_sup_hp, t_ret, m, q_hp, hp_electricity, hp_cop) = [variables[name] for name in variable_names] - t_ret[0] = 13 - - for i in range(1, len(demand)): - if demand[i] > 0.15 * self._cooling_peak_load: - 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: - q_hp[i] = 0.25 * hp.nominal_cooling_output - 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 - t_sup_hp[i] = t_ret[i - 1] - q_hp[i] / (m[i] * cte.WATER_HEAT_CAPACITY) - else: - q_hp[i] = 0 - t_sup_hp[i] = t_ret[i - 1] - if m[i] == 0: - t_ret[i] = t_sup_hp[i] - else: - t_ret[i] = t_sup_hp[i] + demand[i] / (m[i] * cte.WATER_HEAT_CAPACITY) - else: - m[i] = 0 - q_hp[i] = 0 - t_sup_hp[i] = t_ret[i - 1] - t_ret[i] = t_ret[i - 1] - t_sup_hp_fahrenheit = 1.8 * t_sup_hp[i] + 32 - t_out_fahrenheit = 1.8 * t_out[i] + 32 - if q_hp[i] > 0: - hp_cop[i] = (1 / (eer_curve_coefficients[0] + - eer_curve_coefficients[1] * t_sup_hp_fahrenheit + - eer_curve_coefficients[2] * t_sup_hp_fahrenheit ** 2 + - 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)) * cooling_efficiency / 3.41 - hp_electricity[i] = q_hp[i] / cooling_efficiency - else: - hp_cop[i] = 0 - hp_electricity[i] = 0 - hp_electricity_j = [(x * cte.WATTS_HOUR_TO_JULES) / number_of_ts for x in hp_electricity] - hp_hourly = [] - hp_sum = 0 - for i in range(1, len(demand)): - hp_sum += hp_electricity_j[i] - if (i - 1) % number_of_ts == 0: - hp_hourly.append(hp_sum) - hp_sum = 0 - hp.energy_consumption[cte.COOLING] = {} - hp.energy_consumption[cte.COOLING][cte.HOUR] = hp_hourly - hp.energy_consumption[cte.COOLING][cte.MONTH] = MonthlyValues.get_total_month( - hp.energy_consumption[cte.COOLING][cte.HOUR]) - hp.energy_consumption[cte.COOLING][cte.YEAR] = [ - sum(hp.energy_consumption[cte.COOLING][cte.MONTH])] - self.results['Cooling Demand (W)'] = demand - self.results['HP Cooling Output (W)'] = q_hp - self.results['HP Cooling Supply Temperature'] = t_sup_hp - self.results['HP Cooling COP'] = hp_cop - self.results['HP Electricity Consumption'] = hp_electricity - self.results['Cooling Loop Flow Rate (kg/s)'] = m - self.results['Cooling Loop Return Temperature'] = t_ret - return hp_hourly - - def dhw_system_simulation(self): - hp, tes = self.dhw_sizing() - cop_curve_coefficients = [float(coefficient) for coefficient in hp.heat_efficiency_curve.coefficients] - number_of_ts = int(cte.HOUR_TO_SECONDS / self.dt) - demand = [0] + [x for x in self._hourly_dhw_demand for _ in range(number_of_ts)] - t_out = [0] + [x for x in self._t_out for _ in range(number_of_ts)] - variable_names = ["t_sup_hp", "t_tank", "m_ch", "m_dis", "q_hp", "q_coil", "hp_cop", - "hp_electricity", "available hot water (m3)", "refill flow rate (kg/s)"] - num_hours = len(demand) - variables = {name: [0] * num_hours for name in variable_names} - (t_sup_hp, t_tank, m_ch, m_dis, m_refill, q_hp, q_coil, hp_cop, hp_electricity, v_dhw) = \ - [variables[name] for name in variable_names] - t_tank[0] = 70 - v_dhw[0] = tes.volume - - hp_heating_cap = hp.nominal_heat_output - hp_delta_t = 8 - v, h = float(tes.volume), float(tes.height) - r_tot = sum(float(layer.thickness) / float(layer.material.conductivity) for layer in - tes.layers) - u_tot = 1 / r_tot - d = math.sqrt((4 * v) / (math.pi * h)) - a_side = math.pi * d * h - a_top = math.pi * d ** 2 / 4 - ua = u_tot * (2 * a_top + a_side) - freshwater_temperature = 18 - for i in range(len(demand) - 1): - delta_t_demand = demand[i] * (self.dt / (cte.WATER_DENSITY * cte.WATER_HEAT_CAPACITY * v)) - if t_tank[i] < 62: - q_hp[i] = hp_heating_cap - delta_t_hp = q_hp[i] * (self.dt / (cte.WATER_DENSITY * cte.WATER_HEAT_CAPACITY * v)) - if demand[i] > 0: - dhw_needed = (demand[i] * cte.HOUR_TO_SECONDS) / (cte.WATER_HEAT_CAPACITY * t_tank[i] * cte.WATER_DENSITY) - m_dis[i] = dhw_needed * cte.WATER_DENSITY / cte.HOUR_TO_SECONDS - m_refill[i] = m_dis[i] - delta_t_freshwater = m_refill[i] * (t_tank[i] - freshwater_temperature) * (self.dt / (v * cte.WATER_DENSITY)) - if t_tank[i] < 60: - q_coil[i] = float(tes.heating_coil_capacity) - delta_t_coil = q_coil[i] * (self.dt / (cte.WATER_DENSITY * cte.WATER_HEAT_CAPACITY * v)) - - if q_hp[i] > 0: - m_ch[i] = q_hp[i] / (cte.WATER_HEAT_CAPACITY * hp_delta_t) - t_sup_hp[i] = (q_hp[i] / (m_ch[i] * cte.WATER_HEAT_CAPACITY)) + t_tank[i] - else: - m_ch[i] = 0 - t_sup_hp[i] = t_tank[i] - t_sup_hp_fahrenheit = 1.8 * t_sup_hp[i] + 32 - t_out_fahrenheit = 1.8 * t_out[i] + 32 - if q_hp[i] > 0: - hp_cop[i] = (cop_curve_coefficients[0] + - cop_curve_coefficients[1] * t_out[i] + - cop_curve_coefficients[2] * t_out[i] ** 2 + - cop_curve_coefficients[3] * t_tank[i] + - cop_curve_coefficients[4] * t_tank[i] ** 2 + - cop_curve_coefficients[5] * t_tank[i] * t_out[i]) * float(hp.heat_efficiency) - hp_electricity[i] = q_hp[i] / hp_cop[i] - else: - hp_cop[i] = 0 - hp_electricity[i] = 0 - - t_tank[i + 1] = t_tank[i] + (delta_t_hp - delta_t_freshwater - delta_t_demand + delta_t_coil) - tes.temperature = [] - hp_electricity_j = [(x * cte.WATTS_HOUR_TO_JULES) / number_of_ts for x in hp_electricity] - heating_coil_j = [(x * cte.WATTS_HOUR_TO_JULES) / number_of_ts for x in q_coil] - hp_hourly = [] - coil_hourly = [] - coil_sum = 0 - hp_sum = 0 - for i in range(1, len(demand)): - hp_sum += hp_electricity_j[i] - coil_sum += heating_coil_j[i] - if (i - 1) % number_of_ts == 0: - tes.temperature.append(t_tank[i]) - hp_hourly.append(hp_sum) - coil_hourly.append(coil_sum) - hp_sum = 0 - coil_sum = 0 - - hp.energy_consumption[cte.DOMESTIC_HOT_WATER] = {} - hp.energy_consumption[cte.DOMESTIC_HOT_WATER][cte.HOUR] = hp_hourly - hp.energy_consumption[cte.DOMESTIC_HOT_WATER][cte.MONTH] = MonthlyValues.get_total_month( - hp.energy_consumption[cte.DOMESTIC_HOT_WATER][cte.HOUR]) - hp.energy_consumption[cte.DOMESTIC_HOT_WATER][cte.YEAR] = [ - sum(hp.energy_consumption[cte.DOMESTIC_HOT_WATER][cte.MONTH])] - tes.heating_coil_energy_consumption = {} - tes.heating_coil_energy_consumption[cte.HOUR] = coil_hourly - tes.heating_coil_energy_consumption[cte.MONTH] = MonthlyValues.get_total_month( - tes.heating_coil_energy_consumption[cte.HOUR]) - tes.heating_coil_energy_consumption[cte.YEAR] = [ - sum(tes.heating_coil_energy_consumption[cte.MONTH])] - tes.temperature = t_tank - - self.results['DHW Demand (W)'] = demand - self.results['DHW HP Heat Output (W)'] = q_hp - self.results['DHW HP Electricity Consumption (W)'] = hp_electricity - self.results['DHW HP Source Temperature'] = t_out - self.results['DHW HP Supply Temperature'] = t_sup_hp - self.results['DHW HP COP'] = hp_cop - self.results['DHW TES Heating Coil Heat Output (W)'] = q_coil - self.results['DHW TES Temperature'] = t_tank - self.results['DHW TES Charging Flow Rate (kg/s)'] = m_ch - self.results['DHW Flow Rate (kg/s)'] = m_dis - self.results['DHW TES Refill Flow Rate (kg/s)'] = m_refill - self.results['Available Water in Tank (m3)'] = v_dhw - return hp_hourly, coil_hourly - - def enrich_buildings(self): - hp_heating, boiler_consumption = self.heating_system_simulation_stratified() - hp_cooling = self.cooling_system_simulation() - hp_dhw, heating_coil = self.dhw_system_simulation() - heating_consumption = [hp_heating[i] + boiler_consumption[i] for i in range(len(hp_heating))] - dhw_consumption = [hp_dhw[i] + heating_coil[i] for i in range(len(hp_dhw))] - self._building.heating_consumption[cte.HOUR] = heating_consumption - self._building.heating_consumption[cte.MONTH] = ( - MonthlyValues.get_total_month(self._building.heating_consumption[cte.HOUR])) - self._building.heating_consumption[cte.YEAR] = sum(self._building.heating_consumption[cte.MONTH]) - self._building.cooling_consumption[cte.HOUR] = hp_cooling - self._building.cooling_consumption[cte.MONTH] = ( - MonthlyValues.get_total_month(self._building.cooling_consumption[cte.HOUR])) - self._building.cooling_consumption[cte.YEAR] = sum(self._building.cooling_consumption[cte.MONTH]) - self._building.domestic_hot_water_consumption[cte.HOUR] = 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])) - 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) - # Write header - output_file.writerow(self.results.keys()) - # Write data - output_file.writerows(zip(*self.results.values())) diff --git a/scripts/system_simulation_models/archetypes14_15.py b/scripts/system_simulation_models/archetypes14_15.py deleted file mode 100644 index cc5f8d6a..00000000 --- a/scripts/system_simulation_models/archetypes14_15.py +++ /dev/null @@ -1,398 +0,0 @@ -import math -import hub.helpers.constants as cte -import csv -from hub.helpers.monthly_values import MonthlyValues - - -class Archetype14_15: - def __init__(self, building, output_path): - self._building = building - self._name = building.name - if 'PV' in building.energy_systems_archetype_name: - i = 1 - self._pv_system = building.energy_systems[0] - else: - i = 0 - self._dhw_system = building.energy_systems[i] - self._heating_system = building.energy_systems[i + 1] - self._cooling_system = building.energy_systems[i + 2] - self._dhw_peak_flow_rate = (building.thermal_zones_from_internal_zones[0].total_floor_area * - building.thermal_zones_from_internal_zones[0].domestic_hot_water.peak_flow * - cte.WATER_DENSITY) - 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.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 - self._t_out = building.external_temperature[cte.HOUR] - self.results = {} - self.dt = 900 - - def heating_system_sizing(self): - storage_factor = 3 - heat_pump = self._heating_system.generation_systems[1] - heat_pump.source_temperature = self._t_out - boiler = self._heating_system.generation_systems[0] - thermal_storage = boiler.energy_storage_systems[0] - heat_pump.nominal_heat_output = round(0.5 * self._heating_peak_load) - boiler.nominal_heat_output = round(0.5 * self._heating_peak_load) - thermal_storage.volume = round( - (self._heating_peak_load * storage_factor * cte.WATTS_HOUR_TO_JULES) / - (cte.WATER_HEAT_CAPACITY * cte.WATER_DENSITY * 25)) - return heat_pump, boiler, thermal_storage - - def cooling_system_sizing(self): - heat_pump = self._cooling_system.generation_systems[0] - heat_pump.nominal_cooling_output = heat_pump.nominal_cooling_output = round(self._cooling_peak_load) - heat_pump.source_temperature = self._t_out - return heat_pump - - - def dhw_system_sizing(self): - storage_factor = 3 - dhw_hp = self._dhw_system.generation_systems[0] - dhw_hp.nominal_heat_output = round(0.7 * self._domestic_hot_water_peak_load) - dhw_hp.source_temperature = self._t_out - dhw_tes = dhw_hp.energy_storage_systems[0] - dhw_tes.volume = round( - (self._domestic_hot_water_peak_load * storage_factor * cte.WATTS_HOUR_TO_JULES) / - (cte.WATER_HEAT_CAPACITY * cte.WATER_DENSITY * 10)) - return dhw_hp, dhw_tes - - def heating_system_simulation(self): - hp, boiler, tes = self.heating_system_sizing() - hp_efficiency = float(hp.heat_efficiency) - cop_curve_coefficients = [float(coefficient) for coefficient in hp.heat_efficiency_curve.coefficients] - number_of_ts = int(cte.HOUR_TO_SECONDS / self.dt) - demand = [0] + [x for x in self._hourly_heating_demand for _ in range(number_of_ts)] - t_out = [0] + [x for x in self._t_out for _ in range(number_of_ts)] - hp.source_temperature = self._t_out - variable_names = ["t_sup_hp", "t_tank", "t_ret", "m_ch", "m_dis", "q_hp", "q_boiler", "hp_cop", - "hp_electricity", "boiler_gas_consumption", "t_sup_boiler", "boiler_energy_consumption", - "heating_consumption"] - num_hours = len(demand) - variables = {name: [0] * num_hours for name in variable_names} - (t_sup_hp, t_tank, t_ret, m_ch, m_dis, q_hp, q_boiler, hp_cop, - hp_electricity, boiler_gas_consumption, t_sup_boiler, boiler_energy_consumption, heating_consumption) = \ - [variables[name] for name in variable_names] - t_tank[0] = 55 - hp_heating_cap = hp.nominal_heat_output - boiler_heating_cap = boiler.nominal_heat_output - hp_delta_t = 5 - boiler_efficiency = float(boiler.heat_efficiency) - v, h = float(tes.volume), float(tes.height) - r_tot = sum(float(layer.thickness) / float(layer.material.conductivity) for layer in - tes.layers) - u_tot = 1 / r_tot - d = math.sqrt((4 * v) / (math.pi * h)) - a_side = math.pi * d * h - a_top = math.pi * d ** 2 / 4 - ua = u_tot * (2 * a_top + a_side) - # storage temperature prediction - for i in range(len(demand) - 1): - t_tank[i + 1] = (t_tank[i] + - (m_ch[i] * (t_sup_boiler[i] - t_tank[i]) + - (ua * (t_out[i] - t_tank[i])) / cte.WATER_HEAT_CAPACITY - - m_dis[i] * (t_tank[i] - t_ret[i])) * (self.dt / (cte.WATER_DENSITY * v))) - # hp operation - if t_tank[i + 1] < 40: - q_hp[i + 1] = hp_heating_cap - m_ch[i + 1] = q_hp[i + 1] / (cte.WATER_HEAT_CAPACITY * hp_delta_t) - t_sup_hp[i + 1] = (q_hp[i + 1] / (m_ch[i + 1] * cte.WATER_HEAT_CAPACITY)) + t_tank[i + 1] - elif 40 <= t_tank[i + 1] < 55 and q_hp[i] == 0: - q_hp[i + 1] = 0 - m_ch[i + 1] = 0 - t_sup_hp[i + 1] = t_tank[i + 1] - elif 40 <= t_tank[i + 1] < 55 and q_hp[i] > 0: - q_hp[i + 1] = hp_heating_cap - m_ch[i + 1] = q_hp[i + 1] / (cte.WATER_HEAT_CAPACITY * hp_delta_t) - t_sup_hp[i + 1] = (q_hp[i + 1] / (m_ch[i + 1] * cte.WATER_HEAT_CAPACITY)) + t_tank[i + 1] - else: - q_hp[i + 1], m_ch[i + 1], t_sup_hp[i + 1] = 0, 0, t_tank[i + 1] - t_tank_fahrenheit = 1.8 * t_tank[i + 1] + 32 - t_out_fahrenheit = 1.8 * t_out[i + 1] + 32 - if q_hp[i + 1] > 0: - hp_cop[i + 1] = (1 / (cop_curve_coefficients[0] + - cop_curve_coefficients[1] * t_tank_fahrenheit + - cop_curve_coefficients[2] * t_tank_fahrenheit ** 2 + - cop_curve_coefficients[3] * t_out_fahrenheit + - cop_curve_coefficients[4] * t_out_fahrenheit ** 2 + - cop_curve_coefficients[5] * t_tank_fahrenheit * t_out_fahrenheit)) * hp_efficiency - hp_electricity[i + 1] = q_hp[i + 1] / hp_cop[i + 1] - else: - hp_cop[i + 1] = 0 - hp_electricity[i + 1] = 0 - # boiler operation - if q_hp[i + 1] > 0: - if t_sup_hp[i + 1] < 45: - q_boiler[i + 1] = boiler_heating_cap - elif demand[i + 1] > 0.5 * self._heating_peak_load / self.dt: - q_boiler[i + 1] = 0.5 * boiler_heating_cap - boiler_energy_consumption[i + 1] = q_boiler[i + 1] / boiler_efficiency - boiler_gas_consumption[i + 1] = (q_boiler[i + 1] * self.dt) / (boiler_efficiency * cte.NATURAL_GAS_LHV) - t_sup_boiler[i + 1] = t_sup_hp[i + 1] + (q_boiler[i + 1] / (m_ch[i + 1] * cte.WATER_HEAT_CAPACITY)) - # storage discharging - if demand[i + 1] == 0: - m_dis[i + 1] = 0 - t_ret[i + 1] = t_tank[i + 1] - else: - if demand[i + 1] > 0.5 * self._heating_peak_load / cte.HOUR_TO_SECONDS: - factor = 8 - else: - factor = 4 - m_dis[i + 1] = self._heating_peak_load / (cte.WATER_HEAT_CAPACITY * factor * cte.HOUR_TO_SECONDS) - 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] - boiler_consumption_j = [(x * cte.WATTS_HOUR_TO_JULES) / number_of_ts for x in boiler_energy_consumption] - hp_hourly = [] - boiler_hourly = [] - boiler_sum = 0 - hp_sum = 0 - for i in range(1, len(demand)): - hp_sum += hp_electricity_j[i] - boiler_sum += boiler_consumption_j[i] - if (i - 1) % number_of_ts == 0: - tes.temperature.append(t_tank[i]) - hp_hourly.append(hp_sum) - boiler_hourly.append(boiler_sum) - hp_sum = 0 - boiler_sum = 0 - hp.energy_consumption[cte.HEATING] = {} - hp.energy_consumption[cte.HEATING][cte.HOUR] = hp_hourly - hp.energy_consumption[cte.HEATING][cte.MONTH] = MonthlyValues.get_total_month( - hp.energy_consumption[cte.HEATING][cte.HOUR]) - hp.energy_consumption[cte.HEATING][cte.YEAR] = [ - sum(hp.energy_consumption[cte.HEATING][cte.MONTH])] - boiler.energy_consumption[cte.HEATING] = {} - boiler.energy_consumption[cte.HEATING][cte.HOUR] = boiler_hourly - boiler.energy_consumption[cte.HEATING][cte.MONTH] = MonthlyValues.get_total_month( - boiler.energy_consumption[cte.HEATING][cte.HOUR]) - boiler.energy_consumption[cte.HEATING][cte.YEAR] = [ - sum(boiler.energy_consumption[cte.HEATING][cte.MONTH])] - - self.results['Heating Demand (W)'] = demand - self.results['HP Heat Output (W)'] = q_hp - self.results['HP Source Temperature'] = t_out - self.results['HP Supply Temperature'] = t_sup_hp - self.results['HP COP'] = hp_cop - self.results['HP Electricity Consumption (W)'] = hp_electricity - self.results['Boiler Heat Output (W)'] = q_boiler - self.results['Boiler Supply Temperature'] = t_sup_boiler - self.results['Boiler Gas Consumption'] = boiler_gas_consumption - self.results['TES Temperature'] = t_tank - self.results['TES Charging Flow Rate (kg/s)'] = m_ch - self.results['TES Discharge Flow Rate (kg/s)'] = m_dis - self.results['Heating Loop Return Temperature'] = t_ret - return hp_hourly, boiler_hourly - - def cooling_system_simulation(self): - hp = self.cooling_system_sizing()[0] - eer_curve_coefficients = [float(coefficient) for coefficient in hp.cooling_efficiency_curve.coefficients] - cooling_efficiency = float(hp.cooling_efficiency) - number_of_ts = int(cte.HOUR_TO_SECONDS / self.dt) - demand = [0] + [x for x in self._hourly_cooling_demand for _ in range(number_of_ts)] - t_out = [0] + [x for x in self._t_out for _ in range(number_of_ts)] - hp.source_temperature = self._t_out - variable_names = ["t_sup_hp", "t_ret", "m", "q_hp", "hp_electricity", "hp_cop"] - num_hours = len(demand) - variables = {name: [0] * num_hours for name in variable_names} - (t_sup_hp, t_ret, m, q_hp, hp_electricity, hp_cop) = [variables[name] for name in variable_names] - t_ret[0] = 13 - - for i in range(1, len(demand)): - if demand[i] > 0.15 * self._cooling_peak_load: - 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: - q_hp[i] = 0.25 * hp.nominal_cooling_output - 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 - t_sup_hp[i] = t_ret[i - 1] - q_hp[i] / (m[i] * cte.WATER_HEAT_CAPACITY) - else: - q_hp[i] = 0 - t_sup_hp[i] = t_ret[i - 1] - if m[i] == 0: - t_ret[i] = t_sup_hp[i] - else: - t_ret[i] = t_sup_hp[i] + demand[i] / (m[i] * cte.WATER_HEAT_CAPACITY) - else: - m[i] = 0 - q_hp[i] = 0 - t_sup_hp[i] = t_ret[i - 1] - t_ret[i] = t_ret[i - 1] - t_sup_hp_fahrenheit = 1.8 * t_sup_hp[i] + 32 - t_out_fahrenheit = 1.8 * t_out[i] + 32 - if q_hp[i] > 0: - hp_cop[i] = (1 / (eer_curve_coefficients[0] + - eer_curve_coefficients[1] * t_sup_hp_fahrenheit + - eer_curve_coefficients[2] * t_sup_hp_fahrenheit ** 2 + - 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)) * cooling_efficiency / 3.41 - hp_electricity[i] = q_hp[i] / cooling_efficiency - else: - hp_cop[i] = 0 - hp_electricity[i] = 0 - hp_electricity_j = [(x * cte.WATTS_HOUR_TO_JULES) / number_of_ts for x in hp_electricity] - hp_hourly = [] - hp_sum = 0 - for i in range(1, len(demand)): - hp_sum += hp_electricity_j[i] - if (i - 1) % number_of_ts == 0: - hp_hourly.append(hp_sum) - hp_sum = 0 - hp.energy_consumption[cte.COOLING] = {} - hp.energy_consumption[cte.COOLING][cte.HOUR] = hp_hourly - hp.energy_consumption[cte.COOLING][cte.MONTH] = MonthlyValues.get_total_month( - hp.energy_consumption[cte.COOLING][cte.HOUR]) - hp.energy_consumption[cte.COOLING][cte.YEAR] = [ - sum(hp.energy_consumption[cte.COOLING][cte.MONTH])] - self.results['Cooling Demand (W)'] = demand - self.results['HP Cooling Output (W)'] = q_hp - self.results['HP Cooling Supply Temperature'] = t_sup_hp - self.results['HP Cooling COP'] = hp_cop - self.results['HP Electricity Consumption'] = hp_electricity - self.results['Cooling Loop Flow Rate (kg/s)'] = m - self.results['Cooling Loop Return Temperature'] = t_ret - return hp_hourly - - def dhw_system_simulation(self): - hp, tes = self.dhw_system_sizing() - cop_curve_coefficients = [float(coefficient) for coefficient in hp.heat_efficiency_curve.coefficients] - number_of_ts = int(cte.HOUR_TO_SECONDS / self.dt) - demand = [0] + [x for x in self._hourly_dhw_demand for _ in range(number_of_ts)] - t_out = [0] + [x for x in self._t_out for _ in range(number_of_ts)] - variable_names = ["t_sup_hp", "t_tank", "m_ch", "m_dis", "q_hp", "q_coil", "hp_cop", - "hp_electricity", "available hot water (m3)", "refill flow rate (kg/s)"] - num_hours = len(demand) - variables = {name: [0] * num_hours for name in variable_names} - (t_sup_hp, t_tank, m_ch, m_dis, m_refill, q_hp, q_coil, hp_cop, hp_electricity, v_dhw) = \ - [variables[name] for name in variable_names] - t_tank[0] = 70 - v_dhw[0] = tes.volume - - hp_heating_cap = hp.nominal_heat_output - hp_delta_t = 8 - v, h = float(tes.volume), float(tes.height) - r_tot = sum(float(layer.thickness) / float(layer.material.conductivity) for layer in - tes.layers) - u_tot = 1 / r_tot - d = math.sqrt((4 * v) / (math.pi * h)) - a_side = math.pi * d * h - a_top = math.pi * d ** 2 / 4 - ua = u_tot * (2 * a_top + a_side) - freshwater_temperature = 18 - for i in range(len(demand) - 1): - delta_t_demand = demand[i] * (self.dt / (cte.WATER_DENSITY * cte.WATER_HEAT_CAPACITY * v)) - if t_tank[i] < 62: - q_hp[i] = hp_heating_cap - delta_t_hp = q_hp[i] * (self.dt / (cte.WATER_DENSITY * cte.WATER_HEAT_CAPACITY * v)) - if demand[i] > 0: - dhw_needed = (demand[i] * cte.HOUR_TO_SECONDS) / (cte.WATER_HEAT_CAPACITY * t_tank[i] * cte.WATER_DENSITY) - m_dis[i] = dhw_needed * cte.WATER_DENSITY / cte.HOUR_TO_SECONDS - m_refill[i] = m_dis[i] - delta_t_freshwater = m_refill[i] * (t_tank[i] - freshwater_temperature) * (self.dt / (v * cte.WATER_DENSITY)) - if t_tank[i] < 60: - q_coil[i] = float(tes.heating_coil_capacity) - delta_t_coil = q_coil[i] * (self.dt / (cte.WATER_DENSITY * cte.WATER_HEAT_CAPACITY * v)) - - if q_hp[i] > 0: - m_ch[i] = q_hp[i] / (cte.WATER_HEAT_CAPACITY * hp_delta_t) - t_sup_hp[i] = (q_hp[i] / (m_ch[i] * cte.WATER_HEAT_CAPACITY)) + t_tank[i] - else: - m_ch[i] = 0 - t_sup_hp[i] = t_tank[i] - t_sup_hp_fahrenheit = 1.8 * t_sup_hp[i] + 32 - t_out_fahrenheit = 1.8 * t_out[i] + 32 - if q_hp[i] > 0: - hp_cop[i] = (cop_curve_coefficients[0] + - cop_curve_coefficients[1] * t_out[i] + - cop_curve_coefficients[2] * t_out[i] ** 2 + - cop_curve_coefficients[3] * t_tank[i] + - cop_curve_coefficients[4] * t_tank[i] ** 2 + - cop_curve_coefficients[5] * t_tank[i] * t_out[i]) * float(hp.heat_efficiency) - hp_electricity[i] = q_hp[i] / hp_cop[i] - else: - hp_cop[i] = 0 - hp_electricity[i] = 0 - - t_tank[i + 1] = t_tank[i] + (delta_t_hp - delta_t_freshwater - delta_t_demand + delta_t_coil) - tes.temperature = [] - hp_electricity_j = [(x * cte.WATTS_HOUR_TO_JULES) / number_of_ts for x in hp_electricity] - heating_coil_j = [(x * cte.WATTS_HOUR_TO_JULES) / number_of_ts for x in q_coil] - hp_hourly = [] - coil_hourly = [] - coil_sum = 0 - hp_sum = 0 - for i in range(1, len(demand)): - hp_sum += hp_electricity_j[i] - coil_sum += heating_coil_j[i] - if (i - 1) % number_of_ts == 0: - tes.temperature.append(t_tank[i]) - hp_hourly.append(hp_sum) - coil_hourly.append(coil_sum) - hp_sum = 0 - coil_sum = 0 - - hp.energy_consumption[cte.DOMESTIC_HOT_WATER] = {} - hp.energy_consumption[cte.DOMESTIC_HOT_WATER][cte.HOUR] = hp_hourly - hp.energy_consumption[cte.DOMESTIC_HOT_WATER][cte.MONTH] = MonthlyValues.get_total_month( - hp.energy_consumption[cte.DOMESTIC_HOT_WATER][cte.HOUR]) - hp.energy_consumption[cte.DOMESTIC_HOT_WATER][cte.YEAR] = [ - sum(hp.energy_consumption[cte.DOMESTIC_HOT_WATER][cte.MONTH])] - tes.heating_coil_energy_consumption = {} - tes.heating_coil_energy_consumption[cte.HOUR] = coil_hourly - tes.heating_coil_energy_consumption[cte.MONTH] = MonthlyValues.get_total_month( - tes.heating_coil_energy_consumption[cte.HOUR]) - tes.heating_coil_energy_consumption[cte.YEAR] = [ - sum(tes.heating_coil_energy_consumption[cte.MONTH])] - tes.temperature = t_tank - - self.results['DHW Demand (W)'] = demand - self.results['DHW HP Heat Output (W)'] = q_hp - self.results['DHW HP Electricity Consumption (W)'] = hp_electricity - self.results['DHW HP Source Temperature'] = t_out - self.results['DHW HP Supply Temperature'] = t_sup_hp - self.results['DHW HP COP'] = hp_cop - self.results['DHW TES Heating Coil Heat Output (W)'] = q_coil - self.results['DHW TES Temperature'] = t_tank - self.results['DHW TES Charging Flow Rate (kg/s)'] = m_ch - self.results['DHW Flow Rate (kg/s)'] = m_dis - self.results['DHW TES Refill Flow Rate (kg/s)'] = m_refill - self.results['Available Water in Tank (m3)'] = v_dhw - return hp_hourly, coil_hourly - - - - def enrich_buildings(self): - hp_heating, boiler_consumption = self.heating_system_simulation() - hp_cooling = self.cooling_system_simulation() - hp_dhw, heating_coil = self.dhw_system_simulation() - heating_consumption = [hp_heating[i] + boiler_consumption[i] for i in range(len(hp_heating))] - dhw_consumption = [hp_dhw[i] + heating_coil[i] for i in range(len(hp_dhw))] - self._building.heating_consumption[cte.HOUR] = heating_consumption - self._building.heating_consumption[cte.MONTH] = ( - MonthlyValues.get_total_month(self._building.heating_consumption[cte.HOUR])) - self._building.heating_consumption[cte.YEAR] = [sum(self._building.heating_consumption[cte.MONTH])] - self._building.cooling_consumption[cte.HOUR] = hp_cooling - self._building.cooling_consumption[cte.MONTH] = ( - MonthlyValues.get_total_month(self._building.cooling_consumption[cte.HOUR])) - self._building.cooling_consumption[cte.YEAR] = [sum(self._building.cooling_consumption[cte.MONTH])] - self._building.domestic_hot_water_consumption[cte.HOUR] = 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])) - 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) - # Write header - output_file.writerow(self.results.keys()) - # Write data - output_file.writerows(zip(*self.results.values())) diff --git a/tests/test_systems_catalog.py b/tests/test_systems_catalog.py index 612a8fe6..68401719 100644 --- a/tests/test_systems_catalog.py +++ b/tests/test_systems_catalog.py @@ -39,9 +39,9 @@ class TestSystemsCatalog(TestCase): catalog_categories = catalog.names() archetypes = catalog.names() - self.assertEqual(15, len(archetypes['archetypes'])) + self.assertEqual(13, len(archetypes['archetypes'])) systems = catalog.names('systems') - self.assertEqual(12, len(systems['systems'])) + self.assertEqual(17, len(systems['systems'])) generation_equipments = catalog.names('generation_equipments') self.assertEqual(27, len(generation_equipments['generation_equipments'])) with self.assertRaises(ValueError): diff --git a/tests/test_systems_factory.py b/tests/test_systems_factory.py index 442e5be5..2e54171d 100644 --- a/tests/test_systems_factory.py +++ b/tests/test_systems_factory.py @@ -114,7 +114,8 @@ class TestSystemsFactory(TestCase): ResultFactory('insel_monthly_energy_balance', self._city, self._output_path).enrich() for building in self._city.buildings: - building.energy_systems_archetype_name = 'PV+4Pipe+DHW' + building.energy_systems_archetype_name = ('Central 4 Pipes Air to Water Heat Pump and Gas Boiler with ' + 'Independent Water Heating and PV') EnergySystemsFactory('montreal_future', self._city).enrich() # Need to assign energy systems to buildings: for building in self._city.buildings: @@ -131,5 +132,4 @@ class TestSystemsFactory(TestCase): self.assertLess(0, building.heating_consumption[cte.YEAR][0]) self.assertLess(0, building.cooling_consumption[cte.YEAR][0]) self.assertLess(0, building.domestic_hot_water_consumption[cte.YEAR][0]) - self.assertLess(0, building.onsite_electrical_production[cte.YEAR][0]) - print('test') \ No newline at end of file + self.assertLess(0, building.onsite_electrical_production[cte.YEAR][0]) \ No newline at end of file