Merge remote-tracking branch 'origin/idf_lights' into idf_lights

This commit is contained in:
Oriol Gavalda 2023-05-02 17:08:20 -04:00
commit bcc2f2fd3a
23 changed files with 387 additions and 263 deletions

View File

@ -130,11 +130,11 @@ class NrcanCatalog(Catalog):
# ACH
mechanical_air_change = space_type['ventilation_air_changes']
# cfm/ft2 to m3/m2.s
ventilation_rate = space_type['ventilation_per_area'] * cte.CUBICFEET_TO_CUBIC_METERS_HOUR
ventilation_rate = space_type['ventilation_per_area'] / (cte.METERS_TO_FEET * cte.MINUTES_TO_SECONDS)
if ventilation_rate == 0:
# cfm/person to m3/m2.s
ventilation_rate = space_type['ventilation_per_person'] * cte.CUBICFEET_TO_CUBIC_METERS_HOUR\
* occupancy_density
ventilation_rate = space_type['ventilation_per_person'] / (cte.METERS_TO_FEET * cte.MINUTES_TO_SECONDS)\
/ occupancy_density
lighting_radiative_fraction = space_type['lighting_fraction_radiant']
lighting_convective_fraction = 0

View File

@ -9,8 +9,11 @@ Code contributors: Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concord
import sys
from typing import List, Union
import numpy as np
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.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
@ -44,8 +47,6 @@ 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 = []
@ -370,15 +371,14 @@ class Building(CityObject):
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
results = {}
if cte.HOUR in self.heating:
monthly_values = pl.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)
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:
@ -386,15 +386,14 @@ class Building(CityObject):
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
results = {}
if cte.HOUR in self.cooling:
monthly_values = pl.peak_loads_from_hourly(self.cooling[cte.HOUR][next(iter(self.cooling[cte.HOUR]))])
else:
monthly_values = pl.cooling_peak_loads_from_methodology(self)
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
@property
def eave_height(self):

View File

@ -78,7 +78,7 @@ class InternalZone:
def usages(self) -> [Usage]:
"""
Get internal zone usage zones
:return: [UsageZone]
:return: [Usage]
"""
return self._usages
@ -86,7 +86,7 @@ class InternalZone:
def usages(self, value):
"""
Set internal zone usage zones
:param value: [UsageZone]
:param value: [Usage]
"""
self._usages = value

View File

@ -603,11 +603,9 @@ class ThermalZone:
_mean_peak_flow = 0
_mean_service_temperature = 0
for usage in self.usages:
#todo: change hardcoded density DHW
#_mean_peak_density_load += usage.percentage * 1 #usage.domestic_hot_water.density
_mean_peak_density_load += usage.percentage * usage.domestic_hot_water.density
_mean_peak_flow += usage.percentage * usage.domestic_hot_water.peak_flow
# todo: change hardcoded service temperature
_mean_service_temperature += usage.percentage * 45
_mean_service_temperature += usage.percentage * usage.domestic_hot_water.service_temperature
self._domestic_hot_water.density = _mean_peak_density_load
self._domestic_hot_water.peak_flow = _mean_peak_flow
self._domestic_hot_water.service_temperature = _mean_service_temperature

View File

@ -122,6 +122,8 @@ class City:
Get the name for the climatic information reference city
:return: None or str
"""
if self._climate_reference_city is None:
self._climate_reference_city = self._get_location().city
return self._climate_reference_city
@climate_reference_city.setter
@ -130,7 +132,6 @@ class City:
Set the name for the climatic information reference city
:param value: str
"""
if value is not None:
self._climate_reference_city = str(value)
@property

View File

