diff --git a/hub/city_model_structure/building.py b/hub/city_model_structure/building.py index 327cc0c7..afcdb85e 100644 --- a/hub/city_model_structure/building.py +++ b/hub/city_model_structure/building.py @@ -44,6 +44,8 @@ class Building(CityObject): self._lighting_electrical_demand = dict() self._appliances_electrical_demand = dict() self._domestic_hot_water_heat_demand = dict() + self._heating_peak_load = dict() + self._cooling_peak_load = dict() self._eave_height = None self._grounds = [] self._roofs = [] @@ -362,6 +364,38 @@ class Building(CityObject): """ self._domestic_hot_water_heat_demand = value + @property + def heating_peak_load(self) -> dict: + """ + Get heating peak load in W + :return: dict{DataFrame(float)} + """ + return self._heating_peak_load + + @heating_peak_load.setter + def heating_peak_load(self, value): + """ + Set heating peak load in W + :param value: dict{DataFrame(float)} + """ + self._heating_peak_load = value + + @property + def cooling_peak_load(self) -> dict: + """ + Get cooling peak load in W + :return: dict{DataFrame(float)} + """ + return self._cooling_peak_load + + @cooling_peak_load.setter + def cooling_peak_load(self, value): + """ + Set peak load in W + :param value: dict{DataFrame(float)} + """ + self._cooling_peak_load = value + @property def eave_height(self): """ diff --git a/hub/city_model_structure/city_object.py b/hub/city_model_structure/city_object.py index 234c50b4..3e688c26 100644 --- a/hub/city_model_structure/city_object.py +++ b/hub/city_model_structure/city_object.py @@ -110,6 +110,14 @@ class CityObject: """ return self._surfaces + @surfaces.setter + def surfaces(self, value): + """ + Set city object surfaces + :return: [Surface] + """ + self._surfaces = value + def surface(self, name) -> Union[Surface, None]: """ Get the city object surface with a given name diff --git a/hub/helpers/constants.py b/hub/helpers/constants.py index b147e26d..9c23605a 100644 --- a/hub/helpers/constants.py +++ b/hub/helpers/constants.py @@ -184,6 +184,7 @@ MIN_FLOAT = float('-inf') # Tools SRA = 'sra' INSEL_MEB = 'insel meb' +PEAK_LOAD = 'peak load' # Costs units CURRENCY_PER_SQM = 'currency/m2' diff --git a/hub/imports/geometry/geojson.py b/hub/imports/geometry/geojson.py index 584f1337..cef39644 100644 --- a/hub/imports/geometry/geojson.py +++ b/hub/imports/geometry/geojson.py @@ -71,8 +71,11 @@ class Geojson: polygon = Polygon(points) polygon.area = igh.ground_area(points) surface = Surface(polygon, polygon) - surfaces.append(surface) - buildings.append(Building(f'{name}_zone_{zone}', surfaces, year_of_construction, function)) + if len(buildings) == 1: + buildings[0].surfaces.append(surface) + else: + surfaces.append(surface) + buildings.append(Building(f'{name}', surfaces, year_of_construction, function)) return buildings @staticmethod @@ -82,9 +85,10 @@ class Geojson: buildings = [] for zone, lod0_building in enumerate(lod0_buildings): + # print(zone, lod0_building.name) + volume = 0 for surface in lod0_building.grounds: - - volume = surface.solid_polygon.area * height + volume = volume + surface.solid_polygon.area * height surfaces.append(surface) roof_coordinates = [] # adding a roof means invert the polygon coordinates and change the Z value @@ -112,10 +116,9 @@ class Geojson: polygon = Polygon(wall_coordinates) wall = Surface(polygon, polygon) surfaces.append(wall) - - building = Building(f'{name}_zone_{zone}', surfaces, year_of_construction, function) - building.volume = volume - buildings.append(building) + building = Building(f'{name}', surfaces, year_of_construction, function) + building.volume = volume + buildings.append(building) return buildings @@ -218,7 +221,7 @@ class Geojson: polygons = self._get_polygons(polygons, coordinates) for polygon in polygons: if extrusion_height == 0: - buildings = buildings + Geojson._create_buildings_lod0(f'{building_name}_part_{part}', + buildings = buildings + Geojson._create_buildings_lod0(f'{building_name}', year_of_construction, function, [polygon]) @@ -226,11 +229,22 @@ class Geojson: else: if self._max_z < extrusion_height: self._max_z = extrusion_height - buildings = buildings + Geojson._create_buildings_lod1(f'{building_name}_part_{part}', - year_of_construction, - function, - extrusion_height, - [polygon]) + if part == 0: + buildings = buildings + Geojson._create_buildings_lod1(f'{building_name}', + year_of_construction, + function, + extrusion_height, + [polygon]) + else: + new_part = Geojson._create_buildings_lod1(f'{building_name}', + year_of_construction, + function, + extrusion_height, + [polygon]) + surfaces = buildings[len(buildings) - 1].surfaces + new_part[0].surfaces + volume = buildings[len(buildings) - 1].volume + new_part[0].volume + buildings[len(buildings) - 1] = Building(f'{building_name}', surfaces, year_of_construction, function) + buildings[len(buildings) - 1].volume = volume self._city = City([self._min_x, self._min_y, 0.0], [self._max_x, self._max_y, self._max_z], 'epsg:26911') for building in buildings: diff --git a/hub/imports/geometry_factory.py b/hub/imports/geometry_factory.py index ba24ef80..a035773d 100644 --- a/hub/imports/geometry_factory.py +++ b/hub/imports/geometry_factory.py @@ -107,6 +107,9 @@ class GeometryFactory: Enrich the city given to the class using the class given handler :return: City """ - if self._data_frame is None: - self._data_frame = geopandas.read_file(self._path) - return GPandas(self._data_frame).city + return Geojson(self._path, + self._name_field, + self._height_field, + self._year_of_construction_field, + self._function_field, + self._function_to_hub).city diff --git a/hub/imports/results/insel_monthly_energry_balance.py b/hub/imports/results/insel_monthly_energry_balance.py index 5e421330..15f2882b 100644 --- a/hub/imports/results/insel_monthly_energry_balance.py +++ b/hub/imports/results/insel_monthly_energry_balance.py @@ -4,7 +4,6 @@ SPDX - License - Identifier: LGPL - 3.0 - or -later Copyright © 2022 Concordia CERC group Project Coder Guillermo.GutierrezMorote@concordia.ca """ - from pathlib import Path import pandas as pd import csv @@ -40,60 +39,55 @@ class InselMonthlyEnergyBalance: monthly_cooling = pd.DataFrame(cooling, columns=[cte.INSEL_MEB]).astype(float) return monthly_heating, monthly_cooling - def _dhw_demand(self): + def _dhw_and_electric_demand(self): for building in self._city.buildings: domestic_hot_water_demand = [] - if building.internal_zones[0].thermal_zones is None: - domestic_hot_water_demand = [0] * 12 - else: - thermal_zone = building.internal_zones[0].thermal_zones[0] - area = thermal_zone.total_floor_area - cold_water = building.cold_water_temperature[cte.MONTH]['epw'] - for month in range(0, 12): - total_dhw_demand = 0 - for schedule in thermal_zone.domestic_hot_water.schedules: - total_day = 0 - for value in schedule.values: - total_day += value - for day_type in schedule.day_types: - demand = thermal_zone.domestic_hot_water.peak_flow * cte.WATER_DENSITY * cte.WATER_HEAT_CAPACITY \ - * (thermal_zone.domestic_hot_water.service_temperature - cold_water[month]) - 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]) - - def _electrical_demand(self): - for building in self._city.buildings: lighting_demand = [] appliances_demand = [] if building.internal_zones[0].thermal_zones is None: + domestic_hot_water_demand = [0] * 12 lighting_demand = [0] * 12 appliances_demand = [0] * 12 else: thermal_zone = building.internal_zones[0].thermal_zones[0] area = thermal_zone.total_floor_area + cold_water = building.cold_water_temperature[cte.MONTH]['epw'] + peak_flow = thermal_zone.domestic_hot_water.peak_flow + service_temperature = thermal_zone.domestic_hot_water.service_temperature + lighting_density = thermal_zone.lighting.density + appliances_density = thermal_zone.appliances.density for month in range(0, 12): + total_dhw_demand = 0 total_lighting = 0 + total_appliances = 0 + for schedule in thermal_zone.lighting.schedules: total_day = 0 for value in schedule.values: total_day += value for day_type in schedule.day_types: - total_lighting += total_day * cte.DAYS_A_MONTH[day_type][month] * thermal_zone.lighting.density + total_lighting += total_day * cte.DAYS_A_MONTH[day_type][month] * lighting_density lighting_demand.append(total_lighting * area) - total_appliances = 0 for schedule in thermal_zone.appliances.schedules: total_day = 0 for value in schedule.values: total_day += value for day_type in schedule.day_types: - total_appliances += total_day * cte.DAYS_A_MONTH[day_type][month] * thermal_zone.appliances.density + total_appliances += total_day * cte.DAYS_A_MONTH[day_type][month] * appliances_density appliances_demand.append(total_appliances * area) + for schedule in thermal_zone.domestic_hot_water.schedules: + total_day = 0 + for value in schedule.values: + total_day += value + for day_type in schedule.day_types: + demand = peak_flow * cte.WATER_DENSITY * cte.WATER_HEAT_CAPACITY * (service_temperature - cold_water[month]) + 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.lighting_electrical_demand[cte.MONTH] = pd.DataFrame(lighting_demand, columns=[cte.INSEL_MEB]) building.appliances_electrical_demand[cte.MONTH] = pd.DataFrame(appliances_demand, columns=[cte.INSEL_MEB]) @@ -109,5 +103,4 @@ class InselMonthlyEnergyBalance: building.cooling[cte.YEAR] = pd.DataFrame( [building.cooling[cte.MONTH][cte.INSEL_MEB].astype(float).sum()], columns=[cte.INSEL_MEB] ) - self._dhw_demand() - self._electrical_demand() + self._dhw_and_electric_demand() diff --git a/hub/imports/results/peak_calculation/__init__.py b/hub/imports/results/peak_calculation/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/hub/imports/results/peak_calculation/loads_calculation.py b/hub/imports/results/peak_calculation/loads_calculation.py new file mode 100644 index 00000000..8b29d94c --- /dev/null +++ b/hub/imports/results/peak_calculation/loads_calculation.py @@ -0,0 +1,118 @@ +""" +Calculation of loads for peak heating and cooling +SPDX - License - Identifier: LGPL - 3.0 - or -later +Copyright © 2022 Concordia CERC group +Project Coder Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca +""" + + +import hub.helpers.constants as cte + + +class LoadsCalculation: + """ + LoadsCalculation class + """ + def __init__(self, building): + self._building = building + + @staticmethod + def _get_load_transmitted(thermal_zone, internal_temperature, ambient_temperature, ground_temperature): + load_transmitted_opaque = 0 + load_transmitted_transparent = 0 + for thermal_boundary in thermal_zone.thermal_boundaries: + if thermal_boundary.type == cte.GROUND: + external_temperature = ground_temperature + elif thermal_boundary.type == cte.INTERIOR_WALL: + external_temperature = internal_temperature + else: + external_temperature = ambient_temperature + + load_transmitted_opaque += thermal_boundary.u_value * thermal_boundary.opaque_area \ + * (internal_temperature - external_temperature) + for thermal_opening in thermal_boundary.thermal_openings: + load_transmitted_transparent += thermal_opening.overall_u_value \ + * (internal_temperature - external_temperature) + load_transmitted_opaque += thermal_zone.additional_thermal_bridge_u_value * thermal_zone.footprint_area \ + * (internal_temperature - ambient_temperature) + load_transmitted = load_transmitted_opaque + load_transmitted_transparent + return load_transmitted + + @staticmethod + def _get_load_ventilation(thermal_zone, internal_temperature, ambient_temperature): + load_renovation_sensible = 0 + for usage in thermal_zone.usages: + load_renovation_sensible += cte.AIR_DENSITY * cte.AIR_HEAT_CAPACITY * usage.mechanical_air_change \ + * thermal_zone.volume / cte.HOUR_TO_MINUTES / cte.MINUTES_TO_SECONDS \ + * (internal_temperature - ambient_temperature) + + load_infiltration_sensible = cte.AIR_DENSITY * cte.AIR_HEAT_CAPACITY * thermal_zone.infiltration_rate_system_off \ + * thermal_zone.volume / cte.HOUR_TO_MINUTES / cte.MINUTES_TO_SECONDS \ + * (internal_temperature - ambient_temperature) + + load_ventilation = load_renovation_sensible + load_infiltration_sensible + + return load_ventilation + + def get_heating_transmitted_load(self, ambient_temperature, ground_temperature): + heating_load_transmitted = 0 + for internal_zone in self._building.internal_zones: + for thermal_zone in internal_zone.thermal_zones: + internal_temperature = thermal_zone.thermal_control.mean_heating_set_point + heating_load_transmitted += self._get_load_transmitted(thermal_zone, internal_temperature, ambient_temperature, + ground_temperature) + return heating_load_transmitted + + def get_cooling_transmitted_load(self, ambient_temperature, ground_temperature): + cooling_load_transmitted = 0 + for internal_zone in self._building.internal_zones: + for thermal_zone in internal_zone.thermal_zones: + internal_temperature = thermal_zone.thermal_control.mean_cooling_set_point + cooling_load_transmitted += self._get_load_transmitted(thermal_zone, internal_temperature, ambient_temperature, + ground_temperature) + return cooling_load_transmitted + + def get_heating_ventilation_load_sensible(self, ambient_temperature): + heating_ventilation_load = 0 + for internal_zone in self._building.internal_zones: + for thermal_zone in internal_zone.thermal_zones: + internal_temperature = thermal_zone.thermal_control.mean_heating_set_point + heating_ventilation_load += self._get_load_ventilation(thermal_zone, internal_temperature, ambient_temperature) + return heating_ventilation_load + + def get_cooling_ventilation_load_sensible(self, ambient_temperature): + cooling_ventilation_load = 0 + for internal_zone in self._building.internal_zones: + for thermal_zone in internal_zone.thermal_zones: + internal_temperature = thermal_zone.thermal_control.mean_cooling_set_point + cooling_ventilation_load += self._get_load_ventilation(thermal_zone, internal_temperature, ambient_temperature) + return cooling_ventilation_load + + def get_internal_load_sensible(self): + cooling_load_occupancy_sensible = 0 + cooling_load_lighting = 0 + cooling_load_equipment_sensible = 0 + for internal_zone in self._building.internal_zones: + for thermal_zone in internal_zone.thermal_zones: + cooling_load_occupancy_sensible += (thermal_zone.occupancy.sensible_convective_internal_gain + + thermal_zone.occupancy.sensible_radiative_internal_gain) \ + * thermal_zone.footprint_area + cooling_load_lighting += (thermal_zone.lighting.density * thermal_zone.lighting.convective_fraction + + thermal_zone.lighting.density * thermal_zone.lighting.radiative_fraction) \ + * thermal_zone.footprint_area + cooling_load_equipment_sensible += (thermal_zone.appliances.density * thermal_zone.appliances.convective_fraction + + thermal_zone.appliances.density * thermal_zone.appliances.radiative_fraction) \ + * thermal_zone.footprint_area + internal_load = cooling_load_occupancy_sensible + cooling_load_lighting + cooling_load_equipment_sensible + return internal_load + + def get_radiation_load(self, irradiance_format, hour): + cooling_load_radiation = 0 + for internal_zone in self._building.internal_zones: + for thermal_zone in internal_zone.thermal_zones: + for thermal_boundary in thermal_zone.thermal_boundaries: + for thermal_opening in thermal_boundary.thermal_openings: + radiation = thermal_boundary.parent_surface.global_irradiance[cte.HOUR][irradiance_format][hour] + cooling_load_radiation += thermal_opening.area * (1 - thermal_opening.frame_ratio) * thermal_opening.g_value \ + * radiation + return cooling_load_radiation diff --git a/hub/imports/results/peak_load.py b/hub/imports/results/peak_load.py new file mode 100644 index 00000000..9ace3104 --- /dev/null +++ b/hub/imports/results/peak_load.py @@ -0,0 +1,61 @@ +import hub.helpers.constants as cte +from hub.imports.results.peak_calculation.loads_calculation import LoadsCalculation + + +class PeakLoad: + + _MONTH_STARTING_HOUR = [0, 744, 1416, 2160, 2880, 3624, 4344, 5088, 5832, 6552, 7296, 8016] + + def __init__(self, city): + self._city = city + self._weather_format = 'epw' + + def enrich(self): + for building in self._city.buildings: + monthly_heating_loads = [] + monthly_cooling_loads = [] + ambient_temperature = building.external_temperature[cte.HOUR][self._weather_format] + for month in range(0, 12): + ground_temperature = building.ground_temperature[cte.MONTH]['2'][month] + heating_ambient_temperature = 100 + cooling_ambient_temperature = -100 + heating_calculation_hour = -1 + cooling_calculation_hour = -1 + start_hour = self._MONTH_STARTING_HOUR[month] + end_hour = 8760 + if month < 11: + end_hour = self._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 + heating_calculation_hour = hour + if temperature > cooling_ambient_temperature: + cooling_ambient_temperature = temperature + cooling_calculation_hour = hour + + 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 + + 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(self._irradiance_format, 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 heating_load < 0: + heating_load = 0 + if cooling_load > 0: + cooling_load = 0 + monthly_heating_loads.append(heating_load) + monthly_cooling_loads.append(cooling_load) + + self._results[building.name] = {'monthly heating peak load': monthly_heating_loads, + 'monthly cooling peak load': monthly_cooling_loads} + self._print_results() diff --git a/hub/imports/results_factory.py b/hub/imports/results_factory.py index 62adf124..4eb7c0e4 100644 --- a/hub/imports/results_factory.py +++ b/hub/imports/results_factory.py @@ -9,6 +9,7 @@ from pathlib import Path from hub.helpers.utils import validate_import_export_type from hub.hub_logger import logger +from hub.imports.results.peak_load import PeakLoad from hub.imports.results.simplified_radiosity_algorithm import SimplifiedRadiosityAlgorithm from hub.imports.results.insel_monthly_energry_balance import InselMonthlyEnergyBalance from hub.imports.results.insel_heatpump_energy_demand import InselHeatPumpEnergyDemand @@ -59,6 +60,12 @@ class ResultFactory: """ InselMonthlyEnergyBalance(self._city, self._base_path).enrich() + def _peak_load(self): + """ + Enrich the city with peak load results + """ + PeakLoad(self._city).enrich() + def enrich(self): """ Enrich the city given to the class using the usage factory given handler diff --git a/requirements.txt b/requirements.txt index 72293904..1538a51d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -24,4 +24,5 @@ geopandas triangle psycopg2-binary Pillow -pathlib \ No newline at end of file +pathlib +pickle5 \ No newline at end of file diff --git a/setup.py b/setup.py index bbc2f8f3..020562ab 100644 --- a/setup.py +++ b/setup.py @@ -87,6 +87,7 @@ setup( ('hub/catalog_factories/greenery/ecore_greenery', glob.glob('hub/catalog_factories/greenery/ecore_greenery/*.ecore')), ('hub/data/construction.', glob.glob('hub/data/construction/*')), ('hub/data/customized_imports', glob.glob('hub/data/customized_imports/*.xml')), + ('data/geolocation', glob.glob('hub/data/geolocation/*.txt')), ('hub/data/energy_systems', glob.glob('hub/data/energy_systems/*.xml')), ('hub/data/energy_systems', glob.glob('hub/data/energy_systems/*.insel')), ('hub/data/energy_systems', glob.glob('hub/data/energy_systems/*.xlsx')),