diff --git a/building_modelling/ep_workflow.py b/building_modelling/ep_workflow.py deleted file mode 100644 index 455cce98..00000000 --- a/building_modelling/ep_workflow.py +++ /dev/null @@ -1,45 +0,0 @@ -import glob -import os -import sys -from pathlib import Path -import csv -from hub.exports.energy_building_exports_factory import EnergyBuildingsExportsFactory -from hub.imports.results_factory import ResultFactory - -sys.path.append('../energy_system_modelling_package/') - - -def energy_plus_workflow(city, output_path): - try: - # city = city - out_path = output_path - files = glob.glob(f'{out_path}/*') - - # for file in files: - # if file != '.gitignore': - # os.remove(file) - 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/building_modelling/geojson_creator.py b/building_modelling/geojson_creator.py deleted file mode 100644 index c96c340d..00000000 --- a/building_modelling/geojson_creator.py +++ /dev/null @@ -1,37 +0,0 @@ -import json -from shapely import Polygon -from shapely import Point -from pathlib import Path - - -def process_geojson(x, y, diff, expansion=False): - selection_box = Polygon([[x + diff, y - diff], - [x - diff, y - diff], - [x - diff, y + diff], - [x + diff, y + diff]]) - geojson_file = Path('./data/collinear_clean 2.geojson').resolve() - if not expansion: - output_file = Path('./input_files/output_buildings.geojson').resolve() - else: - output_file = Path('./input_files/output_buildings_expanded.geojson').resolve() - buildings_in_region = [] - - with open(geojson_file, 'r') as file: - city = json.load(file) - buildings = city['features'] - - for building in buildings: - coordinates = building['geometry']['coordinates'][0] - building_polygon = Polygon(coordinates) - centroid = Point(building_polygon.centroid) - - if centroid.within(selection_box): - buildings_in_region.append(building) - - output_region = {"type": "FeatureCollection", - "features": buildings_in_region} - - with open(output_file, 'w') as file: - file.write(json.dumps(output_region, indent=2)) - - return output_file diff --git a/costing_package/capital_costs.py b/costing_package/capital_costs.py deleted file mode 100644 index 951e92c1..00000000 --- a/costing_package/capital_costs.py +++ /dev/null @@ -1,417 +0,0 @@ -""" -Capital costs module -SPDX - License - Identifier: LGPL - 3.0 - or -later -Copyright © 2023 Project Coder Guille Gutierrez guillermo.gutierrezmorote@concordia.ca -Code contributor Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca -Code contributor Oriol Gavalda Torrellas oriol.gavalda@concordia.ca -""" -import math - -import pandas as pd -import numpy_financial as npf -from hub.city_model_structure.building import Building -import hub.helpers.constants as cte -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): - """ - Capital costs class - """ - - def __init__(self, building: Building, configuration: Configuration): - super().__init__(building, configuration) - self._yearly_capital_costs = pd.DataFrame( - index=self._rng, - columns=[ - 'B2010_opaque_walls', - 'B2020_transparent', - 'B3010_opaque_roof', - 'B1010_superstructure', - 'D2010_photovoltaic_system', - 'D3020_simultaneous_heat_and_cooling_generating_systems', - 'D3030_heating_systems', - 'D3040_cooling_systems', - 'D3050_distribution_systems', - 'D3060_other_hvac_ahu', - 'D3070_storage_systems', - 'D40_dhw', - ], - dtype='float' - ) - self._yearly_capital_costs.loc[0, 'B2010_opaque_walls'] = 0 - self._yearly_capital_costs.loc[0, 'B2020_transparent'] = 0 - self._yearly_capital_costs.loc[0, 'B3010_opaque_roof'] = 0 - self._yearly_capital_costs.loc[0, 'B1010_superstructure'] = 0 - self._yearly_capital_costs.loc[0, 'D2010_photovoltaic_system'] = 0 - self._yearly_capital_costs.loc[0, 'D3020_simultaneous_heat_and_cooling_generating_systems'] = 0 - self._yearly_capital_costs.loc[0, 'D3030_heating_systems'] = 0 - self._yearly_capital_costs.loc[0, 'D3040_cooling_systems'] = 0 - self._yearly_capital_costs.loc[0, 'D3050_distribution_systems'] = 0 - self._yearly_capital_costs.loc[0, 'D3060_other_hvac_ahu'] = 0 - self._yearly_capital_costs.loc[0, 'D3070_storage_systems'] = 0 - self._yearly_capital_costs.loc[0, 'D40_dhw'] = 0 - - self._yearly_capital_incomes = pd.DataFrame( - index=self._rng, - columns=[ - 'Subsidies construction', - 'Subsidies HVAC', - 'Subsidies PV' - ], - dtype='float' - ) - self._yearly_capital_incomes.loc[0, 'Subsidies construction'] = 0 - self._yearly_capital_incomes.loc[0, 'Subsidies HVAC'] = 0 - self._yearly_capital_incomes.loc[0, 'Subsidies PV'] = 0 - self._yearly_capital_costs.fillna(0, inplace=True) - self._own_capital = 1 - self._configuration.percentage_credit - self._surface_pv = 0 - for roof in self._building.roofs: - self._surface_pv += roof.solid_polygon.area * roof.solar_collectors_area_reduction_factor - - for roof in self._building.roofs: - if roof.installed_solar_collector_area is not None: - self._surface_pv += roof.installed_solar_collector_area - else: - self._surface_pv += roof.solid_polygon.area * roof.solar_collectors_area_reduction_factor - def calculate(self) -> tuple[pd.DataFrame, pd.DataFrame]: - self.skin_capital_cost() - self.energy_system_capital_cost() - self.skin_yearly_capital_costs() - self.yearly_energy_system_costs() - self.yearly_incomes() - return self._yearly_capital_costs, self._yearly_capital_incomes - - def skin_capital_cost(self): - """ - calculating skin costs - :return: - """ - surface_opaque = 0 - surface_transparent = 0 - surface_roof = 0 - surface_ground = 0 - - for thermal_zone in self._building.thermal_zones_from_internal_zones: - for thermal_boundary in thermal_zone.thermal_boundaries: - if thermal_boundary.type == 'Ground': - surface_ground += thermal_boundary.opaque_area - elif thermal_boundary.type == 'Roof': - surface_roof += thermal_boundary.opaque_area - elif thermal_boundary.type == 'Wall': - surface_opaque += thermal_boundary.opaque_area * (1 - thermal_boundary.window_ratio) - surface_transparent += thermal_boundary.opaque_area * thermal_boundary.window_ratio - - chapter = self._capital_costs_chapter.chapter('B_shell') - capital_cost_opaque = surface_opaque * chapter.item('B2010_opaque_walls').refurbishment[0] - capital_cost_transparent = surface_transparent * chapter.item('B2020_transparent').refurbishment[0] - capital_cost_roof = surface_roof * chapter.item('B3010_opaque_roof').refurbishment[0] - capital_cost_ground = surface_ground * chapter.item('B1010_superstructure').refurbishment[0] - if self._configuration.retrofit_scenario not in (SYSTEM_RETROFIT_AND_PV, CURRENT_STATUS, PV, SYSTEM_RETROFIT): - self._yearly_capital_costs.loc[0, 'B2010_opaque_walls'] = capital_cost_opaque * self._own_capital - self._yearly_capital_costs.loc[0, 'B2020_transparent'] = capital_cost_transparent * self._own_capital - self._yearly_capital_costs.loc[0, 'B3010_opaque_roof'] = capital_cost_roof * self._own_capital - self._yearly_capital_costs.loc[0, 'B1010_superstructure'] = capital_cost_ground * self._own_capital - capital_cost_skin = capital_cost_opaque + capital_cost_ground + capital_cost_transparent + capital_cost_roof - return capital_cost_opaque, capital_cost_transparent, capital_cost_roof, capital_cost_ground, capital_cost_skin - - def skin_yearly_capital_costs(self): - skin_capital_cost = self.skin_capital_cost() - for year in range(1, self._configuration.number_of_years): - self._yearly_capital_costs.loc[year, 'B2010_opaque_walls'] = ( - -npf.pmt( - self._configuration.interest_rate, - self._configuration.credit_years, - skin_capital_cost[0] * self._configuration.percentage_credit - ) - ) - self._yearly_capital_costs.loc[year, 'B2020_transparent'] = ( - -npf.pmt( - self._configuration.interest_rate, - self._configuration.credit_years, - skin_capital_cost[1] * self._configuration.percentage_credit - ) - ) - self._yearly_capital_costs.loc[year, 'B3010_opaque_roof'] = ( - -npf.pmt( - self._configuration.interest_rate, - self._configuration.credit_years, - skin_capital_cost[2] * self._configuration.percentage_credit - ) - ) - self._yearly_capital_costs.loc[year, 'B1010_superstructure'] = ( - -npf.pmt( - self._configuration.interest_rate, - self._configuration.credit_years, - skin_capital_cost[3] * self._configuration.percentage_credit - ) - ) - - def energy_system_capital_cost(self): - chapter = self._capital_costs_chapter.chapter('D_services') - system_components, component_categories, component_sizes = self.system_components() - capital_cost_heating_and_cooling_equipment = 0 - capital_cost_heating_equipment = 0 - capital_cost_cooling_equipment = 0 - capital_cost_domestic_hot_water_equipment = 0 - capital_cost_energy_storage_equipment = 0 - capital_cost_distribution_equipment = 0 - capital_cost_lighting = 0 - capital_cost_pv = self._surface_pv * chapter.item('D2010_photovoltaic_system').initial_investment[0] - for (i, component) in enumerate(system_components): - if component_categories[i] == 'multi_generation': - capital_cost_heating_and_cooling_equipment += chapter.item(component).initial_investment[0] * component_sizes[i] - elif component_categories[i] == 'heating': - capital_cost_heating_equipment += chapter.item(component).initial_investment[0] * component_sizes[i] - elif component_categories[i] == 'cooling': - capital_cost_cooling_equipment += chapter.item(component).initial_investment[0] * component_sizes[i] - elif component_categories[i] == 'dhw': - capital_cost_domestic_hot_water_equipment += chapter.item(component).initial_investment[0] * \ - component_sizes[i] - elif component_categories[i] == 'distribution': - capital_cost_distribution_equipment += chapter.item(component).initial_investment[0] * \ - component_sizes[i] - else: - capital_cost_energy_storage_equipment += chapter.item(component).initial_investment[0] * component_sizes[i] - - if self._configuration.retrofit_scenario in (SKIN_RETROFIT_AND_SYSTEM_RETROFIT_AND_PV, SYSTEM_RETROFIT_AND_PV, PV): - self._yearly_capital_costs.loc[0, 'D2010_photovoltaic_system'] = capital_cost_pv - if (self._configuration.retrofit_scenario in - (SYSTEM_RETROFIT_AND_PV, SKIN_RETROFIT_AND_SYSTEM_RETROFIT_AND_PV, SYSTEM_RETROFIT)): - self._yearly_capital_costs.loc[0, 'D3020_simultaneous_heat_and_cooling_generating_systems'] = ( - capital_cost_heating_and_cooling_equipment * self._own_capital) - self._yearly_capital_costs.loc[0, 'D3030_heating_systems'] = ( - capital_cost_heating_equipment * self._own_capital) - self._yearly_capital_costs.loc[0, 'D3040_cooling_systems'] = ( - capital_cost_cooling_equipment * self._own_capital) - self._yearly_capital_costs.loc[0, 'D3050_distribution_systems'] = ( - capital_cost_distribution_equipment * self._own_capital) - self._yearly_capital_costs.loc[0, 'D3070_storage_systems'] = ( - capital_cost_energy_storage_equipment * self._own_capital) - self._yearly_capital_costs.loc[0, 'D40_dhw'] = ( - capital_cost_domestic_hot_water_equipment * self._own_capital) - capital_cost_hvac = (capital_cost_heating_and_cooling_equipment + capital_cost_distribution_equipment + - capital_cost_energy_storage_equipment + capital_cost_domestic_hot_water_equipment) - return (capital_cost_pv, capital_cost_heating_and_cooling_equipment, capital_cost_heating_equipment, - capital_cost_distribution_equipment, capital_cost_cooling_equipment, capital_cost_energy_storage_equipment, - capital_cost_domestic_hot_water_equipment, capital_cost_lighting, capital_cost_hvac) - - def yearly_energy_system_costs(self): - chapter = self._capital_costs_chapter.chapter('D_services') - system_investment_costs = self.energy_system_capital_cost() - system_components, component_categories, component_sizes = self.system_components() - pv = False - for energy_system in self._building.energy_systems: - for generation_system in energy_system.generation_systems: - if generation_system.system_type == cte.PHOTOVOLTAIC: - pv = True - for year in range(1, self._configuration.number_of_years): - costs_increase = math.pow(1 + self._configuration.consumer_price_index, year) - self._yearly_capital_costs.loc[year, 'D2010_photovoltaic_system'] = ( - -npf.pmt( - self._configuration.interest_rate, - self._configuration.credit_years, - system_investment_costs[0] * self._configuration.percentage_credit - ) - ) - self._yearly_capital_costs.loc[year, 'D3020_simultaneous_heat_and_cooling_generating_systems'] = ( - -npf.pmt( - self._configuration.interest_rate, - self._configuration.credit_years, - system_investment_costs[1] * self._configuration.percentage_credit - ) - ) - self._yearly_capital_costs.loc[year, 'D3030_heating_systems'] = ( - -npf.pmt( - self._configuration.interest_rate, - self._configuration.credit_years, - system_investment_costs[2] * self._configuration.percentage_credit - ) - ) - self._yearly_capital_costs.loc[year, 'D3040_cooling_systems'] = ( - -npf.pmt( - self._configuration.interest_rate, - self._configuration.credit_years, - system_investment_costs[3] * self._configuration.percentage_credit - ) - ) - self._yearly_capital_costs.loc[year, 'D3050_distribution_systems'] = ( - -npf.pmt( - self._configuration.interest_rate, - self._configuration.credit_years, - system_investment_costs[4] * self._configuration.percentage_credit - ) - ) - self._yearly_capital_costs.loc[year, 'D3070_storage_systems'] = ( - -npf.pmt( - self._configuration.interest_rate, - self._configuration.credit_years, - system_investment_costs[5] * self._configuration.percentage_credit - ) - ) - self._yearly_capital_costs.loc[year, 'D40_dhw'] = ( - -npf.pmt( - self._configuration.interest_rate, - self._configuration.credit_years, - system_investment_costs[6] * self._configuration.percentage_credit - ) - ) - if self._configuration.retrofit_scenario not in (SKIN_RETROFIT, PV): - for (i, component) in enumerate(system_components): - if (year % chapter.item(component).lifetime) == 0 and year != (self._configuration.number_of_years - 1): - if component_categories[i] == 'multi_generation': - reposition_cost_heating_and_cooling_equipment = (chapter.item(component).reposition[0] * - component_sizes[i] * costs_increase) - self._yearly_capital_costs.loc[year, 'D3020_simultaneous_heat_and_cooling_generating_systems'] += ( - reposition_cost_heating_and_cooling_equipment) - elif component_categories[i] == 'heating': - reposition_cost_heating_equipment = (chapter.item(component).reposition[0] * - component_sizes[i] * costs_increase) - self._yearly_capital_costs.loc[year, 'D3030_heating_systems'] += ( - reposition_cost_heating_equipment) - elif component_categories[i] == 'cooling': - reposition_cost_cooling_equipment = (chapter.item(component).reposition[0] * - component_sizes[i] * costs_increase) - self._yearly_capital_costs.loc[year, 'D3040_cooling_systems'] += ( - reposition_cost_cooling_equipment) - elif component_categories[i] == 'dhw': - reposition_cost_domestic_hot_water_equipment = ( - chapter.item(component).reposition[0] * component_sizes[i] * costs_increase) - self._yearly_capital_costs.loc[year, 'D40_dhw'] += reposition_cost_domestic_hot_water_equipment - elif component_categories[i] == 'distribution': - reposition_cost_distribution_equipment = ( - chapter.item(component).reposition[0] * component_sizes[i] * costs_increase) - self._yearly_capital_costs.loc[year, 'D3050_distribution_systems'] += ( - reposition_cost_distribution_equipment) - else: - reposition_cost_energy_storage_equipment = ( - chapter.item(component).initial_investment[0] * component_sizes[i] * costs_increase) - self._yearly_capital_costs.loc[year, 'D3070_storage_systems'] += reposition_cost_energy_storage_equipment - if self._configuration.retrofit_scenario == CURRENT_STATUS and pv: - if (year % chapter.item('D2010_photovoltaic_system').lifetime) == 0: - self._yearly_capital_costs.loc[year, 'D2010_photovoltaic_system'] += ( - self._surface_pv * chapter.item('D2010_photovoltaic_system').reposition[0] * costs_increase - ) - elif self._configuration.retrofit_scenario in (PV, SYSTEM_RETROFIT_AND_PV, - SKIN_RETROFIT_AND_SYSTEM_RETROFIT_AND_PV): - if (year % chapter.item('D2010_photovoltaic_system').lifetime) == 0: - self._yearly_capital_costs.loc[year, 'D2010_photovoltaic_system'] += ( - self._surface_pv * chapter.item('D2010_photovoltaic_system').reposition[0] * costs_increase - ) - - def system_components(self): - system_components = [] - component_categories = [] - sizes = [] - energy_systems = self._building.energy_systems - for energy_system in energy_systems: - demand_types = energy_system.demand_types - generation_systems = energy_system.generation_systems - distribution_systems = energy_system.distribution_systems - for generation_system in generation_systems: - if generation_system.system_type != cte.PHOTOVOLTAIC: - heating_capacity = generation_system.nominal_heat_output or 0 - cooling_capacity = generation_system.nominal_cooling_output or 0 - installed_capacity = max(heating_capacity, cooling_capacity) / 1000 - if cte.DOMESTIC_HOT_WATER in demand_types and cte.HEATING not in demand_types: - component_categories.append('dhw') - sizes.append(installed_capacity) - if generation_system.system_type == cte.HEAT_PUMP: - system_components.append(self.heat_pump_type(generation_system, domestic_how_water=True)) - elif generation_system.system_type == cte.BOILER and generation_system.fuel_type == cte.ELECTRICITY: - system_components.append(self.boiler_type(generation_system)) - else: - system_components.append('D302010_template_heat') - elif cte.HEATING in demand_types: - if cte.COOLING in demand_types and generation_system.fuel_type == cte.ELECTRICITY: - component_categories.append('multi_generation') - else: - component_categories.append('heating') - sizes.append(installed_capacity) - if generation_system.system_type == cte.HEAT_PUMP: - item_type = self.heat_pump_type(generation_system) - system_components.append(item_type) - elif generation_system.system_type == cte.BOILER: - item_type = self.boiler_type(generation_system) - system_components.append(item_type) - else: - if cooling_capacity > heating_capacity: - system_components.append('D302090_template_cooling') - else: - system_components.append('D302010_template_heat') - elif cte.COOLING in demand_types: - component_categories.append('cooling') - sizes.append(installed_capacity) - if generation_system.system_type == cte.HEAT_PUMP: - item_type = self.heat_pump_type(generation_system) - system_components.append(item_type) - else: - system_components.append('D302090_template_cooling') - if generation_system.energy_storage_systems is not None: - energy_storage_systems = generation_system.energy_storage_systems - for storage_system in energy_storage_systems: - if storage_system.type_energy_stored == 'thermal': - component_categories.append('thermal storage') - sizes.append(storage_system.volume or 0) - system_components.append('D306010_storage_tank') - if distribution_systems is not None: - for distribution_system in distribution_systems: - component_categories.append('distribution') - sizes.append(self._building.cooling_peak_load[cte.YEAR][0] / 1000) - system_components.append('D3040_distribution_systems') - return system_components, component_categories, sizes - - def yearly_incomes(self): - capital_cost_skin = self.skin_capital_cost()[-1] - system_investment_cost = self.energy_system_capital_cost() - capital_cost_hvac = system_investment_cost[-1] - capital_cost_pv = system_investment_cost[0] - - self._yearly_capital_incomes.loc[0, 'Subsidies construction'] = ( - capital_cost_skin * self._archetype.income.construction_subsidy/100 - ) - self._yearly_capital_incomes.loc[0, 'Subsidies HVAC'] = capital_cost_hvac * self._archetype.income.hvac_subsidy/100 - self._yearly_capital_incomes.loc[0, 'Subsidies PV'] = capital_cost_pv * self._archetype.income.photovoltaic_subsidy/100 - self._yearly_capital_incomes.fillna(0, inplace=True) - - @staticmethod - def heat_pump_type(generation_system, domestic_how_water=False): - source_medium = generation_system.source_medium - supply_medium = generation_system.supply_medium - if domestic_how_water: - heat_pump_item = 'D4010_hot_water_heat_pump' - else: - if source_medium == cte.AIR and supply_medium == cte.WATER: - heat_pump_item = 'D302020_air_to_water_heat_pump' - elif source_medium == cte.AIR and supply_medium == cte.AIR: - heat_pump_item = 'D302050_air_to_air_heat_pump' - elif source_medium == cte.GROUND and supply_medium == cte.WATER: - heat_pump_item = 'D302030_ground_to_water_heat_pump' - elif source_medium == cte.GROUND and supply_medium == cte.AIR: - heat_pump_item = 'D302100_ground_to_air_heat_pump' - elif source_medium == cte.WATER and supply_medium == cte.WATER: - heat_pump_item = 'D302040_water_to_water_heat_pump' - elif source_medium == cte.WATER and supply_medium == cte.AIR: - heat_pump_item = 'D302110_water_to_air_heat_pump' - else: - heat_pump_item = 'D302010_template_heat' - return heat_pump_item - - @staticmethod - def boiler_type(generation_system): - fuel = generation_system.fuel_type - if fuel == cte.ELECTRICITY: - boiler_item = 'D302080_electrical_boiler' - elif fuel == cte.GAS: - boiler_item = 'D302070_natural_gas_boiler' - else: - boiler_item = 'D302010_template_heat' - return boiler_item - - - - diff --git a/costing_package/configuration.py b/costing_package/configuration.py deleted file mode 100644 index 3d5b9485..00000000 --- a/costing_package/configuration.py +++ /dev/null @@ -1,238 +0,0 @@ -""" -Configuration module -SPDX - License - Identifier: LGPL - 3.0 - or -later -Copyright © 2023 Project Coder Guille Gutierrez guillermo.gutierrezmorote@concordia.ca -Code contributor Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca -Code contributor Oriol Gavalda Torrellas oriol.gavalda@concordia.ca -""" -from hub.catalog_factories.costs_catalog_factory import CostsCatalogFactory -from hub.catalog_factories.catalog import Catalog - - -class Configuration: - """ - Configuration class - """ - - def __init__(self, - number_of_years, - percentage_credit, - interest_rate, - credit_years, - consumer_price_index, - electricity_peak_index, - electricity_price_index, - gas_price_index, - discount_rate, - retrofitting_year_construction, - factories_handler, - retrofit_scenario, - fuel_type, - dictionary, - fuel_tariffs - ): - self._number_of_years = number_of_years - self._percentage_credit = percentage_credit - self._interest_rate = interest_rate - self._credit_years = credit_years - self._consumer_price_index = consumer_price_index - self._electricity_peak_index = electricity_peak_index - self._electricity_price_index = electricity_price_index - self._gas_price_index = gas_price_index - self._discount_rate = discount_rate - self._retrofitting_year_construction = retrofitting_year_construction - self._factories_handler = factories_handler - self._costs_catalog = CostsCatalogFactory(factories_handler).catalog - self._retrofit_scenario = retrofit_scenario - self._fuel_type = fuel_type - self._dictionary = dictionary - self._fuel_tariffs = fuel_tariffs - - @property - def number_of_years(self): - """ - Get number of years - """ - return self._number_of_years - - @number_of_years.setter - def number_of_years(self, value): - """ - Set number of years - """ - self._number_of_years = value - - @property - def percentage_credit(self): - """ - Get percentage credit - """ - return self._percentage_credit - - @percentage_credit.setter - def percentage_credit(self, value): - """ - Set percentage credit - """ - self._percentage_credit = value - - @property - def interest_rate(self): - """ - Get interest rate - """ - return self._interest_rate - - @interest_rate.setter - def interest_rate(self, value): - """ - Set interest rate - """ - self._interest_rate = value - - @property - def credit_years(self): - """ - Get credit years - """ - return self._credit_years - - @credit_years.setter - def credit_years(self, value): - """ - Set credit years - """ - self._credit_years = value - - @property - def consumer_price_index(self): - """ - Get consumer price index - """ - return self._consumer_price_index - - @consumer_price_index.setter - def consumer_price_index(self, value): - """ - Set consumer price index - """ - self._consumer_price_index = value - - @property - def electricity_peak_index(self): - """ - Get electricity peak index - """ - return self._electricity_peak_index - - @electricity_peak_index.setter - def electricity_peak_index(self, value): - """ - Set electricity peak index - """ - self._electricity_peak_index = value - - @property - def electricity_price_index(self): - """ - Get electricity price index - """ - return self._electricity_price_index - - @electricity_price_index.setter - def electricity_price_index(self, value): - """ - Set electricity price index - """ - self._electricity_price_index = value - - @property - def gas_price_index(self): - """ - Get gas price index - """ - return self._gas_price_index - - @gas_price_index.setter - def gas_price_index(self, value): - """ - Set gas price index - """ - self._gas_price_index = value - - @property - def discount_rate(self): - """ - Get discount rate - """ - return self._discount_rate - - @discount_rate.setter - def discount_rate(self, value): - """ - Set discount rate - """ - self._discount_rate = value - - @property - def retrofitting_year_construction(self): - """ - Get retrofitting year construction - """ - return self._retrofitting_year_construction - - @retrofitting_year_construction.setter - def retrofitting_year_construction(self, value): - """ - Set retrofitting year construction - """ - self._retrofitting_year_construction = value - - @property - def factories_handler(self): - """ - Get factories handler - """ - return self._factories_handler - - @factories_handler.setter - def factories_handler(self, value): - """ - Set factories handler - """ - self._factories_handler = value - - @property - def costs_catalog(self) -> Catalog: - """ - Get costs catalog - """ - return self._costs_catalog - - @property - def retrofit_scenario(self): - """ - Get retrofit scenario - """ - return self._retrofit_scenario - - @property - def fuel_type(self): - """ - Get fuel type (0: Electricity, 1: Gas) - """ - return self._fuel_type - - @property - def dictionary(self): - """ - Get hub function to cost function dictionary - """ - return self._dictionary - - @property - def fuel_tariffs(self): - """ - Get fuel tariffs - """ - return self._fuel_tariffs diff --git a/costing_package/constants.py b/costing_package/constants.py deleted file mode 100644 index 87372d3d..00000000 --- a/costing_package/constants.py +++ /dev/null @@ -1,23 +0,0 @@ -""" -Constants module -SPDX - License - Identifier: LGPL - 3.0 - or -later -Copyright © 2023 Project Coder Guille Gutierrez guillermo.gutierrezmorote@concordia.ca -Code contributor Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca -Code contributor Oriol Gavalda Torrellas oriol.gavalda@concordia.ca -""" - -# constants -CURRENT_STATUS = 0 -SKIN_RETROFIT = 1 -SYSTEM_RETROFIT_AND_PV = 2 -SKIN_RETROFIT_AND_SYSTEM_RETROFIT_AND_PV = 3 -PV = 4 -SYSTEM_RETROFIT = 5 -RETROFITTING_SCENARIOS = [ - CURRENT_STATUS, - SKIN_RETROFIT, - SYSTEM_RETROFIT_AND_PV, - SKIN_RETROFIT_AND_SYSTEM_RETROFIT_AND_PV, - PV, - SYSTEM_RETROFIT -] diff --git a/costing_package/cost.py b/costing_package/cost.py deleted file mode 100644 index 2b2bdcd7..00000000 --- a/costing_package/cost.py +++ /dev/null @@ -1,170 +0,0 @@ -""" -Cost module -SPDX - License - Identifier: LGPL - 3.0 - or -later -Copyright © 2023 Project Coder Guille Gutierrez guillermo.gutierrezmorote@concordia.ca -Code contributor Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca -Code contributor Oriol Gavalda Torrellas oriol.gavalda@concordia.ca -""" - -import pandas as pd -import numpy_financial as npf -from hub.city_model_structure.building import Building -from hub.helpers.dictionaries import Dictionaries -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 - - -class Cost: - """ - Cost class - """ - - def __init__(self, - building: Building, - number_of_years=31, - percentage_credit=0, - interest_rate=0.04, - credit_years=15, - consumer_price_index=0.04, - electricity_peak_index=0.05, - electricity_price_index=0.05, - fuel_price_index=0.05, - discount_rate=0.03, - retrofitting_year_construction=2020, - factories_handler='montreal_new', - retrofit_scenario=CURRENT_STATUS, - dictionary=None, - fuel_tariffs=None): - if fuel_tariffs is None: - fuel_tariffs = ['Electricity-D', 'Gas-Energir'] - if dictionary is None: - dictionary = Dictionaries().hub_function_to_montreal_custom_costs_function - self._building = building - fuel_type = self._building.energy_consumption_breakdown.keys() - self._configuration = Configuration(number_of_years, - percentage_credit, - interest_rate, credit_years, - consumer_price_index, - electricity_peak_index, - electricity_price_index, - fuel_price_index, - discount_rate, - retrofitting_year_construction, - factories_handler, - retrofit_scenario, - fuel_type, - dictionary, - fuel_tariffs) - - @property - def building(self) -> Building: - """ - Get current building. - """ - return self._building - - def _npv_from_list(self, list_cashflow): - return npf.npv(self._configuration.discount_rate, list_cashflow) - - @property - def life_cycle(self) -> pd.DataFrame: - """ - Get complete life cycle costs - :return: DataFrame - """ - results = pd.DataFrame() - global_capital_costs, global_capital_incomes = CapitalCosts(self._building, self._configuration).calculate() - global_end_of_life_costs = EndOfLifeCosts(self._building, self._configuration).calculate() - global_operational_costs = TotalOperationalCosts(self._building, self._configuration).calculate() - global_maintenance_costs = TotalMaintenanceCosts(self._building, self._configuration).calculate() - global_operational_incomes = TotalOperationalIncomes(self._building, self._configuration).calculate() - - df_capital_costs_skin = ( - global_capital_costs['B2010_opaque_walls'] + - global_capital_costs['B2020_transparent'] + - global_capital_costs['B3010_opaque_roof'] + - global_capital_costs['B1010_superstructure'] - ) - df_capital_costs_systems = ( - global_capital_costs['D3020_simultaneous_heat_and_cooling_generating_systems'] + - global_capital_costs['D3030_heating_systems'] + - global_capital_costs['D3040_cooling_systems'] + - global_capital_costs['D3050_distribution_systems'] + - global_capital_costs['D3060_other_hvac_ahu'] + - global_capital_costs['D3070_storage_systems'] + - global_capital_costs['D40_dhw'] + - global_capital_costs['D2010_photovoltaic_system'] - ) - - df_end_of_life_costs = global_end_of_life_costs['End_of_life_costs'] - operational_costs_list = [ - global_operational_costs['Fixed Costs Electricity Peak'], - global_operational_costs['Fixed Costs Electricity Monthly'], - global_operational_costs['Variable Costs Electricity'] - ] - additional_costs = [ - global_operational_costs[f'Fixed Costs {fuel}'] for fuel in - self._building.energy_consumption_breakdown.keys() if fuel != cte.ELECTRICITY - ] + [ - global_operational_costs[f'Variable Costs {fuel}'] for fuel in - self._building.energy_consumption_breakdown.keys() if fuel != cte.ELECTRICITY - ] - df_operational_costs = sum(operational_costs_list + additional_costs) - df_maintenance_costs = ( - global_maintenance_costs['Heating_maintenance'] + - global_maintenance_costs['Cooling_maintenance'] + - global_maintenance_costs['PV_maintenance'] - ) - df_operational_incomes = global_operational_incomes['Incomes electricity'] - df_capital_incomes = ( - global_capital_incomes['Subsidies construction'] + - global_capital_incomes['Subsidies HVAC'] + - global_capital_incomes['Subsidies PV'] - ) - - life_cycle_costs_capital_skin = self._npv_from_list(df_capital_costs_skin.values.tolist()) - life_cycle_costs_capital_systems = self._npv_from_list(df_capital_costs_systems.values.tolist()) - life_cycle_costs_end_of_life_costs = self._npv_from_list(df_end_of_life_costs.values.tolist()) - life_cycle_operational_costs = self._npv_from_list(df_operational_costs) - life_cycle_maintenance_costs = self._npv_from_list(df_maintenance_costs.values.tolist()) - life_cycle_operational_incomes = self._npv_from_list(df_operational_incomes.values.tolist()) - life_cycle_capital_incomes = self._npv_from_list(df_capital_incomes.values.tolist()) - - results[f'Scenario {self._configuration.retrofit_scenario}'] = [ - life_cycle_costs_capital_skin, - life_cycle_costs_capital_systems, - life_cycle_costs_end_of_life_costs, - life_cycle_operational_costs, - life_cycle_maintenance_costs, - life_cycle_operational_incomes, - life_cycle_capital_incomes, - global_capital_costs, - global_capital_incomes, - global_end_of_life_costs, - global_operational_costs, - global_maintenance_costs, - global_operational_incomes - ] - - results.index = [ - 'total_capital_costs_skin', - 'total_capital_costs_systems', - 'end_of_life_costs', - 'total_operational_costs', - 'total_maintenance_costs', - 'operational_incomes', - 'capital_incomes', - 'global_capital_costs', - 'global_capital_incomes', - 'global_end_of_life_costs', - 'global_operational_costs', - 'global_maintenance_costs', - 'global_operational_incomes' - ] - return results diff --git a/costing_package/cost_base.py b/costing_package/cost_base.py deleted file mode 100644 index ad5a5aee..00000000 --- a/costing_package/cost_base.py +++ /dev/null @@ -1,40 +0,0 @@ -""" -Cost base module -SPDX - License - Identifier: LGPL - 3.0 - or -later -Copyright © 2023 Project Coder Guille Gutierrez guillermo.gutierrezmorote@concordia.ca -Code contributor Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca -Code contributor Oriol Gavalda Torrellas oriol.gavalda@concordia.ca -""" - -from hub.city_model_structure.building import Building - -from costing_package.configuration import Configuration - - -class CostBase: - """ - Abstract base class for the costs - """ - def __init__(self, building: Building, configuration: Configuration): - self._building = building - self._configuration = configuration - self._total_floor_area = 0 - for thermal_zone in building.thermal_zones_from_internal_zones: - self._total_floor_area += thermal_zone.total_floor_area - self._archetype = None - self._capital_costs_chapter = None - for archetype in self._configuration.costs_catalog.entries().archetypes: - if configuration.dictionary[str(building.function)] == str(archetype.function): - self._archetype = archetype - self._capital_costs_chapter = self._archetype.capital_cost - break - if not self._archetype: - raise KeyError(f'archetype not found for function {building.function}') - - self._rng = range(configuration.number_of_years) - - def calculate(self): - """ - Raises not implemented exception - """ - raise NotImplementedError() diff --git a/costing_package/end_of_life_costs.py b/costing_package/end_of_life_costs.py deleted file mode 100644 index f04386ef..00000000 --- a/costing_package/end_of_life_costs.py +++ /dev/null @@ -1,38 +0,0 @@ -""" -End of life costs module -SPDX - License - Identifier: LGPL - 3.0 - or -later -Copyright © 2023 Project Coder Guille Gutierrez guillermo.gutierrezmorote@concordia.ca -Code contributor Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca -Code contributor Oriol Gavalda Torrellas oriol.gavalda@concordia.ca -""" -import math -import pandas as pd -from hub.city_model_structure.building import Building - -from costing_package.configuration import Configuration -from costing_package.cost_base import CostBase - - -class EndOfLifeCosts(CostBase): - """ - End of life costs class - """ - def __init__(self, building: Building, configuration: Configuration): - super().__init__(building, configuration) - self._yearly_end_of_life_costs = pd.DataFrame(index=self._rng, columns=['End_of_life_costs'], dtype='float') - - def calculate(self): - """ - Calculate end of life costs - :return: pd.DataFrame - """ - archetype = self._archetype - total_floor_area = self._total_floor_area - for year in range(1, self._configuration.number_of_years + 1): - price_increase = math.pow(1 + self._configuration.consumer_price_index, year) - if year == self._configuration.number_of_years: - self._yearly_end_of_life_costs.at[year, 'End_of_life_costs'] = ( - total_floor_area * archetype.end_of_life_cost * price_increase - ) - self._yearly_end_of_life_costs.fillna(0, inplace=True) - return self._yearly_end_of_life_costs diff --git a/costing_package/peak_load.py b/costing_package/peak_load.py deleted file mode 100644 index 422f563b..00000000 --- a/costing_package/peak_load.py +++ /dev/null @@ -1,56 +0,0 @@ -""" -Peak load module -SPDX - License - Identifier: LGPL - 3.0 - or -later -Copyright © 2023 Project Coder Guille Gutierrez guillermo.gutierrezmorote@concordia.ca -Code contributor Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca -Code contributor Oriol Gavalda Torrellas oriol.gavalda@concordia.ca -""" - -import pandas as pd - -import hub.helpers.constants as cte - - -class PeakLoad: - """ - Peak load class - """ - - def __init__(self, building): - self._building = building - - @property - def electricity_peak_load(self): - """ - Get the electricity peak load in W - """ - array = [None] * 12 - heating = 0 - cooling = 0 - for system in self._building.energy_systems: - if cte.HEATING in system.demand_types: - heating = 1 - if cte.COOLING in system.demand_types: - cooling = 1 - if cte.MONTH in self._building.heating_peak_load.keys() and cte.MONTH in self._building.cooling_peak_load.keys(): - peak_lighting = self._building.lighting_peak_load[cte.YEAR][0] - peak_appliances = self._building.appliances_peak_load[cte.YEAR][0] - monthly_electricity_peak = [0.9 * peak_lighting + 0.7 * peak_appliances] * 12 - conditioning_peak = max(self._building.heating_peak_load[cte.MONTH], self._building.cooling_peak_load[cte.MONTH]) - for i in range(len(conditioning_peak)): - if cooling == 1 and heating == 1: - conditioning_peak[i] = conditioning_peak[i] - continue - elif cooling == 0: - conditioning_peak[i] = self._building.heating_peak_load[cte.MONTH][i] * heating - else: - conditioning_peak[i] = self._building.cooling_peak_load[cte.MONTH][i] * cooling - monthly_electricity_peak[i] += 0.8 * conditioning_peak[i] - - electricity_peak_load_results = pd.DataFrame( - monthly_electricity_peak, - columns=[f'electricity peak load W'] - ) - else: - electricity_peak_load_results = pd.DataFrame(array, columns=[f'electricity peak load W']) - return electricity_peak_load_results diff --git a/costing_package/total_maintenance_costs.py b/costing_package/total_maintenance_costs.py deleted file mode 100644 index c4e89f20..00000000 --- a/costing_package/total_maintenance_costs.py +++ /dev/null @@ -1,138 +0,0 @@ -""" -Total maintenance costs module -SPDX - License - Identifier: LGPL - 3.0 - or -later -Copyright © 2023 Project Coder Guille Gutierrez guillermo.gutierrezmorote@concordia.ca -Code contributor Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca -Code contributor Oriol Gavalda Torrellas oriol.gavalda@concordia.ca -""" -import math -import pandas as pd -from hub.city_model_structure.building import Building -import hub.helpers.constants as cte - -from costing_package.configuration import Configuration -from costing_package.cost_base import CostBase - - -class TotalMaintenanceCosts(CostBase): - """ - Total maintenance costs class - """ - def __init__(self, building: Building, configuration: Configuration): - super().__init__(building, configuration) - self._yearly_maintenance_costs = pd.DataFrame( - index=self._rng, - columns=[ - 'Heating_maintenance', - 'Cooling_maintenance', - 'DHW_maintenance', - 'PV_maintenance' - ], - dtype='float' - ) - - def calculate(self) -> pd.DataFrame: - """ - Calculate total maintenance costs - :return: pd.DataFrame - """ - building = self._building - archetype = self._archetype - # todo: change area pv when the variable exists - roof_area = 0 - surface_pv = 0 - for roof in self._building.roofs: - if roof.installed_solar_collector_area is not None: - surface_pv += roof.installed_solar_collector_area - else: - surface_pv = roof_area * 0.5 - - energy_systems = building.energy_systems - maintenance_heating_0 = 0 - maintenance_cooling_0 = 0 - maintenance_dhw_0 = 0 - heating_equipments = {} - cooling_equipments = {} - dhw_equipments = {} - for energy_system in energy_systems: - if cte.COOLING in energy_system.demand_types: - for generation_system in energy_system.generation_systems: - if generation_system.fuel_type == cte.ELECTRICITY: - if generation_system.system_type == cte.HEAT_PUMP and generation_system.source_medium == cte.AIR: - cooling_equipments['air_source_heat_pump'] = generation_system.nominal_cooling_output / 1000 - elif generation_system.system_type == cte.HEAT_PUMP and generation_system.source_medium == cte.GROUND: - cooling_equipments['ground_source_heat_pump'] = generation_system.nominal_cooling_output / 1000 - elif generation_system.system_type == cte.HEAT_PUMP and generation_system.source_medium == cte.WATER: - cooling_equipments['water_source_heat_pump'] = generation_system.nominal_cooling_output / 1000 - else: - cooling_equipments['general_cooling_equipment'] = generation_system.nominal_cooling_output / 1000 - if cte.HEATING in energy_system.demand_types: - for generation_system in energy_system.generation_systems: - if generation_system.system_type == cte.HEAT_PUMP and generation_system.source_medium == cte.AIR: - heating_equipments['air_source_heat_pump'] = generation_system.nominal_heat_output / 1000 - elif generation_system.system_type == cte.HEAT_PUMP and generation_system.source_medium == cte.GROUND: - heating_equipments['ground_source_heat_pump'] = generation_system.nominal_heat_output / 1000 - elif generation_system.system_type == cte.HEAT_PUMP and generation_system.source_medium == cte.WATER: - heating_equipments['water_source_heat_pump'] = generation_system.nominal_heat_output / 1000 - elif generation_system.system_type == cte.BOILER and generation_system.fuel_type == cte.GAS: - heating_equipments['gas_boiler'] = generation_system.nominal_heat_output / 1000 - elif generation_system.system_type == cte.BOILER and generation_system.fuel_type == cte.ELECTRICITY: - heating_equipments['electric_boiler'] = generation_system.nominal_heat_output / 1000 - else: - heating_equipments['general_heating_equipment'] = generation_system.nominal_heat_output / 1000 - if cte.DOMESTIC_HOT_WATER in energy_system.demand_types and cte.HEATING not in energy_system.demand_types: - for generation_system in energy_system.generation_systems: - if generation_system.system_type == cte.HEAT_PUMP and generation_system.source_medium == cte.AIR: - dhw_equipments['air_source_heat_pump'] = generation_system.nominal_heat_output / 1000 - elif generation_system.system_type == cte.HEAT_PUMP and generation_system.source_medium == cte.GROUND: - dhw_equipments['ground_source_heat_pump'] = generation_system.nominal_heat_output / 1000 - elif generation_system.system_type == cte.HEAT_PUMP and generation_system.source_medium == cte.WATER: - dhw_equipments['water_source_heat_pump'] = generation_system.nominal_heat_output / 1000 - elif generation_system.system_type == cte.BOILER and generation_system.fuel_type == cte.GAS: - dhw_equipments['gas_boiler'] = generation_system.nominal_heat_output / 1000 - elif generation_system.system_type == cte.BOILER and generation_system.fuel_type == cte.ELECTRICITY: - dhw_equipments['electric_boiler'] = generation_system.nominal_heat_output / 1000 - else: - dhw_equipments['general_heating_equipment'] = generation_system.nominal_heat_output / 1000 - - - for heating_equipment in heating_equipments: - component = self.search_hvac_equipment(heating_equipment) - maintenance_cost = component.maintenance[0] - maintenance_heating_0 += (heating_equipments[heating_equipment] * maintenance_cost) - - for cooling_equipment in cooling_equipments: - component = self.search_hvac_equipment(cooling_equipment) - maintenance_cost = component.maintenance[0] - maintenance_cooling_0 += (cooling_equipments[cooling_equipment] * maintenance_cost) - - for dhw_equipment in dhw_equipments: - component = self.search_hvac_equipment(dhw_equipment) - maintenance_cost = component.maintenance[0] - maintenance_dhw_0 += (dhw_equipments[dhw_equipment] * maintenance_cost) - - maintenance_pv_0 = surface_pv * archetype.operational_cost.maintenance_pv - - for year in range(1, self._configuration.number_of_years + 1): - costs_increase = math.pow(1 + self._configuration.consumer_price_index, year) - self._yearly_maintenance_costs.loc[year, 'Heating_maintenance'] = ( - maintenance_heating_0 * costs_increase - ) - self._yearly_maintenance_costs.loc[year, 'Cooling_maintenance'] = ( - maintenance_cooling_0 * costs_increase - ) - self._yearly_maintenance_costs.loc[year, 'DHW_maintenance'] = ( - maintenance_dhw_0 * costs_increase - ) - self._yearly_maintenance_costs.loc[year, 'PV_maintenance'] = ( - maintenance_pv_0 * costs_increase - ) - self._yearly_maintenance_costs.fillna(0, inplace=True) - return self._yearly_maintenance_costs - - def search_hvac_equipment(self, equipment_type): - for component in self._archetype.operational_cost.maintenance_hvac: - if component.type == equipment_type: - return component - - diff --git a/costing_package/total_operational_costs.py b/costing_package/total_operational_costs.py deleted file mode 100644 index cc8c56d6..00000000 --- a/costing_package/total_operational_costs.py +++ /dev/null @@ -1,237 +0,0 @@ -""" -Total operational costs module -SPDX - License - Identifier: LGPL - 3.0 - or -later -Copyright © 2024 Project Coder Saeed Ranjbar saeed.ranjbar@mail.concordia.ca -Code contributor Oriol Gavalda Torrellas oriol.gavalda@concordia.ca -""" -import math -import pandas as pd - -from hub.city_model_structure.building import Building -import hub.helpers.constants as cte - -from costing_package.configuration import Configuration -from costing_package.cost_base import CostBase -from costing_package.peak_load import PeakLoad - - -class TotalOperationalCosts(CostBase): - """ - Total Operational costs class - """ - - def __init__(self, building: Building, configuration: Configuration): - super().__init__(building, configuration) - columns_list = self.columns() - self._yearly_operational_costs = pd.DataFrame( - index=self._rng, - columns=columns_list, - dtype='float' - ) - - def calculate(self) -> pd.DataFrame: - """ - Calculate total operational costs - :return: pd.DataFrame - """ - building = self._building - fuel_consumption_breakdown = building.energy_consumption_breakdown - archetype = self._archetype - total_floor_area = self._total_floor_area - if archetype.function == 'residential': - factor = total_floor_area / 80 - else: - factor = 1 - total_electricity_consumption = sum(self._building.energy_consumption_breakdown[cte.ELECTRICITY].values()) / 3600 - peak_electricity_load = PeakLoad(self._building).electricity_peak_load - peak_load_value = peak_electricity_load.max(axis=1) - peak_electricity_demand = peak_load_value[1] / 1000 # self._peak_electricity_demand adapted to kW - for system_fuel in self._configuration.fuel_type: - fuel = None - for fuel_tariff in self._configuration.fuel_tariffs: - if system_fuel in fuel_tariff: - fuel = self.search_fuel(system_fuel, fuel_tariff) - if fuel.type == cte.ELECTRICITY: - if fuel.variable.rate_type == 'fixed': - variable_electricity_cost_year_0 = ( - total_electricity_consumption * float(fuel.variable.values[0]) / 1000 - ) - else: - hourly_electricity_consumption = self.hourly_fuel_consumption_profile(fuel.type) - hourly_electricity_price_profile = fuel.variable.values * len(hourly_electricity_consumption) - hourly_electricity_price = [hourly_electricity_consumption[i] / 1000 * hourly_electricity_price_profile[i] - for i in range(len(hourly_electricity_consumption))] - variable_electricity_cost_year_0 = sum(hourly_electricity_price) - peak_electricity_cost_year_0 = peak_electricity_demand * fuel.fixed_power * 12 - monthly_electricity_cost_year_0 = fuel.fixed_monthly * 12 * factor - for year in range(1, self._configuration.number_of_years + 1): - price_increase_electricity = math.pow(1 + self._configuration.electricity_price_index, year) - price_increase_peak_electricity = math.pow(1 + self._configuration.electricity_peak_index, year) - self._yearly_operational_costs.at[year, 'Fixed Costs Electricity Peak'] = ( - peak_electricity_cost_year_0 * price_increase_peak_electricity - ) - self._yearly_operational_costs.at[year, 'Fixed Costs Electricity Monthly'] = ( - monthly_electricity_cost_year_0 * price_increase_peak_electricity - ) - if not isinstance(variable_electricity_cost_year_0, pd.DataFrame): - variable_costs_electricity = variable_electricity_cost_year_0 * price_increase_electricity - else: - variable_costs_electricity = float(variable_electricity_cost_year_0.iloc[0] * price_increase_electricity) - self._yearly_operational_costs.at[year, 'Variable Costs Electricity'] = ( - variable_costs_electricity - ) - else: - fuel_fixed_cost = fuel.fixed_monthly * 12 * factor - if fuel.type == cte.BIOMASS: - conversion_factor = 1 - else: - conversion_factor = fuel.density[0] - if fuel.variable.rate_type == 'fixed': - variable_cost_fuel = ( - (sum(fuel_consumption_breakdown[fuel.type].values()) / ( - 1e6 * fuel.lower_heating_value[0] * conversion_factor)) * fuel.variable.values[0]) - - else: - hourly_fuel_consumption = self.hourly_fuel_consumption_profile(fuel.type) - hourly_fuel_price_profile = fuel.variable.values * len(hourly_fuel_consumption) - hourly_fuel_price = [hourly_fuel_consumption[i] / ( - 1e6 * fuel.lower_heating_value[0] * conversion_factor) * hourly_fuel_price_profile[i] - for i in range(len(hourly_fuel_consumption))] - variable_cost_fuel = sum(hourly_fuel_price) - - for year in range(1, self._configuration.number_of_years + 1): - price_increase_gas = math.pow(1 + self._configuration.gas_price_index, year) - self._yearly_operational_costs.at[year, f'Fixed Costs {fuel.type}'] = fuel_fixed_cost * price_increase_gas - self._yearly_operational_costs.at[year, f'Variable Costs {fuel.type}'] = ( - variable_cost_fuel * price_increase_gas) - self._yearly_operational_costs.fillna(0, inplace=True) - - return self._yearly_operational_costs - - def columns(self): - columns_list = [] - fuels = [key for key in self._building.energy_consumption_breakdown.keys()] - for fuel in fuels: - if fuel == cte.ELECTRICITY: - columns_list.append('Fixed Costs Electricity Peak') - columns_list.append('Fixed Costs Electricity Monthly') - columns_list.append('Variable Costs Electricity') - else: - columns_list.append(f'Fixed Costs {fuel}') - columns_list.append(f'Variable Costs {fuel}') - - return columns_list - - def search_fuel(self, system_fuel, tariff): - fuels = self._archetype.operational_cost.fuels - for fuel in fuels: - if system_fuel == fuel.type and tariff == fuel.variable.name: - return fuel - raise KeyError(f'fuel {system_fuel} with {tariff} tariff not found') - - - def hourly_fuel_consumption_profile(self, fuel_type): - hourly_fuel_consumption = [] - energy_systems = self._building.energy_systems - if fuel_type == cte.ELECTRICITY: - appliance = self._building.appliances_electrical_demand[cte.HOUR] - lighting = self._building.lighting_electrical_demand[cte.HOUR] - elec_heating = 0 - elec_cooling = 0 - elec_dhw = 0 - if cte.HEATING in self._building.energy_consumption_breakdown[cte.ELECTRICITY]: - elec_heating = 1 - if cte.COOLING in self._building.energy_consumption_breakdown[cte.ELECTRICITY]: - elec_cooling = 1 - if cte.DOMESTIC_HOT_WATER in self._building.energy_consumption_breakdown[cte.ELECTRICITY]: - elec_dhw = 1 - heating = None - cooling = None - dhw = None - - if elec_heating == 1: - for energy_system in energy_systems: - if cte.HEATING in energy_system.demand_types: - for generation_system in energy_system.generation_systems: - if generation_system.fuel_type == cte.ELECTRICITY: - if cte.HEATING in generation_system.energy_consumption: - heating = generation_system.energy_consumption[cte.HEATING][cte.HOUR] - else: - if len(energy_system.generation_systems) > 1: - heating = [x / 2 for x in self._building.heating_consumption[cte.HOUR]] - else: - heating = self._building.heating_consumption[cte.HOUR] - - if elec_dhw == 1: - for energy_system in energy_systems: - if cte.DOMESTIC_HOT_WATER in energy_system.demand_types: - for generation_system in energy_system.generation_systems: - if generation_system.fuel_type == cte.ELECTRICITY: - if cte.DOMESTIC_HOT_WATER in generation_system.energy_consumption: - dhw = generation_system.energy_consumption[cte.DOMESTIC_HOT_WATER][cte.HOUR] - else: - if len(energy_system.generation_systems) > 1: - dhw = [x / 2 for x in self._building.domestic_hot_water_consumption[cte.HOUR]] - else: - dhw = self._building.domestic_hot_water_consumption[cte.HOUR] - - if elec_cooling == 1: - for energy_system in energy_systems: - if cte.COOLING in energy_system.demand_types: - for generation_system in energy_system.generation_systems: - if cte.COOLING in generation_system.energy_consumption: - cooling = generation_system.energy_consumption[cte.COOLING][cte.HOUR] - else: - if len(energy_system.generation_systems) > 1: - cooling = [x / 2 for x in self._building.cooling_consumption[cte.HOUR]] - else: - cooling = self._building.cooling_consumption[cte.HOUR] - - for i in range(len(self._building.heating_demand[cte.HOUR])): - hourly = 0 - hourly += appliance[i] / 3600 - hourly += lighting[i] / 3600 - if heating is not None: - hourly += heating[i] / 3600 - if cooling is not None: - hourly += cooling[i] / 3600 - if dhw is not None: - hourly += dhw[i] / 3600 - hourly_fuel_consumption.append(hourly) - else: - heating = None - dhw = None - if cte.HEATING in self._building.energy_consumption_breakdown[fuel_type]: - for energy_system in energy_systems: - if cte.HEATING in energy_system.demand_types: - for generation_system in energy_system.generation_systems: - if cte.HEATING in generation_system.energy_consumption: - heating = generation_system.energy_consumption[cte.HEATING][cte.HOUR] - else: - if len(energy_system.generation_systems) > 1: - heating = [x / 2 for x in self._building.heating_consumption[cte.HOUR]] - else: - heating = self._building.heating_consumption[cte.HOUR] - if cte.DOMESTIC_HOT_WATER in self._building.energy_consumption_breakdown[fuel_type]: - for energy_system in energy_systems: - if cte.DOMESTIC_HOT_WATER in energy_system.demand_types: - for generation_system in energy_system.generation_systems: - if cte.DOMESTIC_HOT_WATER in generation_system.energy_consumption: - dhw = generation_system.energy_consumption[cte.DOMESTIC_HOT_WATER][cte.HOUR] - else: - if len(energy_system.generation_systems) > 1: - dhw = [x / 2 for x in self._building.domestic_hot_water_consumption[cte.HOUR]] - else: - dhw = self._building.domestic_hot_water_consumption[cte.HOUR] - - for i in range(len(self._building.heating_demand[cte.HOUR])): - hourly = 0 - if heating is not None: - hourly += heating[i] / 3600 - if dhw is not None: - hourly += dhw[i] / 3600 - hourly_fuel_consumption.append(hourly) - return hourly_fuel_consumption - - - diff --git a/costing_package/total_operational_incomes.py b/costing_package/total_operational_incomes.py deleted file mode 100644 index bb190999..00000000 --- a/costing_package/total_operational_incomes.py +++ /dev/null @@ -1,44 +0,0 @@ -""" -Total operational incomes module -SPDX - License - Identifier: LGPL - 3.0 - or -later -Copyright © 2023 Project Coder Guille Gutierrez guillermo.gutierrezmorote@concordia.ca -Code contributor Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca -Code contributor Oriol Gavalda Torrellas oriol.gavalda@concordia.ca -""" -import math -import pandas as pd -from hub.city_model_structure.building import Building -import hub.helpers.constants as cte - -from costing_package.configuration import Configuration -from costing_package.cost_base import CostBase - - -class TotalOperationalIncomes(CostBase): - """ - Total operational incomes class - """ - def __init__(self, building: Building, configuration: Configuration): - super().__init__(building, configuration) - self._yearly_operational_incomes = pd.DataFrame(index=self._rng, columns=['Incomes electricity'], dtype='float') - - def calculate(self) -> pd.DataFrame: - """ - Calculate total operational incomes - :return: pd.DataFrame - """ - building = self._building - archetype = self._archetype - if cte.YEAR not in building.onsite_electrical_production: - onsite_electricity_production = 0 - else: - onsite_electricity_production = building.onsite_electrical_production[cte.YEAR][0] - for year in range(1, self._configuration.number_of_years + 1): - price_increase_electricity = math.pow(1 + self._configuration.electricity_price_index, year) - price_export = archetype.income.electricity_export # to account for unit change - self._yearly_operational_incomes.loc[year, 'Incomes electricity'] = ( - (onsite_electricity_production / 3.6e6) * price_export * price_increase_electricity - ) - - self._yearly_operational_incomes.fillna(0, inplace=True) - return self._yearly_operational_incomes \ No newline at end of file diff --git a/costing_package/version.py b/costing_package/version.py deleted file mode 100644 index 0f4beb14..00000000 --- a/costing_package/version.py +++ /dev/null @@ -1,8 +0,0 @@ -""" -Cost version number -SPDX - License - Identifier: LGPL - 3.0 - or -later -Copyright © 2023 Project Coder Guille Gutierrez guillermo.gutierrezmorote@concordia.ca -Code contributor Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca -Code contributor Oriol Gavalda Torrellas oriol.gavalda@concordia.ca -""" -__version__ = '0.1.0.5' 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 deleted file mode 100644 index e66140d8..00000000 --- a/energy_system_modelling_package/energy_system_modelling_factories/archetypes/montreal/archetype_cluster_1.py +++ /dev/null @@ -1,147 +0,0 @@ -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[2].generation_systems[0] - cooling_demand_joules = self.building.cooling_demand[cte.HOUR] - cooling_peak_load = self.building.cooling_peak_load[cte.YEAR][0] - cutoff_temperature = 11 - outdoor_temperature = self.building.external_temperature[cte.HOUR] - results = HeatPumpCooling(hp=hp, - hourly_cooling_demand_joules=cooling_demand_joules, - cooling_peak_load_watts=cooling_peak_load, - cutoff_temperature=cutoff_temperature, - outdoor_temperature=outdoor_temperature, - dt=self.dt).simulation() - building_cooling_hourly_consumption = hp.energy_consumption[cte.COOLING][cte.HOUR] - return results, building_cooling_hourly_consumption - - def dhw_system_simulation(self): - building_dhw_hourly_consumption = [] - hp = self.building.energy_systems[-1].generation_systems[0] - tes = self.building.energy_systems[-1].generation_systems[0].energy_storage_systems[0] - dhw_demand_joules = self.building.domestic_hot_water_heat_demand[cte.HOUR] - upper_limit_tes = 65 - outdoor_temperature = self.building.external_temperature[cte.HOUR] - results = DomesticHotWaterHeatPumpTes(hp=hp, - tes=tes, - hourly_dhw_demand_joules=dhw_demand_joules, - upper_limit_tes=upper_limit_tes, - outdoor_temperature=outdoor_temperature, - dt=self.dt).simulation() - number_of_ts = int(cte.HOUR_TO_SECONDS/self.dt) - dhw_consumption_joules = [(x * cte.WATTS_HOUR_TO_JULES) / number_of_ts for x in - results['Total DHW Power Consumption (W)']] - dhw_consumption = 0 - for i in range(1, len(dhw_consumption_joules)): - dhw_consumption += dhw_consumption_joules[i] - if (i - 1) % number_of_ts == 0: - building_dhw_hourly_consumption.append(dhw_consumption) - dhw_consumption = 0 - return results, building_dhw_hourly_consumption - - def 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 deleted file mode 100644 index 8ea92fae..00000000 --- a/energy_system_modelling_package/energy_system_modelling_factories/energy_system_sizing_factory.py +++ /dev/null @@ -1,79 +0,0 @@ -""" -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.optimal_sizing import \ - OptimalSizing -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.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 _optimal_sizing(self): - """ - Size Energy Systems using a Single or Multi Objective GA - """ - OptimalSizing(self._city, optimization_scenario='cost_energy_consumption').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 deleted file mode 100644 index 1034ce63..00000000 --- a/energy_system_modelling_package/energy_system_modelling_factories/hvac_dhw_systems_simulation_models/domestic_hot_water_heat_pump_with_tes.py +++ /dev/null @@ -1,120 +0,0 @@ -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=None): - 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 = [0] + [x for x in source_temperature_hourly for _ in range(number_of_ts)] - demand = [0] + [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] = 65 - for i in range(len(demand) - 1): - delta_t_demand = demand[i] * (self.dt / (cte.WATER_DENSITY * cte.WATER_HEAT_CAPACITY * - storage_tank.volume)) - if t_tank[i] < self.upper_limit_tes: - q_hp[i] = hp.nominal_heat_output - delta_t_hp = q_hp[i] * (self.dt / (cte.WATER_DENSITY * cte.WATER_HEAT_CAPACITY * storage_tank.volume)) - if demand[i] > 0: - dhw_needed = (demand[i] * cte.HOUR_TO_SECONDS) / (cte.WATER_HEAT_CAPACITY * t_tank[i] * cte.WATER_DENSITY) - m_dis[i] = dhw_needed * cte.WATER_DENSITY / cte.HOUR_TO_SECONDS - m_refill[i] = m_dis[i] - delta_t_freshwater = m_refill[i] * (t_tank[i] - freshwater_temperature) * (self.dt / (storage_tank.volume * - cte.WATER_DENSITY)) - if t_tank[i] < 60: - q_coil[i] = float(storage_tank.heating_coil_capacity) - delta_t_coil = q_coil[i] * (self.dt / (cte.WATER_DENSITY * cte.WATER_HEAT_CAPACITY * storage_tank.volume)) - if q_hp[i] > 0: - m_ch[i] = q_hp[i] / (cte.WATER_HEAT_CAPACITY * hp_delta_t) - t_sup_hp[i] = (q_hp[i] / (m_ch[i] * cte.WATER_HEAT_CAPACITY)) + t_tank[i] - else: - m_ch[i] = 0 - t_sup_hp[i] = t_tank[i] - if q_hp[i] > 0: - if hp.source_medium == cte.AIR and hp.supply_medium == cte.WATER: - hp_cop[i] = self.hp_characteristics.air_to_water_cop(source_temperature[i], t_tank[i], - mode=cte.DOMESTIC_HOT_WATER) - hp_electricity[i] = q_hp[i] / hp_cop[i] - else: - hp_cop[i] = 0 - hp_electricity[i] = 0 - - t_tank[i + 1] = t_tank[i] + (delta_t_hp - delta_t_freshwater - delta_t_demand + delta_t_coil) - total_consumption[i] = hp_electricity[i] + q_coil[i] - tes.temperature = [] - hp_electricity_j = [(x * cte.WATTS_HOUR_TO_JULES) / number_of_ts for x in hp_electricity] - heating_coil_j = [(x * cte.WATTS_HOUR_TO_JULES) / number_of_ts for x in q_coil] - hp_hourly = [] - coil_hourly = [] - coil_sum = 0 - hp_sum = 0 - for i in range(1, len(demand)): - hp_sum += hp_electricity_j[i] - coil_sum += heating_coil_j[i] - if (i - 1) % number_of_ts == 0: - tes.temperature.append(t_tank[i]) - hp_hourly.append(hp_sum) - coil_hourly.append(coil_sum) - hp_sum = 0 - coil_sum = 0 - hp.energy_consumption[cte.DOMESTIC_HOT_WATER] = {} - hp.energy_consumption[cte.DOMESTIC_HOT_WATER][cte.HOUR] = hp_hourly - if len(self.dhw_demand) == 8760: - 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 - if len(self.dhw_demand) == 8760: - 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 deleted file mode 100644 index affaa871..00000000 --- a/energy_system_modelling_package/energy_system_modelling_factories/hvac_dhw_systems_simulation_models/heat_pump_boiler_tes_heating.py +++ /dev/null @@ -1,171 +0,0 @@ -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=None): - 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] - if heating_peak_load_watts is not None: - self.heating_peak_load = heating_peak_load_watts - else: - self.heating_peak_load = max(hourly_heating_demand_joules) / cte.HOUR_TO_SECONDS - self.upper_limit_tes = upper_limit_tes - self.hp_characteristics = HeatPump(self.hp, outdoor_temperature) - self.t_out = outdoor_temperature - self.dt = dt - self.results = {} - - def simulation(self): - hp, boiler, tes = self.hp, self.boiler, self.tes - heating_coil_nominal_output = 0 - if tes.heating_coil_capacity is not None: - heating_coil_nominal_output = float(tes.heating_coil_capacity) - storage_tank = StorageTank(volume=float(tes.volume), - height=float(tes.height), - material_layers=tes.layers, - heating_coil_capacity=heating_coil_nominal_output) - number_of_ts = int(cte.HOUR_TO_SECONDS / self.dt) - demand = [0] + [x for x in self.heating_demand for _ in range(number_of_ts)] - t_out = [0] + [x for x in self.t_out for _ in range(number_of_ts)] - source_temperature_hourly = self.hp_characteristics.hp_source_temperature() - source_temperature = [0] + [x for x in source_temperature_hourly for _ in range(number_of_ts)] - variable_names = ["t_sup_hp", "t_tank", "t_ret", "m_ch", "m_dis", "q_hp", "q_boiler", "hp_cop", - "hp_electricity", "boiler_gas_consumption", "t_sup_boiler", "boiler_energy_consumption", - "heating_consumption", "heating_coil_output", "total_heating_energy_consumption"] - num_hours = len(demand) - variables = {name: [0] * num_hours for name in variable_names} - (t_sup_hp, t_tank, t_ret, m_ch, m_dis, q_hp, q_boiler, hp_cop, - hp_electricity, boiler_fuel_consumption, t_sup_boiler, boiler_energy_consumption, heating_consumption, q_coil, - total_consumption) = [variables[name] for name in variable_names] - t_tank[0] = self.upper_limit_tes - hp_delta_t = 5 - # storage temperature prediction - for i in range(len(demand) - 1): - t_tank[i + 1] = storage_tank.calculate_space_heating_fully_mixed(charging_flow_rate=m_ch[i], - discharging_flow_rate=m_dis[i], - supply_temperature=t_sup_boiler[i], - return_temperature=t_ret[i], - current_tank_temperature=t_tank[i], - heat_generator_input=q_coil[i], - ambient_temperature=t_out[i], - dt=self.dt) - # hp operation - if t_tank[i + 1] < 40: - q_hp[i + 1] = hp.nominal_heat_output - m_ch[i + 1] = q_hp[i + 1] / (cte.WATER_HEAT_CAPACITY * hp_delta_t) - t_sup_hp[i + 1] = (q_hp[i + 1] / (m_ch[i + 1] * cte.WATER_HEAT_CAPACITY)) + t_tank[i + 1] - elif 40 <= t_tank[i + 1] < self.upper_limit_tes and q_hp[i] == 0: - q_hp[i + 1] = 0 - m_ch[i + 1] = 0 - t_sup_hp[i + 1] = t_tank[i + 1] - elif 40 <= t_tank[i + 1] < self.upper_limit_tes and q_hp[i] > 0: - q_hp[i + 1] = hp.nominal_heat_output - m_ch[i + 1] = q_hp[i + 1] / (cte.WATER_HEAT_CAPACITY * hp_delta_t) - t_sup_hp[i + 1] = (q_hp[i + 1] / (m_ch[i + 1] * cte.WATER_HEAT_CAPACITY)) + t_tank[i + 1] - else: - q_hp[i + 1], m_ch[i + 1], t_sup_hp[i + 1] = 0, 0, t_tank[i + 1] - if q_hp[i + 1] > 0: - if hp.source_medium == cte.AIR and self.hp.supply_medium == cte.WATER: - hp_cop[i + 1] = self.hp_characteristics.air_to_water_cop(source_temperature[i + 1], t_tank[i + 1], mode=cte.HEATING) - hp_electricity[i + 1] = q_hp[i + 1] / hp_cop[i + 1] - else: - hp_cop[i + 1] = 0 - hp_electricity[i + 1] = 0 - # boiler operation - if q_hp[i + 1] > 0: - if t_sup_hp[i + 1] < 45: - q_boiler[i + 1] = boiler.nominal_heat_output - elif demand[i + 1] > 0.5 * self.heating_peak_load / self.dt: - q_boiler[i + 1] = 0.5 * boiler.nominal_heat_output - boiler_energy_consumption[i + 1] = q_boiler[i + 1] / float(boiler.heat_efficiency) - if boiler.fuel_type == cte.ELECTRICITY: - boiler_fuel_consumption[i + 1] = boiler_energy_consumption[i + 1] - else: - # TODO: Other fuels should be considered - boiler_fuel_consumption[i + 1] = (q_boiler[i + 1] * self.dt) / ( - float(boiler.heat_efficiency) * cte.NATURAL_GAS_LHV) - t_sup_boiler[i + 1] = t_sup_hp[i + 1] + (q_boiler[i + 1] / (m_ch[i + 1] * cte.WATER_HEAT_CAPACITY)) - # heating coil operation - if t_tank[i + 1] < 35: - q_coil[i + 1] = heating_coil_nominal_output - # storage discharging - if demand[i + 1] == 0: - m_dis[i + 1] = 0 - t_ret[i + 1] = t_tank[i + 1] - else: - if demand[i + 1] > 0.5 * self.heating_peak_load: - factor = 8 - else: - factor = 4 - m_dis[i + 1] = self.heating_peak_load / (cte.WATER_HEAT_CAPACITY * factor) - t_ret[i + 1] = t_tank[i + 1] - demand[i + 1] / (m_dis[i + 1] * cte.WATER_HEAT_CAPACITY) - # total consumption - total_consumption[i + 1] = hp_electricity[i + 1] + boiler_energy_consumption[i + 1] + q_coil[i + 1] - tes.temperature = [] - hp_electricity_j = [(x * cte.WATTS_HOUR_TO_JULES) / number_of_ts for x in hp_electricity] - boiler_consumption_j = [(x * cte.WATTS_HOUR_TO_JULES) / number_of_ts for x in boiler_energy_consumption] - heating_coil_j = [(x * cte.WATTS_HOUR_TO_JULES) / number_of_ts for x in q_coil] - hp_hourly = [] - boiler_hourly = [] - coil_hourly = [] - boiler_sum = 0 - hp_sum = 0 - coil_sum = 0 - for i in range(1, len(demand)): - hp_sum += hp_electricity_j[i] - boiler_sum += boiler_consumption_j[i] - coil_sum += heating_coil_j[i] - if (i - 1) % number_of_ts == 0: - tes.temperature.append(t_tank[i]) - hp_hourly.append(hp_sum) - boiler_hourly.append(boiler_sum) - coil_hourly.append(coil_sum) - hp_sum = 0 - boiler_sum = 0 - coil_sum = 0 - hp.energy_consumption[cte.HEATING] = {} - hp.energy_consumption[cte.HEATING][cte.HOUR] = hp_hourly - boiler.energy_consumption[cte.HEATING] = {} - boiler.energy_consumption[cte.HEATING][cte.HOUR] = boiler_hourly - if len(self.heating_demand) == 8760: - 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][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] = {} - if len(self.heating_demand) == 8760: - 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 deleted file mode 100644 index 944c6094..00000000 --- a/energy_system_modelling_package/energy_system_modelling_factories/hvac_dhw_systems_simulation_models/heat_pump_characteristics.py +++ /dev/null @@ -1,54 +0,0 @@ -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)) / 3.41214 - hp_efficiency = float(self.hp.cooling_efficiency) - else: - if self.hp.heat_efficiency_curve is not None: - cop_curve_coefficients = [float(coefficient) for coefficient in self.hp.heat_efficiency_curve.coefficients] - cop_coefficient = (cop_curve_coefficients[0] + - cop_curve_coefficients[1] * source_temperature + - cop_curve_coefficients[2] * source_temperature ** 2 + - cop_curve_coefficients[3] * inlet_water_temperature + - cop_curve_coefficients[4] * inlet_water_temperature ** 2 + - cop_curve_coefficients[5] * inlet_water_temperature * source_temperature) - hp_efficiency = float(self.hp.heat_efficiency) - hp_cop = cop_coefficient * hp_efficiency - return hp_cop 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 deleted file mode 100644 index cf838bdf..00000000 --- a/energy_system_modelling_package/energy_system_modelling_factories/hvac_dhw_systems_simulation_models/heat_pump_cooling.py +++ /dev/null @@ -1,89 +0,0 @@ -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] / hp_cop[i] - 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_supply_temperature_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_supply_temperature_hourly.append(t_sup_hp[i]) - hp_sum = 0 - self.hp.cooling_supply_temperature = hp_supply_temperature_hourly - 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 Cooling 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 deleted file mode 100644 index 552e1807..00000000 --- a/energy_system_modelling_package/energy_system_modelling_factories/hvac_dhw_systems_simulation_models/thermal_storage_tank.py +++ /dev/null @@ -1,47 +0,0 @@ -import math - -import hub.helpers.constants as cte - - -class StorageTank: - """ - Calculation of the temperature inside a hot water storage tank in the next time step - """ - - def __init__(self, volume, height, material_layers, heating_coil_capacity, stratification_layer=1): - self.volume = volume - self.height = height - self.materials = material_layers - self.number_of_vertical_layers = stratification_layer - self.heating_coil_capacity = heating_coil_capacity - - def heat_loss_coefficient(self): - r_tot = sum(float(layer.thickness) / float(layer.material.conductivity) for layer in - self.materials) - u_tot = 1 / r_tot - d = math.sqrt((4 * self.volume) / (math.pi * self.height)) - a_side = math.pi * d * self.height - a_top = math.pi * d ** 2 / 4 - if self.number_of_vertical_layers == 1: - ua = u_tot * (2 * a_top + a_side) - return ua - else: - ua_side = u_tot * a_side - ua_top_bottom = u_tot * (a_top + a_side) - return ua_side, ua_top_bottom - - - def calculate_space_heating_fully_mixed(self, charging_flow_rate, discharging_flow_rate, supply_temperature, - return_temperature, - current_tank_temperature, heat_generator_input, ambient_temperature, dt): - ua = self.heat_loss_coefficient() - t_tank = (current_tank_temperature + - (charging_flow_rate * (supply_temperature - current_tank_temperature) + - (ua * (ambient_temperature - current_tank_temperature)) / cte.WATER_HEAT_CAPACITY - - discharging_flow_rate * (current_tank_temperature - return_temperature) + - heat_generator_input / cte.WATER_HEAT_CAPACITY) * (dt / (cte.WATER_DENSITY * self.volume))) - return t_tank - - def calculate_dhw_fully_mixed(self, charging_flow_rate, discharging_flow_rate, supply_temperature, return_temperature, - current_tank_temperature, heat_generator_input, ambient_temperature, dt): - pass 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 deleted file mode 100644 index 9fe1176b..00000000 --- a/energy_system_modelling_package/energy_system_modelling_factories/montreal_energy_system_archetype_modelling_factory.py +++ /dev/null @@ -1,36 +0,0 @@ -""" -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, csv_output=True): - self._output_path = output_path - self._handler = '_' + handler.lower() - self._building = building - self._csv_output = csv_output - - 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, self._csv_output).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 deleted file mode 100644 index 175f367e..00000000 --- a/energy_system_modelling_package/energy_system_modelling_factories/pv_assessment/electricity_demand_calculator.py +++ /dev/null @@ -1,73 +0,0 @@ -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/energy_system_modelling_package/energy_system_modelling_factories/pv_assessment/pv_feasibility.py b/energy_system_modelling_package/energy_system_modelling_factories/pv_assessment/pv_feasibility.py deleted file mode 100644 index 9278b16c..00000000 --- a/energy_system_modelling_package/energy_system_modelling_factories/pv_assessment/pv_feasibility.py +++ /dev/null @@ -1,37 +0,0 @@ -from pathlib import Path -import subprocess -from hub.imports.geometry_factory import GeometryFactory -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 -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.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 - geojson_file = process_geojson(x=current_x, y=current_y, diff=new_diff, expansion=True) - file_path = input_files_path / 'output_buildings.geojson' - city = GeometryFactory('geojson', - path=file_path, - height_field='height', - year_of_construction_field='year_of_construction', - function_field='function', - function_to_hub=Dictionaries().montreal_function_to_hub_function).city - WeatherFactory('epw', city).enrich() - ExportsFactory('sra', city, sra_output_path).export() - sra_path = (sra_output_path / f'{city.name}_sra.xml').resolve() - subprocess.run(['sra', str(sra_path)]) - ResultFactory('sra', city, sra_output_path).enrich() - for selected_building in selected_buildings: - for building in city.buildings: - if selected_building.name == building.name: - selected_building.roofs[0].global_irradiance = building.roofs[0].global_irradiance - - - - 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 deleted file mode 100644 index 2714befa..00000000 --- a/energy_system_modelling_package/energy_system_modelling_factories/pv_assessment/pv_model.py +++ /dev/null @@ -1,42 +0,0 @@ -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 deleted file mode 100644 index b53ba465..00000000 --- a/energy_system_modelling_package/energy_system_modelling_factories/pv_assessment/pv_sizing.py +++ /dev/null @@ -1,70 +0,0 @@ -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/energy_system_modelling_package/energy_system_modelling_factories/pv_assessment/pv_sizing_and_simulation.py b/energy_system_modelling_package/energy_system_modelling_factories/pv_assessment/pv_sizing_and_simulation.py deleted file mode 100644 index fc26fe7e..00000000 --- a/energy_system_modelling_package/energy_system_modelling_factories/pv_assessment/pv_sizing_and_simulation.py +++ /dev/null @@ -1,59 +0,0 @@ -import math - -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 - - -class PVSizingSimulation(RadiationTilted): - def __init__(self, building, solar_angles, tilt_angle, module_height, module_width, ghi): - super().__init__(building, solar_angles, tilt_angle, ghi) - self.module_height = module_height - self.module_width = module_width - self.total_number_of_panels = 0 - self.enrich() - - def available_space(self): - roof_area = self.building.roofs[0].perimeter_area - maintenance_factor = 0.1 - orientation_factor = 0.2 - if self.building.function == cte.RESIDENTIAL: - mechanical_equipment_factor = 0.2 - else: - mechanical_equipment_factor = 0.3 - available_roof = (maintenance_factor + orientation_factor + mechanical_equipment_factor) * roof_area - return available_roof - - def inter_row_spacing(self): - winter_solstice = self.df[(self.df['AST'].dt.month == 12) & - (self.df['AST'].dt.day == 21) & - (self.df['AST'].dt.hour == 12)] - solar_altitude = winter_solstice['solar altitude'].values[0] - solar_azimuth = winter_solstice['solar azimuth'].values[0] - distance = ((self.module_height * abs(math.cos(math.radians(solar_azimuth)))) / - math.tan(math.radians(solar_altitude))) - distance = float(format(distance, '.1f')) - return distance - - def number_of_panels(self, available_roof, inter_row_distance): - space_dimension = math.sqrt(available_roof) - space_dimension = float(format(space_dimension, '.2f')) - panels_per_row = math.ceil(space_dimension / self.module_width) - number_of_rows = math.ceil(space_dimension / inter_row_distance) - self.total_number_of_panels = panels_per_row * number_of_rows - return panels_per_row, number_of_rows - - def pv_output_constant_efficiency(self): - radiation = self.total_radiation_tilted - pv_module_area = self.module_width * self.module_height - available_roof = self.available_space() - inter_row_spacing = self.inter_row_spacing() - self.number_of_panels(available_roof, inter_row_spacing) - self.building.roofs[0].installed_solar_collector_area = pv_module_area * self.total_number_of_panels - system_efficiency = 0.2 - pv_hourly_production = [x * system_efficiency * self.total_number_of_panels * pv_module_area * - cte.WATTS_HOUR_TO_JULES for x in radiation] - self.building.onsite_electrical_production[cte.HOUR] = pv_hourly_production - self.building.onsite_electrical_production[cte.MONTH] = ( - MonthlyValues.get_total_month(self.building.onsite_electrical_production[cte.HOUR])) - self.building.onsite_electrical_production[cte.YEAR] = [sum(self.building.onsite_electrical_production[cte.MONTH])] \ No newline at end of file diff --git a/energy_system_modelling_package/energy_system_modelling_factories/pv_assessment/radiation_tilted.py b/energy_system_modelling_package/energy_system_modelling_factories/pv_assessment/radiation_tilted.py deleted file mode 100644 index 31bd5636..00000000 --- a/energy_system_modelling_package/energy_system_modelling_factories/pv_assessment/radiation_tilted.py +++ /dev/null @@ -1,110 +0,0 @@ -import pandas as pd -import math -import hub.helpers.constants as cte -from hub.helpers.monthly_values import MonthlyValues - - -class RadiationTilted: - def __init__(self, building, solar_angles, tilt_angle, ghi, solar_constant=1366.1, maximum_clearness_index=1, - min_cos_zenith=0.065, maximum_zenith_angle=87): - self.building = building - self.ghi = ghi - self.tilt_angle = tilt_angle - self.zeniths = solar_angles['zenith'].tolist()[:-1] - self.incidents = solar_angles['incident angle'].tolist()[:-1] - self.date_time = solar_angles['DateTime'].tolist()[:-1] - self.ast = solar_angles['AST'].tolist()[:-1] - self.solar_azimuth = solar_angles['solar azimuth'].tolist()[:-1] - self.solar_altitude = solar_angles['solar altitude'].tolist()[:-1] - data = {'DateTime': self.date_time, 'AST': self.ast, 'solar altitude': self.solar_altitude, 'zenith': self.zeniths, - 'solar azimuth': self.solar_azimuth, 'incident angle': self.incidents, 'ghi': self.ghi} - self.df = pd.DataFrame(data) - self.df['DateTime'] = pd.to_datetime(self.df['DateTime']) - self.df['AST'] = pd.to_datetime(self.df['AST']) - self.df.set_index('DateTime', inplace=True) - self.solar_constant = solar_constant - self.maximum_clearness_index = maximum_clearness_index - self.min_cos_zenith = min_cos_zenith - self.maximum_zenith_angle = maximum_zenith_angle - self.i_on = [] - self.i_oh = [] - self.k_t = [] - self.fraction_diffuse = [] - self.diffuse_horizontal = [] - self.beam_horizontal = [] - self.dni = [] - self.beam_tilted = [] - self.diffuse_tilted = [] - self.total_radiation_tilted = [] - self.calculate() - - def dni_extra(self): - for i in range(len(self.df)): - self.i_on.append(self.solar_constant * (1 + 0.033 * math.cos(math.radians(360 * self.df.index.dayofyear[i] / 365)))) - - self.df['extraterrestrial normal radiation (Wh/m2)'] = self.i_on - - def clearness_index(self): - for i in range(len(self.df)): - self.i_oh.append(self.i_on[i] * max(math.cos(math.radians(self.zeniths[i])), self.min_cos_zenith)) - self.k_t.append(self.ghi[i] / self.i_oh[i]) - self.k_t[i] = max(0, self.k_t[i]) - self.k_t[i] = min(self.maximum_clearness_index, self.k_t[i]) - self.df['extraterrestrial radiation on horizontal (Wh/m2)'] = self.i_oh - self.df['clearness index'] = self.k_t - - def diffuse_fraction(self): - for i in range(len(self.df)): - if self.k_t[i] <= 0.22: - self.fraction_diffuse.append(1 - 0.09 * self.k_t[i]) - elif self.k_t[i] <= 0.8: - self.fraction_diffuse.append(0.9511 - 0.1604 * self.k_t[i] + 4.388 * self.k_t[i] ** 2 - - 16.638 * self.k_t[i] ** 3 + 12.336 * self.k_t[i] ** 4) - else: - self.fraction_diffuse.append(0.165) - if self.zeniths[i] > self.maximum_zenith_angle: - self.fraction_diffuse[i] = 1 - - self.df['diffuse fraction'] = self.fraction_diffuse - - def radiation_components_horizontal(self): - for i in range(len(self.df)): - self.diffuse_horizontal.append(self.ghi[i] * self.fraction_diffuse[i]) - self.beam_horizontal.append(self.ghi[i] - self.diffuse_horizontal[i]) - self.dni.append((self.ghi[i] - self.diffuse_horizontal[i]) / math.cos(math.radians(self.zeniths[i]))) - if self.zeniths[i] > self.maximum_zenith_angle or self.dni[i] < 0: - self.dni[i] = 0 - - self.df['diffuse horizontal (Wh/m2)'] = self.diffuse_horizontal - self.df['dni (Wh/m2)'] = self.dni - self.df['beam horizontal (Wh/m2)'] = self.beam_horizontal - - def radiation_components_tilted(self): - for i in range(len(self.df)): - self.beam_tilted.append(self.dni[i] * math.cos(math.radians(self.incidents[i]))) - self.beam_tilted[i] = max(self.beam_tilted[i], 0) - self.diffuse_tilted.append(self.diffuse_horizontal[i] * ((1 + math.cos(math.radians(self.tilt_angle))) / 2)) - self.total_radiation_tilted.append(self.beam_tilted[i] + self.diffuse_tilted[i]) - - self.df['beam tilted (Wh/m2)'] = self.beam_tilted - self.df['diffuse tilted (Wh/m2)'] = self.diffuse_tilted - self.df['total radiation tilted (Wh/m2)'] = self.total_radiation_tilted - - def calculate(self) -> pd.DataFrame: - self.dni_extra() - self.clearness_index() - self.diffuse_fraction() - self.radiation_components_horizontal() - self.radiation_components_tilted() - return self.df - - def enrich(self): - tilted_radiation = self.total_radiation_tilted - self.building.roofs[0].global_irradiance_tilted[cte.HOUR] = tilted_radiation - self.building.roofs[0].global_irradiance_tilted[cte.MONTH] = ( - MonthlyValues.get_total_month(self.building.roofs[0].global_irradiance_tilted[cte.HOUR])) - self.building.roofs[0].global_irradiance_tilted[cte.YEAR] = \ - [sum(self.building.roofs[0].global_irradiance_tilted[cte.MONTH])] - - - diff --git a/energy_system_modelling_package/energy_system_modelling_factories/pv_assessment/solar_angles.py b/energy_system_modelling_package/energy_system_modelling_factories/pv_assessment/solar_angles.py deleted file mode 100644 index 560bd27c..00000000 --- a/energy_system_modelling_package/energy_system_modelling_factories/pv_assessment/solar_angles.py +++ /dev/null @@ -1,145 +0,0 @@ -import math -import pandas as pd -from datetime import datetime -from pathlib import Path - - -class CitySolarAngles: - def __init__(self, location_latitude, location_longitude, tilt_angle, surface_azimuth_angle, - standard_meridian=-75): - self.location_latitude = location_latitude - self.location_longitude = location_longitude - self.location_latitude_rad = math.radians(location_latitude) - self.surface_azimuth_angle = surface_azimuth_angle - self.surface_azimuth_rad = math.radians(surface_azimuth_angle) - self.tilt_angle = tilt_angle - self.tilt_angle_rad = math.radians(tilt_angle) - self.standard_meridian = standard_meridian - self.longitude_correction = (location_longitude - standard_meridian) * 4 - self.timezone = 'Etc/GMT+5' - - self.eot = [] - self.ast = [] - self.hour_angles = [] - self.declinations = [] - self.solar_altitudes = [] - self.solar_azimuths = [] - self.zeniths = [] - self.incidents = [] - self.beam_tilted = [] - self.factor = [] - self.times = pd.date_range(start='2023-01-01', end='2024-01-01', freq='h', tz=self.timezone) - self.df = pd.DataFrame(index=self.times) - self.day_of_year = self.df.index.dayofyear - - def solar_time(self, datetime_val, day_of_year): - b = (day_of_year - 81) * 2 * math.pi / 364 - eot = 9.87 * math.sin(2 * b) - 7.53 * math.cos(b) - 1.5 * math.sin(b) - self.eot.append(eot) - - # Calculate Local Solar Time (LST) - lst_hour = datetime_val.hour - lst_minute = datetime_val.minute - lst_second = datetime_val.second - lst = lst_hour + lst_minute / 60 + lst_second / 3600 - - # Calculate Apparent Solar Time (AST) in decimal hours - ast_decimal = lst + eot / 60 + self.longitude_correction / 60 - ast_hours = int(ast_decimal) - ast_minutes = round((ast_decimal - ast_hours) * 60) - - # Ensure ast_minutes is within valid range - if ast_minutes == 60: - ast_hours += 1 - ast_minutes = 0 - elif ast_minutes < 0: - ast_minutes = 0 - ast_time = datetime(year=datetime_val.year, month=datetime_val.month, day=datetime_val.day, - hour=ast_hours, minute=ast_minutes) - self.ast.append(ast_time) - return ast_time - - def declination_angle(self, day_of_year): - declination = 23.45 * math.sin(math.radians(360 / 365 * (284 + day_of_year))) - declination_radian = math.radians(declination) - self.declinations.append(declination) - return declination_radian - - def hour_angle(self, ast_time): - hour_angle = ((ast_time.hour * 60 + ast_time.minute) - 720) / 4 - hour_angle_radian = math.radians(hour_angle) - self.hour_angles.append(hour_angle) - return hour_angle_radian - - def solar_altitude(self, declination_radian, hour_angle_radian): - solar_altitude_radians = math.asin(math.cos(self.location_latitude_rad) * math.cos(declination_radian) * - math.cos(hour_angle_radian) + math.sin(self.location_latitude_rad) * - math.sin(declination_radian)) - solar_altitude = math.degrees(solar_altitude_radians) - self.solar_altitudes.append(solar_altitude) - return solar_altitude_radians - - def zenith(self, solar_altitude_radians): - solar_altitude = math.degrees(solar_altitude_radians) - zenith_degree = 90 - solar_altitude - zenith_radian = math.radians(zenith_degree) - self.zeniths.append(zenith_degree) - return zenith_radian - - def solar_azimuth_analytical(self, hourangle, declination, zenith): - numer = (math.cos(zenith) * math.sin(self.location_latitude_rad) - math.sin(declination)) - denom = (math.sin(zenith) * math.cos(self.location_latitude_rad)) - if math.isclose(denom, 0.0, abs_tol=1e-8): - cos_azi = 1.0 - else: - cos_azi = numer / denom - - cos_azi = max(-1.0, min(1.0, cos_azi)) - - sign_ha = math.copysign(1, hourangle) - solar_azimuth_radians = sign_ha * math.acos(cos_azi) + math.pi - solar_azimuth_degrees = math.degrees(solar_azimuth_radians) - self.solar_azimuths.append(solar_azimuth_degrees) - return solar_azimuth_radians - - def incident_angle(self, solar_altitude_radians, solar_azimuth_radians): - incident_radian = math.acos(math.cos(solar_altitude_radians) * - math.cos(abs(solar_azimuth_radians - self.surface_azimuth_rad)) * - math.sin(self.tilt_angle_rad) + math.sin(solar_altitude_radians) * - math.cos(self.tilt_angle_rad)) - incident_angle_degrees = math.degrees(incident_radian) - self.incidents.append(incident_angle_degrees) - return incident_radian - - @property - def calculate(self) -> pd.DataFrame: - for i in range(len(self.times)): - datetime_val = self.times[i] - day_of_year = self.day_of_year[i] - declination_radians = self.declination_angle(day_of_year) - ast_time = self.solar_time(datetime_val, day_of_year) - hour_angle_radians = self.hour_angle(ast_time) - solar_altitude_radians = self.solar_altitude(declination_radians, hour_angle_radians) - zenith_radians = self.zenith(solar_altitude_radians) - solar_azimuth_radians = self.solar_azimuth_analytical(hour_angle_radians, declination_radians, zenith_radians) - incident_angle_radian = self.incident_angle(solar_altitude_radians, solar_azimuth_radians) - - self.df['DateTime'] = self.times - self.df['AST'] = self.ast - self.df['hour angle'] = self.hour_angles - self.df['eot'] = self.eot - self.df['declination angle'] = self.declinations - self.df['solar altitude'] = self.solar_altitudes - self.df['zenith'] = self.zeniths - self.df['solar azimuth'] = self.solar_azimuths - self.df['incident angle'] = self.incidents - - return self.df - - - - - - - - diff --git a/energy_system_modelling_package/energy_system_modelling_factories/system_sizing_methods/genetic_algorithm/individual.py b/energy_system_modelling_package/energy_system_modelling_factories/system_sizing_methods/genetic_algorithm/individual.py deleted file mode 100644 index 5a25e72e..00000000 --- a/energy_system_modelling_package/energy_system_modelling_factories/system_sizing_methods/genetic_algorithm/individual.py +++ /dev/null @@ -1,470 +0,0 @@ -import math -import random -from hub.helpers.dictionaries import Dictionaries -from hub.catalog_factories.costs_catalog_factory import CostsCatalogFactory -import hub.helpers.constants as cte -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.hvac_dhw_systems_simulation_models.heat_pump_boiler_tes_heating import \ - HeatPumpBoilerTesHeating -import numpy_financial as npf - - -class Individual: - def __init__(self, building, energy_system, design_period_energy_demands, optimization_scenario, - heating_design_load=None, cooling_design_load=None, dt=900, fuel_price_index=0.05, - electricity_tariff_type='fixed', consumer_price_index=0.04, interest_rate=0.04, - discount_rate=0.03, percentage_credit=0, credit_years=15): - """ - :param building: building object - :param energy_system: energy system to be optimized - :param design_period_energy_demands: A dictionary of design period heating, cooling and dhw demands. Design period - is the day with the highest total demand and the two days before and after it - :param optimization_scenario: a string indicating the objective function from minimization of cost, - energy consumption, and both together - :param heating_design_load: heating design load in W - :param cooling_design_load: cooling design load in W - :param dt the time step size used for simulations - :param fuel_price_index the price increase index of all fuels. A single value is used for all fuels. - :param electricity_tariff_type the electricity tariff type between 'fixed' and 'variable' for economic optimization - :param consumer_price_index - """ - self.building = building - self.energy_system = energy_system - self.design_period_energy_demands = design_period_energy_demands - self.demand_types = energy_system.demand_types - self.optimization_scenario = optimization_scenario - self.heating_design_load = heating_design_load - self.cooling_design_load = cooling_design_load - self.available_space = building.volume / building.storeys_above_ground - self.dt = dt - self.fuel_price_index = fuel_price_index - self.electricity_tariff_type = electricity_tariff_type - self.consumer_price_index = consumer_price_index - self.interest_rate = interest_rate - self.discount_rate = discount_rate - self.credit_years = credit_years - self.percentage_credit = percentage_credit - self.costs = self.costs_archetype() - self.feasibility = True - self.fitness_score = 0 - self.individual = {} - - def system_components(self): - """ - Extracts system components (generation and storage) for a given energy system. - :return: Dictionary of system components. - """ - self.individual['Generation Components'] = [] - self.individual['Energy Storage Components'] = [] - self.individual['End of Life Cost'] = self.costs.end_of_life_cost - for generation_component in self.energy_system.generation_systems: - investment_cost, reposition_cost, lifetime = self.unit_investment_cost('Generation', - generation_component.system_type) - maintenance_cost = self.unit_maintenance_cost(generation_component) - if generation_component.system_type == cte.PHOTOVOLTAIC: - heating_capacity = None - cooling_capacity = None - heat_efficiency = None - cooling_efficiency = None - unit_fuel_cost = 0 - else: - heating_capacity = 0 - cooling_capacity = 0 - heat_efficiency = generation_component.heat_efficiency - cooling_efficiency = generation_component.cooling_efficiency - unit_fuel_cost = self.fuel_cost_per_kwh(generation_component.fuel_type, 'fixed') - self.individual['Generation Components'].append({ - 'type': generation_component.system_type, - 'heating_capacity': heating_capacity, - 'cooling_capacity': cooling_capacity, - 'electricity_capacity': 0, - 'nominal_heating_efficiency': heat_efficiency, - 'nominal_cooling_efficiency': cooling_efficiency, - 'nominal_electricity_efficiency': generation_component.electricity_efficiency, - 'fuel_type': generation_component.fuel_type, - 'unit_investment_cost': investment_cost, - 'unit_reposition_cost': reposition_cost, - 'unit_fuel_cost(CAD/kWh)': unit_fuel_cost, - 'unit_maintenance_cost': maintenance_cost, - 'lifetime': lifetime - }) - if generation_component.energy_storage_systems is not None: - for energy_storage_system in generation_component.energy_storage_systems: - investment_cost, reposition_cost, lifetime = ( - self.unit_investment_cost('Storage', - f'{energy_storage_system.type_energy_stored}_storage')) - if energy_storage_system.type_energy_stored == cte.THERMAL: - heating_coil_capacity = energy_storage_system.heating_coil_capacity - heating_coil_fuel_cost = self.fuel_cost_per_kwh(f'{cte.ELECTRICITY}', 'fixed') - volume = 0 - capacity = None - else: - heating_coil_capacity = None - heating_coil_fuel_cost = None - volume = None - capacity = 0 - self.individual['Energy Storage Components'].append( - {'type': f'{energy_storage_system.type_energy_stored}_storage', - 'capacity': capacity, - 'volume': volume, - 'heating_coil_capacity': heating_coil_capacity, - 'unit_investment_cost': investment_cost, - 'unit_reposition_cost': reposition_cost, - 'heating_coil_fuel_cost': heating_coil_fuel_cost, - 'unit_maintenance_cost': 0, - 'lifetime': lifetime - }) - - def initialization(self): - """ - Assigns initial sizes to generation and storage components based on heating and cooling design loads and - available space in the building. - :return: - """ - self.system_components() - generation_components = self.individual['Generation Components'] - storage_components = self.individual['Energy Storage Components'] - for generation_component in generation_components: - if generation_component[ - 'nominal_heating_efficiency'] is not None and cte.HEATING or cte.DOMESTIC_HOT_WATER in self.demand_types: - if self.heating_design_load is not None: - generation_component['heating_capacity'] = random.uniform(0, self.heating_design_load) - else: - if cte.HEATING in self.demand_types: - generation_component['heating_capacity'] = random.uniform(0, max( - self.design_period_energy_demands[cte.HEATING]['demands']) / cte.WATTS_HOUR_TO_JULES) - else: - generation_component['heating_capacity'] = random.uniform(0, max( - self.design_period_energy_demands[cte.DOMESTIC_HOT_WATER]['demands']) / cte.WATTS_HOUR_TO_JULES) - else: - generation_component['heating_capacity'] = None - if generation_component['nominal_cooling_efficiency'] is not None and cte.COOLING in self.demand_types: - if self.cooling_design_load is not None: - generation_component['cooling_capacity'] = random.uniform(0, self.cooling_design_load) - else: - generation_component['cooling_capacity'] = random.uniform(0, max( - self.design_period_energy_demands[cte.COOLING]['demands']) / cte.WATTS_HOUR_TO_JULES) - else: - generation_component['cooling_capacity'] = None - if generation_component['nominal_electricity_efficiency'] is None: - generation_component['electricity_capacity'] = None - for storage_component in storage_components: - if storage_component['type'] == f'{cte.THERMAL}_storage': - storage_component['volume'] = random.uniform(0, 0.01 * self.available_space) - if storage_component['heating_coil_capacity'] is not None: - if self.heating_design_load is not None: - storage_component['heating_coil_capacity'] = random.uniform(0, self.heating_design_load) - else: - if cte.HEATING in self.demand_types: - storage_component['heating_coil_capacity'] = random.uniform(0, max( - self.design_period_energy_demands[cte.HEATING]['demands']) / cte.WATTS_HOUR_TO_JULES) - else: - storage_component['heating_coil_capacity'] = random.uniform(0, max( - self.design_period_energy_demands[cte.DOMESTIC_HOT_WATER]['demands']) / cte.WATTS_HOUR_TO_JULES) - - def score_evaluation(self): - self.system_simulation() - self.individual['feasible'] = self.feasibility - lcc = 0 - total_energy_consumption = 0 - if self.feasibility: - if 'cost' in self.optimization_scenario: - investment_cost = 0 - operation_cost_year_0 = 0 - maintenance_cost_year_0 = 0 - for generation_system in self.individual['Generation Components']: - heating_capacity = 0 if generation_system['heating_capacity'] is None else generation_system[ - 'heating_capacity'] - cooling_capacity = 0 if generation_system['cooling_capacity'] is None else generation_system[ - 'cooling_capacity'] - capacity = max(heating_capacity, cooling_capacity) - investment_cost += capacity * generation_system['unit_investment_cost'] / 1000 - maintenance_cost_year_0 += capacity * generation_system['unit_maintenance_cost'] / 1000 - operation_cost_year_0 += (generation_system['total_energy_consumption(kWh)'] * - generation_system['unit_fuel_cost(CAD/kWh)']) - for storage_system in self.individual['Energy Storage Components']: - if cte.THERMAL in storage_system['type']: - investment_cost += storage_system['volume'] * storage_system['unit_investment_cost'] - if storage_system['heating_coil_capacity'] is not None: - operation_cost_year_0 += (storage_system['total_energy_consumption(kWh)'] * - storage_system['heating_coil_fuel_cost']) - lcc = self.life_cycle_cost_calculation(investment_cost=investment_cost, - operation_cost_year_0=operation_cost_year_0, - maintenance_cost_year_0=maintenance_cost_year_0) - self.individual['lcc'] = lcc - if 'energy_consumption' in self.optimization_scenario: - total_energy_consumption = 0 - for generation_system in self.individual['Generation Components']: - total_energy_consumption += generation_system['total_energy_consumption(kWh)'] - for storage_system in self.individual['Energy Storage Components']: - if cte.THERMAL in storage_system['type'] and storage_system['heating_coil_capacity'] is not None: - total_energy_consumption += storage_system['total_energy_consumption(kWh)'] - self.individual['total_energy_consumption'] = total_energy_consumption - # Fitness score based on the optimization scenario - if self.optimization_scenario == 'cost': - self.fitness_score = lcc - self.individual['fitness_score'] = lcc - elif self.optimization_scenario == 'energy_consumption': - self.fitness_score = total_energy_consumption - self.individual['fitness_score'] = total_energy_consumption - elif self.optimization_scenario == 'cost_energy_consumption' or self.optimization_scenario == 'energy_consumption_cost': - self.fitness_score = (lcc, total_energy_consumption) - self.individual['fitness_score'] = (lcc, total_energy_consumption) - else: - lcc = float('inf') - total_energy_consumption = float('inf') - self.individual['lcc'] = lcc - self.individual['total_energy_consumption'] = total_energy_consumption - if self.optimization_scenario == 'cost_energy_consumption' or self.optimization_scenario == 'energy_consumption_cost': - self.individual['fitness_score'] = (float('inf'), float('inf')) - self.fitness_score = (float('inf'), float('inf')) - else: - self.individual['fitness_score'] = float('inf') - self.fitness_score = float('inf') - - def system_simulation(self): - """ - The method to run the energy system model using the existing models in the energy_system_modelling_package. - Based on cluster id and demands, model is imported and run. - :return: dictionary of results - """ - if self.building.energy_systems_archetype_cluster_id == '1': - if cte.HEATING in self.demand_types: - boiler = self.energy_system.generation_systems[0] - boiler.nominal_heat_output = self.individual['Generation Components'][0]['heating_capacity'] - hp = self.energy_system.generation_systems[1] - hp.nominal_heat_output = self.individual['Generation Components'][1]['heating_capacity'] - tes = self.energy_system.generation_systems[0].energy_storage_systems[0] - tes.volume = self.individual['Energy Storage Components'][0]['volume'] - tes.height = self.building.average_storey_height - 1 - tes.heating_coil_capacity = self.individual['Energy Storage Components'][0]['heating_coil_capacity'] \ - if self.individual['Energy Storage Components'][0]['heating_coil_capacity'] is not None else None - heating_demand_joules = self.design_period_energy_demands[cte.HEATING]['demands'] - heating_peak_load_watts = max(self.design_period_energy_demands[cte.HEATING]) if \ - (self.heating_design_load is not None) else self.building.heating_peak_load[cte.YEAR][0] - upper_limit_tes_heating = 55 - design_period_start_index = self.design_period_energy_demands[cte.HEATING]['start_index'] - design_period_end_index = self.design_period_energy_demands[cte.HEATING]['end_index'] - outdoor_temperature = self.building.external_temperature[cte.HOUR][ - design_period_start_index:design_period_end_index] - 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() - if min(results['TES Temperature']) < 35: - self.feasibility = False - elif cte.DOMESTIC_HOT_WATER in self.demand_types: - hp = self.energy_system.generation_systems[0] - hp.nominal_heat_output = self.individual['Generation Components'][0]['heating_capacity'] - tes = self.energy_system.generation_systems[0].energy_storage_systems[0] - tes.volume = self.individual['Energy Storage Components'][0]['volume'] - tes.height = self.building.average_storey_height - 1 - tes.heating_coil_capacity = self.individual['Energy Storage Components'][0]['heating_coil_capacity'] \ - if self.individual['Energy Storage Components'][0]['heating_coil_capacity'] is not None else None - dhw_demand_joules = self.design_period_energy_demands[cte.DOMESTIC_HOT_WATER]['demands'] - upper_limit_tes = 65 - design_period_start_index = self.design_period_energy_demands[cte.DOMESTIC_HOT_WATER]['start_index'] - design_period_end_index = self.design_period_energy_demands[cte.DOMESTIC_HOT_WATER]['end_index'] - outdoor_temperature = self.building.external_temperature[cte.HOUR][ - design_period_start_index:design_period_end_index] - 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() - if min(results['DHW TES Temperature']) < 55: - self.feasibility = False - if self.feasibility: - generation_system_types = [generation_system.system_type for generation_system in - self.energy_system.generation_systems] - for generation_component in self.individual['Generation Components']: - if generation_component['type'] in generation_system_types: - index = generation_system_types.index(generation_component['type']) - for demand_type in self.demand_types: - if demand_type in self.energy_system.generation_systems[index].energy_consumption: - generation_component['total_energy_consumption(kWh)'] = (sum( - self.energy_system.generation_systems[index].energy_consumption[demand_type][cte.HOUR]) / 3.6e6) - for storage_component in self.individual['Energy Storage Components']: - if storage_component['type'] == f'{cte.THERMAL}_storage' and storage_component[ - 'heating_coil_capacity'] is not None: - for generation_system in self.energy_system.generation_systems: - if generation_system.energy_storage_systems is not None: - for storage_system in generation_system.energy_storage_systems: - if storage_system.type_energy_stored == cte.THERMAL: - for demand_type in self.demand_types: - if demand_type in storage_system.heating_coil_energy_consumption: - storage_component['total_energy_consumption(kWh)'] = (sum( - storage_system.heating_coil_energy_consumption[demand_type][cte.HOUR]) / 3.6e6) - - def life_cycle_cost_calculation(self, investment_cost, operation_cost_year_0, maintenance_cost_year_0, - life_cycle_duration=41): - """ - Calculating the life cycle cost of the energy system. The original cost workflow in hub is not used to reduce - computation time.Here are the steps: - 1- Costs catalog and different components are imported. - 2- Capital costs (investment and reposition) are calculated and appended to a list to have the capital cash - flow. - 3- - :param maintenance_cost_year_0: - :param operation_cost_year_0: - :param investment_cost: - :param life_cycle_duration: - :return: - """ - capital_costs_cash_flow = [investment_cost] - operational_costs_cash_flow = [0] - maintenance_costs_cash_flow = [0] - end_of_life_costs = [0] * (life_cycle_duration + 1) - for i in range(1, life_cycle_duration + 1): - yearly_operational_cost = math.pow(1 + self.fuel_price_index, i) * operation_cost_year_0 - yearly_maintenance_cost = math.pow(1 + self.consumer_price_index, i) * maintenance_cost_year_0 - yearly_capital_cost = 0 - for generation_system in self.individual['Generation Components']: - if (i % generation_system['lifetime']) == 0 and i != (life_cycle_duration - 1): - cost_increase = math.pow(1 + self.consumer_price_index, i) - heating_capacity = 0 if generation_system['heating_capacity'] is None else generation_system[ - 'heating_capacity'] - cooling_capacity = 0 if generation_system['cooling_capacity'] is None else generation_system[ - 'cooling_capacity'] - capacity = max(heating_capacity, cooling_capacity) - yearly_capital_cost += generation_system['unit_reposition_cost'] * capacity * cost_increase / 1000 - yearly_capital_cost += -npf.pmt(self.interest_rate, self.credit_years, - investment_cost * self.percentage_credit) - for storage_system in self.individual['Energy Storage Components']: - if (i % storage_system['lifetime']) == 0 and i != (life_cycle_duration - 1): - cost_increase = math.pow(1 + self.consumer_price_index, i) - capacity = storage_system['volume'] if cte.THERMAL in storage_system['type'] else storage_system['capacity'] - yearly_capital_cost += storage_system['unit_reposition_cost'] * capacity * cost_increase - yearly_capital_cost += -npf.pmt(self.interest_rate, self.credit_years, - investment_cost * self.percentage_credit) - capital_costs_cash_flow.append(yearly_capital_cost) - operational_costs_cash_flow.append(yearly_operational_cost) - maintenance_costs_cash_flow.append(yearly_maintenance_cost) - for year in range(1, life_cycle_duration + 1): - price_increase = math.pow(1 + self.consumer_price_index, year) - if year == life_cycle_duration: - end_of_life_costs[year] = ( - self.building.thermal_zones_from_internal_zones[0].total_floor_area * - self.individual['End of Life Cost'] * price_increase - ) - - life_cycle_capital_cost = npf.npv(self.discount_rate, capital_costs_cash_flow) - life_cycle_operational_cost = npf.npv(self.discount_rate, operational_costs_cash_flow) - life_cycle_maintenance_cost = npf.npv(self.discount_rate, maintenance_costs_cash_flow) - life_cycle_end_of_life_cost = npf.npv(self.discount_rate, end_of_life_costs) - total_life_cycle_cost = life_cycle_capital_cost + life_cycle_operational_cost + life_cycle_maintenance_cost + life_cycle_end_of_life_cost - return total_life_cycle_cost - - def costs_archetype(self): - costs_catalogue = CostsCatalogFactory('montreal_new').catalog.entries().archetypes - dictionary = Dictionaries().hub_function_to_montreal_custom_costs_function - costs_archetype = None - for archetype in costs_catalogue: - if dictionary[str(self.building.function)] == str(archetype.function): - costs_archetype = archetype - return costs_archetype - - def unit_investment_cost(self, component_category, component_type): - """ - Reading the investment and reposition costs of any component from costs catalogue - :param component_category: Due to the categorizations in the cost catalogue, we need this parameter to realize if - the component is a generation or storage component - :param component_type: Type of the component - :return: - """ - investment_cost = 0 - reposition_cost = 0 - lifetime = 0 - name = '' - capital_costs_chapter = self.costs.capital_cost.chapter('D_services') - if component_category == 'Generation': - generation_systems = self.energy_system.generation_systems - for generation_system in generation_systems: - if component_type == generation_system.system_type: - if generation_system.system_type == cte.HEAT_PUMP: - name += ( - generation_system.source_medium.lower() + '_to_' + generation_system.supply_medium.lower() + '_' + - generation_system.system_type.lower().replace(' ', '_')) - elif generation_system.system_type == cte.BOILER: - if generation_system.fuel_type == cte.ELECTRICITY: - name += cte.ELECTRICAL.lower() + f'_{generation_system.system_type}'.lower() - else: - name += generation_system.fuel_type.lower() + f'_{generation_system.system_type}'.lower() - elif generation_system.system_type == cte.PHOTOVOLTAIC: - name += 'D2010_photovoltaic_system' - else: - if cte.HEATING or cte.DOMESTIC_HOT_WATER in self.demand_types: - name += 'template_heat' - else: - name += 'template_cooling' - for item in capital_costs_chapter.items: - if name in item.type: - investment_cost += float(capital_costs_chapter.item(item.type).initial_investment[0]) - reposition_cost += float(capital_costs_chapter.item(item.type).reposition[0]) - lifetime += float(capital_costs_chapter.item(item.type).lifetime) - elif component_category == 'Storage': - for generation_system in self.energy_system.generation_systems: - if generation_system.energy_storage_systems is not None: - for energy_storage_system in generation_system.energy_storage_systems: - if energy_storage_system.type_energy_stored == cte.THERMAL: - if energy_storage_system.heating_coil_capacity is not None: - investment_cost += float(capital_costs_chapter.item('D306010_storage_tank').initial_investment[0]) - reposition_cost += float(capital_costs_chapter.item('D306010_storage_tank').reposition[0]) - lifetime += float(capital_costs_chapter.item('D306010_storage_tank').lifetime) - else: - investment_cost += float( - capital_costs_chapter.item('D306020_storage_tank_with_coil').initial_investment[0]) - reposition_cost += float(capital_costs_chapter.item('D306020_storage_tank_with_coil').reposition[0]) - lifetime += float(capital_costs_chapter.item('D306020_storage_tank_with_coil').lifetime) - - return investment_cost, reposition_cost, lifetime - - def unit_maintenance_cost(self, generation_system): - hvac_maintenance = self.costs.operational_cost.maintenance_hvac - pv_maintenance = self.costs.operational_cost.maintenance_pv - maintenance_cost = 0 - component = None - if generation_system.system_type == cte.HEAT_PUMP and generation_system.source_medium == cte.AIR: - component = 'air_source_heat_pump' - elif generation_system.system_type == cte.HEAT_PUMP and generation_system.source_medium == cte.GROUND: - component = 'ground_source_heat_pump' - elif generation_system.system_type == cte.HEAT_PUMP and generation_system.source_medium == cte.WATER: - component = 'water_source_heat_pump' - elif generation_system.system_type == cte.BOILER and generation_system.fuel_type == cte.GAS: - component = 'gas_boiler' - elif generation_system.system_type == cte.BOILER and generation_system.fuel_type == cte.ELECTRICITY: - component = 'electric_boiler' - elif generation_system.system_type == cte.PHOTOVOLTAIC: - maintenance_cost += pv_maintenance - else: - if cte.HEATING or cte.DOMESTIC_HOT_WATER in self.demand_types: - component = 'general_heating_equipment' - else: - component = 'general_cooling_equipment' - for item in hvac_maintenance: - if item.type == component: - maintenance_cost += item.maintenance[0] - return maintenance_cost - - def fuel_cost_per_kwh(self, fuel_type, fuel_cost_tariff_type): - fuel_cost = 0 - catalog_fuels = self.costs.operational_cost.fuels - for fuel in catalog_fuels: - if fuel_type == fuel.type and fuel_cost_tariff_type == fuel.variable.rate_type: - if fuel.type == cte.ELECTRICITY and fuel_cost_tariff_type == 'fixed': - fuel_cost = fuel.variable.values[0] - elif fuel.type == cte.ELECTRICITY and fuel_cost_tariff_type == 'variable': - fuel_cost = fuel.variable.values[0] - else: - if fuel.type == cte.BIOMASS: - conversion_factor = 1 - else: - conversion_factor = fuel.density[0] - fuel_cost = fuel.variable.values[0] / (conversion_factor * fuel.lower_heating_value[0] * 0.277) - return fuel_cost diff --git a/energy_system_modelling_package/energy_system_modelling_factories/system_sizing_methods/genetic_algorithm/multi_objective_genetic_algorithm.py b/energy_system_modelling_package/energy_system_modelling_factories/system_sizing_methods/genetic_algorithm/multi_objective_genetic_algorithm.py deleted file mode 100644 index a063cb48..00000000 --- a/energy_system_modelling_package/energy_system_modelling_factories/system_sizing_methods/genetic_algorithm/multi_objective_genetic_algorithm.py +++ /dev/null @@ -1,418 +0,0 @@ -import copy -import math -import random -import hub.helpers.constants as cte -from energy_system_modelling_package.energy_system_modelling_factories.system_sizing_methods.genetic_algorithm.individual import \ - Individual -import matplotlib.pyplot as plt - - -class MultiObjectiveGeneticAlgorithm: - def __init__(self, population_size=100, generations=50, crossover_rate=0.8, mutation_rate=0.1, - optimization_scenario=None, output_path=None): - self.population_size = population_size - self.population = [] - self.generations = generations - self.crossover_rate = crossover_rate - self.mutation_rate = mutation_rate - self.optimization_scenario = optimization_scenario - self.list_of_solutions = [] - self.best_solution = None - self.best_solution_generation = None - self.output_path = output_path - -# Initialize Population - def initialize_population(self, building, energy_system): - design_period_energy_demands = self.design_period_identification(building) - attempts = 0 - max_attempts = self.population_size * 5 - while len(self.population) < self.population_size and attempts < max_attempts: - individual = Individual(building=building, - energy_system=energy_system, - design_period_energy_demands=design_period_energy_demands, - optimization_scenario=self.optimization_scenario) - individual.initialization() - attempts += 1 - if self.initial_population_feasibility_check(individual, energy_system.demand_types, - design_period_energy_demands): - self.population.append(individual) - if len(self.population) < self.population_size: - raise RuntimeError(f"Could not generate a feasible population of size {self.population_size}. " - f"Only {len(self.population)} feasible individuals were generated.") - - @staticmethod - def initial_population_feasibility_check(individual, demand_types, design_period_demands): - total_heating_capacity = sum( - component['heating_capacity'] for component in individual.individual['Generation Components'] - if component['heating_capacity'] is not None - ) - total_cooling_capacity = sum( - component['cooling_capacity'] for component in individual.individual['Generation Components'] - if component['cooling_capacity'] is not None - ) - max_heating_demand = max(design_period_demands[cte.HEATING]['demands']) / cte.WATTS_HOUR_TO_JULES - max_cooling_demand = max(design_period_demands[cte.COOLING]['demands']) / cte.WATTS_HOUR_TO_JULES - max_dhw_demand = max(design_period_demands[cte.DOMESTIC_HOT_WATER]['demands']) / cte.WATTS_HOUR_TO_JULES - if cte.HEATING in demand_types and total_heating_capacity < 0.5 * max_heating_demand: - return False - if cte.DOMESTIC_HOT_WATER in demand_types and total_heating_capacity < 0.5 * max_dhw_demand: - return False - if cte.COOLING in demand_types and total_cooling_capacity < 0.5 * max_cooling_demand: - return False - total_volume = sum( - component['volume'] for component in individual.individual['Energy Storage Components'] - if component['volume'] is not None - ) - max_storage_volume = individual.available_space * 0.1 - if total_volume > max_storage_volume: - return False - return True - - def nsga2_selection(self, population, fronts, crowding_distances): - new_population = [] - i = 0 - while len(new_population) + len(fronts[i]) <= self.population_size: - for index in fronts[i]: - # Skip individuals with infinite fitness values to avoid propagation - if not math.isinf(self.population[index].individual['fitness_score'][0]) and \ - not math.isinf(self.population[index].individual['fitness_score'][1]): - new_population.append(population[index]) - i += 1 - if i >= len(fronts): - break - if len(new_population) < self.population_size and i < len(fronts): - sorted_front = sorted(fronts[i], key=lambda x: crowding_distances[i][x], reverse=True) - for index in sorted_front: - if len(new_population) < self.population_size: - if not math.isinf(self.population[index].individual['fitness_score'][0]) and \ - not math.isinf(self.population[index].individual['fitness_score'][1]): - new_population.append(population[index]) - else: - break - return new_population - - def fast_non_dominated_sort(self): - population_size = self.population_size - s = [[] for _ in range(population_size)] - front = [[]] - n = [0] * population_size - rank = [0] * population_size - for p in range(population_size): - s[p] = [] - n[p] = 0 - for q in range(population_size): - if self.dominates(self.population[p], self.population[q]): - s[p].append(q) - elif self.dominates(self.population[q], self.population[p]): - n[p] += 1 - if n[p] == 0: - rank[p] = 0 - front[0].append(p) - i = 0 - while front[i]: - next_front = set() - for p in front[i]: - for q in s[p]: - n[q] -= 1 - if n[q] == 0: - rank[q] = i + 1 - next_front.add(q) - i += 1 - front.append(list(next_front)) - del front[-1] - return front - - @staticmethod - def dominates(individual1, individual2): - lcc1, lce1 = individual1.individual['fitness_score'] - lcc2, lce2 = individual2.individual['fitness_score'] - return (lcc1 <= lcc2 and lce1 <= lce2) and (lcc1 < lcc2 or lce1 < lce2) - - def calculate_crowding_distance(self, front): - crowding_distance = [0] * len(self.population) - for objective in ['lcc', 'total_energy_consumption']: - sorted_front = sorted(front, key=lambda x: self.population[x].individual[objective]) - # Set distances to finite large numbers rather than `inf` - crowding_distance[sorted_front[0]] = float(1e9) - crowding_distance[sorted_front[-1]] = float(1e9) - objective_min = self.population[sorted_front[0]].individual[objective] - objective_max = self.population[sorted_front[-1]].individual[objective] - if objective_max != objective_min: - for i in range(1, len(sorted_front) - 1): - crowding_distance[sorted_front[i]] += ( - (self.population[sorted_front[i + 1]].individual[objective] - - self.population[sorted_front[i - 1]].individual[objective]) / - (objective_max - objective_min)) - return crowding_distance - - def crossover(self, parent1, parent2): - """ - Crossover between two parents to produce two children. - swaps generation components and storage components between the two parents with a 50% chance. - :param parent1: First parent individual. - :param parent2: second parent individual. - :return: Two child individuals (child1 and child2). - """ - if random.random() < self.crossover_rate: - # Deep copy of the parents to create children - child1, child2 = copy.deepcopy(parent1), copy.deepcopy(parent2) - # Crossover for Generation Components - for i in range(len(parent1.individual['Generation Components'])): - if random.random() < 0.5: - # swap the entire generation component - child1.individual['Generation Components'][i], child2.individual['Generation Components'][i] = ( - child2.individual['Generation Components'][i], - child1.individual['Generation Components'][i] - ) - - # Crossover for Energy storage Components - for i in range(len(parent1.individual['Energy Storage Components'])): - if random.random() < 0.5: - # swap the entire storage component - child1.individual['Energy Storage Components'][i], child2.individual['Energy Storage Components'][i] = ( - child2.individual['Energy Storage Components'][i], - child1.individual['Energy Storage Components'][i] - ) - - return child1, child2 - else: - # If crossover does not happen, return copies of the original parents - return copy.deepcopy(parent1), copy.deepcopy(parent2) - - def mutate(self, individual, building, energy_system): - """ - Mutates the individual's generation and storage components. - - - `individual`: The individual to mutate (contains generation and storage components). - - `building`: Building data that contains constraints such as peak heating load and available space. - - Returns the mutated individual. - """ - design_period_energy_demands = self.design_period_identification(building) - # Mutate Generation Components - for generation_component in individual['Generation Components']: - if random.random() < self.mutation_rate: - if (generation_component['nominal_heating_efficiency'] is not None and cte.HEATING or cte.DOMESTIC_HOT_WATER in - energy_system.demand_types): - # Mutate heating capacity - if cte.HEATING in energy_system.demand_types: - generation_component['heating_capacity'] = random.uniform( - 0, max(design_period_energy_demands[cte.HEATING]['demands']) / cte.WATTS_HOUR_TO_JULES) - else: - generation_component['heating_capacity'] = random.uniform( - 0, max(design_period_energy_demands[cte.DOMESTIC_HOT_WATER]['demands']) / cte.WATTS_HOUR_TO_JULES) - if generation_component['nominal_cooling_efficiency'] is not None and cte.COOLING in energy_system.demand_types: - # Mutate cooling capacity - generation_component['cooling_capacity'] = random.uniform( - 0, max(design_period_energy_demands[cte.COOLING]['demands']) / cte.WATTS_HOUR_TO_JULES) - # Mutate storage Components - for storage_component in individual['Energy Storage Components']: - if random.random() < self.mutation_rate: - if storage_component['type'] == f'{cte.THERMAL}_storage': - # Mutate the volume of thermal storage - max_available_space = 0.01 * building.volume / building.storeys_above_ground - storage_component['volume'] = random.uniform(0, max_available_space) - if storage_component['heating_coil_capacity'] is not None: - if cte.HEATING in energy_system.demand_types: - storage_component['heating_coil_capacity'] = random.uniform(0, max( - design_period_energy_demands[cte.HEATING]['demands']) / cte.WATTS_HOUR_TO_JULES) - else: - storage_component['heating_coil_capacity'] = random.uniform(0, max( - design_period_energy_demands[cte.DOMESTIC_HOT_WATER]['demands']) / cte.WATTS_HOUR_TO_JULES) - return individual - - def solve_ga(self, building, energy_system): - self.initialize_population(building, energy_system) - for individual in self.population: - individual.score_evaluation() - pareto_population = [] - - for generation in range(1, self.generations + 1): - print(f"Generation {generation}") - fronts = self.fast_non_dominated_sort() - - # Ensure the front calculation is valid - if not fronts or not fronts[0]: - print("Warning: No valid non-dominated front found.") - continue - - # Calculate crowding distances and select individuals - crowding_distances = [self.calculate_crowding_distance(front) for front in fronts] - self.population = self.nsga2_selection(self.population, fronts, crowding_distances) - - # Add only valid indices to pareto_population - pareto_population.extend([self.population[i] for i in fronts[0] if i < len(self.population)]) - - # Check population bounds - if len(pareto_population) > self.population_size: - pareto_population = pareto_population[:self.population_size] - - # Generate the next population through crossover and mutation - next_population = [] - while len(next_population) < self.population_size: - parent1, parent2 = random.choice(self.population), random.choice(self.population) - child1, child2 = self.crossover(parent1, parent2) - self.mutate(child1.individual, building, energy_system) - self.mutate(child2.individual, building, energy_system) - child1.score_evaluation() - child2.score_evaluation() - next_population.extend([child1, child2]) - - self.population = next_population[:self.population_size] - - # Ensure pareto_population contains the correct non-dominated individuals before TOPSIS - if not pareto_population: - print("No Pareto solutions found during optimization.") - return None - - # Recalculate pareto front indices based on updated pareto_population - fronts = self.fast_non_dominated_sort() - pareto_front_indices = fronts[0] if fronts else [] - pareto_front_indices = [i for i in pareto_front_indices if i < len(pareto_population)] - print(f"Final pareto_front_indices: {pareto_front_indices}, pareto_population size: {len(pareto_population)}") - - if not pareto_front_indices: - print("No valid solution found after TOPSIS due to empty pareto front indices.") - return None - - global_pareto_front = [pareto_population[i] for i in pareto_front_indices] - - # Get the top N solutions with TOPSIS - top_n = 3 # Adjust this value based on how many top solutions you want to explore - self.best_solution = self.topsis_decision_making(global_pareto_front, top_n=top_n) - - # Print the top N solutions - if self.best_solution: - print("Top solutions after TOPSIS:") - for idx, solution in enumerate(self.best_solution, 1): - print(f"Solution {idx}: LCC = {solution.individual['lcc']}, " - f"LCE = {solution.individual['total_energy_consumption']}") - else: - print("No valid solutions found after TOPSIS.") - - if pareto_population: - self.plot_pareto_front(pareto_population) - - return self.best_solution - - @staticmethod - def design_period_identification(building): - def get_start_end_indices(max_day_index, total_days): - if max_day_index > 0 and max_day_index < total_days - 1: - start_index = (max_day_index - 1) * 24 - end_index = (max_day_index + 2) * 24 - elif max_day_index == 0: - start_index = 0 - end_index = (max_day_index + 2) * 24 - else: - start_index = (max_day_index - 1) * 24 - end_index = total_days * 24 - return start_index, end_index - - # Calculate daily demands - heating_daily_demands = [sum(building.heating_demand[cte.HOUR][i:i + 24]) for i in - range(0, len(building.heating_demand[cte.HOUR]), 24)] - cooling_daily_demands = [sum(building.cooling_demand[cte.HOUR][i:i + 24]) for i in - range(0, len(building.cooling_demand[cte.HOUR]), 24)] - dhw_daily_demands = [sum(building.domestic_hot_water_heat_demand[cte.HOUR][i:i + 24]) for i in - range(0, len(building.domestic_hot_water_heat_demand[cte.HOUR]), 24)] - # Get the day with maximum demand for each type - heating_max_day = heating_daily_demands.index(max(heating_daily_demands)) - cooling_max_day = cooling_daily_demands.index(max(cooling_daily_demands)) - dhw_max_day = dhw_daily_demands.index(max(dhw_daily_demands)) - # Get the start and end indices for each demand type - heating_start, heating_end = get_start_end_indices(heating_max_day, len(heating_daily_demands)) - cooling_start, cooling_end = get_start_end_indices(cooling_max_day, len(cooling_daily_demands)) - dhw_start, dhw_end = get_start_end_indices(dhw_max_day, len(dhw_daily_demands)) - # Return the design period energy demands - return { - f'{cte.HEATING}': {'demands': building.heating_demand[cte.HOUR][heating_start:heating_end], - 'start_index': heating_start, 'end_index': heating_end}, - f'{cte.COOLING}': {'demands': building.cooling_demand[cte.HOUR][cooling_start:cooling_end], - 'start_index': cooling_start, 'end_index': cooling_end}, - f'{cte.DOMESTIC_HOT_WATER}': {'demands': building.domestic_hot_water_heat_demand[cte.HOUR][dhw_start:dhw_end], - 'start_index': dhw_start, 'end_index': dhw_end} - } - - def topsis_decision_making(self, pareto_front, top_n=5): - """ - Perform TOPSIS decision-making to choose the best solutions from the Pareto front. - - :param pareto_front: List of individuals in the Pareto front - :param top_n: Number of top solutions to select based on TOPSIS ranking - :return: List of top N individuals based on TOPSIS ranking - """ - # Filter out infinite values from the pareto front before processing - pareto_front = [ind for ind in pareto_front if all(math.isfinite(v) for v in ind.individual['fitness_score'])] - if not pareto_front: - return None - - # Step 1: Normalize the objective functions (cost and energy consumption) - min_lcc = min([ind.individual['lcc'] for ind in pareto_front]) - max_lcc = max([ind.individual['lcc'] for ind in pareto_front]) - min_lce = min([ind.individual['total_energy_consumption'] for ind in pareto_front]) - max_lce = max([ind.individual['total_energy_consumption'] for ind in pareto_front]) - - normalized_pareto_front = [] - for ind in pareto_front: - normalized_lcc = (ind.individual['lcc'] - min_lcc) / (max_lcc - min_lcc) if max_lcc > min_lcc else 0 - normalized_lce = (ind.individual['total_energy_consumption'] - min_lce) / ( - max_lce - min_lce) if max_lce > min_lce else 0 - normalized_pareto_front.append((ind, normalized_lcc, normalized_lce)) - - # Step 2: Calculate the ideal and worst solutions - ideal_solution = (0, 0) # Ideal is minimum LCC and minimum LCE (0, 0 after normalization) - worst_solution = (1, 1) # Worst is maximum LCC and maximum LCE (1, 1 after normalization) - - # Step 3: Calculate the distance to the ideal and worst solutions - best_distances = [] - worst_distances = [] - for ind, normalized_lcc, normalized_lce in normalized_pareto_front: - distance_to_ideal = math.sqrt( - (normalized_lcc - ideal_solution[0]) ** 2 + (normalized_lce - ideal_solution[1]) ** 2) - distance_to_worst = math.sqrt( - (normalized_lcc - worst_solution[0]) ** 2 + (normalized_lce - worst_solution[1]) ** 2) - best_distances.append(distance_to_ideal) - worst_distances.append(distance_to_worst) - - # Step 4: Calculate relative closeness to the ideal solution - similarity = [worst / (best + worst) for best, worst in zip(best_distances, worst_distances)] - - # Step 5: Select the top N individuals with the highest similarity scores - top_indices = sorted(range(len(similarity)), key=lambda i: similarity[i], reverse=True)[:top_n] - top_solutions = [pareto_front[i] for i in top_indices] - - # Plot the similarity scores - self.plot_topsis_similarity(similarity) - - return top_solutions - - @staticmethod - def plot_topsis_similarity(similarity): - """ - Plot the TOPSIS similarity scores for visualization. - - :param similarity: List of similarity scores for each individual in the Pareto front - """ - plt.figure(figsize=(10, 6)) - plt.plot(range(len(similarity)), similarity, 'bo-', label='TOPSIS Similarity Scores') - plt.xlabel('Pareto Front Solution Index') - plt.ylabel('Similarity Score') - plt.title('TOPSIS Similarity Scores for Pareto Front Solutions') - plt.legend() - plt.grid(True) - plt.show() - - @staticmethod - def plot_pareto_front(pareto_population): - # Extract LCC and LCE for plotting - lcc_values = [individual.individual['lcc'] for individual in pareto_population] - lce_values = [individual.individual['total_energy_consumption'] for individual in pareto_population] - plt.figure(figsize=(10, 6)) - plt.scatter(lcc_values, lce_values, color='blue', label='Pareto Front', alpha=0.6, edgecolors='w', s=80) - plt.title('Pareto Front for Life Cycle Cost vs Life Cycle Energy') - plt.xlabel('Life Cycle Cost (LCC)') - plt.ylabel('Life Cycle Energy (LCE)') - plt.grid(True) - plt.legend() - plt.show() diff --git a/energy_system_modelling_package/energy_system_modelling_factories/system_sizing_methods/genetic_algorithm/multi_objective_genetic_algorithm_rethinking.py b/energy_system_modelling_package/energy_system_modelling_factories/system_sizing_methods/genetic_algorithm/multi_objective_genetic_algorithm_rethinking.py deleted file mode 100644 index 23ef7ff2..00000000 --- a/energy_system_modelling_package/energy_system_modelling_factories/system_sizing_methods/genetic_algorithm/multi_objective_genetic_algorithm_rethinking.py +++ /dev/null @@ -1,94 +0,0 @@ -import copy -import math -import random -import hub.helpers.constants as cte -from energy_system_modelling_package.energy_system_modelling_factories.system_sizing_methods.genetic_algorithm.individual import \ - Individual -import matplotlib.pyplot as plt -import time - - -class MultiObjectiveGeneticAlgorithm: - def __init__(self, population_size=100, generations=50, crossover_rate=0.8, mutation_rate=0.1, - optimization_scenario=None, output_path=None): - self.population_size = population_size - self.population = [] - self.generations = generations - self.crossover_rate = crossover_rate - self.mutation_rate = mutation_rate - self.optimization_scenario = optimization_scenario - self.list_of_solutions = [] - self.best_solution = None - self.best_solution_generation = None - self.output_path = output_path - -# Initialize Population - def initialize_population(self, building, energy_system): - design_period_start_time = time.time() - design_period_energy_demands = self.design_period_identification(building) - design_period_time = time.time() - design_period_start_time - print(f"design period identification took {design_period_time:.2f} seconds") - initializing_time_start = time.time() - attempts = 0 - max_attempts = self.population_size * 20 - while len(self.population) < self.population_size and attempts < max_attempts: - individual = Individual(building=building, - energy_system=energy_system, - design_period_energy_demands=design_period_energy_demands, - optimization_scenario=self.optimization_scenario) - individual.initialization() - individual.score_evaluation() - attempts += 1 - if individual.feasibility: - self.population.append(individual) - if len(self.population) < self.population_size: - raise RuntimeError(f"Could not generate a feasible population of size {self.population_size}. " - f"Only {len(self.population)} feasible individuals were generated.") - initializing_time = time.time() - initializing_time_start - print(f"initializing took {initializing_time:.2f} seconds") - - - @staticmethod - def design_period_identification(building): - def get_start_end_indices(max_day_index, total_days): - if max_day_index > 0 and max_day_index < total_days - 1: - start_index = (max_day_index - 1) * 24 - end_index = (max_day_index + 2) * 24 - elif max_day_index == 0: - start_index = 0 - end_index = (max_day_index + 2) * 24 - else: - start_index = (max_day_index - 1) * 24 - end_index = total_days * 24 - return start_index, end_index - - # Calculate daily demands - heating_daily_demands = [sum(building.heating_demand[cte.HOUR][i:i + 24]) for i in - range(0, len(building.heating_demand[cte.HOUR]), 24)] - cooling_daily_demands = [sum(building.cooling_demand[cte.HOUR][i:i + 24]) for i in - range(0, len(building.cooling_demand[cte.HOUR]), 24)] - dhw_daily_demands = [sum(building.domestic_hot_water_heat_demand[cte.HOUR][i:i + 24]) for i in - range(0, len(building.domestic_hot_water_heat_demand[cte.HOUR]), 24)] - # Get the day with maximum demand for each type - heating_max_day = heating_daily_demands.index(max(heating_daily_demands)) - cooling_max_day = cooling_daily_demands.index(max(cooling_daily_demands)) - dhw_max_day = dhw_daily_demands.index(max(dhw_daily_demands)) - # Get the start and end indices for each demand type - heating_start, heating_end = get_start_end_indices(heating_max_day, len(heating_daily_demands)) - cooling_start, cooling_end = get_start_end_indices(cooling_max_day, len(cooling_daily_demands)) - dhw_start, dhw_end = get_start_end_indices(dhw_max_day, len(dhw_daily_demands)) - # Return the design period energy demands - return { - f'{cte.HEATING}': {'demands': building.heating_demand[cte.HOUR][heating_start:heating_end], - 'start_index': heating_start, 'end_index': heating_end}, - f'{cte.COOLING}': {'demands': building.cooling_demand[cte.HOUR][cooling_start:cooling_end], - 'start_index': cooling_start, 'end_index': cooling_end}, - f'{cte.DOMESTIC_HOT_WATER}': {'demands': building.domestic_hot_water_heat_demand[cte.HOUR][dhw_start:dhw_end], - 'start_index': dhw_start, 'end_index': dhw_end} - } - - def solve_ga(self, building, energy_system): - self.initialize_population(building, energy_system) - for individual in self.population: - print(individual.individual) - print([ind.fitness_score for ind in self.population]) diff --git a/energy_system_modelling_package/energy_system_modelling_factories/system_sizing_methods/genetic_algorithm/single_objective_genetic_algorithm.py b/energy_system_modelling_package/energy_system_modelling_factories/system_sizing_methods/genetic_algorithm/single_objective_genetic_algorithm.py deleted file mode 100644 index b7e5f7c3..00000000 --- a/energy_system_modelling_package/energy_system_modelling_factories/system_sizing_methods/genetic_algorithm/single_objective_genetic_algorithm.py +++ /dev/null @@ -1,342 +0,0 @@ -import copy -import math -import random -import hub.helpers.constants as cte -from energy_system_modelling_package.energy_system_modelling_factories.system_sizing_methods.genetic_algorithm.individual import \ - Individual - - -class SingleObjectiveGeneticAlgorithm: - def __init__(self, population_size=100, generations=20, crossover_rate=0.8, mutation_rate=0.1, - optimization_scenario=None, output_path=None): - self.population_size = population_size - self.population = [] - self.generations = generations - self.crossover_rate = crossover_rate - self.mutation_rate = mutation_rate - self.optimization_scenario = optimization_scenario - self.list_of_solutions = [] - self.best_solution = None - self.best_solution_generation = None - self.output_path = output_path - - # Initialize Population - def initialize_population(self, building, energy_system): - """ - Initialize a population of individuals with feasible configurations for optimizing the sizes of - generation and storage components of an energy system. - - :param building: Building object with associated data - :param energy_system: Energy system to optimize - """ - design_period_energy_demands = self.design_period_identification(building) - attempts = 0 # Track attempts to avoid an infinite loop in rare cases - max_attempts = self.population_size * 5 - - while len(self.population) < self.population_size and attempts < max_attempts: - individual = Individual(building=building, - energy_system=energy_system, - design_period_energy_demands=design_period_energy_demands, - optimization_scenario=self.optimization_scenario) - - individual.initialization() - attempts += 1 - - # Enhanced feasibility check - if self.initial_population_feasibility_check(individual, energy_system.demand_types, design_period_energy_demands): - self.population.append(individual) - - # Raise an error or print a warning if the population size goal is not met after max_attempts - if len(self.population) < self.population_size: - raise RuntimeError(f"Could not generate a feasible population of size {self.population_size}. " - f"Only {len(self.population)} feasible individuals were generated.") - - @staticmethod - def initial_population_feasibility_check(individual, demand_types, design_period_demands): - """ - Check if the individual meets basic feasibility requirements for heating, cooling, and DHW capacities - and storage volume. - - :param individual: Individual to check - :param demand_types: List of demand types (e.g., heating, cooling, DHW) - :param design_period_demands: Design period demand values for heating, cooling, and DHW - :return: True if feasible, False otherwise - """ - # Calculate total heating and cooling capacities - total_heating_capacity = sum( - component['heating_capacity'] for component in individual.individual['Generation Components'] - if component['heating_capacity'] is not None - ) - total_cooling_capacity = sum( - component['cooling_capacity'] for component in individual.individual['Generation Components'] - if component['cooling_capacity'] is not None - ) - - # Maximum demands for each demand type (converted to kW) - max_heating_demand = max(design_period_demands[cte.HEATING]['demands']) / cte.WATTS_HOUR_TO_JULES - max_cooling_demand = max(design_period_demands[cte.COOLING]['demands']) / cte.WATTS_HOUR_TO_JULES - max_dhw_demand = max(design_period_demands[cte.DOMESTIC_HOT_WATER]['demands']) / cte.WATTS_HOUR_TO_JULES - - # Check heating capacity feasibility - if cte.HEATING in demand_types and total_heating_capacity < 0.5 * max_heating_demand: - return False - - # Check DHW capacity feasibility - if cte.DOMESTIC_HOT_WATER in demand_types and total_heating_capacity < 0.5 * max_dhw_demand: - return False - - # Check cooling capacity feasibility - if cte.COOLING in demand_types and total_cooling_capacity < 0.5 * max_cooling_demand: - return False - - # Check storage volume feasibility - total_volume = sum( - component['volume'] for component in individual.individual['Energy Storage Components'] - if component['volume'] is not None - ) - # Limit storage to 10% of building's available space - max_storage_volume = individual.available_space * 0.1 - if total_volume > max_storage_volume: - return False - - return True # Feasible if all checks are passed - - def order_population(self): - """ - ordering the population based on the fitness score in ascending order - :return: - """ - self.population = sorted(self.population, key=lambda x: x.fitness_score) - - def tournament_selection(self): - selected = [] - for _ in range(len(self.population)): - i, j = random.sample(range(self.population_size), 2) - if self.population[i].individual['fitness_score'] < self.population[j].individual['fitness_score']: - selected.append(copy.deepcopy(self.population[i])) - else: - selected.append(copy.deepcopy(self.population[j])) - return selected - - def crossover(self, parent1, parent2): - """ - Crossover between two parents to produce two children. - - swaps generation components and storage components between the two parents with a 50% chance. - - :param parent1: First parent individual. - :param parent2: second parent individual. - :return: Two child individuals (child1 and child2). - """ - if random.random() < self.crossover_rate: - # Deep copy of the parents to create children - child1, child2 = copy.deepcopy(parent1), copy.deepcopy(parent2) - # Crossover for Generation Components - for i in range(len(parent1.individual['Generation Components'])): - if random.random() < 0.5: - # swap the entire generation component - child1.individual['Generation Components'][i], child2.individual['Generation Components'][i] = ( - child2.individual['Generation Components'][i], - child1.individual['Generation Components'][i] - ) - - # Crossover for Energy storage Components - for i in range(len(parent1.individual['Energy Storage Components'])): - if random.random() < 0.5: - # swap the entire storage component - child1.individual['Energy Storage Components'][i], child2.individual['Energy Storage Components'][i] = ( - child2.individual['Energy Storage Components'][i], - child1.individual['Energy Storage Components'][i] - ) - - return child1, child2 - else: - # If crossover does not happen, return copies of the original parents - return copy.deepcopy(parent1), copy.deepcopy(parent2) - - def mutate(self, individual, building, energy_system): - """ - Mutates the individual's generation and storage components. - - - `individual`: The individual to mutate (contains generation and storage components). - - `building`: Building data that contains constraints such as peak heating load and available space. - - Returns the mutated individual. - """ - design_period_energy_demands = self.design_period_identification(building) - # Mutate Generation Components - for generation_component in individual['Generation Components']: - if random.random() < self.mutation_rate: - if (generation_component['nominal_heating_efficiency'] is not None and cte.HEATING or cte.DOMESTIC_HOT_WATER in - energy_system.demand_types): - # Mutate heating capacity - if cte.HEATING in energy_system.demand_types: - generation_component['heating_capacity'] = random.uniform( - 0, max(design_period_energy_demands[cte.HEATING]['demands']) / cte.WATTS_HOUR_TO_JULES) - else: - generation_component['heating_capacity'] = random.uniform( - 0, max(design_period_energy_demands[cte.DOMESTIC_HOT_WATER]['demands']) / cte.WATTS_HOUR_TO_JULES) - if generation_component['nominal_cooling_efficiency'] is not None and cte.COOLING in energy_system.demand_types: - # Mutate cooling capacity - generation_component['cooling_capacity'] = random.uniform( - 0, max(design_period_energy_demands[cte.COOLING]['demands']) / cte.WATTS_HOUR_TO_JULES) - # Mutate storage Components - for storage_component in individual['Energy Storage Components']: - if random.random() < self.mutation_rate: - if storage_component['type'] == f'{cte.THERMAL}_storage': - # Mutate the volume of thermal storage - max_available_space = 0.01 * building.volume / building.storeys_above_ground - storage_component['volume'] = random.uniform(0, max_available_space) - if storage_component['heating_coil_capacity'] is not None: - if cte.HEATING in energy_system.demand_types: - storage_component['heating_coil_capacity'] = random.uniform(0, max( - design_period_energy_demands[cte.HEATING]['demands']) / cte.WATTS_HOUR_TO_JULES) - else: - storage_component['heating_coil_capacity'] = random.uniform(0, max( - design_period_energy_demands[cte.DOMESTIC_HOT_WATER]['demands']) / cte.WATTS_HOUR_TO_JULES) - return individual - - def solve_ga(self, building, energy_system): - """ - solving GA for a single energy system. Here are the steps: - 1. Initialize population using the "initialize_population" method in this class. - 2. Evaluate the initial population using the "score_evaluation" method in the Individual class. - 3. sort population based on fitness score. - 4. Repeat selection, crossover, and mutation for a fixed number of generations. - 5. Track the best solution found during the optimization process. - - :param building: Building object for the energy system. - :param energy_system: Energy system to optimize. - :return: Best solution after running the GA. - """ - # step 1: Initialize the population - self.initialize_population(building, energy_system) - # step 2: Evaluate the initial population - for individual in self.population: - individual.score_evaluation() - # step 3: Order population based on fitness scores - self.order_population() - print([individual.fitness_score for individual in self.population]) - # Track the best solution - self.best_solution = self.population[0] - self.best_solution_generation = 0 - self.list_of_solutions.append(copy.deepcopy(self.best_solution.individual)) - # step 4: Run GA for a fixed number of generations - for generation in range(1, self.generations): - print(f"Generation {generation}") - # selection (using tournament selection) - selected_population = self.tournament_selection() - # Create the next generation through crossover and mutation - next_population = [] - for i in range(0, self.population_size, 2): - parent1 = selected_population[i] - parent2 = selected_population[i + 1] if (i + 1) < len(selected_population) else selected_population[0] - # step 5: Apply crossover - child1, child2 = self.crossover(parent1, parent2) - # step 6: Apply mutation - self.mutate(child1.individual, building, energy_system) - self.mutate(child2.individual, building, energy_system) - # step 7: Evaluate the children - child1.score_evaluation() - child2.score_evaluation() - next_population.extend([child1, child2]) - # Replace old population with the new one - self.population = next_population - # step 8: sort the new population based on fitness - self.order_population() - print([individual.fitness_score for individual in self.population]) - # Track the best solution found in this generation - if self.population[0].individual['fitness_score'] < self.best_solution.individual['fitness_score']: - self.best_solution = self.population[0] - self.best_solution_generation = generation - # store the best solution in the list of solutions - self.list_of_solutions.append(copy.deepcopy(self.population[0].individual)) - print(f"Best solution found in generation {self.best_solution_generation}") - print(f"Best solution: {self.best_solution.individual}") - return self.best_solution - - @staticmethod - def topsis_decision_making(pareto_front): - """ - Perform TOPSIS decision-making to choose the best solution from the Pareto front. - - :param pareto_front: List of individuals in the Pareto front - :return: The best individual based on TOPSIS ranking - """ - # Step 1: Normalize the objective functions (cost and energy consumption) - min_lcc = min([ind.individual['lcc'] for ind in pareto_front]) - max_lcc = max([ind.individual['lcc'] for ind in pareto_front]) - min_lce = min([ind.individual['total_energy_consumption'] for ind in pareto_front]) - max_lce = max([ind.individual['total_energy_consumption'] for ind in pareto_front]) - - normalized_pareto_front = [] - for ind in pareto_front: - normalized_lcc = (ind.individual['lcc'] - min_lcc) / (max_lcc - min_lcc) if max_lcc > min_lcc else 0 - normalized_lce = (ind.individual['total_energy_consumption'] - min_lce) / ( - max_lce - min_lce) if max_lce > min_lce else 0 - normalized_pareto_front.append((ind, normalized_lcc, normalized_lce)) - - # Step 2: Calculate the ideal and worst solutions - ideal_solution = (0, 0) # Ideal is minimum LCC and minimum LCE (0, 0 after normalization) - worst_solution = (1, 1) # Worst is maximum LCC and maximum LCE (1, 1 after normalization) - - # Step 3: Calculate the distance to the ideal and worst solutions - best_distances = [] - worst_distances = [] - - for ind, normalized_lcc, normalized_lce in normalized_pareto_front: - distance_to_ideal = math.sqrt( - (normalized_lcc - ideal_solution[0]) ** 2 + (normalized_lce - ideal_solution[1]) ** 2) - distance_to_worst = math.sqrt( - (normalized_lcc - worst_solution[0]) ** 2 + (normalized_lce - worst_solution[1]) ** 2) - best_distances.append(distance_to_ideal) - worst_distances.append(distance_to_worst) - - # Step 4: Calculate relative closeness to the ideal solution - similarity = [worst / (best + worst) for best, worst in zip(best_distances, worst_distances)] - - # Step 5: Select the individual with the highest similarity score - best_index = similarity.index(max(similarity)) - best_solution = pareto_front[best_index] - - return best_solution - - @staticmethod - def design_period_identification(building): - def get_start_end_indices(max_day_index, total_days): - if max_day_index > 0 and max_day_index < total_days - 1: - start_index = (max_day_index - 1) * 24 - end_index = (max_day_index + 2) * 24 - elif max_day_index == 0: - start_index = 0 - end_index = (max_day_index + 2) * 24 - else: - start_index = (max_day_index - 1) * 24 - end_index = total_days * 24 - return start_index, end_index - - # Calculate daily demands - heating_daily_demands = [sum(building.heating_demand[cte.HOUR][i:i + 24]) for i in - range(0, len(building.heating_demand[cte.HOUR]), 24)] - cooling_daily_demands = [sum(building.cooling_demand[cte.HOUR][i:i + 24]) for i in - range(0, len(building.cooling_demand[cte.HOUR]), 24)] - dhw_daily_demands = [sum(building.domestic_hot_water_heat_demand[cte.HOUR][i:i + 24]) for i in - range(0, len(building.domestic_hot_water_heat_demand[cte.HOUR]), 24)] - # Get the day with maximum demand for each type - heating_max_day = heating_daily_demands.index(max(heating_daily_demands)) - cooling_max_day = cooling_daily_demands.index(max(cooling_daily_demands)) - dhw_max_day = dhw_daily_demands.index(max(dhw_daily_demands)) - # Get the start and end indices for each demand type - heating_start, heating_end = get_start_end_indices(heating_max_day, len(heating_daily_demands)) - cooling_start, cooling_end = get_start_end_indices(cooling_max_day, len(cooling_daily_demands)) - dhw_start, dhw_end = get_start_end_indices(dhw_max_day, len(dhw_daily_demands)) - # Return the design period energy demands - return { - f'{cte.HEATING}': {'demands': building.heating_demand[cte.HOUR][heating_start:heating_end], - 'start_index': heating_start, 'end_index': heating_end}, - f'{cte.COOLING}': {'demands': building.cooling_demand[cte.HOUR][cooling_start:cooling_end], - 'start_index': cooling_start, 'end_index': cooling_end}, - f'{cte.DOMESTIC_HOT_WATER}': {'demands': building.domestic_hot_water_heat_demand[cte.HOUR][dhw_start:dhw_end], - 'start_index': dhw_start, 'end_index': dhw_end} - } - diff --git a/energy_system_modelling_package/energy_system_modelling_factories/system_sizing_methods/optimal_sizing.py b/energy_system_modelling_package/energy_system_modelling_factories/system_sizing_methods/optimal_sizing.py deleted file mode 100644 index 2127dcc4..00000000 --- a/energy_system_modelling_package/energy_system_modelling_factories/system_sizing_methods/optimal_sizing.py +++ /dev/null @@ -1,42 +0,0 @@ -import hub.helpers.constants as cte -from energy_system_modelling_package.energy_system_modelling_factories.system_sizing_methods.genetic_algorithm.single_objective_genetic_algorithm import \ - SingleObjectiveGeneticAlgorithm - - -class OptimalSizing: - def __init__(self, city, optimization_scenario): - self.city = city - self.optimization_scenario = optimization_scenario - - def enrich_buildings(self): - for building in self.city.buildings: - for energy_system in building.energy_systems: - if len(energy_system.generation_systems) == 1 and energy_system.generation_systems[0].energy_storage_systems is None: - if energy_system.generation_systems[0].system_type == cte.PHOTOVOLTAIC: - pass - else: - if cte.HEATING in energy_system.demand_types: - if cte.DOMESTIC_HOT_WATER in energy_system.demand_types: - design_load = max([building.heating_demand[cte.HOUR][i] + - building.domestic_hot_water_heat_demand[cte.HOUR][i] for i in - range(len(building.heating_demand))]) / cte.WATTS_HOUR_TO_JULES - else: - design_load = building.heating_peak_load[cte.YEAR][0] - energy_system.generation_systems[0].nominal_heat_output = design_load - elif cte.COOLING in energy_system.demand_types: - energy_system.generation_systems[0].nominal_cooling_output = building.cooling_peak_load[cte.YEAR][0] - else: - optimized_system = SingleObjectiveGeneticAlgorithm(optimization_scenario=self.optimization_scenario).solve_ga(building, energy_system) - for generation_system in energy_system.generation_systems: - system_type = generation_system.system_type - for generation_component in optimized_system.individual['Generation Components']: - if generation_component['type'] == system_type: - generation_system.nominal_heat_output = generation_component['heating_capacity'] - generation_system.nominal_cooling_output = generation_component['cooling_capacity'] - if generation_system.energy_storage_systems is not None: - for storage_system in generation_system.energy_storage_systems: - storage_type = f'{storage_system.type_energy_stored}_storage' - for storage_component in optimized_system.individual['Energy Storage Components']: - if storage_component['type'] == storage_type: - storage_system.nominal_capacity = storage_component['capacity'] - storage_system.volume = storage_component['volume'] 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 deleted file mode 100644 index 1f1fe74a..00000000 --- a/energy_system_modelling_package/energy_system_modelling_factories/system_sizing_methods/peak_load_sizing.py +++ /dev/null @@ -1,99 +0,0 @@ -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/energy_system_modelling_package/energy_system_retrofit/energy_system_retrofit_report.py b/energy_system_modelling_package/energy_system_retrofit/energy_system_retrofit_report.py deleted file mode 100644 index 1c211eba..00000000 --- a/energy_system_modelling_package/energy_system_retrofit/energy_system_retrofit_report.py +++ /dev/null @@ -1,595 +0,0 @@ -import os -import hub.helpers.constants as cte -import matplotlib.pyplot as plt -from matplotlib import cm -from energy_system_modelling_package.report_creation import LatexReport -from matplotlib.ticker import MaxNLocator -import numpy as np -from pathlib import Path -import glob - - -class EnergySystemRetrofitReport: - def __init__(self, city, output_path, retrofit_scenario, current_status_energy_consumption_data, - retrofitted_energy_consumption_data, current_status_lcc_data, retrofitted_lcc_data): - self.city = city - self.current_status_data = current_status_energy_consumption_data - self.retrofitted_data = retrofitted_energy_consumption_data - self.current_status_lcc = current_status_lcc_data - self.retrofitted_lcc = retrofitted_lcc_data - self.output_path = output_path - self.content = [] - self.retrofit_scenario = retrofit_scenario - self.report = LatexReport('energy_system_retrofit_report', - 'Energy System Retrofit Report', self.retrofit_scenario, output_path) - self.system_schemas_path = (Path(__file__).parent.parent.parent / 'hub' / 'data' / 'energy_systems' / 'schemas') - self.charts_path = Path(output_path) / 'charts' - self.charts_path.mkdir(parents=True, exist_ok=True) - files = glob.glob(f'{self.charts_path}/*') - for file in files: - os.remove(file) - - def building_energy_info(self): - table_data = [ - ["Building Name", "Year of Construction", "function", "Yearly Heating Demand (MWh)", - "Yearly Cooling Demand (MWh)", "Yearly DHW Demand (MWh)", "Yearly Electricity Demand (MWh)"] - ] - intensity_table_data = [["Building Name", "Total Floor Area $m^2$", "Heating Demand Intensity kWh/ $m^2$", - "Cooling Demand Intensity kWh/ $m^2$", "Electricity Intensity kWh/ $m^2$"]] - peak_load_data = [["Building Name", "Heating Peak Load (kW)", "Cooling Peak Load (kW)", - "Domestic Hot Water Peak Load (kW)"]] - - for building in self.city.buildings: - total_floor_area = 0 - for zone in building.thermal_zones_from_internal_zones: - total_floor_area += zone.total_floor_area - building_data = [ - building.name, - str(building.year_of_construction), - building.function, - str(format(building.heating_demand[cte.YEAR][0] / 3.6e9, '.2f')), - str(format(building.cooling_demand[cte.YEAR][0] / 3.6e9, '.2f')), - str(format(building.domestic_hot_water_heat_demand[cte.YEAR][0] / 3.6e9, '.2f')), - str(format( - (building.lighting_electrical_demand[cte.YEAR][0] + building.appliances_electrical_demand[cte.YEAR][0]) - / 3.6e9, '.2f')), - ] - intensity_data = [ - building.name, - str(format(total_floor_area, '.2f')), - str(format(building.heating_demand[cte.YEAR][0] / (3.6e6 * total_floor_area), '.2f')), - str(format(building.cooling_demand[cte.YEAR][0] / (3.6e6 * total_floor_area), '.2f')), - str(format( - (building.lighting_electrical_demand[cte.YEAR][0] + building.appliances_electrical_demand[cte.YEAR][0]) / - (3.6e6 * total_floor_area), '.2f')) - ] - peak_data = [ - building.name, - str(format(building.heating_peak_load[cte.YEAR][0] / 1000, '.2f')), - str(format(building.cooling_peak_load[cte.YEAR][0] / 1000, '.2f')), - str(format( - (building.lighting_electrical_demand[cte.YEAR][0] + building.appliances_electrical_demand[cte.YEAR][0]) / - (3.6e6 * total_floor_area), '.2f')) - ] - table_data.append(building_data) - intensity_table_data.append(intensity_data) - peak_load_data.append(peak_data) - - self.report.add_table(table_data, caption='Buildings Energy Consumption Data') - self.report.add_table(intensity_table_data, caption='Buildings Energy Use Intensity Data') - self.report.add_table(peak_load_data, caption='Buildings Peak Load Data') - - def plot_monthly_energy_demands(self, data, file_name, title): - # Data preparation - months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'] - demands = { - 'Heating': ('heating', '#2196f3'), - 'Cooling': ('cooling', '#ff5a5f'), - 'DHW': ('dhw', '#4caf50'), - 'Electricity': ('lighting_appliance', '#ffc107') - } - - # Helper function for plotting - def plot_bar_chart(ax, demand_type, color, ylabel, title): - values = data[demand_type] - ax.bar(months, values, color=color, width=0.6, zorder=2) - ax.grid(which="major", axis='x', color='#DAD8D7', alpha=0.5, zorder=1) - ax.grid(which="major", axis='y', color='#DAD8D7', alpha=0.5, zorder=1) - ax.set_ylabel(ylabel, fontsize=14, labelpad=10) - ax.set_title(title, fontsize=14, weight='bold', alpha=.8, pad=40) - ax.xaxis.set_major_locator(MaxNLocator(integer=True)) - ax.yaxis.set_major_locator(MaxNLocator(integer=True)) - ax.set_xticks(np.arange(len(months))) - ax.set_xticklabels(months, rotation=45, ha='right') - ax.bar_label(ax.containers[0], padding=3, color='black', fontsize=12, rotation=90) - ax.spines[['top', 'left', 'bottom']].set_visible(False) - ax.spines['right'].set_linewidth(1.1) - average_value = np.mean(values) - ax.axhline(y=average_value, color='grey', linewidth=2, linestyle='--') - ax.text(len(months) - 1, average_value, f'Average = {average_value:.1f} kWh', ha='right', va='bottom', - color='grey') - - # Plotting - fig, axs = plt.subplots(4, 1, figsize=(20, 16), dpi=96) - fig.suptitle(title, fontsize=16, weight='bold', alpha=.8) - - plot_bar_chart(axs[0], 'heating', demands['Heating'][1], 'Heating Demand (kWh)', 'Monthly Heating Demand') - plot_bar_chart(axs[1], 'cooling', demands['Cooling'][1], 'Cooling Demand (kWh)', 'Monthly Cooling Demand') - plot_bar_chart(axs[2], 'dhw', demands['DHW'][1], 'DHW Demand (kWh)', 'Monthly DHW Demand') - plot_bar_chart(axs[3], 'lighting_appliance', demands['Electricity'][1], 'Electricity Demand (kWh)', - 'Monthly Electricity Demand') - - # Set a white background - fig.patch.set_facecolor('white') - - # Adjust the margins around the plot area - plt.subplots_adjust(left=0.05, right=0.95, top=0.9, bottom=0.1, hspace=0.5) - - # Save the plot - chart_path = self.charts_path / f'{file_name}.png' - plt.savefig(chart_path, bbox_inches='tight') - plt.close() - - return chart_path - - def plot_monthly_energy_consumption(self, data, file_name, title): - # Data preparation - months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'] - consumptions = { - 'Heating': ('heating', '#2196f3', 'Heating Consumption (kWh)', 'Monthly Energy Consumption for Heating'), - 'Cooling': ('cooling', '#ff5a5f', 'Cooling Consumption (kWh)', 'Monthly Energy Consumption for Cooling'), - 'DHW': ('dhw', '#4caf50', 'DHW Consumption (kWh)', 'Monthly DHW Consumption') - } - - # Helper function for plotting - def plot_bar_chart(ax, consumption_type, color, ylabel, title): - values = data[consumption_type] - ax.bar(months, values, color=color, width=0.6, zorder=2) - ax.grid(which="major", axis='x', color='#DAD8D7', alpha=0.5, zorder=1) - ax.grid(which="major", axis='y', color='#DAD8D7', alpha=0.5, zorder=1) - ax.set_xlabel('Month', fontsize=12, labelpad=10) - ax.set_ylabel(ylabel, fontsize=14, labelpad=10) - ax.set_title(title, fontsize=14, weight='bold', alpha=.8, pad=40) - ax.xaxis.set_major_locator(MaxNLocator(integer=True)) - ax.yaxis.set_major_locator(MaxNLocator(integer=True)) - ax.set_xticks(np.arange(len(months))) - ax.set_xticklabels(months, rotation=45, ha='right') - ax.bar_label(ax.containers[0], padding=3, color='black', fontsize=12, rotation=90) - ax.spines[['top', 'left', 'bottom']].set_visible(False) - ax.spines['right'].set_linewidth(1.1) - average_value = np.mean(values) - ax.axhline(y=average_value, color='grey', linewidth=2, linestyle='--') - ax.text(len(months) - 1, average_value, f'Average = {average_value:.1f} kWh', ha='right', va='bottom', - color='grey') - - # Plotting - fig, axs = plt.subplots(3, 1, figsize=(20, 15), dpi=96) - fig.suptitle(title, fontsize=16, weight='bold', alpha=.8) - - plot_bar_chart(axs[0], 'heating', consumptions['Heating'][1], consumptions['Heating'][2], - consumptions['Heating'][3]) - plot_bar_chart(axs[1], 'cooling', consumptions['Cooling'][1], consumptions['Cooling'][2], - consumptions['Cooling'][3]) - plot_bar_chart(axs[2], 'dhw', consumptions['DHW'][1], consumptions['DHW'][2], consumptions['DHW'][3]) - - # Set a white background - fig.patch.set_facecolor('white') - - # Adjust the margins around the plot area - plt.subplots_adjust(left=0.05, right=0.95, top=0.9, bottom=0.1, wspace=0.3, hspace=0.5) - - # Save the plot - chart_path = self.charts_path / f'{file_name}.png' - plt.savefig(chart_path, bbox_inches='tight') - plt.close() - - return chart_path - - def fuel_consumption_breakdown(self, file_name, data): - fuel_consumption_breakdown = {} - for building in self.city.buildings: - for key, breakdown in data[f'{building.name}']['energy_consumption_breakdown'].items(): - if key not in fuel_consumption_breakdown: - fuel_consumption_breakdown[key] = {sector: 0 for sector in breakdown} - for sector, value in breakdown.items(): - if sector in fuel_consumption_breakdown[key]: - fuel_consumption_breakdown[key][sector] += value / 3.6e6 - else: - fuel_consumption_breakdown[key][sector] = value / 3.6e6 - - plt.style.use('ggplot') - num_keys = len(fuel_consumption_breakdown) - fig, axs = plt.subplots(1 if num_keys <= 2 else num_keys, min(num_keys, 2), figsize=(12, 5)) - axs = axs if num_keys > 1 else [axs] # Ensure axs is always iterable - - for i, (fuel, breakdown) in enumerate(fuel_consumption_breakdown.items()): - labels = breakdown.keys() - values = breakdown.values() - colors = cm.get_cmap('tab20c', len(labels)) - ax = axs[i] if num_keys > 1 else axs[0] - ax.pie(values, labels=labels, - autopct=lambda pct: f"{pct:.1f}%\n({pct / 100 * sum(values):.2f})", - startangle=90, colors=[colors(j) for j in range(len(labels))]) - ax.set_title(f'{fuel} Consumption Breakdown') - - plt.suptitle('City Energy Consumption Breakdown', fontsize=16, fontweight='bold') - plt.tight_layout(rect=[0, 0, 1, 0.95]) # Adjust layout to fit the suptitle - - chart_path = self.charts_path / f'{file_name}.png' - plt.savefig(chart_path, dpi=300) - plt.close() - return chart_path - - def energy_system_archetype_schematic(self): - energy_system_archetypes = {} - for building in self.city.buildings: - if building.energy_systems_archetype_name not in energy_system_archetypes: - energy_system_archetypes[building.energy_systems_archetype_name] = [building.name] - else: - energy_system_archetypes[building.energy_systems_archetype_name].append(building.name) - - text = "" - items = "" - for archetype, buildings in energy_system_archetypes.items(): - buildings_str = ", ".join(buildings) - text += f"Figure 4 shows the schematic of the proposed energy system for buildings {buildings_str}.\n" - if archetype in ['PV+4Pipe+DHW', 'PV+ASHP+GasBoiler+TES', 'Central 4 Pipes Air to Water Heat Pump and Gas Boiler with Independent Water Heating and PV']: - text += "This energy system archetype is formed of the following systems: \par" - items = ['Rooftop Photovoltaic System: The rooftop PV system is tied to the grid and in case there is surplus ' - 'energy, it is sold to Hydro-Quebec through their Net-Meterin program.', - '4-Pipe HVAC System: This systems includes a 4-pipe heat pump capable of generating heat and cooling ' - 'at the same time, a natural gas boiler as the auxiliary heating system, and a hot water storage tank.' - 'The temperature inside the tank is kept between 40-55 C. The cooling demand is totally supplied by ' - 'the heat pump unit.', - 'Domestic Hot Water Heat Pump System: This system is in charge of supplying domestic hot water demand.' - 'The heat pump is connected to a thermal storage tank with electric resistance heating coil inside it.' - ' The temperature inside the tank should always remain above 60 C.'] - - self.report.add_text(text) - self.report.add_itemize(items=items) - schema_path = self.system_schemas_path / f'{archetype}.jpg' - self.report.add_image(str(schema_path).replace('\\', '/'), - f'Proposed energy system for buildings {buildings_str}') - - def plot_monthly_radiation(self): - months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'] - monthly_roof_radiation = [] - for i in range(len(months)): - tilted_radiation = 0 - for building in self.city.buildings: - tilted_radiation += (building.roofs[0].global_irradiance_tilted[cte.MONTH][i] / 1000) - monthly_roof_radiation.append(tilted_radiation) - - def plot_bar_chart(ax, months, values, color, ylabel, title): - ax.bar(months, values, color=color, width=0.6, zorder=2) - ax.grid(which="major", axis='x', color='#DAD8D7', alpha=0.5, zorder=1) - ax.grid(which="major", axis='y', color='#DAD8D7', alpha=0.5, zorder=1) - ax.set_xlabel('Month', fontsize=12, labelpad=10) - ax.set_ylabel(ylabel, fontsize=14, labelpad=10) - ax.set_title(title, fontsize=14, weight='bold', alpha=.8, pad=40) - ax.xaxis.set_major_locator(MaxNLocator(integer=True)) - ax.yaxis.set_major_locator(MaxNLocator(integer=True)) - ax.set_xticks(np.arange(len(months))) - ax.set_xticklabels(months, rotation=45, ha='right') - ax.bar_label(ax.containers[0], padding=3, color='black', fontsize=12, rotation=90) - ax.spines[['top', 'left', 'bottom']].set_visible(False) - ax.spines['right'].set_linewidth(1.1) - average_value = np.mean(values) - ax.axhline(y=average_value, color='grey', linewidth=2, linestyle='--') - ax.text(len(months) - 1, average_value, f'Average = {average_value:.1f} kWh', ha='right', va='bottom', - color='grey') - - # Plotting the bar chart - fig, ax = plt.subplots(figsize=(15, 8), dpi=96) - plot_bar_chart(ax, months, monthly_roof_radiation, '#ffc107', 'Tilted Roof Radiation (kWh / m2)', - 'Monthly Tilted Roof Radiation') - - # Set a white background - fig.patch.set_facecolor('white') - - # Adjust the margins around the plot area - plt.subplots_adjust(left=0.1, right=0.95, top=0.9, bottom=0.1) - - # Save the plot - chart_path = self.charts_path / 'monthly_tilted_roof_radiation.png' - plt.savefig(chart_path, bbox_inches='tight') - plt.close() - return chart_path - - def energy_consumption_comparison(self, current_status_data, retrofitted_data, file_name, title): - months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'] - consumptions = { - 'Heating': ('heating', '#2196f3', 'Heating Consumption (kWh)', 'Monthly Energy Consumption for Heating'), - 'Cooling': ('cooling', '#ff5a5f', 'Cooling Consumption (kWh)', 'Monthly Energy Consumption for Cooling'), - 'DHW': ('dhw', '#4caf50', 'DHW Consumption (kWh)', 'Monthly DHW Consumption') - } - - # Helper function for plotting - def plot_double_bar_chart(ax, consumption_type, color, ylabel, title): - current_values = current_status_data[consumption_type] - retrofitted_values = retrofitted_data[consumption_type] - bar_width = 0.35 - index = np.arange(len(months)) - - ax.bar(index, current_values, bar_width, label='Current Status', color=color, alpha=0.7, zorder=2) - ax.bar(index + bar_width, retrofitted_values, bar_width, label='Retrofitted', color=color, hatch='/', zorder=2) - - ax.grid(which="major", axis='x', color='#DAD8D7', alpha=0.5, zorder=1) - ax.grid(which="major", axis='y', color='#DAD8D7', alpha=0.5, zorder=1) - ax.set_xlabel('Month', fontsize=12, labelpad=10) - ax.set_ylabel(ylabel, fontsize=14, labelpad=10) - ax.set_title(title, fontsize=14, weight='bold', alpha=.8, pad=40) - ax.set_xticks(index + bar_width / 2) - ax.set_xticklabels(months, rotation=45, ha='right') - ax.legend() - - # Adding bar labels - ax.bar_label(ax.containers[0], padding=3, color='black', fontsize=12, rotation=90) - ax.bar_label(ax.containers[1], padding=3, color='black', fontsize=12, rotation=90) - - ax.spines[['top', 'left', 'bottom']].set_visible(False) - ax.spines['right'].set_linewidth(1.1) - - # Plotting - fig, axs = plt.subplots(3, 1, figsize=(20, 25), dpi=96) - fig.suptitle(title, fontsize=16, weight='bold', alpha=.8) - - plot_double_bar_chart(axs[0], 'heating', consumptions['Heating'][1], consumptions['Heating'][2], - consumptions['Heating'][3]) - plot_double_bar_chart(axs[1], 'cooling', consumptions['Cooling'][1], consumptions['Cooling'][2], - consumptions['Cooling'][3]) - plot_double_bar_chart(axs[2], 'dhw', consumptions['DHW'][1], consumptions['DHW'][2], consumptions['DHW'][3]) - - # Set a white background - fig.patch.set_facecolor('white') - - # Adjust the margins around the plot area - plt.subplots_adjust(left=0.05, right=0.95, top=0.9, bottom=0.1, wspace=0.3, hspace=0.5) - - # Save the plot - chart_path = self.charts_path / f'{file_name}.png' - plt.savefig(chart_path, bbox_inches='tight') - plt.close() - - return chart_path - - def yearly_consumption_comparison(self): - current_total_consumption = round(self.current_status_data['total_consumption'], 2) - retrofitted_total_consumption = round(self.retrofitted_data['total_consumption'], 2) - text = ( - f'The total yearly energy consumption before and after the retrofit are {current_total_consumption} MWh and ' - f'{retrofitted_total_consumption} MWh, respectively.') - if retrofitted_total_consumption < current_total_consumption: - change = str(round((current_total_consumption - retrofitted_total_consumption) * 100 / current_total_consumption, - 2)) - text += f'Therefore, the total yearly energy consumption decreased by {change} \%.' - else: - change = str(round((retrofitted_total_consumption - current_total_consumption) * 100 / - retrofitted_total_consumption, 2)) - text += f'Therefore, the total yearly energy consumption increased by {change} \%. \par' - self.report.add_text(text) - - def pv_system(self): - self.report.add_text('The first step in PV assessments is evaluating the potential of buildings for installing ' - 'rooftop PV system. The benchmark value used for this evaluation is the mean yearly solar ' - 'incident in Montreal. According to Hydro-Quebec, the mean annual incident in Montreal is 1350' - 'kWh/m2. Therefore, any building with rooftop annual global horizontal radiation of less than ' - '1080 kWh/m2 is considered to be infeasible. Table 4 shows the yearly horizontal radiation on ' - 'buildings roofs. \par') - radiation_data = [ - ["Building Name", "Roof Area $m^2$", "Function", "Rooftop Annual Global Horizontal Radiation kWh/ $m^2$"] - ] - pv_feasible_buildings = [] - for building in self.city.buildings: - if building.roofs[0].global_irradiance[cte.YEAR][0] > 1080: - pv_feasible_buildings.append(building.name) - data = [building.name, str(format(building.roofs[0].perimeter_area, '.2f')), building.function, - str(format(building.roofs[0].global_irradiance[cte.YEAR][0] / (cte.WATTS_HOUR_TO_JULES * 1000), '.2f'))] - radiation_data.append(data) - - self.report.add_table(radiation_data, - caption='Buildings Roof Characteristics') - - if len(pv_feasible_buildings) == len(self.city.buildings): - buildings_str = 'all' - else: - buildings_str = ", ".join(pv_feasible_buildings) - self.report.add_text(f"From the table it can be seen that {buildings_str} buildings are good candidates to have " - f"rooftop PV system. The next step is calculating the amount of solar radiation on a tilted " - f"surface. Figure 5 shows the total monthly solar radiation on a surface with the tilt angle " - f"of 45 degrees on the roofs of those buildings that are identified to have rooftop PV system." - f"\par") - tilted_radiation = self.plot_monthly_radiation() - self.report.add_image(str(tilted_radiation).replace('\\', '/'), - caption='Total Monthly Solar Radiation on Buildings Roofs on a 45 Degrees Tilted Surface', - placement='H') - self.report.add_text('The first step in sizing the PV system is to find the available roof area. ' - 'Few considerations need to be made here. The considerations include space for maintenance ' - 'crew, space for mechanical equipment, and orientation correction factor to make sure all ' - 'the panel are truly facing south. After all these considerations, the minimum distance ' - 'between the panels to avoid shading throughout the year is found. Table 5 shows the number of' - 'panles on each buildings roof, yearly PV production, total electricity consumption, and self ' - 'consumption. \par') - - pv_output_table = [['Building Name', 'Total Surface Area of PV Panels ($m^2$)', - 'Total Solar Incident on PV Modules (MWh)', 'Yearly PV production (MWh)']] - - for building in self.city.buildings: - if building.name in pv_feasible_buildings: - pv_data = [] - pv_data.append(building.name) - yearly_solar_incident = (building.roofs[0].global_irradiance_tilted[cte.YEAR][0] * - building.roofs[0].installed_solar_collector_area) / (cte.WATTS_HOUR_TO_JULES * 1e6) - yearly_solar_incident_str = format(yearly_solar_incident, '.2f') - yearly_pv_output = building.onsite_electrical_production[cte.YEAR][0] / (cte.WATTS_HOUR_TO_JULES * 1e6) - yearly_pv_output_str = format(yearly_pv_output, '.2f') - - pv_data.append(format(building.roofs[0].installed_solar_collector_area, '.2f')) - pv_data.append(yearly_solar_incident_str) - pv_data.append(yearly_pv_output_str) - - pv_output_table.append(pv_data) - - self.report.add_table(pv_output_table, caption='PV System Simulation Results', first_column_width=3) - - def life_cycle_cost_stacked_bar(self, file_name, title): - # Aggregate LCC components for current and retrofitted statuses - current_status_capex = 0 - current_status_opex = 0 - current_status_maintenance = 0 - current_status_end_of_life = 0 - retrofitted_capex = 0 - retrofitted_opex = 0 - retrofitted_maintenance = 0 - retrofitted_end_of_life = 0 - current_status_operational_income = 0 - retrofitted_operational_income = 0 - - for building in self.city.buildings: - current_status_capex += self.current_status_lcc[f'{building.name}']['capital_cost_per_sqm'] - retrofitted_capex += self.retrofitted_lcc[f'{building.name}']['capital_cost_per_sqm'] - current_status_opex += self.current_status_lcc[f'{building.name}']['operational_cost_per_sqm'] - retrofitted_opex += self.retrofitted_lcc[f'{building.name}']['operational_cost_per_sqm'] - current_status_maintenance += self.current_status_lcc[f'{building.name}']['maintenance_cost_per_sqm'] - retrofitted_maintenance += self.retrofitted_lcc[f'{building.name}']['maintenance_cost_per_sqm'] - current_status_end_of_life += self.current_status_lcc[f'{building.name}']['end_of_life_cost_per_sqm'] - retrofitted_end_of_life += self.retrofitted_lcc[f'{building.name}']['end_of_life_cost_per_sqm'] - current_status_operational_income += self.current_status_lcc[f'{building.name}']['operational_income_per_sqm'] - retrofitted_operational_income += self.retrofitted_lcc[f'{building.name}']['operational_income_per_sqm'] - - current_status_lcc_components_sqm = { - 'Capital Cost': current_status_capex / len(self.city.buildings), - 'Operational Cost': (current_status_opex - current_status_operational_income) / len(self.city.buildings), - 'Maintenance Cost': current_status_maintenance / len(self.city.buildings), - 'End of Life Cost': current_status_end_of_life / len(self.city.buildings), - } - retrofitted_lcc_components_sqm = { - 'Capital Cost': retrofitted_capex / len(self.city.buildings), - 'Operational Cost': (retrofitted_opex - retrofitted_operational_income) / len(self.city.buildings), - 'Maintenance Cost': retrofitted_maintenance / len(self.city.buildings), - 'End of Life Cost': retrofitted_end_of_life / len(self.city.buildings), - } - - labels = ['Current Status', 'Retrofitted Status'] - categories = ['Capital Cost', 'Operational Cost', 'Maintenance Cost', 'End of Life Cost'] - colors = ['#2196f3', '#ff5a5f', '#4caf50', '#ffc107'] # Added new color - - # Data preparation - bar_width = 0.35 - r = np.arange(len(labels)) - - fig, ax = plt.subplots(figsize=(12, 8), dpi=96) - fig.suptitle(title, fontsize=16, weight='bold', alpha=.8) - - # Plotting current status data - bottom = np.zeros(len(labels)) - for category, color in zip(categories, colors): - values = [current_status_lcc_components_sqm[category], retrofitted_lcc_components_sqm[category]] - ax.bar(r, values, bottom=bottom, color=color, edgecolor='white', width=bar_width, label=category) - bottom += values - - # Adding summation annotations at the top of the bars - for idx, (x, total) in enumerate(zip(r, bottom)): - ax.text(x, total, f'{total:.1f}', ha='center', va='bottom', fontsize=12, fontweight='bold') - - # Adding labels, title, and grid - ax.set_xlabel('LCC Components', fontsize=12, labelpad=10) - ax.set_ylabel('Average Cost (CAD/m²)', fontsize=14, labelpad=10) - ax.grid(which="major", axis='y', color='#DAD8D7', alpha=0.5, zorder=1) - ax.set_xticks(r) - ax.set_xticklabels(labels, rotation=45, ha='right') - ax.legend() - - # Adding a white background - fig.patch.set_facecolor('white') - - # Adjusting the margins around the plot area - plt.subplots_adjust(left=0.05, right=0.95, top=0.9, bottom=0.2) - - # Save the plot - chart_path = self.charts_path / f'{file_name}.png' - plt.savefig(chart_path, bbox_inches='tight') - plt.close() - - return chart_path - - def create_report(self): - # Add sections and text to the report - self.report.add_section('Overview of the Current Status in Buildings') - self.report.add_text('In this section, an overview of the current status of buildings characteristics, ' - 'energy demand and consumptions are provided') - self.report.add_subsection('Buildings Characteristics') - - self.building_energy_info() - - # current monthly demands and consumptions - current_monthly_demands = self.current_status_data['monthly_demands'] - current_monthly_consumptions = self.current_status_data['monthly_consumptions'] - - # Plot and save demand chart - current_demand_chart_path = self.plot_monthly_energy_demands(data=current_monthly_demands, - file_name='current_monthly_demands', - title='Current Status Monthly Energy Demands') - # Plot and save consumption chart - current_consumption_chart_path = self.plot_monthly_energy_consumption(data=current_monthly_consumptions, - file_name='monthly_consumptions', - title='Monthly Energy Consumptions') - current_consumption_breakdown_path = self.fuel_consumption_breakdown('City_Energy_Consumption_Breakdown', - self.current_status_data) - retrofitted_consumption_breakdown_path = self.fuel_consumption_breakdown( - 'fuel_consumption_breakdown_after_retrofit', - self.retrofitted_data) - life_cycle_cost_sqm_stacked_bar_chart_path = self.life_cycle_cost_stacked_bar('lcc_per_sqm', - 'LCC Analysis') - # Add current state of energy demands in the city - self.report.add_subsection('Current State of Energy Demands in the City') - self.report.add_text('The total monthly energy demands in the city are shown in Figure 1. It should be noted ' - 'that the electricity demand refers to total lighting and appliance electricity demands') - self.report.add_image(str(current_demand_chart_path).replace('\\', '/'), - 'Total Monthly Energy Demands in City', - placement='h') - - # Add current state of energy consumption in the city - self.report.add_subsection('Current State of Energy Consumption in the City') - self.report.add_text('The following figure shows the amount of energy consumed to supply heating, cooling, and ' - 'domestic hot water needs in the city. The details of the systems in each building before ' - 'and after retrofit are provided in Section 4. \par') - self.report.add_image(str(current_consumption_chart_path).replace('\\', '/'), - 'Total Monthly Energy Consumptions in City', - placement='H') - self.report.add_text('Figure 3 shows the yearly energy supplied to the city by fuel in different sectors. ' - 'All the values are in kWh.') - self.report.add_image(str(current_consumption_breakdown_path).replace('\\', '/'), - 'Current Energy Consumption Breakdown in the City by Fuel', - placement='H') - self.report.add_section(f'{self.retrofit_scenario}') - self.report.add_subsection('Proposed Systems') - self.energy_system_archetype_schematic() - if 'PV' in self.retrofit_scenario: - self.report.add_subsection('Rooftop Photovoltaic System Implementation') - self.pv_system() - if 'System' in self.retrofit_scenario: - self.report.add_subsection('Retrofitted HVAC and DHW Systems') - self.report.add_text('Figure 6 shows a comparison between total monthly energy consumption in the selected ' - 'buildings before and after retrofitting.') - consumption_comparison = self.energy_consumption_comparison(self.current_status_data['monthly_consumptions'], - self.retrofitted_data['monthly_consumptions'], - 'energy_consumption_comparison_in_city', - 'Total Monthly Energy Consumption Comparison in ' - 'Buildings') - self.report.add_image(str(consumption_comparison).replace('\\', '/'), - caption='Comparison of Total Monthly Energy Consumption in City Buildings', - placement='H') - self.yearly_consumption_comparison() - self.report.add_text('Figure 7 shows the fuel consumption breakdown in the area after the retrofit.') - self.report.add_image(str(retrofitted_consumption_breakdown_path).replace('\\', '/'), - caption=f'Fuel Consumption Breakdown After {self.retrofit_scenario}', - placement='H') - self.report.add_subsection('Life Cycle Cost Analysis') - self.report.add_image(str(life_cycle_cost_sqm_stacked_bar_chart_path).replace('\\', '/'), - caption='Average Life Cycle Cost Components', - placement='H') - - # Save and compile the report - self.report.save_report() - self.report.compile_to_pdf() diff --git a/energy_system_modelling_package/energy_system_retrofit/energy_system_retrofit_results.py b/energy_system_modelling_package/energy_system_retrofit/energy_system_retrofit_results.py deleted file mode 100644 index 1b84601a..00000000 --- a/energy_system_modelling_package/energy_system_retrofit/energy_system_retrofit_results.py +++ /dev/null @@ -1,176 +0,0 @@ -import hub.helpers.constants as cte - - -def hourly_electricity_consumption_profile(building): - hourly_electricity_consumption = [] - energy_systems = building.energy_systems - appliance = building.appliances_electrical_demand[cte.HOUR] - lighting = building.lighting_electrical_demand[cte.HOUR] - elec_heating = 0 - elec_cooling = 0 - elec_dhw = 0 - if cte.HEATING in building.energy_consumption_breakdown[cte.ELECTRICITY]: - elec_heating = 1 - if cte.COOLING in building.energy_consumption_breakdown[cte.ELECTRICITY]: - elec_cooling = 1 - if cte.DOMESTIC_HOT_WATER in building.energy_consumption_breakdown[cte.ELECTRICITY]: - elec_dhw = 1 - heating = None - cooling = None - dhw = None - if elec_heating == 1: - for energy_system in energy_systems: - if cte.HEATING in energy_system.demand_types: - for generation_system in energy_system.generation_systems: - if generation_system.fuel_type == cte.ELECTRICITY: - if cte.HEATING in generation_system.energy_consumption: - heating = generation_system.energy_consumption[cte.HEATING][cte.HOUR] - else: - if len(energy_system.generation_systems) > 1: - heating = [x / 2 for x in building.heating_consumption[cte.HOUR]] - else: - heating = building.heating_consumption[cte.HOUR] - if elec_dhw == 1: - for energy_system in energy_systems: - if cte.DOMESTIC_HOT_WATER in energy_system.demand_types: - for generation_system in energy_system.generation_systems: - if generation_system.fuel_type == cte.ELECTRICITY: - if cte.DOMESTIC_HOT_WATER in generation_system.energy_consumption: - dhw = generation_system.energy_consumption[cte.DOMESTIC_HOT_WATER][cte.HOUR] - else: - if len(energy_system.generation_systems) > 1: - dhw = [x / 2 for x in building.domestic_hot_water_consumption[cte.HOUR]] - else: - dhw = building.domestic_hot_water_consumption[cte.HOUR] - - if elec_cooling == 1: - for energy_system in energy_systems: - if cte.COOLING in energy_system.demand_types: - for generation_system in energy_system.generation_systems: - if cte.COOLING in generation_system.energy_consumption: - cooling = generation_system.energy_consumption[cte.COOLING][cte.HOUR] - else: - if len(energy_system.generation_systems) > 1: - cooling = [x / 2 for x in building.cooling_consumption[cte.HOUR]] - else: - cooling = building.cooling_consumption[cte.HOUR] - - for i in range(len(building.heating_demand[cte.HOUR])): - hourly = 0 - hourly += appliance[i] / 3600 - hourly += lighting[i] / 3600 - if heating is not None: - hourly += heating[i] / 3600 - if cooling is not None: - hourly += cooling[i] / 3600 - if dhw is not None: - hourly += dhw[i] / 3600 - hourly_electricity_consumption.append(hourly) - return hourly_electricity_consumption - - -def consumption_data(city): - energy_consumption_data = {} - for building in city.buildings: - hourly_electricity_consumption = hourly_electricity_consumption_profile(building) - energy_consumption_data[f'{building.name}'] = {'heating_consumption': building.heating_consumption, - 'cooling_consumption': building.cooling_consumption, - 'domestic_hot_water_consumption': - building.domestic_hot_water_consumption, - 'energy_consumption_breakdown': - building.energy_consumption_breakdown, - 'hourly_electricity_consumption': hourly_electricity_consumption} - peak_electricity_consumption = 0 - for building in energy_consumption_data: - peak_electricity_consumption += max(energy_consumption_data[building]['hourly_electricity_consumption']) - heating_demand_monthly = [] - cooling_demand_monthly = [] - dhw_demand_monthly = [] - lighting_appliance_monthly = [] - heating_consumption_monthly = [] - cooling_consumption_monthly = [] - dhw_consumption_monthly = [] - for i in range(12): - heating_demand = 0 - cooling_demand = 0 - dhw_demand = 0 - lighting_appliance_demand = 0 - heating_consumption = 0 - cooling_consumption = 0 - dhw_consumption = 0 - for building in city.buildings: - heating_demand += building.heating_demand[cte.MONTH][i] / 3.6e6 - cooling_demand += building.cooling_demand[cte.MONTH][i] / 3.6e6 - dhw_demand += building.domestic_hot_water_heat_demand[cte.MONTH][i] / 3.6e6 - lighting_appliance_demand += building.lighting_electrical_demand[cte.MONTH][i] / 3.6e6 - heating_consumption += building.heating_consumption[cte.MONTH][i] / 3.6e6 - if building.cooling_consumption[cte.YEAR][0] == 0: - cooling_consumption += building.cooling_demand[cte.MONTH][i] / (3.6e6 * 2) - else: - cooling_consumption += building.cooling_consumption[cte.MONTH][i] / 3.6e6 - dhw_consumption += building.domestic_hot_water_consumption[cte.MONTH][i] / 3.6e6 - heating_demand_monthly.append(heating_demand) - cooling_demand_monthly.append(cooling_demand) - dhw_demand_monthly.append(dhw_demand) - lighting_appliance_monthly.append(lighting_appliance_demand) - heating_consumption_monthly.append(heating_consumption) - cooling_consumption_monthly.append(cooling_consumption) - dhw_consumption_monthly.append(dhw_consumption) - - monthly_demands = {'heating': heating_demand_monthly, - 'cooling': cooling_demand_monthly, - 'dhw': dhw_demand_monthly, - 'lighting_appliance': lighting_appliance_monthly} - monthly_consumptions = {'heating': heating_consumption_monthly, - 'cooling': cooling_consumption_monthly, - 'dhw': dhw_consumption_monthly} - yearly_heating = 0 - yearly_cooling = 0 - yearly_dhw = 0 - yearly_appliance = 0 - yearly_lighting = 0 - for building in city.buildings: - yearly_appliance += building.appliances_electrical_demand[cte.YEAR][0] / 3.6e9 - yearly_lighting += building.lighting_electrical_demand[cte.YEAR][0] / 3.6e9 - yearly_heating += building.heating_consumption[cte.YEAR][0] / 3.6e9 - yearly_cooling += building.cooling_consumption[cte.YEAR][0] / 3.6e9 - yearly_dhw += building.domestic_hot_water_consumption[cte.YEAR][0] / 3.6e9 - - total_consumption = yearly_heating + yearly_cooling + yearly_dhw + yearly_appliance + yearly_lighting - energy_consumption_data['monthly_demands'] = monthly_demands - energy_consumption_data['monthly_consumptions'] = monthly_consumptions - energy_consumption_data['total_consumption'] = total_consumption - energy_consumption_data['maximum_hourly_electricity_consumption'] = peak_electricity_consumption - - return energy_consumption_data - - -def cost_data(building, lcc_dataframe, cost_retrofit_scenario): - total_floor_area = 0 - for thermal_zone in building.thermal_zones_from_internal_zones: - total_floor_area += thermal_zone.total_floor_area - capital_cost = lcc_dataframe.loc['total_capital_costs_systems', f'Scenario {cost_retrofit_scenario}'] - operational_cost = lcc_dataframe.loc['total_operational_costs', f'Scenario {cost_retrofit_scenario}'] - maintenance_cost = lcc_dataframe.loc['total_maintenance_costs', f'Scenario {cost_retrofit_scenario}'] - end_of_life_cost = lcc_dataframe.loc['end_of_life_costs', f'Scenario {cost_retrofit_scenario}'] - operational_income = lcc_dataframe.loc['operational_incomes', f'Scenario {cost_retrofit_scenario}'] - total_life_cycle_cost = capital_cost + operational_cost + maintenance_cost + end_of_life_cost + operational_income - specific_capital_cost = capital_cost / total_floor_area - specific_operational_cost = operational_cost / total_floor_area - specific_maintenance_cost = maintenance_cost / total_floor_area - specific_end_of_life_cost = end_of_life_cost / total_floor_area - specific_operational_income = operational_income / total_floor_area - specific_life_cycle_cost = total_life_cycle_cost / total_floor_area - life_cycle_cost_analysis = {'capital_cost': capital_cost, - 'capital_cost_per_sqm': specific_capital_cost, - 'operational_cost': operational_cost, - 'operational_cost_per_sqm': specific_operational_cost, - 'maintenance_cost': maintenance_cost, - 'maintenance_cost_per_sqm': specific_maintenance_cost, - 'end_of_life_cost': end_of_life_cost, - 'end_of_life_cost_per_sqm': specific_end_of_life_cost, - 'operational_income': operational_income, - 'operational_income_per_sqm': specific_operational_income, - 'total_life_cycle_cost': total_life_cycle_cost, - 'total_life_cycle_cost_per_sqm': specific_life_cycle_cost} - return life_cycle_cost_analysis diff --git a/energy_system_modelling_package/random_assignation.py b/energy_system_modelling_package/random_assignation.py deleted file mode 100644 index 605def1c..00000000 --- a/energy_system_modelling_package/random_assignation.py +++ /dev/null @@ -1,123 +0,0 @@ -""" -This project aims to assign energy systems archetype names to Montreal buildings. -The random assignation is based on statistical information extracted from different sources, being: -- For residential buildings: - - SHEU 2015: https://oee.nrcan.gc.ca/corporate/statistics/neud/dpa/menus/sheu/2015/tables.cfm -- For non-residential buildings: - - Montreal dataportal: https://dataportalforcities.org/north-america/canada/quebec/montreal - - https://www.eia.gov/consumption/commercial/data/2018/ -""" -import json -import random - -from hub.city_model_structure.building import Building - -energy_systems_format = 'montreal_custom' - -# parameters: -residential_systems_percentage = {'system 1 gas': 15, - 'system 1 electricity': 35, - 'system 2 gas': 0, - 'system 2 electricity': 0, - 'system 3 and 4 gas': 0, - 'system 3 and 4 electricity': 0, - 'system 5 gas': 0, - 'system 5 electricity': 0, - 'system 6 gas': 0, - 'system 6 electricity': 0, - 'system 8 gas': 15, - 'system 8 electricity': 35} - -residential_new_systems_percentage = { - 'Central Hydronic Air and Gas Source Heating System with Unitary Split Cooling and Air Source HP DHW and PV': 100, - 'Central Hydronic Air and Electricity Source Heating System with Unitary Split Cooling and Air Source HP DHW and PV': 0, - 'Central Hydronic Ground and Gas Source Heating System with Unitary Split Cooling and Air Source HP DHW and PV': 0, - 'Central Hydronic Ground and Electricity Source Heating System with Unitary Split Cooling and Air Source HP DHW ' - 'and PV': 0, - 'Central Hydronic Water and Gas Source Heating System with Unitary Split Cooling and Air Source HP DHW and PV': 0, - 'Central Hydronic Water and Electricity Source Heating System with Unitary Split Cooling and Air Source HP DHW ' - 'and PV': 0, - 'Central Hydronic Air and Gas Source Heating System with Unitary Split and Air Source HP DHW': 0, - 'Central Hydronic Air and Electricity Source Heating System with Unitary Split and Air Source HP DHW': 0, - 'Central Hydronic Ground and Gas Source Heating System with Unitary Split and Air Source HP DHW': 0, - 'Central Hydronic Ground and Electricity Source Heating System with Unitary Split and Air Source HP DHW': 0, - 'Central Hydronic Water and Gas Source Heating System with Unitary Split and Air Source HP DHW': 0, - 'Central Hydronic Water and Electricity Source Heating System with Unitary Split and Air Source HP DHW': 0, - 'Rooftop PV System': 0 -} - -non_residential_systems_percentage = {'system 1 gas': 0, - 'system 1 electricity': 0, - 'system 2 gas': 0, - 'system 2 electricity': 0, - 'system 3 and 4 gas': 39, - 'system 3 and 4 electricity': 36, - 'system 5 gas': 0, - 'system 5 electricity': 0, - 'system 6 gas': 13, - 'system 6 electricity': 12, - 'system 8 gas': 0, - 'system 8 electricity': 0} - - -def _retrieve_buildings(path, year_of_construction_field=None, - function_field=None, function_to_hub=None, aliases_field=None): - _buildings = [] - with open(path, 'r', encoding='utf8') as json_file: - _geojson = json.loads(json_file.read()) - for feature in _geojson['features']: - _building = {} - year_of_construction = None - if year_of_construction_field is not None: - year_of_construction = int(feature['properties'][year_of_construction_field]) - function = None - if function_field is not None: - function = feature['properties'][function_field] - if function_to_hub is not None: - # use the transformation dictionary to retrieve the proper function - if function in function_to_hub: - function = function_to_hub[function] - building_name = '' - building_aliases = [] - if 'id' in feature: - building_name = feature['id'] - if aliases_field is not None: - for alias_field in aliases_field: - building_aliases.append(feature['properties'][alias_field]) - _building['year_of_construction'] = year_of_construction - _building['function'] = function - _building['building_name'] = building_name - _building['building_aliases'] = building_aliases - _buildings.append(_building) - return _buildings - - -def call_random(_buildings: [Building], _systems_percentage): - _buildings_with_systems = [] - _systems_distribution = [] - _selected_buildings = list(range(0, len(_buildings))) - random.shuffle(_selected_buildings) - total = 0 - maximum = 0 - add_to = 0 - for _system in _systems_percentage: - if _systems_percentage[_system] > 0: - number_of_buildings = round(_systems_percentage[_system] / 100 * len(_selected_buildings)) - _systems_distribution.append({'system': _system, 'number': _systems_percentage[_system], - 'number_of_buildings': number_of_buildings}) - if number_of_buildings > maximum: - maximum = number_of_buildings - add_to = len(_systems_distribution) - 1 - total += number_of_buildings - missing = 0 - if total != len(_selected_buildings): - missing = len(_selected_buildings) - total - if missing != 0: - _systems_distribution[add_to]['number_of_buildings'] += missing - _position = 0 - for case in _systems_distribution: - for i in range(0, case['number_of_buildings']): - _buildings[_selected_buildings[_position]].energy_systems_archetype_name = case['system'] - _position += 1 - return _buildings - diff --git a/energy_system_modelling_package/report_creation.py b/energy_system_modelling_package/report_creation.py deleted file mode 100644 index 6298d232..00000000 --- a/energy_system_modelling_package/report_creation.py +++ /dev/null @@ -1,119 +0,0 @@ -import subprocess -import datetime -import os -from pathlib import Path - - -class LatexReport: - def __init__(self, file_name, title, subtitle, output_path): - self.file_name = file_name - self.output_path = Path(output_path) / 'report' - self.output_path.mkdir(parents=True, exist_ok=True) - self.file_path = self.output_path / f"{file_name}.tex" - self.content = [] - self.content.append(r'\documentclass{article}') - self.content.append(r'\usepackage[margin=2.5cm]{geometry}') - self.content.append(r'\usepackage{graphicx}') - self.content.append(r'\usepackage{tabularx}') - self.content.append(r'\usepackage{multirow}') - self.content.append(r'\usepackage{float}') - self.content.append(r'\begin{document}') - - current_datetime = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") - - self.content.append(r'\title{' + title + '}') - self.content.append(r'\author{Next-Generation Cities Institute}') - self.content.append(r'\date{}') - self.content.append(r'\maketitle') - - self.content.append(r'\begin{center}') - self.content.append(r'\large ' + subtitle + r'\\') - self.content.append(r'\large ' + current_datetime) - self.content.append(r'\end{center}') - - def add_section(self, section_title): - self.content.append(r'\section{' + section_title + r'}') - - def add_subsection(self, subsection_title): - self.content.append(r'\subsection{' + subsection_title + r'}') - - def add_subsubsection(self, subsection_title): - self.content.append(r'\subsubsection{' + subsection_title + r'}') - - def add_text(self, text): - self.content.append(text) - - def add_table(self, table_data, caption=None, first_column_width=None, merge_first_column=False): - num_columns = len(table_data[0]) - total_width = 0.9 - first_column_width_str = '' - - if first_column_width is not None: - first_column_width_str = str(first_column_width) + 'cm' - total_width -= first_column_width / 16.0 - - if caption: - self.content.append(r'\begin{table}[htbp]') - self.content.append(r'\caption{' + caption + r'}') - self.content.append(r'\centering') - - column_format = '|p{' + first_column_width_str + r'}|' + '|'.join( - ['X'] * (num_columns - 1)) + '|' if first_column_width is not None else '|' + '|'.join(['X'] * num_columns) + '|' - self.content.append(r'\begin{tabularx}{\textwidth}{' + column_format + '}') - self.content.append(r'\hline') - - previous_first_column = None - rowspan_count = 1 - - for i, row in enumerate(table_data): - if merge_first_column and i > 0 and row[0] == previous_first_column: - rowspan_count += 1 - self.content.append(' & '.join(['' if j == 0 else cell for j, cell in enumerate(row)]) + r' \\') - else: - if merge_first_column and i > 0 and rowspan_count > 1: - self.content[-rowspan_count] = self.content[-rowspan_count].replace(r'\multirow{1}', - r'\multirow{' + str(rowspan_count) + '}') - rowspan_count = 1 - if merge_first_column and i < len(table_data) - 1 and row[0] == table_data[i + 1][0]: - self.content.append(r'\multirow{1}{*}{' + row[0] + '}' + ' & ' + ' & '.join(row[1:]) + r' \\') - else: - self.content.append(' & '.join(row) + r' \\') - previous_first_column = row[0] - self.content.append(r'\hline') - - if merge_first_column and rowspan_count > 1: - self.content[-rowspan_count] = self.content[-rowspan_count].replace(r'\multirow{1}', - r'\multirow{' + str(rowspan_count) + '}') - - self.content.append(r'\end{tabularx}') - - if caption: - self.content.append(r'\end{table}') - - def add_image(self, image_path, caption=None, placement='ht'): - if caption: - self.content.append(r'\begin{figure}[' + placement + r']') - self.content.append(r'\centering') - self.content.append(r'\includegraphics[width=\textwidth]{' + image_path + r'}') - self.content.append(r'\caption{' + caption + r'}') - self.content.append(r'\end{figure}') - else: - self.content.append(r'\begin{figure}[' + placement + r']') - self.content.append(r'\centering') - self.content.append(r'\includegraphics[width=\textwidth]{' + image_path + r'}') - self.content.append(r'\end{figure}') - - def add_itemize(self, items): - self.content.append(r'\begin{itemize}') - for item in items: - self.content.append(r'\item ' + item) - self.content.append(r'\end{itemize}') - - def save_report(self): - self.content.append(r'\end{document}') - with open(self.file_path, 'w') as f: - f.write('\n'.join(self.content)) - - def compile_to_pdf(self): - subprocess.run(['pdflatex', '-output-directory', str(self.output_path), str(self.file_path)]) - diff --git a/input_files/omhm_selected_buildings.geojson b/input_files/omhm_selected_buildings.geojson new file mode 100644 index 00000000..31854509 --- /dev/null +++ b/input_files/omhm_selected_buildings.geojson @@ -0,0 +1,16 @@ +{ +"type": "FeatureCollection", +"crs": { "type": "name", "properties": { "name": "urn:ogc:def:crs:OGC:1.3:CRS84" } }, +"features": [ +{ "type": "Feature", "properties": { "OBJECTID": 162726, "id": "01001802", "CIVIQUE_DE": 4530, "CIVIQUE_FI": 4530, "NOM_RUE": "avenue Henri-Julien (MTL)", "MUNICIPALI": 50, "CODE_UTILI": 1000, "LIBELLE_UT": "Logement", "CATEGORIE_": "Régulier", "Hieght_LiD": 9, "AREA_NEW": 158, "MBG_Width": 12, "MBG_Length": 13, "MBG_Orientation": 122, "Shape_Length": 50, "Shape_Area": 158, "BuildingCategory": "detached", "BuildingVolume": 1422, "AspectRatio": 1.034, "SurfacetoVolumeRatio": 0.111, "FloorNu_RawTax": 3, "FloorNu_RawTax.1": 3, "Floor_frmHieght": 3, "TotalFloorArea": 474, "ANNEE_CONS": 1980 }, "geometry": { "type": "Polygon", "coordinates": [ [ [ -73.584529976107675, 45.523035946589552 ], [ -73.584668476667062, 45.523096845969846 ], [ -73.584752377018077, 45.523002646985603 ], [ -73.584613876598254, 45.522941746806083 ], [ -73.584529976107675, 45.523035946589552 ] ] ] } }, +{ "type": "Feature", "properties": { "OBJECTID": 162727, "id": "01001804", "CIVIQUE_DE": 4535, "CIVIQUE_FI": 4535, "NOM_RUE": "avenue Henri-Julien (MTL)", "MUNICIPALI": 50, "CODE_UTILI": 1000, "LIBELLE_UT": "Logement", "CATEGORIE_": "Régulier", "Hieght_LiD": 11, "AREA_NEW": 161, "MBG_Width": 12, "MBG_Length": 14, "MBG_Orientation": 118, "Shape_Length": 52, "Shape_Area": 169, "BuildingCategory": "semi-attached", "BuildingVolume": 1771, "AspectRatio": 1.118, "SurfacetoVolumeRatio": 0.091, "FloorNu_RawTax": 2, "FloorNu_RawTax.1": 2, "Floor_frmHieght": 3, "TotalFloorArea": 483, "ANNEE_CONS": 1980 }, "geometry": { "type": "Polygon", "coordinates": [ [ [ -73.584504328100977, 45.523318275073017 ], [ -73.584470063117109, 45.523357528940657 ], [ -73.584430877357136, 45.523409646947947 ], [ -73.584428076873849, 45.52341334646033 ], [ -73.584574755505997, 45.523468170484847 ], [ -73.584656456001568, 45.523374796559551 ], [ -73.584574577402094, 45.523346746210301 ], [ -73.584576876816001, 45.523343546476525 ], [ -73.584505377070585, 45.523318645930914 ], [ -73.584504328100977, 45.523318275073017 ] ] ] } }, +{ "type": "Feature", "properties": { "OBJECTID": 162168, "id": "01001944", "CIVIQUE_DE": 4820, "CIVIQUE_FI": 4850, "NOM_RUE": "rue de Grand-Pré (MTL)", "MUNICIPALI": 50, "CODE_UTILI": 1000, "LIBELLE_UT": "Logement", "CATEGORIE_": "Régulier", "Hieght_LiD": 11, "AREA_NEW": 504, "MBG_Width": 16, "MBG_Length": 42, "MBG_Orientation": 123, "Shape_Length": 116, "Shape_Area": 669, "BuildingCategory": "fully-attached", "BuildingVolume": 5544, "AspectRatio": 2.648, "SurfacetoVolumeRatio": 0.091, "FloorNu_RawTax": 3, "FloorNu_RawTax.1": 3, "Floor_frmHieght": 3, "TotalFloorArea": 1512, "ANNEE_CONS": 1980 }, "geometry": { "type": "Polygon", "coordinates": [ [ [ -73.586673951057165, 45.524838247152971 ], [ -73.586568305570552, 45.524952686098082 ], [ -73.58662307714782, 45.524979547084882 ], [ -73.586731877320233, 45.525032846618785 ], [ -73.586815478124791, 45.525073847297307 ], [ -73.586820877038548, 45.525068346972965 ], [ -73.586874077743531, 45.525094146322125 ], [ -73.586895378594193, 45.525104346900406 ], [ -73.586943277305565, 45.525127046703169 ], [ -73.587016976014752, 45.525161993449707 ], [ -73.587125015986871, 45.525044959829344 ], [ -73.587121277851907, 45.525043246686081 ], [ -73.587092777275416, 45.525074446965931 ], [ -73.587032277294412, 45.525047146948445 ], [ -73.587039678023586, 45.525038947369048 ], [ -73.587033777622878, 45.525036346828877 ], [ -73.586929477897286, 45.524990346357107 ], [ -73.58664877714105, 45.524866746179505 ], [ -73.586674177796809, 45.524838346863149 ], [ -73.586673951057165, 45.524838247152971 ] ] ] } }, +{ "type": "Feature", "properties": { "OBJECTID": 160896, "id": "01024652", "CIVIQUE_DE": 350, "CIVIQUE_FI": 350, "NOM_RUE": "rue Boucher (MTL)", "MUNICIPALI": 50, "CODE_UTILI": 1000, "LIBELLE_UT": "Logement", "CATEGORIE_": "Régulier", "Hieght_LiD": 22, "AREA_NEW": 863, "MBG_Width": 23, "MBG_Length": 48, "MBG_Orientation": 121, "Shape_Length": 142, "Shape_Area": 1115, "BuildingCategory": "detached", "BuildingVolume": 18986, "AspectRatio": 2.038, "SurfacetoVolumeRatio": 0.045, "FloorNu_RawTax": 4, "FloorNu_RawTax.1": 4, "Floor_frmHieght": 6, "TotalFloorArea": 5178, "ANNEE_CONS": 1991 }, "geometry": { "type": "Polygon", "coordinates": [ [ [ -73.591937079830473, 45.5275066477327 ], [ -73.591937179849452, 45.52750674753436 ], [ -73.591964178776365, 45.527466247428634 ], [ -73.592026878777105, 45.527486947227878 ], [ -73.592027580168264, 45.527486846780647 ], [ -73.592013879829011, 45.527422746963936 ], [ -73.591984880030196, 45.527286547239505 ], [ -73.591984579182366, 45.527286547481765 ], [ -73.591875879019881, 45.527239846914121 ], [ -73.591626180051733, 45.527132446482739 ], [ -73.591624679856523, 45.527134147482663 ], [ -73.591472379004458, 45.527302947133734 ], [ -73.59147727935661, 45.527305146916163 ], [ -73.591543180175734, 45.527334146336074 ], [ -73.59156227958276, 45.527312847127916 ], [ -73.591817879694347, 45.527425747269653 ], [ -73.591794879188384, 45.527451547052145 ], [ -73.591794980324778, 45.52745154697088 ], [ -73.591937079830473, 45.5275066477327 ] ] ] } }, +{ "type": "Feature", "properties": { "OBJECTID": 158453, "id": "01026412", "CIVIQUE_DE": 3964, "CIVIQUE_FI": 3964, "NOM_RUE": "rue Rivard (MTL)", "MUNICIPALI": 50, "CODE_UTILI": 1000, "LIBELLE_UT": "Logement", "CATEGORIE_": "Régulier", "Hieght_LiD": 13, "AREA_NEW": 155, "MBG_Width": 12, "MBG_Length": 13, "MBG_Orientation": 33, "Shape_Length": 51, "Shape_Area": 161, "BuildingCategory": "semi-attached", "BuildingVolume": 2015, "AspectRatio": 1.053, "SurfacetoVolumeRatio": 0.077, "FloorNu_RawTax": 3, "FloorNu_RawTax.1": 3, "Floor_frmHieght": 4, "TotalFloorArea": 620, "ANNEE_CONS": 1981 }, "geometry": { "type": "Polygon", "coordinates": [ [ [ -73.574691673036824, 45.520801446234479 ], [ -73.574606373743563, 45.520895346331137 ], [ -73.574608572928923, 45.520896346417111 ], [ -73.574741150104259, 45.52095245567763 ], [ -73.574829542976886, 45.520856483821881 ], [ -73.574691673036824, 45.520801446234479 ] ] ] } }, +{ "type": "Feature", "properties": { "OBJECTID": 160314, "id": "01027192", "CIVIQUE_DE": 3876, "CIVIQUE_FI": 3880, "NOM_RUE": "rue de Mentana (MTL)", "MUNICIPALI": 50, "CODE_UTILI": 1000, "LIBELLE_UT": "Logement", "CATEGORIE_": "Régulier", "Hieght_LiD": 12, "AREA_NEW": 366, "MBG_Width": 16, "MBG_Length": 32, "MBG_Orientation": 110, "Shape_Length": 96, "Shape_Area": 507, "BuildingCategory": "semi-attached", "BuildingVolume": 4392, "AspectRatio": 2.009, "SurfacetoVolumeRatio": 0.083, "FloorNu_RawTax": 3, "FloorNu_RawTax.1": 3, "Floor_frmHieght": 3, "TotalFloorArea": 1098, "ANNEE_CONS": 1980 }, "geometry": { "type": "Polygon", "coordinates": [ [ [ -73.570800071665957, 45.522686946460304 ], [ -73.57093107256577, 45.522726047825749 ], [ -73.571028271866595, 45.522613046698432 ], [ -73.570955272361019, 45.522582046683695 ], [ -73.57092677184869, 45.522615346769584 ], [ -73.570836471663341, 45.522577047118958 ], [ -73.570681923964358, 45.522511513915816 ], [ -73.570599160784397, 45.522601741674258 ], [ -73.57068447255547, 45.522631046198299 ], [ -73.570683872035971, 45.52263194640998 ], [ -73.5706905727058, 45.522641647038853 ], [ -73.570695971264669, 45.522651146908998 ], [ -73.570720172024352, 45.522643346512112 ], [ -73.570741072289536, 45.522674046912528 ], [ -73.570768873124834, 45.522664547177342 ], [ -73.570786672496425, 45.522690747590424 ], [ -73.570800772812049, 45.522685647556457 ], [ -73.570800071665957, 45.522686946460304 ] ] ] } }, +{ "type": "Feature", "properties": { "OBJECTID": 160961, "id": "01027868", "CIVIQUE_DE": 1315, "CIVIQUE_FI": 1315, "NOM_RUE": "rue Gilford (MTL)", "MUNICIPALI": 50, "CODE_UTILI": 1000, "LIBELLE_UT": "Logement", "CATEGORIE_": "Régulier", "Hieght_LiD": 25, "AREA_NEW": 792, "MBG_Width": 21, "MBG_Length": 39, "MBG_Orientation": 33, "Shape_Length": 119, "Shape_Area": 799, "BuildingCategory": "detached", "BuildingVolume": 19800, "AspectRatio": 1.892, "SurfacetoVolumeRatio": 0.04, "FloorNu_RawTax": 7, "FloorNu_RawTax.1": 7, "Floor_frmHieght": 7, "TotalFloorArea": 5544, "ANNEE_CONS": 1982 }, "geometry": { "type": "Polygon", "coordinates": [ [ [ -73.58083517681375, 45.531873048325558 ], [ -73.580835076949739, 45.531873048396221 ], [ -73.580854575894378, 45.531884548031371 ], [ -73.580836876619628, 45.53189954827026 ], [ -73.580834276336617, 45.531902247826601 ], [ -73.581032076072006, 45.531993748148139 ], [ -73.581035576182472, 45.531995348277754 ], [ -73.581308475569898, 45.531702748189019 ], [ -73.581094575638417, 45.5316043481116 ], [ -73.58108917684747, 45.531601847693175 ], [ -73.58083517681375, 45.531873048325558 ] ] ] } }, +{ "type": "Feature", "properties": { "OBJECTID": 160929, "id": "01030953", "CIVIQUE_DE": 5115, "CIVIQUE_FI": 5115, "NOM_RUE": "rue Clark (MTL)", "MUNICIPALI": 50, "CODE_UTILI": 1000, "LIBELLE_UT": "Logement", "CATEGORIE_": "Régulier", "Hieght_LiD": 14, "AREA_NEW": 181, "MBG_Width": 13, "MBG_Length": 17, "MBG_Orientation": 33, "Shape_Length": 60, "Shape_Area": 217, "BuildingCategory": "semi-attached", "BuildingVolume": 2534, "AspectRatio": 1.366, "SurfacetoVolumeRatio": 0.071, "FloorNu_RawTax": 2, "FloorNu_RawTax.1": 2, "Floor_frmHieght": 4, "TotalFloorArea": 724, "ANNEE_CONS": 1983 }, "geometry": { "type": "Polygon", "coordinates": [ [ [ -73.593727979935949, 45.522833645694313 ], [ -73.593622379981483, 45.522943045763455 ], [ -73.593627979641539, 45.5229456462108 ], [ -73.593757046904472, 45.523005285227981 ], [ -73.59376545085415, 45.522996062168147 ], [ -73.593790280472035, 45.522967946078182 ], [ -73.593790847971491, 45.522968193968069 ], [ -73.593875827697602, 45.522874944824011 ], [ -73.59387543312792, 45.522874766080079 ], [ -73.593855979643365, 45.52289494658158 ], [ -73.593791679795828, 45.52286404669664 ], [ -73.593727979935949, 45.522833645694313 ] ] ] } }, +{ "type": "Feature", "properties": { "OBJECTID": 159711, "id": "01031631", "CIVIQUE_DE": 5222, "CIVIQUE_FI": 5222, "NOM_RUE": "avenue Casgrain (MTL)", "MUNICIPALI": 50, "CODE_UTILI": 1000, "LIBELLE_UT": "Logement", "CATEGORIE_": "Régulier", "Hieght_LiD": 10, "AREA_NEW": 146, "MBG_Width": 10, "MBG_Length": 15, "MBG_Orientation": 32, "Shape_Length": 50, "Shape_Area": 147, "BuildingCategory": "semi-attached", "BuildingVolume": 1460, "AspectRatio": 1.607, "SurfacetoVolumeRatio": 0.1, "FloorNu_RawTax": 2, "FloorNu_RawTax.1": 2, "Floor_frmHieght": 3, "TotalFloorArea": 438, "ANNEE_CONS": 1983 }, "geometry": { "type": "Polygon", "coordinates": [ [ [ -73.594000879767933, 45.525218447055174 ], [ -73.594043080289808, 45.525236947218026 ], [ -73.5941039805478, 45.525264146909009 ], [ -73.594208881213859, 45.52514714696995 ], [ -73.594107197565634, 45.525102137119283 ], [ -73.594000390389937, 45.525218232396192 ], [ -73.594000879767933, 45.525218447055174 ] ] ] } }, +{ "type": "Feature", "properties": { "OBJECTID": 162724, "id": "01093229", "CIVIQUE_DE": 5180, "CIVIQUE_FI": 5180, "NOM_RUE": "rue Drolet (MTL)", "MUNICIPALI": 50, "CODE_UTILI": 1000, "LIBELLE_UT": "Logement", "CATEGORIE_": "Régulier", "Hieght_LiD": 13, "AREA_NEW": 162, "MBG_Width": 13, "MBG_Length": 13, "MBG_Orientation": 32, "Shape_Length": 52, "Shape_Area": 171, "BuildingCategory": "fully-attached", "BuildingVolume": 2106, "AspectRatio": 1.061, "SurfacetoVolumeRatio": 0.077, "FloorNu_RawTax": 3, "FloorNu_RawTax.1": 3, "Floor_frmHieght": 4, "TotalFloorArea": 648, "ANNEE_CONS": 1989 }, "geometry": { "type": "Polygon", "coordinates": [ [ [ -73.591401779194541, 45.526781846358489 ], [ -73.591391179875345, 45.526792546731429 ], [ -73.591396178918018, 45.526794847220536 ], [ -73.591449340007159, 45.526819974082471 ], [ -73.591532003408886, 45.52672522496804 ], [ -73.591538651998277, 45.526716092568755 ], [ -73.591526179377823, 45.526710747618488 ], [ -73.591400782956626, 45.526656800065901 ], [ -73.591320310492023, 45.526757487175246 ], [ -73.591374078538919, 45.526782746763345 ], [ -73.59137467927394, 45.526782946946994 ], [ -73.591384179409957, 45.526773347058757 ], [ -73.591401779194541, 45.526781846358489 ] ] ] } } +] +} diff --git a/base_case_modelling.py b/scripts/base_case_modelling.py similarity index 100% rename from base_case_modelling.py rename to scripts/base_case_modelling.py diff --git a/monthly_dhw.py b/scripts/monthly_dhw.py similarity index 100% rename from monthly_dhw.py rename to scripts/monthly_dhw.py diff --git a/monthly_hvac_plots.py b/scripts/monthly_hvac_plots.py similarity index 100% rename from monthly_hvac_plots.py rename to scripts/monthly_hvac_plots.py diff --git a/peak_load.py b/scripts/peak_load.py similarity index 100% rename from peak_load.py rename to scripts/peak_load.py