@ -4,7 +4,6 @@ SPDX - License - Identifier: LGPL - 3.0 - or -later
Copyright © 2022 Concordia CERC group
Project Coder Guille Guillermo.GutierrezMorote@concordia.ca
Code contributors: Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca
Soroush Samareh Abolhassani soroush.samarehabolhassani@mail.concordia.ca
"""
import copy
from pathlib import Path
@ -190,7 +189,6 @@ class Idf:
_schedule.Minutes_per_Item = 60
def _add_infiltration_schedules(self, thermal_zone):
# todo: clean the way infiltration is hardcoded
_infiltration_schedules = []
if thermal_zone.thermal_control is None:
return
@ -204,9 +202,9 @@ class Idf:
_infiltration_values = []
for hvac_value in hvac_availability_schedule.values:
if hvac_value == 0:
_infiltration_values.append(1)
_infiltration_values.append(thermal_zone.infiltration_rate_system_off)
else:
_infiltration_values.append(1)
_infiltration_values.append(thermal_zone.infiltration_rate_system_on)
_schedule.values = _infiltration_values
_infiltration_schedules.append(_schedule)
for schedule in self._idf.idfobjects[self._HOURLY_SCHEDULE]:
@ -424,13 +422,13 @@ class Idf:
Air_Changes_per_Hour=thermal_zone.infiltration_rate_system_off * factorreduct
)
def _add_DHW(self, thermal_zone, zone_name):
def _add_dhw(self, thermal_zone, zone_name):
fuel_type = 'Electricity'
method = 'Watts/Area'
factor_size = thermal_zone.total_floor_area / thermal_zone.footprint_area
# todo: revision of values of peak flow (too low). Added a factor, but to check original units
peak_flow_rate = thermal_zone.domestic_hot_water.peak_flow * thermal_zone.total_floor_area
# = self._idf.newidfobject(self._DHW)
# = self._idf.newidfobject(self._dhw)
# print(vars(_object))
self._idf.newidfobject(self._DHW,
Name=f'DHW {zone_name}',
@ -501,7 +499,7 @@ class Idf:
self._add_occupancy(thermal_zone, building.name)
self._add_lighting(thermal_zone, building.name)
self._add_appliances(thermal_zone, building.name)
self._add_DHW(thermal_zone, building.name)
self._add_dhw(thermal_zone, building.name)
if self._export_type == "Surfaces":
if building.name in self._target_buildings or building.name in self._adjacent_buildings:
if building.internal_zones[0].thermal_zones is not None:

View File

@ -51,6 +51,7 @@ class InselMonthlyEnergyBalance(Insel):
)
self._export()
def _export(self):
for i_file, content in enumerate(self._contents):
file_name = self._insel_files_paths[i_file]
@ -94,12 +95,19 @@ class InselMonthlyEnergyBalance(Insel):
inputs.append(f"{str(100 + i)}.1 % Radiation surface {str(i)}")
number_of_storeys = int(building.eave_height / building.average_storey_height)
attic_heated = building.attic_heated
basement_heated = building.basement_heated
if building.attic_heated is None:
attic_heated = 0
if building.basement_heated is None:
basement_heated = 0
# BUILDING PARAMETERS
parameters = [f'{building.volume} % BP(1) Heated Volume (m3)',
f'{building.average_storey_height} % BP(2) Average storey height (m)',
f'{number_of_storeys} % BP(3) Number of storeys above ground',
f'{building.attic_heated} % BP(4) Attic heating type (0=no room, 1=unheated, 2=heated)',
f'{building.basement_heated} % BP(5) Cellar heating type (0=no room, 1=unheated, 2=heated, '
f'{attic_heated} % BP(4) Attic heating type (0=no room, 1=unheated, 2=heated)',
f'{basement_heated} % BP(5) Cellar heating type (0=no room, 1=unheated, 2=heated, '
f'99=invalid)']
# todo: this method and the insel model have to be reviewed for more than one internal zone
@ -127,7 +135,7 @@ class InselMonthlyEnergyBalance(Insel):
f'zone {i + 1} (degree Celsius)')
parameters.append(f'{usage.thermal_control.heating_set_back} % BP(14) #4 Heating setback temperature '
f'zone {i + 1} (degree Celsius)')
parameters.append(f'{usage.thermal_control.mean_cooling_set_point+4} % BP(15) #5 Cooling setpoint temperature '
parameters.append(f'{usage.thermal_control.mean_cooling_set_point} % BP(15) #5 Cooling setpoint temperature '
f'zone {i + 1} (degree Celsius)')
parameters.append(f'{usage.hours_day} % BP(16) #6 Usage hours per day zone {i + 1}')
parameters.append(f'{usage.days_year} % BP(17) #7 Usage days per year zone {i + 1}')
@ -153,8 +161,8 @@ class InselMonthlyEnergyBalance(Insel):
for day_type in schedule.day_types:
infiltration += infiltration_day * cte.DAYS_A_YEAR[day_type] / 365
ventilation += ventilation_day * cte.DAYS_A_YEAR[day_type] / 365
#todo: eliminate hardcoded coefficient to ventilationinf
ventilation_infiltration = (ventilation + infiltration)*0.5
ventilation_infiltration = ventilation + infiltration
parameters.append(f'{ventilation_infiltration} % BP(18) #8 Minimum air change rate zone {i + 1} (ACH)')
parameters.append(f'{len(thermal_zone.thermal_boundaries)} % Number of surfaces = BP(11+8z) \n'

View File

@ -55,9 +55,7 @@ class EnergyBuildingsExportsFactory:
"""
idf_data_path = (Path(__file__).parent / './building_energy/idf_files/').resolve()
# todo: create a get epw file function based on the city
#print('path', idf_data_path)
weather_path = (Path(__file__).parent / '../data/weather/epw/CAN_PQ_Montreal.Intl.AP.716270_CWEC.epw').resolve()
#print(weather_path)
return Idf(self._city, self._path, (idf_data_path / 'Minimal.idf'), (idf_data_path / 'Energy+.idd'), weather_path,
target_buildings=self._target_buildings, adjacent_buildings=self._adjacent_buildings)
@ -74,7 +72,6 @@ class EnergyBuildingsExportsFactory:
Export the city given to the class using the given export type handler
:return: None
"""
print(self)
return getattr(self, self._export_type, lambda: None)
def export_debug(self):

View File

@ -65,14 +65,6 @@ class ExportsFactory:
"""
return Obj(self._city, self._path)
@property
def _grounded_obj(self):
"""
Export the city geometry to obj with grounded coordinates
:return: None
"""
return Obj(self._city, self._path)
@property
def _sra(self):
"""

View File

@ -4,6 +4,8 @@ SPDX - License - Identifier: LGPL - 3.0 - or -later
Copyright © 2022 Concordia CERC group
Project Coder Guillermo.GutierrezMorote@concordia.ca
"""
from pathlib import Path
import xmltodict
from hub.imports.weather_factory import WeatherFactory
@ -32,9 +34,12 @@ class SimplifiedRadiosityAlgorithm:
self._end_month = end_month
self._end_day = end_day
self._city = city
self._city.climate_file = str((Path(file_name).parent / f'{city.name}.cli').resolve())
self._city.climate_reference_city = city.location
self._target_buildings = target_buildings
self._weather_format = weather_format
self._weather_file = weather_file
self._export()
def _correct_point(self, point):
@ -45,8 +50,8 @@ class SimplifiedRadiosityAlgorithm:
return [x, y, z]
def _export(self):
self._export_sra_xml()
self._export_sra_cli()
self._export_sra_xml()
def _export_sra_cli(self):
file = self._city.climate_file

