From 4cc9c2dcdb53c341843b05944e76abe095272627 Mon Sep 17 00:00:00 2001 From: Pilar Date: Mon, 13 Mar 2023 14:40:07 -0400 Subject: [PATCH 01/13] added dhw classes to catalogs and CDM including importers --- .../data_models/usages/domestic_hot_water.py | 45 +++++ .../data_models/usages/usage.py | 15 +- .../energy_systems/nrcan_catalog.py | 189 ++++++++++++++++++ .../energy_systems_catalog_factory.py | 42 ++++ hub/catalog_factories/usage/comnet_catalog.py | 16 +- hub/catalog_factories/usage/nrcan_catalog.py | 25 ++- hub/catalog_factories/usage/usage_helper.py | 3 +- .../building_demand/domestic_hot_water.py | 70 +++++++ .../building_demand/thermal_zone.py | 39 ++++ .../building_demand/usage.py | 22 +- hub/data/energy_systems/nrcan.xml | 11 + hub/helpers/constants.py | 1 + hub/imports/usage/comnet_usage_parameters.py | 6 + hub/imports/usage/nrcan_usage_parameters.py | 6 + hub/unittests/test_usage_factory.py | 4 + 15 files changed, 483 insertions(+), 11 deletions(-) create mode 100644 hub/catalog_factories/data_models/usages/domestic_hot_water.py create mode 100644 hub/catalog_factories/energy_systems/nrcan_catalog.py create mode 100644 hub/catalog_factories/energy_systems_catalog_factory.py create mode 100644 hub/city_model_structure/building_demand/domestic_hot_water.py create mode 100644 hub/data/energy_systems/nrcan.xml diff --git a/hub/catalog_factories/data_models/usages/domestic_hot_water.py b/hub/catalog_factories/data_models/usages/domestic_hot_water.py new file mode 100644 index 00000000..edf4b8ab --- /dev/null +++ b/hub/catalog_factories/data_models/usages/domestic_hot_water.py @@ -0,0 +1,45 @@ +""" +Usage catalog domestic hot water +SPDX - License - Identifier: LGPL - 3.0 - or -later +Copyright © 2023 Concordia CERC group +Project Coder Pilar Monsalvete Álvarez de Uribarri pilar.monsalvete@concordia.ca +""" + +from typing import Union, List + +from hub.catalog_factories.data_models.usages.schedule import Schedule + + +class DomesticHotWater: + """ + DomesticHotWater class + """ + def __init__(self, density, service_temperature, schedules): + self._density = density + self._service_temperature = service_temperature + self._schedules = schedules + + @property + def density(self) -> Union[None, float]: + """ + Get domestic hot water load density in Watts per m2 + :return: None or float + """ + return self._density + + @property + def service_temperature(self) -> Union[None, float]: + """ + Get service temperature in degrees Celsius + :return: None or float + """ + return self._service_temperature + + @property + def schedules(self) -> Union[None, List[Schedule]]: + """ + Get schedules + dataType = fraction of loads + :return: None or [Schedule] + """ + return self._schedules diff --git a/hub/catalog_factories/data_models/usages/usage.py b/hub/catalog_factories/data_models/usages/usage.py index 95868b21..07ac17bc 100644 --- a/hub/catalog_factories/data_models/usages/usage.py +++ b/hub/catalog_factories/data_models/usages/usage.py @@ -10,6 +10,7 @@ from hub.catalog_factories.data_models.usages.appliances import Appliances from hub.catalog_factories.data_models.usages.lighting import Lighting from hub.catalog_factories.data_models.usages.ocupancy import Occupancy from hub.catalog_factories.data_models.usages.thermal_control import ThermalControl +from hub.catalog_factories.data_models.usages.domestic_hot_water import DomesticHotWater class Usage: @@ -21,7 +22,8 @@ class Usage: occupancy, lighting, appliances, - thermal_control): + thermal_control, + domestic_hot_water): self._name = name self._hours_day = hours_day self._days_year = days_year @@ -32,6 +34,7 @@ class Usage: self._lighting = lighting self._appliances = appliances self._thermal_control = thermal_control + self._domestic_hot_water = domestic_hot_water @property def name(self) -> Union[None, str]: @@ -108,7 +111,15 @@ class Usage: @property def thermal_control(self) -> Union[None, ThermalControl]: """ - Get thermal control of this thermal zone + Get thermal control information :return: None or ThermalControl """ return self._thermal_control + + @property + def domestic_hot_water(self) -> Union[None, DomesticHotWater]: + """ + Get domestic hot water information + :return: None or DomesticHotWater + """ + return self._domestic_hot_water diff --git a/hub/catalog_factories/energy_systems/nrcan_catalog.py b/hub/catalog_factories/energy_systems/nrcan_catalog.py new file mode 100644 index 00000000..baf8a2c2 --- /dev/null +++ b/hub/catalog_factories/energy_systems/nrcan_catalog.py @@ -0,0 +1,189 @@ +""" +NRCAN energy systems catalog +SPDX - License - Identifier: LGPL - 3.0 - or -later +Copyright © 2022 Concordia CERC group +Project Coder Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca +""" + +import json +import urllib.request +import xmltodict + +import hub.helpers.constants as cte +from hub.catalog_factories.catalog import Catalog +from hub.catalog_factories.data_models.usages.appliances import Appliances +from hub.catalog_factories.data_models.usages.content import Content +from hub.catalog_factories.data_models.usages.lighting import Lighting +from hub.catalog_factories.data_models.usages.ocupancy import Occupancy +from hub.catalog_factories.data_models.usages.schedule import Schedule +from hub.catalog_factories.data_models.usages.thermal_control import ThermalControl +from hub.catalog_factories.data_models.usages.usage import Usage +from hub.catalog_factories.usage.usage_helper import UsageHelper + + +class NrcanCatalog(Catalog): + def __init__(self, path): + path = str(path / 'nrcan.xml') + self._content = None + self._schedules = {} + with open(path) as xml: + self._metadata = xmltodict.parse(xml.read()) + self._base_url = self._metadata['nrcan']['@base_url'] + self._load_schedules() + self._content = Content(self._load_archetypes()) + + def _load_archetypes(self): + usages = [] + name = self._metadata['nrcan'] + url = f'{self._base_url}{name["space_types_location"]}' + with urllib.request.urlopen(url) as json_file: + space_types = json.load(json_file)['tables']['space_types']['table'] +# space_types = [st for st in space_types if st['building_type'] == 'Space Function'] + space_types = [st for st in space_types if st['space_type'] == 'WholeBuilding'] + for space_type in space_types: +# usage_type = space_type['space_type'] + usage_type = space_type['building_type'] + occupancy_schedule_name = space_type['occupancy_schedule'] + lighting_schedule_name = space_type['lighting_schedule'] + appliance_schedule_name = space_type['electric_equipment_schedule'] + hvac_schedule_name = space_type['exhaust_schedule'] + if 'FAN' in hvac_schedule_name: + hvac_schedule_name = hvac_schedule_name.replace('FAN', 'Fan') + heating_setpoint_schedule_name = space_type['heating_setpoint_schedule'] + cooling_setpoint_schedule_name = space_type['cooling_setpoint_schedule'] + occupancy_schedule = self._get_schedules(occupancy_schedule_name) + lighting_schedule = self._get_schedules(lighting_schedule_name) + appliance_schedule = self._get_schedules(appliance_schedule_name) + heating_schedule = self._get_schedules(heating_setpoint_schedule_name) + cooling_schedule = self._get_schedules(cooling_setpoint_schedule_name) + hvac_availability = self._get_schedules(hvac_schedule_name) + + occupancy_density = space_type['occupancy_per_area'] + + # ACH + mechanical_air_change = space_type['ventilation_air_changes'] + # cfm/ft2 to m3/m2.s + ventilation_rate = space_type['ventilation_per_area'] / (cte.METERS_TO_FEET * cte.MINUTES_TO_SECONDS) + if ventilation_rate == 0: + # cfm/person to m3/m2.s + ventilation_rate = space_type['ventilation_per_person'] / occupancy_density\ + / (cte.METERS_TO_FEET * cte.MINUTES_TO_SECONDS) + + # W/sqft to W/m2 + lighting_density = space_type['lighting_per_area'] * cte.METERS_TO_FEET * cte.METERS_TO_FEET + lighting_radiative_fraction = space_type['lighting_fraction_radiant'] + lighting_convective_fraction = 0 + if lighting_radiative_fraction is not None: + lighting_convective_fraction = 1 - lighting_radiative_fraction + lighting_latent_fraction = 0 + # W/sqft to W/m2 + appliances_density = space_type['electric_equipment_per_area'] * cte.METERS_TO_FEET * cte.METERS_TO_FEET + appliances_radiative_fraction = space_type['electric_equipment_fraction_radiant'] + appliances_latent_fraction = space_type['electric_equipment_fraction_latent'] + appliances_convective_fraction = 0 + if appliances_radiative_fraction is not None and appliances_latent_fraction is not None: + appliances_convective_fraction = 1 - appliances_radiative_fraction - appliances_latent_fraction + + occupancy = Occupancy(occupancy_density, + None, + None, + None, + occupancy_schedule) + lighting = Lighting(lighting_density, + lighting_convective_fraction, + lighting_radiative_fraction, + lighting_latent_fraction, + lighting_schedule) + appliances = Appliances(appliances_density, + appliances_convective_fraction, + appliances_radiative_fraction, + appliances_latent_fraction, + appliance_schedule) + thermal_control = ThermalControl(None, + None, + None, + hvac_availability, + heating_schedule, + cooling_schedule) + hours_day = None + days_year = None + usages.append(Usage(usage_type, + hours_day, + days_year, + mechanical_air_change, + ventilation_rate, + occupancy, + lighting, + appliances, + thermal_control)) + return usages + + def names(self, category=None): + """ + Get the catalog elements names + :parm: optional category filter + """ + if category is None: + _names = {'archetypes': [], 'constructions': [], 'materials': [], 'windows': []} + for archetype in self._content.archetypes: + _names['archetypes'].append(archetype.name) + for construction in self._content.constructions: + _names['constructions'].append(construction.name) + for material in self._content.materials: + _names['materials'].append(material.name) + for window in self._content.windows: + _names['windows'].append(window.name) + else: + _names = {category: []} + if category.lower() == 'archetypes': + for archetype in self._content.archetypes: + _names[category].append(archetype.name) + elif category.lower() == 'constructions': + for construction in self._content.constructions: + _names[category].append(construction.name) + elif category.lower() == 'materials': + for material in self._content.materials: + _names[category].append(material.name) + elif category.lower() == 'windows': + for window in self._content.windows: + _names[category].append(window.name) + else: + raise ValueError(f'Unknown category [{category}]') + + def entries(self, category=None): + """ + Get the catalog elements + :parm: optional category filter + """ + if category is None: + return self._content + else: + if category.lower() == 'archetypes': + return self._content.archetypes + elif category.lower() == 'constructions': + return self._content.constructions + elif category.lower() == 'materials': + return self._content.materials + elif category.lower() == 'windows': + return self._content.windows + else: + raise ValueError(f'Unknown category [{category}]') + + def get_entry(self, name): + """ + Get one catalog element by names + :parm: entry name + """ + for entry in self._content.archetypes: + if entry.name.lower() == name.lower(): + return entry + for entry in self._content.constructions: + if entry.name.lower() == name.lower(): + return entry + for entry in self._content.materials: + if entry.name.lower() == name.lower(): + return entry + for entry in self._content.windows: + if entry.name.lower() == name.lower(): + return entry + raise IndexError(f"{name} doesn't exists in the catalog") diff --git a/hub/catalog_factories/energy_systems_catalog_factory.py b/hub/catalog_factories/energy_systems_catalog_factory.py new file mode 100644 index 00000000..0bfc9459 --- /dev/null +++ b/hub/catalog_factories/energy_systems_catalog_factory.py @@ -0,0 +1,42 @@ +""" +Usage catalog factory, publish the usage information +SPDX - License - Identifier: LGPL - 3.0 - or -later +Copyright © 2022 Concordia CERC group +Project Coder Pilar Monsalvete Álvarez de Uribarri pilar.monsalvete@concordia.ca +""" + +from pathlib import Path +from typing import TypeVar +from hub.catalog_factories.energy_systems.nrcan_catalog import NrcanCatalog +from hub.hub_logger import logger +from hub.helpers.utils import validate_import_export_type +Catalog = TypeVar('Catalog') + + +class UsageCatalogFactory: + def __init__(self, file_type, base_path=None): + if base_path is None: + base_path = Path(Path(__file__).parent.parent / 'data/energy_systems') + self._catalog_type = '_' + file_type.lower() + class_funcs = validate_import_export_type(UsageCatalogFactory) + if self._catalog_type not in class_funcs: + err_msg = f"Wrong import type. Valid functions include {class_funcs}" + logger.error(err_msg) + raise Exception(err_msg) + self._path = base_path + + @property + def _nrcan(self): + """ + Retrieve NRCAN catalog + """ + # nrcan retrieves the data directly from github + return NrcanCatalog(self._path) + + @property + def catalog(self) -> Catalog: + """ + Enrich the city given to the class using the class given handler + :return: Catalog + """ + return getattr(self, self._catalog_type, lambda: None) diff --git a/hub/catalog_factories/usage/comnet_catalog.py b/hub/catalog_factories/usage/comnet_catalog.py index 90a54314..2c267477 100644 --- a/hub/catalog_factories/usage/comnet_catalog.py +++ b/hub/catalog_factories/usage/comnet_catalog.py @@ -14,6 +14,7 @@ from hub.catalog_factories.data_models.usages.appliances import Appliances from hub.catalog_factories.data_models.usages.content import Content from hub.catalog_factories.data_models.usages.lighting import Lighting from hub.catalog_factories.data_models.usages.ocupancy import Occupancy +from hub.catalog_factories.data_models.usages.domestic_hot_water import DomesticHotWater from hub.catalog_factories.data_models.usages.schedule import Schedule from hub.catalog_factories.data_models.usages.thermal_control import ThermalControl from hub.catalog_factories.data_models.usages.usage import Usage @@ -51,6 +52,7 @@ class ComnetCatalog(Catalog): ventilation_rate = self._archetypes['ventilation rate'][comnet_usage] # convert cfm/ft2 to m3/m2.s ventilation_rate = ventilation_rate / (cte.METERS_TO_FEET * cte.MINUTES_TO_SECONDS) + domestic_hot_water_archetype = self._archetypes['water heating'][comnet_usage] # get occupancy occupancy_density = occupancy_archetype[0] / pow(cte.METERS_TO_FEET, 2) @@ -96,6 +98,15 @@ class ComnetCatalog(Catalog): self._schedules[schedule_name]['ClgSetPt'] ) + # get domestic hot water + density = domestic_hot_water_archetype + # convert Btu/h/occ to W/m2 + density = float(density) * cte.BTU_H_TO_WATTS * occupancy_density + domestic_hot_water_service_temperature = self._schedules[schedule_name]['WtrHtrSetPt'][0].values[0] + domestic_hot_water = DomesticHotWater(density, + domestic_hot_water_service_temperature, + self._schedules[schedule_name]['Service Hot Water'] + ) usages.append(Usage(comnet_usage, hours_day, days_year, @@ -104,7 +115,8 @@ class ComnetCatalog(Catalog): occupancy, lighting, appliances, - thermal_control)) + thermal_control, + domestic_hot_water)) self._content = Content(usages) @@ -136,7 +148,7 @@ class ComnetCatalog(Catalog): _schedule_values[day] = _extracted_data.iloc[start:end, 3:27].to_numpy().tolist()[0] _schedule = [] for day in _schedule_values: - if schedule_name == 'ClgSetPt' or schedule_name == 'HtgSetPt': + if schedule_name == 'ClgSetPt' or schedule_name == 'HtgSetPt' or schedule_name == 'WtrHtrSetPt': # to celsius if 'n.a.' in _schedule_values[day]: _schedule_values[day] = None diff --git a/hub/catalog_factories/usage/nrcan_catalog.py b/hub/catalog_factories/usage/nrcan_catalog.py index 6e12b682..a7e8dda9 100644 --- a/hub/catalog_factories/usage/nrcan_catalog.py +++ b/hub/catalog_factories/usage/nrcan_catalog.py @@ -16,6 +16,7 @@ from hub.catalog_factories.data_models.usages.appliances import Appliances from hub.catalog_factories.data_models.usages.content import Content from hub.catalog_factories.data_models.usages.lighting import Lighting from hub.catalog_factories.data_models.usages.ocupancy import Occupancy +from hub.catalog_factories.data_models.usages.domestic_hot_water import DomesticHotWater from hub.catalog_factories.data_models.usages.schedule import Schedule from hub.catalog_factories.data_models.usages.thermal_control import ThermalControl from hub.catalog_factories.data_models.usages.usage import Usage @@ -36,7 +37,7 @@ class NrcanCatalog(Catalog): @staticmethod def _extract_schedule(raw): nrcan_schedule_type = raw['category'] - if 'Heating' in raw['name']: + if 'Heating' in raw['name'] and 'Water' not in raw['name']: nrcan_schedule_type = f'{nrcan_schedule_type} Heating' elif 'Cooling' in raw['name']: nrcan_schedule_type = f'{nrcan_schedule_type} Cooling' @@ -45,8 +46,8 @@ class NrcanCatalog(Catalog): hub_type = UsageHelper().nrcan_schedule_type_to_hub_schedule_type[nrcan_schedule_type] data_type = UsageHelper().nrcan_data_type_to_hub_data_type[raw['units']] time_step = UsageHelper().nrcan_time_to_hub_time[raw['type']] - # nrcan only uses yearly range for the schedules - time_range = cte.YEAR + # nrcan only uses daily range for the schedules + time_range = cte.DAY day_types = UsageHelper().nrcan_day_type_to_hub_days[raw['day_types']] return Schedule(hub_type, raw['values'], data_type, time_step, time_range, day_types) @@ -91,12 +92,14 @@ class NrcanCatalog(Catalog): hvac_schedule_name = hvac_schedule_name.replace('FAN', 'Fan') heating_setpoint_schedule_name = space_type['heating_setpoint_schedule'] cooling_setpoint_schedule_name = space_type['cooling_setpoint_schedule'] + domestic_hot_water_schedule_name = space_type['service_water_heating_schedule'] occupancy_schedule = self._get_schedules(occupancy_schedule_name) lighting_schedule = self._get_schedules(lighting_schedule_name) appliance_schedule = self._get_schedules(appliance_schedule_name) heating_schedule = self._get_schedules(heating_setpoint_schedule_name) cooling_schedule = self._get_schedules(cooling_setpoint_schedule_name) hvac_availability = self._get_schedules(hvac_schedule_name) + domestic_hot_water_load_schedule = self._get_schedules(domestic_hot_water_schedule_name) occupancy_density = space_type['occupancy_per_area'] @@ -124,6 +127,15 @@ class NrcanCatalog(Catalog): if appliances_radiative_fraction is not None and appliances_latent_fraction is not None: appliances_convective_fraction = 1 - appliances_radiative_fraction - appliances_latent_fraction + # peak flow in m3/day/m2 + domestic_hot_water_peak_flow = space_type['service_water_heating_peak_flow_per_area'] + domestic_hot_water_service_temperature = space_type['service_water_heating_target_temperature'] + average_domestic_hot_water_inlet_temperature = 16.5 + # result in W/m2 + domestic_hot_water_density = domestic_hot_water_peak_flow / 24 / 3.6 * 4184 \ + * (domestic_hot_water_service_temperature - + average_domestic_hot_water_inlet_temperature) + occupancy = Occupancy(occupancy_density, None, None, @@ -145,6 +157,10 @@ class NrcanCatalog(Catalog): hvac_availability, heating_schedule, cooling_schedule) + domestic_hot_water = DomesticHotWater(domestic_hot_water_density, + domestic_hot_water_service_temperature, + domestic_hot_water_load_schedule) + hours_day = None days_year = None usages.append(Usage(usage_type, @@ -155,7 +171,8 @@ class NrcanCatalog(Catalog): occupancy, lighting, appliances, - thermal_control)) + thermal_control, + domestic_hot_water)) return usages def names(self, category=None): diff --git a/hub/catalog_factories/usage/usage_helper.py b/hub/catalog_factories/usage/usage_helper.py index e2886bc9..57d58d84 100644 --- a/hub/catalog_factories/usage/usage_helper.py +++ b/hub/catalog_factories/usage/usage_helper.py @@ -19,7 +19,8 @@ class UsageHelper: 'Equipment': cte.APPLIANCES, 'Thermostat Setpoint Cooling': cte.COOLING_SET_POINT, # Compose 'Thermostat Setpoint' + 'Cooling' 'Thermostat Setpoint Heating': cte.HEATING_SET_POINT, # Compose 'Thermostat Setpoint' + 'Heating' - 'Fan': cte.HVAC_AVAILABILITY + 'Fan': cte.HVAC_AVAILABILITY, + 'Service Water Heating': cte.DOMESTIC_HOT_WATER } _nrcan_data_type_to_hub_data_type = { 'FRACTION': cte.FRACTION, diff --git a/hub/city_model_structure/building_demand/domestic_hot_water.py b/hub/city_model_structure/building_demand/domestic_hot_water.py new file mode 100644 index 00000000..f48bf68a --- /dev/null +++ b/hub/city_model_structure/building_demand/domestic_hot_water.py @@ -0,0 +1,70 @@ +""" +Domestic Hot Water module +SPDX - License - Identifier: LGPL - 3.0 - or -later +Copyright © 2023 Concordia CERC group +Project Coder Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca +""" +from typing import Union, List +from hub.city_model_structure.attributes.schedule import Schedule + + +class DomesticHotWater: + """ + DomesticHotWater class + """ + def __init__(self): + self._density = None + self._service_temperature = None + self._schedules = None + + @property + def density(self) -> Union[None, float]: + """ + Get domestic hot water load density in Watts per m2 + :return: None or float + """ + return self._density + + @density.setter + def density(self, value): + """ + Set domestic hot water load density in Watts per m2 + :param value: float + """ + if value is not None: + self._density = float(value) + + @property + def service_temperature(self) -> Union[None, float]: + """ + Get service temperature in degrees Celsius + :return: None or float + """ + return self._service_temperature + + @service_temperature.setter + def service_temperature(self, value): + """ + Set service temperature in degrees Celsius + :param value: float + """ + if value is not None: + self._service_temperature = float(value) + + @property + def schedules(self) -> Union[None, List[Schedule]]: + """ + Get schedules + dataType = fraction + :return: None or [Schedule] + """ + return self._schedules + + @schedules.setter + def schedules(self, value): + """ + Set schedules + dataType = fraction + :param value: [Schedule] + """ + self._schedules = value diff --git a/hub/city_model_structure/building_demand/thermal_zone.py b/hub/city_model_structure/building_demand/thermal_zone.py index beb26045..667c0631 100644 --- a/hub/city_model_structure/building_demand/thermal_zone.py +++ b/hub/city_model_structure/building_demand/thermal_zone.py @@ -15,6 +15,7 @@ from hub.city_model_structure.building_demand.appliances import Appliances from hub.city_model_structure.building_demand.lighting import Lighting from hub.city_model_structure.building_demand.internal_gain import InternalGain from hub.city_model_structure.building_demand.thermal_control import ThermalControl +from hub.city_model_structure.building_demand.domestic_hot_water import DomesticHotWater from hub.city_model_structure.attributes.schedule import Schedule import hub.helpers.constants as cte @@ -53,6 +54,7 @@ class ThermalZone: self._appliances = None self._internal_gains = None self._thermal_control = None + self._domestic_hot_water = None self._usages = None @property @@ -590,6 +592,43 @@ class ThermalZone: return self._thermal_control + @property + def domestic_hot_water(self) -> Union[None, DomesticHotWater]: + """ + Get domestic hot water information of this thermal zone + :return: None or DomesticHotWater + """ + self._domestic_hot_water = DomesticHotWater() + _mean_peak_density_load = 0 + _mean_service_temperature = 0 + for usage in self.usages: + _mean_peak_density_load += usage.percentage * usage.domestic_hot_water.density + _mean_service_temperature += usage.percentage * usage.domestic_hot_water.service_temperature + self._domestic_hot_water.density = _mean_peak_density_load + self._domestic_hot_water.service_temperature = _mean_service_temperature + + _domestic_hot_water_reference = self.usages[0].domestic_hot_water + if _domestic_hot_water_reference.schedules is not None: + _schedules = [] + for i_schedule in range(0, len(_domestic_hot_water_reference.schedules)): + schedule = Schedule() + schedule.type = _domestic_hot_water_reference.schedules[i_schedule].type + schedule.day_types = _domestic_hot_water_reference.schedules[i_schedule].day_types + schedule.data_type = _domestic_hot_water_reference.schedules[i_schedule].data_type + schedule.time_step = _domestic_hot_water_reference.schedules[i_schedule].time_step + schedule.time_range = _domestic_hot_water_reference.schedules[i_schedule].time_range + + new_values = [] + for i_value in range(0, len(_domestic_hot_water_reference.schedules[i_schedule].values)): + _new_value = 0 + for usage in self.usages: + _new_value += usage.percentage * usage.domestic_hot_water.schedules[i_schedule].values[i_value] + new_values.append(_new_value) + schedule.values = new_values + _schedules.append(schedule) + self._domestic_hot_water.schedules = _schedules + return self._domestic_hot_water + @property def total_floor_area(self): """ diff --git a/hub/city_model_structure/building_demand/usage.py b/hub/city_model_structure/building_demand/usage.py index 36240038..c3eea9fe 100644 --- a/hub/city_model_structure/building_demand/usage.py +++ b/hub/city_model_structure/building_demand/usage.py @@ -12,6 +12,7 @@ from hub.city_model_structure.building_demand.occupancy import Occupancy from hub.city_model_structure.building_demand.lighting import Lighting from hub.city_model_structure.building_demand.appliances import Appliances from hub.city_model_structure.building_demand.thermal_control import ThermalControl +from hub.city_model_structure.building_demand.domestic_hot_water import DomesticHotWater from hub.city_model_structure.building_demand.internal_gain import InternalGain @@ -31,6 +32,7 @@ class Usage: self._lighting = None self._appliances = None self._thermal_control = None + self._domestic_hot_water = None @property def id(self): @@ -236,7 +238,7 @@ class Usage: @property def thermal_control(self) -> Union[None, ThermalControl]: """ - Get thermal control of this thermal zone + Get thermal control information :return: None or ThermalControl """ return self._thermal_control @@ -244,7 +246,23 @@ class Usage: @thermal_control.setter def thermal_control(self, value): """ - Set thermal control for this thermal zone + Set thermal control information :param value: ThermalControl """ self._thermal_control = value + + @property + def domestic_hot_water(self) -> Union[None, DomesticHotWater]: + """ + Get domestic hot water information + :return: None or ThermalControl + """ + return self._domestic_hot_water + + @domestic_hot_water.setter + def domestic_hot_water(self, value): + """ + Set domestic hot water information + :return: None or ThermalControl + """ + self._domestic_hot_water = value diff --git a/hub/data/energy_systems/nrcan.xml b/hub/data/energy_systems/nrcan.xml new file mode 100644 index 00000000..d9843ae5 --- /dev/null +++ b/hub/data/energy_systems/nrcan.xml @@ -0,0 +1,11 @@ + + + boiler_set.json + chiller_set.json + curves.json + furnace_set.json + heat_pumps.json + shw_set.json + pv.json + unitary_acs.json + diff --git a/hub/helpers/constants.py b/hub/helpers/constants.py index 4792130f..8c601f5c 100644 --- a/hub/helpers/constants.py +++ b/hub/helpers/constants.py @@ -137,6 +137,7 @@ HEATING_SET_POINT = 'HtgSetPt' EQUIPMENT = 'Equipment' ACTIVITY = 'Activity' PEOPLE_ACTIVITY_LEVEL = 'People Activity Level' +DOMESTIC_HOT_WATER = 'Domestic Hot Water' # Geometry EPSILON = 0.0000001 diff --git a/hub/imports/usage/comnet_usage_parameters.py b/hub/imports/usage/comnet_usage_parameters.py index 6dcab450..2747c1af 100644 --- a/hub/imports/usage/comnet_usage_parameters.py +++ b/hub/imports/usage/comnet_usage_parameters.py @@ -15,6 +15,7 @@ from hub.city_model_structure.building_demand.lighting import Lighting from hub.city_model_structure.building_demand.occupancy import Occupancy from hub.city_model_structure.building_demand.appliances import Appliances from hub.city_model_structure.building_demand.thermal_control import ThermalControl +from hub.city_model_structure.building_demand.domestic_hot_water import DomesticHotWater from hub.city_model_structure.attributes.schedule import Schedule from hub.city_model_structure.building_demand.internal_gain import InternalGain from hub.catalog_factories.usage_catalog_factory import UsageCatalogFactory @@ -101,6 +102,11 @@ class ComnetUsageParameters: _control.heating_set_point_schedules = archetype.thermal_control.heating_set_point_schedules _control.hvac_availability_schedules = archetype.thermal_control.hvac_availability_schedules usage.thermal_control = _control + _domestic_hot_water = DomesticHotWater() + _domestic_hot_water.density = archetype.domestic_hot_water.density + _domestic_hot_water.service_temperature = archetype.domestic_hot_water.service_temperature + _domestic_hot_water.schedules = archetype.domestic_hot_water.schedules + usage.domestic_hot_water = _domestic_hot_water @staticmethod def _calculate_reduced_values_from_extended_library(usage, archetype): diff --git a/hub/imports/usage/nrcan_usage_parameters.py b/hub/imports/usage/nrcan_usage_parameters.py index 68d8a2bf..bc80487b 100644 --- a/hub/imports/usage/nrcan_usage_parameters.py +++ b/hub/imports/usage/nrcan_usage_parameters.py @@ -14,6 +14,7 @@ from hub.city_model_structure.building_demand.lighting import Lighting from hub.city_model_structure.building_demand.occupancy import Occupancy from hub.city_model_structure.building_demand.appliances import Appliances from hub.city_model_structure.building_demand.thermal_control import ThermalControl +from hub.city_model_structure.building_demand.domestic_hot_water import DomesticHotWater from hub.catalog_factories.usage_catalog_factory import UsageCatalogFactory @@ -111,6 +112,11 @@ class NrcanUsageParameters: _control.heating_set_point_schedules = archetype.thermal_control.heating_set_point_schedules _control.hvac_availability_schedules = archetype.thermal_control.hvac_availability_schedules usage.thermal_control = _control + _domestic_hot_water = DomesticHotWater() + _domestic_hot_water.density = archetype.domestic_hot_water.density + _domestic_hot_water.service_temperature = archetype.domestic_hot_water.service_temperature + _domestic_hot_water.schedules = archetype.domestic_hot_water.schedules + usage.domestic_hot_water = _domestic_hot_water @staticmethod def _assign_comnet_extra_values(usage, archetype): diff --git a/hub/unittests/test_usage_factory.py b/hub/unittests/test_usage_factory.py index 4cefef8c..6ff43b60 100644 --- a/hub/unittests/test_usage_factory.py +++ b/hub/unittests/test_usage_factory.py @@ -124,3 +124,7 @@ class TestUsageFactory(TestCase): self.assertIsNotNone(appliances.schedules, 'appliances schedule is none') self.assertIsNotNone(usage.thermal_control.hvac_availability_schedules, 'control hvac availability is none') + self.assertIsNotNone(usage.domestic_hot_water.density, 'domestic hot water density is none') + self.assertIsNotNone(usage.domestic_hot_water.service_temperature, + 'domestic hot water service temperature is none') + self.assertIsNotNone(usage.domestic_hot_water.schedules, 'domestic hot water schedules is none') -- 2.39.2 From 34477c6d8f09b3b78a9aa33e2f620d84fc43e5d6 Mon Sep 17 00:00:00 2001 From: Pilar Date: Mon, 13 Mar 2023 18:23:35 -0400 Subject: [PATCH 02/13] updating costs catalog -> not completed --- .../cost/montreal_custom_catalog.py | 3 +- .../data_models/cost/shell.py | 64 +++++ hub/data/costs/montreal_costs.xml | 233 +++++++++++++----- 3 files changed, 243 insertions(+), 57 deletions(-) create mode 100644 hub/catalog_factories/data_models/cost/shell.py diff --git a/hub/catalog_factories/cost/montreal_custom_catalog.py b/hub/catalog_factories/cost/montreal_custom_catalog.py index d4cf0c10..e86fd3cd 100644 --- a/hub/catalog_factories/cost/montreal_custom_catalog.py +++ b/hub/catalog_factories/cost/montreal_custom_catalog.py @@ -2,8 +2,7 @@ Cost catalog SPDX - License - Identifier: LGPL - 3.0 - or -later Copyright © 2022 Concordia CERC group -Project Coder Atiya atiya.atiya@mail.concordia.ca -Code contributors: Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca +Project Coder Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca """ import xmltodict diff --git a/hub/catalog_factories/data_models/cost/shell.py b/hub/catalog_factories/data_models/cost/shell.py new file mode 100644 index 00000000..d0465fd2 --- /dev/null +++ b/hub/catalog_factories/data_models/cost/shell.py @@ -0,0 +1,64 @@ +""" +Shell costs from Cost catalog +SPDX - License - Identifier: LGPL - 3.0 - or -later +Copyright © 2023 Concordia CERC group +Project Coder Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca +""" + + +class Shell: + def __init__(self, refurbishment_basement, envelope, roofing): + self._opaque_reposition = opaque_reposition + self._opaque_initial_investment = opaque_initial_investment + self._opaque_lifetime = opaque_lifetime + self._transparent_reposition = transparent_reposition + self._transparent_initial_investment = transparent_initial_investment + self._transparent_lifetime = transparent_lifetime + + @property + def opaque_reposition(self): + """ + Get reposition costs for opaque envelope per area of external opaque surfaces in currency/m2 + :return: float + """ + return self._opaque_reposition + + @property + def opaque_initial_investment(self): + """ + Get initial investment for opaque envelope per area of external opaque surfaces in currency/m2 + :return: float + """ + return self._opaque_initial_investment + + @property + def opaque_lifetime(self): + """ + Get lifetime of opaque envelope in years + :return: float + """ + return self._opaque_lifetime + + @property + def transparent_reposition(self): + """ + Get reposition costs for transparent envelope per area of windows in currency/m2 + :return: float + """ + return self._transparent_reposition + + @property + def transparent_initial_investment(self): + """ + Get initial investment for transparent envelope per area of windows in currency/m2 + :return: float + """ + return self._transparent_initial_investment + + @property + def transparent_lifetime(self): + """ + Get lifetime of transparent envelope in years + :return: float + """ + return self._transparent_lifetime diff --git a/hub/data/costs/montreal_costs.xml b/hub/data/costs/montreal_costs.xml index 8cb0f0da..5853cbf1 100644 --- a/hub/data/costs/montreal_costs.xml +++ b/hub/data/costs/montreal_costs.xml @@ -1,71 +1,196 @@ - 56 - 9.8 - - - 43.4 - 36 - 50 - - - 78 - 984.5 - 20 - - - - - - 363.5 - 363.5 + + + 0 + + + + 304 + + + 857.14 + + + + + 118 + + + + + + + + 800 + 800 + 25 + + + + 622.86 + 622.86 + 25 + + + 622.86 + 622.86 15 - - - 363.5 - 363.5 + + + 0 + 0 15 - - - 363.5 - 363.5 + + + 47.62 + 47.62 15 - - - - 17 - 17 - 15 - - - 365 - 365 - 15 - - - 365 - 365 - 15 - - - 88 - 2.5 + + + + + 139 + 139 + 20 + + + + + 2.5 + 14 + - 0 - 0 + 12.27 + 0 - 5.6 + 0.075 + + + + 17.71 + + 0.640 + + + 1.2 + + + 0.09 + + + 40 + 40 + 1 + + 30 + + 6.3 + + + 2 + 1.5 + 3.6 + + + 0 + + + 2 + + + + + + + + 0 + + + + 304 + + + 857.14 + + + + + 118 + + + + + + + + 800 + 800 + 25 + + + + 622.86 + 622.86 + 25 + + + 622.86 + 622.86 + 15 + + + 0 + 0 + 15 + + + 47.62 + 47.62 + 15 + + + + + 139 + 139 + 20 + + + + + 6 + 14 + + + + + + 12.27 + 0 + + 0.075 + + + + 17.71 + + 0.640 + + + 1.2 + + + 0.09 40 40 - 0.05 1 - 4.6 30 @@ -78,12 +203,10 @@ 0 - 0 2 - 0 - + \ No newline at end of file -- 2.39.2 From 64384876a4087556221dd814ae46fb70c1800e48 Mon Sep 17 00:00:00 2001 From: Pilar Date: Thu, 16 Mar 2023 10:06:32 -0400 Subject: [PATCH 03/13] updating costs catalog -> not completed --- .../cost/montreal_custom_catalog.py | 175 ++++++++++-------- .../data_models/cost/archetype.py | 36 +++- .../data_models/cost/capital_cost.py | 74 +++----- .../data_models/cost/chapter.py | 32 ++++ .../data_models/cost/cost_helper.py | 54 ++++++ .../data_models/cost/envelope.py | 66 ------- .../data_models/cost/fuel.py | 54 ++++++ .../data_models/cost/hvac.py | 96 ---------- .../data_models/cost/income.py | 74 ++++---- .../data_models/cost/item_description.py | 68 +++++++ .../data_models/cost/operational_cost.py | 102 +++------- .../data_models/cost/shell.py | 64 ------- .../data_models/cost/systems.py | 106 ----------- hub/data/costs/montreal_costs.xml | 100 +++++----- hub/helpers/constants.py | 22 +++ .../data/hft_function_to_hub_function.py | 30 +-- hub/helpers/dictionaries.py | 1 + hub/unittests/test_exports.py | 2 - 18 files changed, 504 insertions(+), 652 deletions(-) create mode 100644 hub/catalog_factories/data_models/cost/chapter.py create mode 100644 hub/catalog_factories/data_models/cost/cost_helper.py delete mode 100644 hub/catalog_factories/data_models/cost/envelope.py create mode 100644 hub/catalog_factories/data_models/cost/fuel.py delete mode 100644 hub/catalog_factories/data_models/cost/hvac.py create mode 100644 hub/catalog_factories/data_models/cost/item_description.py delete mode 100644 hub/catalog_factories/data_models/cost/shell.py delete mode 100644 hub/catalog_factories/data_models/cost/systems.py diff --git a/hub/catalog_factories/cost/montreal_custom_catalog.py b/hub/catalog_factories/cost/montreal_custom_catalog.py index e86fd3cd..a5a921ab 100644 --- a/hub/catalog_factories/cost/montreal_custom_catalog.py +++ b/hub/catalog_factories/cost/montreal_custom_catalog.py @@ -1,20 +1,21 @@ """ Cost catalog SPDX - License - Identifier: LGPL - 3.0 - or -later -Copyright © 2022 Concordia CERC group +Copyright © 2023 Concordia CERC group Project Coder Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca """ import xmltodict from hub.catalog_factories.catalog import Catalog -from hub.catalog_factories.data_models.cost.capital_cost import CapitalCost -from hub.catalog_factories.data_models.cost.envelope import Envelope -from hub.catalog_factories.data_models.cost.systems import Systems -from hub.catalog_factories.data_models.cost.hvac import Hvac -from hub.catalog_factories.data_models.cost.operational_cost import OperationalCost -from hub.catalog_factories.data_models.cost.income import Income from hub.catalog_factories.data_models.cost.archetype import Archetype from hub.catalog_factories.data_models.cost.content import Content +from hub.catalog_factories.data_models.cost.capital_cost import CapitalCost +from hub.catalog_factories.data_models.cost.chapter import Chapter +from hub.catalog_factories.data_models.cost.item_description import ItemDescription +from hub.catalog_factories.data_models.cost.operational_cost import OperationalCost +from hub.catalog_factories.data_models.cost.fuel import Fuel +from hub.catalog_factories.data_models.cost.income import Income +from hub.catalog_factories.data_models.cost.cost_helper import CostHelper class MontrealCustomCatalog(Catalog): @@ -27,90 +28,93 @@ class MontrealCustomCatalog(Catalog): self._content = Content(self._load_archetypes()) @staticmethod - def _get_threesome(entry): - _reposition = float(entry['reposition']['#text']) - _investment = float(entry['initial_investment']['#text']) - _lifetime = float(entry['lifetime_equipment']['#text']) - return _reposition, _investment, _lifetime + def _item_with_threesome(entry, item_type): + _reposition = float(entry[item_type]['reposition']['#text']) + _reposition_unit = entry[item_type]['reposition']['@cost_unit'] + _investment = float(entry[item_type]['investment_cost']['#text']) + _investment_unit = entry[item_type]['investment_cost']['@cost_unit'] + _lifetime = float(entry[item_type]['lifetime_equipment']['#text']) + _item_description = ItemDescription(item_type, + initial_investment=_investment, + initial_investment_units=_investment_unit, + reposition=_reposition, + reposition_units=_reposition_unit, + lifetime=_lifetime) + return _item_description + + @staticmethod + def _item_with_refurbishment_values(entry, item_type): + _refurbishment = float(entry[item_type]['refurbishment_cost']['#text']) + _refurbishment_unit = entry[item_type]['refurbishment_cost']['@cost_unit'] + _item_description = ItemDescription(item_type, + refurbishment=_refurbishment, + refurbishment_units=_refurbishment_unit) + return _item_description def _get_capital_costs(self, entry): - structural = float(entry['structural']['#text']) - sub_structural = float(entry['sub_structural']['#text']) - surface_finish = float(entry['surface_finish']['#text']) - engineer = float(entry['engineer']['#text']) - opaque_reposition, opaque_initial_investment, opaque_lifetime = \ - self._get_threesome(entry['envelope']['opaque']) - transparent_reposition, transparent_initial_investment, transparent_lifetime = \ - self._get_threesome(entry['envelope']['transparent']) - envelope = Envelope(opaque_reposition, - opaque_initial_investment, - opaque_lifetime, - transparent_reposition, - transparent_initial_investment, - transparent_lifetime) - heating_equipment_reposition, heating_equipment_initial_investment, heating_equipment_lifetime = \ - self._get_threesome(entry['systems']['hvac']['heating_equipment_cost']) - heating_equipment_reposition = heating_equipment_reposition / 1000 - heating_equipment_initial_investment = heating_equipment_initial_investment / 1000 - cooling_equipment_reposition, cooling_equipment_initial_investment, cooling_equipment_lifetime = \ - self._get_threesome(entry['systems']['hvac']['cooling_equipment_cost']) - cooling_equipment_reposition = cooling_equipment_reposition / 1000 - cooling_equipment_initial_investment = cooling_equipment_initial_investment / 1000 - general_hvac_equipment_reposition, general_hvac_equipment_initial_investment, general_hvac_equipment_lifetime = \ - self._get_threesome(entry['systems']['hvac']['general_hvac_equipment_cost']) - general_hvac_equipment_reposition = general_hvac_equipment_reposition * 3600 - general_hvac_equipment_initial_investment = general_hvac_equipment_initial_investment * 3600 - hvac = Hvac(heating_equipment_reposition, heating_equipment_initial_investment, heating_equipment_lifetime, - cooling_equipment_reposition, cooling_equipment_initial_investment, cooling_equipment_lifetime, - general_hvac_equipment_reposition, general_hvac_equipment_initial_investment, - general_hvac_equipment_lifetime) + general_chapters = [] + chapters_titles = CostHelper().chapters_in_lod1 + items_list = [] + item_type = 'B10_superstructure' + item_description = self._item_with_refurbishment_values(entry, item_type) + items_list.append(item_description) + for item in entry['B20_envelope']: + item_type = item + item_description = self._item_with_refurbishment_values(entry['B20_envelope'], item_type) + items_list.append(item_description) + item_type = 'B30_roofing' + item_description = self._item_with_refurbishment_values(entry, item_type) + items_list.append(item_description) + general_chapters.append(Chapter('B_shell', items_list)) - photovoltaic_system_reposition, photovoltaic_system_initial_investment, photovoltaic_system_lifetime = \ - self._get_threesome(entry['systems']['photovoltaic_system']) - other_conditioning_systems_reposition, other_conditioning_systems_initial_investment, \ - other_conditioning_systems_lifetime = self._get_threesome(entry['systems']['other_systems']) - lighting_reposition, lighting_initial_investment, lighting_lifetime = \ - self._get_threesome(entry['systems']['lighting']) - systems = Systems(hvac, - photovoltaic_system_reposition, - photovoltaic_system_initial_investment, - photovoltaic_system_lifetime, - other_conditioning_systems_reposition, - other_conditioning_systems_initial_investment, - other_conditioning_systems_lifetime, - lighting_reposition, - lighting_initial_investment, - lighting_lifetime) - _capital_cost = CapitalCost(structural, - sub_structural, - envelope, - systems, - surface_finish, - engineer) + items_list = [] + item_type = 'D301010_photovoltaic_system' + item_description = self._item_with_threesome(entry['D30_hvac']['D3010_energy_supply'], item_type) + items_list.append(item_description) + item_type_list = ['D3020_heat_generating_systems', 'D3030_cooling_generation_systems', 'D3040_distribution_systems', + 'D3080_other_hvac_ahu'] + for item_type in item_type_list: + item_description = self._item_with_threesome(entry['D30_hvac'], item_type) + items_list.append(item_description) + item_type = 'D5020lighting_and_branch_wiring' + item_description = self._item_with_threesome(entry['D50_electrical'], item_type) + items_list.append(item_description) + general_chapters.append(Chapter('D_services', items_list)) + + design_allowance = float(entry['Z_allowances_overhead_profit']['Z10_design_allowance']['#text']) + overhead_and_profit = float(entry['Z_allowances_overhead_profit']['Z10_overhead_and_profit']['#text']) + _capital_cost = CapitalCost(general_chapters, design_allowance, overhead_and_profit) return _capital_cost @staticmethod def _get_operational_costs(entry): - fuel_type = entry['fuel']['@fuel_type'] - fuel_fixed_operational_monthly = float(entry['fuel']['fixed']['fixed_monthly']['#text']) - fuel_fixed_operational_peak = float(entry['fuel']['fixed']['fixed_power']['#text']) / 1000 - fuel_variable_operational = float(entry['fuel']['variable']['#text']) / 1000 / 3600 + fuels = [] + for item in entry['fuels']: + fuel_type = item['fuel']['@fuel_type'] + fuel_variable = float(entry['fuel']['variable']['#text']) + fuel_variable_units = float(entry['fuel']['variable']['@cost_unit']) + fuel_fixed_monthly = None + fuel_fixed_peak = None + if fuel_type == 'electricity': + fuel_fixed_monthly = float(entry['fuel']['fixed']['fixed_monthly']['#text']) + fuel_fixed_peak = float(entry['fuel']['fixed']['fixed_power']['#text']) / 1000 + elif fuel_type == 'gas': + fuel_fixed_monthly = float(entry['fuel']['fixed']['fixed_monthly']['#text']) + fuel = Fuel(fuel_type, + fixed_monthly=fuel_fixed_monthly, + fixed_power=fuel_fixed_peak, + variable=fuel_variable, + variable_units=fuel_variable_units) + fuels.append(fuel) heating_equipment_maintenance = float(entry['maintenance']['heating_equipment']['#text']) / 1000 cooling_equipment_maintenance = float(entry['maintenance']['cooling_equipment']['#text']) / 1000 - general_hvac_equipment_maintenance = float(entry['maintenance']['general_hvac_equipment']['#text']) * 3600 photovoltaic_system_maintenance = float(entry['maintenance']['photovoltaic_system']['#text']) - other_systems_maintenance = float(entry['maintenance']['other_systems']['#text']) co2_emissions = float(entry['CO2_cost']['#text']) - _operational_cost = OperationalCost(fuel_type, - fuel_fixed_operational_monthly, - fuel_fixed_operational_peak, - fuel_variable_operational, + _operational_cost = OperationalCost(fuels, heating_equipment_maintenance, cooling_equipment_maintenance, - general_hvac_equipment_maintenance, photovoltaic_system_maintenance, - other_systems_maintenance, co2_emissions) return _operational_cost @@ -120,7 +124,10 @@ class MontrealCustomCatalog(Catalog): for archetype in archetypes: function = archetype['@function'] municipality = archetype['@municipality'] - currency = archetype['@currency'] + country = archetype['@country'] + lod = float(archetype['@lod']) + currency = archetype['currency'] + print(currency) capital_cost = self._get_capital_costs(archetype['capital_cost']) operational_cost = self._get_operational_costs(archetype['operational_cost']) end_of_life_cost = float(archetype['end_of_life_cost']['#text']) @@ -128,17 +135,21 @@ class MontrealCustomCatalog(Catalog): hvac = float(archetype['incomes']['subsidies']['hvac_subsidy']['#text']) photovoltaic_system = float(archetype['incomes']['subsidies']['photovoltaic_subsidy']['#text']) electricity_exports = float(archetype['incomes']['energy_exports']['electricity']['#text']) / 1000 / 3600 - heat_exports = float(archetype['incomes']['energy_exports']['heat']['#text']) / 1000 / 3600 - co2 = float(archetype['incomes']['CO2_income']['#text']) - income = Income(construction, hvac, photovoltaic_system, electricity_exports, heat_exports, co2) - _catalog_archetypes.append(Archetype(function, + reduction_tax = float(archetype['incomes']['tax_reduction']['#text']) + income = Income(construction_subsidy=construction, + hvac_subsidy=hvac, + photovoltaic_subsidy=photovoltaic_system, + electricity_export=electricity_exports, + reductions_taxes=reduction_tax) + _catalog_archetypes.append(Archetype(lod, + function, municipality, + country, currency, capital_cost, operational_cost, end_of_life_cost, income)) - return _catalog_archetypes def names(self, category=None): diff --git a/hub/catalog_factories/data_models/cost/archetype.py b/hub/catalog_factories/data_models/cost/archetype.py index 69ba2654..cf24c6ee 100644 --- a/hub/catalog_factories/data_models/cost/archetype.py +++ b/hub/catalog_factories/data_models/cost/archetype.py @@ -1,8 +1,8 @@ """ Archetype catalog Cost SPDX - License - Identifier: LGPL - 3.0 - or -later -Copyright © 2022 Concordia CERC group -Project Coder Atiya atiya.atiya@mail.concordia.ca +Copyright © 2023 Concordia CERC group +Project Coder Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca """ from hub.catalog_factories.data_models.cost.capital_cost import CapitalCost @@ -11,9 +11,21 @@ from hub.catalog_factories.data_models.cost.income import Income class Archetype: - def __init__(self, function, municipality, currency, capital_cost, operational_cost, end_of_life_cost, income): + def __init__(self, + lod, + function, + municipality, + country, + currency, + capital_cost, + operational_cost, + end_of_life_cost, + income): + + self._lod = lod self._function = function self._municipality = municipality + self._country = country self._currency = currency self._capital_cost = capital_cost self._operational_cost = operational_cost @@ -26,7 +38,15 @@ class Archetype: Get name :return: string """ - return f'{self._municipality}_{self._function}' + return f'{self._country}_{self._municipality}_{self._function}_{self._lod}' + + @property + def lod(self): + """ + Get level of detail of the catalog + :return: string + """ + return self._lod @property def function(self): @@ -44,6 +64,14 @@ class Archetype: """ return self._municipality + @property + def country(self): + """ + Get country + :return: string + """ + return self._country + @property def currency(self): """ diff --git a/hub/catalog_factories/data_models/cost/capital_cost.py b/hub/catalog_factories/data_models/cost/capital_cost.py index 6d5f2504..233043ac 100644 --- a/hub/catalog_factories/data_models/cost/capital_cost.py +++ b/hub/catalog_factories/data_models/cost/capital_cost.py @@ -1,68 +1,40 @@ """ -Cost catalog CapitalCost +Capital costs included in the catalog SPDX - License - Identifier: LGPL - 3.0 - or -later -Copyright © 2022 Concordia CERC group -Project Coder Atiya atiya.atiya@mail.concordia.ca -Code contributors: Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca +Copyright © 2023 Concordia CERC group +Project Coder Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca """ -from hub.catalog_factories.data_models.cost.envelope import Envelope -from hub.catalog_factories.data_models.cost.systems import Systems +from typing import List +from hub.catalog_factories.data_models.cost.chapter import Chapter class CapitalCost: - def __init__(self, structural, sub_structural, envelope, systems, surface_finish, engineer): - self._structural = structural - self._sub_structural = sub_structural - self._envelope = envelope - self._systems = systems - self._surface_finish = surface_finish - self._engineer = engineer + def __init__(self, general_chapters, design_allowance, overhead_and_profit): + self._general_chapters = general_chapters + self._design_allowance = design_allowance + self._overhead_and_profit = overhead_and_profit @property - def structural(self): + def general_chapters(self) -> List[Chapter]: """ - Get structural cost per building volume in currency/m3 + Get general chapters in capital costs + :return: [Chapter] + """ + return self._general_chapters + + @property + def design_allowance(self): + """ + Get design allowance in percentage :return: float """ - return self._structural + return self._design_allowance @property - def sub_structural(self): + def overhead_and_profit(self): """ - Get sub structural cost per building foot-print in currency/m2 + Get overhead profit in percentage :return: float """ - return self._sub_structural - - @property - def envelope(self) -> Envelope: - """ - Get envelope cost - :return: Envelope - """ - return self._envelope - - @property - def systems(self) -> Systems: - """ - Get systems cost - :return: Systems - """ - return self._systems - - @property - def surface_finish(self): - """ - Get surface finish cost per external surfaces areas in currency/m2 - :return: float - """ - return self._surface_finish - - @property - def engineer(self): - """ - Get engineer cost in % - :return: float - """ - return self._engineer + return self._overhead_and_profit diff --git a/hub/catalog_factories/data_models/cost/chapter.py b/hub/catalog_factories/data_models/cost/chapter.py new file mode 100644 index 00000000..d1be68ac --- /dev/null +++ b/hub/catalog_factories/data_models/cost/chapter.py @@ -0,0 +1,32 @@ +""" +Cost chapter description +SPDX - License - Identifier: LGPL - 3.0 - or -later +Copyright © 2023 Concordia CERC group +Project Coder Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca +""" + +from typing import List +from hub.catalog_factories.data_models.cost.item_description import ItemDescription + + +class Chapter: + def __init__(self, chapter_type, items_list): + + self._chapter_type = chapter_type + self._items_list = items_list + + @property + def chapter_type(self): + """ + Get chapter type + :return: str + """ + return self._chapter_type + + @property + def items_list(self) -> List[ItemDescription]: + """ + Get list of items contained in the chapter + :return: [str] + """ + return self._items_list diff --git a/hub/catalog_factories/data_models/cost/cost_helper.py b/hub/catalog_factories/data_models/cost/cost_helper.py new file mode 100644 index 00000000..c51ab02b --- /dev/null +++ b/hub/catalog_factories/data_models/cost/cost_helper.py @@ -0,0 +1,54 @@ +""" +Cost helper +SPDX - License - Identifier: LGPL - 3.0 - or -later +Copyright © 2023 Concordia CERC group +Project Coder Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca +""" + +import hub.helpers.constants as cte +from typing import Dict + + +class CostHelper: + """ + Cost helper class + """ + _costs_units = { + 'currency/m2': cte.CURRENCY_PER_SQM, + 'currency/m3': cte.CURRENCY_PER_CBM, + 'currency/kW': cte.CURRENCY_PER_KW, + 'currency/kWh': cte.CURRENCY_PER_KWH, + 'currency/month': cte.CURRENCY_PER_MONTH, + 'currency/l': cte.CURRENCY_PER_LITRE, + 'currency/kg': cte.CURRENCY_PER_KG, + 'currency/(m3/h)': cte.CURRENCY_PER_CBM_PER_HOUR, + '%': cte.PERCENTAGE + } + + _chapters_in_lod1 = { + 'B10_superstructure': cte.SUPERSTRUCTURE, + 'B20_envelope': cte.ENVELOPE, + 'B30_roofing': cte.ROOFING, + 'D3010_energy_supply': cte.ENERGY_SUPPLY, + 'D3020_heat_generating_systems': cte.HEAT_GENERATION, + 'D3030_cooling_generation_systems': cte.COOL_GENERATION, + 'D3040_distribution_systems': cte.DISTRIBUTION, + 'D3080_other_hvac_ahu': cte.OTHER_SYSTEMS, + 'D5020lighting_and_branch_wiring': cte.LIGHTING_AND_WIRING + } + + @property + def costs_units(self) -> Dict: + """ + List of supported costs units + :return: dict + """ + return self._costs_units + + @property + def chapters_in_lod1(self) -> Dict: + """ + List of chapters included in lod 1 + :return: dict + """ + return self._chapters_in_lod1 diff --git a/hub/catalog_factories/data_models/cost/envelope.py b/hub/catalog_factories/data_models/cost/envelope.py deleted file mode 100644 index c2d2d419..00000000 --- a/hub/catalog_factories/data_models/cost/envelope.py +++ /dev/null @@ -1,66 +0,0 @@ -""" -Envelope costs from Cost catalog -SPDX - License - Identifier: LGPL - 3.0 - or -later -Copyright © 2022 Concordia CERC group -Project Coder Atiya atiya.atiya@mail.concordia.ca -Code contributors: Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca -""" - - -class Envelope: - def __init__(self, opaque_reposition, opaque_initial_investment, opaque_lifetime, - transparent_reposition, transparent_initial_investment, transparent_lifetime): - self._opaque_reposition = opaque_reposition - self._opaque_initial_investment = opaque_initial_investment - self._opaque_lifetime = opaque_lifetime - self._transparent_reposition = transparent_reposition - self._transparent_initial_investment = transparent_initial_investment - self._transparent_lifetime = transparent_lifetime - - @property - def opaque_reposition(self): - """ - Get reposition costs for opaque envelope per area of external opaque surfaces in currency/m2 - :return: float - """ - return self._opaque_reposition - - @property - def opaque_initial_investment(self): - """ - Get initial investment for opaque envelope per area of external opaque surfaces in currency/m2 - :return: float - """ - return self._opaque_initial_investment - - @property - def opaque_lifetime(self): - """ - Get lifetime of opaque envelope in years - :return: float - """ - return self._opaque_lifetime - - @property - def transparent_reposition(self): - """ - Get reposition costs for transparent envelope per area of windows in currency/m2 - :return: float - """ - return self._transparent_reposition - - @property - def transparent_initial_investment(self): - """ - Get initial investment for transparent envelope per area of windows in currency/m2 - :return: float - """ - return self._transparent_initial_investment - - @property - def transparent_lifetime(self): - """ - Get lifetime of transparent envelope in years - :return: float - """ - return self._transparent_lifetime diff --git a/hub/catalog_factories/data_models/cost/fuel.py b/hub/catalog_factories/data_models/cost/fuel.py new file mode 100644 index 00000000..974b3db2 --- /dev/null +++ b/hub/catalog_factories/data_models/cost/fuel.py @@ -0,0 +1,54 @@ +""" +Cost fuel +SPDX - License - Identifier: LGPL - 3.0 - or -later +Copyright © 2023 Concordia CERC group +Project Coder Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca +""" + +from typing import Union + + +class Fuel: + def __init__(self, fuel_type, + fixed_monthly=None, + fixed_power=None, + variable=None, + variable_units=None): + + self._fuel_type = fuel_type + self._fixed_monthly = fixed_monthly + self._fixed_power = fixed_power + self._variable = variable + self._variable_units = variable_units + + @property + def type(self): + """ + Get fuel type + :return: str + """ + return self._fuel_type + + @property + def fixed_monthly(self) -> Union[None, float]: + """ + Get fixed operational costs in currency per month + :return: None or float + """ + return self._fixed_monthly + + @property + def fixed_power(self) -> Union[None, float]: + """ + Get fixed operational costs depending on the peak power consumed in currency per month per kW + :return: None or float + """ + return self._fixed_power + + @property + def variable(self) -> Union[(None, None), (float, str)]: + """ + Get variable costs in given units + :return: None, None or float, str + """ + return self._variable, self._variable_units diff --git a/hub/catalog_factories/data_models/cost/hvac.py b/hub/catalog_factories/data_models/cost/hvac.py deleted file mode 100644 index 01b6c7b7..00000000 --- a/hub/catalog_factories/data_models/cost/hvac.py +++ /dev/null @@ -1,96 +0,0 @@ -""" -Hvac costs -SPDX - License - Identifier: LGPL - 3.0 - or -later -Copyright © 2022 Concordia CERC group -Project Coder Pilar Monsalvete Álvarez de Uribarri pilar.monsalvete@concordia.ca -""" - - -class Hvac: - def __init__(self, heating_equipment_reposition, heating_equipment_initial_investment, - heating_equipment_lifetime, cooling_equipment_reposition, - cooling_equipment_initial_investment, cooling_equipment_lifetime, - general_hvac_equipment_reposition, general_hvac_equipment_initial_investment, - general_hvac_equipment_lifetime): - - self._heating_equipment_reposition = heating_equipment_reposition - self._heating_equipment_initial_investment = heating_equipment_initial_investment - self._heating_equipment_lifetime = heating_equipment_lifetime - self._cooling_equipment_reposition = cooling_equipment_reposition - self._cooling_equipment_initial_investment = cooling_equipment_initial_investment - self._cooling_equipment_lifetime = cooling_equipment_lifetime - self._general_hvac_equipment_reposition = general_hvac_equipment_reposition - self._general_hvac_equipment_initial_investment = general_hvac_equipment_initial_investment - self._general_hvac_equipment_lifetime = general_hvac_equipment_lifetime - - @property - def heating_equipment_reposition(self): - """ - Get reposition costs of heating equipment per peak-load in currency/W - :return: float - """ - return self._heating_equipment_reposition - - @property - def heating_equipment_initial_investment(self): - """ - Get initial investment costs of heating equipment per peak-load in currency/W - :return: float - """ - return self._heating_equipment_initial_investment - - @property - def heating_equipment_lifetime(self): - """ - Get lifetime of heating equipment in years - :return: float - """ - return self._heating_equipment_lifetime - - @property - def cooling_equipment_reposition(self): - """ - Get reposition costs of cooling equipment per peak-load in currency/W - :return: float - """ - return self._cooling_equipment_reposition - - @property - def cooling_equipment_initial_investment(self): - """ - Get initial investment costs of cooling equipment per peak-load in currency/W - :return: float - """ - return self._cooling_equipment_initial_investment - - @property - def cooling_equipment_lifetime(self): - """ - Get lifetime of cooling equipment in years - :return: float - """ - return self._cooling_equipment_lifetime - - @property - def general_hvac_equipment_reposition(self): - """ - Get reposition costs of general hvac equipment per peak-air-flow in currency/(m3/s) - :return: float - """ - return self._general_hvac_equipment_reposition - - @property - def general_hvac_equipment_initial_investment(self): - """ - Get initial investment costs of cooling equipment per peak-air-flow in currency/(m3/s) - :return: float - """ - return self._general_hvac_equipment_initial_investment - - @property - def general_hvac_equipment_lifetime(self): - """ - Get lifetime of cooling equipment in years - :return: float - """ - return self._general_hvac_equipment_lifetime diff --git a/hub/catalog_factories/data_models/cost/income.py b/hub/catalog_factories/data_models/cost/income.py index 9bc975f2..abd74f77 100644 --- a/hub/catalog_factories/data_models/cost/income.py +++ b/hub/catalog_factories/data_models/cost/income.py @@ -1,64 +1,62 @@ """ -Income from costs catalog +Incomes included in the costs catalog SPDX - License - Identifier: LGPL - 3.0 - or -later -Copyright © 2022 Concordia CERC group -Project Coder Pilar Monsalvete Álvarez de Uribarri pilar.monsalvete@concordia.ca +Copyright © 2023 Concordia CERC group +Project Coder Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca """ +from typing import Union + class Income: - def __init__(self, construction, hvac, photovoltaic_system, electricity_exports, heat_exports, co2): - self._construction = construction - self._hvac = hvac - self._photovoltaic_system = photovoltaic_system - self._electricity_exports = electricity_exports - self._heat_exports = heat_exports - self._co2 = co2 + def __init__(self, construction_subsidy=None, + hvac_subsidy=None, + photovoltaic_subsidy=None, + electricity_export=None, + reductions_taxes=None): + + self._construction_subsidy = construction_subsidy + self._hvac_subsidy = hvac_subsidy + self._photovoltaic_subsidy = photovoltaic_subsidy + self._electricity_export = electricity_export + self._reductions_taxes = reductions_taxes @property - def construction(self): + def construction_subsidy(self) -> Union[None, float]: """ - Get construction subsidy in % of total investment construction cost - :return: float + Get subsidy for construction in percentage + :return: None or float """ - return self._construction + return self._construction_subsidy @property - def hvac(self): + def hvac_subsidy(self) -> Union[None, float]: """ - Get hvac subsidy in % of total investment HVAC cost - :return: float + Get subsidy for HVAC system in percentage + :return: None or float """ - return self._hvac + return self._hvac_subsidy @property - def photovoltaic_system(self): + def photovoltaic_subsidy(self) -> Union[None, float]: """ - Get photovoltaic system subsidy in % of total investment photovoltaic cost - :return: float + Get subsidy PV systems in percentage + :return: None or float """ - return self._photovoltaic_system + return self._photovoltaic_subsidy @property - def electricity_exports(self): + def electricity_export(self) -> Union[None, float]: """ - Get electricity exports gains in currency/J - :return: float + Get electricity export incomes in currency per J + :return: None or float """ - return self._construction + return self._construction_subsidy @property - def heat_exports(self): + def reductions_taxes(self) -> Union[None, float]: """ - Get heat exports gains in currency/J - :return: float + Get reduction in taxes in percentage + :return: None or float """ - return self._heat_exports - - @property - def co2(self): - """ - Get co2 income in currency/kg - :return: float - """ - return self._co2 + return self._reductions_taxes diff --git a/hub/catalog_factories/data_models/cost/item_description.py b/hub/catalog_factories/data_models/cost/item_description.py new file mode 100644 index 00000000..345e04b7 --- /dev/null +++ b/hub/catalog_factories/data_models/cost/item_description.py @@ -0,0 +1,68 @@ +""" +Cost item properties +SPDX - License - Identifier: LGPL - 3.0 - or -later +Copyright © 2023 Concordia CERC group +Project Coder Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca +""" + +from typing import Union + + +class ItemDescription: + def __init__(self, item_type, + initial_investment=None, + initial_investment_units=None, + refurbishment=None, + refurbishment_units=None, + reposition=None, + reposition_units=None, + lifetime=None): + + self._item_type = item_type + self._initial_investment = initial_investment + self._initial_investment_units = initial_investment_units + self._refurbishment = refurbishment + self._refurbishment_units = refurbishment_units + self._reposition = reposition + self._reposition_units = reposition_units + self._lifetime = lifetime + + @property + def type(self): + """ + Get item type + :return: str + """ + return self._item_type + + @property + def initial_investment(self) -> Union[(None, None), (float, str)]: + """ + Get initial investment of the specific item in given units + :return: None, None or float, str + """ + return self._initial_investment, self._initial_investment_units + + @property + def refurbishment(self) -> Union[(None, None), (float, str)]: + """ + Get refurbishment costs of the specific item in given units + :return: None, None or float, str + """ + return self._refurbishment, self._refurbishment_units + + @property + def reposition(self) -> Union[(None, None), (float, str)]: + """ + Get reposition costs of the specific item in given units + :return: None, None or float, str + """ + return self._reposition, self._reposition_units + + @property + def lifetime(self) -> Union[None, float]: + """ + Get lifetime in years + :return: None or float + """ + return self._lifetime diff --git a/hub/catalog_factories/data_models/cost/operational_cost.py b/hub/catalog_factories/data_models/cost/operational_cost.py index b1a7b3aa..f7cbcc6f 100644 --- a/hub/catalog_factories/data_models/cost/operational_cost.py +++ b/hub/catalog_factories/data_models/cost/operational_cost.py @@ -1,104 +1,58 @@ """ -Cost catalog OperationalCost +Operational costs included in the catalog SPDX - License - Identifier: LGPL - 3.0 - or -later -Copyright © 2022 Concordia CERC group -Project Coder Atiya atiya.atiya@mail.concordia.ca -Code contributors: Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca +Copyright © 2023 Concordia CERC group +Project Coder Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca """ +from typing import List +from hub.catalog_factories.data_models.cost.fuel import Fuel + class OperationalCost: - def __init__(self, fuel_type, fuel_fixed_operational_monthly, fuel_fixed_operational_peak, - fuel_variable_operational, heating_equipment_maintenance, cooling_equipment_maintenance, - general_hvac_equipment_maintenance, photovoltaic_system_maintenance, other_systems_maintenance, - co2_emissions): - self._fuel_type = fuel_type - self._fuel_fixed_operational_monthly = fuel_fixed_operational_monthly - self._fuel_fixed_operational_peak = fuel_fixed_operational_peak - self._fuel_variable_operational = fuel_variable_operational - self._heating_equipment_maintenance = heating_equipment_maintenance - self._cooling_equipment_maintenance = cooling_equipment_maintenance - self._general_hvac_equipment_maintenance = general_hvac_equipment_maintenance - self._photovoltaic_system_maintenance = photovoltaic_system_maintenance - self._other_systems_maintenance = other_systems_maintenance - self._co2_emissions = co2_emissions + def __init__(self, fuels, maintenance_heating, maintenance_cooling, maintenance_pv, co2): + self._fuels = fuels + self._maintenance_heating = maintenance_heating + self._maintenance_cooling = maintenance_cooling + self._maintenance_pv = maintenance_pv + self._co2 = co2 @property - def fuel_type(self): + def fuels(self) -> List[Fuel]: """ - Get fuel type - :return: string + Get fuels listed in capital costs + :return: [FUEL] """ - return self._fuel_type + return self._fuels @property - def fuel_fixed_operational_monthly(self): + def maintenance_heating(self): """ - Get fuel fixed operational cost in currency/month + Get cost of maintaining the heating system in currency/W :return: float """ - return self._fuel_fixed_operational_monthly + return self._maintenance_heating @property - def fuel_fixed_operational_peak(self): + def maintenance_cooling(self): """ - Get fuel fixed operational cost per peak power in currency/W + Get cost of maintaining the cooling system in currency/W :return: float """ - return self._fuel_fixed_operational_peak + return self._maintenance_cooling @property - def fuel_variable_operational(self): + def maintenance_pv(self): """ - Get fuel variable operational cost in currency/J + Get cost of maintaining the PV system in currency/m2 :return: float """ - return self._fuel_variable_operational + return self._maintenance_pv @property - def heating_equipment_maintenance(self): + def co2(self): """ - Get heating equipment maintenance cost per peak power in currency/W + Get cost of CO2 emissions in currency/kgCO2 :return: float """ - return self._heating_equipment_maintenance - - @property - def cooling_equipment_maintenance(self): - """ - Get cooling equipment maintenance cost per peak power in currency/W - :return: float - """ - return self._cooling_equipment_maintenance - - @property - def general_hvac_equipment_maintenance(self): - """ - Get general hvac equipment maintenance cost per peak-air-flow in currency/(m3/s) - :return: float - """ - return self._general_hvac_equipment_maintenance - - @property - def photovoltaic_system_maintenance(self): - """ - Get photovoltaic system maintenance cost per panels area in currency/m2 - :return: float - """ - return self._photovoltaic_system_maintenance - - @property - def other_systems_maintenance(self): - """ - Get other systems' maintenance cost per building's foot-print area in currency/m2 - :return: float - """ - return self._other_systems_maintenance - - @property - def co2_emissions(self): - """ - Get CO2 emissions cost in currency/kg - :return: float - """ - return self._co2_emissions + return self._co2 diff --git a/hub/catalog_factories/data_models/cost/shell.py b/hub/catalog_factories/data_models/cost/shell.py deleted file mode 100644 index d0465fd2..00000000 --- a/hub/catalog_factories/data_models/cost/shell.py +++ /dev/null @@ -1,64 +0,0 @@ -""" -Shell costs from Cost catalog -SPDX - License - Identifier: LGPL - 3.0 - or -later -Copyright © 2023 Concordia CERC group -Project Coder Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca -""" - - -class Shell: - def __init__(self, refurbishment_basement, envelope, roofing): - self._opaque_reposition = opaque_reposition - self._opaque_initial_investment = opaque_initial_investment - self._opaque_lifetime = opaque_lifetime - self._transparent_reposition = transparent_reposition - self._transparent_initial_investment = transparent_initial_investment - self._transparent_lifetime = transparent_lifetime - - @property - def opaque_reposition(self): - """ - Get reposition costs for opaque envelope per area of external opaque surfaces in currency/m2 - :return: float - """ - return self._opaque_reposition - - @property - def opaque_initial_investment(self): - """ - Get initial investment for opaque envelope per area of external opaque surfaces in currency/m2 - :return: float - """ - return self._opaque_initial_investment - - @property - def opaque_lifetime(self): - """ - Get lifetime of opaque envelope in years - :return: float - """ - return self._opaque_lifetime - - @property - def transparent_reposition(self): - """ - Get reposition costs for transparent envelope per area of windows in currency/m2 - :return: float - """ - return self._transparent_reposition - - @property - def transparent_initial_investment(self): - """ - Get initial investment for transparent envelope per area of windows in currency/m2 - :return: float - """ - return self._transparent_initial_investment - - @property - def transparent_lifetime(self): - """ - Get lifetime of transparent envelope in years - :return: float - """ - return self._transparent_lifetime diff --git a/hub/catalog_factories/data_models/cost/systems.py b/hub/catalog_factories/data_models/cost/systems.py deleted file mode 100644 index 7f2dc34e..00000000 --- a/hub/catalog_factories/data_models/cost/systems.py +++ /dev/null @@ -1,106 +0,0 @@ -""" -Systems cost catalog -SPDX - License - Identifier: LGPL - 3.0 - or -later -Copyright © 2022 Concordia CERC group -Project Coder Atiya atiya.atiya@mail.concordia.ca -Code contributors: Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca -""" - -from hub.catalog_factories.data_models.cost.hvac import Hvac - - -class Systems: - def __init__(self, hvac, photovoltaic_system_reposition, photovoltaic_system_initial_investment, - photovoltaic_system_lifetime, other_conditioning_systems_reposition, - other_conditioning_systems_initial_investment, other_conditioning_systems_lifetime, - lighting_reposition, lighting_initial_investment, lighting_lifetime): - self._hvac = hvac - self._photovoltaic_system_reposition = photovoltaic_system_reposition - self._photovoltaic_system_initial_investment = photovoltaic_system_initial_investment - self._photovoltaic_system_lifetime = photovoltaic_system_lifetime - self._other_conditioning_systems_reposition = other_conditioning_systems_reposition - self._other_conditioning_systems_initial_investment = other_conditioning_systems_initial_investment - self._other_conditioning_systems_lifetime = other_conditioning_systems_lifetime - self._lighting_reposition = lighting_reposition - self._lighting_initial_investment = lighting_initial_investment - self._lighting_lifetime = lighting_lifetime - - @property - def hvac(self) -> Hvac: - """ - Get hvac capital cost - :return: Hvac - """ - return self._hvac - - @property - def photovoltaic_system_reposition(self): - """ - Get photovoltaic system reposition cost per area of panels in currency/m2 - :return: float - """ - return self._photovoltaic_system_reposition - - @property - def photovoltaic_system_initial_investment(self): - """ - Get photovoltaic system initial investment per area of panels in currency/m2 - :return: float - """ - return self._photovoltaic_system_initial_investment - - @property - def photovoltaic_system_lifetime(self): - """ - Get photovoltaic system lifetime in years - :return: float - """ - return self._photovoltaic_system_lifetime - - @property - def other_conditioning_systems_reposition(self): - """ - Get other conditioning systems reposition cost per building's foot-print area in currency/m2 - :return: float - """ - return self._other_conditioning_systems_reposition - - @property - def other_conditioning_systems_initial_investment(self): - """ - Get other conditioning systems initial investment per building's foot-print area in currency/m2 - :return: float - """ - return self._other_conditioning_systems_initial_investment - - @property - def other_conditioning_systems_lifetime(self): - """ - Get other conditioning systems lifetime in years - :return: float - """ - return self._other_conditioning_systems_lifetime - - @property - def lighting_reposition(self): - """ - Get lighting reposition cost per building's foot-print area in currency/m2 - :return: float - """ - return self._lighting_reposition - - @property - def lighting_initial_investment(self): - """ - Get lighting initial investment per building's foot-print area in currency/m2 - :return: float - """ - return self._lighting_initial_investment - - @property - def lighting_lifetime(self): - """ - Get lighting lifetime in years - :return: float - """ - return self._lighting_lifetime diff --git a/hub/data/costs/montreal_costs.xml b/hub/data/costs/montreal_costs.xml index 5853cbf1..1b40f6b8 100644 --- a/hub/data/costs/montreal_costs.xml +++ b/hub/data/costs/montreal_costs.xml @@ -1,5 +1,6 @@ - + + CAD @@ -63,48 +64,45 @@ - - + + 12.27 - 0 - - 0.075 - - - + 0 + 0.075 + + 17.71 - - 0.640 - - - 1.2 - - - 0.09 - - - 40 - 40 - 1 - + 0.640 + + + 1.2 + + + 0.09 + + + + 40 + 40 + 1 + 30 6.3 - - 2 - 1.5 - 3.6 - + + 2 + 1.5 + 3.6 + - 0 + 0 - - 2 - + 2 - + + CAD @@ -169,16 +167,12 @@ - - 12.27 - 0 - + 12.27 + 0 0.075 - - 17.71 - + 17.71 0.640 @@ -187,26 +181,24 @@ 0.09 - - 40 - 40 - 1 - + + 40 + 40 + 1 + 30 6.3 - - 2 - 1.5 - 3.6 - + + 2 + 1.5 + 3.6 + - 0 + 0 - - 2 - + 2 \ No newline at end of file diff --git a/hub/helpers/constants.py b/hub/helpers/constants.py index 8c601f5c..b67bd61a 100644 --- a/hub/helpers/constants.py +++ b/hub/helpers/constants.py @@ -158,3 +158,25 @@ MIN_FLOAT = float('-inf') # Tools SRA = 'sra' INSEL_MEB = 'insel meb' + +# Costs units +CURRENCY_PER_SQM = 'currency/m2' +CURRENCY_PER_CBM = 'currency/m3' +CURRENCY_PER_KW = 'currency/kW' +CURRENCY_PER_KWH = 'currency/kWh' +CURRENCY_PER_MONTH = 'currency/month' +CURRENCY_PER_LITRE = 'currency/l' +CURRENCY_PER_KG = 'currency/kg' +CURRENCY_PER_CBM_PER_HOUR = 'currency/(m3/h)' +PERCENTAGE = '%' + +# Costs chapters +SUPERSTRUCTURE = 'B10_superstructure' +ENVELOPE = 'B20_envelope' +ROOFING = 'B30_roofing' +ENERGY_SUPPLY = 'D3010_energy_supply' +HEAT_GENERATION = 'D3020_heat_generating_systems' +COOL_GENERATION = 'D3030_cooling_generation_systems' +DISTRIBUTION = 'D3040_distribution_systems' +OTHER_SYSTEMS = 'D3080_other_hvac_ahu' +LIGHTING_AND_WIRING = 'D5020lighting_and_branch_wiring' diff --git a/hub/helpers/data/hft_function_to_hub_function.py b/hub/helpers/data/hft_function_to_hub_function.py index 00f63a53..a14ddfb1 100644 --- a/hub/helpers/data/hft_function_to_hub_function.py +++ b/hub/helpers/data/hft_function_to_hub_function.py @@ -12,21 +12,21 @@ class HftFunctionToHubFunction: def __init__(self): self._dictionary = { - 'residential': cte.RESIDENTIAL, - 'single family house': cte.SINGLE_FAMILY_HOUSE, - 'multifamily house': cte.MULTI_FAMILY_HOUSE, - 'hotel': cte.HOTEL, - 'hospital': cte.HOSPITAL, - 'outpatient': cte.OUT_PATIENT_HEALTH_CARE, - 'commercial': cte.SUPERMARKET, - 'strip mall': cte.STRIP_MALL, - 'warehouse': cte.WAREHOUSE, - 'primary school': cte.PRIMARY_SCHOOL, - 'secondary school': cte.EDUCATION, - 'office': cte.MEDIUM_OFFICE, - 'large office': cte.LARGE_OFFICE - } + 'residential': cte.RESIDENTIAL, + 'single family house': cte.SINGLE_FAMILY_HOUSE, + 'multifamily house': cte.MULTI_FAMILY_HOUSE, + 'hotel': cte.HOTEL, + 'hospital': cte.HOSPITAL, + 'outpatient': cte.OUT_PATIENT_HEALTH_CARE, + 'commercial': cte.SUPERMARKET, + 'strip mall': cte.STRIP_MALL, + 'warehouse': cte.WAREHOUSE, + 'primary school': cte.PRIMARY_SCHOOL, + 'secondary school': cte.EDUCATION, + 'office': cte.MEDIUM_OFFICE, + 'large office': cte.LARGE_OFFICE + } @property - def dictionary(self) -> dict: + def dictionary(self) -> dict: return self._dictionary diff --git a/hub/helpers/dictionaries.py b/hub/helpers/dictionaries.py index 3561d682..1fc73fde 100644 --- a/hub/helpers/dictionaries.py +++ b/hub/helpers/dictionaries.py @@ -15,6 +15,7 @@ from hub.helpers.data.hub_usage_to_comnet_usage import HubUsageToComnetUsage from hub.helpers.data.hub_usage_to_hft_usage import HubUsageToHftUsage from hub.helpers.data.hub_usage_to_nrcan_usage import HubUsageToNrcanUsage + class Dictionaries: """ Dictionaries class diff --git a/hub/unittests/test_exports.py b/hub/unittests/test_exports.py index 283c9595..7fa84103 100644 --- a/hub/unittests/test_exports.py +++ b/hub/unittests/test_exports.py @@ -71,8 +71,6 @@ class TestExports(TestCase): self._complete_city = self._get_complete_city(from_pickle) EnergyBuildingsExportsFactory(export_type, self._complete_city, self._output_path).export() - - def test_obj_export(self): """ export to obj -- 2.39.2 From d672eee762f597ffdd134f6f9e7df6260a8ba999 Mon Sep 17 00:00:00 2001 From: Pilar Date: Thu, 16 Mar 2023 12:24:09 -0400 Subject: [PATCH 04/13] changed percentage to per one (-) --- hub/catalog_factories/cost/montreal_custom_catalog.py | 6 +++--- hub/catalog_factories/data_models/cost/capital_cost.py | 4 ++-- hub/catalog_factories/data_models/cost/income.py | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/hub/catalog_factories/cost/montreal_custom_catalog.py b/hub/catalog_factories/cost/montreal_custom_catalog.py index a5a921ab..4624e13b 100644 --- a/hub/catalog_factories/cost/montreal_custom_catalog.py +++ b/hub/catalog_factories/cost/montreal_custom_catalog.py @@ -81,8 +81,8 @@ class MontrealCustomCatalog(Catalog): items_list.append(item_description) general_chapters.append(Chapter('D_services', items_list)) - design_allowance = float(entry['Z_allowances_overhead_profit']['Z10_design_allowance']['#text']) - overhead_and_profit = float(entry['Z_allowances_overhead_profit']['Z10_overhead_and_profit']['#text']) + design_allowance = float(entry['Z_allowances_overhead_profit']['Z10_design_allowance']['#text']) / 100 + overhead_and_profit = float(entry['Z_allowances_overhead_profit']['Z10_overhead_and_profit']['#text']) / 100 _capital_cost = CapitalCost(general_chapters, design_allowance, overhead_and_profit) return _capital_cost @@ -135,7 +135,7 @@ class MontrealCustomCatalog(Catalog): hvac = float(archetype['incomes']['subsidies']['hvac_subsidy']['#text']) photovoltaic_system = float(archetype['incomes']['subsidies']['photovoltaic_subsidy']['#text']) electricity_exports = float(archetype['incomes']['energy_exports']['electricity']['#text']) / 1000 / 3600 - reduction_tax = float(archetype['incomes']['tax_reduction']['#text']) + reduction_tax = float(archetype['incomes']['tax_reduction']['#text']) / 100 income = Income(construction_subsidy=construction, hvac_subsidy=hvac, photovoltaic_subsidy=photovoltaic_system, diff --git a/hub/catalog_factories/data_models/cost/capital_cost.py b/hub/catalog_factories/data_models/cost/capital_cost.py index 233043ac..828b5cfc 100644 --- a/hub/catalog_factories/data_models/cost/capital_cost.py +++ b/hub/catalog_factories/data_models/cost/capital_cost.py @@ -26,7 +26,7 @@ class CapitalCost: @property def design_allowance(self): """ - Get design allowance in percentage + Get design allowance in percentage (-) :return: float """ return self._design_allowance @@ -34,7 +34,7 @@ class CapitalCost: @property def overhead_and_profit(self): """ - Get overhead profit in percentage + Get overhead profit in percentage (-) :return: float """ return self._overhead_and_profit diff --git a/hub/catalog_factories/data_models/cost/income.py b/hub/catalog_factories/data_models/cost/income.py index abd74f77..baa880ec 100644 --- a/hub/catalog_factories/data_models/cost/income.py +++ b/hub/catalog_factories/data_models/cost/income.py @@ -56,7 +56,7 @@ class Income: @property def reductions_taxes(self) -> Union[None, float]: """ - Get reduction in taxes in percentage + Get reduction in taxes in percentage (-) :return: None or float """ return self._reductions_taxes -- 2.39.2 From 1b90974b7711f122f7fa8a70a515f2bb47b62462 Mon Sep 17 00:00:00 2001 From: Pilar Date: Fri, 17 Mar 2023 10:10:30 -0400 Subject: [PATCH 05/13] some refactoring to keep our naming agreement --- .../cost/montreal_custom_catalog.py | 8 ++++---- .../data_models/cost/chapter.py | 8 ++++---- .../data_models/cost/cost_helper.py | 12 +++--------- .../data_models/cost/income.py | 8 ++++---- .../data_models/cost/item_description.py | 18 +++++++++--------- hub/helpers/constants.py | 12 +++--------- 6 files changed, 27 insertions(+), 39 deletions(-) diff --git a/hub/catalog_factories/cost/montreal_custom_catalog.py b/hub/catalog_factories/cost/montreal_custom_catalog.py index 4624e13b..a0679b89 100644 --- a/hub/catalog_factories/cost/montreal_custom_catalog.py +++ b/hub/catalog_factories/cost/montreal_custom_catalog.py @@ -36,9 +36,9 @@ class MontrealCustomCatalog(Catalog): _lifetime = float(entry[item_type]['lifetime_equipment']['#text']) _item_description = ItemDescription(item_type, initial_investment=_investment, - initial_investment_units=_investment_unit, + initial_investment_unit=_investment_unit, reposition=_reposition, - reposition_units=_reposition_unit, + reposition_unit=_reposition_unit, lifetime=_lifetime) return _item_description @@ -48,7 +48,7 @@ class MontrealCustomCatalog(Catalog): _refurbishment_unit = entry[item_type]['refurbishment_cost']['@cost_unit'] _item_description = ItemDescription(item_type, refurbishment=_refurbishment, - refurbishment_units=_refurbishment_unit) + refurbishment_unit=_refurbishment_unit) return _item_description def _get_capital_costs(self, entry): @@ -140,7 +140,7 @@ class MontrealCustomCatalog(Catalog): hvac_subsidy=hvac, photovoltaic_subsidy=photovoltaic_system, electricity_export=electricity_exports, - reductions_taxes=reduction_tax) + reductions_tax=reduction_tax) _catalog_archetypes.append(Archetype(lod, function, municipality, diff --git a/hub/catalog_factories/data_models/cost/chapter.py b/hub/catalog_factories/data_models/cost/chapter.py index d1be68ac..bf393cbb 100644 --- a/hub/catalog_factories/data_models/cost/chapter.py +++ b/hub/catalog_factories/data_models/cost/chapter.py @@ -10,10 +10,10 @@ from hub.catalog_factories.data_models.cost.item_description import ItemDescript class Chapter: - def __init__(self, chapter_type, items_list): + def __init__(self, chapter_type, items): self._chapter_type = chapter_type - self._items_list = items_list + self._items = items @property def chapter_type(self): @@ -24,9 +24,9 @@ class Chapter: return self._chapter_type @property - def items_list(self) -> List[ItemDescription]: + def items(self) -> List[ItemDescription]: """ Get list of items contained in the chapter :return: [str] """ - return self._items_list + return self._items diff --git a/hub/catalog_factories/data_models/cost/cost_helper.py b/hub/catalog_factories/data_models/cost/cost_helper.py index c51ab02b..fb0d551e 100644 --- a/hub/catalog_factories/data_models/cost/cost_helper.py +++ b/hub/catalog_factories/data_models/cost/cost_helper.py @@ -26,15 +26,9 @@ class CostHelper: } _chapters_in_lod1 = { - 'B10_superstructure': cte.SUPERSTRUCTURE, - 'B20_envelope': cte.ENVELOPE, - 'B30_roofing': cte.ROOFING, - 'D3010_energy_supply': cte.ENERGY_SUPPLY, - 'D3020_heat_generating_systems': cte.HEAT_GENERATION, - 'D3030_cooling_generation_systems': cte.COOL_GENERATION, - 'D3040_distribution_systems': cte.DISTRIBUTION, - 'D3080_other_hvac_ahu': cte.OTHER_SYSTEMS, - 'D5020lighting_and_branch_wiring': cte.LIGHTING_AND_WIRING + 'B_shell': cte.SUPERSTRUCTURE, + 'D_services': cte.ENVELOPE, + 'Z_allowances_overhead_profit': cte.ALLOWANCES_OVERHEAD_PROFIT } @property diff --git a/hub/catalog_factories/data_models/cost/income.py b/hub/catalog_factories/data_models/cost/income.py index baa880ec..0a8c0442 100644 --- a/hub/catalog_factories/data_models/cost/income.py +++ b/hub/catalog_factories/data_models/cost/income.py @@ -13,13 +13,13 @@ class Income: hvac_subsidy=None, photovoltaic_subsidy=None, electricity_export=None, - reductions_taxes=None): + reductions_tax=None): self._construction_subsidy = construction_subsidy self._hvac_subsidy = hvac_subsidy self._photovoltaic_subsidy = photovoltaic_subsidy self._electricity_export = electricity_export - self._reductions_taxes = reductions_taxes + self._reductions_tax = reductions_tax @property def construction_subsidy(self) -> Union[None, float]: @@ -54,9 +54,9 @@ class Income: return self._construction_subsidy @property - def reductions_taxes(self) -> Union[None, float]: + def reductions_tax(self) -> Union[None, float]: """ Get reduction in taxes in percentage (-) :return: None or float """ - return self._reductions_taxes + return self._reductions_tax diff --git a/hub/catalog_factories/data_models/cost/item_description.py b/hub/catalog_factories/data_models/cost/item_description.py index 345e04b7..42ce94b4 100644 --- a/hub/catalog_factories/data_models/cost/item_description.py +++ b/hub/catalog_factories/data_models/cost/item_description.py @@ -11,20 +11,20 @@ from typing import Union class ItemDescription: def __init__(self, item_type, initial_investment=None, - initial_investment_units=None, + initial_investment_unit=None, refurbishment=None, - refurbishment_units=None, + refurbishment_unit=None, reposition=None, - reposition_units=None, + reposition_unit=None, lifetime=None): self._item_type = item_type self._initial_investment = initial_investment - self._initial_investment_units = initial_investment_units + self._initial_investment_unit = initial_investment_unit self._refurbishment = refurbishment - self._refurbishment_units = refurbishment_units + self._refurbishment_unit = refurbishment_unit self._reposition = reposition - self._reposition_units = reposition_units + self._reposition_unit = reposition_unit self._lifetime = lifetime @property @@ -41,7 +41,7 @@ class ItemDescription: Get initial investment of the specific item in given units :return: None, None or float, str """ - return self._initial_investment, self._initial_investment_units + return self._initial_investment, self._initial_investment_unit @property def refurbishment(self) -> Union[(None, None), (float, str)]: @@ -49,7 +49,7 @@ class ItemDescription: Get refurbishment costs of the specific item in given units :return: None, None or float, str """ - return self._refurbishment, self._refurbishment_units + return self._refurbishment, self._refurbishment_unit @property def reposition(self) -> Union[(None, None), (float, str)]: @@ -57,7 +57,7 @@ class ItemDescription: Get reposition costs of the specific item in given units :return: None, None or float, str """ - return self._reposition, self._reposition_units + return self._reposition, self._reposition_unit @property def lifetime(self) -> Union[None, float]: diff --git a/hub/helpers/constants.py b/hub/helpers/constants.py index b67bd61a..2caaee0d 100644 --- a/hub/helpers/constants.py +++ b/hub/helpers/constants.py @@ -171,12 +171,6 @@ CURRENCY_PER_CBM_PER_HOUR = 'currency/(m3/h)' PERCENTAGE = '%' # Costs chapters -SUPERSTRUCTURE = 'B10_superstructure' -ENVELOPE = 'B20_envelope' -ROOFING = 'B30_roofing' -ENERGY_SUPPLY = 'D3010_energy_supply' -HEAT_GENERATION = 'D3020_heat_generating_systems' -COOL_GENERATION = 'D3030_cooling_generation_systems' -DISTRIBUTION = 'D3040_distribution_systems' -OTHER_SYSTEMS = 'D3080_other_hvac_ahu' -LIGHTING_AND_WIRING = 'D5020lighting_and_branch_wiring' +SUPERSTRUCTURE = 'B_shell' +ENVELOPE = 'D_services' +ALLOWANCES_OVERHEAD_PROFIT = 'Z_allowances_overhead_profit' -- 2.39.2 From b61d84763ac80d35479665dd390c1b34a1eeab37 Mon Sep 17 00:00:00 2001 From: p_monsalvete Date: Mon, 20 Mar 2023 09:52:40 -0400 Subject: [PATCH 06/13] test --- hub/catalog_factories/catalog.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/hub/catalog_factories/catalog.py b/hub/catalog_factories/catalog.py index 96b61726..0f5c08df 100644 --- a/hub/catalog_factories/catalog.py +++ b/hub/catalog_factories/catalog.py @@ -8,7 +8,8 @@ Project Coder Guille Gutierrez guillermo.gutierrezmorote@concordia.ca class Catalog: """ - Catalogs base class not implemented instance of the Catalog base class, catalog_factories will inherit from this class. + Catalogs base class not implemented instance of the Catalog base class, + catalog_factories will inherit from this class. """ def names(self, category=None): -- 2.39.2 From 6a402485c53b1c7afa0029eb6808018a3b01adb5 Mon Sep 17 00:00:00 2001 From: p_monsalvete Date: Mon, 20 Mar 2023 09:59:48 -0400 Subject: [PATCH 07/13] test --- hub/catalog_factories/catalog.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hub/catalog_factories/catalog.py b/hub/catalog_factories/catalog.py index 0f5c08df..a7e3cd85 100644 --- a/hub/catalog_factories/catalog.py +++ b/hub/catalog_factories/catalog.py @@ -14,7 +14,7 @@ class Catalog: def names(self, category=None): """ - Base property to return the catalog entries names + Base property to return the catalog entries names. :return: Catalog names filter by category if provided """ raise NotImplementedError -- 2.39.2 From aa0952969f8aa63f12e9714996e2d7a5b78a6469 Mon Sep 17 00:00:00 2001 From: p_monsalvete Date: Mon, 20 Mar 2023 10:07:31 -0400 Subject: [PATCH 08/13] solved bug in typing --- hub/catalog_factories/data_models/cost/fuel.py | 2 +- hub/catalog_factories/data_models/cost/item_description.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/hub/catalog_factories/data_models/cost/fuel.py b/hub/catalog_factories/data_models/cost/fuel.py index 974b3db2..82b47477 100644 --- a/hub/catalog_factories/data_models/cost/fuel.py +++ b/hub/catalog_factories/data_models/cost/fuel.py @@ -46,7 +46,7 @@ class Fuel: return self._fixed_power @property - def variable(self) -> Union[(None, None), (float, str)]: + def variable(self) -> Union[tuple[None, None], tuple[float, str]]: """ Get variable costs in given units :return: None, None or float, str diff --git a/hub/catalog_factories/data_models/cost/item_description.py b/hub/catalog_factories/data_models/cost/item_description.py index 42ce94b4..45b24bbb 100644 --- a/hub/catalog_factories/data_models/cost/item_description.py +++ b/hub/catalog_factories/data_models/cost/item_description.py @@ -36,7 +36,7 @@ class ItemDescription: return self._item_type @property - def initial_investment(self) -> Union[(None, None), (float, str)]: + def initial_investment(self) -> Union[tuple[None, None], tuple[float, str]]: """ Get initial investment of the specific item in given units :return: None, None or float, str @@ -44,7 +44,7 @@ class ItemDescription: return self._initial_investment, self._initial_investment_unit @property - def refurbishment(self) -> Union[(None, None), (float, str)]: + def refurbishment(self) -> Union[tuple[None, None], tuple[float, str]]: """ Get refurbishment costs of the specific item in given units :return: None, None or float, str @@ -52,7 +52,7 @@ class ItemDescription: return self._refurbishment, self._refurbishment_unit @property - def reposition(self) -> Union[(None, None), (float, str)]: + def reposition(self) -> Union[tuple[None, None], tuple[float, str]]: """ Get reposition costs of the specific item in given units :return: None, None or float, str -- 2.39.2 From 9f28c2f93db078ac3fbb5108951674c21255610e Mon Sep 17 00:00:00 2001 From: p_monsalvete Date: Mon, 20 Mar 2023 11:07:43 -0400 Subject: [PATCH 09/13] added domestic hot water parameters in catalog and importers --- .../data_models/usages/domestic_hot_water.py | 11 ++++- hub/catalog_factories/usage/comnet_catalog.py | 1 + hub/catalog_factories/usage/nrcan_catalog.py | 15 +++---- .../building_demand/domestic_hot_water.py | 17 ++++++++ hub/city_model_structure/city_object.py | 19 ++++++++ hub/config/configuration.ini | 4 +- hub/helpers/configuration_helper.py | 8 ++++ hub/helpers/constants.py | 4 ++ hub/imports/usage/comnet_usage_parameters.py | 11 ++++- hub/imports/usage/nrcan_usage_parameters.py | 10 +++-- hub/imports/usage/usage_helper.py | 43 +++++++++++++++++++ hub/imports/weather/epw_weather_parameters.py | 8 ++-- 12 files changed, 130 insertions(+), 21 deletions(-) create mode 100644 hub/imports/usage/usage_helper.py diff --git a/hub/catalog_factories/data_models/usages/domestic_hot_water.py b/hub/catalog_factories/data_models/usages/domestic_hot_water.py index edf4b8ab..a34a4c65 100644 --- a/hub/catalog_factories/data_models/usages/domestic_hot_water.py +++ b/hub/catalog_factories/data_models/usages/domestic_hot_water.py @@ -14,8 +14,9 @@ class DomesticHotWater: """ DomesticHotWater class """ - def __init__(self, density, service_temperature, schedules): + def __init__(self, density, peak_flow, service_temperature, schedules): self._density = density + self._peak_flow = peak_flow self._service_temperature = service_temperature self._schedules = schedules @@ -27,6 +28,14 @@ class DomesticHotWater: """ return self._density + @property + def peak_flow(self) -> Union[None, float]: + """ + Get domestic hot water peak_flow density in m3 per second and m2 + :return: None or float + """ + return self._peak_flow + @property def service_temperature(self) -> Union[None, float]: """ diff --git a/hub/catalog_factories/usage/comnet_catalog.py b/hub/catalog_factories/usage/comnet_catalog.py index 2c267477..7c226722 100644 --- a/hub/catalog_factories/usage/comnet_catalog.py +++ b/hub/catalog_factories/usage/comnet_catalog.py @@ -104,6 +104,7 @@ class ComnetCatalog(Catalog): density = float(density) * cte.BTU_H_TO_WATTS * occupancy_density domestic_hot_water_service_temperature = self._schedules[schedule_name]['WtrHtrSetPt'][0].values[0] domestic_hot_water = DomesticHotWater(density, + None, domestic_hot_water_service_temperature, self._schedules[schedule_name]['Service Hot Water'] ) diff --git a/hub/catalog_factories/usage/nrcan_catalog.py b/hub/catalog_factories/usage/nrcan_catalog.py index a7e8dda9..0f60dc68 100644 --- a/hub/catalog_factories/usage/nrcan_catalog.py +++ b/hub/catalog_factories/usage/nrcan_catalog.py @@ -79,10 +79,8 @@ class NrcanCatalog(Catalog): url = f'{self._base_url}{name["space_types_location"]}' with urllib.request.urlopen(url) as json_file: space_types = json.load(json_file)['tables']['space_types']['table'] -# space_types = [st for st in space_types if st['building_type'] == 'Space Function'] space_types = [st for st in space_types if st['space_type'] == 'WholeBuilding'] for space_type in space_types: -# usage_type = space_type['space_type'] usage_type = space_type['building_type'] occupancy_schedule_name = space_type['occupancy_schedule'] lighting_schedule_name = space_type['lighting_schedule'] @@ -127,14 +125,10 @@ class NrcanCatalog(Catalog): if appliances_radiative_fraction is not None and appliances_latent_fraction is not None: appliances_convective_fraction = 1 - appliances_radiative_fraction - appliances_latent_fraction - # peak flow in m3/day/m2 - domestic_hot_water_peak_flow = space_type['service_water_heating_peak_flow_per_area'] + # peak flow in gallons/h/ft2 + domestic_hot_water_peak_flow = space_type['service_water_heating_peak_flow_per_area'] \ + * cte.GALLONS_TO_QUBIC_METERS / cte.HOUR_TO_SECONDS * pow(cte.METERS_TO_FEET, 2) domestic_hot_water_service_temperature = space_type['service_water_heating_target_temperature'] - average_domestic_hot_water_inlet_temperature = 16.5 - # result in W/m2 - domestic_hot_water_density = domestic_hot_water_peak_flow / 24 / 3.6 * 4184 \ - * (domestic_hot_water_service_temperature - - average_domestic_hot_water_inlet_temperature) occupancy = Occupancy(occupancy_density, None, @@ -157,7 +151,8 @@ class NrcanCatalog(Catalog): hvac_availability, heating_schedule, cooling_schedule) - domestic_hot_water = DomesticHotWater(domestic_hot_water_density, + domestic_hot_water = DomesticHotWater(None, + domestic_hot_water_peak_flow, domestic_hot_water_service_temperature, domestic_hot_water_load_schedule) diff --git a/hub/city_model_structure/building_demand/domestic_hot_water.py b/hub/city_model_structure/building_demand/domestic_hot_water.py index f48bf68a..d91977e5 100644 --- a/hub/city_model_structure/building_demand/domestic_hot_water.py +++ b/hub/city_model_structure/building_demand/domestic_hot_water.py @@ -14,6 +14,7 @@ class DomesticHotWater: """ def __init__(self): self._density = None + self._peak_flow = None self._service_temperature = None self._schedules = None @@ -34,6 +35,22 @@ class DomesticHotWater: if value is not None: self._density = float(value) + @property + def peak_flow(self) -> Union[None, float]: + """ + Get domestic hot water peak_flow density in m3 per second and m2 + :return: None or float + """ + return self._peak_flow + + @peak_flow.setter + def peak_flow(self, value): + """ + Set domestic hot water peak_flow density in m3 per second and m2 + :return: None or float + """ + self._peak_flow = value + @property def service_temperature(self) -> Union[None, float]: """ diff --git a/hub/city_model_structure/city_object.py b/hub/city_model_structure/city_object.py index ab50250c..bf95b040 100644 --- a/hub/city_model_structure/city_object.py +++ b/hub/city_model_structure/city_object.py @@ -35,6 +35,7 @@ class CityObject: self._max_z = ConfigurationHelper().min_coordinate self._centroid = None self._external_temperature = dict() + self._ground_temperature = dict() self._global_horizontal = dict() self._diffuse = dict() self._beam = dict() @@ -158,6 +159,24 @@ class CityObject: """ self._external_temperature = value + # todo: this is the new format we will use to get rid of the data frames + @property + def ground_temperature(self) -> dict: + """ + Get ground temperature under the city object in Celsius at different depths in meters for different time steps + example of use: {month: {0.5: [10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10]}} + :return: dict{dict{[float]}} + """ + return self._ground_temperature + + @ground_temperature.setter + def ground_temperature(self, value): + """ + Set ground temperature under the city object in Celsius at different depths + :param value: dict{dict{[float]}} + """ + self._ground_temperature = value + @property def global_horizontal(self) -> dict: """ diff --git a/hub/config/configuration.ini b/hub/config/configuration.ini index 58bf8d87..24d01eea 100644 --- a/hub/config/configuration.ini +++ b/hub/config/configuration.ini @@ -17,4 +17,6 @@ convective_heat_transfer_coefficient_exterior = 20 #W/mK soil_conductivity = 3 #m -soil_thickness = 0.5 \ No newline at end of file +soil_thickness = 0.5 +#C +cold_water_temperature = 10 \ No newline at end of file diff --git a/hub/helpers/configuration_helper.py b/hub/helpers/configuration_helper.py index 88cc5671..d9bf5372 100644 --- a/hub/helpers/configuration_helper.py +++ b/hub/helpers/configuration_helper.py @@ -138,3 +138,11 @@ class ConfigurationHelper: :return: 0.5 """ return self._config.getfloat('buildings', 'soil_thickness').real + + @property + def cold_water_temperature(self) -> float: + """ + Get configured cold water temperature in Celsius + :return: 10 + """ + return self._config.getfloat('buildings', 'cold_water_temperature').real diff --git a/hub/helpers/constants.py b/hub/helpers/constants.py index 2caaee0d..2a20c263 100644 --- a/hub/helpers/constants.py +++ b/hub/helpers/constants.py @@ -8,13 +8,17 @@ Project Coder Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca # universal constants KELVIN = 273.15 +WATER_DENSITY = 1000 +WATER_HEAT_CAPACITY = 4182 # converters HOUR_TO_MINUTES = 60 MINUTES_TO_SECONDS = 60 +HOUR_TO_SECONDS = 3600 METERS_TO_FEET = 3.28084 BTU_H_TO_WATTS = 0.29307107 KILO_WATTS_HOUR_TO_JULES = 3600000 +GALLONS_TO_QUBIC_METERS = 0.0037854117954011185 # time SECOND = 'second' diff --git a/hub/imports/usage/comnet_usage_parameters.py b/hub/imports/usage/comnet_usage_parameters.py index 2747c1af..62fcb2c9 100644 --- a/hub/imports/usage/comnet_usage_parameters.py +++ b/hub/imports/usage/comnet_usage_parameters.py @@ -19,6 +19,7 @@ from hub.city_model_structure.building_demand.domestic_hot_water import Domestic from hub.city_model_structure.attributes.schedule import Schedule from hub.city_model_structure.building_demand.internal_gain import InternalGain from hub.catalog_factories.usage_catalog_factory import UsageCatalogFactory +from hub.imports.usage.usage_helper import UsageHelper class ComnetUsageParameters: @@ -55,7 +56,7 @@ class ComnetUsageParameters: volume_per_area = internal_zone.volume / internal_zone.area usage = Usage() usage.name = usage_name - self._assign_values(usage, archetype_usage, volume_per_area) + self._assign_values(usage, archetype_usage, volume_per_area, building.ground_temperature) usage.percentage = 1 self._calculate_reduced_values_from_extended_library(usage, archetype_usage) @@ -70,7 +71,7 @@ class ComnetUsageParameters: raise KeyError('archetype not found') @staticmethod - def _assign_values(usage, archetype, volume_per_area): + def _assign_values(usage, archetype, volume_per_area, ground_temperature): # Due to the fact that python is not a typed language, the wrong object type is assigned to # usage.occupancy when writing usage.occupancy = archetype.occupancy. # Same happens for lighting and appliances. Therefore, this walk around has been done. @@ -105,6 +106,12 @@ class ComnetUsageParameters: _domestic_hot_water = DomesticHotWater() _domestic_hot_water.density = archetype.domestic_hot_water.density _domestic_hot_water.service_temperature = archetype.domestic_hot_water.service_temperature + cold_temperature = UsageHelper().cold_water_temperature(ground_temperature) + peak_flow = 0 + if (archetype.domestic_hot_water.service_temperature - cold_temperature) > 0: + peak_flow = archetype.domestic_hot_water.density / cte.WATER_DENSITY / cte.WATER_HEAT_CAPACITY \ + / (archetype.domestic_hot_water.service_temperature - cold_temperature) + _domestic_hot_water.peak_flow = peak_flow _domestic_hot_water.schedules = archetype.domestic_hot_water.schedules usage.domestic_hot_water = _domestic_hot_water diff --git a/hub/imports/usage/nrcan_usage_parameters.py b/hub/imports/usage/nrcan_usage_parameters.py index bc80487b..08520b89 100644 --- a/hub/imports/usage/nrcan_usage_parameters.py +++ b/hub/imports/usage/nrcan_usage_parameters.py @@ -16,6 +16,7 @@ from hub.city_model_structure.building_demand.appliances import Appliances from hub.city_model_structure.building_demand.thermal_control import ThermalControl from hub.city_model_structure.building_demand.domestic_hot_water import DomesticHotWater from hub.catalog_factories.usage_catalog_factory import UsageCatalogFactory +from hub.imports.usage.usage_helper import UsageHelper class NrcanUsageParameters: @@ -62,7 +63,7 @@ class NrcanUsageParameters: volume_per_area = internal_zone.volume / internal_zone.area usage = Usage() usage.name = usage_name - self._assign_values(usage, archetype_usage, volume_per_area) + self._assign_values(usage, archetype_usage, volume_per_area, building.ground_temperature) self._assign_comnet_extra_values(usage, comnet_archetype_usage) usage.percentage = 1 self._calculate_reduced_values_from_extended_library(usage, archetype_usage) @@ -78,7 +79,7 @@ class NrcanUsageParameters: raise KeyError('archetype not found') @staticmethod - def _assign_values(usage, archetype, volume_per_area): + def _assign_values(usage, archetype, volume_per_area, ground_temperature): if archetype.mechanical_air_change > 0: usage.mechanical_air_change = archetype.mechanical_air_change elif archetype.ventilation_rate > 0: @@ -113,8 +114,11 @@ class NrcanUsageParameters: _control.hvac_availability_schedules = archetype.thermal_control.hvac_availability_schedules usage.thermal_control = _control _domestic_hot_water = DomesticHotWater() - _domestic_hot_water.density = archetype.domestic_hot_water.density + _domestic_hot_water.peak_flow = archetype.domestic_hot_water.peak_flow _domestic_hot_water.service_temperature = archetype.domestic_hot_water.service_temperature + cold_temperature = UsageHelper().cold_water_temperature(ground_temperature) + _domestic_hot_water.density = archetype.domestic_hot_water.peak_flow * cte.WATER_DENSITY * cte.WATER_HEAT_CAPACITY \ + * (archetype.domestic_hot_water.service_temperature - cold_temperature) _domestic_hot_water.schedules = archetype.domestic_hot_water.schedules usage.domestic_hot_water = _domestic_hot_water diff --git a/hub/imports/usage/usage_helper.py b/hub/imports/usage/usage_helper.py new file mode 100644 index 00000000..2d6ac514 --- /dev/null +++ b/hub/imports/usage/usage_helper.py @@ -0,0 +1,43 @@ +""" +Usage helper +SPDX - License - Identifier: LGPL - 3.0 - or -later +Copyright © 2023 Concordia CERC group +Project Coder Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca +""" + +import sys +from hub.hub_logger import logger +import hub.helpers.constants as cte +from hub.helpers.configuration_helper import ConfigurationHelper as ch + + +class UsageHelper: + """ + UsageHelper class + """ + + @staticmethod + def cold_water_temperature(ground_temperature): + keys = ground_temperature.keys() + depths = ground_temperature[keys[0]].keys() + if keys[0] == cte.YEAR: + # taking the deeper temperature available + _cold_temperature = ground_temperature[cte.YEAR][depths[len(depths-1)]] + elif keys[0] == cte.MONTH: + _cold_temperature = 0 + for i in range(0, 12): + # taking the deeper temperature available + _cold_temperature += ground_temperature[cte.MONTH][depths[len(depths-1)]] / 12 + elif keys[0] == cte.HOUR: + _cold_temperature = 0 + for i in range(0, 8760): + # taking the deeper temperature available + _cold_temperature += ground_temperature[cte.HOUR][depths[len(depths-1)]] / 8760 + else: + _cold_temperature = ch().cold_water_temperature + logger.error(f'Cold water temperature could not be calculated. Assigned default value = ' + f'{ch().cold_water_temperature} degrees Celsius\n') + sys.stderr.write(f'Cold water temperature could not be calculated. Assigned default value = ' + f'{ch().cold_water_temperature} degrees Celsius\n') + + return _cold_temperature diff --git a/hub/imports/weather/epw_weather_parameters.py b/hub/imports/weather/epw_weather_parameters.py index 47ddb5fe..2b53bc24 100644 --- a/hub/imports/weather/epw_weather_parameters.py +++ b/hub/imports/weather/epw_weather_parameters.py @@ -32,14 +32,13 @@ class EpwWeatherParameters: _ = file.readline().split(',') line = file.readline().split(',') number_records = int(line[1]) - depth_measurement_ground_temperature = [] - ground_temperature = [] + ground_temperature = {} for i in range(0, number_records): - depth_measurement_ground_temperature.append(line[i*16+2]) + depth_measurement_ground_temperature = line[i*16+2] temperatures = [] for j in range(0, 12): temperatures.append(line[i*16+j+6]) - ground_temperature.append(temperatures) + ground_temperature[depth_measurement_ground_temperature] = temperatures file.close() except SystemExit: sys.stderr.write(f'Error: weather file {self._path} not found. Please download it from ' @@ -74,6 +73,7 @@ class EpwWeatherParameters: sys.exit() for building in self._city.buildings: + building.ground_temperature[cte.MONTH] = ground_temperature if cte.HOUR in building.external_temperature: del building.external_temperature[cte.HOUR] new_value = pd.DataFrame(self._weather_values[['dry_bulb_temperature_c']].to_numpy(), columns=['epw']) -- 2.39.2 From 6bbc3007309553b47fb6a38df54b0d1cb6e236a3 Mon Sep 17 00:00:00 2001 From: p_monsalvete Date: Mon, 20 Mar 2023 11:39:41 -0400 Subject: [PATCH 10/13] erased not needed max_location_distance_for_shared_walls in configuration --- hub/config/configuration.ini | 1 - hub/helpers/configuration_helper.py | 8 -------- 2 files changed, 9 deletions(-) diff --git a/hub/config/configuration.ini b/hub/config/configuration.ini index eb3331dc..4ae34882 100644 --- a/hub/config/configuration.ini +++ b/hub/config/configuration.ini @@ -1,6 +1,5 @@ # These values are intended as configurable assumptions [buildings] -max_location_distance_for_shared_walls = 5.0 min_coordinate = -1.7976931348623157e+308 max_coordinate = 1.7976931348623157e+308 comnet_lighting_latent = 0 diff --git a/hub/helpers/configuration_helper.py b/hub/helpers/configuration_helper.py index eec21eb7..936d46c7 100644 --- a/hub/helpers/configuration_helper.py +++ b/hub/helpers/configuration_helper.py @@ -17,14 +17,6 @@ class ConfigurationHelper: self._config = configparser.ConfigParser() self._config.read(config_file) - @property - def max_location_distance_for_shared_walls(self) -> float: - """ - Get configured maximal distance between attributes to consider that they may share walls in meters - :return: 5.0 - """ - return self._config.getfloat('buildings', 'max_location_distance_for_shared_walls') - @property def min_coordinate(self) -> float: """ -- 2.39.2 From d4fc6aa27d7dde82c6be5cdd4ad838cce20d32b5 Mon Sep 17 00:00:00 2001 From: p_monsalvete Date: Mon, 20 Mar 2023 11:40:20 -0400 Subject: [PATCH 11/13] re-introduced shared walls = 0 for no neighbours --- hub/imports/geometry/geojson.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/hub/imports/geometry/geojson.py b/hub/imports/geometry/geojson.py index 50c5583d..9105760b 100644 --- a/hub/imports/geometry/geojson.py +++ b/hub/imports/geometry/geojson.py @@ -133,8 +133,9 @@ class Geojson: @staticmethod def _find_wall(line_1, line_2): for i in range(0, 2): + j = 1 - i point_1 = line_1[i] - point_2 = line_2[i] + point_2 = line_2[j] distance = GeometryHelper.distance_between_points(point_1, point_2) if distance > 1e-2: return False @@ -143,6 +144,8 @@ class Geojson: def _store_shared_percentage_to_walls(self, city, city_mapped): for building in city.buildings: if building.name not in city_mapped.keys(): + for wall in building.walls: + wall.percentage_shared = 0 continue building_mapped = city_mapped[building.name] for wall in building.walls: @@ -151,12 +154,8 @@ class Geojson: for point in wall.perimeter_polygon.coordinates: if point[2] < 0.5: ground_line.append(point) - # todo: erase when we have no triangulation - if len(ground_line) < 2: - continue - # todo: erase down to here for entry in building_mapped: - if building_mapped[entry]['shared_points'] <= 5: + if building_mapped[entry]['shared_points'] <= 3: continue line = [building_mapped[entry]['line_start'], building_mapped[entry]['line_end']] neighbour_line = [building_mapped[entry]['neighbour_line_start'], -- 2.39.2 From bb10d888c30558b3c0ef3b8bffde14dc6ed84959 Mon Sep 17 00:00:00 2001 From: p_monsalvete Date: Mon, 20 Mar 2023 11:41:43 -0400 Subject: [PATCH 12/13] re-introduced shared walls = 0 for no neighbours --- hub/imports/geometry/geojson.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/hub/imports/geometry/geojson.py b/hub/imports/geometry/geojson.py index 9105760b..aa63e0e0 100644 --- a/hub/imports/geometry/geojson.py +++ b/hub/imports/geometry/geojson.py @@ -7,10 +7,8 @@ Project Coder Guillermo Gutierrez Guillermo.GutierrezMorote@concordia.ca import json import numpy as np -import trimesh.creation from pyproj import Transformer -from shapely.geometry import Polygon as ShapelyPolygon import hub.helpers.constants as cte from hub.helpers.geometry_helper import GeometryHelper -- 2.39.2 From 88e2cb679698f02931e7592c276a5a27fdc9a1fe Mon Sep 17 00:00:00 2001 From: p_monsalvete Date: Mon, 20 Mar 2023 14:15:57 -0400 Subject: [PATCH 13/13] introduced dhw to the meb exporter --- hub/city_model_structure/building.py | 17 ++ .../building_demand/thermal_zone.py | 3 + .../building_demand/usage_zone.py | 250 ------------------ .../results/insel_monthly_energry_balance.py | 1 + hub/imports/usage/comnet_usage_parameters.py | 12 +- hub/imports/usage/nrcan_usage_parameters.py | 16 +- hub/imports/usage/usage_helper.py | 43 --- hub/imports/weather/epw_weather_parameters.py | 18 +- hub/imports/weather/helpers/weather.py | 37 ++- 9 files changed, 86 insertions(+), 311 deletions(-) delete mode 100644 hub/city_model_structure/building_demand/usage_zone.py delete mode 100644 hub/imports/usage/usage_helper.py diff --git a/hub/city_model_structure/building.py b/hub/city_model_structure/building.py index d5129142..327cc0c7 100644 --- a/hub/city_model_structure/building.py +++ b/hub/city_model_structure/building.py @@ -38,6 +38,7 @@ class Building(CityObject): self._shell = None self._alias = None self._type = 'building' + self._cold_water_temperature = dict() self._heating = dict() self._cooling = dict() self._lighting_electrical_demand = dict() @@ -265,6 +266,22 @@ class Building(CityObject): if value is not None: self._storeys_above_ground = int(value) + @property + def cold_water_temperature(self) -> {float}: + """ + Get cold water temperature in degrees Celsius + :return: dict{DataFrame(float)} + """ + return self._cold_water_temperature + + @cold_water_temperature.setter + def cold_water_temperature(self, value): + """ + Set cold water temperature in degrees Celsius + :param value: dict{DataFrame(float)} + """ + self._cold_water_temperature = value + @property def heating(self) -> dict: """ diff --git a/hub/city_model_structure/building_demand/thermal_zone.py b/hub/city_model_structure/building_demand/thermal_zone.py index 667c0631..65b40b29 100644 --- a/hub/city_model_structure/building_demand/thermal_zone.py +++ b/hub/city_model_structure/building_demand/thermal_zone.py @@ -600,11 +600,14 @@ class ThermalZone: """ self._domestic_hot_water = DomesticHotWater() _mean_peak_density_load = 0 + _mean_peak_flow = 0 _mean_service_temperature = 0 for usage in self.usages: _mean_peak_density_load += usage.percentage * usage.domestic_hot_water.density + _mean_peak_flow += usage.percentage * usage.domestic_hot_water.peak_flow _mean_service_temperature += usage.percentage * usage.domestic_hot_water.service_temperature self._domestic_hot_water.density = _mean_peak_density_load + self._domestic_hot_water.peak_flow = _mean_peak_flow self._domestic_hot_water.service_temperature = _mean_service_temperature _domestic_hot_water_reference = self.usages[0].domestic_hot_water diff --git a/hub/city_model_structure/building_demand/usage_zone.py b/hub/city_model_structure/building_demand/usage_zone.py deleted file mode 100644 index 6357cff8..00000000 --- a/hub/city_model_structure/building_demand/usage_zone.py +++ /dev/null @@ -1,250 +0,0 @@ -""" -UsageZone module -SPDX - License - Identifier: LGPL - 3.0 - or -later -Copyright © 2022 Concordia CERC group -Project Coder Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca -Code contributors: Guille Gutierrez guillermo.gutierrezmorote@concordia.ca -""" -import uuid -from typing import Union, List -import hub.helpers.constants as cte -from hub.city_model_structure.building_demand.occupancy import Occupancy -from hub.city_model_structure.building_demand.lighting import Lighting -from hub.city_model_structure.building_demand.appliances import Appliances -from hub.city_model_structure.building_demand.thermal_control import ThermalControl -from hub.city_model_structure.building_demand.internal_gain import InternalGain - - -class UsageZone: - """ - UsageZone class - """ - def __init__(self): - self._id = None - self._usage = None - self._percentage = None - self._internal_gains = None - self._hours_day = None - self._days_year = None - self._mechanical_air_change = None - self._occupancy = None - self._lighting = None - self._appliances = None - self._thermal_control = None - - @property - def id(self): - """ - Get usage zone id, a universally unique identifier randomly generated - :return: str - """ - if self._id is None: - 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 internal_gains(self) -> List[InternalGain]: - """ - Calculates and returns the list of all internal gains defined - :return: InternalGains - """ - if self._internal_gains is None: - if self.occupancy is not None: - if self.occupancy.latent_internal_gain is not None: - _internal_gain = InternalGain() - _internal_gain.type = cte.OCCUPANCY - _total_heat_gain = (self.occupancy.sensible_convective_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 = 0 - _internal_gain.radiative_fraction = 0 - _internal_gain.convective_fraction = 0 - if _total_heat_gain != 0: - _internal_gain.latent_fraction = self.occupancy.latent_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.schedules = self.occupancy.occupancy_schedules - self._internal_gains = [_internal_gain] - if self.lighting is not None: - _internal_gain = InternalGain() - _internal_gain.type = cte.LIGHTING - _internal_gain.average_internal_gain = self.lighting.density - _internal_gain.latent_fraction = self.lighting.latent_fraction - _internal_gain.radiative_fraction = self.lighting.radiative_fraction - _internal_gain.convective_fraction = self.lighting.convective_fraction - _internal_gain.schedules = self.lighting.schedules - if self._internal_gains is not None: - self._internal_gains.append(_internal_gain) - else: - self._internal_gains = [_internal_gain] - if self.appliances is not None: - _internal_gain = InternalGain() - _internal_gain.type = cte.APPLIANCES - _internal_gain.average_internal_gain = self.appliances.density - _internal_gain.latent_fraction = self.appliances.latent_fraction - _internal_gain.radiative_fraction = self.appliances.radiative_fraction - _internal_gain.convective_fraction = self.appliances.convective_fraction - _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 - - @internal_gains.setter - def internal_gains(self, value): - """ - Set usage zone internal gains - :param value: [InternalGain] - """ - self._internal_gains = value - - @property - def hours_day(self) -> Union[None, float]: - """ - Get usage zone usage hours per day - :return: None or float - """ - return self._hours_day - - @hours_day.setter - def hours_day(self, value): - """ - Set usage zone usage hours per day - :param value: float - """ - if value is not None: - self._hours_day = float(value) - - @property - def days_year(self) -> Union[None, float]: - """ - Get usage zone usage days per year - :return: None or float - """ - return self._days_year - - @days_year.setter - def days_year(self, value): - """ - Set usage zone usage days per year - :param value: float - """ - if value is not None: - self._days_year = float(value) - - @property - def mechanical_air_change(self) -> Union[None, float]: - """ - Get usage zone mechanical air change in air change per hour (ACH) - :return: None or float - """ - return self._mechanical_air_change - - @mechanical_air_change.setter - def mechanical_air_change(self, value): - """ - Set usage zone mechanical air change in air change per hour (ACH) - :param value: float - """ - if value is not None: - self._mechanical_air_change = float(value) - - @property - def occupancy(self) -> Union[None, Occupancy]: - """ - Get occupancy in the usage zone - :return: None or Occupancy - """ - return self._occupancy - - @occupancy.setter - def occupancy(self, value): - """ - Set occupancy in the usage zone - :param value: Occupancy - """ - self._occupancy = value - - @property - def lighting(self) -> Union[None, Lighting]: - """ - Get lighting information - :return: None or Lighting - """ - return self._lighting - - @lighting.setter - def lighting(self, value): - """ - Set lighting information - :param value: Lighting - """ - self._lighting = value - - @property - def appliances(self) -> Union[None, Appliances]: - """ - Get appliances information - :return: None or Appliances - """ - return self._appliances - - @appliances.setter - def appliances(self, value): - """ - Set appliances information - :param value: Appliances - """ - self._appliances = value - - @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/hub/imports/results/insel_monthly_energry_balance.py b/hub/imports/results/insel_monthly_energry_balance.py index 50b5f34d..3ded75d2 100644 --- a/hub/imports/results/insel_monthly_energry_balance.py +++ b/hub/imports/results/insel_monthly_energry_balance.py @@ -10,6 +10,7 @@ import pandas as pd import csv import hub.helpers.constants as cte + class InselMonthlyEnergyBalance: """ Import SRA results diff --git a/hub/imports/usage/comnet_usage_parameters.py b/hub/imports/usage/comnet_usage_parameters.py index 9e1836d2..163bd3fe 100644 --- a/hub/imports/usage/comnet_usage_parameters.py +++ b/hub/imports/usage/comnet_usage_parameters.py @@ -8,6 +8,7 @@ import copy import sys import numpy +from hub.hub_logger import logger import hub.helpers.constants as cte from hub.helpers.dictionaries import Dictionaries from hub.city_model_structure.building_demand.usage import Usage @@ -19,7 +20,6 @@ from hub.city_model_structure.building_demand.domestic_hot_water import Domestic from hub.city_model_structure.attributes.schedule import Schedule from hub.city_model_structure.building_demand.internal_gain import InternalGain from hub.catalog_factories.usage_catalog_factory import UsageCatalogFactory -from hub.imports.usage.usage_helper import UsageHelper class ComnetUsageParameters: @@ -42,8 +42,8 @@ class ComnetUsageParameters: try: archetype_usage = self._search_archetypes(comnet_catalog, usage_name) except KeyError: - sys.stderr.write(f'Building {building.name} has unknown usage archetype for building function:' - f' {building.function}') + logger.error(f'Building {building.name} has unknown usage archetype for usage: {usage_name}') + sys.stderr.write(f'Building {building.name} has unknown usage archetype for usage: {usage_name}') continue for internal_zone in building.internal_zones: @@ -56,7 +56,7 @@ class ComnetUsageParameters: volume_per_area = internal_zone.volume / internal_zone.area usage = Usage() usage.name = usage_name - self._assign_values(usage, archetype_usage, volume_per_area, building.ground_temperature) + self._assign_values(usage, archetype_usage, volume_per_area, building.cold_water_temperature) usage.percentage = 1 self._calculate_reduced_values_from_extended_library(usage, archetype_usage) @@ -71,7 +71,7 @@ class ComnetUsageParameters: raise KeyError('archetype not found') @staticmethod - def _assign_values(usage, archetype, volume_per_area, ground_temperature): + def _assign_values(usage, archetype, volume_per_area, cold_water_temperature): # Due to the fact that python is not a typed language, the wrong object type is assigned to # usage.occupancy when writing usage.occupancy = archetype.occupancy. # Same happens for lighting and appliances. Therefore, this walk around has been done. @@ -106,7 +106,7 @@ class ComnetUsageParameters: _domestic_hot_water = DomesticHotWater() _domestic_hot_water.density = archetype.domestic_hot_water.density _domestic_hot_water.service_temperature = archetype.domestic_hot_water.service_temperature - cold_temperature = UsageHelper().cold_water_temperature(ground_temperature) + cold_temperature = cold_water_temperature[cte.YEAR]['epw'] peak_flow = 0 if (archetype.domestic_hot_water.service_temperature - cold_temperature) > 0: peak_flow = archetype.domestic_hot_water.density / cte.WATER_DENSITY / cte.WATER_HEAT_CAPACITY \ diff --git a/hub/imports/usage/nrcan_usage_parameters.py b/hub/imports/usage/nrcan_usage_parameters.py index ff008709..67a5e380 100644 --- a/hub/imports/usage/nrcan_usage_parameters.py +++ b/hub/imports/usage/nrcan_usage_parameters.py @@ -7,6 +7,7 @@ Project Coder Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca import sys +from hub.hub_logger import logger import hub.helpers.constants as cte from hub.helpers.dictionaries import Dictionaries from hub.city_model_structure.building_demand.usage import Usage @@ -16,7 +17,6 @@ from hub.city_model_structure.building_demand.appliances import Appliances from hub.city_model_structure.building_demand.thermal_control import ThermalControl from hub.city_model_structure.building_demand.domestic_hot_water import DomesticHotWater from hub.catalog_factories.usage_catalog_factory import UsageCatalogFactory -from hub.imports.usage.usage_helper import UsageHelper class NrcanUsageParameters: @@ -41,16 +41,16 @@ class NrcanUsageParameters: try: archetype_usage = self._search_archetypes(nrcan_catalog, usage_name) except KeyError: - sys.stderr.write(f'Building {building.name} has unknown usage archetype for building function:' - f' {building.function}') + logger.error(f'Building {building.name} has unknown usage archetype for usage: {usage_name}\n') + sys.stderr.write(f'Building {building.name} has unknown usage archetype for usage: {usage_name}\n') continue usage_name = Dictionaries().hub_usage_to_comnet_usage[building.function] try: comnet_archetype_usage = self._search_archetypes(comnet_catalog, usage_name) except KeyError: - sys.stderr.write(f'Building {building.name} has unknown usage archetype for building function:' - f' {building.function}') + logger.error(f'Building {building.name} has unknown usage archetype for usage: {usage_name}\n') + sys.stderr.write(f'Building {building.name} has unknown usage archetype for usage: {usage_name}\n') continue for internal_zone in building.internal_zones: @@ -63,7 +63,7 @@ class NrcanUsageParameters: volume_per_area = internal_zone.volume / internal_zone.area usage = Usage() usage.name = usage_name - self._assign_values(usage, archetype_usage, volume_per_area, building.ground_temperature) + self._assign_values(usage, archetype_usage, volume_per_area, building.cold_water_temperature) self._assign_comnet_extra_values(usage, comnet_archetype_usage) usage.percentage = 1 self._calculate_reduced_values_from_extended_library(usage, archetype_usage) @@ -79,7 +79,7 @@ class NrcanUsageParameters: raise KeyError('archetype not found') @staticmethod - def _assign_values(usage, archetype, volume_per_area, ground_temperature): + def _assign_values(usage, archetype, volume_per_area, cold_water_temperature): if archetype.mechanical_air_change > 0: usage.mechanical_air_change = archetype.mechanical_air_change elif archetype.ventilation_rate > 0: @@ -116,7 +116,7 @@ class NrcanUsageParameters: _domestic_hot_water = DomesticHotWater() _domestic_hot_water.peak_flow = archetype.domestic_hot_water.peak_flow _domestic_hot_water.service_temperature = archetype.domestic_hot_water.service_temperature - cold_temperature = UsageHelper().cold_water_temperature(ground_temperature) + cold_temperature = cold_water_temperature[cte.YEAR]['epw'] _domestic_hot_water.density = archetype.domestic_hot_water.peak_flow * cte.WATER_DENSITY * cte.WATER_HEAT_CAPACITY \ * (archetype.domestic_hot_water.service_temperature - cold_temperature) _domestic_hot_water.schedules = archetype.domestic_hot_water.schedules diff --git a/hub/imports/usage/usage_helper.py b/hub/imports/usage/usage_helper.py deleted file mode 100644 index 2d6ac514..00000000 --- a/hub/imports/usage/usage_helper.py +++ /dev/null @@ -1,43 +0,0 @@ -""" -Usage helper -SPDX - License - Identifier: LGPL - 3.0 - or -later -Copyright © 2023 Concordia CERC group -Project Coder Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca -""" - -import sys -from hub.hub_logger import logger -import hub.helpers.constants as cte -from hub.helpers.configuration_helper import ConfigurationHelper as ch - - -class UsageHelper: - """ - UsageHelper class - """ - - @staticmethod - def cold_water_temperature(ground_temperature): - keys = ground_temperature.keys() - depths = ground_temperature[keys[0]].keys() - if keys[0] == cte.YEAR: - # taking the deeper temperature available - _cold_temperature = ground_temperature[cte.YEAR][depths[len(depths-1)]] - elif keys[0] == cte.MONTH: - _cold_temperature = 0 - for i in range(0, 12): - # taking the deeper temperature available - _cold_temperature += ground_temperature[cte.MONTH][depths[len(depths-1)]] / 12 - elif keys[0] == cte.HOUR: - _cold_temperature = 0 - for i in range(0, 8760): - # taking the deeper temperature available - _cold_temperature += ground_temperature[cte.HOUR][depths[len(depths-1)]] / 8760 - else: - _cold_temperature = ch().cold_water_temperature - logger.error(f'Cold water temperature could not be calculated. Assigned default value = ' - f'{ch().cold_water_temperature} degrees Celsius\n') - sys.stderr.write(f'Cold water temperature could not be calculated. Assigned default value = ' - f'{ch().cold_water_temperature} degrees Celsius\n') - - return _cold_temperature diff --git a/hub/imports/weather/epw_weather_parameters.py b/hub/imports/weather/epw_weather_parameters.py index 2b53bc24..89315ab0 100644 --- a/hub/imports/weather/epw_weather_parameters.py +++ b/hub/imports/weather/epw_weather_parameters.py @@ -111,10 +111,24 @@ class EpwWeatherParameters: building.beam[cte.HOUR] = new_value else: pd.concat([building.beam[cte.HOUR], new_value], axis=1) + + new_value = wh().cold_water_temperature(building.external_temperature[cte.HOUR]['epw']) + if cte.HOUR not in building.cold_water_temperature: + building.cold_water_temperature[cte.HOUR] = new_value + else: + pd.concat([building.cold_water_temperature[cte.HOUR], new_value], axis=1) # create the monthly and yearly values out of the hourly for building in self._city.buildings: if cte.MONTH not in building.external_temperature: - building.external_temperature[cte.MONTH] = wh().get_monthly_mean_values(building.external_temperature[cte.HOUR][['epw']]) + building.external_temperature[cte.MONTH] = \ + wh().get_monthly_mean_values(building.external_temperature[cte.HOUR][['epw']]) if cte.YEAR not in building.external_temperature: - building.external_temperature[cte.YEAR] = wh(). get_yearly_mean_values(building.external_temperature[cte.HOUR][['epw']]) + building.external_temperature[cte.YEAR] = \ + wh(). get_yearly_mean_values(building.external_temperature[cte.HOUR][['epw']]) + if cte.MONTH not in building.cold_water_temperature: + building.cold_water_temperature[cte.MONTH] = wh().get_monthly_mean_values( + building.cold_water_temperature[cte.HOUR][['epw']]) + if cte.YEAR not in building.cold_water_temperature: + building.cold_water_temperature[cte.YEAR] = wh().get_yearly_mean_values( + building.cold_water_temperature[cte.HOUR][['epw']]) self._city.level_of_detail.weather = 2 diff --git a/hub/imports/weather/helpers/weather.py b/hub/imports/weather/helpers/weather.py index ae4c9ad2..f595f41e 100644 --- a/hub/imports/weather/helpers/weather.py +++ b/hub/imports/weather/helpers/weather.py @@ -4,6 +4,7 @@ SPDX - License - Identifier: LGPL - 3.0 - or -later Copyright © 2022 Concordia CERC group Project Coder Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca """ + import math import hub.helpers.constants as cte import pandas as pd @@ -20,7 +21,7 @@ class Weather: def sky_temperature(ambient_temperature): """ Get sky temperature from ambient temperature in Celsius - :return: float + :return: List[float] """ # Swinbank - Source sky model approximation(1963) based on cloudiness statistics(32 %) in United States # ambient temperatures( in °C) @@ -32,6 +33,37 @@ class Weather: values.append(value) return values + @staticmethod + def cold_water_temperature(ambient_temperature): + """ + Get cold water temperature from ambient temperature in Celsius + :return: dict + """ + # Equation from "TOWARDS DEVELOPMENT OF AN ALGORITHM FOR MAINS WATER TEMPERATURE", 2004, Jay Burch + # and Craig Christensen, National Renewable Energy Laboratory + # ambient temperatures( in °C) + # cold water temperatures( in °C) + ambient_temperature_fahrenheit = [] + average_temperature = 0 + maximum_temperature = -1000 + minimum_temperature = 1000 + for temperature in ambient_temperature: + value = temperature * 9 / 5 + 32 + ambient_temperature_fahrenheit.append(value) + average_temperature += value / 8760 + if value > maximum_temperature: + maximum_temperature = value + if value < minimum_temperature: + minimum_temperature = value + delta_temperature = maximum_temperature - minimum_temperature + ratio = 0.4 + 0.01 * (average_temperature - 44) + lag = 35 - 1 * (average_temperature - 44) + cold_temperature = [] + for temperature in ambient_temperature_fahrenheit: + radians = (0.986 * (temperature-15-lag) - 90) * math.pi / 180 + cold_temperature.append((average_temperature + 6 + ratio * (delta_temperature/2) * math.sin(radians) - 32) * 5/9) + return pd.DataFrame(cold_temperature, columns=['epw']) + def get_monthly_mean_values(self, values): out = None if values is not None: @@ -41,7 +73,8 @@ class Weather: del out['month'] return out - def get_yearly_mean_values(self, values): + @staticmethod + def get_yearly_mean_values(values): return values.mean() def get_total_month(self, values): -- 2.39.2