From e48dec81cf431f27e93a63816da1501875b4ac55 Mon Sep 17 00:00:00 2001 From: p_monsalvete Date: Fri, 12 May 2023 09:27:29 -0400 Subject: [PATCH] added lod to the city_objects --- .../energy_systems/nrcan_catalog.py | 0 hub/city_model_structure/building.py | 76 +++----- hub/city_model_structure/city_object.py | 10 + .../generic_generation_system.py | 43 +++++ .../insel/insel_monthly_energy_balance.py | 19 +- ...l_demand_type_to_hub_energy_demand_type.py | 22 +++ hub/helpers/dictionaries.py | 9 + hub/helpers/peak_loads.py | 172 +++++++++++------- hub/imports/construction_factory.py | 4 + ...ontreal_custom_energy_system_parameters.py | 26 +-- hub/imports/energy_systems_factory.py | 10 +- hub/imports/geometry/citygml.py | 2 + hub/imports/geometry/geojson.py | 4 +- hub/imports/geometry/gpandas.py | 2 + hub/imports/geometry/obj.py | 3 + hub/imports/geometry/rhino.py | 1 + .../results/insel_monthly_energry_balance.py | 13 +- .../results/simplified_radiosity_algorithm.py | 3 + hub/imports/usage_factory.py | 8 +- hub/imports/weather/epw_weather_parameters.py | 3 + hub/unittests/test_geometry_factory.py | 5 +- hub/unittests/test_systems_factory.py | 63 ++++--- 22 files changed, 332 insertions(+), 166 deletions(-) delete mode 100644 hub/catalog_factories/energy_systems/nrcan_catalog.py create mode 100644 hub/helpers/data/montreal_demand_type_to_hub_energy_demand_type.py diff --git a/hub/catalog_factories/energy_systems/nrcan_catalog.py b/hub/catalog_factories/energy_systems/nrcan_catalog.py deleted file mode 100644 index e69de29b..00000000 diff --git a/hub/city_model_structure/building.py b/hub/city_model_structure/building.py index 05b4c374..48bf7e6f 100644 --- a/hub/city_model_structure/building.py +++ b/hub/city_model_structure/building.py @@ -13,7 +13,7 @@ import pandas as pd from hub.hub_logger import logger import hub.helpers.constants as cte -import hub.helpers.peak_loads as pl +from hub.helpers.peak_loads import PeakLoads from hub.city_model_structure.building_demand.surface import Surface from hub.city_model_structure.city_object import CityObject from hub.city_model_structure.building_demand.household import Household @@ -50,8 +50,6 @@ class Building(CityObject): self._domestic_hot_water_heat_demand = dict() self._heating_consumption = dict() self._cooling_consumption = dict() - self._lighting_electrical_consumption = dict() - self._appliances_electrical_consumption = dict() self._domestic_hot_water_consumption = dict() self._onsite_electrical_production = dict() self._eave_height = None @@ -367,31 +365,40 @@ class Building(CityObject): self._domestic_hot_water_heat_demand = value @property - def heating_peak_load(self) -> dict: + def heating_peak_load(self) -> Union[None, dict]: """ Get heating peak load in W :return: dict{DataFrame(float)} """ results = {} + # todo: needed?? + monthly_values = None if cte.HOUR in self.heating: - monthly_values = pl.peak_loads_from_hourly(self.heating[cte.HOUR][next(iter(self.heating[cte.HOUR]))].values) + monthly_values = PeakLoads().\ + peak_loads_from_hourly(self.heating[cte.HOUR][next(iter(self.heating[cte.HOUR]))].values) else: - monthly_values = pl.heating_peak_loads_from_methodology(self) + monthly_values = PeakLoads(self).heating_peak_loads_from_methodology + if monthly_values is None: + return None results[cte.MONTH] = pd.DataFrame(monthly_values, columns=['heating peak loads']) results[cte.YEAR] = pd.DataFrame([max(monthly_values)], columns=['heating peak loads']) return results @property - def cooling_peak_load(self) -> dict: + def cooling_peak_load(self) -> Union[None, dict]: """ Get cooling peak load in W :return: dict{DataFrame(float)} """ results = {} + monthly_values = None if cte.HOUR in self.cooling: - monthly_values = pl.peak_loads_from_hourly(self.cooling[cte.HOUR][next(iter(self.cooling[cte.HOUR]))]) + # todo: .values???????? Like heating + monthly_values = PeakLoads().peak_loads_from_hourly(self.cooling[cte.HOUR][next(iter(self.cooling[cte.HOUR]))]) else: - monthly_values = pl.cooling_peak_loads_from_methodology(self) + monthly_values = PeakLoads(self).cooling_peak_loads_from_methodology + if monthly_values is None: + return None results[cte.MONTH] = pd.DataFrame(monthly_values, columns=['cooling peak loads']) results[cte.YEAR] = pd.DataFrame([max(monthly_values)], columns=['cooling peak loads']) return results @@ -532,7 +539,7 @@ class Building(CityObject): return: dict """ for heating_demand_key in self.heating: - demand = self.heating[heating_demand_key][0] + demand = self.heating[heating_demand_key][cte.INSEL_MEB] consumption_type = cte.HEATING final_energy_consumed = self._calculate_consumption(consumption_type, demand) self._heating_consumption[heating_demand_key] = final_energy_consumed @@ -545,7 +552,7 @@ class Building(CityObject): return: dict """ for cooling_demand_key in self.cooling: - demand = self.cooling[cooling_demand_key][0] + demand = self.cooling[cooling_demand_key][cte.INSEL_MEB] consumption_type = cte.COOLING final_energy_consumed = self._calculate_consumption(consumption_type, demand) self._cooling_consumption[cooling_demand_key] = final_energy_consumed @@ -558,44 +565,18 @@ class Building(CityObject): return: dict """ for domestic_hot_water_demand_key in self.domestic_hot_water_heat_demand: - demand = self.domestic_hot_water_heat_demand[domestic_hot_water_demand_key][0] + demand = self.domestic_hot_water_heat_demand[domestic_hot_water_demand_key][cte.INSEL_MEB] consumption_type = cte.DOMESTIC_HOT_WATER final_energy_consumed = self._calculate_consumption(consumption_type, demand) self._domestic_hot_water_consumption[domestic_hot_water_demand_key] = final_energy_consumed return self._domestic_hot_water_consumption - @property - def lighting_electrical_consumption(self): - """ - Get energy consumption for lighting according to the electricity system installed - return: dict - """ - for lighting_demand_key in self.lighting_electrical_demand: - demand = self.lighting_electrical_demand[lighting_demand_key][0] - consumption_type = cte.ELECTRICITY - final_energy_consumed = self._calculate_consumption(consumption_type, demand) - self._lighting_electrical_consumption[lighting_demand_key] = final_energy_consumed - return self._lighting_electrical_consumption - - @property - def appliances_electrical_consumption(self): - """ - Get energy consumption for appliances according to the electricity system installed - return: dict - """ - for appliances_demand_key in self.appliances_electrical_demand: - demand = self.appliances_electrical_demand[appliances_demand_key][0] - consumption_type = cte.ELECTRICITY - final_energy_consumed = self._calculate_consumption(consumption_type, demand) - self._appliances_electrical_consumption[appliances_demand_key] = final_energy_consumed - return self._appliances_electrical_consumption - def _calculate_consumption(self, consumption_type, demand): # todo: modify when COP depends on the hour coefficient_of_performance = 0 for energy_system in self.energy_systems: for demand_type in energy_system.demand_types: - if demand_type == consumption_type: + if demand_type.lower() == consumption_type.lower(): if consumption_type == cte.HEATING or consumption_type == cte.DOMESTIC_HOT_WATER: coefficient_of_performance = energy_system.generation_system.heat_efficiency elif consumption_type == cte.COOLING: @@ -620,18 +601,11 @@ class Building(CityObject): for energy_system in self.energy_systems: if energy_system.generation_system.type == cte.PHOTOVOLTAIC: _efficiency = energy_system.generation_system.electricity_efficiency - _temporal_cases = self.roofs[0].global_irradiance.keys() self._onsite_electrical_production = {} - for _case in _temporal_cases: - _results = [] + for _key in self.roofs[0].global_irradiance.keys(): + _results = [0 for _ in range(0, len(self.roofs[0].global_irradiance[_key]))] for surface in self.surfaces: - for i, value in enumerate(surface.global_irradiance[_case]): - if len(_results) == 0: - _collector_production = value * _efficiency \ - * surface.perimeter_area * surface.solar_collectors_area_reduction_factor - else: - _collector_production = _results[i] * value * _efficiency \ - * surface.perimeter_area * surface.solar_collectors_area_reduction_factor - _results.append(_collector_production) - self._onsite_electrical_production[_case] = _results + _results = [x + y * _efficiency * surface.perimeter_area * surface.solar_collectors_area_reduction_factor + for x, y in zip(_results, surface.global_irradiance[_key])] + self._onsite_electrical_production[_key] = _results return self._onsite_electrical_production diff --git a/hub/city_model_structure/city_object.py b/hub/city_model_structure/city_object.py index 3e688c26..c8312d9a 100644 --- a/hub/city_model_structure/city_object.py +++ b/hub/city_model_structure/city_object.py @@ -8,6 +8,7 @@ Project Coder Guille Gutierrez guillermo.gutierrezmorote@concordia.ca from __future__ import annotations from typing import List, Union +from hub.city_model_structure.level_of_detail import LevelOfDetail from hub.city_model_structure.iot.sensor import Sensor from hub.city_model_structure.building_demand.surface import Surface from hub.city_model_structure.attributes.polyhedron import Polyhedron @@ -21,6 +22,7 @@ class CityObject: """ def __init__(self, name, surfaces): self._name = name + self._level_of_detail = LevelOfDetail() self._surfaces = surfaces self._type = None self._city_object_lower_corner = None @@ -43,6 +45,14 @@ class CityObject: self._sensors = [] self._neighbours = None + @property + def level_of_detail(self) -> LevelOfDetail: + """ + Get level of detail of different aspects of the city: geometry, construction and usage + :return: LevelOfDetail + """ + return self._level_of_detail + @property def name(self): """ diff --git a/hub/city_model_structure/energy_systems/generic_generation_system.py b/hub/city_model_structure/energy_systems/generic_generation_system.py index 1f31bae2..2ddd686b 100644 --- a/hub/city_model_structure/energy_systems/generic_generation_system.py +++ b/hub/city_model_structure/energy_systems/generic_generation_system.py @@ -26,7 +26,9 @@ class GenericGenerationSystem: self._source_temperature = None self._source_mass_flow = None self._storage_capacity = None + self._storage = None self._auxiliary_equipment = None + self._peak_coverages = None @property def type(self): @@ -82,6 +84,15 @@ class GenericGenerationSystem: Get heat_power in W :return: float """ + if building.heating_peak_load is not None: + _generation_system.heat_power = building.heating_peak_load + if building.cooling_peak_load is not None: + _generation_system.cooling_power = building.cooling_peak_load + # the only system that generates electricity in the montreal custom catalog is PV systems + _generation_system.electricity_power = 0 + if archetype_generation_equipment.storage: + # todo: calculate storage capacity in J + _generation_system.storage_capacity = 0 return self._heat_power @heat_power.setter @@ -220,6 +231,22 @@ class GenericGenerationSystem: """ self._storage_capacity = value + @property + def storage(self): + """ + Get boolean storage exists + :return: bool + """ + return self._storage + + @storage.setter + def storage(self, value): + """ + Set boolean storage exists + :return: bool + """ + self._storage = value + @property def auxiliary_equipment(self) -> Union[None, GenericGenerationSystem]: """ @@ -235,3 +262,19 @@ class GenericGenerationSystem: :param value: GenerationSystem """ self._auxiliary_equipment = value + + @property + def peak_coverages(self) -> dict: + """ + Get ratio of each energy type power peak covered by the system + :return: dict {Heating: value, Cooling: value, Domestic Hot Water: value, Electricity: value} + """ + return self._peak_coverages + + @peak_coverages.setter + def peak_coverages(self, value): + """ + Set ratio of each energy type power peak covered by the system + :param value: dict + """ + self._peak_coverages = value diff --git a/hub/exports/building_energy/insel/insel_monthly_energy_balance.py b/hub/exports/building_energy/insel/insel_monthly_energy_balance.py index 28a3b9e3..6cd228c5 100644 --- a/hub/exports/building_energy/insel/insel_monthly_energy_balance.py +++ b/hub/exports/building_energy/insel/insel_monthly_energy_balance.py @@ -51,7 +51,6 @@ class InselMonthlyEnergyBalance(Insel): ) self._export() - def _export(self): for i_file, content in enumerate(self._contents): file_name = self._insel_files_paths[i_file] @@ -63,7 +62,7 @@ class InselMonthlyEnergyBalance(Insel): levels_of_detail = self._city.level_of_detail if levels_of_detail.geometry is None: raise Exception(f'Level of detail of geometry not assigned') - if levels_of_detail.geometry < 0.5: + if levels_of_detail.geometry < 1: raise Exception(f'Level of detail of geometry = {levels_of_detail.geometry}. Required minimum level 0.5') if levels_of_detail.construction is None: raise Exception(f'Level of detail of construction not assigned') @@ -73,13 +72,15 @@ class InselMonthlyEnergyBalance(Insel): raise Exception(f'Level of detail of usage not assigned') if levels_of_detail.usage < 1: raise Exception(f'Level of detail of usage = {levels_of_detail.usage}. Required minimum level 1') - for building in self._city.buildings: - if cte.MONTH not in building.external_temperature: - raise Exception(f'Building {building.name} does not have external temperature assigned') - for surface in building.surfaces: - if surface.type != cte.GROUND: - if cte.MONTH not in surface.global_irradiance: - raise Exception(f'Building {building.name} does not have global irradiance on surfaces assigned') + if levels_of_detail.weather is None: + raise Exception(f'Level of detail of usage not assigned') + if levels_of_detail.weather < 1: + raise Exception(f'Level of detail of weather = {levels_of_detail.weather}. Required minimum level 1') + if levels_of_detail.surface_radiation is None: + raise Exception(f'Level of detail of usage not assigned') + if levels_of_detail.surface_radiation < 1: + raise Exception(f'Level of detail of surface radiation = {levels_of_detail.surface_radiation}. ' + f'Required minimum level 1') @staticmethod def _generate_meb_template(building, insel_outputs_path, radiation_calculation_method, weather_format): diff --git a/hub/helpers/data/montreal_demand_type_to_hub_energy_demand_type.py b/hub/helpers/data/montreal_demand_type_to_hub_energy_demand_type.py new file mode 100644 index 00000000..26d0d454 --- /dev/null +++ b/hub/helpers/data/montreal_demand_type_to_hub_energy_demand_type.py @@ -0,0 +1,22 @@ +""" +Dictionaries module for Montreal system catalog demand types to hub energy demand types +SPDX - License - Identifier: LGPL - 3.0 - or -later +Copyright © 2023 Concordia CERC group +Project Coder Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca +""" + +import hub.helpers.constants as cte + + +class MontrealDemandTypeToHubEnergyDemandType: + + def __init__(self): + self._dictionary = {'heating': cte.HEATING, + 'cooling': cte.COOLING, + 'domestic_hot_water': cte.DOMESTIC_HOT_WATER, + 'electricity': cte.ELECTRICITY, + } + + @property + def dictionary(self) -> dict: + return self._dictionary diff --git a/hub/helpers/dictionaries.py b/hub/helpers/dictionaries.py index 7cb94d79..c596857c 100644 --- a/hub/helpers/dictionaries.py +++ b/hub/helpers/dictionaries.py @@ -15,6 +15,7 @@ from hub.helpers.data.hub_usage_to_comnet_usage import HubUsageToComnetUsage from hub.helpers.data.hub_usage_to_hft_usage import HubUsageToHftUsage from hub.helpers.data.hub_usage_to_nrcan_usage import HubUsageToNrcanUsage from hub.helpers.data.montreal_system_to_hub_energy_generation_system import MontrealSystemToHubEnergyGenerationSystem +from hub.helpers.data.montreal_demand_type_to_hub_energy_demand_type import MontrealDemandTypeToHubEnergyDemandType from hub.helpers.data.hub_function_to_montreal_custom_costs_function import HubFunctionToMontrealCustomCostsFunction @@ -99,6 +100,14 @@ class Dictionaries: Get montreal custom system names to hub energy system names, transformation dictionary """ return MontrealSystemToHubEnergyGenerationSystem().dictionary + + @property + def montreal_demand_type_to_hub_energy_demand_type(self): + """ + Get montreal custom system demand type to hub energy demand type, transformation dictionary + """ + return MontrealDemandTypeToHubEnergyDemandType().dictionary + @property def hub_function_to_montreal_custom_costs_function(self) -> dict: """ diff --git a/hub/helpers/peak_loads.py b/hub/helpers/peak_loads.py index 11bfc208..a9bcf58f 100644 --- a/hub/helpers/peak_loads.py +++ b/hub/helpers/peak_loads.py @@ -1,71 +1,121 @@ +""" +Cooling and Heating peak loads module +SPDX - License - Identifier: LGPL - 3.0 - or -later +Copyright © 2023 Concordia CERC group +Project Coder Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca +Code contributors: Guille Gutierrez guillermo.gutierrezmorote@concordia.ca +""" + +import sys import math +from hub.hub_logger import logger import hub.helpers.constants as cte from hub.helpers.peak_calculation.loads_calculation import LoadsCalculation _MONTH_STARTING_HOUR = [0, 744, 1416, 2160, 2880, 3624, 4344, 5088, 5832, 6552, 7296, 8016, math.inf] -def peak_loads_from_hourly(hourly_values): - month = 1 - peaks = [0 for _ in range(12)] - for i, value in enumerate(hourly_values): - if _MONTH_STARTING_HOUR[month] <= i: - month += 1 - if value > peaks[month-1]: - peaks[month-1] = value - return peaks -def heating_peak_loads_from_methodology(building): - monthly_heating_loads = [] - ambient_temperature = building.external_temperature[cte.HOUR]['epw'] - for month in range(0, 12): - ground_temperature = building.ground_temperature[cte.MONTH]['2'][month] - heating_ambient_temperature = 100 - start_hour = _MONTH_STARTING_HOUR[month] - end_hour = 8760 - if month < 11: - end_hour = _MONTH_STARTING_HOUR[month + 1] - for hour in range(start_hour, end_hour): - temperature = ambient_temperature[hour] - if temperature < heating_ambient_temperature: - heating_ambient_temperature = temperature - loads = LoadsCalculation(building) - heating_load_transmitted = loads.get_heating_transmitted_load(heating_ambient_temperature, ground_temperature) - heating_load_ventilation_sensible = loads.get_heating_ventilation_load_sensible(heating_ambient_temperature) - heating_load_ventilation_latent = 0 - heating_load = heating_load_transmitted + heating_load_ventilation_sensible + heating_load_ventilation_latent - if heating_load < 0: - heating_load = 0 - monthly_heating_loads.append(heating_load) - return monthly_heating_loads +class PeakLoads: + """ + PeakLoads class + """ + def __init__(self, building=None): + self._building = building -def cooling_peak_loads_from_methodology(building): - monthly_cooling_loads = [] - ambient_temperature = building.external_temperature[cte.HOUR]['epw'] - for month in range(0, 12): - ground_temperature = building.ground_temperature[cte.MONTH]['2'][month] - cooling_ambient_temperature = -100 - cooling_calculation_hour = -1 - start_hour = _MONTH_STARTING_HOUR[month] - end_hour = 8760 - if month < 11: - end_hour = _MONTH_STARTING_HOUR[month + 1] - for hour in range(start_hour, end_hour): - temperature = ambient_temperature[hour] - if temperature > cooling_ambient_temperature: - cooling_ambient_temperature = temperature - cooling_calculation_hour = hour - loads = LoadsCalculation(building) - cooling_load_transmitted = loads.get_cooling_transmitted_load(cooling_ambient_temperature, ground_temperature) - cooling_load_renovation_sensible = loads.get_cooling_ventilation_load_sensible(cooling_ambient_temperature) - cooling_load_internal_gains_sensible = loads.get_internal_load_sensible() - cooling_load_radiation = loads.get_radiation_load('sra', cooling_calculation_hour) - cooling_load_sensible = cooling_load_transmitted + cooling_load_renovation_sensible - cooling_load_radiation \ - - cooling_load_internal_gains_sensible + def _can_be_calculated(self): + levels_of_detail = self._building.level_of_detail + can_be_calculated = True + if levels_of_detail.geometry is None: + can_be_calculated = False + if levels_of_detail.geometry < 1: + can_be_calculated = False + if levels_of_detail.construction is None: + can_be_calculated = False + if levels_of_detail.construction < 1: + can_be_calculated = False + if levels_of_detail.usage is None: + can_be_calculated = False + if levels_of_detail.usage < 1: + can_be_calculated = False + if levels_of_detail.weather is None: + can_be_calculated = False + if levels_of_detail.weather < 2: + can_be_calculated = False + if levels_of_detail.surface_radiation is None: + can_be_calculated = False + if levels_of_detail.surface_radiation < 2: + can_be_calculated = False + return can_be_calculated - cooling_load_latent = 0 - cooling_load = cooling_load_sensible + cooling_load_latent - if cooling_load > 0: - cooling_load = 0 - monthly_cooling_loads.append(abs(cooling_load)) - return monthly_cooling_loads + @staticmethod + def peak_loads_from_hourly(hourly_values): + month = 1 + peaks = [0 for _ in range(12)] + for i, value in enumerate(hourly_values): + if _MONTH_STARTING_HOUR[month] <= i: + month += 1 + if value > peaks[month-1]: + peaks[month-1] = value + return peaks + + @property + def heating_peak_loads_from_methodology(self): + if self._can_be_calculated(): + return None + monthly_heating_loads = [] + ambient_temperature = self._building.external_temperature[cte.HOUR]['epw'] + for month in range(0, 12): + ground_temperature = self._building.ground_temperature[cte.MONTH]['2'][month] + heating_ambient_temperature = 100 + start_hour = _MONTH_STARTING_HOUR[month] + end_hour = 8760 + if month < 11: + end_hour = _MONTH_STARTING_HOUR[month + 1] + for hour in range(start_hour, end_hour): + temperature = ambient_temperature[hour] + if temperature < heating_ambient_temperature: + heating_ambient_temperature = temperature + loads = LoadsCalculation(self._building) + heating_load_transmitted = loads.get_heating_transmitted_load(heating_ambient_temperature, ground_temperature) + heating_load_ventilation_sensible = loads.get_heating_ventilation_load_sensible(heating_ambient_temperature) + heating_load_ventilation_latent = 0 + heating_load = heating_load_transmitted + heating_load_ventilation_sensible + heating_load_ventilation_latent + if heating_load < 0: + heating_load = 0 + monthly_heating_loads.append(heating_load) + return monthly_heating_loads + + @property + def cooling_peak_loads_from_methodology(self): + if self._can_be_calculated(): + return None + monthly_cooling_loads = [] + ambient_temperature = self._building.external_temperature[cte.HOUR]['epw'] + for month in range(0, 12): + ground_temperature = self._building.ground_temperature[cte.MONTH]['2'][month] + cooling_ambient_temperature = -100 + cooling_calculation_hour = -1 + start_hour = _MONTH_STARTING_HOUR[month] + end_hour = 8760 + if month < 11: + end_hour = _MONTH_STARTING_HOUR[month + 1] + for hour in range(start_hour, end_hour): + temperature = ambient_temperature[hour] + if temperature > cooling_ambient_temperature: + cooling_ambient_temperature = temperature + cooling_calculation_hour = hour + loads = LoadsCalculation(self._building) + cooling_load_transmitted = loads.get_cooling_transmitted_load(cooling_ambient_temperature, ground_temperature) + cooling_load_renovation_sensible = loads.get_cooling_ventilation_load_sensible(cooling_ambient_temperature) + cooling_load_internal_gains_sensible = loads.get_internal_load_sensible() + cooling_load_radiation = loads.get_radiation_load('sra', cooling_calculation_hour) + cooling_load_sensible = cooling_load_transmitted + cooling_load_renovation_sensible - cooling_load_radiation \ + - cooling_load_internal_gains_sensible + + cooling_load_latent = 0 + cooling_load = cooling_load_sensible + cooling_load_latent + if cooling_load > 0: + cooling_load = 0 + monthly_cooling_loads.append(abs(cooling_load)) + return monthly_cooling_loads diff --git a/hub/imports/construction_factory.py b/hub/imports/construction_factory.py index f27d05f7..6c903a17 100644 --- a/hub/imports/construction_factory.py +++ b/hub/imports/construction_factory.py @@ -33,6 +33,8 @@ class ConstructionFactory: """ NrelPhysicsParameters(self._city).enrich_buildings() self._city.level_of_detail.construction = 2 + for building in self._city.buildings: + building.level_of_detail.construction = 2 def _nrcan(self): """ @@ -40,6 +42,8 @@ class ConstructionFactory: """ NrcanPhysicsParameters(self._city).enrich_buildings() self._city.level_of_detail.construction = 2 + for building in self._city.buildings: + building.level_of_detail.construction = 2 def enrich(self): """ diff --git a/hub/imports/energy_systems/montreal_custom_energy_system_parameters.py b/hub/imports/energy_systems/montreal_custom_energy_system_parameters.py index 4d5e25c6..11bfe59f 100644 --- a/hub/imports/energy_systems/montreal_custom_energy_system_parameters.py +++ b/hub/imports/energy_systems/montreal_custom_energy_system_parameters.py @@ -7,14 +7,13 @@ Project Coder Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca import sys -from hub.hub_logger import get_logger +from hub.hub_logger import logger from hub.catalog_factories.energy_systems_catalog_factory import EnergySystemsCatalogFactory from hub.city_model_structure.energy_systems.generic_energy_system import GenericEnergySystem from hub.city_model_structure.energy_systems.generic_generation_system import GenericGenerationSystem from hub.city_model_structure.energy_systems.generic_distribution_system import GenericDistributionSystem from hub.helpers.dictionaries import Dictionaries - -logger = get_logger() +import hub.helpers.constants as cte class MontrealCustomEnergySystemParameters: @@ -48,17 +47,15 @@ class MontrealCustomEnergySystemParameters: building_systems = [] for equipment in archetype.equipments: energy_system = GenericEnergySystem() - energy_system.demand_types = equipment.demand_types + _hub_demand_types = [] + for demand_type in equipment.demand_types: + _hub_demand_types.append(Dictionaries().montreal_demand_type_to_hub_energy_demand_type[demand_type]) + energy_system.demand_types = _hub_demand_types _generation_system = GenericGenerationSystem() archetype_generation_equipment = equipment.generation_system _type = str(equipment.name).split('_')[0] _generation_system.type = Dictionaries().montreal_system_to_hub_energy_generation_system[ _type] - # dhw peak does not add anything to the total heat peak - _generation_system.heat_power = building.heating_peak_load - _generation_system.cooling_power = building.cooling_peak_load - # the only system that generates electricity in the montreal custom catalog is PV systems - _generation_system.electricity_power = 0 _generation_system.fuel_type = archetype_generation_equipment.fuel_type _generation_system.source_types = archetype_generation_equipment.source_types _generation_system.heat_efficiency = archetype_generation_equipment.heat_efficiency @@ -66,10 +63,12 @@ class MontrealCustomEnergySystemParameters: _generation_system.electricity_efficiency = archetype_generation_equipment.electricity_efficiency _generation_system.source_temperature = archetype_generation_equipment.source_temperature _generation_system.source_mass_flow = archetype_generation_equipment.source_mass_flow - if archetype_generation_equipment.storage: - # todo: calculate storage capacity in J - _generation_system.storage_capacity = 0 - + # dhw peak does not add anything to the total heat peak + _generation_system.peak_coverages = {cte.HEATING: 1, + cte.COOLING: 1, + cte.DOMESTIC_HOT_WATER: 0, + cte.ELECTRICITY: 0} + _generation_system.storage = archetype_generation_equipment.storage _generation_system.auxiliary_equipment = None energy_system.generation_system = _generation_system @@ -84,6 +83,7 @@ class MontrealCustomEnergySystemParameters: energy_system.distribution_system = _distribution_system building_systems.append(energy_system) + building.energy_systems = building_systems @staticmethod def _search_archetypes(catalog, name): diff --git a/hub/imports/energy_systems_factory.py b/hub/imports/energy_systems_factory.py index fb923300..b09d560c 100644 --- a/hub/imports/energy_systems_factory.py +++ b/hub/imports/energy_systems_factory.py @@ -37,19 +37,27 @@ class EnergySystemsFactory: Enrich the city by using xlsx heat pump information """ AirSourceHeatPumpParameters(self._city, self._base_path).enrich_city() + self._city.level_of_detail.energy_systems = 0 + for building in self._city.buildings: + building.level_of_detail.energy_systems = 0 def _water_to_water_hp(self): """ Enrich the city by using water to water heat pump information """ WaterToWaterHPParameters(self._city, self._base_path).enrich_city() + self._city.level_of_detail.energy_systems = 0 + for building in self._city.buildings: + building.level_of_detail.energy_systems = 0 def _montreal_custom(self): """ Enrich the city by using montreal custom energy systems catalog information """ - self._city.level_of_detail.energy_systems = 1 MontrealCustomEnergySystemParameters(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 enrich(self): """ diff --git a/hub/imports/geometry/citygml.py b/hub/imports/geometry/citygml.py index ce1ef4a8..bb84178d 100644 --- a/hub/imports/geometry/citygml.py +++ b/hub/imports/geometry/citygml.py @@ -166,4 +166,6 @@ class CityGml: else: self._city.add_city_object(self._create_building(city_object)) self._city.level_of_detail.geometry = self._lod + for building in self._city.buildings: + building.level_of_detail.geometry = self._lod return self._city diff --git a/hub/imports/geometry/geojson.py b/hub/imports/geometry/geojson.py index 8c820e48..f05724c7 100644 --- a/hub/imports/geometry/geojson.py +++ b/hub/imports/geometry/geojson.py @@ -177,7 +177,7 @@ class Geojson: extrusion_height = 0 if self._extrusion_height_field is not None: extrusion_height = float(feature['properties'][self._extrusion_height_field]) - lod = 0.5 + lod = 1 year_of_construction = None if self._year_of_construction_field is not None: year_of_construction = int(feature['properties'][self._year_of_construction_field]) @@ -215,6 +215,8 @@ class Geojson: if building.floor_area >= 25: self._city.add_city_object(building) self._city.level_of_detail.geometry = lod + for building in self._city.buildings: + building.level_of_detail.geometry = lod if lod > 0: lines_information = GeometryHelper.city_mapping(self._city, plot=False) self._store_shared_percentage_to_walls(self._city, lines_information) diff --git a/hub/imports/geometry/gpandas.py b/hub/imports/geometry/gpandas.py index f1bd2815..3ae71eca 100644 --- a/hub/imports/geometry/gpandas.py +++ b/hub/imports/geometry/gpandas.py @@ -83,6 +83,8 @@ class GPandas: building = Building(name, surfaces, year_of_construction, function, terrains=None) self._city.add_city_object(building) self._city.level_of_detail.geometry = lod + for building in self._city.buildings: + building.level_of_detail.geometry = lod return self._city @staticmethod diff --git a/hub/imports/geometry/obj.py b/hub/imports/geometry/obj.py index 6bf62a33..e8fc5298 100644 --- a/hub/imports/geometry/obj.py +++ b/hub/imports/geometry/obj.py @@ -81,4 +81,7 @@ class Obj: building = Building(name, surfaces, year_of_construction, function, terrains=None) self._city.add_city_object(building) self._city.level_of_detail.geometry = lod + for building in self._city.buildings: + building.level_of_detail.geometry = lod + return self._city diff --git a/hub/imports/geometry/rhino.py b/hub/imports/geometry/rhino.py index c4bf354d..2c490886 100644 --- a/hub/imports/geometry/rhino.py +++ b/hub/imports/geometry/rhino.py @@ -133,5 +133,6 @@ class Rhino: for building in buildings: city.add_city_object(building) + building.level_of_detail.geometry = 3 city.level_of_detail.geometry = 3 return city diff --git a/hub/imports/results/insel_monthly_energry_balance.py b/hub/imports/results/insel_monthly_energry_balance.py index a2b5b322..087abd67 100644 --- a/hub/imports/results/insel_monthly_energry_balance.py +++ b/hub/imports/results/insel_monthly_energry_balance.py @@ -87,9 +87,20 @@ class InselMonthlyEnergyBalance: total_dhw_demand += total_day * cte.DAYS_A_MONTH[day_type][month] * demand domestic_hot_water_demand.append(total_dhw_demand * area) - building.domestic_hot_water_heat_demand[cte.MONTH] = pd.DataFrame(domestic_hot_water_demand, columns=[cte.INSEL_MEB]) + building.domestic_hot_water_heat_demand[cte.MONTH] = pd.DataFrame(domestic_hot_water_demand, + columns=[cte.INSEL_MEB]) + yearly_domestic_hot_water_demand = [sum(domestic_hot_water_demand)] + building.domestic_hot_water_heat_demand[cte.YEAR] = pd.DataFrame(yearly_domestic_hot_water_demand, + columns=[cte.INSEL_MEB]) building.lighting_electrical_demand[cte.MONTH] = pd.DataFrame(lighting_demand, columns=[cte.INSEL_MEB]) + yearly_lighting_electrical_demand = [sum(lighting_demand)] + building.lighting_electrical_demand[cte.YEAR] = pd.DataFrame(yearly_lighting_electrical_demand, + columns=[cte.INSEL_MEB]) building.appliances_electrical_demand[cte.MONTH] = pd.DataFrame(appliances_demand, columns=[cte.INSEL_MEB]) + yearly_appliances_electrical_demand = [sum(appliances_demand)] + building.appliances_electrical_demand[cte.YEAR] = pd.DataFrame(yearly_appliances_electrical_demand, + columns=[cte.INSEL_MEB]) + def enrich(self): for building in self._city.buildings: diff --git a/hub/imports/results/simplified_radiosity_algorithm.py b/hub/imports/results/simplified_radiosity_algorithm.py index 334a26b1..e6f01cf8 100644 --- a/hub/imports/results/simplified_radiosity_algorithm.py +++ b/hub/imports/results/simplified_radiosity_algorithm.py @@ -95,3 +95,6 @@ class SimplifiedRadiosityAlgorithm: if cte.YEAR not in surface.global_irradiance: surface.global_irradiance[cte.YEAR] = self._get_yearly_mean_values(new_value) self._city.level_of_detail.surface_radiation = 2 + for building in self._city.buildings: + building.level_of_detail.surface_radiation = 2 + diff --git a/hub/imports/usage_factory.py b/hub/imports/usage_factory.py index 9602166e..bf35f4ee 100644 --- a/hub/imports/usage_factory.py +++ b/hub/imports/usage_factory.py @@ -32,15 +32,19 @@ class UsageFactory: """ Enrich the city with COMNET usage library """ - self._city.level_of_detail.usage = 2 ComnetUsageParameters(self._city).enrich_buildings() + self._city.level_of_detail.usage = 2 + for building in self._city.buildings: + building.level_of_detail.usage = 2 def _nrcan(self): """ Enrich the city with NRCAN usage library """ - self._city.level_of_detail.usage = 2 NrcanUsageParameters(self._city).enrich_buildings() + self._city.level_of_detail.usage = 2 + for building in self._city.buildings: + building.level_of_detail.usage = 2 def enrich(self): """ diff --git a/hub/imports/weather/epw_weather_parameters.py b/hub/imports/weather/epw_weather_parameters.py index 2e3b407c..390e0683 100644 --- a/hub/imports/weather/epw_weather_parameters.py +++ b/hub/imports/weather/epw_weather_parameters.py @@ -160,3 +160,6 @@ class EpwWeatherParameters: usage.domestic_hot_water.density = density self._city.level_of_detail.weather = 2 + for building in self._city.buildings: + building.level_of_detail.weather = 2 + diff --git a/hub/unittests/test_geometry_factory.py b/hub/unittests/test_geometry_factory.py index 9fb2c3ac..7e925d5b 100644 --- a/hub/unittests/test_geometry_factory.py +++ b/hub/unittests/test_geometry_factory.py @@ -8,10 +8,7 @@ from pathlib import Path from unittest import TestCase import hub.exports.exports_factory -from hub.imports.usage_factory import UsageFactory - -from hub.exports.energy_building_exports_factory import EnergyBuildingsExportsFactory -from hub.helpers.dictionaries import MontrealFunctionToHubFunction, Dictionaries +from hub.helpers.dictionaries import MontrealFunctionToHubFunction from hub.helpers.geometry_helper import GeometryHelper from hub.imports.construction_factory import ConstructionFactory from hub.imports.geometry_factory import GeometryFactory diff --git a/hub/unittests/test_systems_factory.py b/hub/unittests/test_systems_factory.py index 80c1f168..318bba7f 100644 --- a/hub/unittests/test_systems_factory.py +++ b/hub/unittests/test_systems_factory.py @@ -5,11 +5,17 @@ Copyright © 2023 Concordia CERC group Project Coder Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca """ +import subprocess from pathlib import Path from unittest import TestCase -from hub.imports.geometry_factory import GeometryFactory +import hub.helpers.constants as cte +from hub.exports.energy_building_exports_factory import EnergyBuildingsExportsFactory +from hub.exports.exports_factory import ExportsFactory +from hub.helpers.dictionaries import Dictionaries from hub.imports.construction_factory import ConstructionFactory +from hub.imports.geometry_factory import GeometryFactory +from hub.imports.results_factory import ResultFactory from hub.imports.usage_factory import UsageFactory from hub.imports.energy_systems_factory import EnergySystemsFactory @@ -20,39 +26,50 @@ class TestSystemsFactory(TestCase): """ def setUp(self) -> None: """ - Configure test environment - :return: + Test setup + :return: None """ - self._city = None self._example_path = (Path(__file__).parent / 'tests_data').resolve() - - def _get_citygml(self, file): - file_path = (self._example_path / file).resolve() - self._city = GeometryFactory('citygml', path=file_path).city - self.assertIsNotNone(self._city, 'city is none') - self.assertIsNotNone(self._city.level_of_detail.geometry, 'wrong construction level of detail') - return self._city + self._gml_path = (self._example_path / 'FZK_Haus_LoD_2.gml').resolve() + self._output_path = (Path(__file__).parent / 'tests_outputs').resolve() + self._city = GeometryFactory('citygml', + self._gml_path, + function_to_hub=Dictionaries().alkis_function_to_hub_function).city def test_montreal_custom_system_factory(self): """ Enrich the city with the construction information and verify it """ - file = 'one_building_in_kelowna.gml' - city = self._get_citygml(file) - for building in city.buildings: + for building in self._city.buildings: building.energy_systems_archetype_name = 'system 1 gas' - EnergySystemsFactory('montreal_custom', city).enrich() - for building in city.buildings: - print(building.energy_systems) + EnergySystemsFactory('montreal_custom', self._city).enrich() + for building in self._city.buildings: + self.assertEqual(2, len(building.energy_systems[0].demand_types)) + self.assertEqual(1, len(building.energy_systems[1].demand_types)) def test_montreal_custom_system_results(self): """ Enrich the city with the construction information and verify it """ - file = 'one_building_in_kelowna.gml' - city = self._get_citygml(file) - for building in city.buildings: - building.year_of_construction = 1980 - ConstructionFactory('nrcan', city).enrich() - UsageFactory('nrcan', city).enrich() + ConstructionFactory('nrcan', self._city).enrich() + UsageFactory('nrcan', self._city).enrich() + weather_file = (self._example_path / 'CAN_PQ_Montreal.Intl.AP.716270_CWEC.epw').resolve() + ExportsFactory('sra', self._city, self._output_path, weather_file=weather_file, weather_format='epw').export() + sra_path = (self._output_path / f'{self._city.name}_sra.xml').resolve() + subprocess.run(['sra', str(sra_path)]) + ResultFactory('sra', self._city, self._output_path).enrich() + EnergyBuildingsExportsFactory('insel_monthly_energy_balance', self._city, self._output_path).export() + for building in self._city.buildings: + insel_path = (self._output_path / f'{building.name}.insel') + subprocess.run(['insel', str(insel_path)]) + ResultFactory('insel_monthly_energy_balance', self._city, self._output_path).enrich() + + for building in self._city.buildings: + building.energy_systems_archetype_name = 'system 1 gas' + EnergySystemsFactory('montreal_custom', self._city).enrich() + + for building in self._city.buildings: + self.assertLess(0, building.heating_consumption[cte.YEAR][0]) + self.assertLess(0, building.cooling_consumption[cte.YEAR][0]) + self.assertLess(0, building.domestic_hot_water_consumption[cte.YEAR][0])