View File

@ -23,7 +23,6 @@ METERS_TO_FEET = 3.28084
BTU_H_TO_WATTS = 0.29307107
KILO_WATTS_HOUR_TO_JULES = 3600000
GALLONS_TO_QUBIC_METERS = 0.0037854117954011185
CUBICFEET_TO_CUBIC_METERS_HOUR=1.699
# time
SECOND = 'second'

71
hub/helpers/peak_loads.py Normal file
View File

@ -0,0 +1,71 @@
import math
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
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
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

View File

@ -61,44 +61,31 @@ class Geojson:
self._min_y = y
@staticmethod
def _create_buildings_lod0(name, year_of_construction, function, surfaces_coordinates):
surfaces = []
buildings = []
for zone, surface_coordinates in enumerate(surfaces_coordinates):
def _create_building_lod0(name, year_of_construction, function, surface_coordinates):
points = igh.points_from_string(igh.remove_last_point_from_string(surface_coordinates))
# geojson provides the roofs, need to be transform into grounds
points = igh.invert_points(points)
polygon = Polygon(points)
polygon.area = igh.ground_area(points)
surface = Surface(polygon, polygon)
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
surface = Surface(polygon, polygon, name=f'{name}_ground')
return Building(f'{name}', [surface], year_of_construction, function)
@staticmethod
def _create_buildings_lod1(name, year_of_construction, function, height, surface_coordinates):
lod0_buildings = Geojson._create_buildings_lod0(name, year_of_construction, function, surface_coordinates)
def _create_building_lod1(name, year_of_construction, function, height, surface_coordinates):
building = Geojson._create_building_lod0(name, year_of_construction, function, surface_coordinates)
surfaces = []
buildings = []
for zone, lod0_building in enumerate(lod0_buildings):
# print(zone, lod0_building.name)
volume = 0
for surface in lod0_building.grounds:
volume = volume + surface.solid_polygon.area * height
surfaces.append(surface)
for ground in building.grounds:
volume += ground.solid_polygon.area * height
surfaces.append(ground)
roof_coordinates = []
# adding a roof means invert the polygon coordinates and change the Z value
for coordinate in surface.solid_polygon.coordinates:
for coordinate in ground.solid_polygon.coordinates:
roof_coordinate = np.array([coordinate[0], coordinate[1], height])
# insert the roof rotated already
roof_coordinates.insert(0, roof_coordinate)
polygon = Polygon(roof_coordinates)
polygon.area = surface.solid_polygon.area
roof = Surface(polygon, polygon)
roof_polygon = Polygon(roof_coordinates)
roof_polygon.area = ground.solid_polygon.area
roof = Surface(roof_polygon, roof_polygon)
surfaces.append(roof)
# adding a wall means add the point coordinates and the next point coordinates with Z's height and 0
coordinates_length = len(roof.solid_polygon.coordinates)
@ -118,9 +105,7 @@ class Geojson:
surfaces.append(wall)
building = Building(f'{name}', surfaces, year_of_construction, function)
building.volume = volume
buildings.append(building)
return buildings
return building
def _get_polygons(self, polygons, coordinates):
if type(coordinates[0][self.X]) != float:
@ -186,14 +171,13 @@ class Geojson:
Get city out of a Geojson file
"""
if self._city is None:
missing_functions = []
buildings = []
building_id = 0
lod = 1
lod = 0
for feature in self._geojson['features']:
extrusion_height = 0
if self._extrusion_height_field is not None:
extrusion_height = float(feature['properties'][self._extrusion_height_field])
lod = 0.5
year_of_construction = None
if self._year_of_construction_field is not None:
year_of_construction = int(feature['properties'][self._year_of_construction_field])
@ -204,57 +188,111 @@ class Geojson:
# use the transformation dictionary to retrieve the proper function
if function in self._function_to_hub:
function = self._function_to_hub[function]
else:
if function not in missing_functions:
missing_functions.append(function)
function = function
geometry = feature['geometry']
if 'id' in feature:
building_name = feature['id']
else:
building_name = f'building_{building_id}'
building_id += 1
if self._name_field is not None:
building_name = feature['properties'][self._name_field]
polygons = []
for part, coordinates in enumerate(geometry['coordinates']):
polygons = self._get_polygons(polygons, coordinates)
for polygon in polygons:
if extrusion_height == 0:
buildings = buildings + Geojson._create_buildings_lod0(f'{building_name}',
year_of_construction,
function,
[polygon])
lod = 0
else:
if self._max_z < extrusion_height:
self._max_z = extrusion_height
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
if str(geometry['type']).lower() == 'polygon':
buildings.append(self._parse_polygon(geometry['coordinates'],
building_name,
function,
year_of_construction,
extrusion_height))
elif str(geometry['type']).lower() == 'multipolygon':
buildings.append(self._parse_multi_polygon(geometry['coordinates'],
building_name,
function,
year_of_construction,
extrusion_height))
else:
raise NotImplementedError(f'Geojson geometry type [{geometry["type"]}] unknown')
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:
# Do not include "small building-like structures" to buildings
if building.floor_area >= 25:
self._city.add_city_object(building)
self._city.level_of_detail.geometry = lod
if lod == 1:
if lod > 0:
lines_information = GeometryHelper.city_mapping(self._city, plot=False)
self._store_shared_percentage_to_walls(self._city, lines_information)
if len(missing_functions) > 0:
print(f'There are unknown functions {missing_functions}')
return self._city
def _polygon_coordinates_to_3d(self, polygon_coordinates):
transformed_coordinates = ''
for coordinate in polygon_coordinates:
transformed = self._transformer.transform(coordinate[self.Y], coordinate[self.X])
self._save_bounds(transformed[self.X], transformed[self.Y])
transformed_coordinates = f'{transformed_coordinates} {transformed[self.X]} {transformed[self.Y]} 0.0'
return transformed_coordinates.lstrip(' ')
def _parse_polygon(self, coordinates, building_name, function, year_of_construction, extrusion_height):
print('poly')
for polygon_coordinates in coordinates:
coordinates_3d = self._polygon_coordinates_to_3d(polygon_coordinates)
if extrusion_height == 0:
building = Geojson._create_building_lod0(f'{building_name}',
year_of_construction,
function,
coordinates_3d)
else:
if self._max_z < extrusion_height:
self._max_z = extrusion_height
building = Geojson._create_building_lod1(f'{building_name}',
year_of_construction,
function,
extrusion_height,
coordinates_3d)
return building
def _parse_multi_polygon(self, coordinates, building_name, function, year_of_construction, extrusion_height):
print('multi')
surfaces = []
for polygon_coordinate in coordinates:
building = self._parse_polygon(polygon_coordinate, building_name, function, year_of_construction, 0)
for surface in building.surfaces:
if surface.type == cte.GROUND:
surfaces.append(surface)
else:
# overwrite last surface by adding the "hole" in the polygon
polygon = Polygon(surfaces[-1].solid_polygon.coordinates + surface.solid_polygon.coordinates)
surfaces[-1] = Surface(polygon, polygon)
if extrusion_height == 0:
return Building(building_name, surfaces, year_of_construction, function)
else:
volume = 0
for ground in building.grounds:
volume += ground.solid_polygon.area * extrusion_height
surfaces.append(ground)
roof_coordinates = []
# adding a roof means invert the polygon coordinates and change the Z value
for coordinate in ground.solid_polygon.coordinates:
roof_coordinate = np.array([coordinate[0], coordinate[1], extrusion_height])
# insert the roof rotated already
roof_coordinates.insert(0, roof_coordinate)
roof_polygon = Polygon(roof_coordinates)
roof_polygon.area = ground.solid_polygon.area
roof = Surface(roof_polygon, roof_polygon)
surfaces.append(roof)
# adding a wall means add the point coordinates and the next point coordinates with Z's height and 0
coordinates_length = len(roof.solid_polygon.coordinates)
for i, coordinate in enumerate(roof.solid_polygon.coordinates):
j = i + 1
if j == coordinates_length:
j = 0
next_coordinate = roof.solid_polygon.coordinates[j]
wall_coordinates = [
np.array([coordinate[0], coordinate[1], 0.0]),
np.array([next_coordinate[0], next_coordinate[1], 0.0]),
np.array([next_coordinate[0], next_coordinate[1], next_coordinate[2]]),
np.array([coordinate[0], coordinate[1], coordinate[2]])
]
polygon = Polygon(wall_coordinates)
wall = Surface(polygon, polygon)
surfaces.append(wall)
building = Building(f'{building_name}', surfaces, year_of_construction, function)
building.volume = volume
return building

View File

@ -12,7 +12,7 @@ import hub.helpers.constants as cte
class InselMonthlyEnergyBalance:
"""
Import SRA results
Import insel monthly energy balance results
"""
def __init__(self, city, base_path):

View File

@ -1,61 +0,0 @@
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()

View File

@ -9,7 +9,6 @@ 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
@ -54,18 +53,12 @@ class ResultFactory:
"""
InselHeatPumpEnergyDemand(self._city, self._base_path, self._hp_model).enrich()
def _insel_meb(self):
def _insel_monthly_energy_balance(self):
"""
Enrich the city with insel monthly energy balance results
"""
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

