diff --git a/city_model_structure/attributes/schedule.py b/city_model_structure/attributes/schedule.py index 1924dd7b..2d009f7f 100644 --- a/city_model_structure/attributes/schedule.py +++ b/city_model_structure/attributes/schedule.py @@ -9,131 +9,131 @@ from typing import Union, List class Schedule: - """ + """ Schedule class """ - def __init__(self): - self._id = None - self._type = None - self._values = None - self._data_type = None - self._time_step = None - self._time_range = None - self._day_types = None + def __init__(self): + self._id = None + self._type = None + self._values = None + self._data_type = None + self._time_step = None + self._time_range = None + self._day_types = None - @property - def id(self): - """ + @property + def id(self): + """ Get schedule id, an universally unique identifier randomly generated :return: str """ - if self._id is None: - self._id = uuid.uuid4() - return self._id + if self._id is None: + self._id = uuid.uuid4() + return self._id - @property - def type(self) -> Union[None, str]: - """ + @property + def type(self) -> Union[None, str]: + """ Get schedule type :return: None or str """ - return self._type + return self._type - @type.setter - def type(self, value): - """ + @type.setter + def type(self, value): + """ Set schedule type :param: str """ - if value is not None: - self._type = str(value) + if value is not None: + self._type = str(value) - @property - def values(self): - """ + @property + def values(self): + """ Get schedule values :return: [Any] """ - return self._values + return self._values - @values.setter - def values(self, value): - """ + @values.setter + def values(self, value): + """ Set schedule values :param: [Any] """ - self._values = value + self._values = value - @property - def data_type(self) -> Union[None, str]: - """ + @property + def data_type(self) -> Union[None, str]: + """ Get schedule data type from: ['any_number', 'fraction', 'on_off', 'temperature', 'humidity', 'control_type'] :return: None or str """ - return self._data_type + return self._data_type - @data_type.setter - def data_type(self, value): - """ + @data_type.setter + def data_type(self, value): + """ Set schedule data type :param: str """ - if value is not None: - self._data_type = str(value) + if value is not None: + self._data_type = str(value) - @property - def time_step(self) -> Union[None, str]: - """ + @property + def time_step(self) -> Union[None, str]: + """ Get schedule time step from: ['second', 'minute', 'hour', 'day', 'week', 'month'] :return: None or str """ - return self._time_step + return self._time_step - @time_step.setter - def time_step(self, value): - """ + @time_step.setter + def time_step(self, value): + """ Set schedule time step :param: str """ - if value is not None: - self._time_step = str(value) + if value is not None: + self._time_step = str(value) - @property - def time_range(self) -> Union[None, str]: - """ + @property + def time_range(self) -> Union[None, str]: + """ Get schedule time range from: ['minute', 'hour', 'day', 'week', 'month', 'year'] :return: None or str """ - return self._time_range + return self._time_range - @time_range.setter - def time_range(self, value): - """ + @time_range.setter + def time_range(self, value): + """ Set schedule time range :param: str """ - if value is not None: - self._time_range = str(value) + if value is not None: + self._time_range = str(value) - @property - def day_types(self) -> Union[None, List[str]]: - """ + @property + def day_types(self) -> Union[None, List[str]]: + """ Get schedule day types, as many as needed from: ['monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sunday', 'holiday', 'winter_design_day', 'summer_design_day'] :return: None or [str] """ - return self._day_types + return self._day_types - @day_types.setter - def day_types(self, value): - """ + @day_types.setter + def day_types(self, value): + """ Set schedule day types :param: [str] """ - if value is not None: - self._day_types = [str(i) for i in value] + if value is not None: + self._day_types = [str(i) for i in value] diff --git a/city_model_structure/building.py b/city_model_structure/building.py index 1aff3b84..a94e55a6 100644 --- a/city_model_structure/building.py +++ b/city_model_structure/building.py @@ -5,18 +5,15 @@ Copyright © 2020 Project Author Guille Gutierrez guillermo.gutierrezmorote@conc contributors: Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca """ -from typing import List, Union, TypeVar +from typing import List, Union import numpy as np from city_model_structure.building_demand.surface import Surface -from city_model_structure.building_demand.usage_zone import UsageZone from city_model_structure.building_demand.storey import Storey from city_model_structure.city_object import CityObject from city_model_structure.building_demand.household import Household from city_model_structure.building_demand.internal_zone import InternalZone from city_model_structure.attributes.polyhedron import Polyhedron -ThermalZone = TypeVar('ThermalZone') - class Building(CityObject): """ @@ -36,9 +33,7 @@ class Building(CityObject): self._roof_type = None self._storeys = None self._internal_zones = None - self._shell = None - self._thermal_zones = None - self._usage_zones = None + self._shell = None self._type = 'building' self._heating = dict() self._cooling = dict() @@ -108,22 +103,6 @@ class Building(CityObject): """ return self._walls - @property - def usage_zones(self) -> Union[None, List[UsageZone]]: - """ - Get city object usage zones - :return: [UsageZone] - """ - return self._usage_zones - - @usage_zones.setter - def usage_zones(self, value): - """ - Set city object usage zones - :param value: [UsageZone] - """ - self._usage_zones = value - @property def terrains(self) -> Union[None, List[Surface]]: """ @@ -166,22 +145,6 @@ class Building(CityObject): if value is not None: self._basement_heated = int(value) - @property - def thermal_zones(self) -> Union[None, List[ThermalZone]]: - """ - Get building thermal zones - :return: [ThermalZone] - """ - return self._thermal_zones - - @thermal_zones.setter - def thermal_zones(self, value): - """ - Set city object thermal zones - :param value: [ThermalZone] - """ - self._thermal_zones = value - @property def heated_volume(self): """ @@ -368,9 +331,11 @@ class Building(CityObject): Get building heated flag :return: Boolean """ - if self.thermal_zones is None: + if self.internal_zones is None: return False - for thermal_zone in self.thermal_zones: - if thermal_zone.hvac_system is not None: - return True + for internal_zone in self.internal_zones: + if internal_zone.usage_zones is not None: + for usage_zone in internal_zone.usage_zones: + if usage_zone.thermal_control is not None: + return True return False diff --git a/city_model_structure/building_demand/appliances.py b/city_model_structure/building_demand/appliances.py index ed09f795..8fd4f0f6 100644 --- a/city_model_structure/building_demand/appliances.py +++ b/city_model_structure/building_demand/appliances.py @@ -3,7 +3,7 @@ Appliances module SPDX - License - Identifier: LGPL - 3.0 - or -later Copyright © 2022 Project Author Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca """ -from typing import Union +from typing import Union, List from city_model_structure.attributes.schedule import Schedule @@ -12,28 +12,28 @@ class Appliances: Appliances class """ def __init__(self): - self._lighting_density = None + self._appliances_density = None self._convective_fraction = None - self._radiant_fraction = None + self._radiative_fraction = None self._latent_fraction = None - self._schedule = None + self._schedules = None @property - def lighting_density(self) -> Union[None, float]: + def appliances_density(self) -> Union[None, float]: """ - Get lighting density in Watts per m2 + Get appliances density in Watts per m2 :return: None or float """ - return self._lighting_density + return self._appliances_density - @lighting_density.setter - def lighting_density(self, value): + @appliances_density.setter + def appliances_density(self, value): """ - Set lighting density in Watts per m2 + Set appliances density in Watts per m2 :param value: float """ if value is not None: - self._lighting_density = float(value) + self._appliances_density = float(value) @property def convective_fraction(self) -> Union[None, float]: @@ -53,21 +53,21 @@ class Appliances: self._convective_fraction = float(value) @property - def radiant_fraction(self) -> Union[None, float]: + def radiative_fraction(self) -> Union[None, float]: """ Get radiant fraction :return: None or float """ - return self._radiant_fraction + return self._radiative_fraction - @radiant_fraction.setter - def radiant_fraction(self, value): + @radiative_fraction.setter + def radiative_fraction(self, value): """ Set radiant fraction :param value: float """ if value is not None: - self._radiant_fraction = float(value) + self._radiative_fraction = float(value) @property def latent_fraction(self) -> Union[None, float]: @@ -87,17 +87,17 @@ class Appliances: self._latent_fraction = float(value) @property - def schedule(self) -> Union[None, Schedule]: + def schedules(self) -> Union[None, List[Schedule]]: """ - Get schedule - :return: None or Schedule + Get schedules + :return: None or [Schedule] """ - return self._schedule + return self._schedules - @schedule.setter - def schedule(self, value): + @schedules.setter + def schedules(self, value): """ - Set schedule - :param value: Schedule + Set schedules + :param value: [Schedule] """ - self._schedule = value + self._schedules = value diff --git a/city_model_structure/building_demand/internal_gains.py b/city_model_structure/building_demand/internal_gains.py index 4cfeabb9..9384cf21 100644 --- a/city_model_structure/building_demand/internal_gains.py +++ b/city_model_structure/building_demand/internal_gains.py @@ -4,7 +4,7 @@ SPDX - License - Identifier: LGPL - 3.0 - or -later Copyright © 2020 Project Author Guille Gutierrez guillermo.gutierrezmorote@concordia.ca """ -from typing import Union +from typing import Union, List from city_model_structure.attributes.schedule import Schedule @@ -19,7 +19,7 @@ class InternalGains: self._convective_fraction = None self._radiative_fraction = None self._latent_fraction = None - self._schedule = None + self._schedules = None @property def type(self) -> Union[None, str]: @@ -107,17 +107,17 @@ class InternalGains: self._latent_fraction = float(value) @property - def schedule(self) -> Union[None, Schedule]: + def schedules(self) -> Union[None, List[Schedule]]: """ Get internal gain schedule - :return: Schedule + :return: [Schedule] """ - return self._schedule + return self._schedules - @schedule.setter - def schedule(self, value): + @schedules.setter + def schedules(self, value): """ Set internal gain schedule - :param value: Schedule + :param value: [Schedule] """ - self._schedule = value + self._schedules = value diff --git a/city_model_structure/building_demand/internal_zone.py b/city_model_structure/building_demand/internal_zone.py index 222174bb..74678473 100644 --- a/city_model_structure/building_demand/internal_zone.py +++ b/city_model_structure/building_demand/internal_zone.py @@ -3,9 +3,13 @@ InternalZone module. It saves the original geometrical information from interior SPDX - License - Identifier: LGPL - 3.0 - or -later Copyright © 2022 Project Author Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca """ + import uuid +from typing import Union, List from city_model_structure.building_demand.usage_zone import UsageZone +from city_model_structure.building_demand.thermal_zone import ThermalZone from city_model_structure.attributes.polyhedron import Polyhedron +from city_model_structure.energy_systems.hvac_system import HvacSystem class InternalZone: @@ -18,7 +22,9 @@ class InternalZone: self._geometry = None self._volume = None self._area = area - self._usage_zones = [] + self._thermal_zones = None + self._usage_zones = None + self._hvac_system = None @property def id(self): @@ -82,3 +88,36 @@ class InternalZone: :param value: [UsageZone] """ self._usage_zones = value + + @property + def hvac_system(self) -> Union[None, HvacSystem]: + """ + Get HVAC system installed for this thermal zone + :return: None or HvacSystem + """ + return self._hvac_system + + @hvac_system.setter + def hvac_system(self, value): + """ + Set HVAC system installed for this thermal zone + :param value: HvacSystem + """ + self._hvac_system = value + + @property + def thermal_zones(self) -> Union[None, List[ThermalZone]]: + """ + Get building thermal zones + :return: [ThermalZone] + """ + return self._thermal_zones + + @thermal_zones.setter + def thermal_zones(self, value): + """ + Set city object thermal zones + :param value: [ThermalZone] + """ + self._thermal_zones = value + diff --git a/city_model_structure/building_demand/lighting.py b/city_model_structure/building_demand/lighting.py index 0407a432..0e72249f 100644 --- a/city_model_structure/building_demand/lighting.py +++ b/city_model_structure/building_demand/lighting.py @@ -3,7 +3,7 @@ Lighting module SPDX - License - Identifier: LGPL - 3.0 - or -later Copyright © 2022 Project Author Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca """ -from typing import Union +from typing import Union, List from city_model_structure.attributes.schedule import Schedule @@ -14,9 +14,9 @@ class Lighting: def __init__(self): self._lighting_density = None self._convective_fraction = None - self._radiant_fraction = None + self._radiative_fraction = None self._latent_fraction = None - self._schedule = None + self._schedules = None @property def lighting_density(self) -> Union[None, float]: @@ -53,21 +53,21 @@ class Lighting: self._convective_fraction = float(value) @property - def radiant_fraction(self) -> Union[None, float]: + def radiative_fraction(self) -> Union[None, float]: """ Get radiant fraction :return: None or float """ - return self._radiant_fraction + return self._radiative_fraction - @radiant_fraction.setter - def radiant_fraction(self, value): + @radiative_fraction.setter + def radiative_fraction(self, value): """ Set radiant fraction :param value: float """ if value is not None: - self._radiant_fraction = float(value) + self._radiative_fraction = float(value) @property def latent_fraction(self) -> Union[None, float]: @@ -87,17 +87,17 @@ class Lighting: self._latent_fraction = float(value) @property - def schedule(self) -> Union[None, Schedule]: + def schedules(self) -> Union[None, List[Schedule]]: """ - Get schedule - :return: None or Schedule + Get schedules + :return: None or [Schedule] """ - return self._schedule + return self._schedules - @schedule.setter - def schedule(self, value): + @schedules.setter + def schedules(self, value): """ - Set schedule - :param value: Schedule + Set schedules + :param value: [Schedule] """ - self._schedule = value + self._schedules = value diff --git a/city_model_structure/building_demand/occupancy.py b/city_model_structure/building_demand/occupancy.py index 443030ca..ec9ebf1e 100644 --- a/city_model_structure/building_demand/occupancy.py +++ b/city_model_structure/building_demand/occupancy.py @@ -15,9 +15,9 @@ class Occupancy: def __init__(self): self._occupancy_density = None self._sensible_convective_internal_gain = None - self._sensible_radiant_internal_gain = None + self._sensible_radiative_internal_gain = None self._latent_internal_gain = None - self._occupancy_schedule = None + self._occupancy_schedules = None self._occupants = None @property @@ -55,21 +55,21 @@ class Occupancy: self._sensible_convective_internal_gain = float(value) @property - def sensible_radiant_internal_gain(self) -> Union[None, float]: + def sensible_radiative_internal_gain(self) -> Union[None, float]: """ Get sensible radiant internal gain in Watts per m2 :return: None or float """ - return self._sensible_radiant_internal_gain + return self._sensible_radiative_internal_gain - @sensible_radiant_internal_gain.setter - def sensible_radiant_internal_gain(self, value): + @sensible_radiative_internal_gain.setter + def sensible_radiative_internal_gain(self, value): """ Set sensible radiant internal gain in Watts per m2 :param value: float """ if value is not None: - self._sensible_radiant_internal_gain = float(value) + self._sensible_radiative_internal_gain = float(value) @property def latent_internal_gain(self) -> Union[None, float]: @@ -89,20 +89,20 @@ class Occupancy: self._latent_internal_gain = float(value) @property - def occupancy_schedule(self) -> Union[None, Schedule]: + def occupancy_schedules(self) -> Union[None, List[Schedule]]: """ - Get occupancy schedule - :return: None or Schedule + Get occupancy schedules + :return: None or [Schedule] """ - return self._occupancy_schedule + return self._occupancy_schedules - @occupancy_schedule.setter - def occupancy_schedule(self, value): + @occupancy_schedules.setter + def occupancy_schedules(self, value): """ - Set occupancy schedule - :param value: Schedule + Set occupancy schedules + :param value: [Schedule] """ - self._occupancy_schedule = value + self._occupancy_schedules = value @property def occupants(self) -> Union[None, List[Occupant]]: diff --git a/city_model_structure/building_demand/thermal_control.py b/city_model_structure/building_demand/thermal_control.py index 20888023..0c4c8a62 100644 --- a/city_model_structure/building_demand/thermal_control.py +++ b/city_model_structure/building_demand/thermal_control.py @@ -3,7 +3,7 @@ ThermalControl module SPDX - License - Identifier: LGPL - 3.0 - or -later Copyright © 2022 Project Author Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca """ -from typing import Union +from typing import Union, List from city_model_structure.attributes.schedule import Schedule @@ -12,35 +12,59 @@ class ThermalControl: ThermalControl class """ def __init__(self): - self._heating_set_point = None - self._cooling_set_point = None - self._hvac_availability = None + self._mean_heating_set_point = None self._heating_set_back = None + self._mean_cooling_set_point = None + self._hvac_availability_schedules = None + self._heating_set_point_schedules = None + self._cooling_set_point_schedules = None + + @staticmethod + def _maximum_value(schedules): + maximum = -1000 + for schedule in schedules: + for value in schedule.values: + if value > maximum: + maximum = value + return maximum + + @staticmethod + def _minimum_value(schedules): + minimum = 1000 + for schedule in schedules: + for value in schedule.values: + if value < minimum: + minimum = value + return minimum @property - def heating_set_point(self) -> Union[None, Schedule]: + def mean_heating_set_point(self) -> Union[None, float]: """ Get heating set point defined for a thermal zone in Celsius - :return: None or Schedule + :return: None or float """ - return self._heating_set_point + if self._mean_heating_set_point is None: + if self.heating_set_point_schedules is not None: + self._mean_heating_set_point = self._maximum_value(self.heating_set_point_schedules) + return self._mean_heating_set_point - @heating_set_point.setter - def heating_set_point(self, value): + @mean_heating_set_point.setter + def mean_heating_set_point(self, value): """ Set heating set point defined for a thermal zone in Celsius - :param value: Schedule + :param value: float """ - self._heating_set_point = value + self._mean_heating_set_point = value @property def heating_set_back(self) -> Union[None, float]: """ Get heating set back defined for a thermal zone in Celsius - Heating set back is the only parameter which is not a schedule as it is either one value or it is implicit in the - set point schedule :return: None or float """ + if self._heating_set_back is None: + if self.heating_set_point_schedules is not None: + self._heating_set_back = self._minimum_value(self.heating_set_point_schedules) return self._heating_set_back @heating_set_back.setter @@ -53,34 +77,68 @@ class ThermalControl: self._heating_set_back = float(value) @property - def cooling_set_point(self) -> Union[None, Schedule]: + def mean_cooling_set_point(self) -> Union[None, float]: """ Get cooling set point defined for a thermal zone in Celsius - :return: None or Schedule + :return: None or float """ - return self._cooling_set_point + if self._mean_cooling_set_point is None: + if self.cooling_set_point_schedules is not None: + self._mean_cooling_set_point = self._minimum_value(self.cooling_set_point_schedules) + return self._mean_cooling_set_point - @cooling_set_point.setter - def cooling_set_point(self, value): + @mean_cooling_set_point.setter + def mean_cooling_set_point(self, value): """ Set cooling set point defined for a thermal zone in Celsius - :param value: Schedule + :param value: float """ - self._cooling_set_point = value + self._mean_cooling_set_point = value @property - def hvac_availability(self) -> Union[None, Schedule]: + def hvac_availability_schedules(self) -> Union[None, List[Schedule]]: """ Get the availability of the conditioning system defined for a thermal zone - :return: None or Schedule + :return: None or [Schedule] """ - return self._hvac_availability + return self._hvac_availability_schedules - @hvac_availability.setter - def hvac_availability(self, value): + @hvac_availability_schedules.setter + def hvac_availability_schedules(self, value): """ Set the availability of the conditioning system defined for a thermal zone - :param value: Schedule + :param value: [Schedule] """ - self._hvac_availability = value + self._hvac_availability_schedules = value + @property + def heating_set_point_schedules(self) -> Union[None, List[Schedule]]: + """ + Get heating set point schedule defined for a thermal zone in Celsius + :return: None or [Schedule] + """ + return self._heating_set_point_schedules + + @heating_set_point_schedules.setter + def heating_set_point_schedules(self, value): + """ + Set heating set point schedule defined for a thermal zone in Celsius + :param value: [Schedule] + """ + self._heating_set_point_schedules = value + + @property + def cooling_set_point_schedules(self) -> Union[None, List[Schedule]]: + """ + Get cooling set point schedule defined for a thermal zone in Celsius + :return: None or [Schedule] + """ + return self._cooling_set_point_schedules + + @cooling_set_point_schedules.setter + def cooling_set_point_schedules(self, value): + """ + Set cooling set point schedule defined for a thermal zone in Celsius + :param value: [Schedule] + """ + self._cooling_set_point_schedules = value diff --git a/city_model_structure/building_demand/thermal_zone.py b/city_model_structure/building_demand/thermal_zone.py index dd20bc28..10f8ac0b 100644 --- a/city_model_structure/building_demand/thermal_zone.py +++ b/city_model_structure/building_demand/thermal_zone.py @@ -5,11 +5,11 @@ Copyright © 2020 Project Author Guille Gutierrez guillermo.gutierrezmorote@conc Contributors Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca """ import uuid -from typing import List, Union, Tuple, TypeVar +from typing import List, Union, TypeVar from city_model_structure.building_demand.usage_zone import UsageZone +from city_model_structure.attributes.schedule import Schedule from city_model_structure.building_demand.thermal_control import ThermalControl from city_model_structure.energy_systems.hvac_system import HvacSystem -from city_model_structure.attributes.schedule import Schedule ThermalBoundary = TypeVar('ThermalBoundary') @@ -27,11 +27,12 @@ class ThermalZone: self._indirectly_heated_area_ratio = None self._infiltration_rate_system_on = None self._infiltration_rate_system_off = None - self._usage_zones = None self._volume = volume self._ordinate_number = None + self._usage_zones = None self._thermal_control = None self._hvac_system = None + self._view_factors_matrix = None @property def id(self): @@ -142,22 +143,6 @@ class ThermalZone: """ self._infiltration_rate_system_off = value - @property - def usage_zones(self) -> Tuple[float, UsageZone]: - """ - Get list of usage zones and the percentage of thermal zone's volume affected by that usage - :return: [UsageZone] - """ - return self._usage_zones - - @usage_zones.setter - def usage_zones(self, values): - """ - Set list of usage zones and the percentage of thermal zone's volume affected by that usage - :param values: Tuple[float, UsageZone] - """ - self._usage_zones = values - @property def volume(self): """ @@ -183,10 +168,29 @@ class ThermalZone: if value is not None: self._ordinate_number = int(value) + @property + def usage_zones(self) -> [UsageZone]: + """ + Get list of usage zones and the percentage of thermal zone's volume affected by that usage + From internal_zone + :return: [UsageZone] + """ + return self._usage_zones + + @usage_zones.setter + def usage_zones(self, values): + """ + Set list of usage zones and the percentage of thermal zone's volume affected by that usage + From internal_zone + :param values: [UsageZone] + """ + self._usage_zones = values + @property def thermal_control(self) -> Union[None, ThermalControl]: """ Get thermal control of this thermal zone + From internal_zone :return: None or ThermalControl """ return self._thermal_control @@ -195,6 +199,7 @@ class ThermalZone: def thermal_control(self, value): """ Set thermal control for this thermal zone + From internal_zone :param value: ThermalControl """ self._thermal_control = value @@ -203,6 +208,7 @@ class ThermalZone: def hvac_system(self) -> Union[None, HvacSystem]: """ Get HVAC system installed for this thermal zone + From internal_zone :return: None or HvacSystem """ return self._hvac_system @@ -211,6 +217,23 @@ class ThermalZone: def hvac_system(self, value): """ Set HVAC system installed for this thermal zone + From internal_zone :param value: HvacSystem """ self._hvac_system = value + + @property + def view_factors_matrix(self): + """ + Get thermal zone view factors matrix + :return: [[float]] + """ + return self._view_factors_matrix + + @view_factors_matrix.setter + def view_factors_matrix(self, value): + """ + Set thermal zone view factors matrix + :param value: [[float]] + """ + self._view_factors_matrix = value diff --git a/city_model_structure/building_demand/usage_zone.py b/city_model_structure/building_demand/usage_zone.py index 590ce6d5..f8d77890 100644 --- a/city_model_structure/building_demand/usage_zone.py +++ b/city_model_structure/building_demand/usage_zone.py @@ -11,6 +11,7 @@ from city_model_structure.building_demand.internal_gains import InternalGains from city_model_structure.building_demand.occupancy import Occupancy from city_model_structure.building_demand.lighting import Lighting from city_model_structure.building_demand.appliances import Appliances +from city_model_structure.building_demand.thermal_control import ThermalControl class UsageZone: @@ -20,6 +21,7 @@ class UsageZone: def __init__(self): self._id = None self._usage = None + self._percentage = None self._not_detailed_source_mean_annual_internal_gains = None self._hours_day = None self._days_year = None @@ -29,6 +31,7 @@ class UsageZone: self._lighting = None self._appliances = None self._internal_gains = None + self._thermal_control = None @property def id(self): @@ -40,6 +43,40 @@ class UsageZone: self._id = uuid.uuid4() return self._id + @property + def usage(self) -> Union[None, str]: + """ + Get usage zone usage + :return: None or str + """ + return self._usage + + @usage.setter + def usage(self, value): + """ + Set usage zone usage + :param value: str + """ + if value is not None: + self._usage = str(value) + + @property + def percentage(self): + """ + Get usage zone percentage in range[0,1] + :return: float + """ + return self._percentage + + @percentage.setter + def percentage(self, value): + """ + Set usage zone percentage in range[0,1] + :param value: float + """ + if value is not None: + self._percentage = float(value) + @property def not_detailed_source_mean_annual_internal_gains(self) -> List[InternalGains]: """ @@ -107,23 +144,6 @@ class UsageZone: if value is not None: self._mechanical_air_change = float(value) - @property - def usage(self) -> Union[None, str]: - """ - Get usage zone usage - :return: None or str - """ - return self._usage - - @usage.setter - def usage(self, value): - """ - Set usage zone usage - :param value: str - """ - if value is not None: - self._usage = str(value) - @property def electrical_app_average_consumption_sqm_year(self) -> Union[None, float]: """ @@ -199,22 +219,22 @@ class UsageZone: _internal_gain = InternalGains() _internal_gain.type = cte.OCCUPANCY _total_heat_gain = (self.occupancy.sensible_convective_internal_gain - + self.occupancy.sensible_radiant_internal_gain + + self.occupancy.sensible_radiative_internal_gain + self.occupancy.latent_internal_gain) _internal_gain.average_internal_gain = _total_heat_gain _internal_gain.latent_fraction = self.occupancy.latent_internal_gain / _total_heat_gain - _internal_gain.radiative_fraction = self.occupancy.sensible_radiant_internal_gain / _total_heat_gain + _internal_gain.radiative_fraction = self.occupancy.sensible_radiative_internal_gain / _total_heat_gain _internal_gain.convective_fraction = self.occupancy.sensible_convective_internal_gain / _total_heat_gain - _internal_gain.schedule = self.occupancy.occupancy_schedule + _internal_gain.schedules = self.occupancy.occupancy_schedules self._internal_gains = [_internal_gain] if self.lighting is not None: _internal_gain = InternalGains() _internal_gain.type = cte.LIGHTING _internal_gain.average_internal_gain = self.lighting.lighting_density _internal_gain.latent_fraction = self.lighting.latent_fraction - _internal_gain.radiative_fraction = self.lighting.radiant_fraction + _internal_gain.radiative_fraction = self.lighting.radiative_fraction _internal_gain.convective_fraction = self.lighting.convective_fraction - _internal_gain.schedule = self.lighting.schedule + _internal_gain.schedules = self.lighting.schedules if self._internal_gains is not None: self._internal_gains.append(_internal_gain) else: @@ -222,13 +242,29 @@ class UsageZone: if self.appliances is not None: _internal_gain = InternalGains() _internal_gain.type = cte.APPLIANCES - _internal_gain.average_internal_gain = self.appliances.lighting_density + _internal_gain.average_internal_gain = self.appliances.appliances_density _internal_gain.latent_fraction = self.appliances.latent_fraction - _internal_gain.radiative_fraction = self.appliances.radiant_fraction + _internal_gain.radiative_fraction = self.appliances.radiative_fraction _internal_gain.convective_fraction = self.appliances.convective_fraction - _internal_gain.schedule = self.appliances.schedule + _internal_gain.schedules = self.appliances.schedules if self._internal_gains is not None: self._internal_gains.append(_internal_gain) else: self._internal_gains = [_internal_gain] return self._internal_gains + + @property + def thermal_control(self) -> Union[None, ThermalControl]: + """ + Get thermal control of this thermal zone + :return: None or ThermalControl + """ + return self._thermal_control + + @thermal_control.setter + def thermal_control(self, value): + """ + Set thermal control for this thermal zone + :param value: ThermalControl + """ + self._thermal_control = value diff --git a/data/usage/comnet_schedules_archetypes.xlsx b/data/usage/comnet_schedules_archetypes.xlsx new file mode 100644 index 00000000..8fce488c Binary files /dev/null and b/data/usage/comnet_schedules_archetypes.xlsx differ diff --git a/data/usage/de_library.xml b/data/usage/de_library.xml index 0580d70a..a13e323c 100644 --- a/data/usage/de_library.xml +++ b/data/usage/de_library.xml @@ -84,7 +84,7 @@ DIN 18599-10 16.0 16.0 16.0 16.0 16.0 16.0 20.0 20.0 20.0 20.0 20.0 20.0 20.0 20.0 20.0 20.0 20.0 - 20.0 20.0 20.0 20.0 20.0 20.0 0.0 + 20.0 20.0 20.0 20.0 20.0 20.0 20.0 @@ -117,7 +117,7 @@ 16.0 16.0 16.0 16.0 16.0 16.0 20.0 20.0 20.0 20.0 20.0 20.0 20.0 20.0 20.0 20.0 20.0 - 20.0 20.0 20.0 20.0 20.0 20.0 0.0 + 20.0 20.0 20.0 20.0 20.0 20.0 20.0 diff --git a/helpers/constants.py b/helpers/constants.py index b55cf955..684a367f 100644 --- a/helpers/constants.py +++ b/helpers/constants.py @@ -11,6 +11,7 @@ KELVIN = 273.15 HOUR_TO_MINUTES = 60 METERS_TO_FEET = 3.28084 BTU_H_TO_WATTS = 0.29307107 +KILO_WATTS_HOUR_TO_JULES = 3600000 # time SECOND = 'second' diff --git a/imports/construction/ca_physics_parameters.py b/imports/construction/ca_physics_parameters.py index d04c81ea..5b2ff50c 100644 --- a/imports/construction/ca_physics_parameters.py +++ b/imports/construction/ca_physics_parameters.py @@ -25,23 +25,25 @@ class CaPhysicsParameters(NrelPhysicsInterface): city = self._city # it is assumed that all buildings have the same archetypes' keys for building in city.buildings: - archetype = self._search_archetype(ConstructionHelper.nrcan_from_function(building.function), - building.year_of_construction) - if archetype is None: + try: + archetype = self._search_archetype(ConstructionHelper.nrcan_from_function(building.function), + building.year_of_construction) + except KeyError: sys.stderr.write(f'Building {building.name} has unknown archetype for building function: ' f'{ConstructionHelper.nrcan_from_function(building.function)} ' f'and building year of construction: {building.year_of_construction}\n') - continue + return # if building has no thermal zones defined from geometry, one thermal zone per storey is assigned - if building.thermal_zones is None: - self._create_storeys(building, archetype) - thermal_zones = [] - for storey in building.storeys: - thermal_zones.append(storey.thermal_zone) - building.thermal_zones = thermal_zones + if len(building.internal_zones) == 1: + if building.internal_zones[0].thermal_zones is None: + self._create_storeys(building, archetype) + thermal_zones = [] + for storey in building.storeys: + thermal_zones.append(storey.thermal_zone) + building.internal_zones[0].thermal_zones = thermal_zones - self._assign_values(building, archetype) + self._assign_values(building.internal_zones, archetype) def _search_archetype(self, function, year_of_construction): for building_archetype in self._building_archetypes: @@ -54,31 +56,32 @@ class CaPhysicsParameters(NrelPhysicsInterface): return building_archetype return None - def _assign_values(self, building, archetype): - for thermal_zone in building.thermal_zones: - thermal_zone.additional_thermal_bridge_u_value = archetype.additional_thermal_bridge_u_value - thermal_zone.effective_thermal_capacity = archetype.effective_thermal_capacity - thermal_zone.indirectly_heated_area_ratio = archetype.indirectly_heated_area_ratio - thermal_zone.infiltration_rate_system_on = archetype.infiltration_rate_system_on - thermal_zone.infiltration_rate_system_off = archetype.infiltration_rate_system_off - for thermal_boundary in thermal_zone.thermal_boundaries: - construction_type = ConstructionHelper.nrcan_construction_types[thermal_boundary.type] - thermal_boundary_archetype = self._search_construction_in_archetype(archetype, construction_type) - thermal_boundary.u_value = thermal_boundary_archetype.overall_u_value - thermal_boundary.outside_solar_absorptance = thermal_boundary_archetype.outside_solar_absorptance - thermal_boundary.construction_name = thermal_boundary_archetype.construction_name - try: - thermal_boundary.window_ratio = thermal_boundary_archetype.window_ratio - except ValueError: - # This is the normal operation way when the windows are defined in the geometry - continue - if thermal_boundary.thermal_openings is not None: - for thermal_opening in thermal_boundary.thermal_openings: - if thermal_boundary_archetype.thermal_opening_archetype is not None: - thermal_opening_archetype = thermal_boundary_archetype.thermal_opening_archetype - thermal_opening.frame_ratio = thermal_opening_archetype.frame_ratio - thermal_opening.g_value = thermal_opening_archetype.g_value - thermal_opening.overall_u_value = thermal_opening_archetype.overall_u_value + def _assign_values(self, internal_zones, archetype): + for internal_zone in internal_zones: + for thermal_zone in internal_zone.thermal_zones: + thermal_zone.additional_thermal_bridge_u_value = archetype.additional_thermal_bridge_u_value + thermal_zone.effective_thermal_capacity = archetype.effective_thermal_capacity + thermal_zone.indirectly_heated_area_ratio = archetype.indirectly_heated_area_ratio + thermal_zone.infiltration_rate_system_on = archetype.infiltration_rate_system_on + thermal_zone.infiltration_rate_system_off = archetype.infiltration_rate_system_off + for thermal_boundary in thermal_zone.thermal_boundaries: + construction_type = ConstructionHelper.nrcan_construction_types[thermal_boundary.type] + thermal_boundary_archetype = self._search_construction_in_archetype(archetype, construction_type) + thermal_boundary.u_value = thermal_boundary_archetype.overall_u_value + thermal_boundary.outside_solar_absorptance = thermal_boundary_archetype.outside_solar_absorptance + thermal_boundary.construction_name = thermal_boundary_archetype.construction_name + try: + thermal_boundary.window_ratio = thermal_boundary_archetype.window_ratio + except ValueError: + # This is the normal operation way when the windows are defined in the geometry + continue + if thermal_boundary.thermal_openings is not None: + for thermal_opening in thermal_boundary.thermal_openings: + if thermal_boundary_archetype.thermal_opening_archetype is not None: + thermal_opening_archetype = thermal_boundary_archetype.thermal_opening_archetype + thermal_opening.frame_ratio = thermal_opening_archetype.frame_ratio + thermal_opening.g_value = thermal_opening_archetype.g_value + thermal_opening.overall_u_value = thermal_opening_archetype.overall_u_value @staticmethod def _create_storeys(building, archetype): diff --git a/imports/construction/helpers/construction_helper.py b/imports/construction/helpers/construction_helper.py index 0cf05326..32402984 100644 --- a/imports/construction/helpers/construction_helper.py +++ b/imports/construction/helpers/construction_helper.py @@ -12,7 +12,7 @@ class ConstructionHelper: Construction helper """ # NREL - function_to_nrel = { + _function_to_nrel = { cte.RESIDENTIAL: 'residential', cte.SFH: 'single family house', cte.MFH: 'multifamily house', @@ -27,12 +27,12 @@ class ConstructionHelper: cte.OFFICE: 'office', cte.LARGE_OFFICE: 'large office' } - nrel_function_default_value = 'residential' - nrel_standards = { + + _nrel_standards = { 'ASHRAE Std189': 1, 'ASHRAE 90.1_2004': 2 } - reference_city_to_nrel_climate_zone = { + _reference_city_to_nrel_climate_zone = { 'Miami': 'ASHRAE_2004:1A', 'Houston': 'ASHRAE_2004:2A', 'Phoenix': 'ASHRAE_2004:2B', @@ -51,6 +51,7 @@ class ConstructionHelper: 'Fairbanks': 'ASHRAE_2004:8A' } nrel_window_types = [cte.WINDOW, cte.DOOR, cte.SKYLIGHT] + nrel_construction_types = { cte.WALL: 'exterior wall', cte.INTERIOR_WALL: 'interior wall', @@ -62,7 +63,7 @@ class ConstructionHelper: } # NRCAN - function_to_nrcan = { + _function_to_nrcan = { cte.RESIDENTIAL: 'residential', cte.SFH: 'single family house', cte.MFH: 'multifamily house', @@ -78,8 +79,9 @@ class ConstructionHelper: cte.LARGE_OFFICE: 'large office', cte.OFFICE_WORKSHOP: 'residential' } - nrcan_function_default_value = 'residential' + nrcan_window_types = [cte.WINDOW] + nrcan_construction_types = { cte.WALL: 'wall', cte.GROUND_WALL: 'basement_wall', @@ -97,10 +99,9 @@ class ConstructionHelper: :return: str """ try: - return ConstructionHelper.function_to_nrel[function] + return ConstructionHelper._function_to_nrel[function] except KeyError: - sys.stderr.write('Error: keyword not found. Returned default NREL function "residential"\n') - return ConstructionHelper.nrel_function_default_value + sys.stderr.write('Error: keyword not found.\n') @staticmethod def yoc_to_nrel_standard(year_of_construction): @@ -136,7 +137,7 @@ class ConstructionHelper: :return: str """ reference_city = ConstructionHelper.city_to_reference_city(city) - return ConstructionHelper.reference_city_to_nrel_climate_zone[reference_city] + return ConstructionHelper._reference_city_to_nrel_climate_zone[reference_city] @staticmethod def nrcan_from_function(function): @@ -146,7 +147,6 @@ class ConstructionHelper: :return: str """ try: - return ConstructionHelper.function_to_nrcan[function] + return ConstructionHelper._function_to_nrcan[function] except KeyError: - sys.stderr.write('Error: keyword not found. Returned default NRCAN function "residential"\n') - return ConstructionHelper.nrcan_function_default_value + sys.stderr.write('Error: keyword not found.\n') diff --git a/imports/construction/us_physics_parameters.py b/imports/construction/us_physics_parameters.py index 6e19e96d..de79697b 100644 --- a/imports/construction/us_physics_parameters.py +++ b/imports/construction/us_physics_parameters.py @@ -33,23 +33,25 @@ class UsPhysicsParameters(NrelPhysicsInterface): building_type = ConstructionHelper.nrel_from_function(building.function) if building_type is None: return - archetype = self._search_archetype(building_type, - ConstructionHelper.yoc_to_nrel_standard(building.year_of_construction), - self._climate_zone) - if archetype is None: + try: + archetype = self._search_archetype(building_type, + ConstructionHelper.yoc_to_nrel_standard(building.year_of_construction), + self._climate_zone) + except KeyError: sys.stderr.write(f'Building {building.name} has unknown archetype for building function: {building.function} ' f'and building year of construction: {building.year_of_construction}\n') - continue + return # if building has no thermal zones defined from geometry, one thermal zone per storey is assigned - if building.thermal_zones is None: - self._create_storeys(building, archetype) - thermal_zones = [] - for storey in building.storeys: - thermal_zones.append(storey.thermal_zone) - building.thermal_zones = thermal_zones + if len(building.internal_zones) == 1: + if building.internal_zones[0].thermal_zones is None: + self._create_storeys(building, archetype) + thermal_zones = [] + for storey in building.storeys: + thermal_zones.append(storey.thermal_zone) + building.internal_zones[0].thermal_zones = thermal_zones - self._assign_values(building, archetype) + self._assign_values(building.internal_zones, archetype) def _search_archetype(self, building_type, standard, climate_zone): for building_archetype in self._building_archetypes: @@ -60,52 +62,53 @@ class UsPhysicsParameters(NrelPhysicsInterface): return building_archetype return None - def _assign_values(self, building, archetype): - for thermal_zone in building.thermal_zones: - thermal_zone.additional_thermal_bridge_u_value = archetype.additional_thermal_bridge_u_value - thermal_zone.effective_thermal_capacity = archetype.effective_thermal_capacity - thermal_zone.indirectly_heated_area_ratio = archetype.indirectly_heated_area_ratio - thermal_zone.infiltration_rate_system_on = archetype.infiltration_rate_system_on - thermal_zone.infiltration_rate_system_off = archetype.infiltration_rate_system_off - for thermal_boundary in thermal_zone.thermal_boundaries: - construction_type = ConstructionHelper.nrel_construction_types[thermal_boundary.type] - thermal_boundary_archetype = self._search_construction_in_archetype(archetype, construction_type) - thermal_boundary.outside_solar_absorptance = thermal_boundary_archetype.outside_solar_absorptance - thermal_boundary.outside_thermal_absorptance = thermal_boundary_archetype.outside_thermal_absorptance - thermal_boundary.outside_visible_absorptance = thermal_boundary_archetype.outside_visible_absorptance - thermal_boundary.construction_name = thermal_boundary_archetype.construction_name - try: - thermal_boundary.window_ratio = thermal_boundary_archetype.window_ratio - except ValueError: - # This is the normal operation way when the windows are defined in the geometry - continue - thermal_boundary.layers = [] - for layer_archetype in thermal_boundary_archetype.layers: - layer = Layer() - layer.thickness = layer_archetype.thickness - material = Material() - material.name = layer_archetype.name - material.no_mass = layer_archetype.no_mass - material.density = layer_archetype.density - material.conductivity = layer_archetype.conductivity - material.specific_heat = layer_archetype.specific_heat - material.solar_absorptance = layer_archetype.solar_absorptance - material.thermal_absorptance = layer_archetype.thermal_absorptance - material.visible_absorptance = layer_archetype.visible_absorptance - material.thermal_resistance = layer_archetype.thermal_resistance - layer.material = material - thermal_boundary.layers.append(layer) - for thermal_opening in thermal_boundary.thermal_openings: - if thermal_boundary_archetype.thermal_opening_archetype is not None: - thermal_opening_archetype = thermal_boundary_archetype.thermal_opening_archetype - thermal_opening.frame_ratio = thermal_opening_archetype.frame_ratio - thermal_opening.g_value = thermal_opening_archetype.g_value - thermal_opening.conductivity = thermal_opening_archetype.conductivity - thermal_opening.thickness = thermal_opening_archetype.thickness - thermal_opening.back_side_solar_transmittance_at_normal_incidence = \ - thermal_opening_archetype.back_side_solar_transmittance_at_normal_incidence - thermal_opening.front_side_solar_transmittance_at_normal_incidence = \ - thermal_opening_archetype.front_side_solar_transmittance_at_normal_incidence + def _assign_values(self, internal_zones, archetype): + for internal_zone in internal_zones: + for thermal_zone in internal_zone.thermal_zones: + thermal_zone.additional_thermal_bridge_u_value = archetype.additional_thermal_bridge_u_value + thermal_zone.effective_thermal_capacity = archetype.effective_thermal_capacity + thermal_zone.indirectly_heated_area_ratio = archetype.indirectly_heated_area_ratio + thermal_zone.infiltration_rate_system_on = archetype.infiltration_rate_system_on + thermal_zone.infiltration_rate_system_off = archetype.infiltration_rate_system_off + for thermal_boundary in thermal_zone.thermal_boundaries: + construction_type = ConstructionHelper.nrel_construction_types[thermal_boundary.type] + thermal_boundary_archetype = self._search_construction_in_archetype(archetype, construction_type) + thermal_boundary.outside_solar_absorptance = thermal_boundary_archetype.outside_solar_absorptance + thermal_boundary.outside_thermal_absorptance = thermal_boundary_archetype.outside_thermal_absorptance + thermal_boundary.outside_visible_absorptance = thermal_boundary_archetype.outside_visible_absorptance + thermal_boundary.construction_name = thermal_boundary_archetype.construction_name + try: + thermal_boundary.window_ratio = thermal_boundary_archetype.window_ratio + except ValueError: + # This is the normal operation way when the windows are defined in the geometry + continue + thermal_boundary.layers = [] + for layer_archetype in thermal_boundary_archetype.layers: + layer = Layer() + layer.thickness = layer_archetype.thickness + material = Material() + material.name = layer_archetype.name + material.no_mass = layer_archetype.no_mass + material.density = layer_archetype.density + material.conductivity = layer_archetype.conductivity + material.specific_heat = layer_archetype.specific_heat + material.solar_absorptance = layer_archetype.solar_absorptance + material.thermal_absorptance = layer_archetype.thermal_absorptance + material.visible_absorptance = layer_archetype.visible_absorptance + material.thermal_resistance = layer_archetype.thermal_resistance + layer.material = material + thermal_boundary.layers.append(layer) + for thermal_opening in thermal_boundary.thermal_openings: + if thermal_boundary_archetype.thermal_opening_archetype is not None: + thermal_opening_archetype = thermal_boundary_archetype.thermal_opening_archetype + thermal_opening.frame_ratio = thermal_opening_archetype.frame_ratio + thermal_opening.g_value = thermal_opening_archetype.g_value + thermal_opening.conductivity = thermal_opening_archetype.conductivity + thermal_opening.thickness = thermal_opening_archetype.thickness + thermal_opening.back_side_solar_transmittance_at_normal_incidence = \ + thermal_opening_archetype.back_side_solar_transmittance_at_normal_incidence + thermal_opening.front_side_solar_transmittance_at_normal_incidence = \ + thermal_opening_archetype.front_side_solar_transmittance_at_normal_incidence @staticmethod def _create_storeys(building, archetype): diff --git a/imports/geometry/helpers/geometry_helper.py b/imports/geometry/helpers/geometry_helper.py index 9c432e15..104792d5 100644 --- a/imports/geometry/helpers/geometry_helper.py +++ b/imports/geometry/helpers/geometry_helper.py @@ -12,229 +12,207 @@ class GeometryHelper: Geometry helper """ # function - pluto_to_function = { - 'A0': 'single family house', - 'A1': 'single family house', - 'A2': 'single family house', - 'A3': 'single family house', - 'A4': 'single family house', - 'A5': 'single family house', - 'A6': 'single family house', - 'A7': 'single family house', - 'A8': 'single family house', - 'A9': 'single family house', - 'B1': 'multifamily house', - 'B2': 'multifamily house', - 'B3': 'multifamily house', - 'B9': 'multifamily house', - 'C0': 'residential', - 'C1': 'residential', - 'C2': 'residential', - 'C3': 'residential', - 'C4': 'residential', - 'C5': 'residential', - 'C6': 'residential', - 'C7': 'residential', - 'C8': 'residential', - 'C9': 'residential', - 'D0': 'residential', - 'D1': 'residential', - 'D2': 'residential', - 'D3': 'residential', - 'D4': 'residential', - 'D5': 'residential', - 'D6': 'residential', - 'D7': 'residential', - 'D8': 'residential', - 'D9': 'residential', - 'E1': 'warehouse', - 'E3': 'warehouse', - 'E4': 'warehouse', - 'E5': 'warehouse', - 'E7': 'warehouse', - 'E9': 'warehouse', - 'F1': 'warehouse', - 'F2': 'warehouse', - 'F4': 'warehouse', - 'F5': 'warehouse', - 'F8': 'warehouse', - 'F9': 'warehouse', - 'G0': 'office', - 'G1': 'office', - 'G2': 'office', - 'G3': 'office', - 'G4': 'office', - 'G5': 'office', - 'G6': 'office', - 'G7': 'office', - 'G8': 'office', - 'G9': 'office', - 'H1': 'hotel', - 'H2': 'hotel', - 'H3': 'hotel', - 'H4': 'hotel', - 'H5': 'hotel', - 'H6': 'hotel', - 'H7': 'hotel', - 'H8': 'hotel', - 'H9': 'hotel', - 'HB': 'hotel', - 'HH': 'hotel', - 'HR': 'hotel', - 'HS': 'hotel', - 'I1': 'hospital', - 'I2': 'outpatient', - 'I3': 'outpatient', - 'I4': 'residential', - 'I5': 'outpatient', - 'I6': 'outpatient', - 'I7': 'outpatient', - 'I9': 'outpatient', - 'J1': 'large office', - 'J2': 'large office', - 'J3': 'large office', - 'J4': 'large office', - 'J5': 'large office', - 'J6': 'large office', - 'J7': 'large office', - 'J8': 'large office', - 'J9': 'large office', - 'K1': 'strip mall', - 'K2': 'strip mall', - 'K3': 'strip mall', - 'K4': 'residential', - 'K5': 'restaurant', - 'K6': 'commercial', - 'K7': 'commercial', - 'K8': 'commercial', - 'K9': 'commercial', - 'L1': 'residential', - 'L2': 'residential', - 'L3': 'residential', - 'L8': 'residential', - 'L9': 'residential', - 'M1': 'large office', - 'M2': 'large office', - 'M3': 'large office', - 'M4': 'large office', - 'M9': 'large office', - 'N1': 'residential', - 'N2': 'residential', - 'N3': 'residential', - 'N4': 'residential', - 'N9': 'residential', - 'O1': 'office', - 'O2': 'office', - 'O3': 'office', - 'O4': 'office', - 'O5': 'office', - 'O6': 'office', - 'O7': 'office', - 'O8': 'office', - 'O9': 'office', - 'P1': 'large office', - 'P2': 'hotel', - 'P3': 'office', - 'P4': 'office', - 'P5': 'office', - 'P6': 'office', - 'P7': 'large office', - 'P8': 'large office', - 'P9': 'office', - 'Q0': 'office', - 'Q1': 'office', - 'Q2': 'office', - 'Q3': 'office', - 'Q4': 'office', - 'Q5': 'office', - 'Q6': 'office', - 'Q7': 'office', - 'Q8': 'office', - 'Q9': 'office', - 'R0': 'residential', - 'R1': 'residential', - 'R2': 'residential', - 'R3': 'residential', - 'R4': 'residential', - 'R5': 'residential', - 'R6': 'residential', - 'R7': 'residential', - 'R8': 'residential', - 'R9': 'residential', - 'RA': 'residential', - 'RB': 'residential', - 'RC': 'residential', - 'RD': 'residential', - 'RG': 'residential', - 'RH': 'residential', - 'RI': 'residential', - 'RK': 'residential', - 'RM': 'residential', - 'RR': 'residential', - 'RS': 'residential', - 'RW': 'residential', - 'RX': 'residential', - 'RZ': 'residential', - 'S0': 'residential', - 'S1': 'residential', - 'S2': 'residential', - 'S3': 'residential', - 'S4': 'residential', - 'S5': 'residential', - 'S9': 'residential', - 'T1': 'na', - 'T2': 'na', - 'T9': 'na', - 'U0': 'warehouse', - 'U1': 'warehouse', - 'U2': 'warehouse', - 'U3': 'warehouse', - 'U4': 'warehouse', - 'U5': 'warehouse', - 'U6': 'warehouse', - 'U7': 'warehouse', - 'U8': 'warehouse', - 'U9': 'warehouse', - 'V0': 'na', - 'V1': 'na', - 'V2': 'na', - 'V3': 'na', - 'V4': 'na', - 'V5': 'na', - 'V6': 'na', - 'V7': 'na', - 'V8': 'na', - 'V9': 'na', - 'W1': 'primary school', - 'W2': 'primary school', - 'W3': 'secondary school', - 'W4': 'secondary school', - 'W5': 'secondary school', - 'W6': 'secondary school', - 'W7': 'secondary school', - 'W8': 'primary school', - 'W9': 'secondary school', - 'Y1': 'large office', - 'Y2': 'large office', - 'Y3': 'large office', - 'Y4': 'large office', - 'Y5': 'large office', - 'Y6': 'large office', - 'Y7': 'large office', - 'Y8': 'large office', - 'Y9': 'large office', - 'Z0': 'na', - 'Z1': 'large office', - 'Z2': 'na', - 'Z3': 'na', - 'Z4': 'na', - 'Z5': 'na', - 'Z6': 'na', - 'Z7': 'na', - 'Z8': 'na', - 'Z9': 'na' + _pluto_to_function = { + 'A0': cte.SFH, + 'A1': cte.SFH, + 'A2': cte.SFH, + 'A3': cte.SFH, + 'A4': cte.SFH, + 'A5': cte.SFH, + 'A6': cte.SFH, + 'A7': cte.SFH, + 'A8': cte.SFH, + 'A9': cte.SFH, + 'B1': cte.MFH, + 'B2': cte.MFH, + 'B3': cte.MFH, + 'B9': cte.MFH, + 'C0': cte.RESIDENTIAL, + 'C1': cte.RESIDENTIAL, + 'C2': cte.RESIDENTIAL, + 'C3': cte.RESIDENTIAL, + 'C4': cte.RESIDENTIAL, + 'C5': cte.RESIDENTIAL, + 'C6': cte.RESIDENTIAL, + 'C7': cte.RESIDENTIAL, + 'C8': cte.RESIDENTIAL, + 'C9': cte.RESIDENTIAL, + 'D0': cte.RESIDENTIAL, + 'D1': cte.RESIDENTIAL, + 'D2': cte.RESIDENTIAL, + 'D3': cte.RESIDENTIAL, + 'D4': cte.RESIDENTIAL, + 'D5': cte.RESIDENTIAL, + 'D6': cte.RESIDENTIAL, + 'D7': cte.RESIDENTIAL, + 'D8': cte.RESIDENTIAL, + 'D9': cte.RESIDENTIAL, + 'E1': cte.WAREHOUSE, + 'E3': cte.WAREHOUSE, + 'E4': cte.WAREHOUSE, + 'E5': cte.WAREHOUSE, + 'E7': cte.WAREHOUSE, + 'E9': cte.WAREHOUSE, + 'F1': cte.WAREHOUSE, + 'F2': cte.WAREHOUSE, + 'F4': cte.WAREHOUSE, + 'F5': cte.WAREHOUSE, + 'F8': cte.WAREHOUSE, + 'F9': cte.WAREHOUSE, + 'G0': cte.OFFICE, + 'G1': cte.OFFICE, + 'G2': cte.OFFICE, + 'G3': cte.OFFICE, + 'G4': cte.OFFICE, + 'G5': cte.OFFICE, + 'G6': cte.OFFICE, + 'G7': cte.OFFICE, + 'G8': cte.OFFICE, + 'G9': cte.OFFICE, + 'H1': cte.HOTEL, + 'H2': cte.HOTEL, + 'H3': cte.HOTEL, + 'H4': cte.HOTEL, + 'H5': cte.HOTEL, + 'H6': cte.HOTEL, + 'H7': cte.HOTEL, + 'H8': cte.HOTEL, + 'H9': cte.HOTEL, + 'HB': cte.HOTEL, + 'HH': cte.HOTEL, + 'HR': cte.HOTEL, + 'HS': cte.HOTEL, + 'I1': cte.HOSPITAL, + 'I2': cte.OUTPATIENT, + 'I3': cte.OUTPATIENT, + 'I4': cte.RESIDENTIAL, + 'I5': cte.OUTPATIENT, + 'I6': cte.OUTPATIENT, + 'I7': cte.OUTPATIENT, + 'I9': cte.OUTPATIENT, + 'J1': cte.LARGE_OFFICE, + 'J2': cte.LARGE_OFFICE, + 'J3': cte.LARGE_OFFICE, + 'J4': cte.LARGE_OFFICE, + 'J5': cte.LARGE_OFFICE, + 'J6': cte.LARGE_OFFICE, + 'J7': cte.LARGE_OFFICE, + 'J8': cte.LARGE_OFFICE, + 'J9': cte.LARGE_OFFICE, + 'K1': cte.STRIP_MALL, + 'K2': cte.STRIP_MALL, + 'K3': cte.STRIP_MALL, + 'K4': cte.RESIDENTIAL, + 'K5': cte.RESTAURANT, + 'K6': cte.COMMERCIAL, + 'K7': cte.COMMERCIAL, + 'K8': cte.COMMERCIAL, + 'K9': cte.COMMERCIAL, + 'L1': cte.RESIDENTIAL, + 'L2': cte.RESIDENTIAL, + 'L3': cte.RESIDENTIAL, + 'L8': cte.RESIDENTIAL, + 'L9': cte.RESIDENTIAL, + 'M1': cte.LARGE_OFFICE, + 'M2': cte.LARGE_OFFICE, + 'M3': cte.LARGE_OFFICE, + 'M4': cte.LARGE_OFFICE, + 'M9': cte.LARGE_OFFICE, + 'N1': cte.RESIDENTIAL, + 'N2': cte.RESIDENTIAL, + 'N3': cte.RESIDENTIAL, + 'N4': cte.RESIDENTIAL, + 'N9': cte.RESIDENTIAL, + 'O1': cte.OFFICE, + 'O2': cte.OFFICE, + 'O3': cte.OFFICE, + 'O4': cte.OFFICE, + 'O5': cte.OFFICE, + 'O6': cte.OFFICE, + 'O7': cte.OFFICE, + 'O8': cte.OFFICE, + 'O9': cte.OFFICE, + 'P1': cte.LARGE_OFFICE, + 'P2': cte.HOTEL, + 'P3': cte.OFFICE, + 'P4': cte.OFFICE, + 'P5': cte.OFFICE, + 'P6': cte.OFFICE, + 'P7': cte.LARGE_OFFICE, + 'P8': cte.LARGE_OFFICE, + 'P9': cte.OFFICE, + 'Q0': cte.OFFICE, + 'Q1': cte.OFFICE, + 'Q2': cte.OFFICE, + 'Q3': cte.OFFICE, + 'Q4': cte.OFFICE, + 'Q5': cte.OFFICE, + 'Q6': cte.OFFICE, + 'Q7': cte.OFFICE, + 'Q8': cte.OFFICE, + 'Q9': cte.OFFICE, + 'R0': cte.RESIDENTIAL, + 'R1': cte.RESIDENTIAL, + 'R2': cte.RESIDENTIAL, + 'R3': cte.RESIDENTIAL, + 'R4': cte.RESIDENTIAL, + 'R5': cte.RESIDENTIAL, + 'R6': cte.RESIDENTIAL, + 'R7': cte.RESIDENTIAL, + 'R8': cte.RESIDENTIAL, + 'R9': cte.RESIDENTIAL, + 'RA': cte.RESIDENTIAL, + 'RB': cte.RESIDENTIAL, + 'RC': cte.RESIDENTIAL, + 'RD': cte.RESIDENTIAL, + 'RG': cte.RESIDENTIAL, + 'RH': cte.RESIDENTIAL, + 'RI': cte.RESIDENTIAL, + 'RK': cte.RESIDENTIAL, + 'RM': cte.RESIDENTIAL, + 'RR': cte.RESIDENTIAL, + 'RS': cte.RESIDENTIAL, + 'RW': cte.RESIDENTIAL, + 'RX': cte.RESIDENTIAL, + 'RZ': cte.RESIDENTIAL, + 'S0': cte.RESIDENTIAL, + 'S1': cte.RESIDENTIAL, + 'S2': cte.RESIDENTIAL, + 'S3': cte.RESIDENTIAL, + 'S4': cte.RESIDENTIAL, + 'S5': cte.RESIDENTIAL, + 'S9': cte.RESIDENTIAL, + 'U0': cte.WAREHOUSE, + 'U1': cte.WAREHOUSE, + 'U2': cte.WAREHOUSE, + 'U3': cte.WAREHOUSE, + 'U4': cte.WAREHOUSE, + 'U5': cte.WAREHOUSE, + 'U6': cte.WAREHOUSE, + 'U7': cte.WAREHOUSE, + 'U8': cte.WAREHOUSE, + 'U9': cte.WAREHOUSE, + 'W1': cte.PRIMARY_SCHOOL, + 'W2': cte.PRIMARY_SCHOOL, + 'W3': cte.SECONDARY_SCHOOL, + 'W4': cte.SECONDARY_SCHOOL, + 'W5': cte.SECONDARY_SCHOOL, + 'W6': cte.SECONDARY_SCHOOL, + 'W7': cte.SECONDARY_SCHOOL, + 'W8': cte.PRIMARY_SCHOOL, + 'W9': cte.SECONDARY_SCHOOL, + 'Y1': cte.LARGE_OFFICE, + 'Y2': cte.LARGE_OFFICE, + 'Y3': cte.LARGE_OFFICE, + 'Y4': cte.LARGE_OFFICE, + 'Y5': cte.LARGE_OFFICE, + 'Y6': cte.LARGE_OFFICE, + 'Y7': cte.LARGE_OFFICE, + 'Y8': cte.LARGE_OFFICE, + 'Y9': cte.LARGE_OFFICE, + 'Z1': cte.LARGE_OFFICE } - hft_to_function = { + _hft_to_function = { 'residential': cte.RESIDENTIAL, 'single family house': cte.SFH, 'multifamily house': cte.MFH, @@ -251,25 +229,18 @@ class GeometryHelper: } # usage - function_to_usage = { - 'full service restaurant': 'restaurant', - 'highrise apartment': cte.RESIDENTIAL, - 'hospital': 'health care', - 'large hotel': 'hotel', - 'large office': 'office and administration', - 'medium office': 'office and administration', - 'midrise apartment': cte.RESIDENTIAL, - 'outpatient healthcare': 'health care', - 'primary school': 'education', - 'quick service restaurant': 'restaurant', - 'secondary school': 'education', - 'small hotel': 'hotel', - 'small office': 'office and administration', - 'stand alone retail': 'retail', - 'strip mall': 'hall', - 'supermarket': 'retail', - 'warehouse': 'industry', - 'residential': cte.RESIDENTIAL + _function_to_usage = { + cte.RESTAURANT: cte.RESTAURANT, + cte.RESIDENTIAL: cte.RESIDENTIAL, + cte.HOSPITAL: cte.HEALTH_CARE, + cte.HOTEL: cte.HOTEL, + cte.LARGE_OFFICE: cte.OFFICE_ADMINISTRATION, + cte.OFFICE: cte.OFFICE_ADMINISTRATION, + cte.PRIMARY_SCHOOL: cte.EDUCATION, + cte.SECONDARY_SCHOOL: cte.EDUCATION, + cte.RETAIL: cte.RETAIL, + cte.STRIP_MALL: cte.HALL, + cte.WAREHOUSE: cte.INDUSTRY } @staticmethod @@ -279,7 +250,7 @@ class GeometryHelper: :param building_hft_function: str :return: str """ - return GeometryHelper.hft_to_function[building_hft_function] + return GeometryHelper._hft_to_function[building_hft_function] @staticmethod def function_from_pluto(building_pluto_function): @@ -288,7 +259,7 @@ class GeometryHelper: :param building_pluto_function: str :return: str """ - return GeometryHelper.pluto_to_function[building_pluto_function] + return GeometryHelper._pluto_to_function[building_pluto_function] @staticmethod def usage_from_function(building_function): @@ -297,7 +268,7 @@ class GeometryHelper: :param building_function: str :return: str """ - return GeometryHelper.function_to_usage[building_function] + return GeometryHelper._function_to_usage[building_function] @staticmethod def to_points_matrix(points): diff --git a/imports/schedules/helpers/schedules_helper.py b/imports/schedules/helpers/schedules_helper.py index fa47b343..9aca6fab 100644 --- a/imports/schedules/helpers/schedules_helper.py +++ b/imports/schedules/helpers/schedules_helper.py @@ -12,7 +12,7 @@ class SchedulesHelper: """ Schedules helper """ - usage_to_comnet = { + _usage_to_comnet = { cte.RESIDENTIAL: 'C-12 Residential', cte.INDUSTRY: 'C-10 Warehouse', cte.OFFICE_ADMINISTRATION: 'C-5 Office', @@ -23,16 +23,15 @@ class SchedulesHelper: cte.RESTAURANT: 'C-7 Restaurant', cte.EDUCATION: 'C-9 School' } - comnet_default_value = 'C-12 Residential' - comnet_to_data_type = { + _comnet_to_data_type = { 'Fraction': cte.FRACTION, 'OnOff': cte.ON_OFF, 'Temperature': cte.TEMPERATURE } # usage - function_to_usage = { + _function_to_usage = { 'full service restaurant': cte.RESTAURANT, 'high-rise apartment': cte.RESIDENTIAL, 'hospital': cte.HEALTH_CARE, @@ -61,10 +60,9 @@ class SchedulesHelper: :return: str """ try: - return SchedulesHelper.usage_to_comnet[usage] + return SchedulesHelper._usage_to_comnet[usage] except KeyError: - sys.stderr.write('Error: keyword not found. Returned default Comnet schedules "residential"\n') - return SchedulesHelper.comnet_default_value + sys.stderr.write('Error: keyword not found.\n') @staticmethod def data_type_from_comnet(comnet_data_type): @@ -74,7 +72,7 @@ class SchedulesHelper: :return: str """ try: - return SchedulesHelper.comnet_to_data_type[comnet_data_type] + return SchedulesHelper._comnet_to_data_type[comnet_data_type] except KeyError: raise ValueError(f"Error: comnet data type keyword not found.") @@ -85,4 +83,4 @@ class SchedulesHelper: :param building_function: str :return: str """ - return SchedulesHelper.function_to_usage[building_function] + return SchedulesHelper._function_to_usage[building_function] diff --git a/imports/usage/ca_usage_parameters.py b/imports/usage/ca_usage_parameters.py index cea058f6..c9034b85 100644 --- a/imports/usage/ca_usage_parameters.py +++ b/imports/usage/ca_usage_parameters.py @@ -5,10 +5,12 @@ Copyright © 2020 Project Author Pilar Monsalvete Alvarez de Uribarri pilar.mons """ import sys -from imports.geometry.helpers.geometry_helper import GeometryHelper as gh from imports.usage.hft_usage_interface import HftUsageInterface from city_model_structure.building_demand.usage_zone import UsageZone from city_model_structure.building_demand.internal_gains import InternalGains +from city_model_structure.building_demand.occupancy import Occupancy +from city_model_structure.building_demand.appliances import Appliances +from city_model_structure.building_demand.thermal_control import ThermalControl class CaUsageParameters(HftUsageInterface): @@ -18,9 +20,6 @@ class CaUsageParameters(HftUsageInterface): def __init__(self, city, base_path): super().__init__(base_path, 'ca_archetypes_reduced.xml') self._city = city - # todo: this is a wrong location for self._min_air_change -> re-think where to place this info - # and where it comes from - self._min_air_change = 0 def enrich_buildings(self): """ @@ -29,19 +28,20 @@ class CaUsageParameters(HftUsageInterface): """ city = self._city for building in city.buildings: - archetype = self._search_archetype(building.function) - if archetype is None: + try: + print(building.function) + archetype = self._search_archetype(building.function) + except KeyError: sys.stderr.write(f'Building {building.name} has unknown archetype for building function:' - f' {building.function}, that assigns building usage as ' - f'{gh.usage_from_function(building.function)}\n') - continue + f' {building.function}\n') + return for internal_zone in building.internal_zones: usage_zone = UsageZone() - usage_zone.usage = building.function - self._assign_values(usage_zone, archetype) - usage_zone.percentage = 1 - internal_zone.usage_zones = [usage_zone] + usage_zone.usage = building.function + usage_zone.percentage = 1 + self._assign_values_usage_zone(usage_zone, archetype) + internal_zone.usage_zones = [usage_zone] def _search_archetype(self, building_usage): for building_archetype in self._usage_archetypes: @@ -50,26 +50,30 @@ class CaUsageParameters(HftUsageInterface): return None @staticmethod - def _assign_values(usage_zone, archetype): + def _assign_values_usage_zone(usage_zone, archetype): # Due to the fact that python is not a typed language, the wrong object type is assigned to # usage_zone.internal_gains when writing usage_zone.internal_gains = archetype.internal_gains. # Therefore, this walk around has been done. - internal_gains = [] - for archetype_internal_gain in archetype.internal_gains: - internal_gain = InternalGains() - internal_gain.average_internal_gain = archetype_internal_gain.average_internal_gain - internal_gain.convective_fraction = archetype_internal_gain.convective_fraction - internal_gain.radiative_fraction = archetype_internal_gain.radiative_fraction - internal_gain.latent_fraction = archetype_internal_gain.latent_fraction - internal_gains.append(internal_gain) - usage_zone.internal_gains = internal_gains - usage_zone.heating_setpoint = archetype.heating_setpoint - usage_zone.heating_setback = archetype.heating_setback - usage_zone.cooling_setpoint = archetype.cooling_setpoint - usage_zone.occupancy_density = archetype.occupancy_density + usage_zone.mechanical_air_change = archetype.mechanical_air_change + _occupancy = Occupancy() + _occupancy.occupancy_density = archetype.occupancy.occupancy_density + usage_zone.occupancy = _occupancy usage_zone.hours_day = archetype.hours_day usage_zone.days_year = archetype.days_year - usage_zone.dhw_average_volume_pers_day = archetype.dhw_average_volume_pers_day - usage_zone.dhw_preparation_temperature = archetype.dhw_preparation_temperature - usage_zone.electrical_app_average_consumption_sqm_year = archetype.electrical_app_average_consumption_sqm_year - usage_zone.mechanical_air_change = archetype.mechanical_air_change + _appliances = Appliances() + _appliances.appliances_density = archetype.appliances.appliances_density + usage_zone.appliances = _appliances + _control = ThermalControl() + _control.mean_heating_set_point = archetype.thermal_control.mean_heating_set_point + _control.heating_set_back = archetype.thermal_control.heating_set_back + _control.mean_cooling_set_point = archetype.thermal_control.mean_cooling_set_point + usage_zone.thermal_control = _control + _internal_gains = [] + for archetype_internal_gain in archetype.not_detailed_source_mean_annual_internal_gains: + _internal_gain = InternalGains() + _internal_gain.average_internal_gain = archetype_internal_gain.average_internal_gain + _internal_gain.convective_fraction = archetype_internal_gain.convective_fraction + _internal_gain.radiative_fraction = archetype_internal_gain.radiative_fraction + _internal_gain.latent_fraction = archetype_internal_gain.latent_fraction + _internal_gains.append(_internal_gain) + usage_zone.not_detailed_source_mean_annual_internal_gains = _internal_gains diff --git a/imports/usage/comnet_usage_parameters.py b/imports/usage/comnet_usage_parameters.py index ed5d187e..851473da 100644 --- a/imports/usage/comnet_usage_parameters.py +++ b/imports/usage/comnet_usage_parameters.py @@ -3,6 +3,7 @@ ComnetUsageParameters model the usage properties SPDX - License - Identifier: LGPL - 3.0 - or -later Copyright © 2021 Project Author Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca """ + import sys from typing import Dict import pandas as pd @@ -11,11 +12,13 @@ import helpers.constants as cte from helpers.configuration_helper import ConfigurationHelper as ch from imports.geometry.helpers.geometry_helper import GeometryHelper from imports.usage.helpers.usage_helper import UsageHelper +from imports.schedules.helpers.schedules_helper import SchedulesHelper from city_model_structure.building_demand.usage_zone import UsageZone from city_model_structure.building_demand.lighting import Lighting from city_model_structure.building_demand.occupancy import Occupancy from city_model_structure.building_demand.appliances import Appliances -from city_model_structure.building_demand.internal_gains import InternalGains +from city_model_structure.building_demand.thermal_control import ThermalControl +from city_model_structure.attributes.schedule import Schedule class ComnetUsageParameters: @@ -25,24 +28,19 @@ class ComnetUsageParameters: def __init__(self, city, base_path): self._city = city self._base_path = str(base_path / 'comnet_archetypes.xlsx') - self._usage_archetypes = [] - data = self._read_file() - for item in data['lighting']: - for usage in UsageHelper.usage_to_comnet: - comnet_usage = UsageHelper.usage_to_comnet[usage] - if comnet_usage == item: - usage_archetype = self._parse_zone_usage_type(comnet_usage, data) - self._usage_archetypes.append(usage_archetype) + self._data = self._read_file() + self._comnet_schedules_path = str(base_path / 'comnet_schedules_archetypes.xlsx') + self._xls = pd.ExcelFile(self._comnet_schedules_path) def _read_file(self) -> Dict: """ - reads xlsx file containing usage information into a dictionary + reads xlsx files containing usage information into a dictionary :return : Dict """ number_usage_types = 33 xl_file = pd.ExcelFile(self._base_path) file_data = pd.read_excel(xl_file, sheet_name="Modeling Data", skiprows=[0, 1, 2], - nrows=number_usage_types, usecols="A:Z") + nrows=number_usage_types, usecols="A:AB") lighting_data = {} plug_loads_data = {} @@ -50,6 +48,7 @@ class ComnetUsageParameters: ventilation_rate = {} water_heating = {} process_data = {} + schedules_key = {} for j in range(0, number_usage_types): usage_parameters = file_data.iloc[j] @@ -60,53 +59,130 @@ class ComnetUsageParameters: ventilation_rate[usage_type] = usage_parameters[20:21].values.tolist() water_heating[usage_type] = usage_parameters[23:24].values.tolist() process_data[usage_type] = usage_parameters[24:26].values.tolist() + schedules_key[usage_type] = usage_parameters[27:28].values.tolist() return {'lighting': lighting_data, 'plug loads': plug_loads_data, 'occupancy': occupancy_data, 'ventilation rate': ventilation_rate, 'water heating': water_heating, - 'process': process_data} + 'process': process_data, + 'schedules_key': schedules_key} @staticmethod - def _parse_zone_usage_type(usage, data): + def _parse_usage_type(comnet_usage, data, schedules_data): _usage_zone = UsageZone() - _usage_zone.usage = usage # lighting _lighting = Lighting() _lighting.latent_fraction = ch().comnet_lighting_latent _lighting.convective_fraction = ch().comnet_lighting_convective _lighting.radiative_fraction = ch().comnet_lighting_radiant - _lighting.average_internal_gain = data['lighting'][usage][4] + _lighting.lighting_density = data['lighting'][comnet_usage][4] # plug loads _appliances = None - if data['plug loads'][usage][0] != 'n.a.': + if data['plug loads'][comnet_usage][0] != 'n.a.': _appliances = Appliances() _appliances.latent_fraction = ch().comnet_plugs_latent _appliances.convective_fraction = ch().comnet_plugs_convective _appliances.radiative_fraction = ch().comnet_plugs_radiant - _appliances.average_internal_gain = data['plug loads'][usage][0] + _appliances.appliances_density = data['plug loads'][comnet_usage][0] # occupancy _occupancy = Occupancy() - _occupancy.occupancy_density = data['occupancy'][usage][0] - _occupancy.sensible_convective_internal_gain = data['occupancy'][usage][1] \ + _occupancy.occupancy_density = data['occupancy'][comnet_usage][0] + _occupancy.sensible_convective_internal_gain = data['occupancy'][comnet_usage][1] \ * ch().comnet_occupancy_sensible_convective - _occupancy.sensible_radiant_internal_gain = data['occupancy'][usage][1] * ch().comnet_occupancy_sensible_radiant - _occupancy.latent_internal_gain = data['occupancy'][usage][2] + _occupancy.sensible_radiative_internal_gain = data['occupancy'][comnet_usage][1] \ + * ch().comnet_occupancy_sensible_radiant + _occupancy.latent_internal_gain = data['occupancy'][comnet_usage][2] if _occupancy.occupancy_density <= 0: _usage_zone.mechanical_air_change = 0 else: - _usage_zone.mechanical_air_change = data['ventilation rate'][usage][0] / _occupancy.occupancy_density + _usage_zone.mechanical_air_change = data['ventilation rate'][comnet_usage][0] / _occupancy.occupancy_density + + schedules_usage = UsageHelper.schedules_key(data['schedules_key'][comnet_usage][0]) + + _extracted_data = pd.read_excel(schedules_data, sheet_name=schedules_usage, + skiprows=[0, 1, 2, 3], nrows=39, usecols="A:AA") + schedules = [] + number_of_schedule_types = 13 + schedules_per_schedule_type = 3 + day_types = dict({'week_day': 0, 'saturday': 1, 'sunday': 2}) + for schedule_types in range(0, number_of_schedule_types): + name = '' + data_type = '' + for schedule_day in range(0, schedules_per_schedule_type): + _schedule = Schedule() + _schedule.time_step = cte.HOUR + _schedule.time_range = cte.DAY + row_cells = _extracted_data.iloc[schedules_per_schedule_type * schedule_types + schedule_day] + if schedule_day == day_types['week_day']: + name = row_cells[0] + data_type = row_cells[1] + _schedule.day_types = [cte.MONDAY, cte.TUESDAY, cte.WEDNESDAY, cte.THURSDAY, cte.FRIDAY] + elif schedule_day == day_types['saturday']: + _schedule.day_types = [cte.SATURDAY] + else: + _schedule.day_types = [cte.SUNDAY] + _schedule.type = name + _schedule.data_type = SchedulesHelper.data_type_from_comnet(data_type) + if _schedule.data_type == cte.TEMPERATURE: + values = [] + for cell in row_cells[schedules_per_schedule_type:].to_numpy(): + values.append((float(cell) - 32.) * 5 / 9) + _schedule.values = values + else: + _schedule.values = row_cells[schedules_per_schedule_type:].to_numpy() + schedules.append(_schedule) + + schedules_types = dict({'Occupancy': 0, 'Lights': 3, 'Receptacle': 6, 'Infiltration': 9, 'HVAC Avail': 12, + 'ClgSetPt': 15, 'HtgSetPt': 18}) + + _schedules = [] + for pointer in range(0, 3): + _schedules.append(schedules[schedules_types['Occupancy']+pointer]) + _occupancy.occupancy_schedules = _schedules + _schedules = [] + for pointer in range(0, 3): + _schedules.append(schedules[schedules_types['Lights']+pointer]) + _lighting.schedules = _schedules + _schedules = [] + for pointer in range(0, 3): + _schedules.append(schedules[schedules_types['Receptacle']+pointer]) + _appliances.schedules = _schedules _usage_zone.occupancy = _occupancy _usage_zone.lighting = _lighting _usage_zone.appliances = _appliances + + _control = ThermalControl() + _schedules = [] + for pointer in range(0, 3): + _schedules.append(schedules[schedules_types['HtgSetPt']+pointer]) + _control.heating_set_point_schedules = _schedules + _schedules = [] + for pointer in range(0, 3): + _schedules.append(schedules[schedules_types['ClgSetPt']+pointer]) + _control.cooling_set_point_schedules = _schedules + _schedules = [] + for pointer in range(0, 3): + _schedules.append(schedules[schedules_types['HVAC Avail']+pointer]) + _control.hvac_availability_schedules = _schedules + _usage_zone.thermal_control = _control + return _usage_zone + def _search_archetypes(self, usage): + for item in self._data['lighting']: + comnet_usage = UsageHelper.comnet_from_usage(usage) + if comnet_usage == item: + usage_archetype = self._parse_usage_type(comnet_usage, self._data, self._xls) + return usage_archetype + return None, None + def enrich_buildings(self): """ Returns the city with the usage parameters assigned to the buildings @@ -115,41 +191,80 @@ class ComnetUsageParameters: city = self._city for building in city.buildings: usage = GeometryHelper.usage_from_function(building.function) - archetype = self._search_archetype(UsageHelper.comnet_from_usage(usage)) - if archetype is None: + try: + archetype_usage = self._search_archetypes(usage) + except KeyError: sys.stderr.write(f'Building {building.name} has unknown archetype for building function:' f' {building.function}, that assigns building usage as ' f'{GeometryHelper.usage_from_function(building.function)}\n') - continue + return for internal_zone in building.internal_zones: + if internal_zone.area is None: + raise Exception('Internal zone area not defined, ACH cannot be calculated') + if internal_zone.volume is None: + raise Exception('Internal zone volume not defined, ACH cannot be calculated') + if internal_zone.area <= 0: + raise Exception('Internal zone area is zero, ACH cannot be calculated') + if internal_zone.volume <= 0: + raise Exception('Internal zone volume is zero, ACH cannot be calculated') + volume_per_area = internal_zone.volume / internal_zone.area usage_zone = UsageZone() usage_zone.usage = usage - self._assign_values(usage_zone, archetype, volume_per_area) + self._assign_values_usage_zone(usage_zone, archetype_usage, volume_per_area) usage_zone.percentage = 1 + self._calculate_reduced_values_from_extended_library(usage_zone, archetype_usage) + internal_zone.usage_zones = [usage_zone] - def _search_archetype(self, building_usage): - for building_archetype in self._usage_archetypes: - if building_archetype.usage == building_usage: - return building_archetype - return None + @staticmethod + def _assign_values_usage_zone(usage_zone, archetype, volume_per_area): + # Due to the fact that python is not a typed language, the wrong object type is assigned to + # usage_zone.occupancy when writing usage_zone.occupancy = archetype.occupancy. + # Same happens for lighting and appliances. Therefore, this walk around has been done. + usage_zone.mechanical_air_change = archetype.mechanical_air_change * cte.METERS_TO_FEET ** 2 \ + * cte.HOUR_TO_MINUTES / cte.METERS_TO_FEET ** 3 / volume_per_area + _occupancy = Occupancy() + _occupancy.occupancy_density = archetype.occupancy.occupancy_density * cte.METERS_TO_FEET**2 + _occupancy.sensible_radiative_internal_gain = archetype.occupancy.sensible_radiative_internal_gain + _occupancy.latent_internal_gain = archetype.occupancy.latent_internal_gain + _occupancy.sensible_convective_internal_gain = archetype.occupancy.sensible_convective_internal_gain + _occupancy.occupancy_schedules = archetype.occupancy.occupancy_schedules + usage_zone.occupancy = _occupancy + _lighting = Lighting() + _lighting.lighting_density = archetype.lighting.lighting_density / cte.METERS_TO_FEET**2 + _lighting.convective_fraction = archetype.lighting.convective_fraction + _lighting.radiative_fraction = archetype.lighting.radiative_fraction + _lighting.latent_fraction = archetype.lighting.latent_fraction + _lighting.schedules = archetype.lighting.schedules + usage_zone.lighting = _lighting + _appliances = Appliances() + _appliances.appliances_density = archetype.appliances.appliances_density / cte.METERS_TO_FEET**2 + _appliances.convective_fraction = archetype.appliances.convective_fraction + _appliances.radiative_fraction = archetype.appliances.radiative_fraction + _appliances.latent_fraction = archetype.appliances.latent_fraction + _appliances.schedules = archetype.appliances.schedules + usage_zone.appliances = _appliances + _control = ThermalControl() + _control.cooling_set_point_schedules = archetype.thermal_control.cooling_set_point_schedules + _control.heating_set_point_schedules = archetype.thermal_control.heating_set_point_schedules + _control.hvac_availability_schedules = archetype.thermal_control.hvac_availability_schedules + usage_zone.thermal_control = _control @staticmethod - def _assign_values(usage_zone, archetype, volume_per_area): - # Due to the fact that python is not a typed language, the wrong object type is assigned to - # usage_zone.internal_gains when writing usage_zone.internal_gains = archetype.internal_gains. - # Therefore, this walk around has been done. - internal_gains = [] - for archetype_internal_gain in archetype.internal_gains: - internal_gain = InternalGains() - internal_gain.type = archetype_internal_gain.type - internal_gain.average_internal_gain = archetype_internal_gain.average_internal_gain - internal_gain.convective_fraction = archetype_internal_gain.convective_fraction - internal_gain.radiative_fraction = archetype_internal_gain.radiative_fraction - internal_gain.latent_fraction = archetype_internal_gain.latent_fraction - internal_gains.append(internal_gain) - usage_zone.internal_gains = internal_gains - usage_zone.occupancy_density = archetype.occupancy_density * cte.METERS_TO_FEET**2 - usage_zone.mechanical_air_change = archetype.mechanical_air_change * cte.METERS_TO_FEET**2 \ - * cte.HOUR_TO_MINUTES / cte.METERS_TO_FEET**3 / volume_per_area \ No newline at end of file + def _calculate_reduced_values_from_extended_library(usage_zone, archetype): + number_of_days_per_type = {'WD': 251, 'Sat': 52, 'Sun': 62} + total = 0 + for schedule in archetype.thermal_control.hvac_availability_schedules: + if schedule.day_types[0] == cte.SATURDAY: + for value in schedule.values: + total += value * number_of_days_per_type['Sat'] + elif schedule.day_types[0] == cte.SUNDAY: + for value in schedule.values: + total += value * number_of_days_per_type['Sun'] + else: + for value in schedule.values: + total += value * number_of_days_per_type['WD'] + + usage_zone.hours_day = total / 365 + usage_zone.days_year = 365 diff --git a/imports/usage/helpers/usage_helper.py b/imports/usage/helpers/usage_helper.py index 310b4888..f6257503 100644 --- a/imports/usage/helpers/usage_helper.py +++ b/imports/usage/helpers/usage_helper.py @@ -11,7 +11,7 @@ class UsageHelper: """ Usage helper class """ - usage_to_hft = { + _usage_to_hft = { cte.RESIDENTIAL: 'residential', cte.INDUSTRY: 'industry', cte.OFFICE_ADMINISTRATION: 'office and administration', @@ -20,9 +20,7 @@ class UsageHelper: cte.RETAIL: 'retail', cte.HALL: 'hall', cte.RESTAURANT: 'restaurant', - cte.EDUCATION: 'education' - } - hft_default_value = 'residential' + cte.EDUCATION: 'education'} @staticmethod def hft_from_usage(usage): @@ -32,12 +30,11 @@ class UsageHelper: :return: str """ try: - return UsageHelper.usage_to_hft[usage] + return UsageHelper._usage_to_hft[usage] except KeyError: - sys.stderr.write('Error: keyword not found. Returned default HfT usage "residential"\n') - return UsageHelper.hft_default_value + sys.stderr.write('Error: keyword not found.\n') - usage_to_comnet = { + _usage_to_comnet = { cte.RESIDENTIAL: 'BA Multifamily', cte.INDUSTRY: 'BA Manufacturing Facility', cte.OFFICE_ADMINISTRATION: 'BA Office', @@ -46,9 +43,23 @@ class UsageHelper: cte.RETAIL: 'BA Retail', cte.HALL: 'BA Town Hall', cte.RESTAURANT: 'BA Dining: Bar Lounge/Leisure', - cte.EDUCATION: 'BA School/University' - } - comnet_default_value = 'BA Multifamily' + cte.EDUCATION: 'BA School/University'} + + _comnet_schedules_key_to_comnet_schedules = { + 'C-1 Assembly': 'C-1 Assembly', + 'C-2 Public': 'C-2 Health', + 'C-3 Hotel Motel': 'C-3 Hotel', + 'C-4 Manufacturing': 'C-4 Manufacturing', + 'C-5 Office': 'C-5 Office', + 'C-6 Parking Garage': 'C-6 Parking', + 'C-7 Restaurant': 'C-7 Restaurant', + 'C-8 Retail': 'C-8 Retail', + 'C-9 Schools': 'C-9 School', + 'C-10 Warehouse': 'C-10 Warehouse', + 'C-11 Laboratory': 'C-11 Lab', + 'C-12 Residential': 'C-12 Residential', + 'C-13 Data Center': 'C-13 Data', + 'C-14 Gymnasium': 'C-14 Gymnasium'} @staticmethod def comnet_from_usage(usage): @@ -58,7 +69,19 @@ class UsageHelper: :return: str """ try: - return UsageHelper.usage_to_comnet[usage] + return UsageHelper._usage_to_comnet[usage] except KeyError: - sys.stderr.write('Error: keyword not found. Returned default Comnet usage "BA Multifamily"\n') - return UsageHelper.comnet_default_value + sys.stderr.write('Error: keyword not found.\n') + + @staticmethod + def schedules_key(usage): + """ + Get Comnet schedules key from the list found in the Comnet usage file + :param usage: str + :return: str + """ + try: + return UsageHelper._comnet_schedules_key_to_comnet_schedules[usage] + except KeyError: + sys.stderr.write('Error: Comnet keyword not found. An update of the Comnet files might have been ' + 'done changing the keywords.\n') diff --git a/imports/usage/hft_usage_interface.py b/imports/usage/hft_usage_interface.py index ed6dfefc..340d8a80 100644 --- a/imports/usage/hft_usage_interface.py +++ b/imports/usage/hft_usage_interface.py @@ -1,12 +1,17 @@ """ -Hft-based interface, it reads format defined within the CERC team based on that one used in SimStadt and developed by -the IAF team at hft-Stuttgart and enriches the city with usage parameters +Hft-based interface, it reads format defined within the CERC team (based on that one used in SimStadt and developed by +the IAF team at hft-Stuttgart) SPDX - License - Identifier: LGPL - 3.0 - or -later -Copyright © 2020 Project Author Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca +Copyright © 2022 Project Author Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca """ import xmltodict -from imports.usage.data_classes.usage_zone_archetype import UsageZoneArchetype as huza -from imports.usage.data_classes.hft_internal_gains_archetype import HftInternalGainsArchetype as higa +from city_model_structure.building_demand.usage_zone import UsageZone +from city_model_structure.building_demand.internal_gains import InternalGains +from city_model_structure.building_demand.occupancy import Occupancy +from city_model_structure.building_demand.appliances import Appliances +from city_model_structure.building_demand.thermal_control import ThermalControl +from city_model_structure.attributes.schedule import Schedule +import helpers.constants as cte class HftUsageInterface: @@ -28,116 +33,196 @@ class HftUsageInterface: usage = usage_zone_variant['id'] usage_archetype_variant = self._parse_zone_usage_variant(usage, usage_archetype, usage_zone_variant) self._usage_archetypes.append(usage_archetype_variant) + for usage in self._usage_archetypes: + print(usage.usage) @staticmethod def _parse_zone_usage_type(usage, zone_usage_type): - occupancy_density = zone_usage_type['occupancy']['occupancyDensity'] - hours_day = zone_usage_type['occupancy']['usageHoursPerDay'] - days_year = zone_usage_type['occupancy']['usageDaysPerYear'] - cooling_setpoint = zone_usage_type['endUses']['space_cooling']['coolingSetPointTemperature'] - heating_setpoint = zone_usage_type['endUses']['space_heating']['heatingSetPointTemperature'] - heating_setback = zone_usage_type['endUses']['space_heating']['heatingSetBackTemperature'] - mechanical_air_change = None - if 'ventilation' in zone_usage_type['endUses'] and zone_usage_type['endUses']['ventilation'] is not None: - mechanical_air_change = zone_usage_type['endUses']['ventilation']['mechanicalAirChangeRate'] - dhw_average_volume_pers_day = None - dhw_preparation_temperature = None - if 'domestic_hot_water' in zone_usage_type['endUses']: - # liters to cubic meters - dhw_average_volume_pers_day = float( - zone_usage_type['endUses']['domestic_hot_water']['averageVolumePerPersAndDay']) / 1000 - dhw_preparation_temperature = zone_usage_type['endUses']['domestic_hot_water']['preparationTemperature'] - electrical_app_average_consumption_sqm_year = None - if 'all_electrical_appliances' in zone_usage_type['endUses']: - if 'averageConsumptionPerSqmAndYear' in zone_usage_type['endUses']['all_electrical_appliances']: - # kWh to J - electrical_app_average_consumption_sqm_year = \ - float(zone_usage_type['endUses']['all_electrical_appliances']['averageConsumptionPerSqmAndYear']) / 3.6 + usage_zone_archetype = UsageZone() + usage_zone_archetype.usage = usage - # todo: for internal_gain in usage_zone_variant['schedules']['internGains']:???????????????? - # There are no more internal gains? How is it saved when more than one??? - internal_gains = [] - if 'internGains' in zone_usage_type['occupancy']: - latent_fraction = zone_usage_type['occupancy']['internGains']['latentFraction'] - convective_fraction = zone_usage_type['occupancy']['internGains']['convectiveFraction'] - average_internal_gain = zone_usage_type['occupancy']['internGains']['averageInternGainPerSqm'] - radiative_fraction = zone_usage_type['occupancy']['internGains']['radiantFraction'] - else: - latent_fraction = 0 - convective_fraction = 0 - average_internal_gain = 0 - radiative_fraction = 0 + if 'occupancy' in zone_usage_type: + _occupancy = Occupancy() + _occupancy.occupancy_density = zone_usage_type['occupancy']['occupancyDensity'] #todo: check units + usage_zone_archetype.hours_day = zone_usage_type['occupancy']['usageHoursPerDay'] + usage_zone_archetype.days_year = zone_usage_type['occupancy']['usageDaysPerYear'] + usage_zone_archetype.occupancy = _occupancy + + if 'internGains' in zone_usage_type['occupancy']: + _internal_gain = InternalGains() + _internal_gain.latent_fraction = zone_usage_type['occupancy']['internGains']['latentFraction'] + _internal_gain.convective_fraction = zone_usage_type['occupancy']['internGains']['convectiveFraction'] + _internal_gain.average_internal_gain = zone_usage_type['occupancy']['internGains']['averageInternGainPerSqm'] + _internal_gain.radiative_fraction = zone_usage_type['occupancy']['internGains']['radiantFraction'] + if 'load' in zone_usage_type['occupancy']['internGains']: + _schedule = Schedule() + _schedule.type = 'internal gains load' + _schedule.time_range = cte.DAY + _schedule.time_step = cte.HOUR + _schedule.data_type = cte.ANY_NUMBER + _schedule.day_types = [cte.MONDAY, cte.TUESDAY, cte.WEDNESDAY, cte.THURSDAY, cte.FRIDAY] + _values = zone_usage_type['occupancy']['internGains']['load']['weekDayProfile']['values'] + while ' ' in _values: + _values = _values.replace(' ', ' ') + _schedule.values = _values.split() + _internal_gain.schedules = [_schedule] + + usage_zone_archetype.not_detailed_source_mean_annual_internal_gains = [_internal_gain] + + if 'endUses' in zone_usage_type: + _thermal_control = ThermalControl() + if 'space_heating' in zone_usage_type['endUses']: + _thermal_control.mean_heating_set_point = \ + zone_usage_type['endUses']['space_heating']['heatingSetPointTemperature'] + _thermal_control.heating_set_back = zone_usage_type['endUses']['space_heating']['heatingSetBackTemperature'] + if 'schedule' in zone_usage_type['endUses']['space_heating']: + _schedule = Schedule() + _schedule.type = 'heating temperature' + _schedule.time_range = cte.DAY + _schedule.time_step = cte.HOUR + _schedule.data_type = cte.TEMPERATURE + _schedule.day_types = [cte.MONDAY, cte.TUESDAY, cte.WEDNESDAY, cte.THURSDAY, cte.FRIDAY] + _values = zone_usage_type['endUses']['space_heating']['schedule']['weekDayProfile']['values'] + while ' ' in _values: + _values = _values.replace(' ', ' ') + _schedule.values = _values.split() + _thermal_control.heating_set_point_schedules = [_schedule] + + if 'space_cooling' in zone_usage_type['endUses']: + _thermal_control.mean_cooling_set_point = \ + zone_usage_type['endUses']['space_cooling']['coolingSetPointTemperature'] + if 'schedule' in zone_usage_type['endUses']['space_cooling']: + _schedule = Schedule() + _schedule.type = 'cooling temperature' + _schedule.time_range = cte.DAY + _schedule.time_step = cte.HOUR + _schedule.data_type = cte.TEMPERATURE + _schedule.day_types = [cte.MONDAY, cte.TUESDAY, cte.WEDNESDAY, cte.THURSDAY, cte.FRIDAY] + _values = zone_usage_type['endUses']['space_cooling']['schedule']['weekDayProfile']['values'] + while ' ' in _values: + _values = _values.replace(' ', ' ') + _schedule.values = _values.split() + _thermal_control.cooling_set_point_schedules = [_schedule] + + usage_zone_archetype.thermal_control = _thermal_control + + if 'ventilation' in zone_usage_type['endUses'] and zone_usage_type['endUses']['ventilation'] is not None: + usage_zone_archetype.mechanical_air_change = \ + zone_usage_type['endUses']['ventilation']['mechanicalAirChangeRate'] + + # todo: not used or assigned anywhere + if 'domestic_hot_water' in zone_usage_type['endUses']: + # liters to cubic meters + dhw_average_volume_pers_day = float( + zone_usage_type['endUses']['domestic_hot_water']['averageVolumePerPersAndDay']) / 1000 + dhw_preparation_temperature = zone_usage_type['endUses']['domestic_hot_water']['preparationTemperature'] + + if 'all_electrical_appliances' in zone_usage_type['endUses']: + if 'averageConsumptionPerSqmAndYear' in zone_usage_type['endUses']['all_electrical_appliances']: + # kWh to J + usage_zone_archetype.electrical_app_average_consumption_sqm_year = \ + float(zone_usage_type['endUses']['all_electrical_appliances']['averageConsumptionPerSqmAndYear']) \ + * cte.KILO_WATTS_HOUR_TO_JULES + + if 'appliance' in zone_usage_type: + _appliances = Appliances() + _appliances.appliances_density = zone_usage_type['appliance']['#text'] #todo: check units + + usage_zone_archetype.appliances = _appliances - internal_gains.append(higa(average_internal_gain=average_internal_gain, convective_fraction=convective_fraction, - radiative_fraction=radiative_fraction, latent_fraction=latent_fraction)) - usage_zone_archetype = huza(usage=usage, internal_gains=internal_gains, heating_set_point=heating_setpoint, - heating_set_back=heating_setback, cooling_set_point=cooling_setpoint, - occupancy_density=occupancy_density, hours_day=hours_day, days_year=days_year, - dhw_average_volume_pers_day=dhw_average_volume_pers_day, - dhw_preparation_temperature=dhw_preparation_temperature, - electrical_app_average_consumption_sqm_year=electrical_app_average_consumption_sqm_year, - mechanical_air_change=mechanical_air_change) return usage_zone_archetype @staticmethod def _parse_zone_usage_variant(usage, usage_zone, usage_zone_variant): - # for the variants all is optional because it mimics the inheritance concept from OOP - occupancy_density = usage_zone.occupancy_density - hours_day = usage_zone.hours_day - days_year = usage_zone.days_year - cooling_setpoint = usage_zone.cooling_setpoint - heating_setpoint = usage_zone.heating_setpoint - heating_setback = usage_zone.heating_setback - mechanical_air_change = usage_zone.mechanical_air_change - dhw_average_volume_pers_day = usage_zone.dhw_average_volume_pers_day - dhw_preparation_temperature = usage_zone.dhw_preparation_temperature - electrical_app_average_consumption_sqm_year = usage_zone.electrical_app_average_consumption_sqm_year + # the variants mimic the inheritance concept from OOP + usage_zone_archetype = usage_zone + usage_zone_archetype.usage = usage - # todo: for internal_gain in usage_zone_variant['schedules']['internGains']:???????????????? - # There are no more internal gains? How is it saved when more than one??? - # for internal_gain in usage_zone.internal_gains: - internal_gains = usage_zone.not_detailed_source_mean_annual_internal_gains[0] - latent_fraction = internal_gains.latent_fraction - convective_fraction = internal_gains.convective_fraction - average_internal_gain = internal_gains.average_internal_gain - radiative_fraction = internal_gains.radiative_fraction + if 'occupancy' in usage_zone_variant: + _occupancy = Occupancy() + if 'occupancyDensity' in usage_zone_variant['occupancy']: + _occupancy.occupancy_density = usage_zone_variant['occupancy']['occupancyDensity'] # todo: check units + if 'usageHoursPerDay' in usage_zone_variant['occupancy']: + usage_zone_archetype.hours_day = usage_zone_variant['occupancy']['usageHoursPerDay'] + if 'usageDaysPerYear' in usage_zone_variant['occupancy']: + usage_zone_archetype.days_year = usage_zone_variant['occupancy']['usageDaysPerYear'] + usage_zone_archetype.occupancy = _occupancy + + if 'internGains' in usage_zone_variant['occupancy']: + _internal_gain = InternalGains() + if 'latentFraction' in usage_zone_variant['occupancy']['internGains']: + _internal_gain.latent_fraction = usage_zone_variant['occupancy']['internGains']['latentFraction'] + if 'convectiveFraction' in usage_zone_variant['occupancy']['internGains']: + _internal_gain.convective_fraction = usage_zone_variant['occupancy']['internGains']['convectiveFraction'] + if 'averageInternGainPerSqm' in usage_zone_variant['occupancy']['internGains']: + _internal_gain.average_internal_gain = \ + usage_zone_variant['occupancy']['internGains']['averageInternGainPerSqm'] + if 'radiantFraction' in usage_zone_variant['occupancy']['internGains']: + _internal_gain.radiative_fraction = usage_zone_variant['occupancy']['internGains']['radiantFraction'] + if 'load' in usage_zone_variant['occupancy']['internGains']: + _schedule = Schedule() + _schedule.type = 'internal gains load' + _schedule.time_range = cte.DAY + _schedule.time_step = cte.HOUR + _schedule.data_type = cte.ANY_NUMBER + _schedule.day_types = [cte.MONDAY, cte.TUESDAY, cte.WEDNESDAY, cte.THURSDAY, cte.FRIDAY] + _values = usage_zone_variant['occupancy']['internGains']['load']['weekDayProfile']['values'] + while ' ' in _values: + _values = _values.replace(' ', ' ') + _schedule.values = _values.split() + _internal_gain.schedules = [_schedule] + + usage_zone_archetype.not_detailed_source_mean_annual_internal_gains = [_internal_gain] + + if 'endUses' in usage_zone_variant: + _thermal_control = ThermalControl() + if 'space_heating' in usage_zone_variant['endUses']: + if 'heatingSetPointTemperature' in usage_zone_variant['endUses']['space_heating']: + _thermal_control.mean_heating_set_point = \ + usage_zone_variant['endUses']['space_heating']['heatingSetPointTemperature'] + if 'heatingSetBackTemperature' in usage_zone_variant['endUses']['space_heating']: + _thermal_control.heating_set_back = usage_zone_variant['endUses']['space_heating']['heatingSetBackTemperature'] + if 'schedule' in usage_zone_variant['endUses']['space_heating']: + _schedule = Schedule() + _schedule.type = 'heating temperature' + _schedule.time_range = cte.DAY + _schedule.time_step = cte.HOUR + _schedule.data_type = cte.TEMPERATURE + _schedule.day_types = [cte.MONDAY, cte.TUESDAY, cte.WEDNESDAY, cte.THURSDAY, cte.FRIDAY] + _values = usage_zone_variant['endUses']['space_heating']['schedule']['weekDayProfile']['values'] + while ' ' in _values: + _values = _values.replace(' ', ' ') + _schedule.values = _values.split() + _thermal_control.heating_set_point_schedules = [_schedule] + + if 'space_cooling' in usage_zone_variant['endUses'] and \ + usage_zone_variant['endUses']['space_cooling'] is not None: + if 'coolingSetPointTemperature' in usage_zone_variant['endUses']['space_cooling']: + _thermal_control.mean_cooling_set_point = \ + usage_zone_variant['endUses']['space_cooling']['coolingSetPointTemperature'] + if 'schedule' in usage_zone_variant['endUses']['space_cooling']: + _schedule = Schedule() + _schedule.type = 'cooling temperature' + _schedule.time_range = cte.DAY + _schedule.time_step = cte.HOUR + _schedule.data_type = cte.TEMPERATURE + _schedule.day_types = [cte.MONDAY, cte.TUESDAY, cte.WEDNESDAY, cte.THURSDAY, cte.FRIDAY] + _values = usage_zone_variant['endUses']['space_cooling']['schedule']['weekDayProfile']['values'] + while ' ' in _values: + _values = _values.replace(' ', ' ') + _schedule.values = _values.split() + _thermal_control.cooling_set_point_schedules = [_schedule] + + usage_zone_archetype.thermal_control = _thermal_control + + if 'ventilation' in usage_zone_variant['endUses'] and usage_zone_variant['endUses']['ventilation'] is not None: + usage_zone_archetype.mechanical_air_change = \ + usage_zone_variant['endUses']['ventilation']['mechanicalAirChangeRate'] + + if 'appliance' in usage_zone_variant: + _appliances = Appliances() + _appliances.appliances_density = usage_zone_variant['appliance']['#text'] # todo: check units + + usage_zone_archetype.appliances = _appliances - if 'space_cooling' in usage_zone_variant['endUses'] and usage_zone_variant['endUses']['space_cooling'] is not None: - if 'coolingSetPointTemperature' in usage_zone_variant['endUses']['space_cooling']: - cooling_setpoint = usage_zone_variant['endUses']['space_cooling']['coolingSetPointTemperature'] - if 'space_heating' in usage_zone_variant['endUses'] and usage_zone_variant['endUses']['space_heating'] is not None: - if 'heatingSetPointTemperature' in usage_zone_variant['endUses']['space_heating']: - heating_setpoint = usage_zone_variant['endUses']['space_heating']['heatingSetPointTemperature'] - if 'heatingSetBackTemperature' in usage_zone_variant['endUses']['space_heating']: - heating_setback = usage_zone_variant['endUses']['space_heating']['heatingSetBackTemperature'] - if 'ventilation' in usage_zone_variant['endUses'] and usage_zone_variant['endUses']['ventilation'] is not None: - if 'mechanicalAirChangeRate' in usage_zone_variant['endUses']['ventilation']: - mechanical_air_change = usage_zone_variant['endUses']['ventilation']['mechanicalAirChangeRate'] - # todo: for internal_gain in usage_zone_variant['schedules']['internGains']:???????????????? - # There are no more internal gains? How is it saved when more than one??? - if 'schedules' in usage_zone_variant: - if 'usageHoursPerDay' in usage_zone_variant['schedules']: - hours_day = usage_zone_variant['schedules']['usageHoursPerDay'] - if 'usageDaysPerYear' in usage_zone_variant['schedules']: - days_year = usage_zone_variant['schedules']['usageDaysPerYear'] - if 'internalGains' in usage_zone_variant['schedules'] and usage_zone_variant['schedules'][ - 'internGains'] is not None: - internal_gains = [] - if 'latentFraction' in usage_zone_variant['schedules']['internGains']: - latent_fraction = usage_zone_variant['schedules']['internGains']['latentFraction'] - if 'convectiveFraction' in usage_zone_variant['schedules']['internGains']: - convective_fraction = usage_zone_variant['schedules']['internGains']['convectiveFraction'] - if 'averageInternGainPerSqm' in usage_zone_variant['schedules']['internGains']: - average_internal_gain = usage_zone_variant['schedules']['internGains']['averageInternGainPerSqm'] - if 'radiantFraction' in usage_zone_variant['schedules']['internGains']: - radiative_fraction = usage_zone_variant['schedules']['internGains']['radiantFraction'] - internal_gains.append(higa(average_internal_gain=average_internal_gain, convective_fraction=convective_fraction, - radiative_fraction=radiative_fraction, latent_fraction=latent_fraction)) - usage_zone_archetype = huza(usage=usage, internal_gains=internal_gains, heating_set_point=heating_setpoint, - heating_set_back=heating_setback, cooling_set_point=cooling_setpoint, - occupancy_density=occupancy_density, hours_day=hours_day, days_year=days_year, - dhw_average_volume_pers_day=dhw_average_volume_pers_day, - dhw_preparation_temperature=dhw_preparation_temperature, - electrical_app_average_consumption_sqm_year=electrical_app_average_consumption_sqm_year, - mechanical_air_change=mechanical_air_change) return usage_zone_archetype diff --git a/imports/usage/hft_usage_parameters.py b/imports/usage/hft_usage_parameters.py index 7411891e..d95a2c3c 100644 --- a/imports/usage/hft_usage_parameters.py +++ b/imports/usage/hft_usage_parameters.py @@ -9,6 +9,9 @@ from imports.geometry.helpers.geometry_helper import GeometryHelper as gh from imports.usage.hft_usage_interface import HftUsageInterface from city_model_structure.building_demand.usage_zone import UsageZone from city_model_structure.building_demand.internal_gains import InternalGains +from city_model_structure.building_demand.occupancy import Occupancy +from city_model_structure.building_demand.appliances import Appliances +from city_model_structure.building_demand.thermal_control import ThermalControl class HftUsageParameters(HftUsageInterface): @@ -26,19 +29,21 @@ class HftUsageParameters(HftUsageInterface): """ city = self._city for building in city.buildings: - archetype = self._search_archetype(gh.usage_from_function(building.function)) - if archetype is None: + usage = gh.usage_from_function(building.function) + try: + archetype = self._search_archetype(usage) + except KeyError: sys.stderr.write(f'Building {building.name} has unknown archetype for building function:' f' {building.function}, that assigns building usage as ' f'{gh.usage_from_function(building.function)}\n') - continue + return for internal_zone in building.internal_zones: usage_zone = UsageZone() - usage_zone.usage = building.function + usage_zone.usage = building.function self._assign_values(usage_zone, archetype) - usage_zone.percentage = 1 - internal_zone.usage_zones = [usage_zone] + usage_zone.percentage = 1 + internal_zone.usage_zones = [usage_zone] def _search_archetype(self, building_usage): for building_archetype in self._usage_archetypes: @@ -51,22 +56,30 @@ class HftUsageParameters(HftUsageInterface): # Due to the fact that python is not a typed language, the wrong object type is assigned to # usage_zone.internal_gains when writing usage_zone.internal_gains = archetype.internal_gains. # Therefore, this walk around has been done. - internal_gains = [] - for archetype_internal_gain in archetype.internal_gains: - internal_gain = InternalGains() - internal_gain.average_internal_gain = archetype_internal_gain.average_internal_gain - internal_gain.convective_fraction = archetype_internal_gain.convective_fraction - internal_gain.radiative_fraction = archetype_internal_gain.radiative_fraction - internal_gain.latent_fraction = archetype_internal_gain.latent_fraction - internal_gains.append(internal_gain) - usage_zone.internal_gains = internal_gains - usage_zone.heating_setpoint = archetype.heating_setpoint - usage_zone.heating_setback = archetype.heating_setback - usage_zone.cooling_setpoint = archetype.cooling_setpoint - usage_zone.occupancy_density = archetype.occupancy_density - usage_zone.hours_day = archetype.hours_day - usage_zone.days_year = archetype.days_year - usage_zone.dhw_average_volume_pers_day = archetype.dhw_average_volume_pers_day - usage_zone.dhw_preparation_temperature = archetype.dhw_preparation_temperature - usage_zone.electrical_app_average_consumption_sqm_year = archetype.electrical_app_average_consumption_sqm_year + # Due to the fact that python is not a typed language, the wrong object type is assigned to + # usage_zone.occupancy when writing usage_zone.occupancy = archetype.occupancy. + # Same happens for lighting and appliances. Therefore, this walk around has been done. usage_zone.mechanical_air_change = archetype.mechanical_air_change + _occupancy = Occupancy() + _occupancy.occupancy_density = archetype.occupancy.occupancy_density + usage_zone.occupancy = _occupancy + _appliances = Appliances() + _appliances.appliances_density = archetype.appliances.appliances_density + usage_zone.appliances = _appliances + _control = ThermalControl() + _control.mean_heating_set_point = archetype.thermal_control.mean_heating_set_point + _control.heating_set_back = archetype.thermal_control.heating_set_back + _control.mean_cooling_set_point = archetype.thermal_control.mean_cooling_set_point + _control.cooling_set_point_schedules = archetype.thermal_control.cooling_set_point_schedules + _control.heating_set_point_schedules = archetype.thermal_control.heating_set_point_schedules + usage_zone.thermal_control = _control + _internal_gains = [] + for archetype_internal_gain in archetype.not_detailed_source_mean_annual_internal_gains: + _internal_gain = InternalGains() + _internal_gain.average_internal_gain = archetype_internal_gain.average_internal_gain + _internal_gain.convective_fraction = archetype_internal_gain.convective_fraction + _internal_gain.radiative_fraction = archetype_internal_gain.radiative_fraction + _internal_gain.latent_fraction = archetype_internal_gain.latent_fraction + _internal_gain.schedules = archetype_internal_gain.schedules + _internal_gains.append(_internal_gain) + usage_zone.not_detailed_source_mean_annual_internal_gains = _internal_gains diff --git a/unittests/test_construction_factory.py b/unittests/test_construction_factory.py index e0ece40a..df7e1edd 100644 --- a/unittests/test_construction_factory.py +++ b/unittests/test_construction_factory.py @@ -47,12 +47,13 @@ class TestConstructionFactory(TestCase): self.assertEqual(len(building.beam), 0, 'building beam is calculated') self.assertIsNotNone(building.lower_corner, 'building lower corner is none') self.assertEqual(len(building.sensors), 0, 'building sensors are assigned') - self.assertIsNotNone(building.geometrical_zones, 'no geometrical zones created') + self.assertIsNotNone(building.internal_zones, 'no internal zones created') self.assertIsNotNone(building.grounds, 'building grounds is none') self.assertIsNotNone(building.walls, 'building walls is none') self.assertIsNotNone(building.roofs, 'building roofs is none') - self.assertIsNone(building.usage_zones, 'usage zones are defined') - self.assertTrue(len(building.thermal_zones) > 0, 'thermal zones are not defined') + for internal_zone in building.internal_zones: + self.assertIsNone(internal_zone.usage_zones, 'usage zones are defined') + self.assertTrue(len(internal_zone.thermal_zones) > 0, 'thermal zones are not defined') self.assertIsNone(building.basement_heated, 'building basement_heated is not none') self.assertIsNone(building.attic_heated, 'building attic_heated is not none') self.assertIsNone(building.terrains, 'building terrains is not none') @@ -69,8 +70,8 @@ class TestConstructionFactory(TestCase): self.assertIsNone(building.households, 'building households is not none') self.assertFalse(building.is_conditioned, 'building is conditioned') - def _check_thermal_zones(self, building): - for thermal_zone in building.thermal_zones: + def _check_thermal_zones(self, internal_zone): + for thermal_zone in internal_zone.thermal_zones: self.assertIsNotNone(thermal_zone.id, 'thermal_zone id is none') self.assertIsNotNone(thermal_zone.floor_area, 'thermal_zone floor area is none') self.assertTrue(len(thermal_zone.thermal_boundaries) > 0, 'thermal_zone thermal_boundaries not defined') @@ -81,9 +82,10 @@ class TestConstructionFactory(TestCase): self.assertIsNotNone(thermal_zone.infiltration_rate_system_off, 'thermal_zone infiltration_rate_system_off is none') self.assertIsNotNone(thermal_zone.infiltration_rate_system_on, 'thermal_zone infiltration_rate_system_on is none') - self.assertIsNone(thermal_zone.usage_zones, 'thermal_zone usage_zones is not none') self.assertIsNotNone(thermal_zone.volume, 'thermal_zone volume is none') self.assertIsNone(thermal_zone.ordinate_number, 'thermal_zone ordinate number is not none') + self.assertIsNotNone(thermal_zone.view_factors_matrix, 'thermal_zone view factors matrix is none') + self.assertIsNone(thermal_zone.usage_zones, 'thermal_zone usage_zones is not none') self.assertIsNone(thermal_zone.thermal_control, 'thermal_zone thermal_control is not none') self.assertIsNone(thermal_zone.hvac_system, 'thermal_zone hvac_system is not none') @@ -164,27 +166,28 @@ class TestConstructionFactory(TestCase): file = 'one_building_in_kelowna.gml' city = self._get_citygml(file) for building in city.buildings: - building.function = GeometryHelper.hft_to_function[building.function] + building.function = GeometryHelper.function_from_hft(building.function) ConstructionFactory('nrcan', city).enrich() self._check_buildings(city) for building in city.buildings: - self._check_thermal_zones(building) - for thermal_zone in building.thermal_zones: - self._check_thermal_boundaries(thermal_zone) - for thermal_boundary in thermal_zone.thermal_boundaries: - self.assertIsNone(thermal_boundary.outside_thermal_absorptance, 'outside_thermal_absorptance is not none') - self.assertIsNone(thermal_boundary.outside_visible_absorptance, 'outside_visible_absorptance is not none') - self.assertIsNone(thermal_boundary.layers, 'layers is not none') + for internal_zone in building.internal_zones: + self._check_thermal_zones(internal_zone) + for thermal_zone in internal_zone.thermal_zones: + self._check_thermal_boundaries(thermal_zone) + for thermal_boundary in thermal_zone.thermal_boundaries: + self.assertIsNone(thermal_boundary.outside_thermal_absorptance, 'outside_thermal_absorptance is not none') + self.assertIsNone(thermal_boundary.outside_visible_absorptance, 'outside_visible_absorptance is not none') + self.assertIsNone(thermal_boundary.layers, 'layers is not none') - self._check_thermal_openings(thermal_boundary) - for thermal_opening in thermal_boundary.thermal_openings: - self.assertIsNone(thermal_opening.conductivity, 'thermal_opening conductivity is not none') - self.assertIsNone(thermal_opening.thickness, 'thermal opening thickness is not none') - self.assertIsNone(thermal_opening.front_side_solar_transmittance_at_normal_incidence, - 'thermal opening front_side_solar_transmittance_at_normal_incidence is not none') - self.assertIsNone(thermal_opening.back_side_solar_transmittance_at_normal_incidence, - 'thermal opening back_side_solar_transmittance_at_normal_incidence is not none') + self._check_thermal_openings(thermal_boundary) + for thermal_opening in thermal_boundary.thermal_openings: + self.assertIsNone(thermal_opening.conductivity, 'thermal_opening conductivity is not none') + self.assertIsNone(thermal_opening.thickness, 'thermal opening thickness is not none') + self.assertIsNone(thermal_opening.front_side_solar_transmittance_at_normal_incidence, + 'thermal opening front_side_solar_transmittance_at_normal_incidence is not none') + self.assertIsNone(thermal_opening.back_side_solar_transmittance_at_normal_incidence, + 'thermal opening back_side_solar_transmittance_at_normal_incidence is not none') def test_city_with_construction_extended_library(self): """ @@ -193,41 +196,39 @@ class TestConstructionFactory(TestCase): file = 'pluto_building.gml' city = self._get_citygml(file) for building in city.buildings: - building.function = GeometryHelper.pluto_to_function[building.function] + building.function = GeometryHelper.function_from_pluto(building.function) ConstructionFactory('nrel', city).enrich() self._check_buildings(city) for building in city.buildings: - self._check_thermal_zones(building) - for thermal_zone in building.thermal_zones: - self._check_thermal_boundaries(thermal_zone) - for thermal_boundary in thermal_zone.thermal_boundaries: - if thermal_boundary.type is not cte.GROUND: - self.assertIsNotNone(thermal_boundary.outside_thermal_absorptance, 'outside_thermal_absorptance is none') - self.assertIsNotNone(thermal_boundary.outside_visible_absorptance, 'outside_visible_absorptance is none') - else: - self.assertIsNone(thermal_boundary.outside_thermal_absorptance, 'outside_thermal_absorptance is not none') - self.assertIsNone(thermal_boundary.outside_visible_absorptance, 'outside_visible_absorptance is not none') - self.assertIsNotNone(thermal_boundary.layers, 'layers is none') + for internal_zone in building.internal_zones: + self._check_thermal_zones(internal_zone) + for thermal_zone in internal_zone.thermal_zones: + self._check_thermal_boundaries(thermal_zone) + for thermal_boundary in thermal_zone.thermal_boundaries: + if thermal_boundary.type is not cte.GROUND: + self.assertIsNotNone(thermal_boundary.outside_thermal_absorptance, 'outside_thermal_absorptance is none') + self.assertIsNotNone(thermal_boundary.outside_visible_absorptance, 'outside_visible_absorptance is none') + else: + self.assertIsNone(thermal_boundary.outside_thermal_absorptance, 'outside_thermal_absorptance is not none') + self.assertIsNone(thermal_boundary.outside_visible_absorptance, 'outside_visible_absorptance is not none') + self.assertIsNotNone(thermal_boundary.layers, 'layers is none') - self._check_thermal_openings(thermal_boundary) - for thermal_opening in thermal_boundary.thermal_openings: - self.assertIsNotNone(thermal_opening.conductivity, 'thermal_opening conductivity is none') - self.assertIsNotNone(thermal_opening.thickness, 'thermal opening thickness is none') - self.assertIsNotNone(thermal_opening.front_side_solar_transmittance_at_normal_incidence, - 'thermal opening front_side_solar_transmittance_at_normal_incidence is none') - self.assertIsNotNone(thermal_opening.back_side_solar_transmittance_at_normal_incidence, - 'thermal opening back_side_solar_transmittance_at_normal_incidence is none') + self._check_thermal_openings(thermal_boundary) + for thermal_opening in thermal_boundary.thermal_openings: + self.assertIsNotNone(thermal_opening.conductivity, 'thermal_opening conductivity is none') + self.assertIsNotNone(thermal_opening.thickness, 'thermal opening thickness is none') + self.assertIsNotNone(thermal_opening.front_side_solar_transmittance_at_normal_incidence, + 'thermal opening front_side_solar_transmittance_at_normal_incidence is none') + self.assertIsNotNone(thermal_opening.back_side_solar_transmittance_at_normal_incidence, + 'thermal opening back_side_solar_transmittance_at_normal_incidence is none') @staticmethod def _internal_function(function_format, original_function): if function_format == 'hft': - new_function = GeometryHelper.hft_to_function[original_function] + new_function = GeometryHelper.function_from_hft(original_function) elif function_format == 'pluto': - new_function = GeometryHelper.pluto_to_function[original_function] - elif function_format == 'alkis': - # todo: not implemented yet!! - raise NotImplementedError + new_function = GeometryHelper.function_from_pluto(original_function) else: raise Exception('Function key not recognized. Implemented only "hft" and "pluto"') return new_function diff --git a/unittests/test_enrichement.py b/unittests/test_enrichement.py new file mode 100644 index 00000000..7c5861d5 --- /dev/null +++ b/unittests/test_enrichement.py @@ -0,0 +1,145 @@ +""" +TestGeometryFactory test and validate the city model structure geometric parameters +SPDX - License - Identifier: LGPL - 3.0 - or -later +Copyright © 2020 Project Author Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca +""" +from pathlib import Path +from unittest import TestCase +from imports.geometry_factory import GeometryFactory +from imports.geometry.helpers.geometry_helper import GeometryHelper +from imports.usage_factory import UsageFactory +from imports.construction_factory import ConstructionFactory + + +class TestGeometryFactory(TestCase): + """ + Non-functional TestGeometryFactory + Load testing + """ + def setUp(self) -> None: + """ + 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', file_path).city + self.assertIsNotNone(self._city, 'city is none') + return self._city + + def _check_buildings(self, city): + for building in city.buildings: + self.assertIsNotNone(building.internal_zones, 'no internal zones created') + for internal_zone in building.internal_zones: + self.assertIsNotNone(internal_zone.usage_zones, 'usage zones are not defined') + self.assertIsNotNone(internal_zone.thermal_zones, 'thermal zones are not defined') + #self.assertIsNotNone(building.basement_heated, 'building basement_heated is none') + #self.assertIsNotNone(building.attic_heated, 'building attic_heated is none') + self.assertIsNotNone(building.average_storey_height, 'building average_storey_height is none') + self.assertIsNotNone(building.storeys_above_ground, 'building storeys_above_ground is none') + self.assertTrue(building.is_conditioned, 'building is_conditioned is not conditioned') + + def _check_usage_zone(self, usage_zone): + self.assertIsNotNone(usage_zone.id, 'usage id is none') + + def _check_thermal_zones(self, thermal_zone): + self.assertIsNotNone(thermal_zone.id, 'thermal_zone id is none') + self.assertIsNone(thermal_zone.usage_zones, 'thermal_zone usage_zones is not none') + self.assertIsNone(thermal_zone.thermal_control, 'thermal_zone thermal_control is not none') + + @staticmethod + def _prepare_case_usage_first(city, input_key, construction_key, usage_key): + if input_key == 'pluto': + for building in city.buildings: + building.function = GeometryHelper.function_from_pluto(building.function) + elif input_key == 'hft': + for building in city.buildings: + building.function = GeometryHelper.function_from_hft(building.function) + UsageFactory(usage_key, city).enrich() + ConstructionFactory(construction_key, city).enrich() + + @staticmethod + def _prepare_case_construction_first(city, input_key, construction_key, usage_key): + if input_key == 'pluto': + for building in city.buildings: + building.function = GeometryHelper.function_from_pluto(building.function) + elif input_key == 'hft': + for building in city.buildings: + building.function = GeometryHelper.function_from_hft(building.function) + ConstructionFactory(construction_key, city).enrich() + UsageFactory(usage_key, city).enrich() + + def test_enrichment(self): + """ + Test enrichment of the city with different order and all possible combinations + :return: None + """ + file_1 = 'one_building_in_kelowna.gml' + file_2 = 'pluto_building.gml' + file_3 = 'C40_Final.gml' + _construction_keys = ['nrel', 'nrcan'] + _usage_keys = ['ca', 'comnet'] # todo: add 'hft' + + for construction_key in _construction_keys: + for usage_key in _usage_keys: + city = self._get_citygml(file_1) + self.assertTrue(len(city.buildings) == 1) + self._prepare_case_construction_first(city, 'hft', construction_key, usage_key) + self._check_buildings(city) + for building in city.buildings: + for internal_zone in building.internal_zones: + self.assertIsNot(len(internal_zone.usage_zones), 0, 'no building usage_zones defined') + for usage_zone in internal_zone.usage_zones: + self._check_usage_zone(usage_zone) + for thermal_zone in internal_zone.thermal_zones: + self._check_thermal_zones(thermal_zone) + + for construction_key in _construction_keys: + for usage_key in _usage_keys: + city = self._get_citygml(file_1) + self.assertTrue(len(city.buildings) == 1) + self._prepare_case_usage_first(city, 'hft', construction_key, usage_key) + self._check_buildings(city) + for building in city.buildings: + for internal_zone in building.internal_zones: + self.assertIsNot(len(internal_zone.usage_zones), 0, 'no building usage_zones defined') + for usage_zone in internal_zone.usage_zones: + self._check_usage_zone(usage_zone) + for thermal_zone in internal_zone.thermal_zones: + self._check_thermal_zones(thermal_zone) + + for construction_key in _construction_keys: + for usage_key in _usage_keys: + if usage_key != 'ca': + city = self._get_citygml(file_2) + self.assertTrue(len(city.buildings) == 1) + self._prepare_case_construction_first(city, 'pluto', construction_key, usage_key) + self._check_buildings(city) + for building in city.buildings: + for internal_zone in building.internal_zones: + self.assertIsNot(len(internal_zone.usage_zones), 0, 'no building usage_zones defined') + for usage_zone in internal_zone.usage_zones: + self._check_usage_zone(usage_zone) + for thermal_zone in internal_zone.thermal_zones: + self._check_thermal_zones(thermal_zone) + + for construction_key in _construction_keys: + for usage_key in _usage_keys: + if usage_key != 'ca': + city = self._get_citygml(file_2) + self.assertTrue(len(city.buildings) == 1) + self._prepare_case_usage_first(city, 'pluto', construction_key, usage_key) + self._check_buildings(city) + for building in city.buildings: + for internal_zone in building.internal_zones: + self.assertIsNot(len(internal_zone.usage_zones), 0, 'no building usage_zones defined') + for usage_zone in internal_zone.usage_zones: + self._check_usage_zone(usage_zone) + for thermal_zone in internal_zone.thermal_zones: + self._check_thermal_zones(thermal_zone) + + city = self._get_citygml(file_3) + self.assertTrue(len(city.buildings) == 10) diff --git a/unittests/test_geometry_factory.py b/unittests/test_geometry_factory.py index b9142591..db5ae4bb 100644 --- a/unittests/test_geometry_factory.py +++ b/unittests/test_geometry_factory.py @@ -51,7 +51,7 @@ class TestGeometryFactory(TestCase): self.assertEqual(len(building.beam), 0, 'building beam is calculated') self.assertIsNotNone(building.lower_corner, 'building lower corner is none') self.assertEqual(len(building.sensors), 0, 'building sensors are assigned') - self.assertIsNotNone(building.geometrical_zones, 'no geometrical zones created') + self.assertIsNotNone(building.internal_zones, 'no internal zones created') self.assertIsNotNone(building.grounds, 'building grounds is none') self.assertIsNotNone(building.walls, 'building walls is none') self.assertIsNotNone(building.roofs, 'building roofs is none') diff --git a/unittests/test_usage_factory.py b/unittests/test_usage_factory.py index b884259d..948cf4e4 100644 --- a/unittests/test_usage_factory.py +++ b/unittests/test_usage_factory.py @@ -8,8 +8,6 @@ from unittest import TestCase from imports.geometry_factory import GeometryFactory from imports.usage_factory import UsageFactory -from imports.schedules_factory import SchedulesFactory -from imports.construction_factory import ConstructionFactory from imports.geometry.helpers.geometry_helper import GeometryHelper from imports.construction_factory import ConstructionFactory @@ -49,34 +47,40 @@ class TestUsageFactory(TestCase): self.assertEqual(len(building.beam), 0, 'building beam is calculated') self.assertIsNotNone(building.lower_corner, 'building lower corner is none') self.assertEqual(len(building.sensors), 0, 'building sensors are assigned') - self.assertIsNotNone(building.geometrical_zones, 'no geometrical zones created') + self.assertIsNotNone(building.internal_zones, 'no internal zones created') self.assertIsNotNone(building.grounds, 'building grounds is none') self.assertIsNotNone(building.walls, 'building walls is none') self.assertIsNotNone(building.roofs, 'building roofs is none') - self.assertTrue(len(building.usage_zones) > 0, 'usage zones are not defined') - self.assertTrue(len(building.thermal_zones) > 0, 'thermal zones are not defined') - self.assertIsNotNone(building.basement_heated, 'building basement_heated is none') - self.assertIsNotNone(building.attic_heated, 'building attic_heated is none') + for internal_zone in building.internal_zones: + self.assertTrue(len(internal_zone.usage_zones) > 0, 'usage zones are not defined') + self.assertIsNone(building.thermal_zones, 'thermal zones are defined') + self.assertIsNone(building.basement_heated, 'building basement_heated is not none') + self.assertIsNone(building.attic_heated, 'building attic_heated is not none') self.assertIsNone(building.terrains, 'building terrains is not none') self.assertIsNotNone(building.year_of_construction, 'building year_of_construction is none') self.assertIsNotNone(building.function, 'building function is none') - self.assertIsNotNone(building.average_storey_height, 'building average_storey_height is none') - self.assertIsNotNone(building.storeys_above_ground, 'building storeys_above_ground is none') + self.assertIsNone(building.average_storey_height, 'building average_storey_height is not none') + self.assertIsNone(building.storeys_above_ground, 'building storeys_above_ground is not none') self.assertEqual(len(building.heating), 0, 'building heating is not none') self.assertEqual(len(building.cooling), 0, 'building cooling is not none') self.assertIsNotNone(building.eave_height, 'building eave height is none') - self.assertIsNotNone(building.storeys, 'building storeys are not defined') + self.assertIsNone(building.storeys, 'building storeys are defined') self.assertIsNotNone(building.roof_type, 'building roof type is none') self.assertIsNotNone(building.floor_area, 'building floor_area is none') self.assertIsNone(building.households, 'building households is not none') self.assertTrue(building.is_conditioned, 'building is not conditioned') - - def _check_hvac(self, thermal_zone): - self.assertIsNotNone(None, 'hvac') - - def _check_control(self, thermal_zone): - self.assertIsNotNone(None, 'control') + def _check_usage_zone(self, usage_zone): + self.assertIsNotNone(usage_zone.usage, 'usage is none') + self.assertIsNotNone(usage_zone.percentage, 'usage percentage is none') + self.assertIsNotNone(usage_zone.get_internal_gains, 'internal gains is none') + self.assertIsNotNone(usage_zone.hours_day, 'hours per day is none') + self.assertIsNotNone(usage_zone.days_year, 'days per year is none') + self.assertIsNotNone(usage_zone.mechanical_air_change, 'mechanical air change is none') + self.assertIsNotNone(usage_zone.thermal_control, 'thermal control is none') + self.assertIsNotNone(usage_zone.thermal_control.mean_heating_set_point, 'control heating set point is none') + self.assertIsNotNone(usage_zone.thermal_control.heating_set_back, 'control heating set back is none') + self.assertIsNotNone(usage_zone.thermal_control.mean_cooling_set_point, 'control cooling set point is none') def test_import_comnet(self): """ @@ -85,33 +89,45 @@ class TestUsageFactory(TestCase): file = 'pluto_building.gml' city = self._get_citygml(file) for building in city.buildings: - building.function = GeometryHelper.pluto_to_function[building.function] + building.function = GeometryHelper.function_from_pluto(building.function) UsageFactory('comnet', city).enrich() - SchedulesFactory('comnet', city).enrich() self._check_buildings(city) for building in city.buildings: for internal_zone in building.internal_zones: self.assertIsNot(len(internal_zone.usage_zones), 0, 'no building usage_zones defined') for usage_zone in internal_zone.usage_zones: - self._check_extended_usage(usage_zone) - - def test_import_hft(self): - """ - Enrich the city with the usage information from hft and verify it - """ - # todo: read schedules!! - file = 'pluto_building.gml' - city = self._get_citygml(file) - for building in city.buildings: - building.function = GeometryHelper.pluto_to_function[building.function] - - UsageFactory('hft', city).enrich() - for building in city.buildings: - for internal_zone in building.internal_zones: - self.assertIsNot(len(internal_zone.usage_zones), 0, 'no building usage_zones defined') - for usage_zone in internal_zone.usage_zones: - self._check_extended_usage(usage_zone) + self._check_usage_zone(usage_zone) + self.assertIsNotNone(usage_zone.thermal_control.heating_set_point_schedules, + 'control heating set point schedule is none') + self.assertIsNotNone(usage_zone.thermal_control.cooling_set_point_schedules, + 'control cooling set point schedule is none') + self.assertIsNotNone(usage_zone.occupancy, 'occupancy is none') + occupancy = usage_zone.occupancy + self.assertIsNotNone(occupancy.occupancy_density, 'occupancy density is none') + self.assertIsNotNone(occupancy.latent_internal_gain, 'occupancy latent internal gain is none') + self.assertIsNotNone(occupancy.sensible_convective_internal_gain, + 'occupancy sensible convective internal gain is none') + self.assertIsNotNone(occupancy.sensible_radiative_internal_gain, + 'occupancy sensible radiant internal gain is none') + self.assertIsNotNone(occupancy.occupancy_schedules, 'occupancy schedule is none') + self.assertIsNone(occupancy.occupants, 'occupancy density is not none') + self.assertIsNotNone(usage_zone.lighting, 'lighting is none') + lighting = usage_zone.lighting + self.assertIsNotNone(lighting.lighting_density, 'lighting density is none') + self.assertIsNotNone(lighting.latent_fraction, 'lighting latent fraction is none') + self.assertIsNotNone(lighting.convective_fraction, 'lighting convective fraction is none') + self.assertIsNotNone(lighting.radiative_fraction, 'lighting radiant fraction is none') + self.assertIsNotNone(lighting.schedules, 'lighting schedule is none') + self.assertIsNotNone(usage_zone.appliances, 'appliances is none') + appliances = usage_zone.appliances + self.assertIsNotNone(appliances.appliances_density, 'appliances density is none') + self.assertIsNotNone(appliances.latent_fraction, 'appliances latent fraction is none') + self.assertIsNotNone(appliances.convective_fraction, 'appliances convective fraction is none') + self.assertIsNotNone(appliances.radiative_fraction, 'appliances radiant fraction is none') + self.assertIsNotNone(appliances.schedules, 'appliances schedule is none') + self.assertIsNotNone(usage_zone.thermal_control.hvac_availability_schedules, + 'control hvac availability is none') def test_import_ca(self): """ @@ -120,34 +136,56 @@ class TestUsageFactory(TestCase): file = 'one_building_in_kelowna.gml' city = self._get_citygml(file) UsageFactory('ca', city).enrich() + self._check_buildings(city) for building in city.buildings: for internal_zone in building.internal_zones: self.assertIsNot(len(internal_zone.usage_zones), 0, 'no building usage_zones defined') for usage_zone in internal_zone.usage_zones: - self._check_reduced_usage(usage_zone) + self._check_usage_zone(usage_zone) + self.assertIsNotNone(usage_zone.not_detailed_source_mean_annual_internal_gains, + 'not detailed internal gains is none') - def _check_extended_usage(self, usage_zone): - self.assertIsNotNone(usage_zone.usage, 'usage is none') - self.assertIsNotNone(usage_zone.not_detailed_source_mean_annual_internal_gains, 'usage is none') - self.assertIsNotNone(usage_zone.cooling_setpoint, 'usage is none') - self.assertIsNotNone(usage_zone.heating_setback, 'usage is none') - self.assertIsNotNone(usage_zone.heating_setpoint, 'usage is none') - self.assertIsNotNone(usage_zone.occupancy_density, 'usage is none') - self.assertIsNotNone(usage_zone.hours_day, 'usage is none') - self.assertIsNotNone(usage_zone.days_year, 'usage is none') - self.assertIsNotNone(usage_zone.dhw_average_volume_pers_day, 'usage is none') - self.assertIsNotNone(usage_zone.dhw_preparation_temperature, 'usage is none') - self.assertIsNotNone(usage_zone.electrical_app_average_consumption_sqm_year, 'usage is none') - self.assertIsNotNone(usage_zone.is_heated, 'thermal_zone heated is none') - self.assertIsNotNone(usage_zone.is_cooled, 'thermal_zone cooled is none') + def test_import_hft(self): + """ + Enrich the city with the usage information from hft and verify it + """ + file = 'pluto_building.gml' + city = self._get_citygml(file) + for building in city.buildings: + building.function = GeometryHelper.function_from_pluto(building.function) - - def _check_reduced_usage(self, usage_zone): - self.assertIsNotNone(usage_zone.usage, 'usage is none') - self.assertIsNotNone(usage_zone.internal_gains, 'usage is none') - self.assertIsNotNone(usage_zone.cooling_setpoint, 'usage is none') - self.assertIsNotNone(usage_zone.heating_setback, 'usage is none') - self.assertIsNotNone(usage_zone.heating_setpoint, 'usage is none') - self.assertIsNotNone(usage_zone.occupancy_density, 'usage is none') - self.assertIsNotNone(usage_zone.hours_day, 'usage is none') - self.assertIsNotNone(usage_zone.days_year, 'usage is none') + UsageFactory('hft', city).enrich() + self._check_buildings(city) + for building in city.buildings: + for internal_zone in building.internal_zones: + self.assertIsNot(len(internal_zone.usage_zones), 0, 'no building usage_zones defined') + for usage_zone in internal_zone.usage_zones: + self._check_usage_zone(usage_zone) + self.assertIsNotNone(usage_zone.thermal_control.heating_set_point_schedules, + 'control heating set point schedule is none') + self.assertIsNotNone(usage_zone.thermal_control.cooling_set_point_schedules, + 'control cooling set point schedule is none') + self.assertIsNotNone(usage_zone.occupancy, 'occupancy is none') + occupancy = usage_zone.occupancy + self.assertIsNotNone(occupancy.occupancy_density, 'occupancy density is none') + self.assertIsNotNone(occupancy.latent_internal_gain, 'occupancy latent internal gain is none') + self.assertIsNotNone(occupancy.sensible_convective_internal_gain, + 'occupancy sensible convective internal gain is none') + self.assertIsNotNone(occupancy.sensible_radiative_internal_gain, + 'occupancy sensible radiant internal gain is none') + self.assertIsNotNone(occupancy.occupancy_schedules, 'occupancy schedule is none') + self.assertIsNone(occupancy.occupants, 'occupancy density is not none') + self.assertIsNotNone(usage_zone.lighting, 'lighting is none') + lighting = usage_zone.lighting + self.assertIsNotNone(lighting.lighting_density, 'lighting density is none') + self.assertIsNotNone(lighting.latent_fraction, 'lighting latent fraction is none') + self.assertIsNotNone(lighting.convective_fraction, 'lighting convective fraction is none') + self.assertIsNotNone(lighting.radiative_fraction, 'lighting radiant fraction is none') + self.assertIsNotNone(lighting.schedules, 'lighting schedule is none') + self.assertIsNotNone(usage_zone.appliances, 'appliances is none') + appliances = usage_zone.appliances + self.assertIsNotNone(appliances.appliances_density, 'appliances density is none') + self.assertIsNotNone(appliances.latent_fraction, 'appliances latent fraction is none') + self.assertIsNotNone(appliances.convective_fraction, 'appliances convective fraction is none') + self.assertIsNotNone(appliances.radiative_fraction, 'appliances radiant fraction is none') + self.assertIsNotNone(appliances.schedules, 'appliances schedule is none')