View File

@ -83,7 +83,7 @@ class NrcanUsageParameters:
if archetype.mechanical_air_change > 0:
usage.mechanical_air_change = archetype.mechanical_air_change
elif archetype.ventilation_rate > 0:
usage.mechanical_air_change = archetype.ventilation_rate / volume_per_area
usage.mechanical_air_change = archetype.ventilation_rate / volume_per_area * cte.HOUR_TO_SECONDS
else:
usage.mechanical_air_change = 0
_occupancy = Occupancy()

View File

@ -71,7 +71,6 @@ class EpwWeatherParameters:
except SystemExit:
sys.stderr.write(f'Error: wrong formatting of weather file {self._path}\n')
sys.exit()
for building in self._city.buildings:
building.ground_temperature[cte.MONTH] = ground_temperature
ground_temperature = {}

View File

@ -52,3 +52,7 @@ class WeatherFactory:
:return: None
"""
getattr(self, self._handler, lambda: None)()
def enrich_debug(self):
_path = Path(self._base_path / 'epw').resolve()
return EpwWeatherParameters(self._city, _path, self._file_name)

View File

@ -137,18 +137,16 @@ class TestGeometryFactory(TestCase):
"""
Test geojson import
"""
file = '2000_buildings.geojson'
file = 'hole_building.geojson'
city = GeometryFactory('geojson',
path=(self._example_path / file).resolve(),
height_field='building_height',
height_field='citygml_me',
year_of_construction_field='ANNEE_CONS',
name_field='ID_UEV',
function_field='CODE_UTILI',
function_to_hub=MontrealFunctionToHubFunction().dictionary).city
# include 25 square meter condition for a building reduces buildings number from 2289 to 2057
for building in city.buildings:
print(building.name)
self.assertEqual(2057, len(city.buildings), 'wrong number of buildings')
hub.exports.exports_factory.ExportsFactory('obj', city, self._output_path).export_debug()
self.assertEqual(1964, len(city.buildings), 'wrong number of buildings')
def test_map_neighbours(self):
"""
@ -164,7 +162,7 @@ class TestGeometryFactory(TestCase):
year_of_construction_field='ANNEE_CONS',
function_field='LIBELLE_UT')
# info_lod0 = GeometryHelper.city_mapping(city, plot=False)
info_lod0 = GeometryHelper.city_mapping(city, plot=False)
hub.exports.exports_factory.ExportsFactory('obj', city, self._output_path).export()
self.assertEqual(info_lod0, info_lod1)
for building in city.buildings:
@ -177,36 +175,6 @@ class TestGeometryFactory(TestCase):
self.assertEqual('1_part_0_zone_0', city.city_object('3_part_0_zone_0').neighbours[0].name)
self.assertEqual('2_part_0_zone_0', city.city_object('3_part_0_zone_0').neighbours[1].name)
def test_neighbours(self):
"""
Test neighbours map creation
"""
file_path = (self._example_path / 'concordia_clean.geojson').resolve()
city = GeometryFactory('geojson',
path=file_path,
height_field='citygml_me',
year_of_construction_field='ANNEE_CONS',
name_field='OBJECTID_12',
function_field='CODE_UTILI',
function_to_hub=Dictionaries().montreal_function_to_hub_function).city
# print(city.lower_corner, city.upper_corner)
for building in city.buildings:
#for ground in building.grounds:
# print(ground.perimeter_polygon.coordinates)
# print(ground.perimeter_polygon.coordinates[0][0] - city.lower_corner[0], ground.perimeter_polygon.coordinates[0][1] - city.lower_corner[1])
# print(ground.perimeter_polygon.coordinates[1][0] - city.lower_corner[0], ground.perimeter_polygon.coordinates[1][1] - city.lower_corner[1])
break
ConstructionFactory('nrcan', city).enrich()
UsageFactory('nrcan', city).enrich()
info_lod1 = GeometryHelper.city_mapping(city, plot=True)
for building in city.buildings:
print(building.name)
ns = ''
for n in building.neighbours:
ns = f'{ns} {n.name}'
for surface in n.surfaces:
print('shared', surface.percentage_shared)
print('\t', ns)
# EnergyBuildingsExportsFactory('idf', city, self._output_path).export()

View File

@ -0,0 +1,115 @@
"""
TestExports test and validate the city export formats
SPDX - License - Identifier: LGPL - 3.0 - or -later
Copyright © 2022 Concordia CERC group
Project Coder Guille Gutierrez guillermo.gutierrezmorote@concordia.ca
"""
import subprocess
from pathlib import Path
from unittest import TestCase
import pandas as pd
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
class TestResultsImport(TestCase):
"""
TestImports class contains the unittest for import functionality
"""
def setUp(self) -> None:
"""
Test setup
:return: None
"""
self._example_path = (Path(__file__).parent / 'tests_data').resolve()
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
ConstructionFactory('nrcan', self._city).enrich()
UsageFactory('nrcan', self._city).enrich()
def test_sra_import(self):
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()
# Check that all the buildings have radiance in the surfaces
for building in self._city.buildings:
for surface in building.surfaces:
self.assertIsNotNone(surface.global_irradiance)
def test_meb_import(self):
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()
# Check that all the buildings have heating and cooling values
for building in self._city.buildings:
self.assertIsNotNone(building.heating[cte.MONTH][cte.INSEL_MEB])
self.assertIsNotNone(building.cooling[cte.MONTH][cte.INSEL_MEB])
self.assertIsNotNone(building.heating[cte.YEAR][cte.INSEL_MEB])
self.assertIsNotNone(building.cooling[cte.YEAR][cte.INSEL_MEB])
def test_peak_loads(self):
# todo: this is not technically a import
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()
for building in self._city.buildings:
self.assertIsNotNone(building.heating_peak_load)
self.assertIsNotNone(building.cooling_peak_load)
values = [0 for _ in range(8760)]
values[0] = 1000
expected_yearly = pd.DataFrame([1000], columns=['expected'])
expected_monthly_list = [0 for _ in range(12)]
expected_monthly_list[0] = 1000
expected_monthly = pd.DataFrame(expected_monthly_list, columns=['expected'])
for building in self._city.buildings:
building.heating[cte.HOUR] = pd.DataFrame(values, columns=['dummy'])
building.cooling[cte.HOUR] = pd.DataFrame(values, columns=['dummy'])
self.assertIsNotNone(building.heating_peak_load)
self.assertIsNotNone(building.cooling_peak_load)
pd.testing.assert_series_equal(
building.heating_peak_load[cte.YEAR]['heating peak loads'],
expected_yearly['expected'],
check_names=False
)
pd.testing.assert_series_equal(
building.cooling_peak_load[cte.YEAR]['cooling peak loads'],
expected_yearly['expected'],
check_names=False
)
pd.testing.assert_series_equal(
building.heating_peak_load[cte.MONTH]['heating peak loads'],
expected_monthly['expected'],
check_names=False
)
pd.testing.assert_series_equal(
building.cooling_peak_load[cte.MONTH]['cooling peak loads'],
expected_monthly['expected'],
check_names=False
)